import { ListOrdered } from "lucide-react"; import { Link } from "react-router"; import { Button } from "~/components/ui/button"; import { Card, CardContent, CardHeader } from "~/components/ui/card"; import { GradientIcon } from "~/components/ui/GradientIcon"; import { TeamAvatar } from "~/components/TeamAvatar"; import type { AvatarData, RawFlagConfig } from "~/lib/flag-types"; import { StatColumn, StatDivider, DeltaBadge, RankingDisplay } from "./StatHelpers"; /** Mirrors PointsDisplay gating: only project while the team still has participants live. */ function hasProjection(entry: StandingsPreviewEntry): boolean { return ( entry.projectedPoints !== null && entry.projectedPoints !== undefined && entry.participantsRemaining !== undefined && entry.participantsRemaining > 0 ); } // ─── Row styles ─────────────────────────────────────────────────────────────── const ROW_RANK_CLASSES: Record = { 1: "bg-yellow-500/10 hover:bg-yellow-500/15", 2: "bg-white/[0.14] hover:bg-white/[0.18]", 3: "bg-orange-600/[0.08] hover:bg-orange-600/[0.12]", }; function rowClasses(currentRank: number | undefined, hasHref: boolean): string { const podium = currentRank !== undefined ? ROW_RANK_CLASSES[currentRank] : undefined; const base = podium ?? `bg-white/[0.04] ${hasHref ? "hover:bg-white/[0.07]" : ""}`; return `flex flex-col sm:flex-row sm:items-center gap-0 rounded-lg px-3 py-3 sm:px-5 sm:py-4 transition-colors ${base}`; } // ─── Row content ────────────────────────────────────────────────────────────── function RowContent({ entry, showProjected, }: { entry: StandingsPreviewEntry; showProjected: boolean; }) { // Only reserve the delta line when this row actually changed. A row with no rank // or point movement drops the line entirely (no "—" placeholders); rows that moved // keep aligned columns by showing "—" for whichever stat didn't change. const rowHasChange = (entry.rankChange !== undefined && entry.rankChange !== 0) || (entry.pointChange !== undefined && entry.pointChange !== 0); return ( <> {/* Left: avatar + name */}

{entry.teamName}

{entry.ownerName && (

{entry.ownerName}

)}
{/* Right: stats — second row on mobile */}
{showProjected && } {showProjected && ( {Math.round(entry.projectedPoints as number).toLocaleString("en-US")} ) : ( ) } /> )} {Math.round(entry.points).toLocaleString("en-US")} } delta={ entry.pointChange !== undefined && entry.pointChange !== 0 ? ( ) : undefined } />
); } // ─── Public API ─────────────────────────────────────────────────────────────── export interface StandingsPreviewEntry { teamId: string; teamName: string; ownerName?: string | null; logoUrl?: string | null; flagConfig?: RawFlagConfig | null; avatarType?: string | null; ownerAvatarData?: AvatarData | null; /** Pre-computed display rank. T prefix (if any) is stripped at render time. */ displayRank: string | number; /** Numeric rank used to select the gold/silver/bronze row tint (1–3 only). */ currentRank?: number; points: number; href?: string; /** Positive = moved up, negative = moved down. From TeamStanding.rankChange. */ rankChange?: number; /** 7-day point delta. From TeamStanding.sevenDayPointChange. */ pointChange?: number; /** Projected final points. From TeamStanding.projectedPoints. */ projectedPoints?: number | null; /** Live participants left; projection only shown while > 0. */ participantsRemaining?: number; } export interface StandingsPreviewProps { entries: StandingsPreviewEntry[]; description?: string; fullStandingsHref?: string; /** When true, adds a "Projected" column (projected final points). Default false. */ showProjected?: boolean; } export function StandingsPreview({ entries, description, fullStandingsHref, showProjected = false, }: StandingsPreviewProps) { return (

Standings

{description && (

{description}

)}
{fullStandingsHref && ( )}
{entries.map((entry) => entry.href ? ( ) : (
) )}
); }