Merkkarten, Praxisaufgaben, Checklisten, Berichtsheft, Lernzielkontrollen und Feedbackgespraech lassen sich unter Verwaltung -> Module fuer die ganze Lern-Bar ab- und einschalten. Wochenplan, Handouts, Glossar, Auswertung und Verwaltung bleiben immer an. Gesperrt wird an drei Stellen, nicht im Layout (Next rendert Layouts beim Seitenwechsel darunter nicht neu): Seiten mit erfordereModul, Routen fuer PDF, Anhaenge und Lernstand mit modulAn, alle 37 Serveraktionen der Module mit pruefeModul. Die Anmeldepruefung kommt jeweils zuerst, damit ein Unangemeldeter nicht am 404 abliest, was abgeschaltet ist. Ein abgeschaltetes Modul traegt nirgends bei: Wochenplan, Fortschritt, Was-wartet, Mein Stand, Ergebnisverlauf, Rahmenplanabdeckung, Vorschlaege fuers Berichtsheft, Karten im Handout, Kurzbefehle im Manifest. Die Lernstand-Schnittstelle antwortet mit 410, das Geraet verwirft daraufhin seine Offline-Warteschlange. Abschalten loescht nichts. Gespeichert in modul_einstellung (Migration 0010); fehlt die Zeile, ist das Modul an, eine bestehende Datenbank verhaelt sich also wie vorher. 12 Tests fuer die Regeln, Abschnitt A8 im Abnahmelauf, Anleitung Kapitel 9 mit Bildschirmfoto, README, E43. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
164 lines
5.6 KiB
TypeScript
164 lines
5.6 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 { anzahlAbgegeben } from '../repos/kontrollen';
|
||
import { heute } from '../zeit';
|
||
import { modulStand } from '../repos/module';
|
||
import type { ModulStand } from '../module/module';
|
||
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;
|
||
/** Welche Module an sind – die Ansicht blendet die übrigen aus. */
|
||
module: ModulStand;
|
||
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),
|
||
};
|
||
});
|
||
|
||
// Ein abgeschaltetes Modul trägt nichts zur Woche bei – weder in der Ansicht
|
||
// noch im Fortschritt. So erreicht eine Woche auch ohne Merkkarten 100 %.
|
||
const eingeschaltet = modulStand();
|
||
const aufgaben = eingeschaltet.aufgaben
|
||
? woche.aufgaben.map((id) => alle.aufgabeNach.get(id)).filter((a) => a !== undefined)
|
||
: [];
|
||
const checklisten = eingeschaltet.checklisten
|
||
? woche.checklisten.map((id) => alle.checklisteNach.get(id)).filter((c) => c !== undefined)
|
||
: [];
|
||
const kontrolle =
|
||
eingeschaltet.kontrollen && woche.lernzielkontrolle ? (alle.kontrolleNach.get(woche.lernzielkontrolle) ?? null) : null;
|
||
|
||
// Die Merkkarten dieser Woche sind die Karten ihrer Handouts.
|
||
const karteIds = eingeschaltet.karten
|
||
? 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 },
|
||
checklisten: {
|
||
erledigt: anzahlAbgeschlossen(azubiId, checklisten.map((c) => c.id)),
|
||
gesamt: checklisten.length,
|
||
},
|
||
kontrolle: {
|
||
erledigt: kontrolle ? anzahlAbgegeben(azubiId, [kontrolle.id]) : 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,
|
||
module: eingeschaltet,
|
||
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);
|
||
}
|