All checks were successful
Build and Deploy / Build & Push (push) Successful in 2m22s
Parks in the 1-hour buffer after scheduled close now show amber instead of green: the dot on the desktop calendar turns yellow, and the mobile card badge changes from "Open today" (green) to "Closing" (amber). - getOperatingStatus() replaces isWithinOperatingWindow's inline logic, returning "open" | "closing" | "closed"; isWithinOperatingWindow now delegates to it so all callers are unchanged - closingParkIds[] is computed server-side and threaded through HomePageClient → WeekCalendar/MobileCardList → ParkRow/ParkCard - New --color-closing-* CSS variables mirror the green palette in amber Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
3.9 KiB
TypeScript
112 lines
3.9 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[] = [];
|
|
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, p.timezone);
|
|
});
|
|
closingParkIds = openTodayParks
|
|
.filter((p) => {
|
|
const dayData = data[p.id]?.[today];
|
|
return dayData?.hoursLabel
|
|
? getOperatingStatus(dayData.hoursLabel, p.timezone) === "closing"
|
|
: false;
|
|
})
|
|
.map((p) => p.id);
|
|
const results = await Promise.all(
|
|
openTodayParks.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 : 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(({ rideCount }) => 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}
|
|
closingParkIds={closingParkIds}
|
|
hasCoasterData={hasCoasterData}
|
|
scrapedCount={scrapedCount}
|
|
/>
|
|
);
|
|
}
|