All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 3m23s
🚀 Deploy / 🧪 Test (push) Successful in 3m9s
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m20s
🚀 Deploy / 🐳 Build (push) Successful in 1m9s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m23s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (push) Successful in 12s
Surface projected final points on the full standings page and restructure the rank/point change indicators so columns align cleanly on desktop and mobile. - StatHelpers: stacked label/value/delta columns with a per-row reservable delta line; merge rank/point indicators into a single DeltaBadge; parameterize StatDivider height. - StandingsPreview: opt-in `showProjected` column (projected points only), gated on participants remaining; reserve the delta line only when a row actually moved (no stray em-dashes). - Full standings page: pass projected data + showProjected (hidden when the season is complete). - League home: fetch via getSevenDayStandingsChange so the preview shows the same 7-day rank/point changes as the full standings page. - LeagueRow: top-align stats and restore h-8 dividers so the shared-component changes don't alter the league list rows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
104 lines
2.8 KiB
TypeScript
104 lines
2.8 KiB
TypeScript
/**
|
|
* Stacked stat column: label / value / delta. The delta slot is always rendered
|
|
* (a muted "—" placeholder when there is no change) so that every row reserves the
|
|
* same vertical space and the columns line up across the standings table.
|
|
*/
|
|
export function StatColumn({
|
|
label,
|
|
value,
|
|
delta,
|
|
reserveDelta = false,
|
|
}: {
|
|
label: string;
|
|
value: React.ReactNode;
|
|
/** Optional change indicator rendered on its own line below the value. */
|
|
delta?: React.ReactNode;
|
|
/**
|
|
* When true, the delta line is always rendered (a muted placeholder when there is
|
|
* no delta) so columns stay vertically aligned across rows. When false and no delta
|
|
* is provided, the line is omitted entirely.
|
|
*/
|
|
reserveDelta?: boolean;
|
|
}) {
|
|
const showDeltaLine = delta !== undefined || reserveDelta;
|
|
return (
|
|
<div className="text-right shrink-0 flex-1 sm:flex-none">
|
|
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
{label}
|
|
</p>
|
|
<div className="flex items-baseline justify-end">{value}</div>
|
|
{showDeltaLine && (
|
|
<div className="flex items-baseline justify-end leading-none mt-0.5">
|
|
{delta ?? <span className="text-xs text-muted-foreground/60">—</span>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function StatDivider({ className = "h-12" }: { className?: string } = {}) {
|
|
return <div className={`${className} w-px bg-border shrink-0 self-center`} />;
|
|
}
|
|
|
|
/**
|
|
* 7-day change indicator. `rank` deltas are unitless places; `points` deltas are
|
|
* rounded point totals. Positive = up (green ▲), negative = down (coral ▼).
|
|
*/
|
|
export function DeltaBadge({
|
|
delta,
|
|
kind,
|
|
}: {
|
|
delta: number;
|
|
kind: "rank" | "points";
|
|
}) {
|
|
const magnitude = kind === "rank" ? Math.abs(delta) : Math.abs(Math.round(delta));
|
|
const up = delta > 0;
|
|
const label =
|
|
kind === "rank"
|
|
? up
|
|
? `up ${magnitude}`
|
|
: `down ${magnitude}`
|
|
: up
|
|
? `+${magnitude} points`
|
|
: `-${magnitude} points`;
|
|
|
|
if (up) {
|
|
return (
|
|
<span className="text-xs font-semibold text-primary" aria-label={label}>
|
|
▲{magnitude}
|
|
</span>
|
|
);
|
|
}
|
|
return (
|
|
<span
|
|
className="text-xs font-semibold"
|
|
style={{ color: "var(--coral-accent, #ef4444)" }}
|
|
aria-label={label}
|
|
>
|
|
▼{magnitude}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export function RankingDisplay({
|
|
displayRank,
|
|
rankChange,
|
|
reserveDelta = false,
|
|
}: {
|
|
displayRank: string | number;
|
|
rankChange?: number;
|
|
reserveDelta?: boolean;
|
|
}) {
|
|
return (
|
|
<StatColumn
|
|
label="Ranking"
|
|
reserveDelta={reserveDelta}
|
|
value={<span className="text-2xl font-bold leading-none">{displayRank}</span>}
|
|
delta={
|
|
rankChange !== undefined && rankChange !== 0 ? (
|
|
<DeltaBadge delta={rankChange} kind="rank" />
|
|
) : undefined
|
|
}
|
|
/>
|
|
);
|
|
}
|