Răsfoiți Sursa

Dashboard improvements, predicted maintenance, and reminders page (#40)

* added tooltip for predicted milage

* predicated maintaince card

* dashboard structure and filters

* reminder page
Bernt Christian Egeland 6 luni în urmă
părinte
comite
b1d6110a4f
51 a modificat fișierele cu 1240 adăugiri și 177 ștergeri
  1. 19 7
      messages/de/dashboard.json
  2. 4 1
      messages/de/navigation.json
  3. 11 0
      messages/de/reminders.json
  4. 1 0
      messages/de/vehicles.json
  5. 19 7
      messages/en/dashboard.json
  6. 4 1
      messages/en/navigation.json
  7. 11 0
      messages/en/reminders.json
  8. 1 0
      messages/en/vehicles.json
  9. 19 7
      messages/es/dashboard.json
  10. 4 1
      messages/es/navigation.json
  11. 11 0
      messages/es/reminders.json
  12. 1 0
      messages/es/vehicles.json
  13. 19 7
      messages/fr/dashboard.json
  14. 4 1
      messages/fr/navigation.json
  15. 11 0
      messages/fr/reminders.json
  16. 1 0
      messages/fr/vehicles.json
  17. 19 7
      messages/it/dashboard.json
  18. 4 1
      messages/it/navigation.json
  19. 11 0
      messages/it/reminders.json
  20. 1 0
      messages/it/vehicles.json
  21. 19 7
      messages/nb/dashboard.json
  22. 4 1
      messages/nb/navigation.json
  23. 11 0
      messages/nb/reminders.json
  24. 1 0
      messages/nb/vehicles.json
  25. 19 7
      messages/nl/dashboard.json
  26. 4 1
      messages/nl/navigation.json
  27. 11 0
      messages/nl/reminders.json
  28. 1 0
      messages/nl/vehicles.json
  29. 19 7
      messages/pl/dashboard.json
  30. 4 1
      messages/pl/navigation.json
  31. 11 0
      messages/pl/reminders.json
  32. 1 0
      messages/pl/vehicles.json
  33. 19 7
      messages/pt-BR/dashboard.json
  34. 4 1
      messages/pt-BR/navigation.json
  35. 11 0
      messages/pt-BR/reminders.json
  36. 1 0
      messages/pt-BR/vehicles.json
  37. 19 7
      messages/tr/dashboard.json
  38. 4 1
      messages/tr/navigation.json
  39. 11 0
      messages/tr/reminders.json
  40. 1 0
      messages/tr/vehicles.json
  41. 246 81
      src/app/(authenticated)/dashboard-client.tsx
  42. 4 2
      src/app/(authenticated)/page.tsx
  43. 53 0
      src/app/(authenticated)/reminders/page.tsx
  44. 22 10
      src/app/(authenticated)/vehicles/[id]/vehicle-detail-client.tsx
  45. 2 0
      src/components/app-sidebar.tsx
  46. 1 0
      src/components/page-header.tsx
  47. 45 2
      src/features/vehicles/Actions/predictedMaintenanceActions.ts
  48. 27 0
      src/features/vehicles/Actions/reminderActions.ts
  49. 480 0
      src/features/vehicles/Components/RemindersPageClient.tsx
  50. 8 2
      src/hooks/use-dashboard-visibility.ts
  51. 2 0
      src/i18n/request.ts

+ 19 - 7
messages/de/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Kunden gesamt"
   },
   "customize": "Anpassen",
+  "viewAll": "Alle anzeigen",
   "showCards": "Karten anzeigen",
   "cards": {
     "maintenance": "Voraussichtliche Wartung",
@@ -25,33 +26,43 @@
     "approaching": "Bevorstehend",
     "estimated": "Gesch. {mileage} {unit}",
     "sinceLastService": "{mileage} {unit} seit letztem Service",
-    "certainty": "{percent}% Sicherheit"
+    "certainty": "{percent}% Sicherheit",
+    "active": "Aktiv",
+    "dismissedTab": "Verworfen",
+    "noActive": "Derzeit sind keine Fahrzeuge wartungsfällig.",
+    "dismissedOn": "Verworfen am {date}",
+    "restore": "Wiederherstellen"
   },
   "reminders": {
     "title": "Anstehende Erinnerungen",
     "overdue": "Überfällig",
-    "dueSoon": "Bald fällig"
+    "dueSoon": "Bald fällig",
+    "noData": "Keine kommenden Erinnerungen."
   },
   "messages": {
     "title": "Letzte Nachrichten",
     "viewAll": "Alle anzeigen",
-    "you": "Sie: "
+    "you": "Sie: ",
+    "noData": "Keine neuen Nachrichten."
   },
   "notifications": {
-    "title": "Letzte Benachrichtigungen"
+    "title": "Letzte Benachrichtigungen",
+    "noData": "Keine neuen Benachrichtigungen."
   },
   "inspections": {
     "title": "Inspektionen",
     "inProgress": "In Bearbeitung",
     "completed": "Abgeschlossen",
-    "itemCount": "{inspected}/{total} Positionen"
+    "itemCount": "{inspected}/{total} Positionen",
+    "noData": "Keine Inspektionen."
   },
   "quoteRequests": {
     "title": "Angebotsanfragen",
     "description": "Kunden, die Angebote aus geteilten Inspektionen anfordern",
     "pending": "Ausstehend",
     "createQuote": "Angebot erstellen",
-    "itemsRequested": "{count, plural, one {# Position angefragt} other {# Positionen angefragt}}"
+    "itemsRequested": "{count, plural, one {# Position angefragt} other {# Positionen angefragt}}",
+    "noData": "Keine ausstehenden Angebotsanfragen."
   },
   "quoteResponses": {
     "title": "Angebotsantworten der Kunden",
@@ -59,7 +70,8 @@
     "accepted": "Angenommen",
     "changesRequested": "Änderungen gewünscht",
     "convertToWorkOrder": "In Arbeitsauftrag umwandeln",
-    "viewQuote": "Angebot anzeigen"
+    "viewQuote": "Angebot anzeigen",
+    "noData": "Keine Kundenantworten."
   },
   "recentCompleted": {
     "title": "Kürzlich abgeschlossen",

+ 4 - 1
messages/de/navigation.json

@@ -46,7 +46,9 @@
     "license": "Lizenz",
     "subscription": "Abonnement",
     "maintenance": "Wartung",
-    "customerPortal": "Kundenportal"
+    "customerPortal": "Kundenportal",
+    "reminders": "Erinnerungen",
+    "allReminders": "Alle Erinnerungen"
   },
   "search": "Suchen...",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Nachrichten",
     "workshop": "Werkstatt",
     "vehicles": "Fahrzeuge",
+    "reminders": "Erinnerungen",
     "workOrders": "Arbeitsaufträge",
     "inspections": "Inspektionen",
     "calendar": "Kalender",

+ 11 - 0
messages/de/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "Erinnerungen konnten nicht geladen werden",
+  "showing": "{count} von {total} angezeigt",
+  "deleted": "Erinnerung gelöscht",
+  "deleteError": "Erinnerung konnte nicht gelöscht werden",
+  "vehicle": "Fahrzeug",
+  "selectVehicle": "Fahrzeug auswählen...",
+  "searchVehicle": "Fahrzeuge suchen...",
+  "noVehicle": "Keine Fahrzeuge gefunden.",
+  "pickDate": "Datum auswählen"
+}

+ 1 - 0
messages/de/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "Fahrzeug nicht gefunden",
     "predictedKm": "Prognostizierte Km",
     "predictedMileage": "Prognostizierter Kilometerstand",
+    "predictedTooltip": "Geschätzter aktueller Kilometerstand basierend auf der Servicehistorie. Der Prozentsatz zeigt die Zuverlässigkeit der Berechnung.",
     "dismissed": "Verworfen",
     "undo": "R\u00fcckg\u00e4ngig",
     "serviceOverdue": "Service \u00fcberf\u00e4llig",

+ 19 - 7
messages/en/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Total Customers"
   },
   "customize": "Customize",
+  "viewAll": "View All",
   "showCards": "Show cards",
   "cards": {
     "maintenance": "Predicted Maintenance",
@@ -25,33 +26,43 @@
     "approaching": "Approaching",
     "estimated": "Est. {mileage} {unit}",
     "sinceLastService": "{mileage} {unit} since last service",
-    "certainty": "{percent}% certainty"
+    "certainty": "{percent}% certainty",
+    "active": "Active",
+    "dismissedTab": "Dismissed",
+    "noActive": "No vehicles currently due for service.",
+    "dismissedOn": "Dismissed {date}",
+    "restore": "Restore"
   },
   "reminders": {
     "title": "Upcoming Reminders",
     "overdue": "Overdue",
-    "dueSoon": "Due Soon"
+    "dueSoon": "Due Soon",
+    "noData": "No upcoming reminders."
   },
   "messages": {
     "title": "Recent Messages",
     "viewAll": "View All",
-    "you": "You: "
+    "you": "You: ",
+    "noData": "No recent messages."
   },
   "notifications": {
-    "title": "Recent Notifications"
+    "title": "Recent Notifications",
+    "noData": "No recent notifications."
   },
   "inspections": {
     "title": "Inspections",
     "inProgress": "In Progress",
     "completed": "Completed",
-    "itemCount": "{inspected}/{total} items"
+    "itemCount": "{inspected}/{total} items",
+    "noData": "No inspections."
   },
   "quoteRequests": {
     "title": "Quote Requests",
     "description": "Customers requesting quotes from shared inspections",
     "pending": "Pending",
     "createQuote": "Create Quote",
-    "itemsRequested": "{count, plural, one {# item requested} other {# items requested}}"
+    "itemsRequested": "{count, plural, one {# item requested} other {# items requested}}",
+    "noData": "No pending quote requests."
   },
   "quoteResponses": {
     "title": "Customer Quote Responses",
@@ -59,7 +70,8 @@
     "accepted": "Accepted",
     "changesRequested": "Changes Requested",
     "convertToWorkOrder": "Convert to Work Order",
-    "viewQuote": "View Quote"
+    "viewQuote": "View Quote",
+    "noData": "No customer responses."
   },
   "recentCompleted": {
     "title": "Recent Completed",

+ 4 - 1
messages/en/navigation.json

@@ -46,7 +46,9 @@
     "license": "License",
     "subscription": "Subscription",
     "maintenance": "Maintenance",
-    "customerPortal": "Customer Portal"
+    "customerPortal": "Customer Portal",
+    "reminders": "Reminders",
+    "allReminders": "All Reminders"
   },
   "search": "Search...",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Messages",
     "workshop": "Workshop",
     "vehicles": "Vehicles",
+    "reminders": "Reminders",
     "workOrders": "Work Orders",
     "inspections": "Inspections",
     "calendar": "Calendar",

+ 11 - 0
messages/en/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "Failed to load reminders",
+  "showing": "Showing {count} of {total}",
+  "deleted": "Reminder deleted",
+  "deleteError": "Failed to delete reminder",
+  "vehicle": "Vehicle",
+  "selectVehicle": "Select vehicle...",
+  "searchVehicle": "Search vehicles...",
+  "noVehicle": "No vehicles found.",
+  "pickDate": "Pick a date"
+}

+ 1 - 0
messages/en/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "Vehicle not found",
     "predictedKm": "Predicted Km",
     "predictedMileage": "Predicted Mileage",
+    "predictedTooltip": "Estimated current mileage based on service history trends. The percentage indicates prediction confidence.",
     "dismissed": "Dismissed",
     "undo": "Undo",
     "serviceOverdue": "Service Overdue",

+ 19 - 7
messages/es/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Total de clientes"
   },
   "customize": "Personalizar",
