Files
SixFlagsSuperCalendar/components/WeekNav.tsx
josh e48038c399
Some checks failed
Build and Deploy / Build & Push (push) Failing after 22s
feat: UI redesign with park detail pages and ride status
Visual overhaul:
- Warmer color system with amber accent for Today, better text hierarchy
- Row hover highlighting, sticky column shadow on horizontal scroll
- Closed cells replaced with dot (·) instead of "Closed" text
- Regional grouping (Northeast/Southeast/Midwest/Texas & South/West)
- Two-row header with park count badge and WeekNav on separate lines
- Amber "Today" button in WeekNav when off current week
- Mobile card layout (< 1024px) with 7-day grid per park; table on desktop
- Skeleton loading state via app/loading.tsx

Park detail pages (/park/[id]):
- Month calendar view with ← → navigation via ?month= param
- Live ride status fetched from Six Flags API (cached 1h)
- Ride hours only shown when they differ from park operating hours
- Fallback to nearest upcoming open day when today is dropped by API,
  including cross-month fallback for end-of-month edge case

Data layer:
- Park type gains region field; parks.ts exports groupByRegion()
- db.ts gains getParkMonthData() for single-park month queries
- sixflags.ts gains scrapeRidesForDay() returning RidesFetchResult
  with rides, dataDate, isExact, and parkHoursLabel

Removed: CalendarGrid.tsx, MonthNav.tsx (dead code)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:53:06 -04:00

140 lines
4.0 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.
"use client";
import { useRouter } from "next/navigation";
interface WeekNavProps {
weekStart: string; // YYYY-MM-DD (Sunday)
weekDates: string[]; // 7 dates YYYY-MM-DD
isCurrentWeek: boolean;
}
const MONTHS = [
"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec",
];
function formatLabel(dates: string[]): string {
const s = new Date(dates[0] + "T00:00:00");
const e = new Date(dates[6] + "T00:00:00");
if (s.getFullYear() === e.getFullYear() && s.getMonth() === e.getMonth()) {
return `${MONTHS[s.getMonth()]} ${s.getDate()}${e.getDate()}, ${s.getFullYear()}`;
}
const startStr = `${MONTHS[s.getMonth()]} ${s.getDate()}`;
const endStr = `${MONTHS[e.getMonth()]} ${e.getDate()}, ${e.getFullYear()}`;
return `${startStr} ${endStr}`;
}
function shiftWeek(weekStart: string, delta: number): string {
const d = new Date(weekStart + "T00:00:00");
d.setDate(d.getDate() + delta * 7);
return d.toISOString().slice(0, 10);
}
export function WeekNav({ weekStart, weekDates, isCurrentWeek }: WeekNavProps) {
const router = useRouter();
const nav = (delta: number) =>
router.push(`/?week=${shiftWeek(weekStart, delta)}`);
return (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<button
onClick={() => nav(-1)}
aria-label="Previous week"
style={navBtnStyle}
onMouseOver={(e) => Object.assign((e.target as HTMLElement).style, navBtnHover)}
onMouseOut={(e) => Object.assign((e.target as HTMLElement).style, navBtnStyle)}
>
</button>
{!isCurrentWeek && (
<button
onClick={() => router.push("/")}
aria-label="Jump to current week"
style={todayBtnStyle}
onMouseOver={(e) => Object.assign((e.target as HTMLElement).style, todayBtnHover)}
onMouseOut={(e) => Object.assign((e.target as HTMLElement).style, todayBtnStyle)}
>
Today
</button>
)}
<span style={{
fontSize: "1rem",
fontWeight: 600,
color: "var(--color-text)",
minWidth: 200,
textAlign: "center",
letterSpacing: "-0.01em",
fontVariantNumeric: "tabular-nums",
}}>
{formatLabel(weekDates)}
</span>
<button
onClick={() => nav(1)}
aria-label="Next week"
style={navBtnStyle}
onMouseOver={(e) => Object.assign((e.target as HTMLElement).style, navBtnHover)}
onMouseOut={(e) => Object.assign((e.target as HTMLElement).style, navBtnStyle)}
>
</button>
</div>
);
}
const navBtnStyle: React.CSSProperties = {
padding: "6px 14px",
borderRadius: 6,
border: "1px solid var(--color-border)",
background: "var(--color-surface)",
color: "var(--color-text-muted)",
cursor: "pointer",
fontSize: "1rem",
lineHeight: 1,
transition: "background 150ms ease, border-color 150ms ease, color 150ms ease",
};
const navBtnHover: React.CSSProperties = {
padding: "6px 14px",
borderRadius: 6,
border: "1px solid var(--color-text-dim)",
background: "var(--color-surface-2)",
color: "var(--color-text-secondary)",
cursor: "pointer",
fontSize: "1rem",
lineHeight: 1,
transition: "background 150ms ease, border-color 150ms ease, color 150ms ease",
};
const todayBtnStyle: React.CSSProperties = {
padding: "5px 12px",
borderRadius: 6,
border: "1px solid var(--color-accent-muted)",
background: "transparent",
color: "var(--color-accent)",
cursor: "pointer",
fontSize: "0.75rem",
fontWeight: 600,
letterSpacing: "0.04em",
textTransform: "uppercase",
lineHeight: 1,
transition: "background 150ms ease, color 150ms ease",
};
const todayBtnHover: React.CSSProperties = {
padding: "5px 12px",
borderRadius: 6,
border: "1px solid var(--color-accent-muted)",
background: "var(--color-accent-muted)",
color: "var(--color-accent-text)",
cursor: "pointer",
fontSize: "0.75rem",
fontWeight: 600,
letterSpacing: "0.04em",
textTransform: "uppercase",
lineHeight: 1,
transition: "background 150ms ease, color 150ms ease",
};