fix: render today's wait chart in viewer's local time + close stale live state
Build and Deploy / Build & Push (push) Successful in 1m8s
Build and Deploy / Build & Push (push) Successful in 1m8s
Two related polish fixes for the ride detail page: 1. Wait-time chart x-axis now uses Intl.DateTimeFormat with no timezone argument, so an Eastern-time user viewing a Pacific park sees ET on the axis. Backend now sends recorded_at (UTC) alongside local_time. 2. Ride-history endpoint now applies the same operating-window gate the /rides route uses. Queue-Times keeps reporting yesterday's last wait with isOpen=true overnight, which made the "Right now" pill show a live wait time when the park was actually closed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ const BACKEND_URL = process.env.BACKEND_URL ?? "http://localhost:3001";
|
||||
type Tab = "today" | "7d" | "30d";
|
||||
|
||||
interface TodaySample {
|
||||
recordedAt: string;
|
||||
localTime: string;
|
||||
isOpen: boolean;
|
||||
waitMinutes: number | null;
|
||||
|
||||
@@ -278,6 +278,7 @@ export function listRidesForPark(parkId: string): RideRow[] {
|
||||
}
|
||||
|
||||
export interface DailySample {
|
||||
recordedAt: string;
|
||||
localTime: string;
|
||||
isOpen: boolean;
|
||||
waitMinutes: number | null;
|
||||
@@ -291,18 +292,20 @@ export function getRideSamplesForDay(
|
||||
): DailySample[] {
|
||||
const rows = getDb()
|
||||
.prepare(
|
||||
`SELECT local_time, is_open, wait_minutes, fast_lane_minutes
|
||||
`SELECT recorded_at, local_time, is_open, wait_minutes, fast_lane_minutes
|
||||
FROM ride_wait_samples
|
||||
WHERE park_id = ? AND qt_ride_id = ? AND local_date = ?
|
||||
ORDER BY local_time`,
|
||||
ORDER BY recorded_at`,
|
||||
)
|
||||
.all(parkId, qtRideId, localDate) as {
|
||||
recorded_at: string;
|
||||
local_time: string;
|
||||
is_open: number;
|
||||
wait_minutes: number | null;
|
||||
fast_lane_minutes: number | null;
|
||||
}[];
|
||||
return rows.map((r) => ({
|
||||
recordedAt: r.recorded_at,
|
||||
localTime: r.local_time,
|
||||
isOpen: r.is_open === 1,
|
||||
waitMinutes: r.wait_minutes,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import { Hono } from "hono";
|
||||
import { PARK_MAP } from "../../../lib/parks";
|
||||
import {
|
||||
getDayData,
|
||||
getRideBySlug,
|
||||
getRideSamplesForDay,
|
||||
getRideDailyAggregates,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
import { liveRidesCache, fastLaneCache } from "../services/live-cache";
|
||||
import { slugifyRideName } from "../../../lib/ride-slug";
|
||||
import { lookupFastLane } from "../../../lib/scrapers/sixflags-waittimes";
|
||||
import { getTodayLocal, isWithinOperatingWindow } from "../../../lib/env";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
@@ -72,6 +74,15 @@ app.get("/:parkId/rides/:slug", (c) => {
|
||||
const fastLaneCacheEntry = fastLaneCache.get(parkId);
|
||||
const flMatch = liveMatch && fastLaneCacheEntry ? lookupFastLane(liveMatch.name, fastLaneCacheEntry) : null;
|
||||
|
||||
// Operating-window gate. Queue-Times keeps reporting yesterday's last wait
|
||||
// with isOpen=true overnight, so we override to closed when we're outside
|
||||
// the park's hours — same behaviour as the /rides route.
|
||||
const todayData = getDayData(parkId, getTodayLocal());
|
||||
const withinWindow = todayData?.hoursLabel
|
||||
? isWithinOperatingWindow(todayData.hoursLabel, park.timezone)
|
||||
: false;
|
||||
const liveIsOpen = Boolean(liveMatch?.isOpen) && withinWindow;
|
||||
|
||||
c.header("Cache-Control", "public, max-age=60, stale-while-revalidate=120");
|
||||
return c.json({
|
||||
park: {
|
||||
@@ -91,10 +102,10 @@ app.get("/:parkId/rides/:slug", (c) => {
|
||||
},
|
||||
live: liveMatch
|
||||
? {
|
||||
isOpen: liveMatch.isOpen,
|
||||
waitMinutes: liveMatch.waitMinutes,
|
||||
isOpen: liveIsOpen,
|
||||
waitMinutes: liveIsOpen ? liveMatch.waitMinutes : 0,
|
||||
hasFastLane: Boolean(flMatch?.hasFastLane),
|
||||
fastLaneMinutes: liveMatch.isOpen ? (flMatch?.fastLaneMinutes ?? null) : null,
|
||||
fastLaneMinutes: liveIsOpen ? (flMatch?.fastLaneMinutes ?? null) : null,
|
||||
lastUpdated: liveMatch.lastUpdated,
|
||||
}
|
||||
: null,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts";
|
||||
|
||||
export interface TodaySample {
|
||||
recordedAt: string;
|
||||
localTime: string;
|
||||
isOpen: boolean;
|
||||
waitMinutes: number | null;
|
||||
@@ -14,10 +15,18 @@ interface Props {
|
||||
hasFastLane: boolean;
|
||||
}
|
||||
|
||||
const TIME_FMT = new Intl.DateTimeFormat([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
});
|
||||
|
||||
export default function WaitTimeTodayChart({ samples, hasFastLane }: Props) {
|
||||
// Map samples: closed periods → null so Recharts breaks the line.
|
||||
// X-axis time is rendered in the viewer's local timezone (Intl with no
|
||||
// tz arg) so an Eastern-time user sees ET regardless of which park.
|
||||
const data = samples.map((s) => ({
|
||||
time: s.localTime,
|
||||
time: TIME_FMT.format(new Date(s.recordedAt)),
|
||||
wait: s.isOpen ? s.waitMinutes : null,
|
||||
fl: s.isOpen ? s.fastLaneMinutes : null,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user