import type { Park } from "@/lib/scrapers/types"; import type { DayData } from "@/lib/db"; interface WeekCalendarProps { parks: Park[]; weekDates: string[]; // 7 dates, YYYY-MM-DD, Sun–Sat data: Record>; // parkId → date → DayData } const DOW = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; const MONTHS = [ "Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec", ]; function parseDate(iso: string) { const d = new Date(iso + "T00:00:00"); return { month: d.getMonth(), day: d.getDate(), dow: d.getDay(), isWeekend: d.getDay() === 0 || d.getDay() === 6, }; } export function WeekCalendar({ parks, weekDates, data }: WeekCalendarProps) { const today = new Date().toISOString().slice(0, 10); const parsedDates = weekDates.map(parseDate); // Detect month boundaries for column headers const firstMonth = parsedDates[0].month; const firstYear = new Date(weekDates[0] + "T00:00:00").getFullYear(); return (
{/* Park name column */} {weekDates.map((d) => ( ))} {/* Month header — only show if we cross a month boundary */} {parsedDates.some((d) => d.month !== firstMonth) && ( ); })} )} {/* Day header */} {weekDates.map((date, i) => { const pd = parsedDates[i]; const isToday = date === today; return ( ); })} {parks.map((park, parkIdx) => { const parkData = data[park.id] ?? {}; return ( {/* Park name */} {/* Day cells */} {weekDates.map((date, i) => { const pd = parsedDates[i]; const isToday = date === today; const dayData = parkData[date]; if (!dayData) { // No data scraped yet return ( ); } // Treat open-but-no-hours the same as closed (stale data or missing hours) if (!dayData.isOpen || !dayData.hoursLabel) { return ( ); } // Open with confirmed hours return ( ); })} ); })}
{weekDates.map((date, i) => { const pd = parsedDates[i]; // Only render the month label on the first day of each new month (or the first column) const showMonth = i === 0 || pd.month !== parsedDates[i - 1].month; return ( {showMonth ? `${MONTHS[pd.month]} ${new Date(date + "T00:00:00").getFullYear() !== firstYear ? new Date(date + "T00:00:00").getFullYear() : ""}` : ""}
Park
{DOW[pd.dow]}
{pd.day}
{park.shortName}
{park.location.city}, {park.location.state}
Closed
{dayData.hoursLabel}
); } // ── Shared styles ────────────────────────────────────────────────────────── const thParkStyle: React.CSSProperties = { position: "sticky", left: 0, zIndex: 10, padding: "10px 12px", textAlign: "left", borderBottom: "1px solid var(--color-border)", background: "var(--color-bg)", verticalAlign: "bottom", }; const thDayStyle: React.CSSProperties = { padding: "10px 8px 8px", textAlign: "center", fontWeight: 400, borderBottom: "1px solid var(--color-border)", borderLeft: "1px solid var(--color-border)", verticalAlign: "bottom", }; const tdParkStyle: React.CSSProperties = { position: "sticky", left: 0, zIndex: 5, padding: "10px 12px", borderBottom: "1px solid var(--color-border)", borderRight: "1px solid var(--color-border)", whiteSpace: "nowrap", verticalAlign: "middle", }; const tdBase: React.CSSProperties = { padding: 0, textAlign: "center", verticalAlign: "middle", borderBottom: "1px solid var(--color-border)", borderLeft: "1px solid var(--color-border)", height: 52, };