import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { fetchDailyCounts } from '../api/client'; const CELL_SIZE = 12; const GAP = 3; const RADIUS = CELL_SIZE / 2; const ROWS = 7; // days per week const LEFT_LABEL_WIDTH = 28; const TOP_LABEL_HEIGHT = 16; const DAY_LABELS = ['', 'mon', '', 'wed', '', 'fri', '']; const MONTH_LABELS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']; const COLORS = [ 'rgba(255,255,255,0.05)', // 0: empty '#0e4429', // 1: low '#006d32', // 2: medium-low '#26a641', // 3: medium '#39d353', // 4: high ]; export function ContributionGraph() { const to = new Date(); const from = new Date(to); from.setFullYear(from.getFullYear() - 1); const fromStr = fmt(from); const toStr = fmt(to); const dailyQ = useQuery({ queryKey: ['daily-counts', fromStr, toStr], queryFn: () => fetchDailyCounts(fromStr, toStr), staleTime: 5 * 60_000, }); const navigate = useNavigate(); const { weeks, monthMarkers, thresholds, totalCount } = useMemo(() => { const counts = dailyQ.data ?? []; const countMap = new Map(counts.map((d) => [d.date, d.count])); // Start from the Sunday before `from` const start = new Date(from); start.setDate(start.getDate() - start.getDay()); const weeks: { date: string; count: number; col: number; row: number }[][] = []; const monthMarkers: { col: number; label: string }[] = []; let col = 0; let prevMonth = -1; const cursor = new Date(start); while (cursor <= to) { const week: typeof weeks[0] = []; for (let row = 0; row < ROWS; row++) { const dateStr = fmt(cursor); const count = countMap.get(dateStr) ?? 0; week.push({ date: dateStr, count, col, row }); // Track month transitions (on the first day of each week) if (row === 0) { const m = cursor.getMonth(); if (m !== prevMonth) { monthMarkers.push({ col, label: MONTH_LABELS[m] }); prevMonth = m; } } cursor.setDate(cursor.getDate() + 1); } weeks.push(week); col++; } // Compute quantile thresholds from non-zero counts const nonZero = counts.map((d) => d.count).filter((c) => c > 0).sort((a, b) => a - b); const thresholds = computeThresholds(nonZero); const totalCount = counts.reduce((sum, d) => sum + d.count, 0); return { weeks, monthMarkers, thresholds, totalCount }; }, [dailyQ.data]); const cols = weeks.length; const svgWidth = LEFT_LABEL_WIDTH + cols * (CELL_SIZE + GAP); const svgHeight = TOP_LABEL_HEIGHT + ROWS * (CELL_SIZE + GAP); if (dailyQ.isLoading) return
loading contribution graph...
; if (dailyQ.isError) return null; return ({totalCount} contributions in the last year