diff --git a/frontend/src/components/fahrzeuge/FahrzeugChecklistTab.tsx b/frontend/src/components/fahrzeuge/FahrzeugChecklistTab.tsx index be89b8e..b025529 100644 --- a/frontend/src/components/fahrzeuge/FahrzeugChecklistTab.tsx +++ b/frontend/src/components/fahrzeuge/FahrzeugChecklistTab.tsx @@ -30,7 +30,7 @@ import type { CreateFahrzeugItemPayload } from '../../types/checklist.types'; // ── Helpers ── const formatDate = (iso?: string) => - iso ? new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) : '\u2013'; + iso ? new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) : '–'; // ══════════════════════════════════════════════════════════════════════════════ // Component @@ -76,8 +76,8 @@ const FahrzeugChecklistTab: React.FC = ({ fahrzeugId const addItemMutation = useMutation({ mutationFn: (data: CreateFahrzeugItemPayload) => checklistenApi.addVehicleItem(fahrzeugId, data), - onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['checklisten-fahrzeug-items', fahrzeugId] }); setNewItem({ bezeichnung: '', pflicht: false, sort_order: 0 }); showSuccess('Item hinzugef\u00fcgt'); }, - onError: () => showError('Fehler beim Hinzuf\u00fcgen'), + onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['checklisten-fahrzeug-items', fahrzeugId] }); setNewItem({ bezeichnung: '', pflicht: false, sort_order: 0 }); showSuccess('Item hinzugefügt'); }, + onError: () => showError('Fehler beim Hinzufügen'), }); const deleteItemMutation = useMutation({ @@ -120,7 +120,7 @@ const FahrzeugChecklistTab: React.FC = ({ fahrzeugId vehicleItems.map((item) => ( {item.bezeichnung} - {item.pflicht ? : '\u2013'} + {item.pflicht ? : '–'} = ({ fahrzeugId setNewItem((n) => ({ ...n, bezeichnung: e.target.value }))} sx={{ flexGrow: 1 }} /> setNewItem((n) => ({ ...n, pflicht: e.target.checked }))} />} label="Pflicht" /> @@ -159,7 +159,7 @@ const FahrzeugChecklistTab: React.FC = ({ fahrzeugId {templatesLoading ? ( ) : templates.length === 0 ? ( - Keine Vorlagen f\u00fcr dieses Fahrzeug. + Keine Vorlagen für dieses Fahrzeug. ) : ( @@ -167,7 +167,7 @@ const FahrzeugChecklistTab: React.FC = ({ fahrzeugId Vorlage Intervall - N\u00e4chste F\u00e4lligkeit + Nächste Fälligkeit Aktion @@ -178,11 +178,11 @@ const FahrzeugChecklistTab: React.FC = ({ fahrzeugId {t.name} - {t.intervall === 'weekly' ? 'W\u00f6chentlich' : t.intervall === 'monthly' ? 'Monatlich' : t.intervall === 'yearly' ? 'J\u00e4hrlich' : t.intervall === 'custom' ? `${t.intervall_tage ?? '?'} Tage` : '\u2013'} + {t.intervall === 'weekly' ? 'Wöchentlich' : t.intervall === 'monthly' ? 'Monatlich' : t.intervall === 'yearly' ? 'Jährlich' : t.intervall === 'custom' ? `${t.intervall_tage ?? '?'} Tage` : '–'} {due ? ( - } label={`F\u00e4llig: ${formatDate(due.naechste_faellig_am)}`} color="error" size="small" /> + } label={`Fällig: ${formatDate(due.naechste_faellig_am)}`} color="error" size="small" /> ) : ( Aktuell )} @@ -195,7 +195,7 @@ const FahrzeugChecklistTab: React.FC = ({ fahrzeugId startIcon={} onClick={() => navigate(`/checklisten/ausfuehrung/new?fahrzeug=${fahrzeugId}&vorlage=${t.id}`)} > - Ausf\u00fchren + Ausführen )} @@ -210,12 +210,12 @@ const FahrzeugChecklistTab: React.FC = ({ fahrzeugId {/* Section 3: Recent executions */} - Letzte Ausf\u00fchrungen + Letzte Ausführungen {executionsLoading ? ( ) : executions.length === 0 ? ( - Noch keine Ausf\u00fchrungen f\u00fcr dieses Fahrzeug. + Noch keine Ausführungen für dieses Fahrzeug. ) : (
@@ -224,7 +224,7 @@ const FahrzeugChecklistTab: React.FC = ({ fahrzeugId Datum Vorlage Status - Ausgef\u00fchrt von + Ausgeführt von Freigegeben von @@ -232,12 +232,12 @@ const FahrzeugChecklistTab: React.FC = ({ fahrzeugId {executions.slice(0, 20).map((e) => ( navigate(`/checklisten/ausfuehrung/${e.id}`)}> {formatDate(e.ausgefuehrt_am ?? e.created_at)} - {e.vorlage_name ?? '\u2013'} + {e.vorlage_name ?? '–'} - {e.ausgefuehrt_von_name ?? '\u2013'} - {e.freigegeben_von_name ?? '\u2013'} + {e.ausgefuehrt_von_name ?? '–'} + {e.freigegeben_von_name ?? '–'} ))} diff --git a/frontend/src/components/shared/Sidebar.tsx b/frontend/src/components/shared/Sidebar.tsx index 7087b7c..7baaf84 100644 --- a/frontend/src/components/shared/Sidebar.tsx +++ b/frontend/src/components/shared/Sidebar.tsx @@ -228,6 +228,16 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { issuesSubItems.push({ text: 'Einstellungen', path: `/issues?tab=${issuesSubItems.length}` }); } + // Build Checklisten sub-items dynamically (tab order must match Checklisten.tsx) + const checklistenSubItems: SubItem[] = [ + { text: 'Übersicht', path: '/checklisten?tab=0' }, + ]; + if (hasPermission('checklisten:manage_templates')) { + checklistenSubItems.push({ text: 'Vorlagen', path: '/checklisten?tab=1' }); + checklistenSubItems.push({ text: 'Fahrzeugtypen', path: '/checklisten?tab=2' }); + } + checklistenSubItems.push({ text: 'Historie', path: `/checklisten?tab=${checklistenSubItems.length}` }); + const items = baseNavigationItems .map((item) => { if (item.path === '/fahrzeuge') return fahrzeugeItem; @@ -239,6 +249,7 @@ function Sidebar({ mobileOpen, onMobileClose }: SidebarProps) { return { ...item, subItems: ausruestungSubItems, permission: canSeeAusruestung ? undefined : 'ausruestungsanfrage:view' }; } if (item.path === '/issues') return { ...item, subItems: issuesSubItems }; + if (item.path === '/checklisten') return { ...item, subItems: checklistenSubItems }; return item; }) .filter((item) => !item.permission || hasPermission(item.permission)); diff --git a/frontend/src/pages/ChecklistAusfuehrung.tsx b/frontend/src/pages/ChecklistAusfuehrung.tsx index dddf1dc..90fe5f8 100644 --- a/frontend/src/pages/ChecklistAusfuehrung.tsx +++ b/frontend/src/pages/ChecklistAusfuehrung.tsx @@ -28,7 +28,7 @@ import type { ChecklistAusfuehrungItem } from '../types/checklist.types'; // ── Helpers ── const formatDate = (iso?: string) => - iso ? new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : '\u2013'; + iso ? new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : '–'; const ERGEBNIS_ICONS: Record = { ok: , @@ -110,7 +110,7 @@ export default function ChecklistAusfuehrung() { queryClient.invalidateQueries({ queryKey: ['checklisten-faellig'] }); showSuccess('Checkliste abgeschlossen'); }, - onError: () => showError('Fehler beim Abschlie\u00dfen'), + onError: () => showError('Fehler beim Abschließen'), }); // ── Approve ── @@ -145,7 +145,7 @@ export default function ChecklistAusfuehrung() { return ( Checkliste konnte nicht geladen werden. - + ); } @@ -222,7 +222,7 @@ export default function ChecklistAusfuehrung() { {execution.vorlage_name ?? 'Checkliste'} - {execution.fahrzeug_name ?? '\u2013'} · {formatDate(execution.ausgefuehrt_am ?? execution.created_at)} + {execution.fahrzeug_name ?? '–'} · {formatDate(execution.ausgefuehrt_am ?? execution.created_at)} - Diese Checkliste wurde als unvollst\u00e4ndig abgeschlossen. Einige Pflicht-Items wurden nicht mit "OK" bewertet. + Diese Checkliste wurde als unvollständig abgeschlossen. Einige Pflicht-Items wurden nicht mit "OK" bewertet. )} @@ -260,7 +260,7 @@ export default function ChecklistAusfuehrung() { fullWidth multiline rows={3} - placeholder="Zus\u00e4tzliche Notizen..." + placeholder="Zusätzliche Notizen..." value={notizen} onChange={(e) => setNotizen(e.target.value)} /> @@ -277,7 +277,7 @@ export default function ChecklistAusfuehrung() { disabled={submitMutation.isPending} startIcon={submitMutation.isPending ? : undefined} > - Abschlie\u00dfen + Abschließen )} @@ -299,7 +299,7 @@ export default function ChecklistAusfuehrung() { {execution.ausgefuehrt_von_name && ( - Ausgef\u00fchrt von: {execution.ausgefuehrt_von_name} am {formatDate(execution.ausgefuehrt_am)} + Ausgeführt von: {execution.ausgefuehrt_von_name} am {formatDate(execution.ausgefuehrt_am)} )} {execution.freigegeben_von_name && ( diff --git a/frontend/src/pages/Checklisten.tsx b/frontend/src/pages/Checklisten.tsx index 05c0390..9ff4cf1 100644 --- a/frontend/src/pages/Checklisten.tsx +++ b/frontend/src/pages/Checklisten.tsx @@ -64,12 +64,12 @@ import type { // ── Helpers ── const formatDate = (iso?: string) => - iso ? new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) : '\u2013'; + iso ? new Date(iso).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) : '–'; const INTERVALL_LABELS: Record = { - weekly: 'W\u00f6chentlich', + weekly: 'Wöchentlich', monthly: 'Monatlich', - yearly: 'J\u00e4hrlich', + yearly: 'Jährlich', custom: 'Benutzerdefiniert', }; @@ -95,7 +95,7 @@ export default function Checklisten() { const canManageTemplates = hasPermission('checklisten:manage_templates'); const canExecute = hasPermission('checklisten:execute'); - // Tabs: 0=\u00dcbersicht, 1=Vorlagen (if perm), 2=Fahrzeugtypen (if perm), 3=Historie + // Tabs: 0=Übersicht, 1=Vorlagen (if perm), 2=Fahrzeugtypen (if perm), 3=Historie const manageTabs = canManageTemplates ? 2 : 0; const TAB_COUNT = 2 + manageTabs; @@ -160,14 +160,14 @@ export default function Checklisten() { variant="scrollable" scrollButtons="auto" > - + {canManageTemplates && } {canManageTemplates && } - {/* Tab 0: \u00dcbersicht */} + {/* Tab 0: Übersicht */} {vehiclesLoading ? ( @@ -185,7 +185,7 @@ export default function Checklisten() { {vOverdue.length > 0 && ( } - label={`${vOverdue.length} f\u00e4llig`} + label={`${vOverdue.length} fällig`} color="error" size="small" /> @@ -200,7 +200,7 @@ export default function Checklisten() { {f.vorlage_name} - ({days > 0 ? `${days}d \u00fcberf\u00e4llig` : 'heute f\u00e4llig'}) + ({days > 0 ? `${days}d überfällig` : 'heute fällig'}) {canExecute && ( @@ -304,8 +304,8 @@ function VorlagenTab({ vorlagen, loading, fahrzeugTypen, queryClient, showSucces const deleteMutation = useMutation({ mutationFn: (id: number) => checklistenApi.deleteVorlage(id), - onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['checklisten-vorlagen'] }); showSuccess('Vorlage gel\u00f6scht'); }, - onError: () => showError('Fehler beim L\u00f6schen der Vorlage'), + onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['checklisten-vorlagen'] }); showSuccess('Vorlage gelöscht'); }, + onError: () => showError('Fehler beim Löschen der Vorlage'), }); const openCreate = () => { setEditingVorlage(null); setForm(emptyForm); setDialogOpen(true); }; @@ -353,9 +353,9 @@ function VorlagenTab({ vorlagen, loading, fahrzeugTypen, queryClient, showSucces setExpandedVorlageId(expandedVorlageId === v.id ? null : v.id)}> {v.name} - {v.fahrzeug_typ?.name ?? '\u2013'} + {v.fahrzeug_typ?.name ?? '–'} - {v.intervall ? INTERVALL_LABELS[v.intervall] || v.intervall : '\u2013'} + {v.intervall ? INTERVALL_LABELS[v.intervall] || v.intervall : '–'} {v.intervall === 'custom' && v.intervall_tage ? ` (${v.intervall_tage} Tage)` : ''} @@ -396,9 +396,9 @@ function VorlagenTab({ vorlagen, loading, fahrzeugTypen, queryClient, showSucces Intervall @@ -431,8 +431,8 @@ function VorlageItemsSection({ vorlageId, queryClient, showSuccess, showError }: const addMutation = useMutation({ mutationFn: (data: CreateVorlageItemPayload) => checklistenApi.addVorlageItem(vorlageId, data), - onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['checklisten-vorlage-items', vorlageId] }); setNewItem({ bezeichnung: '', pflicht: false, sort_order: 0 }); showSuccess('Item hinzugef\u00fcgt'); }, - onError: () => showError('Fehler beim Hinzuf\u00fcgen'), + onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['checklisten-vorlage-items', vorlageId] }); setNewItem({ bezeichnung: '', pflicht: false, sort_order: 0 }); showSuccess('Item hinzugefügt'); }, + onError: () => showError('Fehler beim Hinzufügen'), }); const deleteMutation = useMutation({ @@ -458,7 +458,7 @@ function VorlageItemsSection({ vorlageId, queryClient, showSuccess, showError }: setNewItem((n) => ({ ...n, bezeichnung: e.target.value }))} sx={{ flexGrow: 1 }} /> setNewItem((n) => ({ ...n, pflicht: e.target.checked }))} />} label="Pflicht" /> @@ -495,8 +495,8 @@ function FahrzeugTypenTab({ fahrzeugTypen, queryClient, showSuccess, showError } const deleteMutation = useMutation({ mutationFn: (id: number) => fahrzeugTypenApi.delete(id), - onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['fahrzeug-typen'] }); showSuccess('Fahrzeugtyp gel\u00f6scht'); }, - onError: () => showError('Fehler beim L\u00f6schen'), + onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['fahrzeug-typen'] }); showSuccess('Fahrzeugtyp gelöscht'); }, + onError: () => showError('Fehler beim Löschen'), }); const openCreate = () => { setEditing(null); setForm({ name: '', beschreibung: '', icon: '' }); setDialogOpen(true); }; @@ -536,8 +536,8 @@ function FahrzeugTypenTab({ fahrzeugTypen, queryClient, showSuccess, showError } fahrzeugTypen.map((t) => ( {t.name} - {t.beschreibung ?? '\u2013'} - {t.icon ?? '\u2013'} + {t.beschreibung ?? '–'} + {t.icon ?? '–'} openEdit(t)}> deleteMutation.mutate(t.id)}> @@ -620,24 +620,24 @@ function HistorieTab({ executions, loading, navigate }: HistorieTabProps) { Vorlage Datum Status - Ausgef\u00fchrt von + Ausgeführt von Freigegeben von {filtered.length === 0 ? ( - Keine Eintr\u00e4ge + Keine Einträge ) : ( filtered.map((e) => ( navigate(`/checklisten/ausfuehrung/${e.id}`)}> - {e.fahrzeug_name ?? '\u2013'} - {e.vorlage_name ?? '\u2013'} + {e.fahrzeug_name ?? '–'} + {e.vorlage_name ?? '–'} {formatDate(e.ausgefuehrt_am ?? e.created_at)} - {e.ausgefuehrt_von_name ?? '\u2013'} - {e.freigegeben_von_name ?? '\u2013'} + {e.ausgefuehrt_von_name ?? '–'} + {e.freigegeben_von_name ?? '–'} )) )}