import { Fragment } from "react"; import Link from "next/link"; import type { Park } from "@/lib/scrapers/types"; import type { DayData } from "@/lib/db"; import type { Region } from "@/lib/parks"; import { getTodayLocal, getTimezoneAbbr } from "@/lib/env"; interface WeekCalendarProps { parks: Park[]; weekDates: string[]; // 7 dates, YYYY-MM-DD, Sun–Sat data: Record>; // parkId → date → DayData grouped?: Map; // pre-grouped parks (if provided, renders region headers) rideCounts?: Record; // parkId → open ride/coaster count for today coastersOnly?: boolean; } 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, }; } function DayCell({ dayData, isWeekend, tzAbbr, }: { dayData: DayData | undefined; isWeekend: boolean; tzAbbr: string; }) { const base: React.CSSProperties = { padding: 0, textAlign: "center", verticalAlign: "middle", borderBottom: "1px solid var(--color-border)", borderLeft: "1px solid var(--color-border)", height: 56, background: isWeekend ? "var(--color-weekend-header)" : "transparent", transition: "background 120ms ease", }; if (!dayData) { return ( ); } if (!dayData.isOpen || !dayData.hoursLabel) { return ( · ); } if (dayData.specialType === "passholder_preview") { return (
Passholder {dayData.hoursLabel} {tzAbbr}
); } return (
{dayData.hoursLabel} {tzAbbr}
); } function RegionHeader({ region, colSpan }: { region: string; colSpan: number }) { return ( {region} ); } function ParkRow({ park, parkIdx, weekDates, parsedDates, parkData, rideCounts, coastersOnly, }: { park: Park; parkIdx: number; weekDates: string[]; parsedDates: ReturnType[]; parkData: Record; rideCounts?: Record; coastersOnly?: boolean; }) { const rowBg = parkIdx % 2 === 0 ? "var(--color-bg)" : "var(--color-surface)"; const tzAbbr = getTimezoneAbbr(park.timezone); return (
{park.name} {rideCounts?.[park.id] !== undefined && ( )}
{park.location.city}, {park.location.state}
{rideCounts?.[park.id] !== undefined && (
{rideCounts[park.id]} {coastersOnly ? (rideCounts[park.id] === 1 ? "coaster" : "coasters") : (rideCounts[park.id] === 1 ? "ride" : "rides")} operating
)} {weekDates.map((date, i) => ( ))} ); } export function WeekCalendar({ parks, weekDates, data, grouped, rideCounts, coastersOnly }: WeekCalendarProps) { const today = getTodayLocal(); const parsedDates = weekDates.map(parseDate); const firstMonth = parsedDates[0].month; const firstYear = new Date(weekDates[0] + "T00:00:00").getFullYear(); // Build ordered list of (region, parks[]) if grouped, otherwise treat all as one group const sections: Array<{ region: string | null; parks: Park[] }> = grouped ? Array.from(grouped.entries()).map(([region, ps]) => ({ region, parks: ps })) : [{ region: null, parks }]; const colSpan = weekDates.length + 1; // park col + 7 day cols return (
{weekDates.map((d) => ( ))} {/* Month header — only when week crosses a month boundary */} {parsedDates.some((d) => d.month !== firstMonth) && ( ); })} )} {/* Day header */} {weekDates.map((date, i) => { const pd = parsedDates[i]; const isToday = date === today; return ( ); })} {sections.map(({ region, parks: sectionParks }) => ( {region && ( )} {sectionParks.map((park, parkIdx) => ( ))} ))}
{weekDates.map((date, i) => { const pd = parsedDates[i]; 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}
); } // ── Shared styles ────────────────────────────────────────────────────────── const thParkStyle: React.CSSProperties = { position: "sticky", left: 0, zIndex: 10, padding: "10px 14px", 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", };