'use client' import { useState, useTransition } from 'react' import { caseText, entryText } from '@/lib/i18n/cases' import { actionErrorsText } from '@/lib/i18n/common' import { changeStatusAction } from '@/app/(app)/faelle/actions' import type { StatusOption } from '@/lib/cases/lifecycle' /** * Der Lebenszyklus als Karte statt als Auswahlliste. * * Vorher stand hier ein Auswahlfeld, das nur die gerade erreichbaren Zustände * enthielt. Das liest sich nicht: man sah weder, wo die Akte steht, noch * welche Zustände es überhaupt gibt, und die Liste hatte nach jedem Wechsel * andere Einträge. Eine Auswahl, die ihre Einträge tauscht, ist keine * Orientierung. * * Jetzt stehen **immer alle sieben** da, in der Reihenfolge des Lebenszyklus. * Was sich ändert, ist allein die Schaltbarkeit - und wer auf einen gesperrten * Zustand zeigt, erfährt den Grund, statt ihn zu raten. * * Nicht optimistisch: ein Statuswechsel schreibt einen Verlaufseintrag und ist * teils nicht umkehrbar. Siehe docs/09-performance-und-ux.md. */ const MIN_BEGRUENDUNG = 10 export function StatusMap({ caseNumber, current, options, }: { caseNumber: string current: string /** Zu jedem Zustand die Auskunft vom Server. Reihenfolge = Lebenszyklus. */ options: StatusOption[] }) { const [ziel, setZiel] = useState(null) const [grund, setGrund] = useState('') const [fehler, setFehler] = useState(null) const [pending, startTransition] = useTransition() const gewaehlt = options.find((o) => o.status === ziel) ?? null const brauchtGrund = gewaehlt?.requiresReason ?? false const grundFehlt = brauchtGrund && grund.trim().length < MIN_BEGRUENDUNG const irgendwasMoeglich = options.some((o) => o.available) // Hauptweg und Nebenwege getrennt: DRAFT bis ARCHIVED ist eine Linie, // Ruhend und Versiegelt hängen daneben. In einer Reihe sähe es aus, als // käme nach "Archiviert" noch etwas. const haupt = options.slice(0, 5) const neben = options.slice(5) function wechseln() { if (!ziel) return const name = caseText.statusNames[ziel] ?? ziel if (!confirm(`${caseText.statusTo(name)}?`)) return setFehler(null) startTransition(async () => { const ergebnis = await changeStatusAction({ caseNumber, to: ziel, reason: grund.trim() || undefined, }) if (ergebnis.ok) { setZiel(null) setGrund('') } else { setFehler(ergebnis.error ?? actionErrorsText.default!) } }) } return (

{caseText.statusMap}

{haupt.map((option, index) => ( {index > 0 ? › : null} setZiel(option.status)} disabled={pending} /> ))}
{caseText.statusSide} {neben.map((option) => ( setZiel(option.status)} disabled={pending} /> ))}
{/* Was der gewählte Zustand bedeutet - und was er verlangt. Beides stand vorher nirgends, sondern kam erst als Absage zurück. */}

{ziel ? (caseText.statusHints[ziel] ?? '') : irgendwasMoeglich ? caseText.statusPick : caseText.statusNothingPossible}

{gewaehlt?.requiresCompleteSections ? (

{caseText.statusNeedsComplete}

) : null} {ziel ? (
setGrund(event.target.value)} placeholder={entryText.changeReason} aria-label={entryText.changeReason} className="field w-56" /> {brauchtGrund ? ( {caseText.statusReasonRequired} ) : null}
) : null} {fehler ?

{fehler}

: null}
) } /** * Ein Zustand. Immer sichtbar - gesperrt heisst blass und nicht anklickbar, * nicht abwesend. Der Grund steht als Titel daran, damit niemand rät. */ function StatusChip({ option, current, selected, onPick, disabled, }: { option: StatusOption current: string selected: boolean onPick: () => void disabled: boolean }) { const name = caseText.statusNames[option.status] ?? option.status const istJetzt = option.status === current const grund = option.block ? option.block.kind === 'no-permission' ? caseText.statusNoPermission(option.block.permission) : option.block.kind === 'locked' ? (caseText.statusBlocked[option.block.reason] ?? caseText.statusBlocked.locked!) : (caseText.statusBlocked[option.block.kind] ?? '') : (caseText.statusHints[option.status] ?? '') if (istJetzt) { return ( {name} {caseText.statusNow} ) } return ( ) }