"use client";
import * as React from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
Album,
BookOpenCheck,
CalendarDays,
ClipboardCheck,
Flame,
Gauge,
GraduationCap,
Map,
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: ["ausbildung.lesen"],
abPhase: 7,
},
{ titel: "Kasse", pfad: "/kasse", symbol: Wallet, rechte: ["kasse.lesen"], abPhase: 9 },
{
// Das Audit-Log wird seit Phase 0 geschrieben, eine Ansicht dafür gibt es
// noch nicht — sie gehört zum Löschkonzept in Phase 8. Bis dahin steht der
// Eintrag ausgegraut da statt ins Leere zu führen.
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 = 6;
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 && (