+  "viewAll": "Ver todo",
   "showCards": "Mostrar tarjetas",
   "cards": {
     "maintenance": "Mantenimiento previsto",
@@ -25,33 +26,43 @@
     "approaching": "Pr\u00f3ximo",
     "estimated": "Est. {mileage} {unit}",
     "sinceLastService": "{mileage} {unit} desde el \u00faltimo servicio",
-    "certainty": "{percent}% de certeza"
+    "certainty": "{percent}% de certeza",
+    "active": "Activos",
+    "dismissedTab": "Descartados",
+    "noActive": "No hay vehículos que requieran servicio actualmente.",
+    "dismissedOn": "Descartado el {date}",
+    "restore": "Restaurar"
   },
   "reminders": {
     "title": "Recordatorios pendientes",
     "overdue": "Vencidos",
-    "dueSoon": "Pr\u00f3ximamente"
+    "dueSoon": "Pr\u00f3ximamente",
+    "noData": "No hay recordatorios próximos."
   },
   "messages": {
     "title": "Mensajes recientes",
     "viewAll": "Ver todos",
-    "you": "Usted: "
+    "you": "Usted: ",
+    "noData": "No hay mensajes recientes."
   },
   "notifications": {
-    "title": "Notificaciones recientes"
+    "title": "Notificaciones recientes",
+    "noData": "No hay notificaciones recientes."
   },
   "inspections": {
     "title": "Inspecciones",
     "inProgress": "En progreso",
     "completed": "Completadas",
-    "itemCount": "{inspected}/{total} elementos"
+    "itemCount": "{inspected}/{total} elementos",
+    "noData": "No hay inspecciones."
   },
   "quoteRequests": {
     "title": "Solicitudes de presupuesto",
     "description": "Clientes que solicitan presupuestos a partir de inspecciones compartidas",
     "pending": "Pendientes",
     "createQuote": "Crear presupuesto",
-    "itemsRequested": "{count, plural, one {# elemento solicitado} other {# elementos solicitados}}"
+    "itemsRequested": "{count, plural, one {# elemento solicitado} other {# elementos solicitados}}",
+    "noData": "No hay solicitudes de presupuesto pendientes."
   },
   "quoteResponses": {
     "title": "Respuestas de presupuesto de clientes",
@@ -59,7 +70,8 @@
     "accepted": "Aceptado",
     "changesRequested": "Cambios solicitados",
     "convertToWorkOrder": "Convertir en orden de trabajo",
-    "viewQuote": "Ver presupuesto"
+    "viewQuote": "Ver presupuesto",
+    "noData": "No hay respuestas de clientes."
   },
   "recentCompleted": {
     "title": "Completados recientemente",

+ 4 - 1
messages/es/navigation.json

@@ -46,7 +46,9 @@
     "license": "Licencia",
     "subscription": "Suscripci\u00f3n",
     "maintenance": "Mantenimiento",
-    "customerPortal": "Portal del cliente"
+    "customerPortal": "Portal del cliente",
+    "reminders": "Recordatorios",
+    "allReminders": "Todos los recordatorios"
   },
   "search": "Buscar...",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Mensajes",
     "workshop": "Taller",
     "vehicles": "Vehículos",
+    "reminders": "Recordatorios",
     "workOrders": "Órdenes de trabajo",
     "inspections": "Inspecciones",
     "calendar": "Calendario",

+ 11 - 0
messages/es/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "No se pudieron cargar los recordatorios",
+  "showing": "Mostrando {count} de {total}",
+  "deleted": "Recordatorio eliminado",
+  "deleteError": "No se pudo eliminar el recordatorio",
+  "vehicle": "Vehículo",
+  "selectVehicle": "Seleccionar vehículo...",
+  "searchVehicle": "Buscar vehículos...",
+  "noVehicle": "No se encontraron vehículos.",
+  "pickDate": "Seleccionar fecha"
+}

+ 1 - 0
messages/es/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "Vehiculo no encontrado",
     "predictedKm": "Km predichos",
     "predictedMileage": "Kilometraje predicho",
+    "predictedTooltip": "Kilometraje actual estimado basado en el historial de servicio. El porcentaje indica la confianza de la predicción.",
     "dismissed": "Descartado",
     "undo": "Deshacer",
     "serviceOverdue": "Servicio vencido",

+ 19 - 7
messages/fr/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Total des clients"
   },
   "customize": "Personnaliser",
+  "viewAll": "Tout voir",
   "showCards": "Afficher les cartes",
   "cards": {
     "maintenance": "Maintenance pr\u00e9vue",
@@ -25,33 +26,43 @@
     "approaching": "Imminent",
     "estimated": "Est. {mileage} {unit}",
     "sinceLastService": "{mileage} {unit} depuis le dernier entretien",
-    "certainty": "{percent}\u00a0% de certitude"
+    "certainty": "{percent}\u00a0% de certitude",
+    "active": "Actifs",
+    "dismissedTab": "Ignorés",
+    "noActive": "Aucun véhicule n'a besoin d'entretien actuellement.",
+    "dismissedOn": "Ignoré le {date}",
+    "restore": "Restaurer"
   },
   "reminders": {
     "title": "Rappels \u00e0 venir",
     "overdue": "En retard",
-    "dueSoon": "Bient\u00f4t d\u00fb"
+    "dueSoon": "Bient\u00f4t d\u00fb",
+    "noData": "Aucun rappel à venir."
   },
   "messages": {
     "title": "Messages r\u00e9cents",
     "viewAll": "Tout afficher",
-    "you": "Vous\u00a0: "
+    "you": "Vous\u00a0: ",
+    "noData": "Aucun message récent."
   },
   "notifications": {
-    "title": "Notifications r\u00e9centes"
+    "title": "Notifications r\u00e9centes",
+    "noData": "Aucune notification récente."
   },
   "inspections": {
     "title": "Inspections",
     "inProgress": "En cours",
     "completed": "Termin\u00e9",
-    "itemCount": "{inspected}/{total} \u00e9l\u00e9ments"
+    "itemCount": "{inspected}/{total} \u00e9l\u00e9ments",
+    "noData": "Aucune inspection."
   },
   "quoteRequests": {
     "title": "Demandes de devis",
     "description": "Clients demandant des devis \u00e0 partir d\u2019inspections partag\u00e9es",
     "pending": "En attente",
     "createQuote": "Cr\u00e9er un devis",
-    "itemsRequested": "{count, plural, one {# \u00e9l\u00e9ment demand\u00e9} other {# \u00e9l\u00e9ments demand\u00e9s}}"
+    "itemsRequested": "{count, plural, one {# \u00e9l\u00e9ment demand\u00e9} other {# \u00e9l\u00e9ments demand\u00e9s}}",
+    "noData": "Aucune demande de devis en attente."
   },
   "quoteResponses": {
     "title": "R\u00e9ponses des clients aux devis",
@@ -59,7 +70,8 @@
     "accepted": "Accept\u00e9",
     "changesRequested": "Modifications demand\u00e9es",
     "convertToWorkOrder": "Convertir en ordre de travail",
-    "viewQuote": "Voir le devis"
+    "viewQuote": "Voir le devis",
+    "noData": "Aucune réponse client."
   },
   "recentCompleted": {
     "title": "Termin\u00e9s r\u00e9cemment",

+ 4 - 1
messages/fr/navigation.json

@@ -46,7 +46,9 @@
     "license": "Licence",
     "subscription": "Abonnement",
     "maintenance": "Maintenance",
-    "customerPortal": "Portail client"
+    "customerPortal": "Portail client",
+    "reminders": "Rappels",
+    "allReminders": "Tous les rappels"
   },
   "search": "Rechercher\u2026",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Messages",
     "workshop": "Atelier",
     "vehicles": "Véhicules",
+    "reminders": "Rappels",
     "workOrders": "Ordres de travail",
     "inspections": "Inspections",
     "calendar": "Calendrier",

+ 11 - 0
messages/fr/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "Impossible de charger les rappels",
+  "showing": "{count} sur {total} affichés",
+  "deleted": "Rappel supprimé",
+  "deleteError": "Impossible de supprimer le rappel",
+  "vehicle": "Véhicule",
+  "selectVehicle": "Sélectionner un véhicule...",
+  "searchVehicle": "Rechercher des véhicules...",
+  "noVehicle": "Aucun véhicule trouvé.",
+  "pickDate": "Choisir une date"
+}

+ 1 - 0
messages/fr/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "V\u00e9hicule introuvable",
     "predictedKm": "Km pr\u00e9vus",
     "predictedMileage": "Kilom\u00e9trage pr\u00e9vu",
+    "predictedTooltip": "Kilom\u00e9trage actuel estim\u00e9 bas\u00e9 sur l'historique d'entretien. Le pourcentage indique la fiabilit\u00e9 de la pr\u00e9diction.",
     "dismissed": "Ignor\u00e9",
     "undo": "Annuler",
     "serviceOverdue": "Service en retard",

+ 19 - 7
messages/it/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Clienti totali"
   },
   "customize": "Personalizza",
+  "viewAll": "Vedi tutto",
   "showCards": "Mostra schede",
   "cards": {
     "maintenance": "Manutenzione prevista",
@@ -25,33 +26,43 @@
     "approaching": "In avvicinamento",
     "estimated": "Stima {mileage} {unit}",
     "sinceLastService": "{mileage} {unit} dall'ultimo intervento",
-    "certainty": "{percent}% di certezza"
+    "certainty": "{percent}% di certezza",
+    "active": "Attivi",
+    "dismissedTab": "Ignorati",
+    "noActive": "Nessun veicolo attualmente in scadenza per la manutenzione.",
+    "dismissedOn": "Ignorato il {date}",
+    "restore": "Ripristina"
   },
   "reminders": {
     "title": "Promemoria in scadenza",
     "overdue": "Scaduto",
-    "dueSoon": "In scadenza"
+    "dueSoon": "In scadenza",
+    "noData": "Nessun promemoria in arrivo."
   },
   "messages": {
     "title": "Messaggi recenti",
     "viewAll": "Visualizza tutti",
-    "you": "Lei: "
+    "you": "Lei: ",
+    "noData": "Nessun messaggio recente."
   },
   "notifications": {
-    "title": "Notifiche recenti"
+    "title": "Notifiche recenti",
+    "noData": "Nessuna notifica recente."
   },
   "inspections": {
     "title": "Ispezioni",
     "inProgress": "In corso",
     "completed": "Completato",
-    "itemCount": "{inspected}/{total} elementi"
+    "itemCount": "{inspected}/{total} elementi",
+    "noData": "Nessuna ispezione."
   },
   "quoteRequests": {
     "title": "Richieste di preventivo",
     "description": "Clienti che richiedono preventivi dalle ispezioni condivise",
     "pending": "In attesa",
     "createQuote": "Crea preventivo",
-    "itemsRequested": "{count, plural, one {# elemento richiesto} other {# elementi richiesti}}"
+    "itemsRequested": "{count, plural, one {# elemento richiesto} other {# elementi richiesti}}",
+    "noData": "Nessuna richiesta di preventivo in sospeso."
   },
   "quoteResponses": {
     "title": "Risposte ai preventivi dei clienti",
@@ -59,7 +70,8 @@
     "accepted": "Accettato",
     "changesRequested": "Modifiche richieste",
     "convertToWorkOrder": "Converti in ordine di lavoro",
-    "viewQuote": "Visualizza preventivo"
+    "viewQuote": "Visualizza preventivo",
+    "noData": "Nessuna risposta dai clienti."
   },
   "recentCompleted": {
     "title": "Completati di recente",

+ 4 - 1
messages/it/navigation.json

@@ -46,7 +46,9 @@
     "license": "Licenza",
     "subscription": "Abbonamento",
     "maintenance": "Manutenzione",
-    "customerPortal": "Portale clienti"
+    "customerPortal": "Portale clienti",
+    "reminders": "Promemoria",
+    "allReminders": "Tutti i promemoria"
   },
   "search": "Cerca...",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Messaggi",
     "workshop": "Officina",
     "vehicles": "Veicoli",
+    "reminders": "Promemoria",
     "workOrders": "Ordini di lavoro",
     "inspections": "Ispezioni",
     "calendar": "Calendario",

+ 11 - 0
messages/it/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "Impossibile caricare i promemoria",
+  "showing": "{count} di {total} visualizzati",
+  "deleted": "Promemoria eliminato",
+  "deleteError": "Impossibile eliminare il promemoria",
+  "vehicle": "Veicolo",
+  "selectVehicle": "Seleziona veicolo...",
+  "searchVehicle": "Cerca veicoli...",
+  "noVehicle": "Nessun veicolo trovato.",
+  "pickDate": "Seleziona una data"
+}

+ 1 - 0
messages/it/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "Veicolo non trovato",
     "predictedKm": "Km Previsti",
     "predictedMileage": "Chilometraggio Previsto",
