import { useMemo, useState } from 'react'; import { ScatterChart, Scatter, XAxis, YAxis, Tooltip, ResponsiveContainer, Rectangle, } from 'recharts'; import { rechartsTheme } from '@cameleer/design-system'; import styles from './DashboardTab.module.css'; export interface PunchcardCell { weekday: number; hour: number; totalCount: number; failedCount: number; } interface PunchcardHeatmapProps { cells: PunchcardCell[]; } type Mode = 'transactions' | 'errors'; const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; function transactionColor(ratio: number): string { if (ratio === 0) return 'var(--bg-inset)'; const lightness = 90 - ratio * 55; return `hsl(220, 60%, ${Math.round(lightness)}%)`; } function errorColor(ratio: number): string { if (ratio === 0) return 'var(--bg-inset)'; const lightness = 90 - ratio * 55; return `hsl(0, 65%, ${Math.round(lightness)}%)`; } interface HeatmapPoint { weekday: number; hour: number; value: number; fill: string; } function HeatmapCell(props: Record) { const { cx, cy, payload } = props as { cx: number; cy: number; payload: HeatmapPoint; }; if (!payload) return null; const cellW = 32; const cellH = 10; return ( ); } function formatDay(value: number): string { return DAYS[value] ?? ''; } function formatHour(value: number): string { return String(value).padStart(2, '0'); } function buildGrid(cells: PunchcardCell[], mode: Mode): HeatmapPoint[] { const values = cells.map(c => mode === 'errors' ? c.failedCount : c.totalCount); const maxVal = Math.max(...values, 1); const cellMap = new Map(); for (const c of cells) cellMap.set(`${c.weekday}-${c.hour}`, c); const points: HeatmapPoint[] = []; for (let d = 0; d < 7; d++) { for (let h = 0; h < 24; h++) { const cell = cellMap.get(`${d}-${h}`); const val = cell ? (mode === 'errors' ? cell.failedCount : cell.totalCount) : 0; const ratio = maxVal > 0 ? val / maxVal : 0; points.push({ weekday: d, hour: h, value: val, fill: mode === 'errors' ? errorColor(ratio) : transactionColor(ratio), }); } } return points; } export function PunchcardHeatmap({ cells }: PunchcardHeatmapProps) { const [mode, setMode] = useState('transactions'); const data = useMemo(() => buildGrid(cells, mode), [cells, mode]); return (
{ const p = entry.payload; if (!p) return ''; return [`${p.value.toLocaleString()} ${mode}`, `${DAYS[p.weekday]} ${formatHour(p.hour)}:00`]; }} labelFormatter={() => ''} /> )} />} isAnimationActive={false} />
); }