feat: add draft queue component and data fetching to draft room page
This commit is contained in:
parent
013edbc661
commit
8a61085f3b
2 changed files with 138 additions and 3 deletions
125
app/components/draft/DraftQueue.tsx
Normal file
125
app/components/draft/DraftQueue.tsx
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
import { GripVertical, X } from "lucide-react";
|
||||||
|
|
||||||
|
interface Participant {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
sport: string;
|
||||||
|
expectedValue: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueueItem {
|
||||||
|
id: string;
|
||||||
|
participantId: string;
|
||||||
|
queuePosition: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DraftQueueProps {
|
||||||
|
queueItems: QueueItem[];
|
||||||
|
participants: Participant[];
|
||||||
|
onReorder?: (participantIds: string[]) => void;
|
||||||
|
onRemove?: (queueItemId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DraftQueue({
|
||||||
|
queueItems,
|
||||||
|
participants,
|
||||||
|
onReorder,
|
||||||
|
onRemove,
|
||||||
|
}: DraftQueueProps) {
|
||||||
|
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
// Create a map for quick participant lookup
|
||||||
|
const participantMap = new Map(participants.map((p) => [p.id, p]));
|
||||||
|
|
||||||
|
// Sort queue items by position
|
||||||
|
const sortedQueue = [...queueItems].sort((a, b) => a.queuePosition - b.queuePosition);
|
||||||
|
|
||||||
|
const handleDragStart = (index: number) => {
|
||||||
|
setDraggedIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e: React.DragEvent, index: number) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (draggedIndex === null || draggedIndex === index) return;
|
||||||
|
|
||||||
|
// Reorder the items
|
||||||
|
const newQueue = [...sortedQueue];
|
||||||
|
const draggedItem = newQueue[draggedIndex];
|
||||||
|
newQueue.splice(draggedIndex, 1);
|
||||||
|
newQueue.splice(index, 0, draggedItem);
|
||||||
|
|
||||||
|
// Update positions and notify parent
|
||||||
|
const participantIds = newQueue.map((item) => item.participantId);
|
||||||
|
onReorder?.(participantIds);
|
||||||
|
setDraggedIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnd = () => {
|
||||||
|
setDraggedIndex(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sortedQueue.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
|
<p>Your queue is empty</p>
|
||||||
|
<p className="text-sm mt-2">Add players from the Available Players list</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{sortedQueue.map((item, index) => {
|
||||||
|
const participant = participantMap.get(item.participantId);
|
||||||
|
if (!participant) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
draggable
|
||||||
|
onDragStart={() => handleDragStart(index)}
|
||||||
|
onDragOver={(e) => handleDragOver(e, index)}
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
className={`flex items-center gap-2 p-3 rounded-lg border bg-card transition-all ${
|
||||||
|
draggedIndex === index ? "opacity-50" : "hover:bg-accent cursor-move"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* Drag Handle */}
|
||||||
|
<div className="flex items-center gap-2 text-muted-foreground">
|
||||||
|
<GripVertical className="h-4 w-4" />
|
||||||
|
<span className="text-sm font-semibold w-6">{index + 1}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Player Info */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="font-medium truncate">{participant.name}</div>
|
||||||
|
<div className="flex items-center gap-2 mt-1">
|
||||||
|
<Badge variant="secondary" className="text-xs">
|
||||||
|
{participant.sport}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
EV: {participant.expectedValue.toFixed(1)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Remove Button */}
|
||||||
|
{onRemove && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onRemove(item.id)}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -10,9 +10,11 @@ import {
|
||||||
getDraftPicks,
|
getDraftPicks,
|
||||||
getSeasonTimers,
|
getSeasonTimers,
|
||||||
findSeasonWithSportsSeasons,
|
findSeasonWithSportsSeasons,
|
||||||
|
getTeamQueue,
|
||||||
} from "~/models";
|
} from "~/models";
|
||||||
import { DraftGrid } from "~/components/draft/DraftGrid";
|
import { DraftGrid } from "~/components/draft/DraftGrid";
|
||||||
import { PlayerList } from "~/components/draft/PlayerList";
|
import { PlayerList } from "~/components/draft/PlayerList";
|
||||||
|
import { DraftQueue } from "~/components/draft/DraftQueue";
|
||||||
|
|
||||||
export async function loader(args: LoaderFunctionArgs) {
|
export async function loader(args: LoaderFunctionArgs) {
|
||||||
const { userId } = await getAuth(args);
|
const { userId } = await getAuth(args);
|
||||||
|
|
@ -68,6 +70,9 @@ export async function loader(args: LoaderFunctionArgs) {
|
||||||
// Get draft data
|
// Get draft data
|
||||||
const draftPicks = await getDraftPicks(season.id);
|
const draftPicks = await getDraftPicks(season.id);
|
||||||
const timers = await getSeasonTimers(season.id);
|
const timers = await getSeasonTimers(season.id);
|
||||||
|
|
||||||
|
// Get user's queue if they have a team
|
||||||
|
const queueItems = userTeam ? await getTeamQueue(userTeam.id) : [];
|
||||||
|
|
||||||
// Calculate current pick info
|
// Calculate current pick info
|
||||||
const totalPicks = season.draftRounds * teams.length;
|
const totalPicks = season.draftRounds * teams.length;
|
||||||
|
|
@ -83,6 +88,7 @@ export async function loader(args: LoaderFunctionArgs) {
|
||||||
userTeam,
|
userTeam,
|
||||||
draftPicks,
|
draftPicks,
|
||||||
participants,
|
participants,
|
||||||
|
queueItems,
|
||||||
timers,
|
timers,
|
||||||
currentPickNumber,
|
currentPickNumber,
|
||||||
totalPicks,
|
totalPicks,
|
||||||
|
|
@ -98,6 +104,7 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited<ReturnTy
|
||||||
teams,
|
teams,
|
||||||
draftPicks,
|
draftPicks,
|
||||||
participants,
|
participants,
|
||||||
|
queueItems,
|
||||||
isCommissioner,
|
isCommissioner,
|
||||||
userTeam,
|
userTeam,
|
||||||
currentPickNumber,
|
currentPickNumber,
|
||||||
|
|
@ -170,10 +177,13 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited<ReturnTy
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Your Queue */}
|
{/* Your Queue */}
|
||||||
<div className="rounded-lg border bg-card p-6">
|
<div className="rounded-lg border bg-card p-6 flex flex-col" style={{ height: "600px" }}>
|
||||||
<h2 className="mb-4 text-xl font-bold">Your Queue</h2>
|
<h2 className="mb-4 text-xl font-bold">Your Queue</h2>
|
||||||
<div className="flex items-center justify-center py-12 text-muted-foreground">
|
<div className="flex-1 overflow-y-auto">
|
||||||
Queue will be implemented in Phase 7
|
<DraftQueue
|
||||||
|
queueItems={queueItems}
|
||||||
|
participants={participants}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue