From 64d85c135a1091e7b644691e511af498e513286c Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Thu, 16 Oct 2025 01:12:50 -0700 Subject: [PATCH] feat: implement draft grid and reorganize draft room layout --- app/components/draft/DraftGrid.tsx | 155 +++++++++++++++++++++++++ app/routes/leagues/$leagueId.draft.tsx | 66 ++++++++--- 2 files changed, 202 insertions(+), 19 deletions(-) create mode 100644 app/components/draft/DraftGrid.tsx diff --git a/app/components/draft/DraftGrid.tsx b/app/components/draft/DraftGrid.tsx new file mode 100644 index 0000000..384e226 --- /dev/null +++ b/app/components/draft/DraftGrid.tsx @@ -0,0 +1,155 @@ +import { useMemo } from "react"; + +interface Team { + id: string; + name: string; + draftOrder: number; + ownerId: string | null; +} + +interface DraftPick { + id: string; + teamId: string; + participantId: string; + pickNumber: number; + round: number; + pickInRound: number; + pickedByType: "owner" | "commissioner" | "auto"; +} + +interface Participant { + id: string; + name: string; + sport: string; +} + +interface DraftGridProps { + teams: Team[]; + draftPicks: DraftPick[]; + participants: Participant[]; + draftRounds: number; + currentPickNumber: number; + userTeamId?: string; +} + +export function DraftGrid({ + teams, + draftPicks, + participants, + draftRounds, + currentPickNumber, + userTeamId, +}: DraftGridProps) { + // Create a map of participant IDs to participant data for quick lookup + const participantMap = useMemo(() => { + return new Map(participants.map((p) => [p.id, p])); + }, [participants]); + + // Create a map of pick numbers to draft picks + const pickMap = useMemo(() => { + return new Map(draftPicks.map((p) => [p.pickNumber, p])); + }, [draftPicks]); + + // Calculate pick number for a given round and team + const getPickNumber = (round: number, teamIndex: number): number => { + const isOddRound = round % 2 === 1; + const pickInRound = isOddRound ? teamIndex + 1 : teams.length - teamIndex; + return (round - 1) * teams.length + pickInRound; + }; + + + return ( +
+
+ {/* Header Row - Team Names */} +
+
+ Round +
+ {teams.map((team) => ( +
+
+ {team.name} +
+
+ ))} +
+ + {/* Draft Rounds */} + {Array.from({ length: draftRounds }, (_, roundIndex) => { + const round = roundIndex + 1; + const isOddRound = round % 2 === 1; + + return ( +
+ {/* Round Number */} +
+ {round} +
+ + {/* Picks for this round */} + {teams.map((team, teamIndex) => { + const pickNumber = getPickNumber(round, teamIndex); + const pick = pickMap.get(pickNumber); + const participant = pick ? participantMap.get(pick.participantId) : null; + const isCurrentPick = pickNumber === currentPickNumber; + const isPastPick = pickNumber < currentPickNumber; + const isUserTeam = team.id === userTeamId; + + return ( +
+
+ {/* Pick Number */} +
+ {round}.{String(isOddRound ? teamIndex + 1 : teams.length - teamIndex).padStart(2, "0")} +
+ + {/* Participant or Status */} + {participant ? ( +
+
+ {participant.name} +
+
+ {participant.sport} +
+ {pick && pick.pickedByType !== "owner" && ( +
+ ({pick.pickedByType}) +
+ )} +
+ ) : isCurrentPick ? ( +
+ On the clock +
+ ) : isPastPick ? ( +
+ Skipped +
+ ) : ( +
+ - +
+ )} +
+
+ ); + })} +
+ ); + })} +
+
+ ); +} diff --git a/app/routes/leagues/$leagueId.draft.tsx b/app/routes/leagues/$leagueId.draft.tsx index 1d19219..a79c937 100644 --- a/app/routes/leagues/$leagueId.draft.tsx +++ b/app/routes/leagues/$leagueId.draft.tsx @@ -9,7 +9,9 @@ import { findDraftSlotsBySeasonId, getDraftPicks, getSeasonTimers, + findSeasonWithSportsSeasons, } from "~/models"; +import { DraftGrid } from "~/components/draft/DraftGrid"; export async function loader(args: LoaderFunctionArgs) { const { userId } = await getAuth(args); @@ -33,6 +35,12 @@ export async function loader(args: LoaderFunctionArgs) { throw new Response("No active season found", { status: 404 }); } + // Get season with sports and participants + const seasonWithSports = await findSeasonWithSportsSeasons(season.id); + const participants = seasonWithSports?.seasonSports?.flatMap( + (ss: any) => ss.sportsSeason?.participants || [] + ) || []; + // Get teams and draft order const teams = await findTeamsBySeasonId(season.id); const draftSlots = await findDraftSlotsBySeasonId(season.id); @@ -67,6 +75,7 @@ export async function loader(args: LoaderFunctionArgs) { isCommissioner: isUserCommissioner, userTeam, draftPicks, + participants, timers, currentPickNumber, totalPicks, @@ -79,6 +88,9 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited {/* Header */}
-
+

{league.name} Draft

@@ -115,7 +127,7 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited {/* Main Content */} -
+
{isDraftComplete ? (

Draft Complete!

@@ -124,29 +136,31 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited
) : ( -
- {/* Left Column - Draft Grid & Player List */} -
- {/* Draft Grid Placeholder */} -
-

Draft Board

-
- Draft grid will be implemented in Phase 5 -
-
+
+ {/* Draft Board - Full Width */} +
+

Draft Board

+ +
- {/* Player List Placeholder */} + {/* 2x2 Grid Below */} +
+ {/* Available Players */}

Available Players

Player list will be implemented in Phase 6
-
- {/* Right Column - Queue & Controls */} -
- {/* Queue Placeholder */} + {/* Your Queue */}

Your Queue

@@ -154,7 +168,7 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited
- {/* Timer Placeholder */} + {/* Draft Timer */}

Draft Timer

@@ -163,13 +177,27 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited {/* Commissioner Controls */} - {isCommissioner && ( + {isCommissioner ? (

Commissioner Controls

Controls will be implemented in Phase 9
+ ) : ( +
+

Draft Info

+
+
+ Current Pick: + {currentPickNumber} of {totalPicks} +
+
+ Draft Rounds: + {season.draftRounds} +
+
+
)}