+    "predictedTooltip": "Chilometraggio attuale stimato in base alla cronologia dei servizi. La percentuale indica l'affidabilità della previsione.",
     "dismissed": "Ignorato",
     "undo": "Annulla",
     "serviceOverdue": "Intervento Scaduto",

+ 19 - 7
messages/nb/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Totalt antall kunder"
   },
   "customize": "Tilpass",
+  "viewAll": "Vis alle",
   "showCards": "Vis kort",
   "cards": {
     "maintenance": "Forventet vedlikehold",
@@ -25,33 +26,43 @@
     "approaching": "Nærmer seg",
     "estimated": "Est. {mileage} {unit}",
     "sinceLastService": "{mileage} {unit} siden siste service",
-    "certainty": "{percent} % sikkerhet"
+    "certainty": "{percent} % sikkerhet",
+    "active": "Aktive",
+    "dismissedTab": "Avvist",
+    "noActive": "Ingen kjøretøy trenger service for øyeblikket.",
+    "dismissedOn": "Avvist {date}",
+    "restore": "Gjenopprett"
   },
   "reminders": {
     "title": "Kommende påminnelser",
     "overdue": "Forfalt",
-    "dueSoon": "Forfaller snart"
+    "dueSoon": "Forfaller snart",
+    "noData": "Ingen kommende påminnelser."
   },
   "messages": {
     "title": "Siste meldinger",
     "viewAll": "Vis alle",
-    "you": "Du: "
+    "you": "Du: ",
+    "noData": "Ingen nylige meldinger."
   },
   "notifications": {
-    "title": "Siste varsler"
+    "title": "Siste varsler",
+    "noData": "Ingen nylige varsler."
   },
   "inspections": {
     "title": "Inspeksjoner",
     "inProgress": "Pågår",
     "completed": "Fullført",
-    "itemCount": "{inspected}/{total} punkter"
+    "itemCount": "{inspected}/{total} punkter",
+    "noData": "Ingen inspeksjoner."
   },
   "quoteRequests": {
     "title": "Tilbudsforespørsler",
     "description": "Kunder som ber om tilbud fra delte inspeksjoner",
     "pending": "Ventende",
     "createQuote": "Opprett tilbud",
-    "itemsRequested": "{count, plural, one {# punkt forespurt} other {# punkter forespurt}}"
+    "itemsRequested": "{count, plural, one {# punkt forespurt} other {# punkter forespurt}}",
+    "noData": "Ingen ventende tilbudsforespørsler."
   },
   "quoteResponses": {
     "title": "Kundens tilbudssvar",
@@ -59,7 +70,8 @@
     "accepted": "Akseptert",
     "changesRequested": "Endringer forespurt",
     "convertToWorkOrder": "Konverter til arbeidsordre",
-    "viewQuote": "Vis tilbud"
+    "viewQuote": "Vis tilbud",
+    "noData": "Ingen kundesvar."
   },
   "recentCompleted": {
     "title": "Nylig fullført",

+ 4 - 1
messages/nb/navigation.json

@@ -46,7 +46,9 @@
     "license": "Lisens",
     "subscription": "Abonnement",
     "maintenance": "Vedlikehold",
-    "customerPortal": "Kundeportal"
+    "customerPortal": "Kundeportal",
+    "reminders": "Påminnelser",
+    "allReminders": "Alle påminnelser"
   },
   "search": "Søk...",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Meldinger",
     "workshop": "Verksted",
     "vehicles": "Kjøretøy",
+    "reminders": "Påminnelser",
     "workOrders": "Arbeidsordre",
     "inspections": "Inspeksjoner",
     "calendar": "Kalender",

+ 11 - 0
messages/nb/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "Kunne ikke laste påminnelser",
+  "showing": "Viser {count} av {total}",
+  "deleted": "Påminnelse slettet",
+  "deleteError": "Kunne ikke slette påminnelse",
+  "vehicle": "Kjøretøy",
+  "selectVehicle": "Velg kjøretøy...",
+  "searchVehicle": "Søk kjøretøy...",
+  "noVehicle": "Ingen kjøretøy funnet.",
+  "pickDate": "Velg en dato"
+}

+ 1 - 0
messages/nb/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "Kjoretoy ikke funnet",
     "predictedKm": "Beregnet km",
     "predictedMileage": "Beregnet kilometerstand",
+    "predictedTooltip": "Estimert kilometerstand basert på servicehistorikk. Prosenten viser hvor sikker beregningen er.",
     "dismissed": "Avvist",
     "undo": "Angre",
     "serviceOverdue": "Service forfalt",

+ 19 - 7
messages/nl/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Totaal klanten"
   },
   "customize": "Aanpassen",
+  "viewAll": "Alles bekijken",
   "showCards": "Kaarten tonen",
   "cards": {
     "maintenance": "Verwacht onderhoud",
@@ -25,33 +26,43 @@
     "approaching": "Binnenkort",
     "estimated": "Geschat {mileage} {unit}",
     "sinceLastService": "{mileage} {unit} sinds laatste service",
-    "certainty": "{percent}% zekerheid"
+    "certainty": "{percent}% zekerheid",
+    "active": "Actief",
+    "dismissedTab": "Genegeerd",
+    "noActive": "Geen voertuigen hebben momenteel onderhoud nodig.",
+    "dismissedOn": "Genegeerd op {date}",
+    "restore": "Herstellen"
   },
   "reminders": {
     "title": "Aankomende herinneringen",
     "overdue": "Achterstallig",
-    "dueSoon": "Binnenkort verlopen"
+    "dueSoon": "Binnenkort verlopen",
+    "noData": "Geen komende herinneringen."
   },
   "messages": {
     "title": "Recente berichten",
     "viewAll": "Alles bekijken",
-    "you": "U: "
+    "you": "U: ",
+    "noData": "Geen recente berichten."
   },
   "notifications": {
-    "title": "Recente meldingen"
+    "title": "Recente meldingen",
+    "noData": "Geen recente meldingen."
   },
   "inspections": {
     "title": "Inspecties",
     "inProgress": "In uitvoering",
     "completed": "Afgerond",
-    "itemCount": "{inspected}/{total} items"
+    "itemCount": "{inspected}/{total} items",
+    "noData": "Geen inspecties."
   },
   "quoteRequests": {
     "title": "Offerte-aanvragen",
     "description": "Klanten die offertes aanvragen vanuit gedeelde inspecties",
     "pending": "In afwachting",
     "createQuote": "Offerte aanmaken",
-    "itemsRequested": "{count, plural, one {# item aangevraagd} other {# items aangevraagd}}"
+    "itemsRequested": "{count, plural, one {# item aangevraagd} other {# items aangevraagd}}",
+    "noData": "Geen openstaande offerteaanvragen."
   },
   "quoteResponses": {
     "title": "Offerte-reacties van klanten",
@@ -59,7 +70,8 @@
     "accepted": "Geaccepteerd",
     "changesRequested": "Wijzigingen aangevraagd",
     "convertToWorkOrder": "Omzetten naar werkorder",
-    "viewQuote": "Offerte bekijken"
+    "viewQuote": "Offerte bekijken",
+    "noData": "Geen klantreacties."
   },
   "recentCompleted": {
     "title": "Recent afgerond",

+ 4 - 1
messages/nl/navigation.json

@@ -46,7 +46,9 @@
     "license": "Licentie",
     "subscription": "Abonnement",
     "maintenance": "Onderhoud",
-    "customerPortal": "Klantenportaal"
+    "customerPortal": "Klantenportaal",
+    "reminders": "Herinneringen",
+    "allReminders": "Alle herinneringen"
   },
   "search": "Zoeken...",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Berichten",
     "workshop": "Werkplaats",
     "vehicles": "Voertuigen",
+    "reminders": "Herinneringen",
     "workOrders": "Werkorders",
     "inspections": "Inspecties",
     "calendar": "Kalender",

+ 11 - 0
messages/nl/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "Herinneringen konden niet worden geladen",
+  "showing": "{count} van {total} weergegeven",
+  "deleted": "Herinnering verwijderd",
+  "deleteError": "Herinnering kon niet worden verwijderd",
+  "vehicle": "Voertuig",
+  "selectVehicle": "Voertuig selecteren...",
+  "searchVehicle": "Voertuigen zoeken...",
+  "noVehicle": "Geen voertuigen gevonden.",
+  "pickDate": "Datum kiezen"
+}

+ 1 - 0
messages/nl/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "Voertuig niet gevonden",
     "predictedKm": "Voorspelde km",
     "predictedMileage": "Voorspelde kilometerstand",
+    "predictedTooltip": "Geschatte huidige kilometerstand op basis van servicehistorie. Het percentage geeft de betrouwbaarheid van de voorspelling aan.",
     "dismissed": "Genegeerd",
     "undo": "Ongedaan maken",
     "serviceOverdue": "Onderhoud achterstallig",

+ 19 - 7
messages/pl/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Łączna liczba klientów"
   },
   "customize": "Dostosuj",
+  "viewAll": "Pokaż wszystkie",
   "showCards": "Pokaż karty",
   "cards": {
     "maintenance": "Przewidywana konserwacja",
@@ -25,33 +26,43 @@
     "approaching": "Zbliżający się",
     "estimated": "Szac. {mileage} {unit}",
     "sinceLastService": "{mileage} {unit} od ostatniego serwisu",
-    "certainty": "{percent}% pewności"
+    "certainty": "{percent}% pewności",
+    "active": "Aktywne",
+    "dismissedTab": "Odrzucone",
+    "noActive": "Żaden pojazd nie wymaga obecnie serwisu.",
+    "dismissedOn": "Odrzucono {date}",
+    "restore": "Przywróć"
   },
   "reminders": {
     "title": "Nadchodzące przypomnienia",
     "overdue": "Zaległe",
-    "dueSoon": "Wkrótce"
+    "dueSoon": "Wkrótce",
+    "noData": "Brak nadchodzących przypomnień."
   },
   "messages": {
     "title": "Ostatnie wiadomości",
     "viewAll": "Pokaż wszystkie",
-    "you": "Ty: "
+    "you": "Ty: ",
+    "noData": "Brak ostatnich wiadomości."
   },
   "notifications": {
-    "title": "Ostatnie powiadomienia"
+    "title": "Ostatnie powiadomienia",
+    "noData": "Brak ostatnich powiadomień."
   },
   "inspections": {
     "title": "Przeglądy",
     "inProgress": "W trakcie",
     "completed": "Ukończone",
-    "itemCount": "{inspected}/{total} pozycji"
+    "itemCount": "{inspected}/{total} pozycji",
+    "noData": "Brak inspekcji."
   },
   "quoteRequests": {
     "title": "Zapytania o wycenę",
     "description": "Klienci wnioskujący o wycenę z udostępnionych przeglądów",
     "pending": "Oczekujące",
     "createQuote": "Utwórz wycenę",
-    "itemsRequested": "{count, plural, one {# pozycja zgłoszona} few {# pozycje zgłoszone} many {# pozycji zgłoszonych} other {# pozycji zgłoszonych}}"
+    "itemsRequested": "{count, plural, one {# pozycja zgłoszona} few {# pozycje zgłoszone} many {# pozycji zgłoszonych} other {# pozycji zgłoszonych}}",
+    "noData": "Brak oczekujących zapytań ofertowych."
   },
   "quoteResponses": {
     "title": "Odpowiedzi klientów na wyceny",
@@ -59,7 +70,8 @@
     "accepted": "Zaakceptowane",
     "changesRequested": "Zgłoszono zmiany",
     "convertToWorkOrder": "Przekształć w zlecenie",
-    "viewQuote": "Pokaż wycenę"
+    "viewQuote": "Pokaż wycenę",
+    "noData": "Brak odpowiedzi klientów."
   },
   "recentCompleted": {
     "title": "Ostatnio ukończone",

+ 4 - 1
messages/pl/navigation.json

@@ -46,7 +46,9 @@
     "license": "Licencja",
     "subscription": "Subskrypcja",
     "maintenance": "Konserwacja",
-    "customerPortal": "Portal klienta"
+    "customerPortal": "Portal klienta",
+    "reminders": "Przypomnienia",
+    "allReminders": "Wszystkie przypomnienia"
   },
   "search": "Szukaj...",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Wiadomości",
     "workshop": "Warsztat",
     "vehicles": "Pojazdy",
