162 lines
5 KiB
TypeScript
162 lines
5 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";
|
|
|
|
interface SportSeasonCardProps {
|
|
leagueId: string;
|
|
sportSeasonId: string;
|
|
sportName: string;
|
|
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,
|
|
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="text-lg mb-1">{sportName}</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>
|
|
);
|
|
}
|