Baustein 4: Merkkarten mit Wiederholung nach Zeitabstand
- Abgespecktes SM-2 mit drei Bewertungsstufen statt sechs - Abstaende 1, 3, dann Abstand mal Leichtigkeit; gedeckelt bei 180 Tagen - "nochmal" bringt die Karte in derselben Sitzung wieder - Taeglicher Stapel, gedeckelt auf 20 Karten, ueberfaellige zuerst - Fuenf Faecher zur Anzeige, abgeleitet aus dem Abstand - Filter nach Themenblock fuer gezieltes Ueben - Lernmodus fuers Handy: grosse Flaechen, Antippen, Wischen, kein Hover; am Rechner zusaetzlich Leertaste und Tasten 1/2/3 - Tabellen karte und karten_verlauf, jede Bewertung wird mitgeschrieben - Merkkarten zaehlen im Wochenfortschritt mit, gelernt ab Fach 4 - Kopfzeile am Handy einzeilig, Tipp-Hervorhebung abgeschaltet - 29 neue Tests, insgesamt 133 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
103a603eb4
commit
bb592ad2bd
@@ -0,0 +1,190 @@
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { erfordereBenutzer } from '@/lib/auth/rechte';
|
||||
import { inhalte } from '@/lib/inhalte';
|
||||
import { alsKartenstand, staendeVon, uebersicht } from '@/lib/repos/karten';
|
||||
import { fach, fachNamen, heutigerStapel } from '@/lib/lernen/sm2';
|
||||
import { alsDatumText, heute } from '@/lib/zeit';
|
||||
|
||||
export const metadata: Metadata = { title: 'Merkkarten' };
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const STAPELGROESSE = 20;
|
||||
|
||||
export default async function KartenSeite() {
|
||||
const benutzer = await erfordereBenutzer();
|
||||
const alle = inhalte();
|
||||
const heuteDatum = heute();
|
||||
|
||||
const istAzubi = benutzer.rolle === 'azubi';
|
||||
const staende = istAzubi ? staendeVon(benutzer.id) : new Map();
|
||||
const stand = istAzubi
|
||||
? uebersicht(benutzer.id, alle.karten.map((k) => k.id), heuteDatum)
|
||||
: { gesamt: alle.karten.length, gelernt: 0, faellig: 0, nieGelernt: alle.karten.length, jeFach: { 1: alle.karten.length, 2: 0, 3: 0, 4: 0, 5: 0 } };
|
||||
|
||||
const faelligeAlle = heutigerStapel(
|
||||
alle.karten.map((karte) => ({
|
||||
karteId: karte.id,
|
||||
faelligAm: staende.get(karte.id)?.faelligAm ?? null,
|
||||
block: karte.block,
|
||||
})),
|
||||
heuteDatum,
|
||||
9999,
|
||||
);
|
||||
|
||||
const jeBlock = alle.bloecke.map((block) => {
|
||||
const desBlocks = alle.karten.filter((k) => k.block === block.id);
|
||||
const faellig = faelligeAlle.filter((f) => f.block === block.id).length;
|
||||
const gelernt = desBlocks.filter((k) => {
|
||||
const zeile = staende.get(k.id);
|
||||
return zeile ? fach(alsKartenstand(zeile)) >= 4 : false;
|
||||
}).length;
|
||||
return { block, gesamt: desBlocks.length, faellig, gelernt };
|
||||
});
|
||||
|
||||
if (!istAzubi) {
|
||||
return (
|
||||
<div className="stapel">
|
||||
<div>
|
||||
<h1>Merkkarten</h1>
|
||||
<p className="leise">
|
||||
{alle.karten.length} Karten aus {alle.handouts.length} Handouts. Gelernt wird nur vom Azubi – du
|
||||
siehst hier, was es gibt.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="raster">
|
||||
{jeBlock.map(({ block, gesamt }) => (
|
||||
<section key={block.id} className="karte">
|
||||
<h2 style={{ fontSize: '1rem', marginBottom: '0.2rem' }}>{block.titel}</h2>
|
||||
<p className="leise" style={{ marginBottom: 0 }}>
|
||||
{gesamt} {gesamt === 1 ? 'Karte' : 'Karten'}
|
||||
</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const heuteStapel = Math.min(stand.faellig, STAPELGROESSE);
|
||||
|
||||
return (
|
||||
<div className="stapel">
|
||||
<div>
|
||||
<h1>Merkkarten</h1>
|
||||
<p className="leise">
|
||||
{stand.gesamt} Karten insgesamt · {stand.gelernt} sitzen · {stand.nieGelernt} noch nie gelernt
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="karte-grund stapel" aria-labelledby="heute">
|
||||
<h2 id="heute" style={{ marginBottom: 0 }}>
|
||||
Heute fällig
|
||||
</h2>
|
||||
|
||||
{stand.faellig === 0 ? (
|
||||
<p style={{ marginBottom: 0 }}>
|
||||
Nichts. Alle Karten liegen weiter hinten – morgen ist wieder etwas dran.
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p style={{ marginBottom: 0 }}>
|
||||
<strong style={{ fontSize: '2rem' }}>{heuteStapel}</strong>{' '}
|
||||
{heuteStapel === 1 ? 'Karte' : 'Karten'}
|
||||
{stand.faellig > STAPELGROESSE ? (
|
||||
<span className="leise"> von {stand.faellig} fälligen – der Rest kommt morgen</span>
|
||||
) : null}
|
||||
</p>
|
||||
<div>
|
||||
<Link href="/karten/lernen" className="knopf knopf-akzent">
|
||||
Lernen starten
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="stapel" aria-labelledby="bloecke">
|
||||
<div>
|
||||
<h2 id="bloecke" style={{ marginBottom: '0.2rem' }}>
|
||||
Nach Themenblock
|
||||
</h2>
|
||||
<p className="leise" style={{ margin: 0 }}>
|
||||
Vor einer Lernzielkontrolle gezielt üben – hier sind auch Karten dabei, die eigentlich erst später
|
||||
dran wären.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="raster">
|
||||
{jeBlock.map(({ block, gesamt, faellig, gelernt }) => (
|
||||
<section key={block.id} className="karte stapel-eng">
|
||||
<h3 style={{ fontSize: '1rem', marginBottom: '0.2rem' }}>{block.titel}</h3>
|
||||
<p className="leise" style={{ margin: 0 }}>
|
||||
{gesamt} Karten · {gelernt} sitzen · {faellig} heute fällig
|
||||
</p>
|
||||
<div className="balken" aria-hidden="true">
|
||||
<span style={{ width: `${gesamt === 0 ? 0 : Math.round((gelernt / gesamt) * 100)}%` }} />
|
||||
</div>
|
||||
<div>
|
||||
<Link
|
||||
href={`/karten/lernen?block=${block.id}`}
|
||||
className={`knopf ${faellig > 0 ? '' : 'knopf-zweit'}`}
|
||||
>
|
||||
{faellig > 0 ? `${Math.min(faellig, STAPELGROESSE)} lernen` : 'Trotzdem üben'}
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="karte stapel-eng" aria-labelledby="faecher">
|
||||
<h2 id="faecher" style={{ fontSize: '1rem', marginBottom: '0.2rem' }}>
|
||||
Wie fest die Karten sitzen
|
||||
</h2>
|
||||
<ul className="faecher">
|
||||
{[1, 2, 3, 4, 5].map((nummer) => {
|
||||
const anzahl = stand.jeFach[nummer] ?? 0;
|
||||
const anteil = stand.gesamt === 0 ? 0 : Math.round((anzahl / stand.gesamt) * 100);
|
||||
return (
|
||||
<li key={nummer}>
|
||||
<span className="fach-nummer">{nummer}</span>
|
||||
<span className="fach-name">{fachNamen[nummer]}</span>
|
||||
<span className="fach-balken" aria-hidden="true">
|
||||
<span style={{ width: `${anteil}%` }} />
|
||||
</span>
|
||||
<span className="fach-zahl">{anzahl}</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{stand.gesamt > stand.nieGelernt ? (
|
||||
<section className="karte" aria-labelledby="naechste">
|
||||
<h2 id="naechste" style={{ fontSize: '1rem', marginBottom: '0.4rem' }}>
|
||||
Als Nächstes fällig
|
||||
</h2>
|
||||
<ul className="stapel-eng" style={{ listStyle: 'none', margin: 0, padding: 0 }}>
|
||||
{[...staende.values()]
|
||||
.filter((z) => z.faelligAm > heuteDatum)
|
||||
.sort((a, b) => a.faelligAm.localeCompare(b.faelligAm))
|
||||
.slice(0, 5)
|
||||
.map((z) => {
|
||||
const karte = alle.karteNach.get(z.karteId);
|
||||
if (!karte) return null;
|
||||
return (
|
||||
<li key={z.karteId} className="reihe" style={{ gap: '0.5rem', flexWrap: 'nowrap' }}>
|
||||
<span className="handout-kuerzel">{karte.handoutId}</span>
|
||||
<span style={{ flex: 1 }}>{karte.frage}</span>
|
||||
<span className="leise">{alsDatumText(z.faelligAm)}</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user