import { format } from "date-fns"; import { Calendar, ChevronRight, Users } from "lucide-react"; import { useState, useEffect } from "react"; import { Link } from "react-router"; import { Badge } from "~/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle, } from "~/components/ui/card"; import { formatEventDate } from "~/lib/date-utils"; import type { UpcomingParticipantEvent } from "~/models/scoring-event"; import { isBracketMajor } from "~/lib/event-utils"; export interface CalendarPanelEvent extends UpcomingParticipantEvent { sportName: string; sportSeasonName: string; /** Present when showing events across multiple leagues (home page) */ leagueName?: string; leagueId?: string; sportsSeasonPageUrl?: string; /** Deep link to the event detail page. Preferred over sportsSeasonPageUrl when set. */ eventDetailUrl?: string; } interface Props { events: CalendarPanelEvent[]; /** When true, renders a league name badge on each row */ showLeague?: boolean; /** If provided, only the first N events are rendered */ limit?: number; /** If provided and events.length > limit, renders a "View all" footer link */ viewAllUrl?: string; /** Override the default empty state message */ emptyMessage?: string; } function ParticipantList({ participants }: { participants: Array<{ id: string; name: string }> }) { const MAX_SHOWN = 3; const shown = participants.slice(0, MAX_SHOWN); const overflow = participants.length - MAX_SHOWN; return ( {shown.map((p) => ( {p.name} ))} {overflow > 0 && ( +{overflow} more )} ); } function EventRow({ event, showLeague }: { event: CalendarPanelEvent; showLeague: boolean }) { // Prefer game-level scheduledAt over event-level eventDate for display const gameDate = event.earliestGameTime ? new Date(event.earliestGameTime) : null; const dateStr = gameDate ? format(gameDate, "MMM d") : formatEventDate(event.eventDate); const participantCount = event.relevantParticipants.length; const isAllCompete = event.eventType !== "playoff_game" && event.eventType !== "group_stage_match" && !isBracketMajor(event.simulatorType); const displayName = event.matchLabel ? `${event.name} — ${event.matchLabel}` : event.name; const content = (
{/* Date pill */}
{dateStr ?? "TBD"} {gameDate && (

{gameDate.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit", })}

)}
{/* Event details */}
{displayName} {showLeague && event.leagueName && ( {event.leagueName} )}
{event.sportName} · {event.sportSeasonName}
{isAllCompete && participantCount > 1 ? ( {participantCount} of your picks ) : ( )}
); const linkUrl = event.eventDetailUrl ?? event.sportsSeasonPageUrl; if (linkUrl) { return ( {content} ); } return content; } export function UpcomingCalendarPanel({ events, showLeague = false, limit, viewAllUrl, emptyMessage }: Props) { // Filter out past events client-side using local timezone. Server sends events // starting from yesterday UTC as a buffer; useEffect trims to today-and-forward // after hydration so the correct local date is used. const [localFilteredEvents, setLocalFilteredEvents] = useState(events); useEffect(() => { const todayStr = new Intl.DateTimeFormat("en-CA").format(new Date()); setLocalFilteredEvents(events.filter((e) => (e.earliestGameTime ?? e.eventDate ?? "9999-12-31") >= todayStr)); }, [events]); const displayedEvents = limit !== undefined ? localFilteredEvents.slice(0, limit) : localFilteredEvents; const hiddenCount = limit !== undefined ? Math.max(0, localFilteredEvents.length - limit) : 0; return ( Upcoming Events {localFilteredEvents.length === 0 ? (

{emptyMessage ?? "No upcoming events in the next 30 days."}

) : (
{displayedEvents.map((event) => ( ))} {viewAllUrl && (
{hiddenCount > 0 ? `View all ${localFilteredEvents.length} upcoming events` : "View all upcoming events"}
)}
)}
); }