Просмотр исходного кода

Add Recent Observations dashboard card and /observations page (#143)

* dashboard observation

* filter
Bernt Christian Egeland 5 месяцев назад
Родитель
Сommit
9034b5c7d2
43 измененных файлов с 784 добавлено и 25 удалено
  1. 10 1
      messages/de/dashboard.json
  2. 2 1
      messages/de/navigation.json
  3. 14 0
      messages/de/vehicles.json
  4. 10 1
      messages/en/dashboard.json
  5. 2 1
      messages/en/navigation.json
  6. 14 0
      messages/en/vehicles.json
  7. 10 1
      messages/es/dashboard.json
  8. 2 1
      messages/es/navigation.json
  9. 14 0
      messages/es/vehicles.json
  10. 10 1
      messages/fr/dashboard.json
  11. 2 1
      messages/fr/navigation.json
  12. 14 0
      messages/fr/vehicles.json
  13. 10 1
      messages/it/dashboard.json
  14. 2 1
      messages/it/navigation.json
  15. 14 0
      messages/it/vehicles.json
  16. 10 1
      messages/lt/dashboard.json
  17. 2 1
      messages/lt/navigation.json
  18. 14 0
      messages/lt/vehicles.json
  19. 10 1
      messages/nb/dashboard.json
  20. 2 1
      messages/nb/navigation.json
  21. 14 0
      messages/nb/vehicles.json
  22. 10 1
      messages/nl/dashboard.json
  23. 2 1
      messages/nl/navigation.json
  24. 14 0
      messages/nl/vehicles.json
  25. 10 1
      messages/pl/dashboard.json
  26. 2 1
      messages/pl/navigation.json
  27. 14 0
      messages/pl/vehicles.json
  28. 10 1
      messages/pt-BR/dashboard.json
  29. 2 1
      messages/pt-BR/navigation.json
  30. 14 0
      messages/pt-BR/vehicles.json
  31. 10 1
      messages/ru/dashboard.json
  32. 2 1
      messages/ru/navigation.json
  33. 14 0
      messages/ru/vehicles.json
  34. 10 1
      messages/tr/dashboard.json
  35. 2 1
      messages/tr/navigation.json
  36. 14 0
      messages/tr/vehicles.json
  37. 93 0
      src/app/(authenticated)/dashboard-client.tsx
  38. 219 0
      src/app/(authenticated)/observations/observations-client.tsx
  39. 52 0
      src/app/(authenticated)/observations/page.tsx
  40. 5 1
      src/app/(authenticated)/page.tsx
  41. 1 0
      src/components/page-header.tsx
  42. 101 0
      src/features/vehicles/Actions/findingActions.ts
  43. 1 0
      src/hooks/use-dashboard-visibility.ts

+ 10 - 1
messages/de/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Letzte Aktivitäten",
   "noRecentActivity": "Keine aktuellen Aktivitäten",
+  "recentObservations": "Aktuelle Beobachtungen",
+  "noRecentObservations": "Keine offenen Beobachtungen",
+  "observations": {
+    "severity": "Schweregrad",
+    "vehicle": "Fahrzeug",
+    "description": "Beschreibung",
+    "when": "Wann"
+  },
   "unknownUser": "Benutzer",
   "stats": {
     "activeJobs": "Aktive Aufträge",
@@ -41,7 +49,8 @@
     "sms": "Letzte Nachrichten",
     "notifications": "Letzte Benachrichtigungen",
     "recentCompleted": "Kürzlich abgeschlossen",
-    "activeJobs": "Aktive Aufträge"
+    "activeJobs": "Aktive Aufträge",
+    "recentObservations": "Aktuelle Beobachtungen"
   },
   "maintenance": {
     "title": "Voraussichtlich fällige Wartung",

+ 2 - 1
messages/de/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Erinnerungen",
     "allReminders": "Alle Erinnerungen",
     "auditLog": "Aktivitätsprotokoll",
-    "aiAssistant": "KI-Assistent"
+    "aiAssistant": "KI-Assistent",
+    "observations": "Beobachtungen"
   },
   "search": "Suchen...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/de/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "pro Seite",
     "page": "Seite {page} von {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Beschreibung, Notizen oder Fahrzeug suchen...",
+    "allStatuses": "Alle Status",
+    "allSeverities": "Alle Schweregrade",
+    "empty": "Keine Beobachtungen gefunden.",
+    "columns": {
+      "severity": "Schweregrad",
+      "status": "Status",
+      "vehicle": "Fahrzeug",
+      "description": "Beschreibung",
+      "notes": "Notizen",
+      "created": "Erstellt"
+    }
+  },
   "form": {
     "editTitle": "Fahrzeug bearbeiten",
     "addTitle": "Neues Fahrzeug hinzuf\u00fcgen",

+ 10 - 1
messages/en/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Recent Activity",
   "noRecentActivity": "No recent activity",
+  "recentObservations": "Recent Observations",
+  "noRecentObservations": "No open observations",
+  "observations": {
+    "severity": "Severity",
+    "vehicle": "Vehicle",
+    "description": "Description",
+    "when": "When"
+  },
   "unknownUser": "User",
   "stats": {
     "activeJobs": "Active Jobs",
@@ -41,7 +49,8 @@
     "sms": "Recent Messages",
     "notifications": "Recent Notifications",
     "recentCompleted": "Recent Completed",
-    "activeJobs": "Active Jobs"
+    "activeJobs": "Active Jobs",
+    "recentObservations": "Recent Observations"
   },
   "maintenance": {
     "title": "Predicted Maintenance Due",

+ 2 - 1
messages/en/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Reminders",
     "allReminders": "All Reminders",
     "auditLog": "Audit Log",
-    "aiAssistant": "AI Assistant"
+    "aiAssistant": "AI Assistant",
+    "observations": "Observations"
   },
   "search": "Search...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/en/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "per page",
     "page": "Page {page} of {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Search description, notes, or vehicle...",
+    "allStatuses": "All Statuses",
+    "allSeverities": "All Severities",
+    "empty": "No observations found.",
+    "columns": {
+      "severity": "Severity",
+      "status": "Status",
+      "vehicle": "Vehicle",
+      "description": "Description",
+      "notes": "Notes",
+      "created": "Created"
+    }
+  },
   "form": {
     "editTitle": "Edit Vehicle",
     "addTitle": "Add New Vehicle",

+ 10 - 1
messages/es/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Actividad reciente",
   "noRecentActivity": "Sin actividad reciente",
+  "recentObservations": "Observaciones recientes",
+  "noRecentObservations": "Sin observaciones abiertas",
+  "observations": {
+    "severity": "Gravedad",
+    "vehicle": "Vehículo",
+    "description": "Descripción",
+    "when": "Cuándo"
+  },
   "unknownUser": "Usuario",
   "stats": {
     "activeJobs": "Trabajos activos",
@@ -41,7 +49,8 @@
     "sms": "Mensajes recientes",
     "notifications": "Notificaciones recientes",
     "recentCompleted": "Completados recientemente",
-    "activeJobs": "Trabajos activos"
+    "activeJobs": "Trabajos activos",
+    "recentObservations": "Observaciones recientes"
   },
   "maintenance": {
     "title": "Mantenimiento previsto pendiente",

+ 2 - 1
messages/es/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Recordatorios",
     "allReminders": "Todos los recordatorios",
     "auditLog": "Registro de auditoría",
-    "aiAssistant": "Asistente IA"
+    "aiAssistant": "Asistente IA",
+    "observations": "Observaciones"
   },
   "search": "Buscar...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/es/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "por página",
     "page": "Página {page} de {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Buscar descripción, notas o vehículo...",
+    "allStatuses": "Todos los estados",
+    "allSeverities": "Todas las gravedades",
+    "empty": "No se encontraron observaciones.",
+    "columns": {
+      "severity": "Gravedad",
+      "status": "Estado",
+      "vehicle": "Vehículo",
+      "description": "Descripción",
+      "notes": "Notas",
+      "created": "Creado"
+    }
+  },
   "form": {
     "editTitle": "Editar vehiculo",
     "addTitle": "Agregar nuevo vehiculo",

+ 10 - 1
messages/fr/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Activité récente",
   "noRecentActivity": "Aucune activité récente",
+  "recentObservations": "Observations récentes",
+  "noRecentObservations": "Aucune observation ouverte",
+  "observations": {
+    "severity": "Gravité",
+    "vehicle": "Véhicule",
+    "description": "Description",
+    "when": "Quand"
+  },
   "unknownUser": "Utilisateur",
   "stats": {
     "activeJobs": "Travaux en cours",
@@ -41,7 +49,8 @@
     "sms": "Messages récents",
     "notifications": "Notifications récentes",
     "recentCompleted": "Terminés récemment",
-    "activeJobs": "Travaux en cours"
+    "activeJobs": "Travaux en cours",
+    "recentObservations": "Observations récentes"
   },
   "maintenance": {
     "title": "Maintenance prévue à effectuer",

+ 2 - 1
messages/fr/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Rappels",
     "allReminders": "Tous les rappels",
     "auditLog": "Journal d'audit",
-    "aiAssistant": "Assistant IA"
+    "aiAssistant": "Assistant IA",
+    "observations": "Observations"
   },
   "search": "Rechercher…",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/fr/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "par page",
     "page": "Page {page} sur {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Rechercher une description, des notes ou un véhicule...",
+    "allStatuses": "Tous les statuts",
+    "allSeverities": "Toutes les gravités",
+    "empty": "Aucune observation trouvée.",
+    "columns": {
+      "severity": "Gravité",
+      "status": "Statut",
+      "vehicle": "Véhicule",
+      "description": "Description",
+      "notes": "Notes",
+      "created": "Créé"
+    }
+  },
   "form": {
     "editTitle": "Modifier le v\u00e9hicule",
     "addTitle": "Ajouter un nouveau v\u00e9hicule",

+ 10 - 1
messages/it/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Attività recente",
   "noRecentActivity": "Nessuna attività recente",
+  "recentObservations": "Osservazioni recenti",
+  "noRecentObservations": "Nessuna osservazione aperta",
+  "observations": {
+    "severity": "Gravità",
+    "vehicle": "Veicolo",
+    "description": "Descrizione",
+    "when": "Quando"
+  },
   "unknownUser": "Utente",
   "stats": {
     "activeJobs": "Lavori attivi",
@@ -41,7 +49,8 @@
     "sms": "Messaggi recenti",
     "notifications": "Notifiche recenti",
     "recentCompleted": "Completati di recente",
-    "activeJobs": "Lavori attivi"
+    "activeJobs": "Lavori attivi",
+    "recentObservations": "Osservazioni recenti"
   },
   "maintenance": {
     "title": "Manutenzione prevista in scadenza",

+ 2 - 1
messages/it/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Promemoria",
     "allReminders": "Tutti i promemoria",
     "auditLog": "Registro attività",
-    "aiAssistant": "Assistente IA"
+    "aiAssistant": "Assistente IA",
+    "observations": "Osservazioni"
   },
   "search": "Cerca...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/it/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "per pagina",
     "page": "Pagina {page} di {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Cerca descrizione, note o veicolo...",
+    "allStatuses": "Tutti gli stati",
+    "allSeverities": "Tutte le gravità",
+    "empty": "Nessuna osservazione trovata.",
+    "columns": {
+      "severity": "Gravità",
+      "status": "Stato",
+      "vehicle": "Veicolo",
+      "description": "Descrizione",
+      "notes": "Note",
+      "created": "Creato"
+    }
+  },
   "form": {
     "editTitle": "Modifica Veicolo",
     "addTitle": "Aggiungi Nuovo Veicolo",

+ 10 - 1
messages/lt/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Naujausios veiklos",
   "noRecentActivity": "Naujausių veiklų nėra",
+  "recentObservations": "Naujausi pastebėjimai",
+  "noRecentObservations": "Atvirų pastebėjimų nėra",
+  "observations": {
+    "severity": "Sunkumas",
+    "vehicle": "Transporto priemonė",
+    "description": "Aprašymas",
+    "when": "Kada"
+  },
   "unknownUser": "Naudotojas",
   "stats": {
     "activeJobs": "Aktyvūs darbai",
@@ -41,7 +49,8 @@
     "sms": "Naujausios žinutės",
     "notifications": "Naujausi pranešimai",
     "recentCompleted": "Neseniai užbaigti",
-    "activeJobs": "Aktyvūs darbai"
+    "activeJobs": "Aktyvūs darbai",
+    "recentObservations": "Naujausi pastebėjimai"
   },
   "maintenance": {
     "title": "Numatoma priežiūra",

+ 2 - 1
messages/lt/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Priminimai",
     "allReminders": "Visi priminimai",
     "auditLog": "Audito žurnalas",
-    "aiAssistant": "DI asistentas"
+    "aiAssistant": "DI asistentas",
+    "observations": "Pastebėjimai"
   },
   "search": "Ieškoti...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/lt/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "puslapyje",
     "page": "Puslapis {page} iš {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Ieškoti pagal aprašymą, pastabas arba transporto priemonę...",
+    "allStatuses": "Visos būsenos",
+    "allSeverities": "Visi sunkumo lygiai",
+    "empty": "Pastebėjimų nerasta.",
+    "columns": {
+      "severity": "Sunkumas",
+      "status": "Būsena",
+      "vehicle": "Transporto priemonė",
+      "description": "Aprašymas",
+      "notes": "Pastabos",
+      "created": "Sukurta"
+    }
+  },
   "form": {
     "editTitle": "Redaguoti transporto priemonę",
     "addTitle": "Pridėti naują transporto priemonę",

+ 10 - 1
messages/nb/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Nylig aktivitet",
   "noRecentActivity": "Ingen nylig aktivitet",
+  "recentObservations": "Nylige observasjoner",
+  "noRecentObservations": "Ingen åpne observasjoner",
+  "observations": {
+    "severity": "Alvorlighetsgrad",
+    "vehicle": "Kjøretøy",
+    "description": "Beskrivelse",
+    "when": "Når"
+  },
   "unknownUser": "Bruker",
   "stats": {
     "activeJobs": "Aktive jobber",
@@ -41,7 +49,8 @@
     "sms": "Siste meldinger",
     "notifications": "Siste varsler",
     "recentCompleted": "Nylig fullført",
-    "activeJobs": "Aktive jobber"
+    "activeJobs": "Aktive jobber",
+    "recentObservations": "Nylige observasjoner"
   },
   "maintenance": {
     "title": "Forventet vedlikehold",

+ 2 - 1
messages/nb/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Påminnelser",
     "allReminders": "Alle påminnelser",
     "auditLog": "Aktivitetslogg",
-    "aiAssistant": "AI-assistent"
+    "aiAssistant": "AI-assistent",
+    "observations": "Observasjoner"
   },
   "search": "Søk...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/nb/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "per side",
     "page": "Side {page} av {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Søk i beskrivelse, notater eller kjøretøy...",
+    "allStatuses": "Alle statuser",
+    "allSeverities": "Alle alvorlighetsgrader",
+    "empty": "Ingen observasjoner funnet.",
+    "columns": {
+      "severity": "Alvorlighetsgrad",
+      "status": "Status",
+      "vehicle": "Kjøretøy",
+      "description": "Beskrivelse",
+      "notes": "Notater",
+      "created": "Opprettet"
+    }
+  },
   "form": {
     "editTitle": "Rediger kjoretoy",
     "addTitle": "Legg til nytt kjoretoy",

+ 10 - 1
messages/nl/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Recente activiteit",
   "noRecentActivity": "Geen recente activiteit",
+  "recentObservations": "Recente waarnemingen",
+  "noRecentObservations": "Geen openstaande waarnemingen",
+  "observations": {
+    "severity": "Ernst",
+    "vehicle": "Voertuig",
+    "description": "Beschrijving",
+    "when": "Wanneer"
+  },
   "unknownUser": "Gebruiker",
   "stats": {
     "activeJobs": "Actieve opdrachten",
@@ -41,7 +49,8 @@
     "sms": "Recente berichten",
     "notifications": "Recente meldingen",
     "recentCompleted": "Recent afgerond",
-    "activeJobs": "Actieve opdrachten"
+    "activeJobs": "Actieve opdrachten",
+    "recentObservations": "Recente waarnemingen"
   },
   "maintenance": {
     "title": "Verwacht onderhoud gepland",

+ 2 - 1
messages/nl/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Herinneringen",
     "allReminders": "Alle herinneringen",
     "auditLog": "Activiteitenlog",
-    "aiAssistant": "AI-assistent"
+    "aiAssistant": "AI-assistent",
+    "observations": "Waarnemingen"
   },
   "search": "Zoeken...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/nl/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "per pagina",
     "page": "Pagina {page} van {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Zoek in beschrijving, notities of voertuig...",
+    "allStatuses": "Alle statussen",
+    "allSeverities": "Alle ernstniveaus",
+    "empty": "Geen waarnemingen gevonden.",
+    "columns": {
+      "severity": "Ernst",
+      "status": "Status",
+      "vehicle": "Voertuig",
+      "description": "Beschrijving",
+      "notes": "Notities",
+      "created": "Aangemaakt"
+    }
+  },
   "form": {
     "editTitle": "Voertuig bewerken",
     "addTitle": "Nieuw voertuig toevoegen",

+ 10 - 1
messages/pl/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Ostatnia aktywność",
   "noRecentActivity": "Brak ostatniej aktywności",
+  "recentObservations": "Ostatnie obserwacje",
+  "noRecentObservations": "Brak otwartych obserwacji",
+  "observations": {
+    "severity": "Ważność",
+    "vehicle": "Pojazd",
+    "description": "Opis",
+    "when": "Kiedy"
+  },
   "unknownUser": "Użytkownik",
   "stats": {
     "activeJobs": "Aktywne zlecenia",
@@ -41,7 +49,8 @@
     "sms": "Ostatnie wiadomości",
     "notifications": "Ostatnie powiadomienia",
     "recentCompleted": "Ostatnio ukończone",
-    "activeJobs": "Aktywne zlecenia"
+    "activeJobs": "Aktywne zlecenia",
+    "recentObservations": "Ostatnie obserwacje"
   },
   "maintenance": {
     "title": "Przewidywany termin konserwacji",

+ 2 - 1
messages/pl/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Przypomnienia",
     "allReminders": "Wszystkie przypomnienia",
     "auditLog": "Dziennik audytu",
-    "aiAssistant": "Asystent AI"
+    "aiAssistant": "Asystent AI",
+    "observations": "Obserwacje"
   },
   "search": "Szukaj...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/pl/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "na stronę",
     "page": "Strona {page} z {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Szukaj opisu, notatek lub pojazdu...",
+    "allStatuses": "Wszystkie statusy",
+    "allSeverities": "Wszystkie poziomy ważności",
+    "empty": "Nie znaleziono obserwacji.",
+    "columns": {
+      "severity": "Ważność",
+      "status": "Status",
+      "vehicle": "Pojazd",
+      "description": "Opis",
+      "notes": "Notatki",
+      "created": "Utworzono"
+    }
+  },
   "form": {
     "editTitle": "Edytuj pojazd",
     "addTitle": "Dodaj nowy pojazd",

+ 10 - 1
messages/pt-BR/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Atividade recente",
   "noRecentActivity": "Nenhuma atividade recente",
+  "recentObservations": "Observações recentes",
+  "noRecentObservations": "Nenhuma observação aberta",
+  "observations": {
+    "severity": "Gravidade",
+    "vehicle": "Veículo",
+    "description": "Descrição",
+    "when": "Quando"
+  },
   "unknownUser": "Usuário",
   "stats": {
     "activeJobs": "Trabalhos Ativos",
@@ -41,7 +49,8 @@
     "sms": "Mensagens Recentes",
     "notifications": "Notificações Recentes",
     "recentCompleted": "Concluídos Recentemente",
-    "activeJobs": "Trabalhos Ativos"
+    "activeJobs": "Trabalhos Ativos",
+    "recentObservations": "Observações recentes"
   },
   "maintenance": {
     "title": "Manutenção Prevista",

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

@@ -52,7 +52,8 @@
     "reminders": "Lembretes",
     "allReminders": "Todos os lembretes",
     "auditLog": "Registro de auditoria",
-    "aiAssistant": "Assistente IA"
+    "aiAssistant": "Assistente IA",
+    "observations": "Observações"
   },
   "search": "Buscar...",
   "shortcut": "Ctrl+K",

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

@@ -229,6 +229,20 @@
     "perPage": "por página",
     "page": "Página {page} de {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Buscar descrição, notas ou veículo...",
+    "allStatuses": "Todos os status",
+    "allSeverities": "Todas as gravidades",
+    "empty": "Nenhuma observação encontrada.",
+    "columns": {
+      "severity": "Gravidade",
+      "status": "Status",
+      "vehicle": "Veículo",
+      "description": "Descrição",
+      "notes": "Notas",
+      "created": "Criado"
+    }
+  },
   "form": {
     "editTitle": "Editar Veículo",
     "addTitle": "Adicionar Novo Veículo",

+ 10 - 1
messages/ru/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Последняя активность",
   "noRecentActivity": "Нет недавней активности",
+  "recentObservations": "Недавние наблюдения",
+  "noRecentObservations": "Нет открытых наблюдений",
+  "observations": {
+    "severity": "Серьёзность",
+    "vehicle": "Транспорт",
+    "description": "Описание",
+    "when": "Когда"
+  },
   "unknownUser": "Пользователь",
   "stats": {
     "activeJobs": "Активные работы",
@@ -41,7 +49,8 @@
     "sms": "Последние сообщения",
     "notifications": "Последние уведомления",
     "recentCompleted": "Недавно завершённые",
-    "activeJobs": "Активные работы"
+    "activeJobs": "Активные работы",
+    "recentObservations": "Недавние наблюдения"
   },
   "maintenance": {
     "title": "Прогнозируемое техобслуживание",

+ 2 - 1
messages/ru/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Напоминания",
     "allReminders": "Все напоминания",
     "auditLog": "Журнал аудита",
-    "aiAssistant": "ИИ-ассистент"
+    "aiAssistant": "ИИ-ассистент",
+    "observations": "Наблюдения"
   },
   "search": "Поиск...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/ru/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "на странице",
     "page": "Страница {page} из {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Поиск по описанию, заметкам или транспорту...",
+    "allStatuses": "Все статусы",
+    "allSeverities": "Все уровни",
+    "empty": "Наблюдения не найдены.",
+    "columns": {
+      "severity": "Серьёзность",
+      "status": "Статус",
+      "vehicle": "Транспорт",
+      "description": "Описание",
+      "notes": "Заметки",
+      "created": "Создано"
+    }
+  },
   "form": {
     "editTitle": "Редактировать транспортное средство",
     "addTitle": "Добавить новое транспортное средство",

+ 10 - 1
messages/tr/dashboard.json

@@ -22,6 +22,14 @@
   },
   "recentActivity": "Son aktivite",
   "noRecentActivity": "Son aktivite yok",
