Files
SixFlagsSuperCalendar/app/page.tsx
Josh Wright 32f0d05038
All checks were successful
Build and Deploy / Build & Push (push) Successful in 49s
feat: show open dot based on hours, Weather Delay when queue-times shows 0 rides
Park open indicator now derives from scheduled hours, not ride counts.
Parks with queue-times coverage but 0 open rides (e.g. storm) show a
"⛈ Weather Delay" notice instead of a ride count on both desktop and mobile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 14:56:54 -04:00

124 lines
4.5 KiB
TypeScript

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<string, number> = {};
let coasterCounts: Record<string, number> = {};
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 (
<HomePageClient
weekStart={weekStart}
weekDates={weekDates}
today={today}
isCurrentWeek={isCurrentWeek}
data={data}
rideCounts={rideCounts}
coasterCounts={coasterCounts}
openParkIds={openParkIds}
closingParkIds={closingParkIds}
weatherDelayParkIds={weatherDelayParkIds}
hasCoasterData={hasCoasterData}
scrapedCount={scrapedCount}
/>
);
}