feat: implement draft grid and reorganize draft room layout
This commit is contained in:
parent
045e0f2cab
commit
64d85c135a
2 changed files with 202 additions and 19 deletions
155
app/components/draft/DraftGrid.tsx
Normal file
155
app/components/draft/DraftGrid.tsx
Normal file
|
|
@ -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 (
|
||||
<div className="overflow-x-auto">
|
||||
<div className="inline-block min-w-full">
|
||||
{/* Header Row - Team Names */}
|
||||
<div className="flex border-b-2 border-border bg-muted/50 sticky top-0 z-10">
|
||||
<div className="w-16 flex-shrink-0 p-2 font-semibold text-sm border-r">
|
||||
Round
|
||||
</div>
|
||||
{teams.map((team) => (
|
||||
<div
|
||||
key={team.id}
|
||||
className={`flex-1 min-w-[140px] p-2 text-center font-semibold text-sm border-r last:border-r-0 ${
|
||||
team.id === userTeamId ? "bg-primary/10" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="truncate" title={team.name}>
|
||||
{team.name}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Draft Rounds */}
|
||||
{Array.from({ length: draftRounds }, (_, roundIndex) => {
|
||||
const round = roundIndex + 1;
|
||||
const isOddRound = round % 2 === 1;
|
||||
|
||||
return (
|
||||
<div key={round} className="flex border-b border-border hover:bg-muted/30">
|
||||
{/* Round Number */}
|
||||
<div className="w-16 flex-shrink-0 p-2 text-center font-medium text-sm border-r bg-muted/30">
|
||||
{round}
|
||||
</div>
|
||||
|
||||
{/* 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 (
|
||||
<div
|
||||
key={team.id}
|
||||
className={`flex-1 min-w-[140px] p-2 border-r last:border-r-0 ${
|
||||
isUserTeam ? "bg-primary/5" : ""
|
||||
} ${isCurrentPick ? "ring-2 ring-primary ring-inset" : ""}`}
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
{/* Pick Number */}
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{round}.{String(isOddRound ? teamIndex + 1 : teams.length - teamIndex).padStart(2, "0")}
|
||||
</div>
|
||||
|
||||
{/* Participant or Status */}
|
||||
{participant ? (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-medium text-sm truncate" title={participant.name}>
|
||||
{participant.name}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground truncate">
|
||||
{participant.sport}
|
||||
</div>
|
||||
{pick && pick.pickedByType !== "owner" && (
|
||||
<div className="text-xs text-muted-foreground italic">
|
||||
({pick.pickedByType})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : isCurrentPick ? (
|
||||
<div className="text-sm font-medium text-primary">
|
||||
On the clock
|
||||
</div>
|
||||
) : isPastPick ? (
|
||||
<div className="text-xs text-muted-foreground italic">
|
||||
Skipped
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
-
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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<ReturnTy
|
|||
const {
|
||||
league,
|
||||
season,
|
||||
teams,
|
||||
draftPicks,
|
||||
participants,
|
||||
isCommissioner,
|
||||
userTeam,
|
||||
currentPickNumber,
|
||||
|
|
@ -90,7 +102,7 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited<ReturnTy
|
|||
<div className="min-h-screen bg-background">
|
||||
{/* Header */}
|
||||
<header className="border-b bg-card">
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
<div className="px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">{league.name} Draft</h1>
|
||||
|
|
@ -115,7 +127,7 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited<ReturnTy
|
|||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="container mx-auto px-4 py-6">
|
||||
<main className="px-6 py-6">
|
||||
{isDraftComplete ? (
|
||||
<div className="rounded-lg border bg-card p-8 text-center">
|
||||
<h2 className="text-2xl font-bold">Draft Complete!</h2>
|
||||
|
|
@ -124,29 +136,31 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited<ReturnTy
|
|||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-6 lg:grid-cols-[1fr_400px]">
|
||||
{/* Left Column - Draft Grid & Player List */}
|
||||
<div className="space-y-6">
|
||||
{/* Draft Grid Placeholder */}
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<h2 className="mb-4 text-xl font-bold">Draft Board</h2>
|
||||
<div className="flex items-center justify-center py-12 text-muted-foreground">
|
||||
Draft grid will be implemented in Phase 5
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{/* Draft Board - Full Width */}
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<h2 className="mb-4 text-xl font-bold">Draft Board</h2>
|
||||
<DraftGrid
|
||||
teams={teams}
|
||||
draftPicks={draftPicks}
|
||||
participants={participants}
|
||||
draftRounds={season.draftRounds}
|
||||
currentPickNumber={currentPickNumber}
|
||||
userTeamId={userTeam?.id}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Player List Placeholder */}
|
||||
{/* 2x2 Grid Below */}
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{/* Available Players */}
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<h2 className="mb-4 text-xl font-bold">Available Players</h2>
|
||||
<div className="flex items-center justify-center py-12 text-muted-foreground">
|
||||
Player list will be implemented in Phase 6
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Queue & Controls */}
|
||||
<div className="space-y-6">
|
||||
{/* Queue Placeholder */}
|
||||
{/* Your Queue */}
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<h2 className="mb-4 text-xl font-bold">Your Queue</h2>
|
||||
<div className="flex items-center justify-center py-12 text-muted-foreground">
|
||||
|
|
@ -154,7 +168,7 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited<ReturnTy
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timer Placeholder */}
|
||||
{/* Draft Timer */}
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<h2 className="mb-4 text-xl font-bold">Draft Timer</h2>
|
||||
<div className="flex items-center justify-center py-12 text-muted-foreground">
|
||||
|
|
@ -163,13 +177,27 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited<ReturnTy
|
|||
</div>
|
||||
|
||||
{/* Commissioner Controls */}
|
||||
{isCommissioner && (
|
||||
{isCommissioner ? (
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<h2 className="mb-4 text-xl font-bold">Commissioner Controls</h2>
|
||||
<div className="flex items-center justify-center py-12 text-muted-foreground">
|
||||
Controls will be implemented in Phase 9
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-lg border bg-card p-6">
|
||||
<h2 className="mb-4 text-xl font-bold">Draft Info</h2>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Current Pick:</span>
|
||||
<span className="ml-2 font-medium">{currentPickNumber} of {totalPicks}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Draft Rounds:</span>
|
||||
<span className="ml-2 font-medium">{season.draftRounds}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue