feat(frontend): lesson progress list with sorting

This commit is contained in:
2026-05-21 07:04:34 +02:00
parent 5df6b240d9
commit a10d02cbaf

View File

@@ -0,0 +1,79 @@
import { useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
export interface LessonProgressRow {
lessonId: number;
name: string;
totalCards: number;
masteredCards: number;
scorePct: number;
lastSessionAt: number | null;
}
type SortKey = 'name' | 'score' | 'last';
function relativeTime(unixSec: number | null): string {
if (!unixSec) return 'nooit';
const diff = Math.floor(Date.now() / 1000) - unixSec;
if (diff < 60) return 'zojuist';
if (diff < 3600) return `${Math.floor(diff / 60)}m geleden`;
if (diff < 86400) return `${Math.floor(diff / 3600)}u geleden`;
return `${Math.floor(diff / 86400)}d geleden`;
}
export function LessonProgressList({ rows }: { rows: LessonProgressRow[] }) {
const [sortBy, setSortBy] = useState<SortKey>('score');
const sorted = useMemo(() => {
const copy = [...rows];
copy.sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name);
if (sortBy === 'last') return (b.lastSessionAt ?? 0) - (a.lastSessionAt ?? 0);
return b.scorePct - a.scorePct;
});
return copy;
}, [rows, sortBy]);
if (rows.length === 0) {
return <p className="text-sm text-slate-500">Nog geen lessen.</p>;
}
return (
<div>
<div className="mb-2 flex gap-1 text-xs">
<span className="text-slate-500">Sorteer:</span>
{(['score', 'name', 'last'] as SortKey[]).map((k) => (
<button
key={k}
onClick={() => setSortBy(k)}
className={`rounded-full px-2 py-0.5 ${
sortBy === k
? 'bg-brand-100 text-brand-700 dark:bg-brand-900/40 dark:text-brand-200'
: 'text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800'
}`}
>
{k === 'score' ? 'score' : k === 'name' ? 'naam' : 'laatst'}
</button>
))}
</div>
<ul className="surface divide-y divide-brand-100/60 dark:divide-slate-800">
{sorted.map((r) => {
const masteredFrac = r.totalCards === 0 ? 0 : r.masteredCards / r.totalCards;
return (
<li key={r.lessonId} className="flex items-center gap-3 p-3 text-sm">
<Link to={`/lessons/${r.lessonId}`} className="flex-1 truncate font-medium">
{r.name}
</Link>
<div className="h-2 w-24 overflow-hidden rounded-full bg-brand-100 dark:bg-slate-800">
<div className="h-full bg-success-500" style={{ width: `${Math.round(masteredFrac * 100)}%` }} />
</div>
<span className="w-12 text-right text-xs text-slate-500">{r.masteredCards}/{r.totalCards}</span>
<span className="w-12 text-right text-xs font-semibold text-brand-700 dark:text-brand-200">{r.scorePct}%</span>
<span className="w-20 text-right text-xs text-slate-500">{relativeTime(r.lastSessionAt)}</span>
</li>
);
})}
</ul>
</div>
);
}