All checks were successful
Build and Deploy / Build & Push (push) Successful in 49s
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>
124 lines
4.5 KiB
TypeScript
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}
|
|
/>
|
|
);
|
|
}
|