44d079efb9
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>
98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts";
|
|
|
|
export interface TodaySample {
|
|
recordedAt: string;
|
|
localTime: string;
|
|
isOpen: boolean;
|
|
waitMinutes: number | null;
|
|
fastLaneMinutes: number | null;
|
|
}
|
|
|
|
interface Props {
|
|
samples: TodaySample[];
|
|
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: TIME_FMT.format(new Date(s.recordedAt)),
|
|
wait: s.isOpen ? s.waitMinutes : null,
|
|
fl: s.isOpen ? s.fastLaneMinutes : null,
|
|
}));
|
|
|
|
// Show every Nth tick on the X axis so labels don't overlap.
|
|
const tickInterval = Math.max(1, Math.floor(data.length / 8));
|
|
|
|
return (
|
|
<div style={{ width: "100%", height: 280 }}>
|
|
<ResponsiveContainer>
|
|
<LineChart data={data} margin={{ top: 8, right: 16, left: 0, bottom: 4 }}>
|
|
<CartesianGrid stroke="#272727" strokeDasharray="3 3" vertical={false} />
|
|
<XAxis
|
|
dataKey="time"
|
|
stroke="#737373"
|
|
tick={{ fontSize: 11, fill: "#737373" }}
|
|
interval={tickInterval}
|
|
tickLine={false}
|
|
/>
|
|
<YAxis
|
|
stroke="#737373"
|
|
tick={{ fontSize: 11, fill: "#737373" }}
|
|
tickLine={false}
|
|
axisLine={false}
|
|
label={{ value: "min", position: "insideLeft", angle: -90, offset: 16, style: { fontSize: 10, fill: "#737373" } }}
|
|
/>
|
|
<Tooltip
|
|
contentStyle={{
|
|
background: "#1c1c1c",
|
|
border: "1px solid #333",
|
|
borderRadius: 6,
|
|
fontSize: "0.75rem",
|
|
color: "#f5f5f5",
|
|
}}
|
|
labelStyle={{ color: "#b0b0b0" }}
|
|
formatter={(value, name) => {
|
|
if (value === null || value === undefined) return ["—", name];
|
|
return [`${value} min`, name];
|
|
}}
|
|
/>
|
|
<Legend wrapperStyle={{ fontSize: "0.72rem", paddingTop: 4 }} />
|
|
<Line
|
|
name="Wait"
|
|
type="monotone"
|
|
dataKey="wait"
|
|
stroke="#4ade80"
|
|
strokeWidth={2}
|
|
dot={false}
|
|
connectNulls={false}
|
|
isAnimationActive={false}
|
|
/>
|
|
{hasFastLane && (
|
|
<Line
|
|
name="Fast Lane"
|
|
type="monotone"
|
|
dataKey="fl"
|
|
stroke="#ff4d8d"
|
|
strokeWidth={2}
|
|
dot={false}
|
|
connectNulls={false}
|
|
isAnimationActive={false}
|
|
/>
|
|
)}
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
);
|
|
}
|