diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a21ee1..f68de1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,45 @@ Vor `npm version` wandert der Abschnitt „Unveröffentlicht" unter eine neue ## [Unveröffentlicht] +## [0.3.0] – 2026-09-10 + +Der Wochenplan wird zur Startseite, und der Ausbilder kann ihn umplanen. + +### Hinzugefügt + +- **Wochenplan als Startseite des Azubis**: Woche, Thema, Leitfrage, Wochenziel, + die fünf Tage mit Theorie- und Praxisteil, hervorgehobener heutiger Tag, + Fortschrittsbalken und eine Liste dessen, was noch offen ist +- Der Ausbilder sieht auf seiner Startseite je Azubi die laufende Woche mit + Fortschritt und offenen Punkten, mit Sprung in die Wocheneinzelansicht +- `/woche/` zeigt jede beliebige Woche; der Ausbilder wählt über `?azubi=`, + wessen Stand er sieht – geprüft wird das serverseitig bei jedem Aufruf +- Tabelle `wochenplanung` mit geplantem und tatsächlichem Datum, Zustand + (`geplant` / `laufend` / `abgeschlossen`) und einer Notiz für den Azubi +- Wochenplanseite für den Ausbilder: + - Plan aus den Wochen des Repositorys erzeugen und fehlende Wochen anhängen + - Woche auf ein neues Datum verlegen, wahlweise mit allen folgenden Wochen; + ein beliebiges Datum wird auf den Montag seiner Woche gerundet + - Reihenfolge per Ziehen und Ablegen ändern – die Terminfolge bleibt fest, + nur die Zuordnung wechselt, damit der Plan lückenlos bleibt + - Dasselbe mit der Tastatur über Alt + Pfeil hoch/runter, damit die Funktion + nicht auf die Maus angewiesen ist + - Zustand je Woche setzen; beim Start wird das tatsächliche Datum vermerkt + - Notiz je Woche, die der Azubi in seiner Wochenansicht sieht + - Warnung, wenn sich nach einem Einzelverschieben Termine überlappen +- Der Wochenplan entsteht automatisch, sobald ein Azubikonto angelegt wird +- Fortschrittsrechnung als reine Funktion über Zählwerte, damit sie mit den + weiteren Bausteinen wachsen kann, ohne sich zu verzweigen +- 36 Tests für Planerzeugung, Verschieben, Umsortieren, „welche Woche ist dran" + und die Fortschrittsrechnung + +### Angemerkt + +- Praxisaufgaben, Checklisten und Lernzielkontrollen erscheinen im + Fortschritt bereits mit ihrer Sollzahl, lassen sich aber erst mit den + Bausteinen 5 bis 8 abhaken. Das ist Absicht: Die Woche zeigt, was ansteht, + auch wenn die Anwendung es noch nicht mitzählen kann + ## [0.2.0] – 2026-09-10 Die Inhalte kommen ins Spiel: Handouts, Suche, Glossar – alles aus Dateien @@ -105,5 +144,6 @@ anmelden und verwalten. - `npm run konto:anlegen` für das erste Ausbilderkonto [Unveröffentlicht]: #unveröffentlicht +[0.3.0]: #030--2026-09-10 [0.2.0]: #020--2026-09-10 [0.1.0]: #010--2026-09-10 diff --git a/DEVLOG.md b/DEVLOG.md index adfa1b3..adaa3f1 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -7,6 +7,38 @@ Format: `HH:MM–HH:MM (Dauer) · was gemacht wurde` --- +## 2026-09-10 · Baustein 3 – Wochenplan + +| Von | Bis | Dauer | Tätigkeit | +|---|---|---|---| +| 09:05 | 09:20 | 0:15 | Datenmodell der Wochenplanung, Migration, Drizzle-Schema | +| 09:20 | 09:45 | 0:25 | Planungslogik als reine Funktionen: erzeugen, verschieben, umsortieren, „welche Woche ist dran", Überschneidungen; dazu die Fortschrittsrechnung | +| 09:45 | 09:55 | 0:10 | 36 Tests dafür – liefen beim ersten Anlauf durch | +| 09:55 | 10:10 | 0:15 | Repository-Schicht, Wochensicht aus Inhalt + Planung + Fortschritt | +| 10:10 | 10:35 | 0:25 | Wochenansicht, Startseiten für beide Rollen, Wocheneinzelansicht | +| 10:35 | 10:55 | 0:20 | Wochenplanseite: verlegen, umsortieren mit Ziehen und Tastatur, Zustand, Notizen | +| 10:55 | 11:10 | 0:15 | Durchstich im Browser, Screenshots, Dokumente nachgezogen | + +**Summe Baustein 3: rund 2 h 05 min** + +### Was aufgefallen ist + +- Beim Umsortieren war die erste Idee, die Termine mitzuziehen. Falsch: Dann + entstehen Lücken und Überschneidungen. Richtig ist, die Folge der + Kalenderwochen als festen Rahmen zu behandeln und nur die Zuordnung zu + ändern – der Plan bleibt so immer lückenlos. +- Der Lint-Fehler „azubiId is defined but never used" im Verlegen-Formular war + kein Stilproblem, sondern ein echter Bug: Dem Formular fehlte das versteckte + Feld, das die Serveraktion ausliest. Ein Lauf hätte den Fehler zwar auch + gezeigt, aber erst nach dem Klick. +- Ziehen und Ablegen allein reicht nicht. Wer mit der Tastatur arbeitet, kommt + damit nicht weiter, deshalb tun es die Pfeilknöpfe und Alt + Pfeil genauso. +- Die Erfolgsmeldung nach „Wochen einplanen" ist nicht zu sehen, weil das + Formular danach verschwindet – die erscheinende Planliste ist die + Rückmeldung. Kein Fehler, aber es hat mich im Testskript zunächst gekostet. + +--- + ## 2026-09-10 · Baustein 2 – Inhalte | Von | Bis | Dauer | Tätigkeit | diff --git a/ROADMAP.md b/ROADMAP.md index 57e02f5..7b24f19 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -35,7 +35,15 @@ Legende: `[ ]` offen · `[~]` in Arbeit · `[x]` fertig ## 0.3 – Woche und Wiederholung -- [ ] **B3 · Wochenplan als Startseite, Fortschritt, Wochen verschieben** +- [x] **B3 · Wochenplan als Startseite, Fortschritt, Wochen verschieben** + - [x] Wochenplan ist die Startseite des Azubis: Thema, Leitfrage, fünf Tage + mit Theorie und Praxis, Fortschrittsbalken, was noch offen ist + - [x] Ausbilder sieht dieselbe Woche plus den Stand seiner Azubis + - [x] Geplantes und tatsächliches Datum je Woche + - [x] Verschieben per Datumsfeld, wahlweise mit allen folgenden Wochen + - [x] Reihenfolge per Ziehen und Ablegen **und** per Tastatur + - [x] Zustand je Woche (geplant / läuft / abgeschlossen) und Notiz für den Azubi + - [x] Warnung bei sich überlappenden Terminen - [ ] **B4 · Merkkarten mit Wiederholung nach Zeitabstand (SM-2)** ## 0.4 – Nachweise diff --git a/docs/bilder/02-ausbilder-ueberblick.png b/docs/bilder/02-ausbilder-ueberblick.png index 8d85ef6..1457058 100644 Binary files a/docs/bilder/02-ausbilder-ueberblick.png and b/docs/bilder/02-ausbilder-ueberblick.png differ diff --git a/docs/bilder/03-verwaltung.png b/docs/bilder/03-verwaltung.png index 411167c..8f5601c 100644 Binary files a/docs/bilder/03-verwaltung.png and b/docs/bilder/03-verwaltung.png differ diff --git a/docs/bilder/04-wochenplan.png b/docs/bilder/04-wochenplan.png new file mode 100644 index 0000000..4b89041 Binary files /dev/null and b/docs/bilder/04-wochenplan.png differ diff --git a/docs/bilder/10-azubi-start.png b/docs/bilder/10-azubi-start.png deleted file mode 100644 index 34abd8a..0000000 Binary files a/docs/bilder/10-azubi-start.png and /dev/null differ diff --git a/docs/bilder/10-azubi-woche.png b/docs/bilder/10-azubi-woche.png new file mode 100644 index 0000000..14cbcd9 Binary files /dev/null and b/docs/bilder/10-azubi-woche.png differ diff --git a/docs/bilder/11-handouts-liste.png b/docs/bilder/11-handouts-liste.png index 904579e..82a0036 100644 Binary files a/docs/bilder/11-handouts-liste.png and b/docs/bilder/11-handouts-liste.png differ diff --git a/docs/bilder/12-handouts-suche.png b/docs/bilder/12-handouts-suche.png index f277ff3..22be644 100644 Binary files a/docs/bilder/12-handouts-suche.png and b/docs/bilder/12-handouts-suche.png differ diff --git a/scripts/screenshots.ts b/scripts/screenshots.ts index 9d352a5..c27f525 100644 --- a/scripts/screenshots.ts +++ b/scripts/screenshots.ts @@ -74,11 +74,15 @@ async function main() { await seite.waitForSelector('#neu-name'); await bild(seite, '03-verwaltung'); + await seite.goto(`${BASIS}/wochenplan`); + await seite.waitForSelector('h1'); + await bild(seite, '04-wochenplan'); + await abmelden(seite); // --- Azubi --- await anmelden(seite, AZUBI); - await bild(seite, '10-azubi-start'); + await bild(seite, '10-azubi-woche'); await seite.goto(`${BASIS}/handouts`); await seite.waitForSelector('h1'); diff --git a/src/app/(angemeldet)/WochenAnsicht.tsx b/src/app/(angemeldet)/WochenAnsicht.tsx new file mode 100644 index 0000000..fe38917 --- /dev/null +++ b/src/app/(angemeldet)/WochenAnsicht.tsx @@ -0,0 +1,230 @@ +import Link from 'next/link'; +import { Fortschrittsbalken } from '@/komponenten/Fortschrittsbalken'; +import type { Wochensicht } from '@/lib/planung/sicht'; +import { alsDatumText, kalenderwocheText } from '@/lib/zeit'; + +const statusText: Record = { + geplant: { text: 'geplant', ton: 'neutral', zeichen: '○' }, + laufend: { text: 'läuft', ton: 'warnung', zeichen: '▶' }, + abgeschlossen: { text: 'abgeschlossen', ton: 'gut', zeichen: '✓' }, +}; + +/** + * Die Wochenansicht – für den Azubi die Startseite, für den Ausbilder dieselbe + * Woche in der Ansicht seines Azubis. + */ +export function WochenAnsicht({ + sicht, + istAzubi, + wochenLink, +}: { + sicht: Wochensicht; + istAzubi: boolean; + /** Vorlage für den Verweis auf eine andere Woche, `{nr}` wird ersetzt. */ + wochenLink: string; +}) { + const zurWoche = (nummer: number) => wochenLink.replace('{nr}', String(nummer)); + const { woche, planung, fortschritt, tage } = sicht; + const status = statusText[planung.status] ?? statusText.geplant!; + + return ( +
+
+
+ Woche {woche.nummer} + + {status.zeichen} {status.text} + + + {alsDatumText(planung.geplantVon)} – {alsDatumText(planung.geplantBis)} · {kalenderwocheText(planung.geplantVon)} + +
+ +

