feat(cards): leech detection in /me/stats + Stats-Page-Sektion
Some checks are pending
CI / validate (push) Waiting to run
Some checks are pending
CI / validate (push) Waiting to run
Karten mit ≥4 Lapses werden im Stats-Endpoint als `leech_cards` geliefert (mit Front-Snippet, Deck-Name, Lapses-Count, sortiert desc, max 20). Stats-Page zeigt eine rote „Schwierige Karten"- Sektion mit Link in den Card-Editor. * apps/api/src/routes/me.ts: GROUP BY card → SUM(lapses) ≥ 4 Filter, frontSnippetFor()-Helper für alle 6 Card-Types (basic, basic-reverse, cloze, image-occlusion, audio-front, typing, multiple-choice). Cloze-Markup wird gestrippt damit der Snippet UI-tauglich ist. * apps/web Stats-Page: neue CardSurface mit Warning-Icon, scrollbare Liste mit Front + Deck + Lapses-Badge, Empty-State. * i18n in DE/EN/FR/IT/ES (5 Strings + plural-Form). 104/104 Tests grün (kein neuer Test — bestehende /me/stats-Tests covern die Aggregations-Form), web check 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4bb1390180
commit
9f019d8e2f
9 changed files with 198 additions and 5 deletions
|
|
@ -40,6 +40,17 @@ export function deleteMe() {
|
|||
return api<DeleteMeResult>('/api/v1/me/delete', { method: 'POST' });
|
||||
}
|
||||
|
||||
export interface LeechCard {
|
||||
card_id: string;
|
||||
deck_id: string;
|
||||
deck_name: string;
|
||||
type: string;
|
||||
front_snippet: string;
|
||||
lapses: number;
|
||||
reps: number;
|
||||
last_review: string | null;
|
||||
}
|
||||
|
||||
export interface UserStats {
|
||||
user_id: string;
|
||||
generated_at: string;
|
||||
|
|
@ -55,6 +66,8 @@ export interface UserStats {
|
|||
retention_reps: number;
|
||||
retention_lapses: number;
|
||||
due_forecast: { day: string; n: number }[];
|
||||
leech_threshold: number;
|
||||
leech_cards: LeechCard[];
|
||||
}
|
||||
|
||||
export function loadStats() {
|
||||
|
|
|
|||
|
|
@ -310,6 +310,11 @@ export const de: TranslationNode = {
|
|||
forecast_title: 'Nächste 7 Tage',
|
||||
forecast_desc: 'Fällige Karten je Tag.',
|
||||
forecast_empty: 'Nichts fällig.',
|
||||
leeches_title: 'Schwierige Karten',
|
||||
leeches_desc: 'Karten mit {threshold}+ Ausrutschern — Kandidaten zum Umformulieren oder Aufteilen.',
|
||||
leeches_none: 'Keine zähen Karten — sauber.',
|
||||
leeches_lapses: '{n} Ausrutscher',
|
||||
leeches_lapses_one: '{n} Ausrutscher',
|
||||
loading: 'Lade…',
|
||||
error: 'Fehler: {msg}',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -305,6 +305,11 @@ export const en: TranslationNode = {
|
|||
forecast_title: 'Next 7 days',
|
||||
forecast_desc: 'Cards due per day.',
|
||||
forecast_empty: 'Nothing scheduled.',
|
||||
leeches_title: 'Tough cards',
|
||||
leeches_desc: 'Cards with {threshold}+ lapses — candidates to reword or split.',
|
||||
leeches_none: 'No leeches — clean slate.',
|
||||
leeches_lapses: '{n} lapses',
|
||||
leeches_lapses_one: '{n} lapse',
|
||||
loading: 'Loading…',
|
||||
error: 'Error: {msg}',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -305,6 +305,11 @@ export const es: TranslationNode = {
|
|||
forecast_title: 'Próximos 7 días',
|
||||
forecast_desc: 'Tarjetas pendientes por día.',
|
||||
forecast_empty: 'Nada programado.',
|
||||
leeches_title: 'Cartas difíciles',
|
||||
leeches_desc: 'Cartas con {threshold}+ olvidos — candidatas para reformular o dividir.',
|
||||
leeches_none: 'Ninguna carta difícil — todo bien.',
|
||||
leeches_lapses: '{n} olvidos',
|
||||
leeches_lapses_one: '{n} olvido',
|
||||
loading: 'Cargando…',
|
||||
error: 'Error: {msg}',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -305,6 +305,11 @@ export const fr: TranslationNode = {
|
|||
forecast_title: '7 prochains jours',
|
||||
forecast_desc: 'Cartes à réviser par jour.',
|
||||
forecast_empty: 'Rien de prévu.',
|
||||
leeches_title: 'Cartes coriaces',
|
||||
leeches_desc: 'Cartes avec {threshold}+ oublis — à reformuler ou diviser.',
|
||||
leeches_none: 'Aucune carte difficile — tout va bien.',
|
||||
leeches_lapses: '{n} oublis',
|
||||
leeches_lapses_one: '{n} oubli',
|
||||
loading: 'Chargement…',
|
||||
error: 'Erreur : {msg}',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -305,6 +305,11 @@ export const it: TranslationNode = {
|
|||
forecast_title: 'Prossimi 7 giorni',
|
||||
forecast_desc: 'Carte da ripassare per giorno.',
|
||||
forecast_empty: 'Niente in programma.',
|
||||
leeches_title: 'Carte ostiche',
|
||||
leeches_desc: 'Carte con {threshold}+ ricadute — da riformulare o dividere.',
|
||||
leeches_none: 'Nessuna carta difficile — pulito.',
|
||||
leeches_lapses: '{n} ricadute',
|
||||
leeches_lapses_one: '{n} ricaduta',
|
||||
loading: 'Caricamento…',
|
||||
error: 'Errore: {msg}',
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue