feat: add per-ride history charts with wait time and uptime tracking
Build and Deploy / Build & Push (push) Successful in 3m7s
Build and Deploy / Build & Push (push) Successful in 3m7s
Adds a cron-driven sampler that snapshots Queue-Times waits and Six Flags Fast Lane data every 5 minutes into a new ride_wait_samples table, and a clickable per-ride detail page at /park/[id]/ride/[slug] with Today / 7d / 30d Recharts views plus a 30d uptime pill. Rides are keyed by Queue-Times' stable qt_ride_id so renames don't fragment history. Samples store pre-bucketed local_date and local_time in the park's IANA timezone so aggregations are pure SQL and DST-safe. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import type { LiveRidesResult, LiveRide } from "@/lib/scrapers/queuetimes";
|
||||
import { slugifyRideName } from "@/lib/ride-slug";
|
||||
|
||||
interface LiveRidePanelProps {
|
||||
parkId: string;
|
||||
liveRides: LiveRidesResult;
|
||||
parkOpenToday: boolean;
|
||||
isWeatherDelay?: boolean;
|
||||
}
|
||||
|
||||
export function LiveRidePanel({ liveRides, parkOpenToday, isWeatherDelay }: LiveRidePanelProps) {
|
||||
export function LiveRidePanel({ parkId, liveRides, parkOpenToday, isWeatherDelay }: LiveRidePanelProps) {
|
||||
const { rides } = liveRides;
|
||||
const hasCoasters = rides.some((r) => r.isCoaster);
|
||||
const hasFastLane = rides.some((r) => r.hasFastLane);
|
||||
@@ -183,21 +186,22 @@ export function LiveRidePanel({ liveRides, parkOpenToday, isWeatherDelay }: Live
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
|
||||
gap: 6,
|
||||
}}>
|
||||
{openRides.map((ride) => <RideRow key={ride.name} ride={ride} fastLaneMode={fastLaneMode} />)}
|
||||
{closedRides.map((ride) => <RideRow key={ride.name} ride={ride} fastLaneMode={fastLaneMode} />)}
|
||||
{openRides.map((ride) => <RideRow key={ride.name} parkId={parkId} ride={ride} fastLaneMode={fastLaneMode} />)}
|
||||
{closedRides.map((ride) => <RideRow key={ride.name} parkId={parkId} ride={ride} fastLaneMode={fastLaneMode} />)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RideRow({ ride, fastLaneMode }: { ride: LiveRide; fastLaneMode: boolean }) {
|
||||
function RideRow({ parkId, ride, fastLaneMode }: { parkId: string; ride: LiveRide; fastLaneMode: boolean }) {
|
||||
const showWait = ride.isOpen && ride.waitMinutes > 0;
|
||||
const fastLaneActive = fastLaneMode && ride.hasFastLane;
|
||||
const flWait = ride.fastLaneMinutes ?? 0;
|
||||
const slug = ride.slug ?? slugifyRideName(ride.name);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
<Link href={`/park/${parkId}/ride/${slug}`} className="ride-row-link" style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
@@ -207,6 +211,9 @@ function RideRow({ ride, fastLaneMode }: { ride: LiveRide; fastLaneMode: boolean
|
||||
border: `1px solid ${ride.isOpen ? "var(--color-open-border)" : "var(--color-border)"}`,
|
||||
borderRadius: 8,
|
||||
opacity: ride.isOpen ? 1 : 0.6,
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
cursor: "pointer",
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 0 }}>
|
||||
<span style={{
|
||||
@@ -277,6 +284,6 @@ function RideRow({ ride, fastLaneMode }: { ride: LiveRide; fastLaneMode: boolean
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user