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:
co-authored by
Claude Opus 5
parent
1af5613e9b
commit
103a603eb4
@@ -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<string, { text: string; ton: string; zeichen: string }> = {
|
||||
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 (
|
||||
<div className="stapel">
|
||||
<header className="stapel-eng">
|
||||
<div className="reihe" style={{ gap: '0.6rem' }}>
|
||||
<span className="wochen-nummer">Woche {woche.nummer}</span>
|
||||
<span className="marke-status" data-ton={status.ton}>
|
||||
{status.zeichen} {status.text}
|
||||
</span>
|
||||
<span className="leise">
|
||||
{alsDatumText(planung.geplantVon)} – {alsDatumText(planung.geplantBis)} · {kalenderwocheText(planung.geplantVon)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h1 style={{ marginBottom: '0.2rem' }}>{woche.titel}</h1>
|
||||
<p className="leitfrage">{woche.leitfrage}</p>
|
||||
<p className="leise" style={{ marginBottom: 0 }}>{woche.ziel}</p>
|
||||
</header>
|
||||
|
||||
{planung.tatsaechlichVon && planung.tatsaechlichVon !== planung.geplantVon ? (
|
||||
<p className="hinweis hinweis-warnung" style={{ marginBottom: 0 }}>
|
||||
Tatsächlich begonnen am {alsDatumText(planung.tatsaechlichVon)}, geplant war der{' '}
|
||||
{alsDatumText(planung.geplantVon)}.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{planung.notiz ? (
|
||||
<p className="hinweis hinweis-warnung" style={{ marginBottom: 0 }}>
|
||||
<strong>Notiz des Ausbilders:</strong> {planung.notiz}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<section className="karte stapel">
|
||||
<Fortschrittsbalken fortschritt={fortschritt} />
|
||||
|
||||
{fortschritt.offen.length > 0 ? (
|
||||
<div>
|
||||
<h2 style={{ fontSize: '1rem', marginBottom: '0.4rem' }}>
|
||||
{istAzubi ? 'Das ist diese Woche noch offen' : 'Offen bei deinem Azubi'}
|
||||
</h2>
|
||||
<ul className="offen-liste">
|
||||
{fortschritt.offen.map((bereich) => (
|
||||
<li key={bereich.schluessel}>
|
||||
<span>{bereich.titel}</span>
|
||||
<span className="leise">
|
||||
{bereich.zaehlwerk.erledigt} von {bereich.zaehlwerk.gesamt}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : fortschritt.gesamt > 0 ? (
|
||||
<p className="hinweis hinweis-gut" style={{ marginBottom: 0 }}>
|
||||
✓ Diese Woche ist vollständig abgearbeitet.
|
||||
</p>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="tage" className="stapel">
|
||||
<h2 id="tage">Die Woche</h2>
|
||||
<ol className="wochentage">
|
||||
{tage.map((tag) => (
|
||||
<li key={tag.tag} className={`wochentag${tag.istHeute ? ' wochentag-heute' : ''}`}>
|
||||
<div className="wochentag-kopf">
|
||||
<strong>{tag.tag}</strong>
|
||||
<span className="leise">{alsDatumText(tag.datum)}</span>
|
||||
{tag.istHeute ? <span className="marke-status" data-ton="warnung">● heute</span> : null}
|
||||
</div>
|
||||
|
||||
<div className="wochentag-inhalt">
|
||||
<div>
|
||||
<span className="wochentag-marke">Theorie</span>
|
||||
{tag.theorie.length === 0 ? (
|
||||
<span className="leise">–</span>
|
||||
) : (
|
||||
<ul className="theorie-liste">
|
||||
{tag.theorie.map((handout) => {
|
||||
const gelesen = sicht.handouts.find((h) => h.handout.id === handout.id)?.gelesen ?? false;
|
||||
return (
|
||||
<li key={handout.id}>
|
||||
<Link href={`/handouts/${handout.id}`}>
|
||||
<span className="handout-kuerzel">{handout.id}</span> {handout.titel}
|
||||
</Link>
|
||||
{gelesen ? (
|
||||
<span className="marke-status" data-ton="gut">
|
||||
✓ gelesen
|
||||
</span>
|
||||
) : null}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="wochentag-marke">Praxis</span>
|
||||
<span>{tag.praxis || '–'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<div className="raster">
|
||||
<section className="karte" aria-labelledby="woche-handouts">
|
||||
<h2 id="woche-handouts" style={{ fontSize: '1rem' }}>
|
||||
Handouts dieser Woche
|
||||
</h2>
|
||||
{sicht.handouts.length === 0 ? (
|
||||
<p className="leise" style={{ marginBottom: 0 }}>Keine.</p>
|
||||
) : (
|
||||
<ul className="stapel-eng" style={{ listStyle: 'none', margin: 0, padding: 0 }}>
|
||||
{sicht.handouts.map(({ handout, gelesen }) => (
|
||||
<li key={handout.id} className="reihe" style={{ gap: '0.5rem', flexWrap: 'nowrap' }}>
|
||||
<span className="handout-kuerzel">{handout.id}</span>
|
||||
<Link href={`/handouts/${handout.id}`} style={{ flex: 1 }}>
|
||||
{handout.titel}
|
||||
</Link>
|
||||
<span aria-label={gelesen ? 'gelesen' : 'noch nicht gelesen'} title={gelesen ? 'gelesen' : 'offen'}>
|
||||
{gelesen ? '✓' : '○'}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="karte" aria-labelledby="woche-aufgaben">
|
||||
<h2 id="woche-aufgaben" style={{ fontSize: '1rem' }}>
|
||||
Praxisaufgaben
|
||||
</h2>
|
||||
{sicht.aufgaben.length === 0 ? (
|
||||
<p className="leise" style={{ marginBottom: 0 }}>Keine.</p>
|
||||
) : (
|
||||
<ul className="stapel-eng" style={{ margin: 0, paddingLeft: '1.1rem' }}>
|
||||
{sicht.aufgaben.map((aufgabe) => (
|
||||
<li key={aufgabe.id}>
|
||||
<strong>{aufgabe.id}</strong> {aufgabe.titel}
|
||||
<br />
|
||||
<span className="leise">{aufgabe.zeitrahmen}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="karte" aria-labelledby="woche-checklisten">
|
||||
<h2 id="woche-checklisten" style={{ fontSize: '1rem' }}>
|
||||
Checklisten
|
||||
</h2>
|
||||
{sicht.checklisten.length === 0 ? (
|
||||
<p className="leise" style={{ marginBottom: 0 }}>Keine.</p>
|
||||
) : (
|
||||
<ul className="stapel-eng" style={{ margin: 0, paddingLeft: '1.1rem' }}>
|
||||
{sicht.checklisten.map((liste) => (
|
||||
<li key={liste.id}>{liste.titel}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="karte" aria-labelledby="woche-kontrolle">
|
||||
<h2 id="woche-kontrolle" style={{ fontSize: '1rem' }}>
|
||||
Lernzielkontrolle
|
||||
</h2>
|
||||
{sicht.kontrolle ? (
|
||||
<p style={{ marginBottom: 0 }}>
|
||||
{sicht.kontrolle.titel}
|
||||
<br />
|
||||
<span className="leise">
|
||||
{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` : ''}
|
||||
</span>
|
||||
</p>
|
||||
) : (
|
||||
<p className="leise" style={{ marginBottom: 0 }}>Diese Woche keine.</p>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<nav className="reihe nicht-drucken" style={{ justifyContent: 'space-between' }} aria-label="Andere Wochen">
|
||||
{sicht.vorherige ? (
|
||||
<Link href={zurWoche(sicht.vorherige)} className="knopf knopf-still">
|
||||
← Woche {sicht.vorherige}
|
||||
</Link>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{sicht.naechste ? (
|
||||
<Link href={zurWoche(sicht.naechste)} className="knopf knopf-still">
|
||||
Woche {sicht.naechste} →
|
||||
</Link>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user