import Link from "next/link";
import { notFound } from "next/navigation";
import { ArrowLeft, MessageSquare, Printer, ScrollText, Star } from "lucide-react";
import { LaufStatus, SchrittTyp, SchrittZustand } from "@/generated/prisma/enums";
import { Markdown } from "@/components/markdown";
import { AbschnittHuelle } from "@/components/laeufe/abschnitt-huelle";
import { Befehlsblock } from "@/components/laeufe/befehlsblock";
import { LaufAktualisierung } from "@/components/laeufe/lauf-aktualisierung";
import {
AbschnittNichtZutreffend,
KommentarSchreiben,
LaufAbschliessen,
LaufArchivieren,
LaufFreigabe,
LaufBeginnen,
LaufMitAdKontoVerknuepfen,
LaufZurueckholen,
} from "@/components/laeufe/lauf-aktionen";
import { SchrittAnhaenge, type SchrittAnhang } from "@/components/laeufe/schritt-anhaenge";
import { SchrittWert } from "@/components/laeufe/schritt-wert";
import { SchrittZustandWaehlen, ZustandKennzeichen } from "@/components/laeufe/schritt-zustand";
import { darf } from "@/server/auth/rollen";
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
import { anhaengeDesLaufs } from "@/server/queries/anhaenge";
import { protokollDesLaufs } from "@/server/queries/protokoll";
import {
kommentareDesLaufs,
laufZumAbarbeiten,
personenZurVerknuepfung,
type AbarbeitungsAbschnitt,
type AbarbeitungsSchritt,
type KommentarZeile,
} from "@/server/queries/laeufe";
/**
* Die Abarbeitungsansicht (#013).
*
* Das ist die Seite, die ein Techniker stundenlang offen hat, oft am Handy, oft
* neben einem aufgeschraubten Rechner. Drei Dinge folgen daraus:
*
* 1. **Jeder Schritt speichert einzeln.** Ein Gesamtformular hiesse, dass ein
* verlorener Klick zwei Stunden Haken kostet.
* 2. **Die Seite aktualisiert sich alle zehn Sekunden** — zwei Techniker teilen
* sich ein Onboarding und sollen die Arbeit des anderen sehen.
* 3. **„Kompakt“ und „Ausführlich“** sind zwei Lesarten desselben Laufs: beim
* dritten Mal will niemand mehr die Hilfetexte lesen, beim ersten Mal sind
* sie der eigentliche Wert.
*/
const ZEITPUNKT = new Intl.DateTimeFormat("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
function Fortschrittsbalken({ erledigt, gesamt }: { erledigt: number; gesamt: number }) {
const anteil = gesamt === 0 ? 0 : erledigt / gesamt;
return (
{/*
* „Kompakt“ blendet **alle** Hilfetexte aus, nicht nur die der
* erledigten Schritte. Beim dritten Mal kennt der Techniker sie, und
* eine halbe Regel („nur bei erledigten“) haette ihm am Handy
* trotzdem die halbe Seite mit Text gefuellt (Reviewbefund zu #013).
*/}
{schritt.hilfetext && ausfuehrlich ? (
{/*
* Die Notiz als Text — aber nur, wenn sie nicht ohnehin im
* Begruendungsfeld steht. `NICHT_ZUTREFFEND` und `FEHLGESCHLAGEN`
* blenden dieses Feld gefuellt ein; ein Absatz darunter waere
* dieselbe Zeile ein zweites Mal.
*/}
{schritt.notiz && !lesend && !brauchtBegruendung ? (
) : null}
{/*
* Anhaenge (#032). Immer sichtbar, wenn es welche gibt — auch an einem
* archivierten Lauf, nur dann ohne Knoepfe. Ein Foto vom Typenschild
* ist Teil des Belegs.
*/}
{anhaenge.length > 0 || (!lesend && schritt.ergebnisId) ? (
) : null}
{lesend || !schritt.ergebnisId ? null : (
Kommentar zu diesem Schritt
)}
);
}
function Abschnitt({
abschnitt,
laufId,
lesend,
ausfuehrlich,
kommentare,
anhaenge,
}: {
abschnitt: AbarbeitungsAbschnitt;
laufId: string;
lesend: boolean;
ausfuehrlich: boolean;
kommentare: readonly KommentarZeile[];
anhaenge: readonly (SchrittAnhang & { readonly schrittergebnisId: string })[];
}) {
const erledigt = abschnitt.schritte.filter(
(schritt) =>
schritt.zustand === SchrittZustand.ERLEDIGT || schritt.zustand === SchrittZustand.NICHT_ZUTREFFEND,
).length;
// Zu, sobald im Abschnitt nichts mehr zu tun ist – aber nur beim ersten Aufbau.
const alleVomTisch = erledigt === abschnitt.schritte.length && abschnitt.schritte.length > 0;
return (
{abschnitt.skript ? (
Alle Befehle als Skript kopieren
Dieser Lauf ist archiviert. Er zeigt für immer, was damals abgearbeitet wurde — geändert wird daran
nichts mehr.
) : null}
{lauf.nurFreitextName && lauf.mitarbeiterName && !lesend ? (
) : null}
{/*
* Die Freigabe-Karte (#040), und zwar **ueber** der Knopfreihe: sie ist
* eine Entscheidung und kein Knopf unter anderen.
*
* Gezeigt wird sie, wenn die Version eine Freigabe verlangt und der Lauf
* noch nicht archiviert ist — danach aendert sich nichts mehr. Wer kein
* `freigeben` hat, sieht sie nur als Auskunft; das Formular bleibt ihm
* verborgen, denn ein Knopf, der nichts tut, ist kein Knopf.
*/}
{lauf.freigabePflicht && !archiviert ? (
darf(benutzer.rolle, "freigeben") ? (
) : (
{lauf.freigegebenAm
? `Freigegeben${lauf.freigegebenVon ? ` von ${lauf.freigegebenVon}` : ""}.`
: "Dieser Lauf verlangt eine Freigabe durch einen Zweiten, bevor er abgeschlossen " +
"werden kann. Die Freigabeberechtigten sind benachrichtigt, sobald du ihn " +
"abzuschließen versuchst."}
{lauf.freigabeNotiz ? ` Letzte Begründung: ${lauf.freigabeNotiz}` : ""}
)
) : null}
{lauf.status === LaufStatus.OFFEN && darfArbeiten ? : null}
{lauf.status === LaufStatus.IN_ARBEIT && darfArbeiten ? (
) : null}
{lauf.status === LaufStatus.ABGESCHLOSSEN && darfArbeiten ? (
) : null}
{archiviert && darf(benutzer.rolle, "archivVerwalten") ? : null}
{ausfuehrlich ? "Kompakt" : "Ausführlich"}
{/*
* Die Druckansicht liegt ausserhalb der Huelle und traegt deshalb kein
* `Link`-Vorabladen: sie ist hell, ohne Navigation und fuer Papier
* gemacht (#015). Auf dem Blatt steht **kein** Passwort — auch dort
* nicht, wo im Lauf eines stuende.
*/}
Drucken
)}
{/*
* Die Geschichte dieses Laufs, eingeklappt (#023). Sie steht hier und
* nicht nur unter Verwaltung → Protokoll, weil die Frage „wer hat das
* zurückgeholt“ am Lauf aufkommt und nicht in der Verwaltung. Eingeklappt,
* weil ein abgearbeiteter Lauf Dutzende Einträge hat und sie die Schritte
* sonst aus dem Bild schieben.
*
* Kein eigenes Recht: wer den Lauf sehen darf, darf auch sehen, was an ihm
* geschehen ist. Die Mandantensicht hat `laufHolen` oben schon angewandt.
*/}
{laufProtokoll.length > 0 ? (
Protokoll dieses Laufs
({laufProtokoll.length})
{laufProtokoll.map((eintrag) => (