feat: show open dot based on hours, Weather Delay when queue-times shows 0 rides
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>
This commit is contained in:
Josh Wright
2026-04-05 14:56:54 -04:00
parent d84a15ad64
commit 32f0d05038
5 changed files with 61 additions and 10 deletions

View File

@@ -63,12 +63,16 @@ export default async function HomePage({ searchParams }: PageProps) {
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 || !QUEUE_TIMES_IDS[p.id] || !dayData.hoursLabel) return false;
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];
@@ -77,17 +81,23 @@ export default async function HomePage({ searchParams }: PageProps) {
: 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(
openTodayParks.map(async (p) => {
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 : 0;
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 > 0).map(({ id, rideCount }) => [id, rideCount])
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])
@@ -103,7 +113,9 @@ export default async function HomePage({ searchParams }: PageProps) {
data={data}
rideCounts={rideCounts}
coasterCounts={coasterCounts}
openParkIds={openParkIds}
closingParkIds={closingParkIds}
weatherDelayParkIds={weatherDelayParkIds}
hasCoasterData={hasCoasterData}
scrapedCount={scrapedCount}
/>

View File

@@ -50,7 +50,9 @@ interface HomePageClientProps {
data: Record<string, Record<string, DayData>>;
rideCounts: Record<string, number>;
coasterCounts: Record<string, number>;
openParkIds: string[];
closingParkIds: string[];
weatherDelayParkIds: string[];
hasCoasterData: boolean;
scrapedCount: number;
}
@@ -63,7 +65,9 @@ export function HomePageClient({
data,
rideCounts,
coasterCounts,
openParkIds,
closingParkIds,
weatherDelayParkIds,
hasCoasterData,
scrapedCount,
}: HomePageClientProps) {
@@ -231,7 +235,9 @@ export function HomePageClient({
today={today}
rideCounts={activeCounts}
coastersOnly={coastersOnly}
openParkIds={openParkIds}
closingParkIds={closingParkIds}
weatherDelayParkIds={weatherDelayParkIds}
/>
</div>
@@ -244,7 +250,9 @@ export function HomePageClient({
grouped={grouped}
rideCounts={activeCounts}
coastersOnly={coastersOnly}
openParkIds={openParkIds}
closingParkIds={closingParkIds}
weatherDelayParkIds={weatherDelayParkIds}
/>
</div>
</>

View File

@@ -10,10 +10,12 @@ interface MobileCardListProps {
today: string;
rideCounts?: Record<string, number>;
coastersOnly?: boolean;
openParkIds?: string[];
closingParkIds?: string[];
weatherDelayParkIds?: string[];
}
export function MobileCardList({ grouped, weekDates, data, today, rideCounts, coastersOnly, closingParkIds }: MobileCardListProps) {
export function MobileCardList({ grouped, weekDates, data, today, rideCounts, coastersOnly, openParkIds, closingParkIds, weatherDelayParkIds }: MobileCardListProps) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 20, paddingTop: 14 }}>
{Array.from(grouped.entries()).map(([region, parks]) => (
@@ -55,7 +57,9 @@ export function MobileCardList({ grouped, weekDates, data, today, rideCounts, co
today={today}
openRideCount={rideCounts?.[park.id]}
coastersOnly={coastersOnly}
isOpen={openParkIds?.includes(park.id)}
isClosing={closingParkIds?.includes(park.id)}
isWeatherDelay={weatherDelayParkIds?.includes(park.id)}
/>
))}
</div>

View File

@@ -10,12 +10,14 @@ interface ParkCardProps {
today: string;
openRideCount?: number;
coastersOnly?: boolean;
isOpen?: boolean;
isClosing?: boolean;
isWeatherDelay?: boolean;
}
const DOW = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
export function ParkCard({ park, weekDates, parkData, today, openRideCount, coastersOnly, isClosing }: ParkCardProps) {
export function ParkCard({ park, weekDates, parkData, today, openRideCount, coastersOnly, isOpen, isClosing, isWeatherDelay }: ParkCardProps) {
const openDays = weekDates.filter((d) => parkData[d]?.isOpen && parkData[d]?.hoursLabel);
const tzAbbr = getTimezoneAbbr(park.timezone);
const isOpenToday = openDays.includes(today);
@@ -87,6 +89,16 @@ export function ParkCard({ park, weekDates, parkData, today, openRideCount, coas
Closed today
</div>
)}
{isOpenToday && isWeatherDelay && (
<div style={{
fontSize: "0.65rem",
color: "var(--color-text-muted)",
fontWeight: 500,
textAlign: "right",
}}>
Weather Delay
</div>
)}
{isOpenToday && openRideCount !== undefined && (
<div style={{
fontSize: "0.65rem",

View File

@@ -12,7 +12,9 @@ interface WeekCalendarProps {
grouped?: Map<Region, Park[]>; // pre-grouped parks (if provided, renders region headers)
rideCounts?: Record<string, number>; // parkId → open ride/coaster count for today
coastersOnly?: boolean;
closingParkIds?: string[]; // parks in the post-close wind-down buffer
openParkIds?: string[];
closingParkIds?: string[];
weatherDelayParkIds?: string[];
}
const DOW = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
@@ -189,7 +191,9 @@ function ParkRow({
parkData,
rideCounts,
coastersOnly,
openParkIds,
closingParkIds,
weatherDelayParkIds,
}: {
park: Park;
parkIdx: number;
@@ -198,11 +202,15 @@ function ParkRow({
parkData: Record<string, DayData>;
rideCounts?: Record<string, number>;
coastersOnly?: boolean;
openParkIds?: string[];
closingParkIds?: string[];
weatherDelayParkIds?: string[];
}) {
const rowBg = parkIdx % 2 === 0 ? "var(--color-bg)" : "var(--color-surface)";
const tzAbbr = getTimezoneAbbr(park.timezone);
const isOpen = openParkIds?.includes(park.id) ?? false;
const isClosing = closingParkIds?.includes(park.id) ?? false;
const isWeatherDelay = weatherDelayParkIds?.includes(park.id) ?? false;
return (
<tr
className="park-row"
@@ -232,7 +240,7 @@ function ParkRow({
<span style={{ fontWeight: 500, fontSize: "0.85rem", lineHeight: 1.2, color: "var(--color-text)", whiteSpace: "nowrap" }}>
{park.name}
</span>
{rideCounts?.[park.id] !== undefined && (
{isOpen && (
<span style={{
width: 7,
height: 7,
@@ -249,7 +257,12 @@ function ParkRow({
{park.location.city}, {park.location.state}
</div>
</div>
{rideCounts?.[park.id] !== undefined && (
{isWeatherDelay && (
<div style={{ fontSize: "0.7rem", color: "var(--color-text-muted)", fontWeight: 500, textAlign: "right" }}>
Weather Delay
</div>
)}
{!isWeatherDelay && rideCounts?.[park.id] !== undefined && (
<div style={{ fontSize: "0.72rem", color: isClosing ? "var(--color-closing-hours)" : "var(--color-open-hours)", fontWeight: 600, textAlign: "right" }}>
{rideCounts[park.id]} {coastersOnly
? (rideCounts[park.id] === 1 ? "coaster" : "coasters")
@@ -271,7 +284,7 @@ function ParkRow({
);
}
export function WeekCalendar({ parks, weekDates, data, grouped, rideCounts, coastersOnly, closingParkIds }: WeekCalendarProps) {
export function WeekCalendar({ parks, weekDates, data, grouped, rideCounts, coastersOnly, openParkIds, closingParkIds, weatherDelayParkIds }: WeekCalendarProps) {
const today = getTodayLocal();
const parsedDates = weekDates.map(parseDate);
@@ -387,7 +400,9 @@ export function WeekCalendar({ parks, weekDates, data, grouped, rideCounts, coas
parkData={data[park.id] ?? {}}
rideCounts={rideCounts}
coastersOnly={coastersOnly}
openParkIds={openParkIds}
closingParkIds={closingParkIds}
weatherDelayParkIds={weatherDelayParkIds}
/>
))}
</Fragment>