brackt/app/components/draft/DraftGridSection.tsx

381 lines
14 KiB
TypeScript
Raw Normal View History

import { useMemo, useState } from "react";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from "~/components/ui/context-menu";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from "~/components/ui/sheet";
import { Button } from "~/components/ui/button";
import { MoreVertical } from "lucide-react";
import { formatClockTime, getTimerColorClass } from "~/lib/draft-timer";
type MobileSheetData =
| { type: "team"; teamId: string }
| { type: "current-cell"; pickNumber: number; teamId: string }
| { type: "picked-cell"; pickNumber: number; teamId: string };
interface DraftGridSectionProps {
draftSlots: Array<{
id: string;
draftOrder: number;
team: {
id: string;
name: string;
};
}>;
draftGrid: Array<
Array<{
pickNumber: number;
round: number;
pickInRound: number;
teamId: string;
pick?: {
participant: {
name: string;
};
sport: {
name: string;
};
};
}>
>;
currentPick: number;
teamTimers: Record<string, number | undefined>;
autodraftStatus: Record<string, boolean>;
connectedTeams: Set<string>;
isCommissioner: boolean;
onAdjustTimeBankOpen?: (teamId: string) => void;
onForceAutopick: (pickNumber: number, teamId: string) => void;
onForceManualPickOpen: (pickNumber: number, teamId: string) => void;
onReplacePick?: (pickNumber: number, teamId: string) => void;
onRollbackToPick?: (pickNumber: number) => void;
ownerMap?: Record<string, string>;
}
export function DraftGridSection({
draftSlots,
draftGrid,
currentPick,
teamTimers,
autodraftStatus,
connectedTeams,
isCommissioner,
onAdjustTimeBankOpen,
onForceAutopick,
onForceManualPickOpen,
onReplacePick,
onRollbackToPick,
ownerMap = {},
}: DraftGridSectionProps) {
const [mobileSheet, setMobileSheet] = useState<MobileSheetData | null>(null);
const currentTeamId = useMemo(
() => draftGrid.flat().find((c) => c.pickNumber === currentPick)?.teamId ?? null,
[draftGrid, currentPick]
);
return (
<div className="h-full overflow-auto p-4">
<h2 className="text-xl font-semibold mb-4">Draft Grid</h2>
<div className="overflow-x-auto">
<div className="inline-block min-w-full">
{/* Team Headers */}
<div className="flex gap-2 mb-2 sticky top-0 z-10 bg-background/95 backdrop-blur-sm py-1">
{/* Spacer for round column — sticky so it covers the corner when scrolling both axes */}
<div className="w-8 flex-shrink-0 sticky left-0 z-[6] bg-background/95" />
{draftSlots.map((slot) => {
const teamTime = teamTimers[slot.team.id];
const isAutodraft = autodraftStatus[slot.team.id] || false;
const isConnected = connectedTeams.has(slot.team.id);
const headerInner = (
<div className="relative">
<div
className={`font-semibold text-sm truncate px-2 ${
slot.team.id === currentTeamId
? "text-electric font-bold"
: !isConnected
? "italic text-muted-foreground"
: ""
}`}
>
{ownerMap[slot.team.id] || slot.team.name}
</div>
<div
className={`text-xs font-mono px-2 ${getTimerColorClass(teamTime)}`}
>
{formatClockTime(teamTime)}
{isAutodraft && (
<span className="ml-1 text-muted-foreground">
(auto)
</span>
)}
</div>
{isCommissioner && onAdjustTimeBankOpen && (
<button
className="absolute top-0 right-0 md:hidden p-1 min-w-[32px] min-h-[32px] flex items-center justify-center rounded hover:bg-muted"
onClick={() => setMobileSheet({ type: "team", teamId: slot.team.id })}
>
<MoreVertical className="h-3.5 w-3.5" />
</button>
)}
</div>
);
if (isCommissioner && onAdjustTimeBankOpen) {
return (
<ContextMenu key={slot.id}>
<ContextMenuTrigger asChild>
<div className="flex-1 min-w-32 text-center cursor-context-menu">
{headerInner}
</div>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
onClick={() => onAdjustTimeBankOpen(slot.team.id)}
>
Adjust Time Bank...
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
}
return (
<div key={slot.id} className="flex-1 min-w-32 text-center">
{headerInner}
</div>
);
})}
</div>
{/* Draft Grid Rows */}
<div className="space-y-2">
{draftGrid.map((roundPicks, roundIndex) => {
const round = roundIndex + 1;
const isEvenRound = round % 2 === 0;
const displayPicks = isEvenRound
? [...roundPicks].reverse()
: roundPicks;
return (
<div key={roundIndex} className="flex gap-2 items-stretch">
{/* Round label */}
<div className="w-8 flex-shrink-0 sticky left-0 z-[5] bg-background/95 flex items-center justify-center">
<span className="text-xs font-mono text-muted-foreground">R{round}</span>
</div>
{displayPicks.map((cell) => {
const isCurrent = cell.pickNumber === currentPick;
const isPicked = !!cell.pick;
const cellClassName = `relative flex-1 min-w-32 h-20 border-2 rounded-lg p-2 transition-all ${
isCurrent
? "border-electric bg-electric/20 shadow-lg shadow-electric/25 ring-1 ring-electric/40"
: isPicked
? "bg-emerald-500/10 border-emerald-500/30"
: "border-border bg-card"
}`;
const cellInner = (
<>
<div className="text-xs font-mono text-muted-foreground mb-1">
{cell.round}.
{String(cell.pickInRound).padStart(2, "0")}
</div>
{isPicked ? (
<div className="text-xs">
<div className="font-semibold truncate">
{cell.pick?.participant.name}
</div>
<div className="text-muted-foreground truncate">
{cell.pick?.sport.name}
</div>
</div>
) : isCurrent ? (
<div className="text-sm font-bold text-electric animate-pulse">
On Clock
</div>
) : null}
{/* Mobile commissioner button */}
{isCommissioner && !isPicked && isCurrent && (
<button
className="absolute top-1 right-1 md:hidden p-1 min-w-[32px] min-h-[32px] flex items-center justify-center rounded hover:bg-muted"
onClick={() => setMobileSheet({ type: "current-cell", pickNumber: cell.pickNumber, teamId: cell.teamId })}
>
<MoreVertical className="h-3.5 w-3.5" />
</button>
)}
{isCommissioner && isPicked && (onReplacePick || onRollbackToPick) && (
<button
className="absolute top-1 right-1 md:hidden p-1 min-w-[32px] min-h-[32px] flex items-center justify-center rounded hover:bg-muted"
onClick={() => setMobileSheet({ type: "picked-cell", pickNumber: cell.pickNumber, teamId: cell.teamId })}
>
<MoreVertical className="h-3.5 w-3.5" />
</button>
)}
</>
);
// Commissioner context menu on the current unpicked cell
if (isCommissioner && !isPicked && isCurrent) {
return (
<ContextMenu key={cell.pickNumber}>
<ContextMenuTrigger asChild>
<div
className={cellClassName}
title={`Overall Pick #${cell.pickNumber}`}
>
{cellInner}
</div>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
onClick={() =>
onForceAutopick(cell.pickNumber, cell.teamId)
}
>
Force Auto Pick
</ContextMenuItem>
<ContextMenuItem
onClick={() =>
onForceManualPickOpen(
cell.pickNumber,
cell.teamId
)
}
>
Force Manual Pick
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
}
// Commissioner context menu on already-picked cells
if (isCommissioner && isPicked && (onReplacePick || onRollbackToPick)) {
return (
<ContextMenu key={cell.pickNumber}>
<ContextMenuTrigger asChild>
<div
className={cellClassName}
title={`Overall Pick #${cell.pickNumber}`}
>
{cellInner}
</div>
</ContextMenuTrigger>
<ContextMenuContent>
{onReplacePick && (
<ContextMenuItem
onClick={() =>
onReplacePick(cell.pickNumber, cell.teamId)
}
>
Replace Pick
</ContextMenuItem>
)}
{onRollbackToPick && (
<ContextMenuItem
onClick={() => onRollbackToPick(cell.pickNumber)}
className="text-destructive focus:text-destructive"
>
Roll Back to This Pick
</ContextMenuItem>
)}
</ContextMenuContent>
</ContextMenu>
);
}
return (
<div
key={cell.pickNumber}
className={cellClassName}
title={`Overall Pick #${cell.pickNumber}`}
>
{cellInner}
</div>
);
})}
</div>
);
})}
</div>
</div>
</div>
{/* Mobile Commissioner Sheet */}
<Sheet open={!!mobileSheet} onOpenChange={(open) => !open && setMobileSheet(null)}>
<SheetContent side="bottom" className="pb-8">
<SheetHeader>
<SheetTitle>Commissioner Actions</SheetTitle>
</SheetHeader>
<div className="flex flex-col gap-3 p-4">
{mobileSheet?.type === "team" && onAdjustTimeBankOpen && (
<Button
onClick={() => {
onAdjustTimeBankOpen(mobileSheet.teamId);
setMobileSheet(null);
}}
>
Adjust Time Bank...
</Button>
)}
{mobileSheet?.type === "current-cell" && (
<>
<Button
onClick={() => {
onForceAutopick(mobileSheet.pickNumber, mobileSheet.teamId);
setMobileSheet(null);
}}
>
Force Auto Pick
</Button>
<Button
variant="outline"
onClick={() => {
onForceManualPickOpen(mobileSheet.pickNumber, mobileSheet.teamId);
setMobileSheet(null);
}}
>
Force Manual Pick
</Button>
</>
)}
{mobileSheet?.type === "picked-cell" && (
<>
{onReplacePick && (
<Button
onClick={() => {
onReplacePick(mobileSheet.pickNumber, mobileSheet.teamId);
setMobileSheet(null);
}}
>
Replace Pick
</Button>
)}
{onRollbackToPick && (
<Button
variant="destructive"
onClick={() => {
onRollbackToPick(mobileSheet.pickNumber);
setMobileSheet(null);
}}
>
Roll Back to This Pick
</Button>
)}
</>
)}
</div>
</SheetContent>
</Sheet>
</div>
);
}