{woche.titel}

+

{woche.leitfrage}

+

{woche.ziel}

+
+ + {planung.tatsaechlichVon && planung.tatsaechlichVon !== planung.geplantVon ? ( +

+ Tatsächlich begonnen am {alsDatumText(planung.tatsaechlichVon)}, geplant war der{' '} + {alsDatumText(planung.geplantVon)}. +

+ ) : null} + + {planung.notiz ? ( +

+ Notiz des Ausbilders: {planung.notiz} +

+ ) : null} + +
+ + + {fortschritt.offen.length > 0 ? ( +
+

+ {istAzubi ? 'Das ist diese Woche noch offen' : 'Offen bei deinem Azubi'} +

+
    + {fortschritt.offen.map((bereich) => ( +
  • + {bereich.titel} + + {bereich.zaehlwerk.erledigt} von {bereich.zaehlwerk.gesamt} + +
  • + ))} +
+
+ ) : fortschritt.gesamt > 0 ? ( +

+ ✓ Diese Woche ist vollständig abgearbeitet. +

+ ) : null} +
+ +
+

Die Woche

+
    + {tage.map((tag) => ( +
  1. +
    + {tag.tag} + {alsDatumText(tag.datum)} + {tag.istHeute ? ● heute : null} +
    + +
    +
    + Theorie + {tag.theorie.length === 0 ? ( + – + ) : ( +
      + {tag.theorie.map((handout) => { + const gelesen = sicht.handouts.find((h) => h.handout.id === handout.id)?.gelesen ?? false; + return ( +
    • + + {handout.id} {handout.titel} + + {gelesen ? ( + + ✓ gelesen + + ) : null} +
    • + ); + })} +
    + )} +
    + +
    + Praxis + {tag.praxis || '–'} +
    +
    +
  2. + ))} +