+  "recentObservations": "Son Gözlemler",
+  "noRecentObservations": "Açık gözlem yok",
+  "observations": {
+    "severity": "Önem",
+    "vehicle": "Araç",
+    "description": "Açıklama",
+    "when": "Ne zaman"
+  },
   "unknownUser": "Kullanıcı",
   "stats": {
     "activeJobs": "Aktif İşler",
@@ -41,7 +49,8 @@
     "sms": "Son Mesajlar",
     "notifications": "Son Bildirimler",
     "recentCompleted": "Son Tamamlananlar",
-    "activeJobs": "Aktif İşler"
+    "activeJobs": "Aktif İşler",
+    "recentObservations": "Son Gözlemler"
   },
   "maintenance": {
     "title": "Tahmini Bakım Zamanı",

+ 2 - 1
messages/tr/navigation.json

@@ -52,7 +52,8 @@
     "reminders": "Hatırlatmalar",
     "allReminders": "Tüm hatırlatmalar",
     "auditLog": "Denetim günlüğü",
-    "aiAssistant": "AI Asistanı"
+    "aiAssistant": "AI Asistanı",
+    "observations": "Gözlemler"
   },
   "search": "Ara...",
   "shortcut": "Ctrl+K",

+ 14 - 0
messages/tr/vehicles.json

