diff --git a/packages/frontend/src/components/LessonStatsPanel.tsx b/packages/frontend/src/components/LessonStatsPanel.tsx
new file mode 100644
index 0000000..6007c5a
--- /dev/null
+++ b/packages/frontend/src/components/LessonStatsPanel.tsx
@@ -0,0 +1,31 @@
+import type { LessonStats } from '../api/stats.js';
+
+function relativeTime(unixSec: number | null): string {
+ if (!unixSec) return 'nooit';
+ const diff = Math.floor(Date.now() / 1000) - unixSec;
+ if (diff < 3600) return `${Math.max(1, 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 LessonStatsPanel({ stats, lastSessionAt }: { stats: LessonStats; lastSessionAt?: number | null }) {
+ const masteredFrac = stats.totalCards === 0 ? 0 : stats.mastered / stats.totalCards;
+ return (
+
+
+
+
+
+
+ );
+}
+
+function Card({ label, value, sub }: { label: string; value: string; sub?: string }) {
+ return (
+
+
{label}
+
{value}
+ {sub &&
{sub}
}
+
+ );
+}
diff --git a/packages/frontend/src/components/RecentSessionsList.tsx b/packages/frontend/src/components/RecentSessionsList.tsx
new file mode 100644
index 0000000..3810b12
--- /dev/null
+++ b/packages/frontend/src/components/RecentSessionsList.tsx
@@ -0,0 +1,41 @@
+function relativeTime(unixSec: number): string {
+ const diff = Math.floor(Date.now() / 1000) - unixSec;
+ if (diff < 3600) return `${Math.max(1, Math.floor(diff / 60))}m geleden`;
+ if (diff < 86400) return `${Math.floor(diff / 3600)}u geleden`;
+ return `${Math.floor(diff / 86400)}d geleden`;
+}
+
+function fmtDuration(s: number): string {
+ if (s < 60) return `${s}s`;
+ return `${Math.floor(s / 60)}m ${s % 60}s`;
+}
+
+export interface RecentSessionRow {
+ id: number;
+ startedAt: number;
+ durationSeconds: number | null;
+ cardsShown: number;
+ cardsCorrect: number;
+}
+
+export function RecentSessionsList({ rows }: { rows: RecentSessionRow[] }) {
+ if (rows.length === 0) {
+ return Nog geen sessies op deze les.
;
+ }
+ return (
+
+ {rows.map((s) => {
+ const pct = s.cardsShown > 0 ? Math.round((s.cardsCorrect / s.cardsShown) * 100) : 0;
+ return (
+ -
+ {relativeTime(s.startedAt)}
+
+ {pct}%
+ {s.cardsCorrect}/{s.cardsShown} ยท {fmtDuration(s.durationSeconds ?? 0)}
+
+
+ );
+ })}
+
+ );
+}
diff --git a/packages/frontend/src/components/SublessonList.tsx b/packages/frontend/src/components/SublessonList.tsx
new file mode 100644
index 0000000..4017ad6
--- /dev/null
+++ b/packages/frontend/src/components/SublessonList.tsx
@@ -0,0 +1,21 @@
+import { Link } from 'react-router-dom';
+import type { LessonTreeNode } from '@flashcard/shared';
+
+export function SublessonList({ items }: { items: LessonTreeNode[] }) {
+ if (items.length === 0) return null;
+ return (
+
+
Sublessen
+
+ {items.map((c) => (
+ -
+ {c.name}
+
+ {c.cardCount}
+
+
+ ))}
+
+
+ );
+}