Files
SixFlagsSuperCalendar/components/ParkCard.tsx
Josh Wright 32f0d05038
All checks were successful
Build and Deploy / Build & Push (push) Successful in 49s
feat: show open dot based on hours, Weather Delay when queue-times shows 0 rides
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>
2026-04-05 14:56:54 -04:00

185 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import type { Park } from "@/lib/scrapers/types";
import type { DayData } from "@/lib/db";
import { getTimezoneAbbr } from "@/lib/env";
interface ParkCardProps {
park: Park;
weekDates: string[]; // 7 dates YYYY-MM-DD, SunSat
parkData: Record<string, DayData>;
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, 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);
return (
<Link
href={`/park/${park.id}`}
data-park={park.name.toLowerCase()}
style={{ textDecoration: "none", display: "block" }}
>
<div className="park-card" style={{
background: "var(--color-surface)",
border: "1px solid var(--color-border)",
borderRadius: 12,
overflow: "hidden",
}}>
{/* ── Card header ───────────────────────────────────────────────────── */}
<div style={{
padding: "14px 16px 12px",
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
gap: 12,
}}>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: "0.95rem",
fontWeight: 600,
color: "var(--color-text)",
lineHeight: 1.2,
}}>
{park.name}
</div>
<div style={{
fontSize: "0.72rem",
color: "var(--color-text-muted)",
marginTop: 3,
}}>
{park.location.city}, {park.location.state}
</div>
</div>
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 5, flexShrink: 0 }}>
{isOpenToday ? (
<div style={{
background: isClosing ? "var(--color-closing-bg)" : "var(--color-open-bg)",
border: `1px solid ${isClosing ? "var(--color-closing-border)" : "var(--color-open-border)"}`,
borderRadius: 20,
padding: "4px 10px",
fontSize: "0.65rem",
fontWeight: 700,
color: isClosing ? "var(--color-closing-text)" : "var(--color-open-text)",
whiteSpace: "nowrap",
letterSpacing: "0.03em",
}}>
{isClosing ? "Closing" : "Open today"}
</div>
) : (
<div style={{
background: "transparent",
border: "1px solid var(--color-border)",
borderRadius: 20,
padding: "4px 10px",
fontSize: "0.65rem",
fontWeight: 500,
color: "var(--color-text-muted)",
whiteSpace: "nowrap",
}}>
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",
color: isClosing ? "var(--color-closing-hours)" : "var(--color-open-hours)",
fontWeight: 500,
textAlign: "right",
}}>
{openRideCount} {coastersOnly
? (openRideCount === 1 ? "coaster" : "coasters")
: (openRideCount === 1 ? "ride" : "rides")} operating
</div>
)}
</div>
</div>
{/* ── Open days list ────────────────────────────────────────────────── */}
{openDays.length > 0 && (
<div style={{ borderTop: "1px solid var(--color-border-subtle)" }}>
{openDays.map((date, i) => {
const dow = new Date(date + "T00:00:00").getDay();
const isToday = date === today;
const dayData = parkData[date];
const isPH = dayData.specialType === "passholder_preview";
const isLast = i === openDays.length - 1;
return (
<div
key={date}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "9px 16px",
background: isToday ? "var(--color-today-bg)" : "transparent",
borderBottom: isLast ? "none" : "1px solid var(--color-border-subtle)",
}}
>
<span style={{
fontSize: "0.82rem",
fontWeight: isToday ? 600 : 400,
color: isToday
? "var(--color-today-text)"
: "var(--color-text-secondary)",
}}>
{isToday ? "Today" : DOW[dow]}
</span>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
{isPH && (
<span style={{
fontSize: "0.65rem",
fontWeight: 700,
color: "var(--color-ph-label)",
letterSpacing: "0.04em",
textTransform: "uppercase",
}}>
Passholder
</span>
)}
<span style={{
fontSize: "0.82rem",
fontWeight: isToday ? 600 : 500,
color: isPH
? "var(--color-ph-hours)"
: isToday
? "var(--color-today-text)"
: "var(--color-open-hours)",
}}>
{dayData.hoursLabel}{" "}
<span style={{ fontSize: "0.68rem", fontWeight: 400, color: "var(--color-text-dim)" }}>
{tzAbbr}
</span>
</span>
</div>
</div>
);
})}
</div>
)}
</div>
</Link>
);
}