@@ -229,6 +229,20 @@
     "perPage": "sayfa başına",
     "page": "Sayfa {page} / {totalPages}"
   },
+  "observationsPage": {
+    "searchPlaceholder": "Açıklama, not veya araç ara...",
+    "allStatuses": "Tüm Durumlar",
+    "allSeverities": "Tüm Önem Düzeyleri",
+    "empty": "Gözlem bulunamadı.",
+    "columns": {
+      "severity": "Önem",
+      "status": "Durum",
+      "vehicle": "Araç",
+      "description": "Açıklama",
+      "notes": "Notlar",
+      "created": "Oluşturuldu"
+    }
+  },
   "form": {
     "editTitle": "Aracı Düzenle",
     "addTitle": "Yeni Araç Ekle",

+ 93 - 0
src/app/(authenticated)/dashboard-client.tsx

@@ -27,6 +27,7 @@ import {
   ClipboardCheck,
   ClipboardList,
   Clock,
+  Eye,
   EyeOff,
   FileText,
   Gauge,
@@ -187,6 +188,27 @@ interface DashboardNotification {
   createdAt: string | Date;
 }
 
+interface DashboardObservation {
+  id: string;
+  description: string;
+  severity: string;
+  notes: string | null;
+  createdAt: string | Date;
+  vehicle: {
+    id: string;
+    make: string;
+    model: string;
+    year: number;
+    licensePlate: string | null;
+  };
+}
+
+const observationSeverityColors: Record<string, string> = {
+  urgent: "bg-red-500/10 text-red-500 border-red-500/20",
+  needs_work: "bg-amber-500/10 text-amber-600 border-amber-500/20",
+  monitor: "bg-blue-500/10 text-blue-500 border-blue-500/20",
+};
+
 export function DashboardClient({
   stats,
   currencyCode = "USD",
@@ -202,6 +224,7 @@ export function DashboardClient({
   smsEnabled = false,
   notifications = [],
   recentAuditLogs = [],
+  recentObservations = [],
 }: {
   stats: DashboardStats;
   currencyCode?: string;
@@ -227,6 +250,7 @@ export function DashboardClient({
     metadata?: any;
     user: { id: string; name: string | null; email: string | null } | null;
   }[];
+  recentObservations?: DashboardObservation[];
 }) {
   const t = useTranslations("dashboard");
   const tAudit = useTranslations("audit");
@@ -1221,6 +1245,75 @@ export function DashboardClient({
             )}
           </CardContent>
         </Card>
+        {/* Recent Observations */}
+        {isVisible("recentObservations") && (
+        <Card className="border-0 shadow-sm">
+          <CardHeader className="pb-1">
+            <div className="flex items-center justify-between">
+              <CardTitle className="flex items-center gap-2 text-base">
+                <Eye className="h-4 w-4" />
+                {t("recentObservations")}
+              </CardTitle>
+              <Button
+                variant="ghost"
+                size="sm"
+                className="h-7 text-xs gap-1"
+                onClick={() => router.push("/observations")}
+              >
+                {t("viewAll")}
+                <ArrowRight className="h-3 w-3" />
+              </Button>
+            </div>
+          </CardHeader>
+          <CardContent className="p-0">
+            {recentObservations.length === 0 ? (
+              <p className="px-5 py-4 text-xs text-muted-foreground">{t("noRecentObservations")}</p>
+            ) : (
+              <Table>
+                <TableHeader>
+                  <TableRow>
+                    <TableHead className="h-8 w-20">{t("observations.severity")}</TableHead>
+                    <TableHead className="h-8 w-24">{t("observations.vehicle")}</TableHead>
+                    <TableHead className="h-8">{t("observations.description")}</TableHead>
+                    <TableHead className="h-8 w-16 text-right">{t("observations.when")}</TableHead>
+                  </TableRow>
+                </TableHeader>
+                <TableBody>
+                  {recentObservations.map((obs) => {
+                    const vehicleLabel = obs.vehicle.licensePlate
+                      ?? `${obs.vehicle.year} ${obs.vehicle.make}`;
+                    return (
+                      <TableRow
+                        key={obs.id}
+                        className="cursor-pointer"
+                        onClick={() => router.push(`/vehicles/${obs.vehicle.id}?tab=findings`)}
+                      >
+                        <TableCell className="py-1.5">
+                          <Badge
+                            variant="outline"
+                            className={`text-[10px] px-1.5 py-0 ${observationSeverityColors[obs.severity] || ""}`}
+                          >
+                            {obs.severity}
+                          </Badge>
+                        </TableCell>
+                        <TableCell className="py-1.5 font-mono text-xs text-muted-foreground truncate">
+                          {vehicleLabel}
+                        </TableCell>
+                        <TableCell className="py-1.5 text-xs font-medium truncate max-w-0">
+                          {obs.description}
+                        </TableCell>
+                        <TableCell className="py-1.5 text-right text-[11px] text-muted-foreground whitespace-nowrap">
+                          {formatRelativeTime(obs.createdAt)}
+                        </TableCell>
+                      </TableRow>
+                    );
+                  })}
+                </TableBody>
+              </Table>
+            )}
+          </CardContent>
+        </Card>
+        )}
       </div>
     </div>
   );

+ 219 - 0
src/app/(authenticated)/observations/observations-client.tsx

@@ -0,0 +1,219 @@
+"use client";
+
+import { useCallback, useTransition } from "react";
+import { useRouter, usePathname, useSearchParams } from "next/navigation";
+import { useTranslations } from "next-intl";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import {
+  Table,
+  TableBody,
+  TableCell,
+  TableHead,
+  TableHeader,
+  TableRow,
+} from "@/components/ui/table";
+import { DataTablePagination } from "@/components/data-table-pagination";
+import { useFormatDate } from "@/lib/use-format-date";
+import { Search, Loader2 } from "lucide-react";
+
+type ObservationsData = {
+  records: {
+    id: string;
+    description: string;
+    severity: string;
+    status: string;
+    notes: string | null;
+    createdAt: Date;
+    vehicle: {
+      id: string;
+      make: string;
+      model: string;
+      year: number;
+      licensePlate: string | null;
+    };
+    serviceRecord: { id: string; title: string } | null;
+    resolvedServiceRecord: { id: string; title: string } | null;
+  }[];
+  total: number;
+  page: number;
+  pageSize: number;
+  totalPages: number;
+};
+
+const severityColors: Record<string, string> = {
+  urgent: "bg-red-500/10 text-red-500 border-red-500/20",
+  needs_work: "bg-amber-500/10 text-amber-600 border-amber-500/20",
+  monitor: "bg-blue-500/10 text-blue-500 border-blue-500/20",
+};
+
+const statusColors: Record<string, string> = {
+  open: "bg-amber-500/10 text-amber-600 border-amber-500/20",
+  resolved: "bg-green-500/10 text-green-600 border-green-500/20",
+};
+
+export function ObservationsClient({
+  data,
+  search,
+  statusFilter,
+  severityFilter,
+}: {
+  data: ObservationsData;
+  search: string;
+  statusFilter: string;
+  severityFilter: string;
+}) {
+  const router = useRouter();
+  const pathname = usePathname();
+  const searchParams = useSearchParams();
+  const [isPending, startTransition] = useTransition();
+  const { formatDate } = useFormatDate();
+  const t = useTranslations("vehicles.observationsPage");
+  const tf = useTranslations("vehicles.findings");
+
+  const navigate = useCallback(
+    (params: Record<string, string | number | undefined>) => {
+      const newParams = new URLSearchParams(searchParams.toString());
+      for (const [key, value] of Object.entries(params)) {
+        if (value === undefined || value === "" || value === "all") {
+          newParams.delete(key);
+        } else {
+          newParams.set(key, String(value));
+        }
+      }
+      if (!("page" in params) && ("search" in params || "status" in params || "severity" in params)) {
+        newParams.delete("page");
+      }
+      startTransition(() => {
+        router.push(`${pathname}?${newParams.toString()}`);
+      });
+    },
+    [router, pathname, searchParams],
+  );
+
+  return (
+    <div className="space-y-4">
+      {/* Filters */}
+      <div className="flex flex-col gap-3 sm:flex-row sm:items-center">
+        <div className="relative flex-1">
+          <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
+          <Input
+            placeholder={t("searchPlaceholder")}
+            defaultValue={search}
+            onChange={(e) => {
+              const value = e.target.value;
+              if (value === search) return;
+              navigate({ search: value || undefined });
+            }}
+            className="pl-9 pr-9"
+          />
+          {isPending && <Loader2 className="absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" />}
+        </div>
+        <div className="flex gap-2">
+          <Select value={statusFilter} onValueChange={(v) => navigate({ status: v })}>
+            <SelectTrigger className="w-[140px]">
+              <SelectValue />
+            </SelectTrigger>
+            <SelectContent>
+              <SelectItem value="all">{t("allStatuses")}</SelectItem>
+              <SelectItem value="open">{tf("status.open")}</SelectItem>
+              <SelectItem value="resolved">{tf("status.resolved")}</SelectItem>
+            </SelectContent>
+          </Select>
+          <Select value={severityFilter} onValueChange={(v) => navigate({ severity: v })}>
+            <SelectTrigger className="w-[160px]">
+              <SelectValue />
+            </SelectTrigger>
+            <SelectContent>
+              <SelectItem value="all">{t("allSeverities")}</SelectItem>
+              <SelectItem value="urgent">{tf("severity.urgent")}</SelectItem>
+              <SelectItem value="needs_work">{tf("severity.needs_work")}</SelectItem>
+              <SelectItem value="monitor">{tf("severity.monitor")}</SelectItem>
+            </SelectContent>
+          </Select>
+        </div>
+      </div>
+
+      {/* Table */}
+      <div className="rounded-md border">
+        <Table>
+          <TableHeader>
+            <TableRow>
+              <TableHead className="w-[110px]">{t("columns.severity")}</TableHead>
+              <TableHead className="w-[100px]">{t("columns.status")}</TableHead>
+              <TableHead className="w-[160px]">{t("columns.vehicle")}</TableHead>
+              <TableHead>{t("columns.description")}</TableHead>
+              <TableHead className="hidden md:table-cell">{t("columns.notes")}</TableHead>
+              <TableHead className="w-[110px] text-right">{t("columns.created")}</TableHead>
+            </TableRow>
+          </TableHeader>
+          <TableBody>
+            {data.records.length === 0 ? (
+              <TableRow>
+                <TableCell colSpan={6} className="h-24 text-center text-muted-foreground">
+                  {t("empty")}
+                </TableCell>
+              </TableRow>
+            ) : (
+              data.records.map((obs) => {
+                const vehicleLabel = obs.vehicle.licensePlate
+                  ?? `${obs.vehicle.year} ${obs.vehicle.make} ${obs.vehicle.model}`;
+                return (
+                  <TableRow
+                    key={obs.id}
+                    className="cursor-pointer"
+                    onClick={() => router.push(`/vehicles/${obs.vehicle.id}?tab=findings`)}
+                  >
+                    <TableCell>
+                      <Badge variant="outline" className={`text-xs ${severityColors[obs.severity] || ""}`}>
+                        {tf(`severity.${obs.severity}` as "severity.urgent" | "severity.needs_work" | "severity.monitor")}
+                      </Badge>
+                    </TableCell>
+                    <TableCell>
+                      <Badge variant="outline" className={`text-xs ${statusColors[obs.status] || ""}`}>
+                        {tf(`status.${obs.status}` as "status.open" | "status.quoted" | "status.resolved")}
+                      </Badge>
+                    </TableCell>
+                    <TableCell>
+                      <div className="flex flex-col">
+                        <span className="font-mono text-xs font-medium">{vehicleLabel}</span>
+                        {obs.vehicle.licensePlate && (
+                          <span className="text-[11px] text-muted-foreground">
+                            {obs.vehicle.year} {obs.vehicle.make} {obs.vehicle.model}
+                          </span>
+                        )}
+                      </div>
+                    </TableCell>
+                    <TableCell className="text-sm font-medium">{obs.description}</TableCell>
+                    <TableCell className="hidden md:table-cell text-sm text-muted-foreground truncate max-w-[300px]">
+                      {obs.notes}
+                    </TableCell>
+                    <TableCell className="text-right text-xs text-muted-foreground whitespace-nowrap">
+                      {formatDate(new Date(obs.createdAt))}
+                    </TableCell>
+                  </TableRow>
+                );
+              })
+            )}
+          </TableBody>
+        </Table>
+      </div>
+
+      {/* Pagination */}
+      <DataTablePagination
+        total={data.total}
+        page={data.page}
+        pageSize={data.pageSize}
+        totalPages={data.totalPages}
+        onNavigate={navigate}
+      />
+    </div>
+  );
+}

+ 52 - 0
src/app/(authenticated)/observations/page.tsx

@@ -0,0 +1,52 @@
+import { getObservationsPaginated } from "@/features/vehicles/Actions/findingActions";
+import { PageHeader } from "@/components/page-header";
+import { ObservationsClient } from "./observations-client";
+
+export default async function ObservationsPage({
+  searchParams,
+}: {
+  searchParams: Promise<{
+    page?: string;
+    pageSize?: string;
+    search?: string;
+    status?: string;
+    severity?: string;
+  }>;
+}) {
+  const params = await searchParams;
+
+  const result = await getObservationsPaginated({
+    page: params.page ? parseInt(params.page) : 1,
+    pageSize: params.pageSize ? parseInt(params.pageSize) : 25,
+    search: params.search,
+    status: params.status,
+    severity: params.severity,
+  });
+
+  if (!result.success || !result.data) {
+    return (
+      <>
+        <PageHeader />
+        <div className="flex h-[50vh] items-center justify-center">
+          <p className="text-muted-foreground">
+            {result.error || "Failed to load observations"}
+          </p>
+        </div>
+      </>
+    );
+  }
+
+  return (
+    <>
+      <PageHeader />
+      <div className="flex flex-1 flex-col gap-4 p-4 pt-0">
+        <ObservationsClient
+          data={result.data}
+          search={params.search || ""}
+          statusFilter={params.status || "open"}
+          severityFilter={params.severity || "all"}
+        />
+      </div>
+    </>
+  );
+}

+ 5 - 1
src/app/(authenticated)/page.tsx

@@ -11,6 +11,7 @@ import { getFeatures } from "@/lib/features";
 import { getRecentSmsThreads } from "@/features/sms/Actions/smsActions";
 import { getNotifications } from "@/features/notifications/Actions/notificationActions";
 import { getRecentAuditLogs } from "@/features/audit/Actions/auditActions";
+import { getRecentObservations } from "@/features/vehicles/Actions/findingActions";
 import { getMyActiveJobs } from "@/features/vehicles/Actions/getMyActiveJobs";
 import { DashboardClient } from "./dashboard-client";
 import { MyActiveJobs } from "@/features/vehicles/Components/MyActiveJobs";
@@ -21,7 +22,7 @@ export default async function DashboardPage() {
   const features = auth ? await getFeatures(auth.organizationId) : null;
   const smsEnabled = features?.sms ?? false;
 
-  const [result, settingsResult, remindersResult, maintenanceResult, dismissedMaintenanceResult, inProgressResult, completedResult, quoteRequestsResult, quoteResponsesResult, smsResult, notificationsResult, auditLogsResult, myJobsResult] = await Promise.all([
+  const [result, settingsResult, remindersResult, maintenanceResult, dismissedMaintenanceResult, inProgressResult, completedResult, quoteRequestsResult, quoteResponsesResult, smsResult, notificationsResult, auditLogsResult, recentObservationsResult, myJobsResult] = await Promise.all([
     getDashboardStats(),
     getSettings([SETTING_KEYS.CURRENCY_CODE, SETTING_KEYS.UNIT_SYSTEM]),
     getUpcomingReminders(),
@@ -34,6 +35,7 @@ export default async function DashboardPage() {
     smsEnabled ? getRecentSmsThreads(0, 5) : Promise.resolve(null),
     getNotifications(),
     getRecentAuditLogs(10),
+    getRecentObservations(),
     getMyActiveJobs(),
   ]);
 
@@ -57,6 +59,7 @@ export default async function DashboardPage() {
   const smsThreads = smsResult && smsResult.success && smsResult.data ? smsResult.data.threads : [];
   const notifications = notificationsResult.success && notificationsResult.data ? notificationsResult.data.notifications : [];
   const recentAuditLogs = auditLogsResult.success && auditLogsResult.data ? auditLogsResult.data : [];
+  const recentObservations = recentObservationsResult.success && recentObservationsResult.data ? recentObservationsResult.data : [];
 
   const myJobs = myJobsResult.success && myJobsResult.data ? myJobsResult.data : [];
 
@@ -80,6 +83,7 @@ export default async function DashboardPage() {
           smsEnabled={smsEnabled}
           notifications={notifications}
           recentAuditLogs={recentAuditLogs}
+          recentObservations={recentObservations}
         />
       </div>
     </>

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

@@ -78,6 +78,7 @@ const breadcrumbMap: Record<string, BreadcrumbSegment[]> = {
   '/settings/customer-portal': [{ key: 'settings', href: '/settings' }, { key: 'customerPortal' }],
   '/ai': [{ key: 'aiAssistant' }],
   '/audit-log': [{ key: 'auditLog' }],
+  '/observations': [{ key: 'observations' }],
 }
 
 export function PageHeader() {

+ 101 - 0
src/features/vehicles/Actions/findingActions.ts

@@ -10,6 +10,107 @@ import {
 } from "../Schema/findingSchema";
 import { revalidatePath } from "next/cache";
 
+export async function getObservationsPaginated(params: {
+  page?: number;
+  pageSize?: number;
+  search?: string;
+  status?: string;
+  severity?: string;
+}) {
+  return withAuth(
+    async ({ organizationId }) => {
+      const page = params.page || 1;
+      const pageSize = params.pageSize || 25;
+      const skip = (page - 1) * pageSize;
+
+      const search = params.search?.trim();
+      const where = {
+        vehicle: { organizationId },
+        ...(params.status && params.status !== "all" ? { status: params.status } : {}),
+        ...(params.severity && params.severity !== "all" ? { severity: params.severity } : {}),
+        ...(search
+          ? {
+              OR: [
+                { description: { contains: search, mode: "insensitive" as const } },
+                { notes: { contains: search, mode: "insensitive" as const } },
+                { vehicle: { licensePlate: { contains: search, mode: "insensitive" as const } } },
+                { vehicle: { make: { contains: search, mode: "insensitive" as const } } },
+                { vehicle: { model: { contains: search, mode: "insensitive" as const } } },
+              ],
+            }
+          : {}),
+      };
+
+      const [records, total] = await Promise.all([
+        db.vehicleFinding.findMany({
+          where,
+          include: {
+            vehicle: {
+              select: {
+                id: true,
+                make: true,
+                model: true,
+                year: true,
+                licensePlate: true,
+              },
+            },
+            serviceRecord: { select: { id: true, title: true } },
+            resolvedServiceRecord: { select: { id: true, title: true } },
+          },
+          orderBy: [{ status: "asc" }, { createdAt: "desc" }],
+          skip,
+          take: pageSize,
+        }),
+        db.vehicleFinding.count({ where }),
+      ]);
+
+      return {
+        records,
+        total,
+        page,
+        pageSize,
+        totalPages: Math.ceil(total / pageSize),
+      };
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.READ, subject: PermissionSubject.VEHICLES },
+      ],
+    }
+  );
+}
+
+export async function getRecentObservations(limit = 10) {
+  return withAuth(
+    async ({ organizationId }) => {
+      return db.vehicleFinding.findMany({
+        where: {
+          status: "open",
+          vehicle: { organizationId },
+        },
+        include: {
+          vehicle: {
+            select: {
+              id: true,
+              make: true,
+              model: true,
+              year: true,
+              licensePlate: true,
+            },
+          },
+        },
+        orderBy: { createdAt: "desc" },
+        take: limit,
+      });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.READ, subject: PermissionSubject.VEHICLES },
+      ],
+    }
+  );
+}
+
 export async function getServiceFindings(serviceRecordId: string) {
   return withAuth(
     async ({ organizationId }) => {

+ 1 - 0
src/hooks/use-dashboard-visibility.ts

@@ -12,6 +12,7 @@ export const DASHBOARD_CARD_IDS = [
   "notifications",
   "recentCompleted",
   "activeJobs",
+  "recentObservations",
 ] as const;
 
 export type DashboardCardId = (typeof DASHBOARD_CARD_IDS)[number];