import { useMemo } from 'react'; export interface PunchcardCell { weekday: number; hour: number; totalCount: number; failedCount: number; } interface PunchcardHeatmapProps { cells: PunchcardCell[]; mode: 'transactions' | 'errors'; width: number; height: number; } const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const LEFT_MARGIN = 28; const TOP_MARGIN = 18; const BOTTOM_MARGIN = 4; const RIGHT_MARGIN = 4; function transactionColor(ratio: number): string { if (ratio === 0) return 'hsl(220, 15%, 95%)'; const lightness = 90 - ratio * 55; return `hsl(220, 60%, ${Math.round(lightness)}%)`; } function errorColor(ratio: number): string { if (ratio === 0) return 'hsl(0, 10%, 95%)'; const lightness = 90 - ratio * 55; return `hsl(0, 65%, ${Math.round(lightness)}%)`; } export function PunchcardHeatmap({ cells, mode, width, height }: PunchcardHeatmapProps) { const grid = useMemo(() => { const map = new Map(); for (const c of cells) { map.set(`${c.weekday}-${c.hour}`, c); } const values: number[] = []; for (const c of cells) { values.push(mode === 'errors' ? c.failedCount : c.totalCount); } const maxVal = Math.max(...values, 1); const gridWidth = width - LEFT_MARGIN - RIGHT_MARGIN; const gridHeight = height - TOP_MARGIN - BOTTOM_MARGIN; const cellW = gridWidth / 7; const cellH = gridHeight / 24; const rects: { x: number; y: number; w: number; h: number; fill: string; value: number; day: string; hour: number }[] = []; for (let d = 0; d < 7; d++) { for (let h = 0; h < 24; h++) { const cell = map.get(`${d}-${h}`); const val = cell ? (mode === 'errors' ? cell.failedCount : cell.totalCount) : 0; const ratio = maxVal > 0 ? val / maxVal : 0; const fill = mode === 'errors' ? errorColor(ratio) : transactionColor(ratio); rects.push({ x: LEFT_MARGIN + d * cellW, y: TOP_MARGIN + h * cellH, w: cellW, h: cellH, fill, value: val, day: DAYS[d], hour: h, }); } } return { rects, cellW, cellH }; }, [cells, mode, width, height]); return ( {/* Day labels (top) */} {DAYS.map((day, i) => ( {day} ))} {/* Hour labels (left, every 4 hours) */} {[0, 4, 8, 12, 16, 20].map((h) => ( {String(h).padStart(2, '0')} ))} {/* Cells */} {grid.rects.map((r) => ( {`${r.day} ${String(r.hour).padStart(2, '0')}:00 — ${r.value.toLocaleString()} ${mode}`} ))} ); }