diff --git a/app/page.tsx b/app/page.tsx index 2e0c4c1..f0fa4dc 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,9 +8,10 @@ import { openDb, getDateRange } from "@/lib/db"; import { getTodayLocal, isWithinOperatingWindow } 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 }>; + searchParams: Promise<{ week?: string; coasters?: string }>; } function getWeekStart(param: string | undefined): string { @@ -45,6 +46,7 @@ function getCurrentWeekStart(): string { export default async function HomePage({ searchParams }: PageProps) { const params = await searchParams; const weekStart = getWeekStart(params.week); + const coastersOnly = params.coasters === "1"; const weekDates = getWeekDates(weekStart); const endDate = weekDates[6]; const today = getTodayLocal(); @@ -61,7 +63,11 @@ export default async function HomePage({ searchParams }: PageProps) { // 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. + const parkMeta = readParkMeta(); + const hasCoasterData = PARKS.some((p) => (parkMeta[p.id]?.coasters.length ?? 0) > 0); + let rideCounts: Record = {}; + let coasterCounts: Record = {}; if (weekDates.includes(today)) { const openTodayParks = PARKS.filter((p) => { const dayData = data[p.id]?.[today]; @@ -70,14 +76,23 @@ export default async function HomePage({ searchParams }: PageProps) { }); 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]; + 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 : 0; + const coasterCount = result ? result.rides.filter((r) => r.isOpen && r.isCoaster).length : 0; + return { id: p.id, rideCount, coasterCount }; }) ); - rideCounts = Object.fromEntries(results.filter(([, count]) => count > 0)); + rideCounts = Object.fromEntries( + results.filter(({ rideCount }) => rideCount > 0).map(({ id, rideCount }) => [id, rideCount]) + ); + coasterCounts = Object.fromEntries( + results.filter(({ coasterCount }) => coasterCount > 0).map(({ id, coasterCount }) => [id, coasterCount]) + ); } + const activeCounts = coastersOnly ? coasterCounts : rideCounts; + const visibleParks = PARKS.filter((park) => weekDates.some((date) => data[park.id]?.[date]?.isOpen) ); @@ -137,6 +152,8 @@ export default async function HomePage({ searchParams }: PageProps) { weekStart={weekStart} weekDates={weekDates} isCurrentWeek={isCurrentWeek} + coastersOnly={coastersOnly} + hasCoasterData={hasCoasterData} />
@@ -157,7 +174,8 @@ export default async function HomePage({ searchParams }: PageProps) { weekDates={weekDates} data={data} today={today} - rideCounts={rideCounts} + rideCounts={activeCounts} + coastersOnly={coastersOnly} />
@@ -168,7 +186,8 @@ export default async function HomePage({ searchParams }: PageProps) { weekDates={weekDates} data={data} grouped={grouped} - rideCounts={rideCounts} + rideCounts={activeCounts} + coastersOnly={coastersOnly} /> diff --git a/components/MobileCardList.tsx b/components/MobileCardList.tsx index b8137f4..ffb57bb 100644 --- a/components/MobileCardList.tsx +++ b/components/MobileCardList.tsx @@ -9,9 +9,10 @@ interface MobileCardListProps { data: Record>; today: string; rideCounts?: Record; + coastersOnly?: boolean; } -export function MobileCardList({ grouped, weekDates, data, today, rideCounts }: MobileCardListProps) { +export function MobileCardList({ grouped, weekDates, data, today, rideCounts, coastersOnly }: MobileCardListProps) { return (
{Array.from(grouped.entries()).map(([region, parks]) => ( @@ -52,6 +53,7 @@ export function MobileCardList({ grouped, weekDates, data, today, rideCounts }: parkData={data[park.id] ?? {}} today={today} openRideCount={rideCounts?.[park.id]} + coastersOnly={coastersOnly} /> ))}
diff --git a/components/ParkCard.tsx b/components/ParkCard.tsx index 698a70f..b3e0fcf 100644 --- a/components/ParkCard.tsx +++ b/components/ParkCard.tsx @@ -8,11 +8,12 @@ interface ParkCardProps { parkData: Record; today: string; openRideCount?: number; + coastersOnly?: boolean; } const DOW = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; -export function ParkCard({ park, weekDates, parkData, today, openRideCount }: ParkCardProps) { +export function ParkCard({ park, weekDates, parkData, today, openRideCount, coastersOnly }: ParkCardProps) { const openDays = weekDates.filter((d) => parkData[d]?.isOpen && parkData[d]?.hoursLabel); const isOpenToday = openDays.includes(today); @@ -90,7 +91,9 @@ export function ParkCard({ park, weekDates, parkData, today, openRideCount }: Pa fontWeight: 500, textAlign: "right", }}> - {openRideCount} {openRideCount === 1 ? "ride" : "rides"} operating + {openRideCount} {coastersOnly + ? (openRideCount === 1 ? "coaster" : "coasters") + : (openRideCount === 1 ? "ride" : "rides")} operating )} diff --git a/components/WeekCalendar.tsx b/components/WeekCalendar.tsx index 8a3b8f3..678de2a 100644 --- a/components/WeekCalendar.tsx +++ b/components/WeekCalendar.tsx @@ -10,7 +10,8 @@ interface WeekCalendarProps { 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 count for today + rideCounts?: Record; // parkId → open ride/coaster count for today + coastersOnly?: boolean; } const DOW = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; @@ -165,6 +166,7 @@ function ParkRow({ parsedDates, parkData, rideCounts, + coastersOnly, }: { park: Park; parkIdx: number; @@ -172,6 +174,7 @@ function ParkRow({ parsedDates: ReturnType[]; parkData: Record; rideCounts?: Record; + coastersOnly?: boolean; }) { const rowBg = parkIdx % 2 === 0 ? "var(--color-bg)" : "var(--color-surface)"; return ( @@ -221,7 +224,9 @@ function ParkRow({ {rideCounts?.[park.id] !== undefined && (
- {rideCounts[park.id]} {rideCounts[park.id] === 1 ? "ride" : "rides"} operating + {rideCounts[park.id]} {coastersOnly + ? (rideCounts[park.id] === 1 ? "coaster" : "coasters") + : (rideCounts[park.id] === 1 ? "ride" : "rides")} operating
)} @@ -238,7 +243,7 @@ function ParkRow({ ); } -export function WeekCalendar({ parks, weekDates, data, grouped, rideCounts }: WeekCalendarProps) { +export function WeekCalendar({ parks, weekDates, data, grouped, rideCounts, coastersOnly }: WeekCalendarProps) { const today = getTodayLocal(); const parsedDates = weekDates.map(parseDate); @@ -353,6 +358,7 @@ export function WeekCalendar({ parks, weekDates, data, grouped, rideCounts }: We parsedDates={parsedDates} parkData={data[park.id] ?? {}} rideCounts={rideCounts} + coastersOnly={coastersOnly} /> ))} diff --git a/components/WeekNav.tsx b/components/WeekNav.tsx index ebd25b8..0548e3e 100644 --- a/components/WeekNav.tsx +++ b/components/WeekNav.tsx @@ -7,6 +7,8 @@ interface WeekNavProps { weekStart: string; // YYYY-MM-DD (Sunday) weekDates: string[]; // 7 dates YYYY-MM-DD isCurrentWeek: boolean; + coastersOnly: boolean; + hasCoasterData: boolean; } const MONTHS = [ @@ -31,10 +33,16 @@ function shiftWeek(weekStart: string, delta: number): string { return d.toISOString().slice(0, 10); } -export function WeekNav({ weekStart, weekDates, isCurrentWeek }: WeekNavProps) { +export function WeekNav({ weekStart, weekDates, isCurrentWeek, coastersOnly, hasCoasterData }: WeekNavProps) { const router = useRouter(); - const nav = (delta: number) => - router.push(`/?week=${shiftWeek(weekStart, delta)}`); + const weekParam = `week=${weekStart}`; + const nav = (delta: number) => { + const base = `/?week=${shiftWeek(weekStart, delta)}`; + router.push(coastersOnly ? `${base}&coasters=1` : base); + }; + const toggleCoasters = () => { + router.push(coastersOnly ? `/?${weekParam}` : `/?${weekParam}&coasters=1`); + }; useEffect(() => { const onKey = (e: KeyboardEvent) => { @@ -91,6 +99,30 @@ export function WeekNav({ weekStart, weekDates, isCurrentWeek }: WeekNavProps) { > → + + {hasCoasterData && ( + + )} ); }