+    "reminders": "Przypomnienia",
     "workOrders": "Zlecenia",
     "inspections": "Przeglądy",
     "calendar": "Kalendarz",

+ 11 - 0
messages/pl/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "Nie udało się załadować przypomnień",
+  "showing": "Wyświetlanie {count} z {total}",
+  "deleted": "Przypomnienie usunięte",
+  "deleteError": "Nie udało się usunąć przypomnienia",
+  "vehicle": "Pojazd",
+  "selectVehicle": "Wybierz pojazd...",
+  "searchVehicle": "Szukaj pojazdów...",
+  "noVehicle": "Nie znaleziono pojazdów.",
+  "pickDate": "Wybierz datę"
+}

+ 1 - 0
messages/pl/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "Nie znaleziono pojazdu",
     "predictedKm": "Przewidywane km",
     "predictedMileage": "Przewidywany przebieg",
+    "predictedTooltip": "Szacowany aktualny przebieg na podstawie historii serwisowej. Procent wskazuje pewność prognozy.",
     "dismissed": "Odrzucono",
     "undo": "Cofnij",
     "serviceOverdue": "Przegląd zaległy",

+ 19 - 7
messages/pt-BR/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Total de Clientes"
   },
   "customize": "Personalizar",
+  "viewAll": "Ver tudo",
   "showCards": "Mostrar cartões",
   "cards": {
     "maintenance": "Manutenção Prevista",
@@ -25,33 +26,43 @@
     "approaching": "Próximo",
     "estimated": "Est. {mileage} {unit}",
     "sinceLastService": "{mileage} {unit} desde o último serviço",
-    "certainty": "{percent}% de certeza"
+    "certainty": "{percent}% de certeza",
+    "active": "Ativos",
+    "dismissedTab": "Dispensados",
+    "noActive": "Nenhum veículo precisa de manutenção no momento.",
+    "dismissedOn": "Dispensado em {date}",
+    "restore": "Restaurar"
   },
   "reminders": {
     "title": "Lembretes Próximos",
     "overdue": "Atrasado",
-    "dueSoon": "Vence em Breve"
+    "dueSoon": "Vence em Breve",
+    "noData": "Nenhum lembrete próximo."
   },
   "messages": {
     "title": "Mensagens Recentes",
     "viewAll": "Ver Todas",
-    "you": "Você: "
+    "you": "Você: ",
+    "noData": "Nenhuma mensagem recente."
   },
   "notifications": {
-    "title": "Notificações Recentes"
+    "title": "Notificações Recentes",
+    "noData": "Nenhuma notificação recente."
   },
   "inspections": {
     "title": "Inspeções",
     "inProgress": "Em Andamento",
     "completed": "Concluído",
-    "itemCount": "{inspected}/{total} itens"
+    "itemCount": "{inspected}/{total} itens",
+    "noData": "Nenhuma inspeção."
   },
   "quoteRequests": {
     "title": "Solicitações de Orçamento",
     "description": "Clientes solicitando orçamentos a partir de inspeções compartilhadas",
     "pending": "Pendente",
     "createQuote": "Criar Orçamento",
-    "itemsRequested": "{count, plural, one {# item solicitado} other {# itens solicitados}}"
+    "itemsRequested": "{count, plural, one {# item solicitado} other {# itens solicitados}}",
+    "noData": "Nenhuma solicitação de orçamento pendente."
   },
   "quoteResponses": {
     "title": "Respostas de Orçamento dos Clientes",
@@ -59,7 +70,8 @@
     "accepted": "Aceito",
     "changesRequested": "Alterações Solicitadas",
     "convertToWorkOrder": "Converter em Ordem de Serviço",
-    "viewQuote": "Ver Orçamento"
+    "viewQuote": "Ver Orçamento",
+    "noData": "Nenhuma resposta de cliente."
   },
   "recentCompleted": {
     "title": "Concluídos Recentemente",

+ 4 - 1
messages/pt-BR/navigation.json

@@ -46,7 +46,9 @@
     "license": "Licença",
     "subscription": "Assinatura",
     "maintenance": "Manutenção",
-    "customerPortal": "Portal do Cliente"
+    "customerPortal": "Portal do Cliente",
+    "reminders": "Lembretes",
+    "allReminders": "Todos os lembretes"
   },
   "search": "Buscar...",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Mensagens",
     "workshop": "Oficina",
     "vehicles": "Veículos",
+    "reminders": "Lembretes",
     "workOrders": "Ordens de Serviço",
     "inspections": "Inspeções",
     "calendar": "Calendário",

+ 11 - 0
messages/pt-BR/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "Falha ao carregar lembretes",
+  "showing": "Mostrando {count} de {total}",
+  "deleted": "Lembrete excluído",
+  "deleteError": "Falha ao excluir lembrete",
+  "vehicle": "Veículo",
+  "selectVehicle": "Selecionar veículo...",
+  "searchVehicle": "Buscar veículos...",
+  "noVehicle": "Nenhum veículo encontrado.",
+  "pickDate": "Escolher uma data"
+}

+ 1 - 0
messages/pt-BR/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "Veículo não encontrado",
     "predictedKm": "Km Previsto",
     "predictedMileage": "Quilometragem Prevista",
+    "predictedTooltip": "Quilometragem atual estimada com base no histórico de serviços. A porcentagem indica a confiança da previsão.",
     "dismissed": "Dispensado",
     "undo": "Desfazer",
     "serviceOverdue": "Serviço Atrasado",

+ 19 - 7
messages/tr/dashboard.json

@@ -6,6 +6,7 @@
     "totalCustomers": "Toplam Müşteriler"
   },
   "customize": "Özelleştir",
+  "viewAll": "Tümünü Gör",
   "showCards": "Kartları göster",
   "cards": {
     "maintenance": "Tahmini Bakım",
@@ -25,33 +26,43 @@
     "approaching": "Yaklaşan",
     "estimated": "Tahmini {mileage} {unit}",
     "sinceLastService": "Son servisten bu yana {mileage} {unit}",
-    "certainty": "%{percent} kesinlik"
+    "certainty": "%{percent} kesinlik",
+    "active": "Aktif",
+    "dismissedTab": "Kapatılan",
+    "noActive": "Şu anda bakım gerektiren araç bulunmuyor.",
+    "dismissedOn": "{date} tarihinde kapatıldı",
+    "restore": "Geri Yükle"
   },
   "reminders": {
     "title": "Yaklaşan Hatırlatmalar",
     "overdue": "Gecikmiş",
-    "dueSoon": "Yakında"
+    "dueSoon": "Yakında",
+    "noData": "Yaklaşan hatırlatma yok."
   },
   "messages": {
     "title": "Son Mesajlar",
     "viewAll": "Tümünü Görüntüle",
-    "you": "Siz: "
+    "you": "Siz: ",
+    "noData": "Son mesaj yok."
   },
   "notifications": {
-    "title": "Son Bildirimler"
+    "title": "Son Bildirimler",
+    "noData": "Son bildirim yok."
   },
   "inspections": {
     "title": "Muayeneler",
     "inProgress": "Devam Ediyor",
     "completed": "Tamamlandı",
-    "itemCount": "{inspected}/{total} öğe"
+    "itemCount": "{inspected}/{total} öğe",
+    "noData": "İnceleme yok."
   },
   "quoteRequests": {
     "title": "Teklif Talepleri",
     "description": "Paylaşılan muayenelerden teklif talep eden müşteriler",
     "pending": "Beklemede",
     "createQuote": "Teklif Oluştur",
-    "itemsRequested": "{count, plural, one {# öğe talep edildi} other {# öğe talep edildi}}"
+    "itemsRequested": "{count, plural, one {# öğe talep edildi} other {# öğe talep edildi}}",
+    "noData": "Bekleyen teklif talebi yok."
   },
   "quoteResponses": {
     "title": "Müşteri Teklif Yanıtları",
@@ -59,7 +70,8 @@
     "accepted": "Kabul Edildi",
     "changesRequested": "Değişiklik Talep Edildi",
     "convertToWorkOrder": "İş Emrine Dönüştür",
-    "viewQuote": "Teklifi Görüntüle"
+    "viewQuote": "Teklifi Görüntüle",
+    "noData": "Müşteri yanıtı yok."
   },
   "recentCompleted": {
     "title": "Son Tamamlananlar",

+ 4 - 1
messages/tr/navigation.json

@@ -46,7 +46,9 @@
     "license": "Lisans",
     "subscription": "Abonelik",
     "maintenance": "Bakım",
-    "customerPortal": "Müşteri Portalı"
+    "customerPortal": "Müşteri Portalı",
+    "reminders": "Hatırlatmalar",
+    "allReminders": "Tüm hatırlatmalar"
   },
   "search": "Ara...",
   "shortcut": "Ctrl+K",
@@ -59,6 +61,7 @@
     "messages": "Mesajlar",
     "workshop": "Atölye",
     "vehicles": "Araçlar",
+    "reminders": "Hatırlatmalar",
     "workOrders": "İş Emirleri",
     "inspections": "Muayeneler",
     "calendar": "Takvim",

+ 11 - 0
messages/tr/reminders.json

@@ -0,0 +1,11 @@
+{
+  "error": "Hatırlatmalar yüklenemedi",
+  "showing": "{total} içinden {count} gösteriliyor",
+  "deleted": "Hatırlatma silindi",
+  "deleteError": "Hatırlatma silinemedi",
+  "vehicle": "Araç",
+  "selectVehicle": "Araç seçin...",
+  "searchVehicle": "Araç ara...",
+  "noVehicle": "Araç bulunamadı.",
+  "pickDate": "Tarih seçin"
+}

+ 1 - 0
messages/tr/vehicles.json

@@ -38,6 +38,7 @@
     "notFound": "Araç bulunamadı",
     "predictedKm": "Tahmini Km",
     "predictedMileage": "Tahmini Kilometre",
+    "predictedTooltip": "Servis geçmişine dayalı tahmini güncel kilometre. Yüzde, tahmin güvenilirliğini gösterir.",
     "dismissed": "Kapatıldı",
     "undo": "Geri Al",
     "serviceOverdue": "Servis Gecikmiş",

+ 246 - 81
src/app/(authenticated)/dashboard-client.tsx

@@ -27,17 +27,20 @@ import {
   ClipboardList,
   Clock,
   DollarSign,
+  EyeOff,
   FileText,
   Gauge,
   Loader2,
   BellRing,
   MessageSquare,
+  Settings,
   SlidersHorizontal,
+  Undo2,
   Users,
   X,
 } from "lucide-react";
 import { formatCurrency } from "@/lib/format";
-import { dismissMaintenance } from "@/features/vehicles/Actions/dismissMaintenance";
+import { dismissMaintenance, undismissMaintenance } from "@/features/vehicles/Actions/dismissMaintenance";
 import { updateQuoteRequestStatus } from "@/features/inspections/Actions/quoteRequestActions";
 import { acknowledgeQuoteResponse } from "@/features/quotes/Actions/quoteResponseActions";
 import { convertQuoteToServiceRecord } from "@/features/quotes/Actions/quoteActions";
@@ -100,6 +103,15 @@ interface VehicleDueForService {
   confidencePercent: number;
 }
 
