"use client"; import * as React from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { Album, BellRing, BookMarked, BookOpenCheck, Bot, CalendarDays, ClipboardCheck, Flame, Gauge, GraduationCap, IdCard, Map, ReceiptText, Menu, MonitorSmartphone, QrCode, ShieldAlert, Siren, TriangleAlert, Truck, Wallet, Users, Wrench, X, } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import type { Recht, Rolle } from "@/lib/authz"; /** * Hauptnavigation. * * Welche Einträge jemand sieht, entscheidet NICHT diese Komponente. Sie bekommt * die Rechte der Sitzung übergeben und blendet danach aus — die Prüfung selbst * bleibt in lib/authz.ts, wie es die Projektregeln verlangen. Ein ausgeblendeter * Eintrag ist Bequemlichkeit, kein Schutz; der Schutz sitzt in der tRPC-Schicht. * * Die Phasenangaben entsprechen den Dateien Phase-0.md bis Phase-9.md. */ interface Eintrag { titel: string; pfad: string; symbol: React.ComponentType<{ className?: string }>; /** Sichtbar, wenn eines dieser Rechte vorliegt. Leer = für alle. */ rechte?: Recht[]; /** Ab welcher Phase es die Ansicht wirklich gibt. */ abPhase?: number; } const EINTRAEGE: Eintrag[] = [ { titel: "Übersicht", pfad: "/", symbol: Gauge }, { titel: "Mitglieder", pfad: "/mitglieder", symbol: Users, rechte: ["person.lesen"], abPhase: 1 }, { titel: "Fahrzeuge", pfad: "/fahrzeuge", symbol: Truck, rechte: ["fahrzeug.lesen"], abPhase: 1 }, { titel: "Geräte", pfad: "/geraete", symbol: Wrench, rechte: ["geraet.lesen"], abPhase: 1 }, { titel: "Qualifikationen", pfad: "/qualifikationen", symbol: BookOpenCheck, rechte: ["qualifikation.lesen"], abPhase: 1, }, { titel: "Prüfungen", pfad: "/pruefungen", symbol: ClipboardCheck, rechte: ["pruefung.lesen"], abPhase: 2, }, { titel: "Mängel", pfad: "/maengel", symbol: TriangleAlert, rechte: ["pruefung.lesen"], abPhase: 2, }, { titel: "Rückrufe", pfad: "/rueckrufe", symbol: ShieldAlert, rechte: ["pruefung.lesen"], abPhase: 2, }, { titel: "Einsätze", pfad: "/einsaetze", symbol: Flame, rechte: ["einsatz.lesen"], abPhase: 3 }, { titel: "Übungen", pfad: "/uebungen", symbol: GraduationCap, rechte: ["ausbildung.lesen"], abPhase: 3, }, { // Führt bewusst auf die Übersicht in der Verwaltung, nicht auf das // Terminal selbst: das läuft am Wandtablet unter /kiosk und hat eine eigene // Oberfläche. Wer hier klickt, will sehen, was dort zusammengekommen ist. titel: "Terminal", pfad: "/terminal", symbol: MonitorSmartphone, rechte: ["fahrtenbuch.lesen"], abPhase: 4, }, { titel: "Anmeldemarke", pfad: "/anmeldemarke", symbol: QrCode, rechte: ["person.eigene"], abPhase: 4, }, { titel: "Atemschutz", pfad: "/atemschutz", symbol: Siren, rechte: ["atemschutz.lesen"], abPhase: 5, }, { titel: "Karte", pfad: "/karte", symbol: Map, rechte: ["einsatz.lesen"], abPhase: 6 }, { titel: "Termine", pfad: "/termine", symbol: CalendarDays, rechte: ["termin.lesen"], abPhase: 7, }, { titel: "Dienstausweis", pfad: "/ausweis", symbol: IdCard, rechte: ["person.eigene"], abPhase: 7, }, { titel: "Benachrichtigungen", pfad: "/benachrichtigungen", symbol: BellRing, rechte: ["benachrichtigung.eigene"], abPhase: 7, }, { titel: "Wissen", pfad: "/wiki", symbol: BookMarked, rechte: ["wiki.lesen"], abPhase: 8, }, { titel: "Belege", pfad: "/belege", symbol: ReceiptText, rechte: ["beleg.erfassen"], abPhase: 8, }, { titel: "KI-Protokoll", pfad: "/ki-protokoll", symbol: Bot, rechte: ["ki.protokoll"], abPhase: 8, }, { titel: "Kasse", pfad: "/kasse", symbol: Wallet, rechte: ["kasse.lesen"], abPhase: 9 }, { // Das Audit-Log wird seit Phase 0 geschrieben; die Ansicht dazu kam in // Phase 8, zusammen mit dem KI-Protokoll. Beides nebeneinander beantwortet // die beiden Fragen, die im Zweifel gestellt werden: was hat ein Mensch // getan, und was hat die Maschine getan. titel: "Protokoll", pfad: "/protokoll", symbol: Album, rechte: ["system.auditlog"], abPhase: 8, }, ]; /** Bis zu dieser Phase gibt es die Ansichten wirklich. Alles darüber ist * sichtbar, aber ausgegraut — so sieht die Wehr, was noch kommt. */ const AKTUELLE_PHASE = 8; export interface SeitenleisteProps { rechte: Recht[]; rollen: Rolle[]; organisationName: string; personName: string; } export function Seitenleiste({ rechte, organisationName, personName }: SeitenleisteProps) { const pfad = usePathname(); const [offen, setOffen] = React.useState(false); const rechteMenge = React.useMemo(() => new Set(rechte), [rechte]); const sichtbar = EINTRAEGE.filter( (eintrag) => !eintrag.rechte || eintrag.rechte.some((recht) => rechteMenge.has(recht)), ); return ( <> {/* Auf schmalen Geräten aufklappbar. */} {offen && (