import { WeekCalendar } from "@/components/WeekCalendar"; import { MobileCardList } from "@/components/MobileCardList"; import { WeekNav } from "@/components/WeekNav"; import { Legend } from "@/components/Legend"; import { EmptyState } from "@/components/EmptyState"; import { PARKS, groupByRegion } from "@/lib/parks"; import { openDb, getDateRange } from "@/lib/db"; import { getTodayLocal } from "@/lib/env"; import { fetchLiveRides } from "@/lib/scrapers/queuetimes"; import { QUEUE_TIMES_IDS } from "@/lib/queue-times-map"; interface PageProps { searchParams: Promise<{ week?: string }>; } /** * Returns true when the current local time is within 1 hour before open * or 1 hour after close, based on a hoursLabel like "10am – 6pm". */ function isWithinOperatingWindow(hoursLabel: string): boolean { const m = hoursLabel.match( /^(\d+)(?::(\d+))?(am|pm)\s*[–-]\s*(\d+)(?::(\d+))?(am|pm)$/i ); if (!m) return true; // unparseable — show anyway const toMinutes = (h: string, min: string | undefined, period: string) => { let hours = parseInt(h, 10); const minutes = min ? parseInt(min, 10) : 0; if (period.toLowerCase() === "pm" && hours !== 12) hours += 12; if (period.toLowerCase() === "am" && hours === 12) hours = 0; return hours * 60 + minutes; }; const openMin = toMinutes(m[1], m[2], m[3]); const closeMin = toMinutes(m[4], m[5], m[6]); const now = new Date(); const nowMin = now.getHours() * 60 + now.getMinutes(); return nowMin >= openMin - 60 && nowMin <= closeMin + 60; } 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 ); // Fetch live ride counts for parks open today (cached 5 min via Queue-Times). // Only shown when the current time is within 1h before open to 1h after close. let rideCounts: Record = {}; if (weekDates.includes(today)) { const openTodayParks = PARKS.filter((p) => { const dayData = data[p.id]?.[today]; if (!dayData?.isOpen || !QUEUE_TIMES_IDS[p.id] || !dayData.hoursLabel) return false; return isWithinOperatingWindow(dayData.hoursLabel); }); const results = await Promise.all( openTodayParks.map(async (p) => { const result = await fetchLiveRides(QUEUE_TIMES_IDS[p.id], null, 300); const count = result ? result.rides.filter((r) => r.isOpen).length : 0; return [p.id, count] as [string, number]; }) ); rideCounts = Object.fromEntries(results.filter(([, count]) => count > 0)); } const visibleParks = PARKS.filter((park) => weekDates.some((date) => data[park.id]?.[date]?.isOpen) ); const grouped = groupByRegion(visibleParks); return (
{/* ── Header ─────────────────────────────────────────────────────────── */}
{/* Row 1: Title + park count */}
Thoosie Calendar {visibleParks.length} of {PARKS.length} parks open
{/* Row 2: Week nav + legend (legend hidden on mobile) */}
{/* ── Main content ───────────────────────────────────────────────────── */}
{scrapedCount === 0 ? ( ) : ( <> {/* Mobile: card list (hidden on lg+) */}
{/* Desktop: week table (hidden below lg) */}
)}
); }