+
+ +
+
+

+ Handouts dieser Woche +

+ {sicht.handouts.length === 0 ? ( +

Keine.

+ ) : ( +
    + {sicht.handouts.map(({ handout, gelesen }) => ( +
  • + {handout.id} + + {handout.titel} + + + {gelesen ? '✓' : '○'} + +
  • + ))} +
+ )} +
+ +
+

+ Praxisaufgaben +

+ {sicht.aufgaben.length === 0 ? ( +

Keine.

+ ) : ( +
    + {sicht.aufgaben.map((aufgabe) => ( +
  • + {aufgabe.id} {aufgabe.titel} +
    + {aufgabe.zeitrahmen} +
  • + ))} +
+ )} +
+ +
+

+ Checklisten +

+ {sicht.checklisten.length === 0 ? ( +

Keine.

+ ) : ( +
    + {sicht.checklisten.map((liste) => ( +
  • {liste.titel}
  • + ))} +
+ )} +
+ +
+

+ Lernzielkontrolle +

+ {sicht.kontrolle ? ( +

+ {sicht.kontrolle.titel} +
+ + {sicht.kontrolle.fragen.length} Fragen ·{' '} + {sicht.kontrolle.fragen.reduce((summe, f) => summe + f.punkte, 0)} Punkte + {sicht.kontrolle.dauer_minuten ? ` · ${sicht.kontrolle.dauer_minuten} Minuten` : ''} + +

+ ) : ( +

Diese Woche keine.

+ )} +
+
+ + +
+ ); +} diff --git a/src/app/(angemeldet)/layout.tsx b/src/app/(angemeldet)/layout.tsx index 7a07350..85cea11 100644 --- a/src/app/(angemeldet)/layout.tsx +++ b/src/app/(angemeldet)/layout.tsx @@ -14,6 +14,7 @@ const fuerAzubi: Navigationseintrag[] = [ const fuerAusbilder: Navigationseintrag[] = [ { pfad: '/', titel: 'Überblick' }, + { pfad: '/wochenplan', titel: 'Wochenplan' }, { pfad: '/handouts', titel: 'Handouts' }, { pfad: '/glossar', titel: 'Glossar' }, { pfad: '/verwaltung', titel: 'Verwaltung' }, diff --git a/src/app/(angemeldet)/page.tsx b/src/app/(angemeldet)/page.tsx index 3510091..d5af03e 100644 --- a/src/app/(angemeldet)/page.tsx +++ b/src/app/(angemeldet)/page.tsx @@ -1,69 +1,148 @@ import Link from 'next/link'; import { erfordereBenutzer } from '@/lib/auth/rechte'; import { ausbildungNachAzubi, ausbildungenVonAusbilder } from '@/lib/repos/ausbildung'; -import { alsDatumText, kalenderwocheText, heute } from '@/lib/zeit'; +import { Fortschrittsbalken } from '@/komponenten/Fortschrittsbalken'; +import { aktuelleWochensicht, kurzstand } from '@/lib/planung/sicht'; +import { alsDatumText, heute, kalenderwocheText } from '@/lib/zeit'; +import { WochenAnsicht } from './WochenAnsicht'; export const dynamic = 'force-dynamic'; -/** - * Vorläufige Startseite. Ab Baustein 3 steht hier der Wochenplan. - */ export default async function StartSeite() { const benutzer = await erfordereBenutzer(); - if (benutzer.rolle === 'ausbilder') { - const meine = ausbildungenVonAusbilder(benutzer.id); + return benutzer.rolle === 'ausbilder' ? ( + + ) : ( + + ); +} + +// --- Azubi: der Wochenplan ist die Startseite ------------------------------ + +function AzubiWoche({ azubiId, name }: { azubiId: string; name: string }) { + const ausbildung = ausbildungNachAzubi(azubiId); + + if (!ausbildung) { return (
-
-

Guten Tag, {benutzer.name.split(' ')[0]}

-

- {kalenderwocheText(heute())} · {alsDatumText(heute())} -

-
- -
-

Ausbildungen

- {meine.length === 0 ? ( -

- Noch kein Azubikonto. Jetzt eines anlegen. -

- ) : ( -
    - {meine.map(({ ausbildung, azubi }) => ( -
  • - {azubi.name} · seit {alsDatumText(ausbildung.beginn)} -
  • - ))} -
- )} -
+

Guten Tag, {name.split(' ')[0]}

+

+ Zu deinem Konto ist noch keine Ausbildung hinterlegt. Sag deinem Ausbilder Bescheid – ohne + Ausbildungsverhältnis gibt es keinen Wochenplan. +

); } - const ausbildung = ausbildungNachAzubi(benutzer.id); + const sicht = aktuelleWochensicht(ausbildung.id, azubiId); + + if (!sicht) { + return ( +
+

Guten Tag, {name.split(' ')[0]}

+

+ Für deine Ausbildung ist noch kein Wochenplan angelegt. Dein Ausbilder kann ihn unter „Wochenplan“ + erzeugen. +

+
+ ); + } + + return ; +} + +// --- Ausbilder: wer steht wo ----------------------------------------------- + +function AusbilderUeberblick({ ausbilderId, name }: { ausbilderId: string; name: string }) { + const meine = ausbildungenVonAusbilder(ausbilderId); return (
-

