From 2a8cbf42993eea076b46cb7b6001815f90417c03 Mon Sep 17 00:00:00 2001
From: gabdevele
Date: Thu, 18 Jun 2026 13:51:54 +0200
Subject: [PATCH] feat: add unread notifications feature and enhance
notification display
---
app/components/Header.vue | 24 ++-
app/components/dashboard/AdminDashboard.vue | 128 +++++----------
app/composables/useAuth.ts | 26 +++
app/pages/admin/reports.vue | 117 ++++----------
app/pages/notifications.vue | 166 ++++++++++----------
5 files changed, 196 insertions(+), 265 deletions(-)
diff --git a/app/components/Header.vue b/app/components/Header.vue
index 3b07a04..7440050 100644
--- a/app/components/Header.vue
+++ b/app/components/Header.vue
@@ -26,7 +26,7 @@ onUnmounted(() => {
}
})
-const { isAuthenticated, user, logout } = useAuth()
+const { isAuthenticated, user, logout, hasUnreadNotifications } = useAuth()
const panelLabel = computed(() => isAdmin(user.value?.role) ? 'Pannello' : 'Dashboard')
const navLinks = [
@@ -70,8 +70,9 @@ const navLinks = [
-
+
+
{{ user?.first_name }}
@@ -215,7 +216,7 @@ const navLinks = [
visibility: visible;
}
- .navbar-collapsable {
+.navbar-collapsable {
position: fixed !important;
top: 0;
left: 0;
@@ -294,4 +295,19 @@ const navLinks = [
height: 48px;
margin-inline: 16px;
}
-
\ No newline at end of file
+
+.profile-icon-wrap {
+ position: relative;
+}
+
+.notification-dot {
+ position: absolute;
+ top: -2px;
+ right: -2px;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: #d9364f;
+ border: 2px solid #ffffff;
+}
+
diff --git a/app/components/dashboard/AdminDashboard.vue b/app/components/dashboard/AdminDashboard.vue
index f7bd20c..6f15461 100644
--- a/app/components/dashboard/AdminDashboard.vue
+++ b/app/components/dashboard/AdminDashboard.vue
@@ -4,36 +4,11 @@ const { getOverviewStats } = useAdmin()
const { data: statsRes, pending } = useAsyncData('admin-stats', () => getOverviewStats())
const adminActions = [
- {
- to: '/admin/reports',
- icon: 'it-files',
- label: 'Report',
- tone: 'primary'
- },
- {
- to: '/admin/catalog',
- icon: 'it-list',
- label: 'Categorie e Team',
- tone: 'primary'
- },
- {
- to: '/admin/areas',
- icon: 'it-map-marker',
- label: 'Aree',
- tone: 'primary'
- },
- {
- to: '/admin/users',
- icon: 'it-user',
- label: 'Gestione Utenti',
- tone: 'primary'
- },
- {
- to: '/admin/moderation',
- icon: 'it-warning',
- label: 'Moderazione Abusi',
- tone: 'danger'
- }
+ { to: '/admin/reports', icon: 'it-files', label: 'Report', tone: 'primary' },
+ { to: '/admin/catalog', icon: 'it-list', label: 'Categorie e Team', tone: 'primary' },
+ { to: '/admin/areas', icon: 'it-map-marker', label: 'Aree', tone: 'primary' },
+ { to: '/admin/users', icon: 'it-user', label: 'Gestione Utenti', tone: 'primary' },
+ { to: '/admin/moderation', icon: 'it-warning', label: 'Moderazione Abusi', tone: 'danger' }
]
@@ -46,78 +21,59 @@ const adminActions = [
-
-
-
-
-
-
{{ statsRes.total_users || 0 }}
-
Utenti Registrati
-
+
+
+
+
+
{{ statsRes.total_users || 0 }}
+
Utenti registrati
-
-
-
-
-
- {{ statsRes.open_reports ?? statsRes.reports_by_status?.find(s => s.status === 'OPEN')?.total ?? 0 }}
-
-
Segn. Aperte
+
+
+
+
+ {{ statsRes.open_reports ?? statsRes.reports_by_status?.find(s => s.status === 'OPEN')?.total ?? 0 }}
+
Segnalazioni aperte
-
Gestione Sistema
+
+
+
+
Gestione sistema
+ {{ adminActions.length }} sezioni
+
-
-
-
-
-
- {{ action.label }}
-
-
+
+
+
+
+ {{ action.label }}
+
+
+
diff --git a/app/composables/useAuth.ts b/app/composables/useAuth.ts
index 9b38c9d..076f1d9 100644
--- a/app/composables/useAuth.ts
+++ b/app/composables/useAuth.ts
@@ -29,7 +29,10 @@ export const useAuth = () => {
type UserContext = CurrentUserResponse['user']
const user = useState
('auth_user', () => null)
const isSyncingUser = useState('auth_syncing', () => false)
+ const unreadNotifications = useState('auth_unread_notifications', () => 0)
+ const isRefreshingNotifications = useState('auth_refreshing_notifications', () => false)
const isAuthenticated = computed(() => !!(tokenState.value || token.value))
+ const hasUnreadNotifications = computed(() => unreadNotifications.value > 0)
const isAuthStatusError = (error: any) => {
const status = error?.statusCode || error?.status || error?.response?.status
return status === 401 || status === 403
@@ -39,6 +42,24 @@ export const useAuth = () => {
tokenState.value = newVal || null
})
+ const refreshUnreadNotifications = async () => {
+ if (!(tokenState.value || token.value) || !user.value || isRefreshingNotifications.value) {
+ unreadNotifications.value = user.value ? unreadNotifications.value : 0
+ return
+ }
+
+ isRefreshingNotifications.value = true
+
+ try {
+ const res = await fetchNotifications({ page: 1, per_page: 20 })
+ unreadNotifications.value = (res.notifications || []).filter(notification => !notification.read_at).length
+ } catch (error) {
+ console.error('[Auth] Failed to refresh notifications:', error)
+ } finally {
+ isRefreshingNotifications.value = false
+ }
+ }
+
const syncUser = async (options: { redirectOnSuccess?: boolean; currentPath?: string } = {}) => {
const { redirectOnSuccess = false, currentPath } = options
const tokenVal = tokenState.value || token.value
@@ -49,6 +70,7 @@ export const useAuth = () => {
const res = await fetchCurrentUser()
if (res.ok) {
user.value = res.user
+ await refreshUnreadNotifications()
const path = currentPath ?? useNuxtApp().$router.currentRoute.value.path
if (redirectOnSuccess && path.startsWith('/auth')) {
@@ -79,6 +101,7 @@ export const useAuth = () => {
token.value = null
tokenState.value = null
user.value = null
+ unreadNotifications.value = 0
nuxtApp.runWithContext(() => navigateTo('/auth/login'))
}
@@ -121,6 +144,9 @@ export const useAuth = () => {
tokenState,
isSyncingUser,
isAuthenticated,
+ unreadNotifications,
+ hasUnreadNotifications,
+ refreshUnreadNotifications,
handleLoginSuccess,
syncUser,
logout,
diff --git a/app/pages/admin/reports.vue b/app/pages/admin/reports.vue
index 47c1e31..f4e63bb 100644
--- a/app/pages/admin/reports.vue
+++ b/app/pages/admin/reports.vue
@@ -94,7 +94,7 @@ const truncate = (value?: string, max = 120) => {
-
+
Overview Segnalazioni
@@ -110,14 +110,16 @@ const truncate = (value?: string, max = 120) => {
{ label: 'In lavorazione', value: activeReports, tone: 'warning' },
{ label: 'Chiuse/Risolte', value: closedReports, tone: 'success' }
]" :key="card.label">
-
-
{{ card.label }}
-
{{ card.value }}
+
+
+
{{ card.value }}
+
{{ card.label }}
+
-
+
@@ -154,17 +156,31 @@ const truncate = (value?: string, max = 120) => {
-
+
Distribuzione stati
{{ filteredReports.length }} elementi visibili
-
-
- {{ item.label }}
- {{ item.total }}
-
+
+
+
+
+ | Stato |
+ Totale |
+
+
+
+
+ |
+
+ {{ item.label }}
+
+ |
+ {{ item.total }} |
+
+
+
@@ -173,7 +189,7 @@ const truncate = (value?: string, max = 120) => {
Nessuna segnalazione trovata con filtri correnti.
-
+
@@ -198,7 +214,7 @@ const truncate = (value?: string, max = 120) => {
| {{ report.address || '-' }} |
{{ report.area_name || 'Non assegnata' }} |
-
+
{{ statusMeta[report.status].label }}
|
@@ -213,78 +229,3 @@ const truncate = (value?: string, max = 120) => {
-
-
diff --git a/app/pages/notifications.vue b/app/pages/notifications.vue
index 19d3e41..ad3f997 100644
--- a/app/pages/notifications.vue
+++ b/app/pages/notifications.vue
@@ -1,11 +1,13 @@