Baustein 3: Wochenplan als Startseite, Fortschritt, Wochen verschieben

- Wochenplan ist die Startseite des Azubis: Thema, Leitfrage, fuenf Tage
  mit Theorie und Praxis, Fortschrittsbalken, was noch offen ist
- Ausbilder sieht je Azubi die laufende Woche mit Stand und offenen Punkten
- /woche/<nr> fuer jede Woche, Ausbilder waehlt ueber ?azubi= den Stand;
  Zugriff wird serverseitig bei jedem Aufruf geprueft
- Tabelle wochenplanung mit geplantem und tatsaechlichem Datum, Zustand
  und Notiz fuer den Azubi
- Verschieben per Datumsfeld, wahlweise mit allen folgenden Wochen
- Reihenfolge per Ziehen und Ablegen sowie per Tastatur (Alt + Pfeil)
- Warnung bei sich ueberlappenden Terminen
- Plan entsteht automatisch beim Anlegen eines Azubikontos
- Planungs- und Fortschrittslogik als reine Funktionen, 36 Tests dafuer
- Insgesamt 104 Tests

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-10 09:04:39 +02:00
co-authored by Claude Opus 5
parent 1af5613e9b
commit 103a603eb4
29 changed files with 2158 additions and 47 deletions
@@ -0,0 +1,146 @@
'use client';
import { useActionState } from 'react';
import { Absenden, Meldung } from '@/komponenten/Formularmeldung';
import { planErzeugen, wochenNotiz, wocheVerlegen, wochenStatusSetzen, type Rueckmeldung } from './aktionen';
export function PlanErzeugenFormular({ azubiId, text }: { azubiId: string; text: string }) {
const [zustand, aktion] = useActionState<Rueckmeldung, FormData>(planErzeugen, {});
return (
<form action={aktion} className="stapel-eng">
<Meldung zustand={zustand} />
<input type="hidden" name="azubiId" value={azubiId} />
<Absenden className="knopf knopf-akzent">{text}</Absenden>
</form>
);
}
export function VerlegenFormular({
azubiId,
wochen,
}: {
azubiId: string;
wochen: { nummer: number; titel: string; geplantVon: string }[];
}) {
const [zustand, aktion] = useActionState<Rueckmeldung, FormData>(wocheVerlegen, {});
const erste = wochen[0];
return (
<form action={aktion} className="stapel">
<Meldung zustand={zustand} />
<input type="hidden" name="azubiId" value={azubiId} />
<div className="reihe" style={{ alignItems: 'flex-end' }}>
<div style={{ flex: '2 1 240px' }}>
<label htmlFor="verlegen-woche">Woche</label>
<select id="verlegen-woche" name="wochenNummer" defaultValue={erste?.nummer}>
{wochen.map((w) => (
<option key={w.nummer} value={w.nummer}>
Woche {w.nummer} – {w.titel}
</option>
))}
</select>
</div>
<div style={{ flex: '1 1 170px' }}>
<label htmlFor="verlegen-datum">Neuer Beginn</label>
<input id="verlegen-datum" name="neuerMontag" type="date" defaultValue={erste?.geplantVon} required />
</div>
<Absenden className="knopf">Verlegen</Absenden>
</div>
<label className="ankreuzfeld">
<input type="checkbox" name="folgende" value="an" defaultChecked />
<span>
Folgende Wochen mitverschieben
<span className="leise" style={{ display: 'block' }}>
Der Normalfall: Fällt eine Woche aus, rutscht der ganze Rest hinterher. Ohne Haken wird nur diese
eine Woche verlegt – dann können sich Termine überlappen.
</span>
</span>
</label>
<p className="leise" style={{ margin: 0 }}>
Ein beliebiges Datum wird auf den Montag seiner Woche gerundet. Abgeschlossene Wochen bleiben stehen.
</p>
</form>
);
}
export function StatusFormular({
azubiId,
wochenNummer,
status,
}: {
azubiId: string;
wochenNummer: number;
status: 'geplant' | 'laufend' | 'abgeschlossen';
}) {
return (
<form action={wochenStatusSetzen} className="reihe" style={{ gap: '0.35rem' }}>
<input type="hidden" name="azubiId" value={azubiId} />
<input type="hidden" name="wochenNummer" value={wochenNummer} />
<button
type="submit"
name="status"
value="laufend"
className="knopf knopf-still"
disabled={status === 'laufend'}
>
Startet jetzt
</button>
<button
type="submit"
name="status"
value="abgeschlossen"
className="knopf knopf-still"
disabled={status === 'abgeschlossen'}
>
Abschließen
</button>
<button
type="submit"
name="status"
value="geplant"
className="knopf knopf-still"
disabled={status === 'geplant'}
>
Zurücksetzen
</button>
</form>
);
}
export function NotizFormular({
azubiId,
wochenNummer,
notiz,
}: {
azubiId: string;
wochenNummer: number;
notiz: string;
}) {
const [zustand, aktion] = useActionState<Rueckmeldung, FormData>(wochenNotiz, {});
return (
<form action={aktion} className="stapel-eng">
<Meldung zustand={zustand} />
<input type="hidden" name="azubiId" value={azubiId} />
<input type="hidden" name="wochenNummer" value={wochenNummer} />
<label htmlFor={`notiz-${wochenNummer}`}>Notiz zu dieser Woche (sieht der Azubi)</label>
<textarea
id={`notiz-${wochenNummer}`}
name="notiz"
defaultValue={notiz}
rows={2}
maxLength={500}
placeholder="z. B. Dienstag Berufsschule, Mittwoch Kundeneinsatz Halle 3"
/>
<div>
<Absenden className="knopf knopf-zweit">Notiz speichern</Absenden>
</div>
</form>
);
}