diff --git a/packages/frontend/src/components/Heatmap.tsx b/packages/frontend/src/components/Heatmap.tsx new file mode 100644 index 0000000..91bf3c5 --- /dev/null +++ b/packages/frontend/src/components/Heatmap.tsx @@ -0,0 +1,91 @@ +import { useMemo } from 'react'; + +interface HeatmapPoint { day: string; sessions: number; attempts: number; } + +export function Heatmap({ points }: { points: HeatmapPoint[] }) { + const grid = useMemo(() => { + const map = new Map(); + for (const p of points) map.set(p.day, p); + const today = new Date(); + today.setUTCHours(0, 0, 0, 0); + const lastSat = new Date(today); + while (lastSat.getUTCDay() !== 6) lastSat.setUTCDate(lastSat.getUTCDate() + 1); + + const weeks: { date: Date; data?: HeatmapPoint }[][] = []; + const cursor = new Date(lastSat); + cursor.setUTCDate(cursor.getUTCDate() - 53 * 7 + 1); + for (let w = 0; w < 53; w++) { + const col: { date: Date; data?: HeatmapPoint }[] = []; + for (let d = 0; d < 7; d++) { + const key = `${cursor.getUTCFullYear()}-${cursor.getUTCMonth()}-${cursor.getUTCDate()}`; + col.push({ date: new Date(cursor), data: map.get(key) }); + cursor.setUTCDate(cursor.getUTCDate() + 1); + } + weeks.push(col); + } + return weeks; + }, [points]); + + function colorFor(attempts: number): string { + if (attempts === 0) return 'bg-slate-100 dark:bg-slate-800'; + if (attempts < 5) return 'bg-success-200 dark:bg-success-400/30'; + if (attempts < 15) return 'bg-success-400 dark:bg-success-400/60'; + if (attempts < 50) return 'bg-success-500 dark:bg-success-500/80'; + return 'bg-success-700 dark:bg-success-500'; + } + + const monthLabels = useMemo(() => { + const labels: { col: number; text: string }[] = []; + let lastMonth = -1; + grid.forEach((col, i) => { + const m = col[0]!.date.getUTCMonth(); + if (m !== lastMonth) { + labels.push({ col: i, text: ['jan','feb','mrt','apr','mei','jun','jul','aug','sep','okt','nov','dec'][m]! }); + lastMonth = m; + } + }); + return labels; + }, [grid]); + + const todayKey = (() => { + const t = new Date(); + return `${t.getUTCFullYear()}-${t.getUTCMonth()}-${t.getUTCDate()}`; + })(); + + return ( +
+
+
+ {monthLabels.map((m, i) => ( + {m.text} + ))} +
+
+
+ {['Ma', '', 'Wo', '', 'Vr', '', ''].map((label, i) => ( + {label} + ))} +
+
+ {grid.map((col, i) => ( +
+ {col.map((cell, j) => { + const key = `${cell.date.getUTCFullYear()}-${cell.date.getUTCMonth()}-${cell.date.getUTCDate()}`; + const isToday = key === todayKey; + const a = cell.data?.attempts ?? 0; + return ( +
+ ); + })} +
+ ))} +
+
+
+
+ ); +}