Guten Tag, {benutzer.name.split(' ')[0]}

+

Guten Tag, {name.split(' ')[0]}

{kalenderwocheText(heute())} · {alsDatumText(heute())}

-
- {ausbildung ? ( -

- Deine Ausbildung läuft seit {alsDatumText(ausbildung.beginn)} - {ausbildung.probezeitBis ? ` (Probezeit bis ${alsDatumText(ausbildung.probezeitBis)})` : ''}. -

- ) : ( -

- Zu deinem Konto ist noch keine Ausbildung hinterlegt. Sag deinem Ausbilder Bescheid. -

- )} + {meine.length === 0 ? ( +

+ Noch kein Azubikonto angelegt. Jetzt eines anlegen. +

+ ) : ( +
+

Wo deine Auszubildenden stehen

+ + {meine.map(({ ausbildung, azubi }) => { + const stand = kurzstand(ausbildung.id, azubi.id); + + return ( +
+
+
+

{azubi.name}

+

+ Ausbildung seit {alsDatumText(ausbildung.beginn)} + {azubi.aktiv === 1 ? '' : ' · Konto gesperrt'} +

+
+ + Wochenplan + +
+ + {stand ? ( + <> +
+
+ + Woche {stand.wochenNummer} · {stand.titel} + + + {alsDatumText(stand.geplantVon)} – {alsDatumText(stand.geplantBis)} + +
+ +
+ + {stand.fortschritt.offen.length > 0 ? ( +
    + {stand.fortschritt.offen.map((bereich) => ( +
  • + {bereich.titel} + + {bereich.zaehlwerk.erledigt} von {bereich.zaehlwerk.gesamt} + +
  • + ))} +
+ ) : null} + + + Die Woche im Einzelnen ansehen → + + + ) : ( +

+ Für {azubi.name} ist noch kein Wochenplan angelegt.{' '} + Jetzt erzeugen. +

+ )} +
+ ); + })} +
+ )} + +
+

