brackt/app/components/sports/SportSeasonCard.tsx
2026-05-07 16:07:34 -07:00

168 lines
5.3 KiB
TypeScript

import { format } from "date-fns";
import { Link } from "react-router";
import { Badge } from "~/components/ui/badge";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Trophy, Target, Flag, Calendar, Users } from "lucide-react";
import { formatEventDate } from "~/lib/date-utils";
import type { UpcomingParticipantEvent } from "~/models/scoring-event";
import { SportIcon } from "~/components/SportIcon";
interface SportSeasonCardProps {
leagueId: string;
sportSeasonId: string;
sportName: string;
sportIconUrl?: string | null;
seasonName: string;
status: "upcoming" | "active" | "completed";
scoringPattern?: string | null;
upcomingParticipantEvents?: UpcomingParticipantEvent[];
}
const MAX_EVENTS_SHOWN = 3;
function EventLine({ event }: { event: UpcomingParticipantEvent }) {
const gameDate = event.earliestGameTime ? new Date(event.earliestGameTime) : null;
const dateStr = gameDate
? format(gameDate, "MMM d")
: formatEventDate(event.eventDate);
const isAllCompete = event.eventType !== "playoff_game";
const count = event.relevantParticipants.length;
const displayName = event.matchLabel
? `${event.name}${event.matchLabel}`
: event.name;
return (
<div className="flex items-center gap-2 text-sm">
<Calendar className="h-3.5 w-3.5 text-electric shrink-0" />
<span className="truncate">
<span className="font-medium" suppressHydrationWarning>{displayName}</span>
{dateStr && (
<span className="text-muted-foreground" suppressHydrationWarning>
{" — "}{dateStr}
</span>
)}
</span>
{isAllCompete && count > 1 ? (
<Badge variant="secondary" className="text-xs shrink-0 ml-auto">
<Users className="h-3 w-3 mr-1" />
{count}
</Badge>
) : (
event.relevantParticipants.slice(0, 2).map((p) => (
<Badge key={p.id} variant="secondary" className="text-xs shrink-0 ml-auto first:ml-auto [&+*]:ml-0">
{p.name}
</Badge>
))
)}
</div>
);
}
export function SportSeasonCard({
leagueId,
sportSeasonId,
sportName,
sportIconUrl,
seasonName,
status,
scoringPattern,
upcomingParticipantEvents = [],
}: SportSeasonCardProps) {
const getStatusBadge = () => {
switch (status) {
case "upcoming":
return (
<Badge variant="outline" className="bg-electric/15 text-electric border-electric/30">
<Flag className="mr-1 h-3 w-3" />
Upcoming
</Badge>
);
case "active":
return (
<Badge variant="outline" className="bg-emerald-500/15 text-emerald-400 border-emerald-500/30">
<Target className="mr-1 h-3 w-3" />
Active
</Badge>
);
case "completed":
return (
<Badge variant="outline" className="bg-muted text-muted-foreground border-border">
<Trophy className="mr-1 h-3 w-3" />
Completed
</Badge>
);
}
};
const getScoringPatternLabel = () => {
if (!scoringPattern) return null;
switch (scoringPattern) {
case "playoff_bracket":
return "Playoff Bracket";
case "season_standings":
return "Season Standings";
case "qualifying_points":
return "Qualifying Points";
default:
return null;
}
};
const scoringLabel = getScoringPatternLabel();
const isPlayoffType = scoringPattern === "playoff_bracket";
const shownEvents = upcomingParticipantEvents.slice(0, MAX_EVENTS_SHOWN);
const overflowCount = upcomingParticipantEvents.length - MAX_EVENTS_SHOWN;
return (
<Link to={`/leagues/${leagueId}/sports-seasons/${sportSeasonId}`}>
<Card className="hover:shadow-lg transition-shadow cursor-pointer h-full">
<CardHeader>
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<CardTitle className="mb-1 flex items-center gap-2 text-lg">
<SportIcon sportName={sportName} iconUrl={sportIconUrl} size="sm" decorative />
<span className="truncate">{sportName}</span>
</CardTitle>
<CardDescription className="truncate">{seasonName}</CardDescription>
</div>
{getStatusBadge()}
</div>
</CardHeader>
<CardContent>
<div className="space-y-2">
{scoringLabel && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
{isPlayoffType ? (
<Trophy className="h-4 w-4" />
) : (
<Target className="h-4 w-4" />
)}
<span>{scoringLabel}</span>
</div>
)}
{shownEvents.length > 0 && (
<div className="space-y-1.5 pt-0.5">
{shownEvents.map((event) => (
<EventLine key={event.id} event={event} />
))}
{overflowCount > 0 && (
<p className="text-xs text-muted-foreground pl-5">
+{overflowCount} more event{overflowCount > 1 ? "s" : ""}
</p>
)}
</div>
)}
</div>
</CardContent>
</Card>
</Link>
);
}