brackt/app/components/standings/RecentScoresCard.tsx
2026-04-15 22:45:39 -07:00

116 lines
3.5 KiB
TypeScript

import { formatDistanceToNow } from "date-fns";
import { TrendingUp } from "lucide-react";
import { GradientIcon } from "~/components/ui/GradientIcon";
import { BRACKT_GRADIENT } from "~/lib/brand";
interface ScoreEventEntry {
id: string;
teamId: string;
teamName: string;
ownerName: string | null;
scoringEventId: string | null;
scoringEventName: string | null;
sportName: string | null;
pointsDelta: string;
occurredAt: Date | string;
participants: Array<{ id: string; name: string }>;
}
interface RecentScoresCardProps {
events: ScoreEventEntry[];
}
function TimelineDot({ isRecent }: { isRecent: boolean }) {
return (
<div
suppressHydrationWarning
className={`relative z-10 mt-1 h-2.5 w-2.5 shrink-0 rounded-full${isRecent ? "" : " border-2 border-border bg-background"}`}
style={isRecent ? { background: BRACKT_GRADIENT } : undefined}
/>
);
}
function ScoreRow({
event,
isLast,
}: {
event: ScoreEventEntry;
isLast: boolean;
}) {
const pts = parseFloat(event.pointsDelta);
const displayPts = Number.isInteger(pts) ? pts.toString() : pts.toFixed(1);
const managerName = event.ownerName ?? event.teamName;
const isRecent = Date.now() - new Date(event.occurredAt).getTime() < 24 * 60 * 60 * 1000;
return (
<div className="flex gap-3 min-w-0">
{/* Timeline spine */}
<div className="flex flex-col items-center">
<TimelineDot isRecent={isRecent} />
{!isLast && <div className="w-px flex-1 bg-border mt-1" />}
</div>
{/* Content row: points box + text */}
<div className={`flex gap-3 flex-1 min-w-0 ${isLast ? "" : "pb-6"}`}>
{/* Points box — square, no rounded corners */}
<div className="shrink-0 w-10 h-10 flex items-center justify-center bg-black text-white font-bold text-sm leading-none self-start">
+{displayPts}
</div>
{/* Text content */}
<div className="flex flex-col gap-0.5 min-w-0">
{/* Date */}
<div className="text-xs text-muted-foreground/60" suppressHydrationWarning>
{formatDistanceToNow(new Date(event.occurredAt), { addSuffix: true })}
</div>
{/* Manager name */}
<div className="text-sm font-bold text-foreground leading-tight">
{managerName}
</div>
{/* Sport · participant name(s) */}
<div className="text-xs text-muted-foreground">
{[
event.sportName,
event.participants.length > 0
? event.participants.map((p) => p.name).join(", ")
: null,
]
.filter(Boolean)
.join(" · ")}
</div>
</div>
</div>
</div>
);
}
export function RecentScoresCard({ events }: RecentScoresCardProps) {
return (
<div>
{/* Header */}
<div className="flex items-center gap-2 mb-6">
<GradientIcon icon={TrendingUp} className="h-5 w-5 shrink-0" />
<span className="text-xl font-bold leading-none tracking-tight">Recent Scores</span>
</div>
{/* Timeline */}
{events.length === 0 ? (
<p className="text-sm text-muted-foreground py-2">
No recent scores yet they&apos;ll appear here as events complete.
</p>
) : (
<div>
{events.map((event, index) => (
<ScoreRow
key={event.id}
event={event}
isLast={index === events.length - 1}
/>
))}
</div>
)}
</div>
);
}