- aggiornamento di tante cose...
- generazione Volantini - pagina RIS
This commit is contained in:
307
src/components/Dashboard/RecentPosters/RecentPosters.vue
Normal file
307
src/components/Dashboard/RecentPosters/RecentPosters.vue
Normal file
@@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<div class="recent-posters">
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="posters-grid">
|
||||
<div v-for="i in 6" :key="i" class="poster-skeleton">
|
||||
<q-skeleton type="rect" class="skeleton-image" />
|
||||
<div class="skeleton-content">
|
||||
<q-skeleton type="text" width="70%" />
|
||||
<q-skeleton type="text" width="40%" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="posters.length === 0" class="empty-state">
|
||||
<q-icon name="image" size="64px" color="grey-4" />
|
||||
<h3>Nessuna locandina</h3>
|
||||
<p>Crea la tua prima locandina per vederla qui</p>
|
||||
<q-btn
|
||||
color="primary"
|
||||
icon="add"
|
||||
label="Crea Locandina"
|
||||
@click="$router.push('/posters/poster-generator')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Posters Grid -->
|
||||
<div v-else class="posters-grid">
|
||||
<div
|
||||
v-for="poster in posters"
|
||||
:key="poster._id"
|
||||
class="poster-card"
|
||||
>
|
||||
<div class="poster-image" @click="$emit('view', poster)">
|
||||
<img
|
||||
:src="poster.renderOutput?.png?.url || poster.renderOutput?.jpg?.url || '/placeholder.png'"
|
||||
:alt="poster.name"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="poster-overlay">
|
||||
<q-btn round color="white" text-color="dark" icon="visibility" size="sm" />
|
||||
</div>
|
||||
|
||||
<q-badge
|
||||
v-if="poster.metadata?.isFavorite"
|
||||
color="amber"
|
||||
floating
|
||||
class="favorite-badge"
|
||||
>
|
||||
<q-icon name="star" size="12px" />
|
||||
</q-badge>
|
||||
</div>
|
||||
|
||||
<div class="poster-info">
|
||||
<h3 :title="poster.name">{{ poster.name }}</h3>
|
||||
<p class="poster-date">
|
||||
<q-icon name="schedule" size="14px" />
|
||||
{{ formatDate(poster.createdAt) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="poster-actions">
|
||||
<q-btn flat dense round icon="download" size="sm" @click="$emit('download', poster)">
|
||||
<q-tooltip>Scarica</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn flat dense round icon="edit" size="sm" @click="$emit('edit', poster)">
|
||||
<q-tooltip>Modifica</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn flat dense round icon="more_vert" size="sm">
|
||||
<q-menu>
|
||||
<q-list dense>
|
||||
<q-item clickable v-close-popup @click="$emit('view', poster)">
|
||||
<q-item-section avatar><q-icon name="visibility" size="20px" /></q-item-section>
|
||||
<q-item-section>Visualizza</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="sharePoster(poster)">
|
||||
<q-item-section avatar><q-icon name="share" size="20px" /></q-item-section>
|
||||
<q-item-section>Condividi</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
<q-item clickable v-close-popup @click="deletePoster(poster)" class="text-negative">
|
||||
<q-item-section avatar><q-icon name="delete" size="20px" /></q-item-section>
|
||||
<q-item-section>Elimina</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
const props = defineProps<{
|
||||
posters: any[];
|
||||
isLoading: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'view', poster: any): void;
|
||||
(e: 'download', poster: any): void;
|
||||
(e: 'edit', poster: any): void;
|
||||
}>();
|
||||
|
||||
const $q = useQuasar();
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(diff / 3600000);
|
||||
const days = Math.floor(diff / 86400000);
|
||||
|
||||
if (minutes < 60) return `${minutes} min fa`;
|
||||
if (hours < 24) return `${hours} ore fa`;
|
||||
if (days < 7) return `${days} giorni fa`;
|
||||
|
||||
return date.toLocaleDateString('it-IT', {
|
||||
day: 'numeric',
|
||||
month: 'short'
|
||||
});
|
||||
};
|
||||
|
||||
const sharePoster = async (poster: any) => {
|
||||
try {
|
||||
if (navigator.share) {
|
||||
await navigator.share({
|
||||
title: poster.name,
|
||||
text: `Guarda la mia locandina: ${poster.content?.title || poster.name}`,
|
||||
url: window.location.origin + `/posters/${poster._id}`
|
||||
});
|
||||
} else {
|
||||
await navigator.clipboard.writeText(window.location.origin + `/posters/${poster._id}`);
|
||||
$q.notify({ type: 'positive', message: 'Link copiato!' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Share error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const deletePoster = (poster: any) => {
|
||||
$q.dialog({
|
||||
title: 'Elimina Locandina',
|
||||
message: `Vuoi eliminare "${poster.name}"?`,
|
||||
cancel: true
|
||||
}).onOk(() => {
|
||||
$q.notify({ type: 'info', message: 'Eliminazione...' });
|
||||
// Implement delete
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.recent-posters {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.posters-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1.25rem;
|
||||
|
||||
@media (max-width: 900px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.poster-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.poster-image {
|
||||
position: relative;
|
||||
aspect-ratio: 3/4;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.poster-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.favorite-badge {
|
||||
top: 0.5rem !important;
|
||||
right: 0.5rem !important;
|
||||
}
|
||||
|
||||
.poster-info {
|
||||
padding: 1rem;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.poster-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin: 0.35rem 0 0;
|
||||
font-size: 0.8rem;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 0 0.5rem 0.75rem;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
.poster-skeleton {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
.skeleton-image {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.skeleton-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Empty State
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
|
||||
h3 {
|
||||
margin: 1rem 0 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #888;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode
|
||||
.body--dark {
|
||||
.poster-card,
|
||||
.poster-skeleton,
|
||||
.empty-state {
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
.poster-info h3 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user