+interface DismissedMaintenanceVehicle {
+  vehicleId: string;
+  make: string;
+  model: string;
+  year: number;
+  licensePlate: string | null;
+  dismissedAt: Date | null;
+}
+
 interface DashboardInspection {
   id: string;
   status: string;
@@ -178,6 +190,7 @@ export function DashboardClient({
   currencyCode = "USD",
   upcomingReminders = [],
   vehiclesDueForService = [],
+  dismissedMaintenanceVehicles = [],
   unitSystem = "imperial",
   inProgressInspections = [],
   completedInspections = [],
@@ -191,6 +204,7 @@ export function DashboardClient({
   currencyCode?: string;
   upcomingReminders?: ReminderItem[];
   vehiclesDueForService?: VehicleDueForService[];
+  dismissedMaintenanceVehicles?: DismissedMaintenanceVehicle[];
   unitSystem?: "metric" | "imperial";
   inProgressInspections?: DashboardInspection[];
   completedInspections?: DashboardInspection[];
@@ -206,8 +220,10 @@ export function DashboardClient({
   const { formatDate } = useFormatDate();
   const [navigatingId, setNavigatingId] = useState<string | null>(null);
   const [dismissingId, setDismissingId] = useState<string | null>(null);
+  const [maintenanceTab, setMaintenanceTab] = useState<"active" | "dismissed">("active");
+  const [restoringId, setRestoringId] = useState<string | null>(null);
   const [isPending, startTransition] = useTransition();
-  const { toggleCard, isVisible } = useDashboardVisibility();
+  const { toggleCard, isVisible, visibleCount, totalCount } = useDashboardVisibility(smsEnabled ? "notifications" : "sms");
 
   const formatRelativeTime = (date: string | Date) => {
     const now = new Date();
@@ -224,12 +240,18 @@ export function DashboardClient({
     return d.toLocaleDateString([], { month: "short", day: "numeric" });
   };
 
-  const handleDismiss = (vehicleId: string) => {
+  const handleDismiss = async (vehicleId: string) => {
     setDismissingId(vehicleId);
-    startTransition(async () => {
-      await dismissMaintenance(vehicleId);
-      setDismissingId(null);
-    });
+    await dismissMaintenance(vehicleId);
+    setDismissingId(null);
+    router.refresh();
+  };
+
+  const handleRestore = async (vehicleId: string) => {
+    setRestoringId(vehicleId);
+    await undismissMaintenance(vehicleId);
+    setRestoringId(null);
+    router.refresh();
   };
 
   return (
@@ -286,7 +308,7 @@ export function DashboardClient({
           <PopoverTrigger asChild>
             <Button variant="outline" size="sm" className="h-8 text-xs gap-1.5">
               <SlidersHorizontal className="h-3.5 w-3.5" />
-              {t("customize")}
+              {t("customize")} ({visibleCount}/{totalCount})
             </Button>
           </PopoverTrigger>
           <PopoverContent align="end" className="w-56 p-3">
@@ -310,93 +332,182 @@ export function DashboardClient({
       {/* Card grid */}
       <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
         {/* Vehicles Due for Service */}
-        {isVisible("maintenance") && vehiclesDueForService.length > 0 && (
+        {isVisible("maintenance") && (
           <Card className="border-0 shadow-sm">
             <CardHeader className="pb-1">
-              <CardTitle className="flex items-center gap-2 text-base">
-                <Gauge className="h-4 w-4" />
-                {t("maintenance.title")}
-              </CardTitle>
+              <div className="flex items-center justify-between">
+                <CardTitle className="flex items-center gap-2 text-base">
+                  <Gauge className="h-4 w-4" />
+                  {t("maintenance.title")}
+                </CardTitle>
+                <Button
+                  variant="ghost"
+                  size="icon"
+                  className="h-7 w-7 text-muted-foreground hover:text-foreground"
+                  onClick={() => router.push("/settings/maintenance")}
+                >
+                  <Settings className="h-3.5 w-3.5" />
+                </Button>
+              </div>
               <p className="text-xs text-muted-foreground">
                 {t("maintenance.description")}
               </p>
+              <div className="flex gap-1 pt-1">
+                  <Button
+                    variant={maintenanceTab === "active" ? "default" : "ghost"}
+                    size="sm"
+                    className="h-7 text-xs"
+                    onClick={() => setMaintenanceTab("active")}
+                  >
+                    {t("maintenance.active")} ({vehiclesDueForService.length})
+                  </Button>
+                  <Button
+                    variant={maintenanceTab === "dismissed" ? "default" : "ghost"}
+                    size="sm"
+                    className="h-7 text-xs"
+                    onClick={() => setMaintenanceTab("dismissed")}
+                  >
+                    {t("maintenance.dismissedTab")} ({dismissedMaintenanceVehicles.length})
+                  </Button>
+              </div>
             </CardHeader>
             <CardContent className="p-0">
-              <div className="divide-y">
-                {vehiclesDueForService.map((v) => (
-                  <div
-                    key={v.vehicleId}
-                    className="flex items-center justify-between px-5 py-3 cursor-pointer hover:bg-muted/50 transition-colors"
-                    onClick={() => router.push(`/vehicles/${v.vehicleId}`)}
-                  >
-                    <div className="flex items-center gap-3 min-w-0">
-                      <div className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${
-                        v.status === "overdue" ? "bg-red-500/10" : "bg-amber-500/10"
-                      }`}>
+              {maintenanceTab === "active" && (
+                <div className="divide-y">
+                  {vehiclesDueForService.length === 0 ? (
+                    <p className="px-5 py-4 text-xs text-muted-foreground">{t("maintenance.noActive")}</p>
+                  ) : vehiclesDueForService.map((v) => (
+                    <div
+                      key={v.vehicleId}
+                      className="flex items-center justify-between px-5 py-3 cursor-pointer hover:bg-muted/50 transition-colors"
+                      onClick={() => router.push(`/vehicles/${v.vehicleId}`)}
+                    >
+                      <div className="flex items-center gap-3 min-w-0">
+                        <div className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${
+                          v.status === "overdue" ? "bg-red-500/10" : "bg-amber-500/10"
+                        }`}>
+                          {v.status === "overdue" ? (
+                            <AlertTriangle className="h-4 w-4 text-red-500" />
+                          ) : (
+                            <Clock className="h-4 w-4 text-amber-500" />
+                          )}
+                        </div>
+                        <div className="min-w-0">
+                          <p className="font-medium text-sm truncate">
+                            {v.year} {v.make} {v.model}
+                          </p>
+                          <p className="text-xs text-muted-foreground truncate">
+                            {v.licensePlate && `${v.licensePlate} · `}
+                            {t("maintenance.estimated", { mileage: v.predictedMileage.toLocaleString(), unit: distUnit })}
+                            {" · "}
+                            {t("maintenance.sinceLastService", { mileage: v.mileageSinceLastService.toLocaleString(), unit: distUnit })}
+                            {" · "}
+                            {t("maintenance.certainty", { percent: v.confidencePercent })}
+                          </p>
+                        </div>
+                      </div>
+                      <div className="shrink-0 ml-3 flex items-center gap-1.5">
                         {v.status === "overdue" ? (
-                          <AlertTriangle className="h-4 w-4 text-red-500" />
+                          <Badge variant="destructive" className="text-[10px]">
+                            {t("maintenance.overdue")}
+                          </Badge>
                         ) : (
-                          <Clock className="h-4 w-4 text-amber-500" />
+                          <Badge className="bg-amber-500/15 text-amber-600 border-amber-500/20 text-[10px]">
+                            {t("maintenance.approaching")}
+                          </Badge>
                         )}
-                      </div>
-                      <div className="min-w-0">
-                        <p className="font-medium text-sm truncate">
-                          {v.year} {v.make} {v.model}
-                        </p>
-                        <p className="text-xs text-muted-foreground truncate">
-                          {v.licensePlate && `${v.licensePlate} · `}
-                          {t("maintenance.estimated", { mileage: v.predictedMileage.toLocaleString(), unit: distUnit })}
-                          {" · "}
-                          {t("maintenance.sinceLastService", { mileage: v.mileageSinceLastService.toLocaleString(), unit: distUnit })}
-                          {" · "}
-                          {t("maintenance.certainty", { percent: v.confidencePercent })}
-                        </p>
+                        <Button
+                          variant="ghost"
+                          size="icon"
+                          className="h-6 w-6 text-muted-foreground hover:text-foreground"
+                          disabled={dismissingId === v.vehicleId}
+                          onClick={(e) => {
+                            e.stopPropagation();
+                            handleDismiss(v.vehicleId);
+                          }}
+                        >
+                          {dismissingId === v.vehicleId ? (
+                            <Loader2 className="h-3.5 w-3.5 animate-spin" />
+                          ) : (
+                            <X className="h-3.5 w-3.5" />
+                          )}
+                        </Button>
                       </div>
                     </div>
-                    <div className="shrink-0 ml-3 flex items-center gap-1.5">
-                      {v.status === "overdue" ? (
-                        <Badge variant="destructive" className="text-[10px]">
-                          {t("maintenance.overdue")}
-                        </Badge>
-                      ) : (
-                        <Badge className="bg-amber-500/15 text-amber-600 border-amber-500/20 text-[10px]">
-                          {t("maintenance.approaching")}
-                        </Badge>
-                      )}
+                  ))}
+                </div>
+              )}
+              {maintenanceTab === "dismissed" && (
+                <div className="divide-y">
+                  {dismissedMaintenanceVehicles.map((v) => (
+                    <div
+                      key={v.vehicleId}
+                      className="flex items-center justify-between px-5 py-3 cursor-pointer hover:bg-muted/50 transition-colors"
+                      onClick={() => router.push(`/vehicles/${v.vehicleId}`)}
+                    >
+                      <div className="flex items-center gap-3 min-w-0">
+                        <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-muted">
+                          <EyeOff className="h-4 w-4 text-muted-foreground" />
+                        </div>
+                        <div className="min-w-0">
+                          <p className="font-medium text-sm truncate">
+                            {v.year} {v.make} {v.model}
+                          </p>
+                          <p className="text-xs text-muted-foreground truncate">
+                            {v.licensePlate && `${v.licensePlate} · `}
+                            {v.dismissedAt && t("maintenance.dismissedOn", { date: formatDate(new Date(v.dismissedAt)) })}
+                          </p>
+                        </div>
+                      </div>
                       <Button
                         variant="ghost"
-                        size="icon"
-                        className="h-6 w-6 text-muted-foreground hover:text-foreground"
-                        disabled={dismissingId === v.vehicleId}
+                        size="sm"
+                        className="h-7 shrink-0 ml-3 text-xs text-muted-foreground hover:text-foreground"
+                        disabled={restoringId === v.vehicleId}
                         onClick={(e) => {
                           e.stopPropagation();
-                          handleDismiss(v.vehicleId);
+                          handleRestore(v.vehicleId);
                         }}
                       >
-                        {dismissingId === v.vehicleId ? (
-                          <Loader2 className="h-3.5 w-3.5 animate-spin" />
+                        {restoringId === v.vehicleId ? (
+                          <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
                         ) : (
-                          <X className="h-3.5 w-3.5" />
+                          <Undo2 className="mr-1 h-3.5 w-3.5" />
                         )}
+                        {t("maintenance.restore")}
                       </Button>
                     </div>
-                  </div>
-                ))}
-              </div>
+                  ))}
+                </div>
+              )}
             </CardContent>
           </Card>
         )}
 
         {/* Upcoming Reminders */}
-        {isVisible("reminders") && upcomingReminders.length > 0 && (
+        {isVisible("reminders") && (
           <Card className="border-0 shadow-sm">
             <CardHeader className="pb-3">
-              <CardTitle className="flex items-center gap-2 text-base">
-                <Bell className="h-4 w-4" />
-                {t("reminders.title")}
-              </CardTitle>
+              <div className="flex items-center justify-between">
+                <CardTitle className="flex items-center gap-2 text-base">
+                  <Bell className="h-4 w-4" />
+                  {t("reminders.title")}
+                </CardTitle>
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  className="h-7 text-xs gap-1"
+                  onClick={() => router.push("/reminders")}
+                >
+                  {t("viewAll")}
+                  <ArrowRight className="h-3 w-3" />
+                </Button>
+              </div>
             </CardHeader>
             <CardContent className="p-0">
+              {upcomingReminders.length === 0 ? (
+                <p className="px-5 py-4 text-xs text-muted-foreground">{t("reminders.noData")}</p>
+              ) : (
               <div className="divide-y">
                 {upcomingReminders.map((r) => {
                   const now = new Date();
@@ -452,12 +563,13 @@ export function DashboardClient({
                   );
                 })}
               </div>
+              )}
             </CardContent>
           </Card>
         )}
 
         {/* SMS Messages */}
-        {isVisible("sms") && smsEnabled && smsThreads.length > 0 && (
+        {isVisible("sms") && smsEnabled && (
           <Card className="border-0 shadow-sm">
             <CardHeader className="pb-3">
               <div className="flex items-center justify-between">
@@ -477,6 +589,9 @@ export function DashboardClient({
               </div>
             </CardHeader>
             <CardContent className="p-0">
+              {smsThreads.length === 0 ? (
+                <p className="px-5 py-4 text-xs text-muted-foreground">{t("messages.noData")}</p>
+              ) : (
               <div className="divide-y">
                 {smsThreads.map((thread) => (
                   <div
@@ -502,12 +617,13 @@ export function DashboardClient({
                   </div>
                 ))}
               </div>
+              )}
             </CardContent>
           </Card>
         )}
 
         {/* Recent Notifications (shown when SMS is not configured) */}
-        {isVisible("notifications") && !smsEnabled && notifications.length > 0 && (
+        {isVisible("notifications") && !smsEnabled && (
           <Card className="border-0 shadow-sm">
             <CardHeader className="pb-3">
               <div className="flex items-center justify-between">
@@ -518,6 +634,9 @@ export function DashboardClient({
               </div>
             </CardHeader>
             <CardContent className="p-0">
+              {notifications.length === 0 ? (
+                <p className="px-5 py-4 text-xs text-muted-foreground">{t("notifications.noData")}</p>
+              ) : (
               <div className="divide-y">
                 {notifications.slice(0, 5).map((n) => (
                   <div
@@ -540,20 +659,35 @@ export function DashboardClient({
                   </div>
                 ))}
               </div>
+              )}
             </CardContent>
           </Card>
         )}
 
         {/* Inspections */}
-        {isVisible("inspections") && (inProgressInspections.length > 0 || completedInspections.length > 0) && (
+        {isVisible("inspections") && (
           <Card className="border-0 shadow-sm">
             <CardHeader className="pb-3">
-              <CardTitle className="flex items-center gap-2 text-base">
-                <ClipboardCheck className="h-4 w-4" />
-                {t("inspections.title")}
-              </CardTitle>
+              <div className="flex items-center justify-between">
+                <CardTitle className="flex items-center gap-2 text-base">
+                  <ClipboardCheck className="h-4 w-4" />
+                  {t("inspections.title")}
+                </CardTitle>
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  className="h-7 text-xs gap-1"
+                  onClick={() => router.push("/inspections")}
+                >
+                  {t("viewAll")}
+                  <ArrowRight className="h-3 w-3" />
+                </Button>
+              </div>
             </CardHeader>
             <CardContent className="p-0">
+              {inProgressInspections.length === 0 && completedInspections.length === 0 ? (
+                <p className="px-5 py-4 text-xs text-muted-foreground">{t("inspections.noData")}</p>
+              ) : (
               <div className="divide-y">
                 {inProgressInspections.map((insp) => {
                   const total = insp.items.length;
@@ -639,23 +773,38 @@ export function DashboardClient({
                   );
                 })}
               </div>
+              )}
             </CardContent>
           </Card>
         )}
 
         {/* Quote Requests */}
-        {isVisible("quoteRequests") && quoteRequests.length > 0 && (
+        {isVisible("quoteRequests") && (
           <Card className="border-0 shadow-sm">
             <CardHeader className="pb-3">
-              <CardTitle className="flex items-center gap-2 text-base">
-                <FileText className="h-4 w-4" />
-                {t("quoteRequests.title")}
-              </CardTitle>
+              <div className="flex items-center justify-between">
+                <CardTitle className="flex items-center gap-2 text-base">
+                  <FileText className="h-4 w-4" />
+                  {t("quoteRequests.title")}
+                </CardTitle>
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  className="h-7 text-xs gap-1"
+                  onClick={() => router.push("/inspections")}
+                >
+                  {t("viewAll")}
+                  <ArrowRight className="h-3 w-3" />
+                </Button>
+              </div>
               <p className="text-xs text-muted-foreground">
                 {t("quoteRequests.description")}
               </p>
             </CardHeader>
             <CardContent className="p-0">
+              {quoteRequests.length === 0 ? (
+                <p className="px-5 py-4 text-xs text-muted-foreground">{t("quoteRequests.noData")}</p>
+              ) : (
               <div className="divide-y">
                 {quoteRequests.map((req) => {
                   const selectedItems = req.inspection.items.filter((i) =>
@@ -718,23 +867,38 @@ export function DashboardClient({
                   );
                 })}
               </div>
+              )}
             </CardContent>
           </Card>
         )}
 
         {/* Customer Quote Responses */}
-        {isVisible("quoteResponses") && quoteResponses.length > 0 && (
+        {isVisible("quoteResponses") && (
           <Card className="border-0 shadow-sm">
             <CardHeader className="pb-3">
-              <CardTitle className="flex items-center gap-2 text-base">
-                <FileText className="h-4 w-4" />
-                {t("quoteResponses.title")}
-              </CardTitle>
+              <div className="flex items-center justify-between">
+                <CardTitle className="flex items-center gap-2 text-base">
+                  <FileText className="h-4 w-4" />
+                  {t("quoteResponses.title")}
+                </CardTitle>
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  className="h-7 text-xs gap-1"
+                  onClick={() => router.push("/quotes")}
+                >
+                  {t("viewAll")}
+                  <ArrowRight className="h-3 w-3" />
+                </Button>
+              </div>
               <p className="text-xs text-muted-foreground">
                 {t("quoteResponses.description")}
               </p>
             </CardHeader>
             <CardContent className="p-0">
+              {quoteResponses.length === 0 ? (
+                <p className="px-5 py-4 text-xs text-muted-foreground">{t("quoteResponses.noData")}</p>
+              ) : (
               <div className="divide-y">
                 {quoteResponses.map((resp) => (
                   <div
@@ -818,6 +982,7 @@ export function DashboardClient({
                   </div>
                 ))}
               </div>
+              )}
             </CardContent>
           </Card>
         )}

+ 4 - 2
src/app/(authenticated)/page.tsx

@@ -2,7 +2,7 @@ import { getTranslations } from "next-intl/server";
 import { getDashboardStats, getUpcomingReminders } from "@/features/vehicles/Actions/dashboardActions";
 import { getSettings } from "@/features/settings/Actions/settingsActions";
 import { SETTING_KEYS } from "@/features/settings/Schema/settingsSchema";
-import { getVehiclesDueForService } from "@/features/vehicles/Actions/predictedMaintenanceActions";
+import { getVehiclesDueForService, getDismissedMaintenanceVehicles } from "@/features/vehicles/Actions/predictedMaintenanceActions";
 import { getInspectionsPaginated } from "@/features/inspections/Actions/inspectionActions";
 import { getQuoteRequests } from "@/features/inspections/Actions/quoteRequestActions";
 import { getQuoteResponses } from "@/features/quotes/Actions/quoteResponseActions";
@@ -18,11 +18,12 @@ export default async function DashboardPage() {
   const features = auth ? await getFeatures(auth.organizationId) : null;
   const smsEnabled = features?.sms ?? false;
 
-  const [result, settingsResult, remindersResult, maintenanceResult, inProgressResult, completedResult, quoteRequestsResult, quoteResponsesResult, smsResult, notificationsResult] = await Promise.all([
+  const [result, settingsResult, remindersResult, maintenanceResult, dismissedMaintenanceResult, inProgressResult, completedResult, quoteRequestsResult, quoteResponsesResult, smsResult, notificationsResult] = await Promise.all([
     getDashboardStats(),
     getSettings([SETTING_KEYS.CURRENCY_CODE, SETTING_KEYS.UNIT_SYSTEM]),
     getUpcomingReminders(),
     getVehiclesDueForService(),
+    getDismissedMaintenanceVehicles(),
     getInspectionsPaginated({ status: "in_progress", pageSize: 5 }),
     getInspectionsPaginated({ status: "completed", pageSize: 5 }),
     getQuoteRequests(),
@@ -60,6 +61,7 @@ export default async function DashboardPage() {
           currencyCode={currencyCode}
           upcomingReminders={remindersResult.success && remindersResult.data ? remindersResult.data : []}
           vehiclesDueForService={maintenanceResult.success && maintenanceResult.data ? maintenanceResult.data : []}
+          dismissedMaintenanceVehicles={dismissedMaintenanceResult.success && dismissedMaintenanceResult.data ? dismissedMaintenanceResult.data : []}
           unitSystem={unitSystem}
           inProgressInspections={inProgressResult.success && inProgressResult.data ? inProgressResult.data.records : []}
           completedInspections={completedResult.success && completedResult.data ? completedResult.data.records : []}

+ 53 - 0
src/app/(authenticated)/reminders/page.tsx

@@ -0,0 +1,53 @@
+import { getTranslations } from "next-intl/server";
+import { getAllReminders } from "@/features/vehicles/Actions/reminderActions";
+import { getVehicles } from "@/features/vehicles/Actions/vehicleActions";
+import { getSettings } from "@/features/settings/Actions/settingsActions";
+import { SETTING_KEYS } from "@/features/settings/Schema/settingsSchema";
+import { RemindersPageClient } from "@/features/vehicles/Components/RemindersPageClient";
+import { PageHeader } from "@/components/page-header";
+
+export default async function RemindersPage() {
+  const [remindersResult, vehiclesResult, settingsResult] = await Promise.all([
+    getAllReminders(),
+    getVehicles(),
+    getSettings([SETTING_KEYS.UNIT_SYSTEM]),
+  ]);
+
+  if (!remindersResult.success || !remindersResult.data) {
+    const t = await getTranslations("reminders");
+    return (
+      <>
+        <PageHeader />
+        <div className="flex h-[50vh] items-center justify-center">
+          <p className="text-muted-foreground">{t("error")}</p>
+        </div>
+      </>
+    );
+  }
+
+  const settings = settingsResult.success && settingsResult.data ? settingsResult.data : {};
+  const unitSystem = (settings[SETTING_KEYS.UNIT_SYSTEM] || "imperial") as "metric" | "imperial";
+  const vehicles = vehiclesResult.success && vehiclesResult.data
+    ? vehiclesResult.data.map((v) => ({
+        id: v.id,
+        make: v.make,
+        model: v.model,
+        year: v.year,
+        licensePlate: v.licensePlate,
+        customerName: v.customer?.name ?? null,
+      }))
+    : [];
+
+  return (
+    <>
+      <PageHeader />
+      <div className="flex flex-1 flex-col gap-4 p-4 pt-0">
+        <RemindersPageClient
+          reminders={remindersResult.data}
+          vehicles={vehicles}
+          unitSystem={unitSystem}
+        />
+      </div>
+    </>
+  );
+}

+ 22 - 10
src/app/(authenticated)/vehicles/[id]/vehicle-detail-client.tsx

@@ -8,6 +8,7 @@ import { Card, CardContent } from '@/components/ui/card'
 import { Button } from '@/components/ui/button'
 import { Badge } from '@/components/ui/badge'
 import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
 import {
   Table,
   TableBody,
@@ -487,16 +488,27 @@ export function VehicleDetailClient({
           </div>
           {predictionData && (
             <div className="flex items-center gap-1.5 text-muted-foreground">
-              <TrendingUp className="h-3.5 w-3.5" />
-              <span className="text-xs">
-                {unitSystem === 'metric' ? t('predictedKm') : t('predictedMileage')}
-              </span>
-              <span className="font-semibold text-foreground">
-                ~{predictionData.predictedMileage.toLocaleString()}
-              </span>
-              <span className="text-[10px] text-muted-foreground/70">
-                ({predictionData.confidencePercent}%)
-              </span>
+              <TooltipProvider>
+                <Tooltip>
+                  <TooltipTrigger asChild>
+                    <div className="flex cursor-help items-center gap-1.5">
+                      <TrendingUp className="h-3.5 w-3.5" />
+                      <span className="text-xs">
+                        {unitSystem === 'metric' ? t('predictedKm') : t('predictedMileage')}
+                      </span>
+                      <span className="font-semibold text-foreground">
+                        ~{predictionData.predictedMileage.toLocaleString()}
+                      </span>
+                      <span className="text-[10px] text-muted-foreground/70">
+                        ({predictionData.confidencePercent}%)
+                      </span>
+                    </div>
+                  </TooltipTrigger>
+                  <TooltipContent>
+                    <p className="max-w-xs text-xs">{t('predictedTooltip')}</p>
+                  </TooltipContent>
+                </Tooltip>
+              </TooltipProvider>
               {predictionData.status && predictionData.maintenanceDismissed ? (
                 <>
                   <Badge variant="outline" className="text-[10px] ml-1 text-muted-foreground">

+ 2 - 0
src/components/app-sidebar.tsx

@@ -38,6 +38,7 @@ import { Label } from '@/components/ui/label'
 import { Button } from '@/components/ui/button'
 import {
   BarChart3,
+  Bell,
   Building2,
   CalendarDays,
   Car,
@@ -113,6 +114,7 @@ export function AppSidebar({
 
   const workshopItems = [
     { titleKey: 'sidebar.vehicles' as const, url: '/vehicles', icon: Car },
+    { titleKey: 'sidebar.reminders' as const, url: '/reminders', icon: Bell },
     { titleKey: 'sidebar.workOrders' as const, url: '/work-orders', icon: ClipboardList },
     { titleKey: 'sidebar.inspections' as const, url: '/inspections', icon: ClipboardCheck },
     { titleKey: 'sidebar.calendar' as const, url: '/calendar', icon: CalendarDays },

+ 1 - 0
src/components/page-header.tsx

@@ -50,6 +50,7 @@ const breadcrumbMap: Record<string, BreadcrumbSegment[]> = {
   '/billing': [{ key: 'billing', href: '/billing' }, { key: 'billingHistory' }],
   '/inventory': [{ key: 'inventory', href: '/inventory' }, { key: 'allParts' }],
   '/reports': [{ key: 'reports', href: '/reports' }, { key: 'reports' }],
+  '/reminders': [{ key: 'reminders', href: '/reminders' }, { key: 'allReminders' }],
   '/work-board': [{ key: 'workBoard' }],
   '/work-board/presenter': [{ key: 'workBoard', href: '/work-board' }, { key: 'presenter' }],
   '/admin': [{ key: 'adminOverview' }],

+ 45 - 2
src/features/vehicles/Actions/predictedMaintenanceActions.ts

@@ -36,6 +36,12 @@ function calculateConfidencePercent(dataPoints: number, totalDays: number): numb
 
 export async function getVehiclePredictedMileage(vehicleId: string) {
   return withAuth(async ({ organizationId }) => {
+    const vehicle = await db.vehicle.findFirst({
+      where: { id: vehicleId, organizationId },
+      select: { mileage: true },
+    });
+    if (!vehicle) return null;
+
     const records = await db.serviceRecord.findMany({
       where: {
         vehicleId,
@@ -71,8 +77,10 @@ export async function getVehiclePredictedMileage(vehicleId: string) {
     // Project from latest known data point to today
     const daysSinceLatest =
       (Date.now() - latest.date.getTime()) / (1000 * 60 * 60 * 24);
-    const predictedMileage = Math.round(
-      latest.mileage + daysSinceLatest * avgPerDay
+    // Never predict less than the vehicle's actual recorded mileage
+    const predictedMileage = Math.max(
+      vehicle.mileage,
+      Math.round(latest.mileage + daysSinceLatest * avgPerDay)
     );
 
     return {
@@ -207,3 +215,38 @@ export async function getVehiclesDueForService() {
     return results;
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.VEHICLES }] });
 }
+
+export type DismissedMaintenanceVehicle = {
+  vehicleId: string;
+  make: string;
+  model: string;
+  year: number;
+  licensePlate: string | null;
+  dismissedAt: Date | null;
+};
+
+export async function getDismissedMaintenanceVehicles() {
+  return withAuth(async ({ organizationId }) => {
+    const vehicles = await db.vehicle.findMany({
+      where: { organizationId, isArchived: false, maintenanceDismissed: true },
+      orderBy: { maintenanceDismissedAt: "desc" },
+      select: {
+        id: true,
+        make: true,
+        model: true,
+        year: true,
+        licensePlate: true,
+        maintenanceDismissedAt: true,
+      },
+    });
+
+    return vehicles.map((v) => ({
+      vehicleId: v.id,
+      make: v.make,
+      model: v.model,
+      year: v.year,
+      licensePlate: v.licensePlate,
+      dismissedAt: v.maintenanceDismissedAt,
+    })) satisfies DismissedMaintenanceVehicle[];
+  }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.VEHICLES }] });
+}

+ 27 - 0
src/features/vehicles/Actions/reminderActions.ts

@@ -6,6 +6,29 @@ import { PermissionAction, PermissionSubject } from "@/lib/permissions";
 import { createReminderSchema, updateReminderSchema } from "../Schema/reminderSchema";
 import { revalidatePath } from "next/cache";
 
+export async function getAllReminders() {
+  return withAuth(async ({ organizationId }) => {
+    return db.reminder.findMany({
+      where: {
+        vehicle: { organizationId },
+      },
+      include: {
+        vehicle: {
+          select: {
+            id: true,
+            make: true,
+            model: true,
+            year: true,
+            licensePlate: true,
+            mileage: true,
+          },
+        },
+      },
+      orderBy: [{ isCompleted: "asc" }, { dueDate: "asc" }],
+    });
+  }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.VEHICLES }] });
+}
+
 export async function createReminder(input: unknown) {
   return withAuth(async ({ organizationId }) => {
     const data = createReminderSchema.parse(input);
@@ -22,6 +45,7 @@ export async function createReminder(input: unknown) {
     });
     revalidatePath(`/vehicles/${data.vehicleId}`);
     revalidatePath("/");
+    revalidatePath("/reminders");
     return reminder;
   }, { requiredPermissions: [{ action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES }] });
 }
@@ -45,6 +69,7 @@ export async function updateReminder(input: unknown) {
     });
     revalidatePath(`/vehicles/${reminder.vehicleId}`);
     revalidatePath("/");
+    revalidatePath("/reminders");
     return updated;
   }, { requiredPermissions: [{ action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES }] });
 }
@@ -62,6 +87,7 @@ export async function toggleReminder(reminderId: string) {
     });
     revalidatePath(`/vehicles/${reminder.vehicleId}`);
     revalidatePath("/");
+    revalidatePath("/reminders");
   }, { requiredPermissions: [{ action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES }] });
 }
 
@@ -75,5 +101,6 @@ export async function deleteReminder(reminderId: string) {
     await db.reminder.delete({ where: { id: reminderId } });
     revalidatePath(`/vehicles/${reminder.vehicleId}`);
     revalidatePath("/");
+    revalidatePath("/reminders");
   }, { requiredPermissions: [{ action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES }] });
 }

+ 480 - 0
src/features/vehicles/Components/RemindersPageClient.tsx

@@ -0,0 +1,480 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { useTranslations } from "next-intl";
+import { useFormatDate } from "@/lib/use-format-date";
+import { format } from "date-fns";
+import { Card, CardContent } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+import { Calendar } from "@/components/ui/calendar";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+import {
+  Command,
+  CommandEmpty,
+  CommandGroup,
+  CommandInput,
+  CommandItem,
+  CommandList,
+} from "@/components/ui/command";
+import {
+  Dialog,
+  DialogContent,
+  DialogHeader,
+  DialogTitle,
+} from "@/components/ui/dialog";
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+  AlertTriangle,
+  Bell,
+  CalendarIcon,
+  Check,
+  CheckCircle2,
+  ChevronsUpDown,
+  Clock,
+  MoreVertical,
+  Pencil,
+  Plus,
+  Trash2,
+} from "lucide-react";
+import { toast } from "sonner";
+import { cn } from "@/lib/utils";
+import { useGlassModal } from "@/components/glass-modal";
+import { Loader2 } from "lucide-react";
+import {
+  toggleReminder,
+  deleteReminder,
+  createReminder,
+  updateReminder,
+} from "../Actions/reminderActions";
+
+interface Reminder {
+  id: string;
+  title: string;
+  description: string | null;
+  dueDate: Date | null;
+  dueMileage: number | null;
+  isCompleted: boolean;
+  createdAt: Date;
+  vehicle: {
+    id: string;
+    make: string;
+    model: string;
+    year: number;
+    licensePlate: string | null;
+    mileage: number;
+  };
+}
+
+interface VehicleOption {
+  id: string;
+  make: string;
+  model: string;
+  year: number;
+  licensePlate: string | null;
+  customerName: string | null;
+}
+
+interface RemindersPageClientProps {
+  reminders: Reminder[];
+  vehicles: VehicleOption[];
+  unitSystem: "metric" | "imperial";
+}
+
+type FilterType = "active" | "completed" | "all";
+
+function getUrgency(r: Reminder): "overdue" | "due-soon" | "normal" {
+  if (r.isCompleted) return "normal";
+  const now = new Date();
+  if (r.dueDate && new Date(r.dueDate) < now) return "overdue";
+  if (r.dueDate) {
+    const sevenDays = new Date(now);
+    sevenDays.setDate(sevenDays.getDate() + 7);
+    if (new Date(r.dueDate) <= sevenDays) return "due-soon";
+  }
+  return "normal";
+}
+
+export function RemindersPageClient({ reminders, vehicles, unitSystem }: RemindersPageClientProps) {
+  const t = useTranslations("reminders");
+  const tv = useTranslations("vehicles.reminders");
+  const tc = useTranslations("common.buttons");
+  const router = useRouter();
+  const modal = useGlassModal();
+  const { formatDate } = useFormatDate();
+  const distUnit = unitSystem === "metric" ? "km" : "mi";
+
+  const [filter, setFilter] = useState<FilterType>("active");
+  const [showForm, setShowForm] = useState(false);
+  const [editingReminder, setEditingReminder] = useState<Reminder | undefined>();
+
+  // Form state
+  const [formVehicleId, setFormVehicleId] = useState("");
+  const [formTitle, setFormTitle] = useState("");
+  const [formDescription, setFormDescription] = useState("");
+  const [formDueDate, setFormDueDate] = useState<Date | undefined>();
+  const [formDueMileage, setFormDueMileage] = useState("");
+  const [formLoading, setFormLoading] = useState(false);
+  const [vehicleOpen, setVehicleOpen] = useState(false);
+  const [calendarOpen, setCalendarOpen] = useState(false);
+
+  const filtered = reminders.filter((r) => {
+    if (filter === "active") return !r.isCompleted;
+    if (filter === "completed") return r.isCompleted;
+    return true;
+  });
+
+  const overdueCount = reminders.filter((r) => !r.isCompleted && getUrgency(r) === "overdue").length;
+
+  const handleToggle = async (id: string) => {
+    await toggleReminder(id);
+    router.refresh();
+  };
+
+  const handleDelete = async (id: string) => {
+    const result = await deleteReminder(id);
+    if (result.success) {
+      toast.success(t("deleted"));
+      router.refresh();
+    } else {
+      toast.error(t("deleteError"));
+    }
+  };
+
+  const openAddForm = () => {
+    setEditingReminder(undefined);
+    setFormVehicleId("");
+    setFormTitle("");
+    setFormDescription("");
+    setFormDueDate(undefined);
+    setFormDueMileage("");
+    setShowForm(true);
+  };
+
+  const openEditForm = (r: Reminder) => {
+    setEditingReminder(r);
+    setFormVehicleId(r.vehicle.id);
+    setFormTitle(r.title);
+    setFormDescription(r.description || "");
+    setFormDueDate(r.dueDate ? new Date(r.dueDate) : undefined);
+    setFormDueMileage(r.dueMileage ? String(r.dueMileage) : "");
+    setShowForm(true);
+  };
+
+  const handleFormSubmit = async (e: React.FormEvent) => {
+    e.preventDefault();
+    if (!formVehicleId) return;
+    setFormLoading(true);
+
+    const payload = {
+      vehicleId: formVehicleId,
+      title: formTitle,
+      description: formDescription || undefined,
+      dueDate: formDueDate ? formDueDate.toISOString().split("T")[0] : undefined,
+      dueMileage: formDueMileage ? Number(formDueMileage) : undefined,
+    };
+
+    const result = editingReminder
+      ? await updateReminder({ ...payload, id: editingReminder.id })
+      : await createReminder(payload);
+
+    if (result.success) {
+      toast.success(editingReminder ? tv("reminderUpdated") : tv("reminderCreated"));
+      setShowForm(false);
+      router.refresh();
+    } else {
+      modal.open("error", "Error", result.error || tv("saveError", { action: editingReminder ? "update" : "create" }));
+    }
+    setFormLoading(false);
+  };
+
+  const selectedVehicle = vehicles.find((v) => v.id === formVehicleId);
+
+  return (
+    <>
+      <div className="flex flex-wrap items-center justify-between gap-3">
+        <div className="flex items-center gap-2">
+          <div className="flex gap-1 rounded-lg border p-1">
+            {(["active", "completed", "all"] as const).map((f) => (
+              <button
+                key={f}
+                onClick={() => setFilter(f)}
+                className={`rounded-md px-3 py-1 text-xs font-medium transition-colors ${
+                  filter === f
+                    ? "bg-primary text-primary-foreground"
+                    : "text-muted-foreground hover:text-foreground"
+                }`}
+              >
+                {tv(f)}
+              </button>
+            ))}
+          </div>
+          {overdueCount > 0 && (
+            <Badge variant="destructive" className="text-xs">
+              {overdueCount} {tv("overdue").toLowerCase()}
+            </Badge>
+          )}
+        </div>
+        <div className="flex items-center gap-2">
+          <span className="text-xs text-muted-foreground">
+            {t("showing", { count: filtered.length, total: reminders.length })}
+          </span>
+          <Button size="sm" onClick={openAddForm}>
+            <Plus className="mr-1 h-3.5 w-3.5" />
+            {tv("addReminder")}
+          </Button>
+        </div>
+      </div>
+
+      {filtered.length === 0 ? (
+        <Card className="border-dashed">
+          <CardContent className="flex flex-col items-center py-12">
+            <Bell className="mb-3 h-10 w-10 text-muted-foreground/40" />
+            <p className="text-sm text-muted-foreground">
+              {filter === "active"
+                ? tv("emptyActive")
+                : filter === "completed"
+                  ? tv("emptyCompleted")
+                  : tv("empty")}
+            </p>
+          </CardContent>
+        </Card>
+      ) : (
+        <Card className="border-0 shadow-sm">
+          <CardContent className="p-0">
+            <div className="divide-y">
+              {filtered.map((r) => {
+                const urgency = getUrgency(r);
+                return (
+                  <div
+                    key={r.id}
+                    className="flex items-center gap-3 px-4 py-2.5 hover:bg-muted/50 transition-colors"
+                  >
+                    <button
+                      onClick={() => handleToggle(r.id)}
+                      className={`flex h-5 w-5 shrink-0 items-center justify-center rounded-full border-2 transition-colors ${
+                        r.isCompleted
+                          ? "border-primary bg-primary/10"
+                          : "border-muted-foreground/40 hover:border-primary"
+                      }`}
+                    >
+                      {r.isCompleted && <CheckCircle2 className="h-4 w-4 text-primary" />}
+                    </button>
+                    <div className="flex-1 min-w-0 flex flex-wrap items-center gap-x-3 gap-y-0.5">
+                      <span
+                        className={`text-sm font-medium ${r.isCompleted ? "line-through text-muted-foreground" : ""}`}
+                      >
+                        {r.title}
+                      </span>
+                      {urgency === "overdue" && (
+                        <Badge variant="destructive" className="text-[10px] px-1.5 py-0 h-4">
+                          <AlertTriangle className="mr-0.5 h-2.5 w-2.5" />
+                          {tv("overdue")}
+                        </Badge>
+                      )}
+                      {urgency === "due-soon" && (
+                        <Badge className="bg-amber-500/15 text-amber-600 border-amber-500/20 text-[10px] px-1.5 py-0 h-4">
+                          <Clock className="mr-0.5 h-2.5 w-2.5" />
+                          {tv("dueSoon")}
+                        </Badge>
+                      )}
+                      <span className="text-xs text-muted-foreground">
+                        <span
+                          className="hover:underline cursor-pointer"
+                          onClick={() => router.push(`/vehicles/${r.vehicle.id}`)}
+                        >
+                          {r.vehicle.year} {r.vehicle.make} {r.vehicle.model}
+                          {r.vehicle.licensePlate && ` · ${r.vehicle.licensePlate}`}
+                        </span>
+                      </span>
+                    </div>
+                    <div className="shrink-0 flex items-center gap-2 text-xs text-muted-foreground">
+                      {r.dueDate && (
+                        <span>{formatDate(new Date(r.dueDate))}</span>
+                      )}
+                      {r.dueMileage && (
+                        <span>{r.dueMileage.toLocaleString()} {distUnit}</span>
+                      )}
+                    </div>
+                    <DropdownMenu>
+                      <DropdownMenuTrigger asChild>
+                        <Button variant="ghost" size="icon" className="h-6 w-6 shrink-0">
+                          <MoreVertical className="h-3.5 w-3.5" />
+                        </Button>
+                      </DropdownMenuTrigger>
+                      <DropdownMenuContent align="end">
+                        <DropdownMenuItem onClick={() => openEditForm(r)}>
+                          <Pencil className="mr-2 h-4 w-4" />
+                          {tc("edit")}
+                        </DropdownMenuItem>
+                        <DropdownMenuItem
+                          className="text-destructive"
+                          onClick={() => handleDelete(r.id)}
+                        >
+                          <Trash2 className="mr-2 h-4 w-4" />
+                          {tc("delete")}
+                        </DropdownMenuItem>
+                      </DropdownMenuContent>
+                    </DropdownMenu>
+                  </div>
+                );
+              })}
+            </div>
+          </CardContent>
+        </Card>
+      )}
+
+      {/* Add/Edit Reminder Dialog */}
+      <Dialog open={showForm} onOpenChange={setShowForm}>
+        <DialogContent className="sm:max-w-md">
+          <DialogHeader>
+            <DialogTitle>{editingReminder ? tv("editTitle") : tv("addTitle")}</DialogTitle>
+          </DialogHeader>
+          <form onSubmit={handleFormSubmit} className="space-y-4">
+            {/* Vehicle selector */}
+            <div className="space-y-2">
+              <Label>{t("vehicle")}</Label>
+              <Popover open={vehicleOpen} onOpenChange={setVehicleOpen}>
+                <PopoverTrigger asChild>
+                  <Button
+                    variant="outline"
+                    role="combobox"
+                    aria-expanded={vehicleOpen}
+                    className="w-full justify-between font-normal"
+                    disabled={!!editingReminder}
+                  >
+                    {selectedVehicle
+                      ? `${selectedVehicle.year} ${selectedVehicle.make} ${selectedVehicle.model}${selectedVehicle.licensePlate ? ` · ${selectedVehicle.licensePlate}` : ""}`
+                      : t("selectVehicle")}
+                    <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
+                  </Button>
+                </PopoverTrigger>
+                <PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
+                  <Command>
+                    <CommandInput placeholder={t("searchVehicle")} />
+                    <CommandList>
+                      <CommandEmpty>{t("noVehicle")}</CommandEmpty>
+                      <CommandGroup>
+                        {vehicles.map((v) => (
+                          <CommandItem
+                            key={v.id}
+                            value={`${v.year} ${v.make} ${v.model} ${v.licensePlate || ""} ${v.customerName || ""}`}
+                            onSelect={() => {
+                              setFormVehicleId(v.id);
+                              setVehicleOpen(false);
+                            }}
+                          >
+                            <Check
+                              className={cn(
+                                "mr-2 h-4 w-4",
+                                formVehicleId === v.id ? "opacity-100" : "opacity-0"
+                              )}
+                            />
+                            <div>
+                              <p className="text-sm">
+                                {v.year} {v.make} {v.model}
+                                {v.licensePlate && <span className="ml-1.5 text-muted-foreground">· {v.licensePlate}</span>}
+                              </p>
+                              {v.customerName && (
+                                <p className="text-xs text-muted-foreground">{v.customerName}</p>
+                              )}
+                            </div>
+                          </CommandItem>
+                        ))}
+                      </CommandGroup>
+                    </CommandList>
+                  </Command>
+                </PopoverContent>
+              </Popover>
+            </div>
+
+            {/* Title */}
+            <div className="space-y-2">
+              <Label htmlFor="reminder-title">{tv("titleLabel")}</Label>
+              <Input
+                id="reminder-title"
+                value={formTitle}
+                onChange={(e) => setFormTitle(e.target.value)}
+                placeholder={tv("titlePlaceholder")}
+                required
+              />
+            </div>
+
+            {/* Description */}
+            <div className="space-y-2">
+              <Label htmlFor="reminder-desc">{tv("descriptionLabel")}</Label>
+              <Textarea
+                id="reminder-desc"
+                value={formDescription}
+                onChange={(e) => setFormDescription(e.target.value)}
+                placeholder={tv("descriptionPlaceholder")}
+                rows={2}
+              />
+            </div>
+
+            {/* Due date + mileage */}
+            <div className="grid grid-cols-2 gap-4">
+              <div className="space-y-2">
+                <Label>{tv("dueDateLabel")}</Label>
+                <Popover open={calendarOpen} onOpenChange={setCalendarOpen}>
+                  <PopoverTrigger asChild>
+                    <Button
+                      variant="outline"
+                      className={cn(
+                        "w-full justify-start text-left font-normal",
+                        !formDueDate && "text-muted-foreground"
+                      )}
+                    >
+                      <CalendarIcon className="mr-2 h-4 w-4" />
+                      {formDueDate ? format(formDueDate, "PPP") : t("pickDate")}
+                    </Button>
+                  </PopoverTrigger>
+                  <PopoverContent className="w-auto p-0" align="start">
+                    <Calendar
+                      mode="single"
+                      selected={formDueDate}
+                      onSelect={(date) => {
+                        setFormDueDate(date);
+                        setCalendarOpen(false);
+                      }}
+                      initialFocus
+                    />
+                  </PopoverContent>
+                </Popover>
+              </div>
+              <div className="space-y-2">
+                <Label htmlFor="reminder-mileage">{tv("dueMileageLabel")}</Label>
+                <Input
+                  id="reminder-mileage"
+                  type="number"
+                  value={formDueMileage}
+                  onChange={(e) => setFormDueMileage(e.target.value)}
+                  placeholder={tv("dueMileagePlaceholder")}
+                />
+              </div>
+            </div>
+
+            <div className="flex justify-end gap-3 pt-2">
+              <Button type="button" variant="outline" onClick={() => setShowForm(false)}>
+                {tc("cancel")}
+              </Button>
+              <Button type="submit" disabled={formLoading || !formVehicleId}>
+                {formLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+                {editingReminder ? tc("saveChanges") : tv("addTitle")}
+              </Button>
+            </div>
+          </form>
+        </DialogContent>
+      </Dialog>
+    </>
+  );
+}

+ 8 - 2
src/hooks/use-dashboard-visibility.ts

@@ -18,7 +18,7 @@ export type DashboardCardId = (typeof DASHBOARD_CARD_IDS)[number];
 
 const STORAGE_KEY = "torqvoice-dashboard-hidden";
 
-export function useDashboardVisibility() {
+export function useDashboardVisibility(excludeId?: DashboardCardId) {
   const [hidden, setHidden] = useState<Set<string>>(() => {
     if (typeof window !== "undefined") {
       try {
@@ -46,5 +46,11 @@ export function useDashboardVisibility() {
 
   const isVisible = useCallback((id: string) => !hidden.has(id), [hidden]);
 
-  return { toggleCard, isVisible };
+  const applicableIds = excludeId
+    ? DASHBOARD_CARD_IDS.filter((id) => id !== excludeId)
+    : DASHBOARD_CARD_IDS;
+  const visibleCount = applicableIds.filter((id) => !hidden.has(id)).length;
+  const totalCount = applicableIds.length;
+
+  return { toggleCard, isVisible, visibleCount, totalCount };
 }

+ 2 - 0
src/i18n/request.ts

@@ -63,6 +63,7 @@ export default getRequestConfig(async () => {
   const notifications = (await import(`../../messages/${locale}/notifications.json`)).default
   const quotes = (await import(`../../messages/${locale}/quotes.json`)).default
   const billing = (await import(`../../messages/${locale}/billing.json`)).default
+  const reminders = (await import(`../../messages/${locale}/reminders.json`)).default
 
   return {
     locale,
@@ -88,6 +89,7 @@ export default getRequestConfig(async () => {
       notifications,
       quotes,
       billing,
+      reminders,
     },
   }
 })