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";
// ─── TeamAvatar ───────────────────────────────────────────────────────────────
const TEAM_AVATAR_COLORS = [
"#adf661",
"#2ce1c1",
"#8b5cf6",
"#f59e0b",
"#ef4444",
"#3b82f6",
];
function hashTeamId(id: string): number {
let hash = 0;
for (let i = 0; i < id.length; i++) {
hash = (hash * 31 + id.charCodeAt(i)) & 0xffff;
}
return hash;
}
function TeamAvatar({ teamId, teamName }: { teamId: string; teamName: string }) {
const color = TEAM_AVATAR_COLORS[hashTeamId(teamId) % TEAM_AVATAR_COLORS.length];
const initials =
teamName
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((w) => w[0].toUpperCase())
.join("") || "?";
return (
{initials}
);
}
// ─── Stat helpers (mirrors LeagueRow) ────────────────────────────────────────
function StatColumn({
label,
children,
}: {
label: string;
children: React.ReactNode;
}) {
return (
);
}
function StatDivider() {
return ;
}
function RankChangeIndicator({ change }: { change: number }) {
if (change === 0) return null;
if (change > 0) {
return (
▲{change}
);
}
return (
▼{Math.abs(change)}
);
}
function PointChangeIndicator({ change }: { change: number }) {
const sign = change >= 0 ? "+" : "";
if (change >= 0) {
return {sign}{change.toFixed(1)};
}
return (
{sign}{change.toFixed(1)}
);
}
// ─── 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 }: { entry: StandingsPreviewEntry }) {
return (
<>
{/* Left: avatar + name */}
{entry.teamName}
{entry.ownerName && (
{entry.ownerName}
)}
{/* Right: stats — second row on mobile */}
{entry.displayRank}
{entry.rankChange !== undefined && entry.rankChange !== 0 && (
)}
{Math.round(entry.points).toLocaleString("en-US")}
{entry.pointChange !== undefined && entry.pointChange !== 0 && (
)}
>
);
}
// ─── Public API ───────────────────────────────────────────────────────────────
export interface StandingsPreviewEntry {
teamId: string;
teamName: string;
ownerName?: string | null;
/** Pre-computed display rank, e.g. 1, "T2", "T5". Use getDisplayRank(). */
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;
}
export interface StandingsPreviewProps {
entries: StandingsPreviewEntry[];
description?: string;
fullStandingsHref?: string;
}
export function StandingsPreview({ entries, description, fullStandingsHref }: StandingsPreviewProps) {
return (
Standings
{description && (
{description}
)}
{fullStandingsHref && (
)}
{entries.map((entry) =>
entry.href ? (
) : (
)
)}
);
}