From d9b913aab7bbc8933d4db3f0a2f0fc8bc85a921a Mon Sep 17 00:00:00 2001 From: Bert Hausmans Date: Thu, 21 May 2026 07:07:46 +0200 Subject: [PATCH] feat(frontend): lesson stats panel + sublesson list + recent sessions list --- .../src/components/LessonStatsPanel.tsx | 31 ++++++++++++++ .../src/components/RecentSessionsList.tsx | 41 +++++++++++++++++++ .../frontend/src/components/SublessonList.tsx | 21 ++++++++++ 3 files changed, 93 insertions(+) create mode 100644 packages/frontend/src/components/LessonStatsPanel.tsx create mode 100644 packages/frontend/src/components/RecentSessionsList.tsx create mode 100644 packages/frontend/src/components/SublessonList.tsx 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 ( + + ); +} 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

+ +
+ ); +}