Phase 2: Wartungs- und Pruefmanagement
Wartungsvorlagen mit Checklisten, Prueftermine aus einem naechtlichen Lauf, Prueffristen-Dashboard, Pruefwizard mit Foto und Unterschrift, unveraenderliche Protokolle als PDF, Maengel-Workflow, Rueckrufe und Lebensdauerueberwachung. Elf neue Tabellen, alle mit organisationId und Row Level Security. Die Unveraenderbarkeit abgeschlossener Protokolle setzt ein Datenbanktrigger durch, nicht der Anwendungscode: eine Korrektur erzeugt eine neue Pruefung mit Verweis auf die alte, beide bleiben lesbar. 79 neue Tests (47 Unit, 10 gegen eine echte Datenbank, 22 E2E). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7f866ac35c
commit
e503aafc18
@@ -0,0 +1,167 @@
|
||||
"use client";
|
||||
|
||||
import type {
|
||||
BetroffenheitStatus,
|
||||
MangelPrioritaet,
|
||||
MangelStatus,
|
||||
PruefErgebnis,
|
||||
PruefungStatus,
|
||||
RueckrufStatus,
|
||||
WartungsterminStatus,
|
||||
} from "@prisma/client";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { Fristfenster } from "@/lib/wartung/fristen";
|
||||
import {
|
||||
BETROFFENHEIT_TEXT,
|
||||
ERGEBNIS_TEXT,
|
||||
FENSTER_TEXT,
|
||||
MANGEL_STATUS_TEXT,
|
||||
PRIORITAET_TEXT,
|
||||
PRUEFUNG_STATUS_TEXT,
|
||||
RUECKRUF_STATUS_TEXT,
|
||||
TERMIN_STATUS_TEXT,
|
||||
} from "@/lib/wartung/texte";
|
||||
|
||||
/**
|
||||
* Anzeigebausteine für Wartung und Prüfung.
|
||||
*
|
||||
* Dieselbe Farblehre wie bei den Stammdaten: grün in Ordnung, gelb es eilt,
|
||||
* rot es steht still, grau erledigt. Und wie dort trägt jede Farbe zusätzlich
|
||||
* Klartext — im Gerätehaus steht kein kalibrierter Bildschirm, und
|
||||
* Rot-Grün-Sehschwäche ist häufig.
|
||||
*
|
||||
* Die Beschriftungen selbst stehen in lib/wartung/texte.ts, damit auch
|
||||
* Server-Komponenten sie verwenden können.
|
||||
*/
|
||||
|
||||
export function ErgebnisAbzeichen({ ergebnis }: { ergebnis: PruefErgebnis | null }) {
|
||||
if (!ergebnis) return <Badge variant="secondary">offen</Badge>;
|
||||
|
||||
const variante =
|
||||
ergebnis === "BESTANDEN"
|
||||
? "einsatzbereit"
|
||||
: ergebnis === "BESTANDEN_MIT_MAENGELN"
|
||||
? "pruefungFaellig"
|
||||
: "gesperrt";
|
||||
|
||||
return <Badge variant={variante}>{ERGEBNIS_TEXT[ergebnis]}</Badge>;
|
||||
}
|
||||
|
||||
export function PruefungStatusAbzeichen({ status }: { status: PruefungStatus }) {
|
||||
const variante =
|
||||
status === "ABGESCHLOSSEN" ? "outline" : status === "ENTWURF" ? "pruefungFaellig" : "ausgesondert";
|
||||
|
||||
return <Badge variant={variante}>{PRUEFUNG_STATUS_TEXT[status]}</Badge>;
|
||||
}
|
||||
|
||||
export function TerminStatusAbzeichen({ status }: { status: WartungsterminStatus }) {
|
||||
const variante =
|
||||
status === "OFFEN" ? "outline" : status === "ERLEDIGT" ? "einsatzbereit" : "ausgesondert";
|
||||
|
||||
return <Badge variant={variante}>{TERMIN_STATUS_TEXT[status]}</Badge>;
|
||||
}
|
||||
|
||||
export function FensterAbzeichen({ fenster }: { fenster: Fristfenster }) {
|
||||
const variante =
|
||||
fenster === "ueberfaellig"
|
||||
? "gesperrt"
|
||||
: fenster === "diese_woche"
|
||||
? "pruefungFaellig"
|
||||
: "outline";
|
||||
|
||||
return <Badge variant={variante}>{FENSTER_TEXT[fenster]}</Badge>;
|
||||
}
|
||||
|
||||
export function MangelStatusAbzeichen({ status }: { status: MangelStatus }) {
|
||||
const variante =
|
||||
status === "BEHOBEN"
|
||||
? "einsatzbereit"
|
||||
: status === "VERWORFEN"
|
||||
? "ausgesondert"
|
||||
: status === "IN_ARBEIT"
|
||||
? "pruefungFaellig"
|
||||
: "outline";
|
||||
|
||||
return <Badge variant={variante}>{MANGEL_STATUS_TEXT[status]}</Badge>;
|
||||
}
|
||||
|
||||
export function PrioritaetAbzeichen({ prioritaet }: { prioritaet: MangelPrioritaet }) {
|
||||
const variante =
|
||||
prioritaet === "KRITISCH"
|
||||
? "gesperrt"
|
||||
: prioritaet === "HOCH"
|
||||
? "pruefungFaellig"
|
||||
: prioritaet === "NIEDRIG"
|
||||
? "ausgesondert"
|
||||
: "secondary";
|
||||
|
||||
return <Badge variant={variante}>{PRIORITAET_TEXT[prioritaet]}</Badge>;
|
||||
}
|
||||
|
||||
export function RueckrufStatusAbzeichen({ status }: { status: RueckrufStatus }) {
|
||||
const variante =
|
||||
status === "OFFEN" ? "gesperrt" : status === "IN_BEARBEITUNG" ? "pruefungFaellig" : "einsatzbereit";
|
||||
|
||||
return <Badge variant={variante}>{RUECKRUF_STATUS_TEXT[status]}</Badge>;
|
||||
}
|
||||
|
||||
export function BetroffenheitAbzeichen({ status }: { status: BetroffenheitStatus }) {
|
||||
const variante =
|
||||
status === "ERLEDIGT"
|
||||
? "einsatzbereit"
|
||||
: status === "NICHT_BETROFFEN"
|
||||
? "ausgesondert"
|
||||
: status === "MASSNAHME_LAEUFT"
|
||||
? "pruefungFaellig"
|
||||
: "gesperrt";
|
||||
|
||||
return <Badge variant={variante}>{BETROFFENHEIT_TEXT[status]}</Badge>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kennzahl mit Klartext darunter. Die Zahl ist groß, weil sie aus zwei Metern
|
||||
* Abstand lesbar sein soll — das Tablet liegt beim Prüfen auf der Werkbank.
|
||||
*/
|
||||
export function Kennzahl({
|
||||
wert,
|
||||
bezeichnung,
|
||||
hinweis,
|
||||
dringend,
|
||||
aktiv,
|
||||
onClick,
|
||||
}: {
|
||||
wert: number;
|
||||
bezeichnung: string;
|
||||
hinweis?: string;
|
||||
dringend?: boolean;
|
||||
aktiv?: boolean;
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
const inhalt = (
|
||||
<>
|
||||
<span
|
||||
className={`tabellenzahlen text-3xl font-semibold ${
|
||||
dringend && wert > 0 ? "text-[var(--gesperrt)]" : ""
|
||||
}`}
|
||||
>
|
||||
{wert}
|
||||
</span>
|
||||
<span className="text-sm font-medium">{bezeichnung}</span>
|
||||
{hinweis && <span className="text-xs text-[var(--muted-foreground)]">{hinweis}</span>}
|
||||
</>
|
||||
);
|
||||
|
||||
const klassen = `flex flex-col gap-0.5 rounded-lg border p-4 text-left transition-colors ${
|
||||
aktiv
|
||||
? "border-[var(--primary)] bg-[var(--accent)]"
|
||||
: "border-[var(--border)] bg-[var(--card)]"
|
||||
} ${onClick ? "hover:border-[var(--primary)]" : ""}`;
|
||||
|
||||
if (!onClick) return <div className={klassen}>{inhalt}</div>;
|
||||
|
||||
return (
|
||||
<button type="button" onClick={onClick} className={klassen} aria-pressed={aktiv}>
|
||||
{inhalt}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user