import Link from "next/link"; import { notFound } from "next/navigation"; import { PARK_MAP } from "@/lib/parks"; import { openDb, getParkMonthData, getApiId } from "@/lib/db"; import { scrapeRidesForDay } from "@/lib/scrapers/sixflags"; import { fetchLiveRides } from "@/lib/scrapers/queuetimes"; import { QUEUE_TIMES_IDS } from "@/lib/queue-times-map"; import { readParkMeta, getCoasterSet } from "@/lib/park-meta"; import { ParkMonthCalendar } from "@/components/ParkMonthCalendar"; import { LiveRidePanel } from "@/components/LiveRidePanel"; import type { RideStatus, RidesFetchResult } from "@/lib/scrapers/sixflags"; import type { LiveRidesResult } from "@/lib/scrapers/queuetimes"; // used as prop type below interface PageProps { params: Promise<{ id: string }>; searchParams: Promise<{ month?: string }>; } function parseMonthParam(param: string | undefined): { year: number; month: number } { if (param && /^\d{4}-\d{2}$/.test(param)) { const [y, m] = param.split("-").map(Number); if (y >= 2020 && y <= 2030 && m >= 1 && m <= 12) { return { year: y, month: m }; } } const now = new Date(); return { year: now.getFullYear(), month: now.getMonth() + 1 }; } export default async function ParkPage({ params, searchParams }: PageProps) { const { id } = await params; const { month: monthParam } = await searchParams; const park = PARK_MAP.get(id); if (!park) notFound(); const today = new Date().toISOString().slice(0, 10); const { year, month } = parseMonthParam(monthParam); const db = openDb(); const monthData = getParkMonthData(db, id, year, month); const apiId = getApiId(db, id); db.close(); const todayData = monthData[today]; const parkOpenToday = todayData?.isOpen && todayData?.hoursLabel; // ── Ride data: try live Queue-Times first, fall back to schedule ────────── const queueTimesId = QUEUE_TIMES_IDS[id]; const parkMeta = readParkMeta(); const coasterSet = getCoasterSet(id, parkMeta); let liveRides: LiveRidesResult | null = null; let ridesResult: RidesFetchResult | null = null; if (queueTimesId) { liveRides = await fetchLiveRides(queueTimesId, coasterSet); } // Only hit the schedule API as a fallback when live data is unavailable if (!liveRides && apiId !== null) { // Note: the API drops today's date from its response (only returns future dates), // so scrapeRidesForDay may fall back to the nearest upcoming date. ridesResult = await scrapeRidesForDay(apiId, today); } return (
{/* ── Header ─────────────────────────────────────────────────────────── */}
← Calendar
{park.name} {park.location.city}, {park.location.state}
{/* ── Month Calendar ───────────────────────────────────────────────── */}
{/* ── Ride Status ─────────────────────────────────────────────────── */}
Rides {liveRides ? ( ) : ridesResult && !ridesResult.isExact ? ( {formatShortDate(ridesResult.dataDate)} ) : ( Today )} {liveRides ? ( ) : ( )}
); } // ── Helpers ──────────────────────────────────────────────────────────────── function formatShortDate(iso: string): string { return new Date(iso + "T00:00:00").toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric", }); } // ── Sub-components ───────────────────────────────────────────────────────── function SectionHeading({ children }: { children: React.ReactNode }) { return (

{children}

); } function LiveBadge() { return ( Live ); } // ── Schedule ride list (Six Flags operating-hours API fallback) ──────────── function RideList({ ridesResult, parkOpenToday, apiIdMissing, }: { ridesResult: RidesFetchResult | null; parkOpenToday: boolean; apiIdMissing: boolean; }) { if (apiIdMissing) { return ( Park API ID not discovered yet. Run{" "} npm run discover {" "} to enable ride data. ); } if (!parkOpenToday) { return Park is closed today — no ride schedule available.; } if (!ridesResult || ridesResult.rides.length === 0) { return Ride schedule is not yet available from the API.; } const { rides, isExact, dataDate, parkHoursLabel } = ridesResult; const openRides = rides.filter((r) => r.isOpen); const closedRides = rides.filter((r) => !r.isOpen); return (
{/* Summary badge row */}
{openRides.length} open
{closedRides.length > 0 && (
{closedRides.length} closed / unscheduled
)} {!isExact && ( Showing {formatShortDate(dataDate)} — live schedule updates daily )}
{/* Two-column grid */}
{openRides.map((ride) => )} {closedRides.map((ride) => )}
); } function RideRow({ ride, parkHoursLabel }: { ride: RideStatus; parkHoursLabel?: string }) { const showHours = ride.isOpen && ride.hoursLabel && ride.hoursLabel !== parkHoursLabel; return (
{ride.name}
{showHours && ( {ride.hoursLabel} )}
); } function Callout({ children }: { children: React.ReactNode }) { return (
{children}
); }