// Adapted from jalcoui (MIT) — github.com/jal-co/ui import type { ActivityEntry } from './types'; import { GAP } from './types'; export interface WeekDay { date: Date; count: number; } /** * Bucket the supplied entries into a fixed-size grid that ends on the * current week and spans `weekCount` weeks. Missing dates fill with 0. */ export function buildWeeks(data: ActivityEntry[], weekCount: number): WeekDay[][] { const countMap = new Map(); for (const entry of data) { countMap.set(entry.date, (countMap.get(entry.date) ?? 0) + entry.count); } const today = new Date(); today.setHours(0, 0, 0, 0); const todayDay = today.getDay(); const endOfWeek = new Date(today); endOfWeek.setDate(today.getDate() + (6 - todayDay)); const totalDays = weekCount * 7; const startDate = new Date(endOfWeek); startDate.setDate(endOfWeek.getDate() - totalDays + 1); const weeks: WeekDay[][] = []; let currentWeek: WeekDay[] = []; for (let i = 0; i < totalDays; i++) { const d = new Date(startDate); d.setDate(startDate.getDate() + i); const key = d.toISOString().slice(0, 10); currentWeek.push({ date: d, count: countMap.get(key) ?? 0 }); if (currentWeek.length === 7) { weeks.push(currentWeek); currentWeek = []; } } if (currentWeek.length > 0) { weeks.push(currentWeek); } return weeks; } /** * Find the column offset for the first week of every new month. Used to * lay month labels along the top of the grid. */ export function getMonthLabels( weeks: WeekDay[][], blockSize: number, ): { label: string; offset: number }[] { const months: { label: string; offset: number }[] = []; let lastKey = ''; for (let w = 0; w < weeks.length; w++) { const firstDay = weeks[w][0]; const key = `${firstDay.date.getFullYear()}-${firstDay.date.getMonth()}`; if (key !== lastKey) { months.push({ label: firstDay.date.toLocaleString('en-US', { month: 'short' }), offset: w * (blockSize + GAP), }); lastKey = key; } } return months; } export function formatDate(date: Date): string { return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', }); }