feat: coaster filter toggle on homepage
All checks were successful
Build and Deploy / Build & Push (push) Successful in 1m14s

- 🎢 Coasters button in nav bar (URL-driven: ?coasters=1)
- When active, swaps ride counts for coaster counts per park
- Label switches between "X rides operating" / "X coasters operating"
- Arrow key navigation preserves coaster filter state
- Only shown when coaster data exists in park-meta

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 21:03:00 -04:00
parent f1fec2355c
commit 7456ead430
5 changed files with 78 additions and 16 deletions

View File

@@ -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<string, number> = {};
let coasterCounts: Record<string, number> = {};
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}
/>
<div className="hidden sm:flex">
<Legend />
@@ -157,7 +174,8 @@ export default async function HomePage({ searchParams }: PageProps) {
weekDates={weekDates}
data={data}
today={today}
rideCounts={rideCounts}
rideCounts={activeCounts}
coastersOnly={coastersOnly}
/>
</div>
@@ -168,7 +186,8 @@ export default async function HomePage({ searchParams }: PageProps) {
weekDates={weekDates}
data={data}
grouped={grouped}
rideCounts={rideCounts}
rideCounts={activeCounts}
coastersOnly={coastersOnly}
/>
</div>
</>