import { useMemo, useRef, useState, useEffect } from 'react'; interface PrintCalendarProps { printDates: string[]; // Array of ISO date strings months?: number; // How many months to show (default 3) } export function PrintCalendar({ printDates, months = 3 }: PrintCalendarProps) { const containerRef = useRef(null); const [containerWidth, setContainerWidth] = useState(0); // Measure container width useEffect(() => { const container = containerRef.current; if (!container) return; const observer = new ResizeObserver((entries) => { const width = entries[0]?.contentRect.width || 0; setContainerWidth(width); }); observer.observe(container); return () => observer.disconnect(); }, []); const { weeks, monthLabels, printCounts } = useMemo(() => { // Count prints per day const counts: Record = {}; printDates.forEach((date) => { const day = date.split('T')[0]; counts[day] = (counts[day] || 0) + 1; }); // Generate weeks for the last N months const today = new Date(); const startDate = new Date(today); startDate.setMonth(startDate.getMonth() - months); startDate.setDate(startDate.getDate() - startDate.getDay()); // Start from Sunday const weeks: Date[][] = []; const monthLabels: { month: string; weekIndex: number }[] = []; let currentWeek: Date[] = []; let lastMonth = -1; const current = new Date(startDate); let weekIndex = 0; while (current <= today) { if (current.getDay() === 0 && currentWeek.length > 0) { weeks.push(currentWeek); currentWeek = []; weekIndex++; } // Track month labels if (current.getMonth() !== lastMonth) { monthLabels.push({ month: current.toLocaleDateString('en-US', { month: 'short' }), weekIndex, }); lastMonth = current.getMonth(); } currentWeek.push(new Date(current)); current.setDate(current.getDate() + 1); } if (currentWeek.length > 0) { weeks.push(currentWeek); } return { weeks, monthLabels, printCounts: counts }; }, [printDates, months]); const maxCount = Math.max(1, ...Object.values(printCounts)); const getColor = (count: number) => { if (count === 0) return 'bg-bambu-dark'; const intensity = count / maxCount; if (intensity <= 0.25) return 'bg-bambu-green/30'; if (intensity <= 0.5) return 'bg-bambu-green/50'; if (intensity <= 0.75) return 'bg-bambu-green/75'; return 'bg-bambu-green'; }; const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; // Calculate cell size based on container width const numWeeks = weeks.length; const dayLabelWidth = 32; // Space for day labels (Mon, Wed, Fri) const gap = 2; // Gap between cells const availableWidth = containerWidth - dayLabelWidth - 16; // 16px padding const calculatedCellSize = numWeeks > 0 ? Math.floor((availableWidth - (numWeeks - 1) * gap) / numWeeks) : 12; // Clamp cell size between 8 and 20 pixels const cellSize = Math.max(8, Math.min(20, calculatedCellSize)); const fontSize = cellSize <= 10 ? 10 : 12; return (
{containerWidth > 0 && (
{/* Month labels */}
{monthLabels.map(({ month, weekIndex }, i) => (
{month}
))}
{/* Day labels */}
{dayLabels.map((day, i) => (
{day}
))}
{/* Calendar grid */} {weeks.map((week, weekIdx) => (
{[0, 1, 2, 3, 4, 5, 6].map((dayOfWeek) => { const day = week.find((d) => d.getDay() === dayOfWeek); if (!day) { return (
); } const dateStr = day.toISOString().split('T')[0]; const count = printCounts[dateStr] || 0; const isToday = dateStr === new Date().toISOString().split('T')[0]; return (
); })}
))}
{/* Legend */}
Less
More
)}
); }