"use client"; import { MessageSquarePlus } from "lucide-react"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { Button } from "@/components/ui/Button"; import { useToast } from "@/components/ui/Toast"; import { alleReichungen, fortschrittVon, istErledigt, type AufgabenGruppe, type AufgabenPunkt, type AufgabenStand, } from "@/lib/checklist"; import { fmtDate } from "@/lib/format"; import type { ActionResult } from "@/lib/db/rpc"; // Die Checkliste, die bisher ein Blatt Papier war — geteilt zwischen // Onboarding und Offboarding. Beide sind dieselbe Sache mit anderen Punkten: // eine Liste, die sich abhaken, mit Ja/Nein beantworten oder mit einem Wert // füllen lässt, mit Kommentar, und mit einem Vermerk, **wer** wann etwas // eingetragen hat — auf dem Blatt stand nur ein Haken. // // Gespeichert wird beim Klick, nicht beim Absenden. Eine Checkliste wird über // Tage abgearbeitet, oft zwischen zwei anderen Dingen; ein „Speichern"-Knopf // am Ende wäre die Stelle, an der ein halber Vormittag verlorengeht. type Props = { gruppen: readonly AufgabenGruppe[]; staende: AufgabenStand[]; /** Ob überhaupt eine Liste existiert. */ vorhanden: boolean; /** Text, solange keine Liste existiert. */ leerText: string; anlegenLabel: string; onSpeichern: ( itemKey: string, teil: { erledigt?: boolean; wert?: string | null; kommentar?: string | null } ) => Promise; onAnlegen: () => Promise; }; export function ChecklistPanel({ gruppen, staende, vorhanden, leerText, anlegenLabel, onSpeichern, onAnlegen }: Props) { const { showToast } = useToast(); const router = useRouter(); const [laeuft, setLaeuft] = useState(null); const [kommentarOffen, setKommentarOffen] = useState>(new Set()); const punkte = alleReichungen(gruppen); const karte = new Map(staende.map((s) => [s.item_key, s])); const stand = fortschrittVon(punkte, staende); async function speichern(key: string, teil: { erledigt?: boolean; wert?: string | null; kommentar?: string | null }) { setLaeuft(key); const ergebnis = await onSpeichern(key, teil); setLaeuft(null); if (ergebnis.success) router.refresh(); else showToast(ergebnis.error ?? "Konnte nicht gespeichert werden.", "error"); } async function listeAnlegen() { setLaeuft("neu"); const ergebnis = await onAnlegen(); setLaeuft(null); if (ergebnis.success) { showToast("Checkliste angelegt."); router.refresh(); } else { showToast(ergebnis.error ?? "Konnte nicht angelegt werden.", "error"); } } if (!vorhanden) { return (

{leerText}

); } const anteil = Math.round((stand.erledigt / stand.gesamt) * 100); return (
{stand.erledigt} von {stand.gesamt} erledigt {stand.offen.length === 0 ? "Vollständig." : `Offen: ${stand.offen.slice(0, 3).join(", ")}`} {stand.offen.length > 3 ? ` und ${stand.offen.length - 3} weitere` : ""}
{gruppen.map((gruppe) => (

{gruppe.label}

    {gruppe.punkte.map((punkt) => ( setKommentarOffen((prev) => { const next = new Set(prev); next.add(punkt.key); return next; }) } onSpeichern={(teil) => speichern(punkt.key, teil)} /> ))}
))}
); } function Zeile({ punkt, stand, laeuft, kommentarOffen, onKommentarOeffnen, onSpeichern, }: { punkt: AufgabenPunkt; stand: AufgabenStand | undefined; laeuft: boolean; kommentarOffen: boolean; onKommentarOeffnen: () => void; onSpeichern: (teil: { erledigt?: boolean; wert?: string | null; kommentar?: string | null }) => void; }) { const erledigt = istErledigt(punkt, stand); const [text, setText] = useState(stand?.wert ?? ""); const [kommentar, setKommentar] = useState(stand?.kommentar ?? ""); const zeigeKommentar = kommentarOffen || Boolean(stand?.kommentar); return (
  • {punkt.label} {punkt.hinweis && {punkt.hinweis}}
    {punkt.art === "haken" && ( onSpeichern({ erledigt: e.target.checked })} className="h-4 w-4 shrink-0 rounded border-border text-brand-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-500" /> )} {punkt.art === "janein" && ( // Zwei Knöpfe statt eines Hakens: „nein" ist ein erhobener Befund, // „noch nicht gefragt" nicht. Ein Haken könnte das nicht sagen. {(["ja", "nein"] as const).map((wert) => ( ))} )} {punkt.art === "text" && ( setText(e.target.value)} onBlur={() => text !== (stand?.wert ?? "") && onSpeichern({ wert: text || null })} placeholder="Wert" className="w-28 shrink-0 rounded border border-border px-2 py-1 text-sm text-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-brand-500" /> )} {!zeigeKommentar && ( )}
    {zeigeKommentar && ( setKommentar(e.target.value)} onBlur={() => kommentar !== (stand?.kommentar ?? "") && onSpeichern({ kommentar: kommentar || null })} placeholder="Kommentar …" className="mt-1.5 w-full rounded border border-border bg-surface px-2 py-1 text-xs text-ink-body focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-brand-500" /> )} {/* Wer und wann — der Unterschied zum Blatt Papier, auf dem nur der Haken stand. */} {stand?.updated_by_name && (erledigt || stand.kommentar) && (

    {stand.updated_by_name} {stand.updated_at ? ` · ${fmtDate(stand.updated_at)}` : ""}

    )}
  • ); }