diff --git a/CHANGELOG.md b/CHANGELOG.md index c043235..5cedf55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,43 @@ Vor `npm version` wandert der Abschnitt „Unveröffentlicht" unter eine neue ## [Unveröffentlicht] +## [0.6.0] – 2026-09-10 + +Checklisten mit Durchläufen – und der erste PDF-Export. + +### Hinzugefügt + +- Aus einer Checklistenvorlage wird ein **Durchlauf**, der zu einem Datum und + wahlweise zu einem Ticket und einem Kunden oder Bereich gehört +- Schritt für Schritt abhaken, je Schritt eine Bemerkung +- Fortschritt gesamt **und je Gruppe** (Vorbereiten, Durchführen, Prüfen, + Abschließen), jeweils als Balken mit Zahlen +- Ein Durchlauf lässt sich erst abschließen, wenn jeder Schritt abgehakt ist; + die Meldung nennt, wie viele Schritte noch offen sind +- Abgeschlossene Durchläufe sind gesperrt und lassen sich nicht löschen – sie + sind der Nachweis. Wieder öffnen geht, verwerfen nur, solange nichts abgehakt ist +- **PDF-Export** eines Durchlaufs: Kopfangaben, alle Gruppen mit gezeichneten + Ankreuzkästchen, Bemerkungen, Unterschriftenzeilen für Bearbeiter und + Ausbilder, Fußzeile mit Herkunft und Seitenzahl +- Gemeinsame PDF-Grundlage (`src/lib/pdf/grundlage.ts`) für alle künftigen + Ausgaben – das Berichtsheft setzt darauf auf +- Der Ausbilder sieht die Durchläufe seiner Azubis und kann das PDF abrufen, + hakt aber nichts für sie ab: Im Nachweis soll keine Arbeit stehen, die der + Azubi nicht gemacht hat +- Titel und Schritttexte werden beim Start aus der Vorlage **kopiert**. Ein + abgeschlossener Durchlauf bleibt damit lesbar, auch wenn die Vorlage im + Repository später geändert oder gelöscht wird +- Abgeschlossene Durchläufe zählen im Wochenfortschritt mit +- 29 Tests für Gruppenfortschritt, Abschlussregel, PDF-Erzeugung und + Zeichenvorrat + +### Angemerkt + +- Die PDF-Ausgabe nutzt die Standardschriften jedes Betrachters (Helvetica). + Das spart eine eingebettete Schriftdatei, beschränkt den Zeichenvorrat aber + auf WinAnsi. Umlaute, ß, €, § und ° sind enthalten; Zeichen wie ✓, ○ oder ▲ + nicht. `fuerPdf()` ersetzt sie, Ankreuzkästchen und Haken werden **gezeichnet** + ## [0.5.0] – 2026-09-10 Praxisaufgaben: bearbeiten, belegen, abnehmen. @@ -230,6 +267,7 @@ anmelden und verwalten. - `npm run konto:anlegen` für das erste Ausbilderkonto [Unveröffentlicht]: #unveröffentlicht +[0.6.0]: #060--2026-09-10 [0.5.0]: #050--2026-09-10 [0.4.0]: #040--2026-09-10 [0.3.0]: #030--2026-09-10 diff --git a/DEVLOG.md b/DEVLOG.md index ec05a01..c78a95a 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -7,6 +7,41 @@ Format: `HH:MM–HH:MM (Dauer) · was gemacht wurde` --- +## 2026-09-10 · Baustein 6 – Checklisten und PDF + +| Von | Bis | Dauer | Tätigkeit | +|---|---|---|---| +| 20:35 | 20:42 | 0:07 | Datenmodell, Migration, Schema | +| 20:42 | 20:48 | 0:06 | Gruppenfortschritt als reine Rechnung, 16 Tests | +| 20:48 | 20:52 | 0:04 | PDF-Grundlage und Checklisten-PDF | +| 20:52 | 21:00 | 0:08 | Repository, Aktionen, Seiten, PDF-Route | +| 21:00 | 21:10 | 0:10 | Durchstich; PDF gerendert und den Fußzeilenfehler gefunden | +| 21:10 | 21:20 | 0:10 | Fehler behoben, Regressionstest, Dokumente | + +**Summe Baustein 6: rund 45 min** + +### Was aufgefallen ist + +- **Das PDF hatte sechs Seiten, vier davon leer.** Aufgefallen ist das erst + beim Ansehen – die Prüfung „Status 200, Content-Type application/pdf" war + grün. Ursache: Die Fußzeile wird unterhalb des Satzspiegels gesetzt, und + PDFKit hält das für überlaufenden Text und legt eine neue Seite an. Bei zwei + Seiten mal zwei Textaufrufen macht das vier leere Seiten. Behoben, indem der + untere Rand für die Dauer der Fußzeile auf 0 gesetzt wird. Ein Test zählt + jetzt die Seitenobjekte im PDF und hätte den Fehler gefunden. +- Headless-Chromium rendert keine PDFs. Für den Blick aufs Ergebnis musste + der Browser mit Fenster starten (`headless: false`) – auf diesem Rechner + geht das, auf einem Server nicht. Für die spätere Prüfstrecke heißt das: + Seitenzahl und Inhalt maschinell prüfen, das Aussehen von Hand. +- Titel und Schritttexte werden beim Start des Durchlaufs kopiert statt + verwiesen. Ein Nachweis, der sich rückwirkend ändert, wenn jemand die + Vorlage anfasst, wäre keiner. +- Der Ausbilder darf das PDF abrufen und mitlesen, aber nicht abhaken. Erst + beim Schreiben der Aktion ist mir aufgefallen, dass „darf sehen" und „darf + bearbeiten" hier auseinanderfallen – daher zwei getrennte Prüffunktionen. + +--- + ## 2026-09-10 · Baustein 5 – Praxisaufgaben | Von | Bis | Dauer | Tätigkeit | diff --git a/ENTSCHEIDUNGEN.md b/ENTSCHEIDUNGEN.md index f0add15..8632e14 100644 --- a/ENTSCHEIDUNGEN.md +++ b/ENTSCHEIDUNGEN.md @@ -208,3 +208,52 @@ Der Abstand ist Absicht: Eine zu große Datei soll die **eigene**, verständlich Meldung bekommen und nicht den Rumpfgrößenfehler des Gerüsts, der nur im Serverprotokoll auftaucht. Zusätzlich prüft der Browser vorab, damit am Mobilfunk nicht erst 30 MB übertragen und dann abgelehnt werden. + +## E23 · PDF mit Standardschriften statt eingebetteter Schrift + +Die PDF-Ausgabe benutzt Helvetica, also eine der Schriften, die jeder +PDF-Betrachter mitbringt. Das spart eine eingebettete Schriftdatei im +Repository und hält die Dateien klein (ein Checklistenblatt wiegt rund 7 KB). + +Der Preis ist der Zeichenvorrat **WinAnsi**. Enthalten sind alle deutschen +Umlaute, ß, €, § und °. Nicht enthalten sind Zeichen wie ✓, ○, ▲ oder Emoji. +Deshalb: + +- `fuerPdf()` ersetzt typografische Feinheiten (Anführungszeichen, + Auslassungspunkte, geschützte Leerzeichen) und wirft nicht Darstellbares weg. +- Ankreuzkästchen und Haken werden **gezeichnet**, nicht gesetzt. Das sieht in + jedem Betrachter gleich aus und ist unabhängig von der Schrift. + +Sollte später ein Zeichen außerhalb von WinAnsi wirklich gebraucht werden, ist +der Weg das Einbetten einer freien Schrift – nicht das Aufweichen dieser Regel. + +## E24 · Fußzeilen brauchen einen aufgehobenen unteren Rand + +PDFKit legt eine neue Seite an, sobald Text unterhalb des Satzspiegels +geschrieben wird. Eine Fußzeile steht dort per Definition. Ohne Gegenmaßnahme +entsteht je Seite und je Textaufruf eine leere Folgeseite. + +`fusszeilen()` setzt deshalb `page.margins.bottom` kurzzeitig auf 0 und stellt +den Wert danach wieder her. Ein Test zählt die Seitenobjekte im erzeugten PDF – +damit fällt ein Rückfall sofort auf. + +## E25 · Ein Durchlauf kopiert die Texte seiner Vorlage + +`checkliste_lauf.titel` und `checkliste_schritt.text` werden beim Start aus der +Vorlage übernommen, statt zur Anzeigezeit nachgeschlagen zu werden. + +Grund: Ein abgeschlossener Durchlauf ist ein Nachweis. Er muss auch in zwei +Jahren noch dasselbe aussagen – auch wenn die Vorlage unter `content/` +inzwischen umformuliert, erweitert oder gelöscht wurde. Die Seite zeigt einen +Hinweis, wenn es die Vorlage nicht mehr gibt, bleibt aber vollständig lesbar. + +## E26 · Mitlesen und Bearbeiten sind getrennte Rechte + +Beim Checklisten-Durchlauf fallen die beiden Rechte auseinander: + +- **Lesen** darf der Eigentümer und sein Ausbilder (`laufZumLesen`). +- **Bearbeiten** darf nur der Eigentümer (`laufZumBearbeiten`). + +Der Ausbilder hakt also nichts für seinen Azubi ab. Im Nachweis soll keine +Arbeit stehen, die der Azubi nicht selbst gemacht hat. Für die Abnahme gibt es +die Praxisaufgaben – dort ist es genau andersherum. diff --git a/README.md b/README.md index 7e103e0..4e07093 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ und – der wichtigste Teil – den gesetzlich vorgeschriebenen Ausbildungsnachw Gebaut für **einen Ausbilder und ein bis drei Auszubildende**. Kein Lernplattform-Baukasten, keine Mandanten, keine Cloud. -> **Stand:** Bausteine 1 bis 5 von 10 sind fertig – Fundament, Inhalte, -> Wochenplan, Merkkarten, Praxisaufgaben. Der [Fahrplan](ROADMAP.md) zeigt, -> was folgt, das [Änderungsprotokoll](CHANGELOG.md), was schon drin ist. +> **Stand:** Bausteine 1 bis 6 von 10 sind fertig – Fundament, Inhalte, +> Wochenplan, Merkkarten, Praxisaufgaben, Checklisten. Der +> [Fahrplan](ROADMAP.md) zeigt, was folgt, das +> [Änderungsprotokoll](CHANGELOG.md), was schon drin ist. --- diff --git a/ROADMAP.md b/ROADMAP.md index d39af91..31fe217 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -64,7 +64,14 @@ Legende: `[ ]` offen · `[~]` in Arbeit · `[x]` fertig - [x] Anhänge nur über Kennung, Berechtigung bei jedem Abruf geprüft - [x] „Was auf dich wartet" zeigt offene Abnahmen - [x] Aufgaben zählen im Wochenfortschritt mit -- [ ] **B6 · Checklisten mit Durchläufen und PDF** +- [x] **B6 · Checklisten mit Durchläufen und PDF** + - [x] Durchlauf aus einer Vorlage, mit Datum, Ticket und Kunde + - [x] Schritt für Schritt abhaken, Bemerkung je Schritt + - [x] Fortschritt je Gruppe (Vorbereiten / Durchführen / Prüfen / Abschließen) + - [x] Abschließen erst, wenn alles abgehakt ist + - [x] Abgeschlossene Durchläufe bleiben als Nachweis und sind gesperrt + - [x] PDF-Export mit Unterschriftenzeilen + - [x] Ausbilder liest mit, hakt aber nicht für den Azubi ab - [ ] **B7 · Berichtsheft: Tagesnotizen, Vorschläge, Prüfung, Abzeichnen, PDF** ## 0.5 – Bewertung diff --git a/docs/beispiel-checkliste.pdf b/docs/beispiel-checkliste.pdf new file mode 100644 index 0000000..f8daf07 Binary files /dev/null and b/docs/beispiel-checkliste.pdf differ diff --git a/docs/bilder/02-ausbilder-ueberblick.png b/docs/bilder/02-ausbilder-ueberblick.png index 5342161..a16833d 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 5dff273..486bc12 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 index 9dcb1b0..be5dba8 100644 Binary files a/docs/bilder/04-wochenplan.png and b/docs/bilder/04-wochenplan.png differ diff --git a/docs/bilder/05-aufgabe-abnahme.png b/docs/bilder/05-aufgabe-abnahme.png index 3ce39ca..c55bfd4 100644 Binary files a/docs/bilder/05-aufgabe-abnahme.png and b/docs/bilder/05-aufgabe-abnahme.png differ diff --git a/docs/bilder/10-azubi-woche.png b/docs/bilder/10-azubi-woche.png index edde01e..ac44d6b 100644 Binary files a/docs/bilder/10-azubi-woche.png 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 9f1d02f..f343dbd 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 1e6080f..e0e3be6 100644 Binary files a/docs/bilder/12-handouts-suche.png and b/docs/bilder/12-handouts-suche.png differ diff --git a/docs/bilder/13-handout-n3.png b/docs/bilder/13-handout-n3.png index 23b2b81..d167a5b 100644 Binary files a/docs/bilder/13-handout-n3.png and b/docs/bilder/13-handout-n3.png differ diff --git a/docs/bilder/14-merkkarten.png b/docs/bilder/14-merkkarten.png index e778d2a..f901b8f 100644 Binary files a/docs/bilder/14-merkkarten.png and b/docs/bilder/14-merkkarten.png differ diff --git a/docs/bilder/15-glossar.png b/docs/bilder/15-glossar.png index f845144..4c1d485 100644 Binary files a/docs/bilder/15-glossar.png and b/docs/bilder/15-glossar.png differ diff --git a/docs/bilder/16-aufgaben-liste.png b/docs/bilder/16-aufgaben-liste.png index c8db751..1a93c31 100644 Binary files a/docs/bilder/16-aufgaben-liste.png and b/docs/bilder/16-aufgaben-liste.png differ diff --git a/docs/bilder/17-aufgabe-azubi.png b/docs/bilder/17-aufgabe-azubi.png index a6aa65e..8261513 100644 Binary files a/docs/bilder/17-aufgabe-azubi.png and b/docs/bilder/17-aufgabe-azubi.png differ diff --git a/docs/bilder/18-checklisten.png b/docs/bilder/18-checklisten.png new file mode 100644 index 0000000..b9f07ae Binary files /dev/null and b/docs/bilder/18-checklisten.png differ diff --git a/docs/bilder/19-checklisten-lauf.png b/docs/bilder/19-checklisten-lauf.png new file mode 100644 index 0000000..70a3a4a Binary files /dev/null and b/docs/bilder/19-checklisten-lauf.png differ diff --git a/docs/bilder/20-dunkel.png b/docs/bilder/20-dunkel.png index b65ac83..a0eabcd 100644 Binary files a/docs/bilder/20-dunkel.png and b/docs/bilder/20-dunkel.png differ diff --git a/docs/bilder/30-handy-handout.png b/docs/bilder/30-handy-handout.png index 79c71e8..1b1681c 100644 Binary files a/docs/bilder/30-handy-handout.png and b/docs/bilder/30-handy-handout.png differ diff --git a/docs/bilder/31-handy-lernmodus-frage.png b/docs/bilder/31-handy-lernmodus-frage.png index ea8f77e..2091cf5 100644 Binary files a/docs/bilder/31-handy-lernmodus-frage.png and b/docs/bilder/31-handy-lernmodus-frage.png differ diff --git a/docs/bilder/32-handy-lernmodus-antwort.png b/docs/bilder/32-handy-lernmodus-antwort.png index a5642d9..74a15fb 100644 Binary files a/docs/bilder/32-handy-lernmodus-antwort.png and b/docs/bilder/32-handy-lernmodus-antwort.png differ diff --git a/scripts/screenshots.ts b/scripts/screenshots.ts index 0ab6a11..014a8cb 100644 --- a/scripts/screenshots.ts +++ b/scripts/screenshots.ts @@ -118,6 +118,17 @@ async function main() { await seite.waitForSelector('h1'); await bild(seite, '17-aufgabe-azubi'); + await seite.goto(`${BASIS}/checklisten`); + await seite.waitForSelector('h1'); + await bild(seite, '18-checklisten'); + + const lauf = await seite.$('.laufzeile-haupt'); + if (lauf) { + await lauf.click(); + await seite.waitForSelector('.schrittliste'); + await bild(seite, '19-checklisten-lauf'); + } + await seite.goto(`${BASIS}/glossar`); await seite.waitForSelector('h1'); await bild(seite, '15-glossar'); diff --git a/src/app/(angemeldet)/checklisten/Formulare.tsx b/src/app/(angemeldet)/checklisten/Formulare.tsx new file mode 100644 index 0000000..4d8b3ef --- /dev/null +++ b/src/app/(angemeldet)/checklisten/Formulare.tsx @@ -0,0 +1,174 @@ +'use client'; + +import { useActionState } from 'react'; +import { Absenden, Meldung } from '@/komponenten/Formularmeldung'; +import { + durchlaufAbschliessen, + durchlaufStarten, + kopfdatenSpeichern, + schrittBemerkung, + type Rueckmeldung, +} from './aktionen'; + +export function StartFormular({ + checklisteId, + mitTicket, + heuteDatum, +}: { + checklisteId: string; + mitTicket: boolean; + heuteDatum: string; +}) { + const [zustand, aktion] = useActionState(durchlaufStarten, {}); + + return ( +
+ + + +
+
+ + +
+ + {mitTicket ? ( +
+ + +
+ ) : null} +
+ +
+ + +
+ +
+ Durchlauf starten +
+ + ); +} + +export function KopfdatenFormular({ + laufId, + datum, + ticket, + kunde, + bemerkung, + gesperrt, +}: { + laufId: string; + datum: string; + ticket: string; + kunde: string; + bemerkung: string; + gesperrt: boolean; +}) { + const [zustand, aktion] = useActionState(kopfdatenSpeichern, {}); + + if (gesperrt) return null; + + return ( +
+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +