brackt/app/components/scoring/PlayoffBracket.tsx

611 lines
26 KiB
TypeScript
Raw Normal View History

import { Badge } from "~/components/ui/badge";
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import { Trophy, Star } from "lucide-react";
import { TeamOwnerBadge } from "~/components/ui/team-owner-badge";
interface Participant {
id: string;
name: string;
}
interface Match {
id: string;
round: string;
matchNumber: number;
participant1Id: string | null;
participant2Id: string | null;
winnerId: string | null;
loserId: string | null;
isComplete: boolean;
participant1Score: string | null;
participant2Score: string | null;
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
participant1?: Participant | null;
participant2?: Participant | null;
winner?: Participant | null;
loser?: Participant | null;
}
interface TeamOwnership {
participantId: string;
teamName: string;
teamId: string;
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
ownerName?: string;
}
interface PlayoffBracketProps {
matches: Match[];
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
rounds: string[]; // Ordered list of round names (earliest first)
preEliminatedParticipants?: { id: string; name: string }[]; // Eliminated before bracket (e.g. group stage)
participantPoints?: { participantId: string; points: number }[]; // Computed fantasy points per participant
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
partialScoreParticipantIds?: string[]; // Still-competing participants with provisional floor scores
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
teamOwnerships?: TeamOwnership[];
userParticipantIds?: string[];
showOwnership?: boolean;
title?: string;
description?: string;
}
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
/** Group matches by round, sorted by matchNumber, in one pass. */
export function groupMatchesByRound(matches: Match[]): Map<string, Match[]> {
const byRound = new Map<string, Match[]>();
for (const match of matches) {
if (!byRound.has(match.round)) byRound.set(match.round, []);
byRound.get(match.round)!.push(match);
}
for (const group of byRound.values()) {
group.sort((a, b) => a.matchNumber - b.matchNumber);
}
return byRound;
}
/**
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
* For a standard single-elimination bracket, slot p1 of match N in round R
* comes from match (2N-1) in the previous round, and slot p2 from match 2N.
*/
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
export function buildFeederMap(
matchesByRound: Map<string, Match[]>,
orderedRounds: string[]
): Map<string, { round: string; matchNumber: number }> {
const feederMap = new Map<string, { round: string; matchNumber: number }>();
for (let ri = 1; ri < orderedRounds.length; ri++) {
const currentRound = orderedRounds[ri];
const prevRound = orderedRounds[ri - 1];
const prevMatchNums = new Set(
(matchesByRound.get(prevRound) || []).map((m) => m.matchNumber)
);
for (const match of matchesByRound.get(currentRound) || []) {
const p1Src = 2 * (match.matchNumber - 1) + 1;
const p2Src = 2 * (match.matchNumber - 1) + 2;
if (prevMatchNums.has(p1Src)) {
feederMap.set(`${currentRound}:${match.matchNumber}:p1`, {
round: prevRound,
matchNumber: p1Src,
});
}
if (prevMatchNums.has(p2Src)) {
feederMap.set(`${currentRound}:${match.matchNumber}:p2`, {
round: prevRound,
matchNumber: p2Src,
});
}
}
}
return feederMap;
}
interface EliminatedEntry {
participant: Participant;
score: string | null;
rankLabel: string;
ownership: TeamOwnership | null;
}
export function PlayoffBracket({
matches,
rounds,
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
preEliminatedParticipants = [],
participantPoints = [],
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
partialScoreParticipantIds = [],
teamOwnerships = [],
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
userParticipantIds = [],
showOwnership = true,
title = "Playoff Bracket",
description,
}: PlayoffBracketProps) {
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
const userParticipantSet = new Set(userParticipantIds);
const ownershipMap = new Map<string, TeamOwnership>();
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
teamOwnerships.forEach((o) => ownershipMap.set(o.participantId, o));
const pointsMap = new Map(participantPoints.map((p) => [p.participantId, p.points]));
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
// Group matches once; reused for feeder map and rendering
const matchesByRound = groupMatchesByRound(matches);
const feederMap = buildFeederMap(matchesByRound, rounds);
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
const getTbdLabel = (round: string, matchNumber: number, slot: "p1" | "p2") => {
const feeder = feederMap.get(`${round}:${matchNumber}:${slot}`);
if (!feeder) return "TBD";
return `Winner of ${feeder.round} M${feeder.matchNumber}`;
};
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
// Build elimination rankings: collect losers per round, then assign rank labels.
// In double-chance brackets (e.g. AFL), a participant may lose one round but
// win a later one — only count them as eliminated at their FINAL losing match.
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
const losersByRound = new Map<string, Array<{ participant: Participant; score: string | null; ownership: TeamOwnership | null }>>();
let hasScore = false;
let bracketWinner: Participant | null = null;
const lastRound = rounds[rounds.length - 1];
const finalMatch = lastRound
? (matchesByRound.get(lastRound) || []).find((m) => m.matchNumber === 1)
: null;
if (finalMatch?.winner) bracketWinner = finalMatch.winner;
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
// Build the set of all participants who won at least one completed match.
// If a participant won any match, their earlier loss was not their final elimination.
const participantWinIds = new Set<string>();
for (const match of matches) {
if (match.isComplete && match.winnerId) participantWinIds.add(match.winnerId);
}
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
for (const match of matches) {
if (!match.isComplete || !match.loser) continue;
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
// Skip if this loser went on to win another match (double-chance bracket)
if (participantWinIds.has(match.loser.id)) continue;
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
const loserScore =
match.loserId === match.participant1Id
? match.participant1Score
: match.participant2Score;
if (loserScore) hasScore = true;
if (!losersByRound.has(match.round)) losersByRound.set(match.round, []);
losersByRound.get(match.round)!.push({
participant: match.loser,
score: loserScore,
ownership: ownershipMap.get(match.loser.id) || null,
});
}
// Walk rounds latest→earliest to assign rank labels (no mutation)
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
// nextRank must start after the still-alive participants (not always 2)
const allBracketParticipantIds = new Set<string>();
for (const match of matches) {
if (match.participant1Id) allBracketParticipantIds.add(match.participant1Id);
if (match.participant2Id) allBracketParticipantIds.add(match.participant2Id);
}
const totalEliminatedInBracket = [...losersByRound.values()].reduce(
(sum, losers) => sum + losers.length,
0
);
const stillAlive =
allBracketParticipantIds.size - totalEliminatedInBracket - (bracketWinner ? 1 : 0);
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
const rankedEntries: EliminatedEntry[] = [];
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
let nextRank = stillAlive + (bracketWinner ? 2 : 1);
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
for (let ri = rounds.length - 1; ri >= 0; ri--) {
const roundLosers = losersByRound.get(rounds[ri]) || [];
if (roundLosers.length === 0) continue;
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
const rankLabel = `T${nextRank}`;
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
for (const loser of roundLosers) {
rankedEntries.push({ ...loser, rankLabel });
}
nextRank += roundLosers.length;
}
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
// Exclude pre-eliminated participants already ranked via bracket match losers
const rankedParticipantIds = new Set(rankedEntries.map((e) => e.participant.id));
if (bracketWinner) rankedParticipantIds.add(bracketWinner.id);
const filteredPreEliminated = preEliminatedParticipants.filter(
(p) => !rankedParticipantIds.has(p.id)
);
const showRankings = rankedEntries.length > 0 || bracketWinner !== null || filteredPreEliminated.length > 0;
// Build participant lookup from match data for the "In Contention" table
const participantMap = new Map<string, Participant>();
for (const match of matches) {
if (match.participant1) participantMap.set(match.participant1.id, match.participant1);
if (match.participant2) participantMap.set(match.participant2.id, match.participant2);
}
const activeParticipants = [...allBracketParticipantIds]
.filter((id) => !rankedParticipantIds.has(id))
.map((id) => participantMap.get(id))
.filter((p): p is Participant => p !== undefined);
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
// Hoist winner row lookups so we don't need an IIFE in JSX
const winnerIsOwned = bracketWinner ? userParticipantSet.has(bracketWinner.id) : false;
const winnerOwnership = bracketWinner ? ownershipMap.get(bracketWinner.id) : undefined;
const winnerPts = bracketWinner ? pointsMap.get(bracketWinner.id) : undefined;
return (
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
<div className="space-y-6">
<div>
<h2 className="text-xl font-semibold">{title}</h2>
{description && (
<p className="text-sm text-muted-foreground mt-1">{description}</p>
)}
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
</div>
{matches.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
<p className="text-sm">No bracket matches available yet.</p>
<p className="text-xs mt-1">
Matches will appear here once the bracket is set up.
</p>
</div>
) : (
<div className="space-y-8">
{rounds.map((round) => {
const roundMatches = matchesByRound.get(round) || [];
if (roundMatches.length === 0) return null;
return (
<div key={round}>
<div className="flex items-center gap-2 mb-3">
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
{round}
</h3>
<div className="flex-1 border-t border-border" />
<span className="text-xs text-muted-foreground">
{roundMatches.length}{" "}
{roundMatches.length === 1 ? "match" : "matches"}
</span>
</div>
<div className="grid md:grid-cols-2 gap-2">
{roundMatches.map((match) => {
const p1IsWinner =
match.isComplete && match.winnerId === match.participant1Id;
const p2IsWinner =
match.isComplete && match.winnerId === match.participant2Id;
const p1IsLoser =
match.isComplete && match.loserId === match.participant1Id;
const p2IsLoser =
match.isComplete && match.loserId === match.participant2Id;
const p1Name =
match.participant1?.name ||
(match.participant1Id
? "TBD"
: getTbdLabel(round, match.matchNumber, "p1"));
const p2Name =
match.participant2?.name ||
(match.participant2Id
? "TBD"
: getTbdLabel(round, match.matchNumber, "p2"));
const p1IsTbd = !match.participant1Id;
const p2IsTbd = !match.participant2Id;
const p1IsOwned = !p1IsTbd && userParticipantSet.has(match.participant1Id!);
const p2IsOwned = !p2IsTbd && userParticipantSet.has(match.participant2Id!);
const matchHasOwned = p1IsOwned || p2IsOwned;
const p1Ownership =
showOwnership && match.participant1Id
? ownershipMap.get(match.participant1Id) || null
: null;
const p2Ownership =
showOwnership && match.participant2Id
? ownershipMap.get(match.participant2Id) || null
: null;
return (
<div
key={match.id}
className={`rounded-lg border bg-card px-3 py-2 ${matchHasOwned ? "border-electric/50" : "border-border"}`}
>
{/* Match label row */}
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-mono text-muted-foreground">
M{match.matchNumber}
</span>
{match.isComplete && (
<Badge variant="secondary" className="text-xs">
Done
</Badge>
)}
</div>
{/* Participants: left vs right (stacks on mobile) */}
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
{/* Participant 1 — left-aligned */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1">
{p1IsOwned && !p1IsWinner && (
<span className="inline-block w-1.5 h-1.5 rounded-full bg-electric shrink-0" />
)}
{p1IsWinner && (
<Trophy className="h-3.5 w-3.5 text-yellow-500 shrink-0" />
)}
<span
className={[
"text-sm font-medium truncate",
p1IsLoser ? "text-muted-foreground line-through" : "",
p1IsTbd ? "text-muted-foreground italic font-normal" : "",
p1IsWinner ? "font-semibold" : "",
p1IsOwned && !p1IsLoser ? "text-electric" : "",
]
.filter(Boolean)
.join(" ")}
>
{p1Name}
</span>
</div>
{!p1IsTbd && p1Ownership && (
<div className="mt-1">
<TeamOwnerBadge
teamName={p1Ownership.teamName}
ownerName={p1Ownership.ownerName}
/>
</div>
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
)}
</div>
{/* Center: scores + vs */}
<div className="shrink-0 flex sm:flex-col items-center gap-1 sm:gap-0.5 min-w-[2.5rem]">
{match.participant1Score ? (
<>
<span className="text-xs tabular-nums font-medium">
{parseFloat(match.participant1Score)}
</span>
<span className="text-[10px] text-muted-foreground/60 uppercase tracking-wider">
vs
</span>
<span className="text-xs tabular-nums font-medium">
{match.participant2Score
? parseFloat(match.participant2Score)
: "—"}
</span>
</>
) : (
<span className="text-xs text-muted-foreground">vs</span>
)}
</div>
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
{/* Participant 2 — right-aligned on sm+, left-aligned on mobile */}
<div className="flex-1 min-w-0 flex flex-col sm:items-end">
<div className="flex items-center gap-1 sm:justify-end">
<span
className={[
"text-sm font-medium truncate",
p2IsLoser ? "text-muted-foreground line-through" : "",
p2IsTbd ? "text-muted-foreground italic font-normal" : "",
p2IsWinner ? "font-semibold" : "",
p2IsOwned && !p2IsLoser ? "text-electric" : "",
]
.filter(Boolean)
.join(" ")}
>
{p2Name}
</span>
{p2IsWinner && (
<Trophy className="h-3.5 w-3.5 text-yellow-500 shrink-0" />
)}
{p2IsOwned && !p2IsWinner && (
<span className="inline-block w-1.5 h-1.5 rounded-full bg-electric shrink-0" />
)}
</div>
{!p2IsTbd && p2Ownership && (
<div className="mt-1">
<TeamOwnerBadge
teamName={p2Ownership.teamName}
ownerName={p2Ownership.ownerName}
/>
</div>
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
)}
</div>
</div>
</div>
);
})}
</div>
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
</div>
);
})}
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
{/* In Contention */}
{activeParticipants.length > 0 && (
<Card className="border-green-500/30">
<CardHeader>
<CardTitle className="text-green-600 dark:text-green-400 flex items-center gap-2">
<Star className="h-5 w-5" />
In Contention
</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Participant</TableHead>
{showOwnership && (
<TableHead className="w-40 pl-6">Drafted By</TableHead>
)}
{pointsMap.size > 0 && (
<TableHead className="text-right w-16">Pts</TableHead>
)}
</TableRow>
</TableHeader>
<TableBody>
{activeParticipants.map((p) => {
const isOwned = userParticipantSet.has(p.id);
const ownership = showOwnership ? ownershipMap.get(p.id) || null : null;
const pts = pointsMap.get(p.id);
return (
<TableRow
key={p.id}
className={isOwned ? "bg-electric/5 border-l-2 border-l-electric" : ""}
>
<TableCell className={isOwned ? "text-electric font-medium" : ""}>
{p.name}
{isOwned && (
<Star className="inline ml-1.5 h-3 w-3 fill-current text-electric" />
)}
</TableCell>
{showOwnership && (
<TableCell className="pl-6">
{ownership ? (
<TeamOwnerBadge teamName={ownership.teamName} ownerName={ownership.ownerName} />
) : (
<span className="text-xs text-muted-foreground">-</span>
)}
</TableCell>
)}
{pointsMap.size > 0 && (
<TableCell className="text-right font-mono text-sm">
{pts !== undefined ? pts : ""}
</TableCell>
)}
</TableRow>
);
})}
</TableBody>
</Table>
</CardContent>
</Card>
)}
{/* Final Rankings / Eliminated Teams */}
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
{showRankings && (
<Card className="border-electric/30">
<CardHeader>
<CardTitle className="text-electric flex items-center gap-2">
<Trophy className="h-5 w-5" />
{bracketWinner ? "Final Rankings" : "Eliminated Teams"}
</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-20">Rank</TableHead>
<TableHead>Participant</TableHead>
{showOwnership && (
<TableHead className="w-40 pl-6">Drafted By</TableHead>
)}
{pointsMap.size > 0 && (
<TableHead className="text-right w-16">Pts</TableHead>
)}
</TableRow>
</TableHeader>
<TableBody>
{bracketWinner && (
<TableRow className={winnerIsOwned ? "bg-electric/8 border-l-2 border-l-electric" : "bg-yellow-50/30 dark:bg-yellow-950/10"}>
<TableCell className="font-semibold text-yellow-600 dark:text-yellow-400">
<span className="flex items-center gap-1">
<Trophy className="h-3 w-3" />
#1
</span>
</TableCell>
<TableCell className="font-semibold">
{bracketWinner.name}
{winnerIsOwned && (
<Star className="inline ml-1.5 h-3 w-3 fill-current text-electric" />
)}
</TableCell>
{showOwnership && (
<TableCell className="pl-6">
{winnerOwnership ? (
<TeamOwnerBadge teamName={winnerOwnership.teamName} ownerName={winnerOwnership.ownerName} />
) : (
<span className="text-xs text-muted-foreground">-</span>
)}
</TableCell>
)}
{pointsMap.size > 0 && (
<TableCell className="text-right font-semibold text-electric">
{winnerPts ?? "—"}
</TableCell>
)}
</TableRow>
)}
{rankedEntries.map((entry, i) => {
const isOwned = userParticipantSet.has(entry.participant.id);
const pts = pointsMap.get(entry.participant.id);
return (
<TableRow
key={`${entry.participant.id}-${i}`}
className={isOwned ? "bg-electric/5 border-l-2 border-l-electric opacity-80" : "opacity-60"}
>
<TableCell className="font-mono text-xs text-muted-foreground">
{entry.rankLabel}
</TableCell>
<TableCell className={isOwned ? "text-electric font-medium" : "text-muted-foreground"}>
{entry.participant.name}
{isOwned && (
<Star className="inline ml-1.5 h-3 w-3 fill-current text-electric" />
)}
</TableCell>
{showOwnership && (
<TableCell className="pl-6">
{entry.ownership ? (
<TeamOwnerBadge
teamName={entry.ownership.teamName}
ownerName={entry.ownership.ownerName}
/>
) : (
<span className="text-xs text-muted-foreground">-</span>
)}
</TableCell>
)}
{pointsMap.size > 0 && (
<TableCell className="text-right tabular-nums text-muted-foreground">
{pts ?? "—"}
</TableCell>
)}
</TableRow>
);
})}
feat: progressive floor scoring for playoff brackets (#100) When a participant wins a bracket round, they immediately earn provisional "floor" points (the averaged minimum they'd receive if eliminated next round). These update as they advance and are replaced by finalized scores on elimination. Key changes: - Add `is_partial_score` column to `participant_results` (migration 0038) - `processPlayoffEvent`: assign provisional position 5 to non-scoring round winners; assign round-appropriate floors to scoring round winners via `getGuaranteedMinimumPosition`; add catch-all for unrecognized round names - `upsertParticipantResult`: guard against un-finalizing rows (never overwrite isPartialScore=false with true) - `calculateBracketPoints`: new function averaging tied bracket tiers (5-8 → 20 pts, 3-4 → avg, 1-2 solo); used in `calculateTeamScore` for playoff_bracket pattern (pattern-aware, doesn't affect F1/golf scoring) - `PlayoffBracket`: "In Contention" table for still-active participants; AFL double-chance fix (participants who won a later match excluded from earlier round's loser list); correct `nextRank` starting position - Server loader: batch owner DB queries (one query vs N+1); deduplicate participantPoints; use calculateBracketPoints for bracket point display - Clean up Phase/Q-number tracking comments throughout scoring-calculator.ts - 3 new tests for non-scoring round provisional floor behavior Also includes a dev admin bypass via DEV_ADMIN_CLERK_ID env var (separate change on this branch, not part of floor scoring feature). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:27:58 -07:00
{filteredPreEliminated.map((p) => {
User/chris/bracket UI redesign (#95) * feat: redesign playoff bracket UI with compact layout and owner display - Replace per-match Card wrappers with compact left-vs-right rows (stacks on mobile) - Add TeamOwnerBadge component (colored avatar + team name + username), matching the standings "Drafted By" style - Show owner info below each participant name in bracket matches - Add TBD forward-reference labels ("Winner of Quarterfinals M1") computed via buildFeederMap - Add Final Rankings table below bracket showing all participants ranked by elimination round - Fix round ordering in loader: sort by match count descending (more matches = earlier round) - Add loser relation to playoff matches query for elimination tracking - Extract getAvatarColor to app/lib/color-hash.ts (shared across GroupStageDisplay, SeasonStandings, TeamOwnerBadge) - Extract groupMatchesByRound helper; share grouped map between feeder computation and render - Remove dead getTeamAvatar from SeasonStandings after TeamOwnerBadge adoption - Add tests for groupMatchesByRound and buildFeederMap (7 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add draft order card to league home page Show draft order on the league home page when order is set and season is in pre_draft or draft status. Includes team name, owner name, and a link to the draft room. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: enhance playoff bracket with eliminated teams, points, and ownership highlights - Show eliminated teams (including group-stage losers) in Final Rankings card - Display computed fantasy points per participant in rankings table - Card title switches between "Eliminated Teams" and "Final Rankings" based on bracket completion - Highlight owned participants with electric blue name, dot indicator, and card border in bracket matches - Highlight owned rows with electric border/background in rankings table - Suppress EventSchedule for playoff_bracket sports seasons - Fix title redundancy: bracket section title no longer repeats sport name - Two-column match grid on desktop (md:grid-cols-2) - Code review fixes: parallel DB fetches, targeted query for pre-eliminated, single-pass parseFloat, remove IIFE Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:44:33 -07:00
const isOwned = userParticipantSet.has(p.id);
const ownership = showOwnership ? ownershipMap.get(p.id) || null : null;
const pts = pointsMap.get(p.id);
return (
<TableRow
key={p.id}
className={isOwned ? "bg-electric/5 border-l-2 border-l-electric opacity-60" : "opacity-40"}
>
<TableCell className="font-mono text-xs text-muted-foreground"></TableCell>
<TableCell className={isOwned ? "text-electric font-medium" : "text-muted-foreground"}>
{p.name}
{isOwned && (
<Star className="inline ml-1.5 h-3 w-3 fill-current text-electric" />
)}
</TableCell>
{showOwnership && (
<TableCell className="pl-6">
{ownership ? (
<TeamOwnerBadge teamName={ownership.teamName} ownerName={ownership.ownerName} />
) : (
<span className="text-xs text-muted-foreground">-</span>
)}
</TableCell>
)}
{pointsMap.size > 0 && (
<TableCell className="text-right tabular-nums text-muted-foreground">
{pts ?? "—"}
</TableCell>
)}
</TableRow>
);
})}
</TableBody>
</Table>
</CardContent>
</Card>
)}
</div>
)}
</div>
);
}