Files
frontend/app/pages/notifications.vue
T
reloia 47327d3a69 feat: implement citizen report creation, detail view, and chat system
- Create report creation page with step-by-step media uploads and categories loading
- Create report details view page with history events log and messaging section
- Implement MessageComposer and MessageList components for chat workflow
- Implement MediaUploader with drag-and-drop support, file type, and size validation
- Implement Pagination component with dynamic buttons and ellipsis support
- Implement user profile settings summary page and notifications listing page
2026-06-12 19:04:19 +02:00

228 lines
7.2 KiB
Vue

<script setup lang="ts">
definePageMeta({
middleware: 'auth',
})
useHead({ title: 'Notifiche - Visione' })
const { fetchNotifications, markNotificationRead } = useAuth()
const router = useRouter()
const page = ref(1)
const perPage = ref(15)
const { data: notificationsData, refresh: refreshNotifications } = await useAsyncData(
'notifications',
() => fetchNotifications({ page: page.value, per_page: perPage.value })
)
const notifications = computed(() => notificationsData.value?.notifications || [])
const totalNotifications = computed(() => notificationsData.value?.notifications?.length || 0)
const unreadCount = computed(() => notifications.value.filter((n: any) => !n.read_at).length)
const handlePageChange = (newPage: number) => {
page.value = newPage
}
const handleMarkAsRead = async (notificationId: number) => {
try {
await markNotificationRead(notificationId)
await refreshNotifications()
} catch (error) {
console.error('Error marking notification as read:', error)
}
}
const handleNotificationClick = async (notification: any) => {
if (!notification.read_at) {
await handleMarkAsRead(notification.id)
}
if (notification.report_id) {
router.push(`/reports/${notification.report_id}`)
}
}
const formatDate = (dateString: string): string => {
const date = new Date(dateString)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffMins = Math.floor(diffMs / 60000)
const diffHours = Math.floor(diffMins / 60)
const diffDays = Math.floor(diffHours / 24)
if (diffMins < 1) return 'Proprio ora'
if (diffMins < 60) return `${diffMins}m fa`
if (diffHours < 24) return `${diffHours}h fa`
if (diffDays < 7) return `${diffDays}d fa`
return date.toLocaleDateString('it-IT', {
day: '2-digit',
month: '2-digit',
year: '2-digit'
})
}
const getNotificationIcon = (type: string): string => {
const icons: Record<string, string> = {
'report_created': 'it-pencil',
'report_assigned': 'it-check-circle',
'report_updated': 'it-refresh',
'report_resolved': 'it-check-circle',
'message_received': 'it-comment',
'feedback_requested': 'it-star',
'reward_earned': 'it-star',
}
return icons[type] || 'it-bell'
}
const getNotificationColor = (type: string): string => {
const colors: Record<string, string> = {
'report_created': 'primary',
'report_assigned': 'info',
'report_updated': 'info',
'report_resolved': 'success',
'message_received': 'primary',
'feedback_requested': 'warning',
'reward_earned': 'warning',
}
return colors[type] || 'secondary'
}
</script>
<template>
<div>
<!-- HERO -->
<div class="bg-light border-bottom py-4 mb-4">
<div class="container">
<div class="row">
<div class="col-12">
<h1 class="h3 font-weight-bold mb-2">
<BIIcon name="it-bell" size="sm" color="primary" class="me-2" />
Centro Notifiche
</h1>
<p class="text-muted mb-0">
{{ unreadCount > 0 ? `Hai ${unreadCount} notifiche non lette` : 'Tutte le notifiche lette' }}
</p>
</div>
</div>
</div>
</div>
<!-- CONTENUTO -->
<div class="container mb-5">
<div class="row">
<div class="col-12 col-lg-10 offset-lg-1">
<!-- FILTRI (Opzionale) -->
<div class="d-flex gap-2 mb-4">
<button class="btn btn-sm btn-outline-primary active">Tutti</button>
<button class="btn btn-sm btn-outline-secondary">Solo non letti</button>
</div>
<!-- LISTA NOTIFICHE -->
<div v-if="notifications.length === 0" class="text-center py-5">
<BIIcon name="it-bell" size="xl" color="info" class="mb-3 d-block" />
<h6 class="text-muted">Nessuna notifica</h6>
<p class="small text-muted">Quando riceverai nuovi aggiornamenti sulle tue segnalazioni, appariranno qui.</p>
</div>
<div v-else class="list-group">
<button
v-for="notification in notifications"
:key="notification.id"
type="button"
class="list-group-item list-group-item-action border-0 p-3 mb-3 rounded-2"
:class="{
'bg-light': !notification.read_at,
'bg-white': notification.read_at,
}"
@click="handleNotificationClick(notification)"
>
<div class="d-flex align-items-start gap-3">
<!-- ICONA -->
<div class="flex-shrink-0 mt-1">
<div class="rounded-circle p-2" :class="`bg-${getNotificationColor(notification.type)}`" style="width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">
<BIIcon :name="getNotificationIcon(notification.type)" size="sm" color="white" />
</div>
</div>
<!-- CONTENUTO -->
<div class="flex-grow-1">
<div class="d-flex align-items-start justify-content-between">
<div>
<h6 class="mb-1 font-weight-bold" :class="{ 'text-dark': !notification.read_at }">
{{ notification.title }}
</h6>
<p class="mb-1 small" :class="{ 'text-muted': notification.read_at }">
{{ notification.body }}
</p>
<small class="text-muted">
{{ formatDate(notification.created_at) }}
</small>
</div>
<!-- INDICATORE NON LETTO -->
<div v-if="!notification.read_at" class="flex-shrink-0">
<span class="badge bg-primary rounded-pill">Nuovo</span>
</div>
</div>
<!-- AZIONI RAPIDE -->
<div v-if="notification.report_id" class="mt-2 d-flex gap-2">
<NuxtLink :to="`/reports/${notification.report_id}`" class="btn btn-sm btn-outline-primary">
<BIIcon name="it-arrow-right" size="xs" class="me-1" />
Vedi segnalazione
</NuxtLink>
<button
v-if="!notification.read_at"
type="button"
class="btn btn-sm btn-outline-secondary"
@click.stop="handleMarkAsRead(notification.id)"
>
Segna come letta
</button>
</div>
</div>
</div>
</button>
</div>
<!-- PAGINATION -->
<Pagination
v-if="notifications.length > 0"
:current-page="page"
:per-page="perPage"
:total-items="totalNotifications"
@page-change="handlePageChange"
/>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.list-group-item {
border: 1px solid #e9ecef;
transition: all 0.2s ease;
cursor: pointer;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
&.bg-light {
border-left: 4px solid #006f9d;
}
}
.rounded-2 {
border-radius: 0.5rem;
}
</style>