- 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
228 lines
7.2 KiB
Vue
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>
|
|
|