- Durchlauf aus einer Vorlage, mit Datum, Ticket und Kunde - Schritt fuer Schritt abhaken, Bemerkung je Schritt - Fortschritt gesamt und je Gruppe (Vorbereiten/Durchfuehren/Pruefen/Abschliessen) - Abschliessen erst, wenn alles abgehakt ist; Meldung nennt die offenen Schritte - Abgeschlossene Durchlaeufe sind gesperrt und bleiben als Nachweis - Titel und Schritttexte werden beim Start kopiert, nicht verwiesen - PDF-Export mit gezeichneten Ankreuzkaestchen und Unterschriftenzeilen - Gemeinsame PDF-Grundlage fuer alle kuenftigen Ausgaben - Ausbilder liest mit und darf das PDF abrufen, hakt aber nicht ab - Abgeschlossene Durchlaeufe zaehlen im Wochenfortschritt mit Behoben beim Durchstich: Die Fusszeile wurde unterhalb des Satzspiegels gesetzt, wodurch PDFKit je Fusszeile eine leere Seite anhaengte - aus zwei Seiten wurden sechs. Ein Test zaehlt jetzt die Seitenobjekte im PDF. 29 neue Tests, insgesamt 186. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
149 lines
5.1 KiB
TypeScript
149 lines
5.1 KiB
TypeScript
import { inhalte, type Handout, type Aufgabe } from '../inhalte';
|
||
import type { Checkliste, Kontrolle, Woche } from '../inhalte/schema';
|
||
import { gelesenSet } from '../repos/fortschritt';
|
||
import { planVon } from '../repos/wochenplanung';
|
||
import { uebersicht as kartenUebersicht } from '../repos/karten';
|
||
import { anzahlAbgenommen } from '../repos/aufgaben';
|
||
import { anzahlAbgeschlossen } from '../repos/checklisten';
|
||
import { heute } from '../zeit';
|
||
import { aktuelleWoche } from './wochen';
|
||
import { wochenFortschritt, type Fortschritt } from './fortschritt';
|
||
import type { Wochenplanung } from '../db/schema';
|
||
|
||
/**
|
||
* Setzt aus Inhalt (Datei), Planung (Datenbank) und Fortschritt (Datenbank)
|
||
* die Wochenansicht zusammen. Alles, was die Startseite braucht, kommt hier her.
|
||
*/
|
||
|
||
export type Tagessicht = {
|
||
tag: string;
|
||
theorie: Handout[];
|
||
praxis: string;
|
||
/** Der Tag, der im Kalender zu diesem Wochentag gehört. */
|
||
datum: string;
|
||
istHeute: boolean;
|
||
};
|
||
|
||
export type Wochensicht = {
|
||
woche: Woche;
|
||
planung: Wochenplanung;
|
||
fortschritt: Fortschritt;
|
||
tage: Tagessicht[];
|
||
handouts: { handout: Handout; gelesen: boolean }[];
|
||
aufgaben: Aufgabe[];
|
||
checklisten: Checkliste[];
|
||
kontrolle: Kontrolle | null;
|
||
vorherige: number | null;
|
||
naechste: number | null;
|
||
};
|
||
|
||
export function wochensicht(ausbildungId: string, azubiId: string, wochenNummer: number): Wochensicht | null {
|
||
const alle = inhalte();
|
||
const woche = alle.wocheNach.get(wochenNummer);
|
||
if (!woche) return null;
|
||
|
||
const plan = planVon(ausbildungId);
|
||
const planung = plan.find((z) => z.wochenNummer === wochenNummer);
|
||
if (!planung) return null;
|
||
|
||
const gelesen = gelesenSet(azubiId);
|
||
const heuteDatum = heute();
|
||
|
||
const handouts = woche.handouts
|
||
.map((id) => alle.handoutNach.get(id))
|
||
.filter((h) => h !== undefined)
|
||
.map((handout) => ({ handout, gelesen: gelesen.has(handout.id) }));
|
||
|
||
const tage: Tagessicht[] = woche.tage.map((tag, stelle) => {
|
||
const datum = tagVersetzt(planung.geplantVon, stelle);
|
||
return {
|
||
tag: tag.tag,
|
||
praxis: tag.praxis,
|
||
datum,
|
||
istHeute: datum === heuteDatum,
|
||
theorie: tag.theorie.map((id) => alle.handoutNach.get(id)).filter((h) => h !== undefined),
|
||
};
|
||
});
|
||
|
||
const aufgaben = woche.aufgaben.map((id) => alle.aufgabeNach.get(id)).filter((a) => a !== undefined);
|
||
const checklisten = woche.checklisten.map((id) => alle.checklisteNach.get(id)).filter((c) => c !== undefined);
|
||
const kontrolle = woche.lernzielkontrolle ? (alle.kontrolleNach.get(woche.lernzielkontrolle) ?? null) : null;
|
||
|
||
// Die Merkkarten dieser Woche sind die Karten ihrer Handouts.
|
||
const karteIds = handouts.flatMap(({ handout }) =>
|
||
alle.karten.filter((k) => k.handoutId === handout.id).map((k) => k.id),
|
||
);
|
||
const kartenStand = kartenUebersicht(azubiId, karteIds, heuteDatum);
|
||
|
||
const fortschritt = wochenFortschritt({
|
||
handouts: { erledigt: handouts.filter((h) => h.gelesen).length, gesamt: handouts.length },
|
||
karten: { erledigt: kartenStand.gelernt, gesamt: kartenStand.gesamt },
|
||
aufgaben: { erledigt: anzahlAbgenommen(azubiId, aufgaben.map((a) => a.id)), gesamt: aufgaben.length },
|
||
// Diese Bereiche werden mit ihren Bausteinen scharf geschaltet. Bis dahin
|
||
// zeigen sie ehrlich an, was die Woche vorsieht und noch offen ist.
|
||
checklisten: {
|
||
erledigt: anzahlAbgeschlossen(azubiId, checklisten.map((c) => c.id)),
|
||
gesamt: checklisten.length,
|
||
},
|
||
kontrolle: { erledigt: 0, gesamt: kontrolle ? 1 : 0 },
|
||
});
|
||
|
||
const nummern = plan.map((z) => z.wochenNummer);
|
||
const stelle = plan.findIndex((z) => z.wochenNummer === wochenNummer);
|
||
|
||
return {
|
||
woche,
|
||
planung,
|
||
fortschritt,
|
||
tage,
|
||
handouts,
|
||
aufgaben,
|
||
checklisten,
|
||
kontrolle,
|
||
vorherige: stelle > 0 ? (nummern[stelle - 1] ?? null) : null,
|
||
naechste: stelle < nummern.length - 1 ? (nummern[stelle + 1] ?? null) : null,
|
||
};
|
||
}
|
||
|
||
/** Die Woche, die gerade dran ist. */
|
||
export function aktuelleWochensicht(ausbildungId: string, azubiId: string): Wochensicht | null {
|
||
const plan = planVon(ausbildungId);
|
||
const dran = aktuelleWoche(plan, heute());
|
||
if (!dran) return null;
|
||
return wochensicht(ausbildungId, azubiId, dran.wochenNummer);
|
||
}
|
||
|
||
/** Kurzstand für die Ausbilderübersicht – ohne den ganzen Wochenaufbau. */
|
||
export type Kurzstand = {
|
||
wochenNummer: number;
|
||
titel: string;
|
||
geplantVon: string;
|
||
geplantBis: string;
|
||
status: Wochenplanung['status'];
|
||
fortschritt: Fortschritt;
|
||
handoutsGelesen: number;
|
||
handoutsGesamt: number;
|
||
};
|
||
|
||
export function kurzstand(ausbildungId: string, azubiId: string): Kurzstand | null {
|
||
const sicht = aktuelleWochensicht(ausbildungId, azubiId);
|
||
if (!sicht) return null;
|
||
|
||
return {
|
||
wochenNummer: sicht.woche.nummer,
|
||
titel: sicht.woche.titel,
|
||
geplantVon: sicht.planung.geplantVon,
|
||
geplantBis: sicht.planung.geplantBis,
|
||
status: sicht.planung.status,
|
||
fortschritt: sicht.fortschritt,
|
||
handoutsGelesen: sicht.handouts.filter((h) => h.gelesen).length,
|
||
handoutsGesamt: sicht.handouts.length,
|
||
};
|
||
}
|
||
|
||
function tagVersetzt(montag: string, tage: number): string {
|
||
const d = new Date(`${montag}T12:00:00Z`);
|
||
d.setUTCDate(d.getUTCDate() + tage);
|
||
return d.toISOString().slice(0, 10);
|
||
}
|