import { HomePageClient } from "@/components/HomePageClient"; import { PARKS } from "@/lib/parks"; import { openDb, getDateRange } from "@/lib/db"; import { getTodayLocal, isWithinOperatingWindow, getOperatingStatus } from "@/lib/env"; import { fetchLiveRides } from "@/lib/scrapers/queuetimes"; import { QUEUE_TIMES_IDS } from "@/lib/queue-times-map"; import { readParkMeta, getCoasterSet } from "@/lib/park-meta"; interface PageProps { searchParams: Promise<{ week?: string }>; } function getWeekStart(param: string | undefined): string { if (param && /^\d{4}-\d{2}-\d{2}$/.test(param)) { const d = new Date(param + "T00:00:00"); if (!isNaN(d.getTime())) { d.setDate(d.getDate() - d.getDay()); return d.toISOString().slice(0, 10); } } const todayIso = getTodayLocal(); const d = new Date(todayIso + "T00:00:00"); d.setDate(d.getDate() - d.getDay()); return d.toISOString().slice(0, 10); } function getWeekDates(sundayIso: string): string[] { return Array.from({ length: 7 }, (_, i) => { const d = new Date(sundayIso + "T00:00:00"); d.setDate(d.getDate() + i); return d.toISOString().slice(0, 10); }); } function getCurrentWeekStart(): string { const todayIso = getTodayLocal(); const d = new Date(todayIso + "T00:00:00"); d.setDate(d.getDate() - d.getDay()); return d.toISOString().slice(0, 10); } export default async function HomePage({ searchParams }: PageProps) { const params = await searchParams; const weekStart = getWeekStart(params.week); const weekDates = getWeekDates(weekStart); const endDate = weekDates[6]; const today = getTodayLocal(); const isCurrentWeek = weekStart === getCurrentWeekStart(); const db = openDb(); const data = getDateRange(db, weekStart, endDate); db.close(); const scrapedCount = Object.values(data).reduce( (sum, parkData) => sum + Object.keys(parkData).length, 0 ); // Always fetch both ride and coaster counts — the client decides which to display. const parkMeta = readParkMeta(); const hasCoasterData = PARKS.some((p) => (parkMeta[p.id]?.coasters.length ?? 0) > 0); let rideCounts: Record = {}; let coasterCounts: Record = {}; let closingParkIds: string[] = []; let openParkIds: string[] = []; let weatherDelayParkIds: string[] = []; if (weekDates.includes(today)) { // Parks within operating hours right now (for open dot — independent of ride counts) const openTodayParks = PARKS.filter((p) => { const dayData = data[p.id]?.[today]; if (!dayData?.isOpen || !dayData.hoursLabel) return false; return isWithinOperatingWindow(dayData.hoursLabel, p.timezone); }); openParkIds = openTodayParks.map((p) => p.id); closingParkIds = openTodayParks .filter((p) => { const dayData = data[p.id]?.[today]; return dayData?.hoursLabel ? getOperatingStatus(dayData.hoursLabel, p.timezone) === "closing" : false; }) .map((p) => p.id); // Only fetch ride counts for parks that have queue-times coverage const trackedParks = openTodayParks.filter((p) => QUEUE_TIMES_IDS[p.id]); const results = await Promise.all( trackedParks.map(async (p) => { const coasterSet = getCoasterSet(p.id, parkMeta); const result = await fetchLiveRides(QUEUE_TIMES_IDS[p.id], coasterSet, 300); const rideCount = result ? result.rides.filter((r) => r.isOpen).length : null; const coasterCount = result ? result.rides.filter((r) => r.isOpen && r.isCoaster).length : 0; return { id: p.id, rideCount, coasterCount }; }) ); // Parks with queue-times coverage but 0 open rides = likely weather delay weatherDelayParkIds = results .filter(({ rideCount }) => rideCount === 0) .map(({ id }) => id); rideCounts = Object.fromEntries( results.filter(({ rideCount }) => rideCount != null && rideCount > 0).map(({ id, rideCount }) => [id, rideCount!]) ); coasterCounts = Object.fromEntries( results.filter(({ coasterCount }) => coasterCount > 0).map(({ id, coasterCount }) => [id, coasterCount]) ); } return ( ); }