'use client'; import { useEffect, useState } from 'react'; export interface DayCounter { date: string; // YYYY-MM-DD counter_id: number; counter_name: string; image_path: string | null; increments: number; } interface Props { dailyCounters: DayCounter[]; } // Mon-first: 0=Mon … 6=Sun const DOW = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; const MONTH_NAMES = [ 'January','February','March','April','May','June', 'July','August','September','October','November','December', ]; function pad(n: number) { return String(n).padStart(2, '0'); } function toDateStr(y: number, m: number, d: number) { return `${y}-${pad(m + 1)}-${pad(d)}`; } // Returns 0 (Mon) … 6 (Sun) for a given date function mondayDow(year: number, month: number, day: number) { const dow = new Date(year, month, day).getDay(); // 0=Sun…6=Sat return (dow + 6) % 7; } export default function CalendarView({ dailyCounters }: Props) { const now = new Date(); const [viewYear, setViewYear] = useState(now.getFullYear()); const [viewMonth, setViewMonth] = useState(now.getMonth()); const [selectedDay, setSelectedDay] = useState<{ date: string; counters: DayCounter[] } | null>(null); // Close modal on Escape useEffect(() => { if (!selectedDay) return; const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') setSelectedDay(null); }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [selectedDay]); const byDate = new Map(); for (const dc of dailyCounters) { if (!byDate.has(dc.date)) byDate.set(dc.date, []); byDate.get(dc.date)!.push(dc); } const todayStr = toDateStr(now.getFullYear(), now.getMonth(), now.getDate()); const isCurrentMonth = viewYear === now.getFullYear() && viewMonth === now.getMonth(); function prev() { if (viewMonth === 0) { setViewMonth(11); setViewYear(y => y - 1); } else setViewMonth(m => m - 1); } function next() { if (viewMonth === 11) { setViewMonth(0); setViewYear(y => y + 1); } else setViewMonth(m => m + 1); } const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate(); const firstOffset = mondayDow(viewYear, viewMonth, 1); // blank cells before day 1 const totalCells = Math.ceil((daysInMonth + firstOffset) / 7) * 7; const cells = Array.from({ length: totalCells }, (_, i) => { const dayNum = i - firstOffset + 1; if (dayNum < 1 || dayNum > daysInMonth) return null; const date = toDateStr(viewYear, viewMonth, dayNum); return { dayNum, date, counters: byDate.get(date) ?? [] }; }); return (
{/* Month navigation */}
{MONTH_NAMES[viewMonth]} {viewYear} {!isCurrentMonth && ( )}
{/* Day-of-week header */}
{DOW.map(d => (
{d}
))}
{/* Day cells */}
{cells.map((cell, i) => cell ? (
setSelectedDay({ date: cell.date, counters: cell.counters })} className={`bg-ctp-base min-h-[4.5rem] p-1 flex flex-col gap-0.5 cursor-pointer hover:bg-ctp-surface0 transition-colors ${ cell.date === todayStr ? 'ring-1 ring-inset ring-ctp-mauve' : '' }`} > 0 ? 'text-ctp-text' : 'text-ctp-overlay1' }`}> {cell.dayNum} {cell.counters.slice(0, 3).map(c => (
{c.counter_name} +{c.increments}
))} {cell.counters.length > 3 && ( +{cell.counters.length - 3} more )}
) : (
) )}
{/* Day detail modal */} {selectedDay && (
setSelectedDay(null)} >
e.stopPropagation()} > {/* Header */}

{new Date(selectedDay.date + 'T00:00:00').toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric', })}

{/* Counter list */} {selectedDay.counters.length === 0 ? (

No activity on this day.

) : (
{selectedDay.counters.map(c => (
{c.image_path ? ( // eslint-disable-next-line @next/next/no-img-element ) : (
#
)}

{c.counter_name}

+{c.increments}

))}
)} {/* Total */} {selectedDay.counters.length > 1 && (

Total: +{selectedDay.counters.reduce((s, c) => s + c.increments, 0)}

)}
)}
); }