+ Was auf dich wartet +

+

+ Offene Abnahmen, unkorrigierte Lernzielkontrollen und nicht abgezeichnete Berichtshefte erscheinen + hier, sobald diese Bausteine stehen. +

); diff --git a/src/app/(angemeldet)/verwaltung/aktionen.ts b/src/app/(angemeldet)/verwaltung/aktionen.ts index c72e0bc..f2c0d15 100644 --- a/src/app/(angemeldet)/verwaltung/aktionen.ts +++ b/src/app/(angemeldet)/verwaltung/aktionen.ts @@ -6,6 +6,8 @@ import { passwortMangel } from '@/lib/auth/passwort'; import { aktivSetzen, benutzerAnlegen, benutzerNachEmail, benutzerNachId, passwortSetzen } from '@/lib/repos/benutzer'; import { ausbildungAendern, ausbildungAnlegen, ausbildungNachAzubi, ausbildungNachId } from '@/lib/repos/ausbildung'; import { alsDatumText } from '@/lib/zeit'; +import { inhalte } from '@/lib/inhalte'; +import { planAuffrischen } from '@/lib/repos/wochenplanung'; export type Rueckmeldung = { fehler?: string; erfolg?: string }; @@ -31,7 +33,7 @@ export async function azubiAnlegen(_vorher: Rueckmeldung, formular: FormData): P if (benutzerNachEmail(email)) return { fehler: 'Diese E-Mail-Adresse ist schon vergeben.' }; const azubi = await benutzerAnlegen({ name, email, passwort, rolle: 'azubi' }); - ausbildungAnlegen({ + const neueAusbildung = ausbildungAnlegen({ azubiId: azubi.id, ausbilderId: ausbilder.id, beginn, @@ -39,8 +41,17 @@ export async function azubiAnlegen(_vorher: Rueckmeldung, formular: FormData): P ende: datumMuster.test(ende) ? ende : null, }); + // Der Wochenplan entsteht gleich mit – ohne ihn ist die Startseite leer. + const wochen = inhalte().wochen.map((w) => w.nummer); + const eingeplant = planAuffrischen(neueAusbildung.id, beginn, wochen); + revalidatePath('/verwaltung'); - return { erfolg: `Konto für ${name} angelegt und Ausbildung ab ${alsDatumText(beginn)} eingerichtet.` }; + revalidatePath('/'); + return { + erfolg: + `Konto für ${name} angelegt und Ausbildung ab ${alsDatumText(beginn)} eingerichtet. ` + + `${eingeplant} ${eingeplant === 1 ? 'Woche' : 'Wochen'} eingeplant.`, + }; } export async function ausbildungBearbeiten(_vorher: Rueckmeldung, formular: FormData): Promise { diff --git a/src/app/(angemeldet)/woche/[nummer]/page.tsx b/src/app/(angemeldet)/woche/[nummer]/page.tsx new file mode 100644 index 0000000..6ec110f --- /dev/null +++ b/src/app/(angemeldet)/woche/[nummer]/page.tsx @@ -0,0 +1,65 @@ +import type { Metadata } from 'next'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import { erfordereBenutzer, erfordereZugriffAufAzubi } from '@/lib/auth/rechte'; +import { ausbildungNachAzubi } from '@/lib/repos/ausbildung'; +import { benutzerNachId } from '@/lib/repos/benutzer'; +import { wochensicht } from '@/lib/planung/sicht'; +import { WochenAnsicht } from '../../WochenAnsicht'; + +export const dynamic = 'force-dynamic'; + +export async function generateMetadata({ params }: { params: Promise<{ nummer: string }> }): Promise { + const { nummer } = await params; + return { title: `Woche ${nummer}` }; +} + +/** + * Eine bestimmte Woche. Der Azubi sieht nur seine eigene; der Ausbilder gibt + * über `?azubi=` an, wessen Stand er sehen will – geprüft wird das serverseitig. + */ +export default async function WochenSeite({ + params, + searchParams, +}: { + params: Promise<{ nummer: string }>; + searchParams: Promise<{ azubi?: string }>; +}) { + const benutzer = await erfordereBenutzer(); + const { nummer } = await params; + const { azubi: azubiParameter } = await searchParams; + + const wochenNummer = Number(nummer); + if (!Number.isInteger(wochenNummer) || wochenNummer < 1) notFound(); + + const azubiId = benutzer.rolle === 'azubi' ? benutzer.id : (azubiParameter ?? ''); + if (!azubiId) notFound(); + + // Der Kern: Ob dieser Betrachter diesen Azubi sehen darf, entscheidet der + // Server – nicht die Oberfläche und nicht die Adresszeile. + erfordereZugriffAufAzubi(benutzer, azubiId); + + const ausbildung = ausbildungNachAzubi(azubiId); + if (!ausbildung) notFound(); + + const sicht = wochensicht(ausbildung.id, azubiId, wochenNummer); + if (!sicht) notFound(); + + const istAzubi = benutzer.rolle === 'azubi'; + const azubi = istAzubi ? null : benutzerNachId(azubiId); + const anhang = istAzubi ? '' : `?azubi=${azubiId}`; + + return ( +
+ {azubi ? ( + + ) : null} + + +
+ ); +} diff --git a/src/app/(angemeldet)/wochenplan/Formulare.tsx b/src/app/(angemeldet)/wochenplan/Formulare.tsx new file mode 100644 index 0000000..527f3bb --- /dev/null +++ b/src/app/(angemeldet)/wochenplan/Formulare.tsx @@ -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(planErzeugen, {}); + + return ( +
+ + + {text} + + ); +} + +export function VerlegenFormular({ + azubiId, + wochen, +}: { + azubiId: string; + wochen: { nummer: number; titel: string; geplantVon: string }[]; +}) { + const [zustand, aktion] = useActionState(wocheVerlegen, {}); + const erste = wochen[0]; + + return ( +
+ + + +
+
+ + +
+ +
+ + +
+ + Verlegen +
+ + + +

+ Ein beliebiges Datum wird auf den Montag seiner Woche gerundet. Abgeschlossene Wochen bleiben stehen. +

+ + ); +} + +export function StatusFormular({ + azubiId, + wochenNummer, + status, +}: { + azubiId: string; + wochenNummer: number; + status: 'geplant' | 'laufend' | 'abgeschlossen'; +}) { + return ( +
+ + + + + +
+ ); +} + +export function NotizFormular({ + azubiId, + wochenNummer, + notiz, +}: { + azubiId: string; + wochenNummer: number; + notiz: string; +}) { + const [zustand, aktion] = useActionState(wochenNotiz, {}); + + return ( +
+ + + + +