From 62945201b148ed510414094870ea8c99552f0707 Mon Sep 17 00:00:00 2001 From: Raffael Sheikh Date: Sun, 7 Jun 2026 12:45:19 +0200 Subject: [PATCH] Feature: Lessons-Learned-Vorlage beim Projekt-Abschluss Der Abschluss verlangte eine URL + freie Vermerk-Textarea, half aber nicht beim Strukturieren der Retro. Jetzt gefuehrte Felder (Ziel erreicht? / Aufwand vs. Plan / was lief gut / was nicht), gespeichert als JSONB project.lessons_learned (Migration 0018). Anzeige im Projekt-Detail als "Retrospektive"; Altprojekte fallen auf den freien Abschlussvermerk zurueck. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/api/src/routers/project.ts | 1 + apps/web/src/routes/projekt.tsx | 86 +- packages/db/migrations/0018_last_lake.sql | 1 + .../db/migrations/meta/0018_snapshot.json | 3350 +++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/src/schema.ts | 8 + packages/shared/src/schemas/project.ts | 10 + 7 files changed, 3443 insertions(+), 20 deletions(-) create mode 100644 packages/db/migrations/0018_last_lake.sql create mode 100644 packages/db/migrations/meta/0018_snapshot.json diff --git a/apps/api/src/routers/project.ts b/apps/api/src/routers/project.ts index 3b9d0dc..2cfa77d 100644 --- a/apps/api/src/routers/project.ts +++ b/apps/api/src/routers/project.ts @@ -137,6 +137,7 @@ export const projectRouter = router({ phase: 'abschluss', lessonsLearnedUrl: input.lessonsLearnedUrl, abschlussvermerk: input.abschlussvermerk ?? p.abschlussvermerk, + lessonsLearned: input.lessonsLearned ?? p.lessonsLearned, pflichtMinimumState: { ...p.pflichtMinimumState, retrospektive: true }, }) .where(eq(project.id, input.projectId)) diff --git a/apps/web/src/routes/projekt.tsx b/apps/web/src/routes/projekt.tsx index 6fc5e66..f8a2d88 100644 --- a/apps/web/src/routes/projekt.tsx +++ b/apps/web/src/routes/projekt.tsx @@ -143,7 +143,7 @@ export function ProjektRoute() { {abschlussOpen && ( setAbschlussOpen(false)} /> )} @@ -205,15 +205,29 @@ export function ProjektRoute() { )} - {p.abschlussvermerk && ( + {p.lessonsLearned ? ( -

Abschlussvermerk

+

Retrospektive

- -

{p.abschlussvermerk}

+ + + + +
+ ) : ( + p.abschlussvermerk && ( + + +

Abschlussvermerk

+
+ +

{p.abschlussvermerk}

+
+
+ ) )} ); @@ -2305,17 +2319,37 @@ function AddMilestoneForm({ projectId, onDone }: { projectId: string; onDone: () ); } +function RetroField({ label, value }: { label: string; value: string }) { + if (!value?.trim()) return null; + return ( +
+

{label}

+

{value}

+
+ ); +} + function AbschlussDialog({ projectId, - abschlussvermerk, + lessonsLearned, onClose, }: { projectId: string; - abschlussvermerk: string | null; + lessonsLearned: { + zielErreicht: string; + aufwand: string; + liefGut: string; + liefSchlecht: string; + } | null; onClose: () => void; }) { const [url, setUrl] = useState(''); - const [vermerk, setVermerk] = useState(abschlussvermerk ?? ''); + const [ll, setLl] = useState({ + zielErreicht: lessonsLearned?.zielErreicht ?? '', + aufwand: lessonsLearned?.aufwand ?? '', + liefGut: lessonsLearned?.liefGut ?? '', + liefSchlecht: lessonsLearned?.liefSchlecht ?? '', + }); const [checks, setChecks] = useState({ abnahme: false, aufraeumen: false, retro: false }); const navigate = useNavigate(); const utils = trpc.useUtils(); @@ -2330,10 +2364,11 @@ function AbschlussDialog({ }); function submit(e: React.FormEvent) { e.preventDefault(); + const hasLl = Object.values(ll).some((v) => v.trim()); mutation.mutate({ projectId, lessonsLearnedUrl: url, - abschlussvermerk: vermerk.trim() || undefined, + lessonsLearned: hasLl ? ll : undefined, }); } const checkItems: Array<{ key: keyof typeof checks; label: string }> = [ @@ -2360,17 +2395,28 @@ function AbschlussDialog({ ))} -