import React, { useEffect, useState } from 'react'; // Helper to get all days in the last 52 weeks, starting on Monday - UTC version function getCalendarDays() { const days = []; // Work entirely in UTC to match Gitea const today = new Date(); const todayUTC = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate())); // Find the Monday that starts our 52-week period const startDate = new Date(todayUTC); startDate.setUTCDate(todayUTC.getUTCDate() - (52 * 7 - 1)); // Adjust startDate to the Monday of that week (using UTC day) const startDayOfWeek = startDate.getUTCDay(); // 0 = Sunday, 1 = Monday, etc. const daysToMonday = startDayOfWeek === 0 ? -6 : 1 - startDayOfWeek; startDate.setUTCDate(startDate.getUTCDate() + daysToMonday); // Generate exactly 52 weeks (364 days) starting from that Monday for (let i = 0; i < 7 * 52; i++) { const d = new Date(startDate); d.setUTCDate(startDate.getUTCDate() + i); days.push(d); } return days; } // Get month labels for the top function getMonthLabels(days) { const labels = []; let lastMonth = null; for (let week = 0; week < 52; week++) { const day = days[week * 7]; const month = day.toLocaleString('default', { month: 'short' }); if (month !== lastMonth) { labels.push({ week, month }); lastMonth = month; } } return labels; } // Day labels for rows (Monday to Sunday) const DAY_LABELS = ['Mon', '', 'Wed', '', 'Fri', '', 'Sun']; // Color scales const COLORS_LIGHT = [ '#ebedf0', // 0 '#c6e48b', // 1 '#7bc96f', // 2 '#239a3b', // 3 '#196127', // 4+ ]; const COLORS_DARK = [ '#23272e', // 0 '#3c4d36', // 1 '#4e7c4e', // 2 '#399150', // 3 '#6ee7b7', // 4+ ]; function getColor(count, isDark) { const palette = isDark ? COLORS_DARK : COLORS_LIGHT; if (!count) return palette[0]; if (count >= 4) return palette[4]; return palette[count]; } export default function ContributionCalendar({ data }) { const days = getCalendarDays(); const monthLabels = getMonthLabels(days); const [isDark, setIsDark] = useState(false); const [isMobile, setIsMobile] = useState(false); const [expanded, setExpanded] = useState(false); useEffect(() => { // Detect dark mode by checking for 'dark' class on const checkDark = () => { setIsDark(document.documentElement.classList.contains('dark')); }; checkDark(); const observer = new MutationObserver(checkDark); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); return () => observer.disconnect(); }, []); useEffect(() => { // Detect mobile screen size const checkMobile = () => { setIsMobile(window.innerWidth <= 640); // Tailwind's sm breakpoint }; checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); // Determine how many weeks to show const weeksToShow = isMobile && !expanded ? 19 : 52; const startWeek = 52 - weeksToShow; const visibleDays = days.slice(startWeek * 7, 52 * 7); const visibleMonthLabels = monthLabels.filter(l => l.week >= startWeek); const calendarRef = React.useRef(null); // When expanding to full year on mobile, scroll to the right (most recent weeks) useEffect(() => { if (!isMobile) return; const el = calendarRef.current; if (!el) return; if (expanded) { // Wait for the DOM to update, then scroll setTimeout(() => { el.scrollLeft = el.scrollWidth; }, 100); } }, [expanded, isMobile]); // Get max count for scaling (optional, for more dynamic color) // const max = Math.max(...Object.values(data)); return (