* Show manager username instead of team name on draft board Both the read-only draft board and the active draft room now display the manager's username (falling back to displayName if no username is set, and to team name if the team is unassigned) in the column headers of the draft grid. https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1 * Address code review: extract ownerMap helper, fix TeamsDraftedGrid - Extract ownerMap building into app/lib/owner-map.ts to eliminate duplication across the two loaders - Guard against null username AND displayName so the map only contains real string values - Apply username display to TeamsDraftedGrid (the "Teams" tab in the active draft room), which was missed in the initial change https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1 --------- Co-authored-by: Claude <noreply@anthropic.com>
181 lines
6.2 KiB
TypeScript
181 lines
6.2 KiB
TypeScript
import { Card } from "~/components/ui/card";
|
|
import {
|
|
ContextMenu,
|
|
ContextMenuContent,
|
|
ContextMenuItem,
|
|
ContextMenuTrigger,
|
|
} from "~/components/ui/context-menu";
|
|
|
|
interface DraftGridProps {
|
|
draftSlots: Array<{
|
|
id: string;
|
|
draftOrder: number;
|
|
team: {
|
|
id: string;
|
|
name: string;
|
|
};
|
|
}>;
|
|
draftGrid: any[][];
|
|
currentPick: number;
|
|
teamTimers?: Record<string, number>;
|
|
formatTime?: (seconds: number | undefined) => string;
|
|
title?: string;
|
|
isCommissioner?: boolean;
|
|
onForceAutopick?: (pickNumber: number, teamId: string) => void;
|
|
onForceManualPick?: (pickNumber: number, teamId: string) => void;
|
|
autodraftStatus?: Record<string, boolean>;
|
|
connectedTeams?: Set<string>;
|
|
ownerMap?: Record<string, string>;
|
|
}
|
|
|
|
export function DraftGrid({
|
|
draftSlots,
|
|
draftGrid,
|
|
currentPick,
|
|
teamTimers,
|
|
formatTime,
|
|
title,
|
|
isCommissioner = false,
|
|
onForceAutopick,
|
|
onForceManualPick,
|
|
autodraftStatus = {},
|
|
connectedTeams = new Set(),
|
|
ownerMap = {},
|
|
}: DraftGridProps) {
|
|
const totalTeams = draftSlots.length;
|
|
|
|
return (
|
|
<Card className="p-4">
|
|
{title && <h2 className="text-xl font-semibold mb-4">{title}</h2>}
|
|
<div className="w-full">
|
|
{/* Team Headers */}
|
|
<div className="flex gap-2 mb-2">
|
|
{draftSlots.map((slot) => {
|
|
const teamTime = teamTimers?.[slot.team.id];
|
|
const isAutodraft = autodraftStatus[slot.team.id] || false;
|
|
const isConnected = connectedTeams.has(slot.team.id);
|
|
|
|
return (
|
|
<div key={slot.id} className="flex-1 min-w-32 text-center">
|
|
<div
|
|
className={`font-semibold text-sm truncate px-2 ${!isConnected ? "italic text-muted-foreground" : ""}`}
|
|
>
|
|
{ownerMap[slot.team.id] || slot.team.name}
|
|
</div>
|
|
{teamTimers && formatTime && (
|
|
<div
|
|
className={`text-xs font-mono ${
|
|
teamTime === undefined
|
|
? "text-muted-foreground"
|
|
: teamTime > 60
|
|
? "text-emerald-400"
|
|
: teamTime > 30
|
|
? "text-amber-accent"
|
|
: "text-coral-accent"
|
|
}`}
|
|
>
|
|
{formatTime(teamTime)}
|
|
{isAutodraft && (
|
|
<span className="ml-1 text-muted-foreground">(auto)</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</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">
|
|
{displayPicks.map((cell, index) => {
|
|
const actualIndex = isEvenRound
|
|
? roundPicks.length - 1 - index
|
|
: index;
|
|
const slot = draftSlots[actualIndex];
|
|
const pickNumber = roundIndex * totalTeams + actualIndex + 1;
|
|
const isCurrent = pickNumber === currentPick;
|
|
const isPicked = !!cell;
|
|
|
|
const cellContent = (
|
|
<div
|
|
className={`flex-1 min-w-0 h-20 border-2 rounded-lg p-2 transition-all ${
|
|
isPicked
|
|
? "bg-emerald-500/10 border-emerald-500/30"
|
|
: isCurrent
|
|
? "border-electric bg-electric/15 shadow-lg shadow-electric/10"
|
|
: "border-border bg-card"
|
|
}`}
|
|
title={`Overall Pick #${pickNumber}`}
|
|
>
|
|
<div className="text-xs font-mono text-muted-foreground mb-1">
|
|
{round}.{String(actualIndex + 1).padStart(2, "0")}
|
|
</div>
|
|
{isPicked ? (
|
|
<div className="text-xs">
|
|
<div className="font-semibold truncate">
|
|
{cell.participant.name}
|
|
</div>
|
|
<div className="text-muted-foreground truncate">
|
|
{cell.sport.name}
|
|
</div>
|
|
</div>
|
|
) : isCurrent ? (
|
|
<div className="text-xs font-semibold text-electric">
|
|
On Clock
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
|
|
// Wrap with context menu if commissioner and current unpicked cell
|
|
if (
|
|
isCommissioner &&
|
|
!isPicked &&
|
|
isCurrent &&
|
|
onForceAutopick &&
|
|
onForceManualPick
|
|
) {
|
|
return (
|
|
<ContextMenu key={pickNumber}>
|
|
<ContextMenuTrigger asChild>
|
|
{cellContent}
|
|
</ContextMenuTrigger>
|
|
<ContextMenuContent>
|
|
<ContextMenuItem
|
|
onClick={() =>
|
|
onForceAutopick(pickNumber, slot.team.id)
|
|
}
|
|
>
|
|
Force Auto Pick
|
|
</ContextMenuItem>
|
|
<ContextMenuItem
|
|
onClick={() =>
|
|
onForceManualPick(pickNumber, slot.team.id)
|
|
}
|
|
>
|
|
Force Manual Pick
|
|
</ContextMenuItem>
|
|
</ContextMenuContent>
|
|
</ContextMenu>
|
|
);
|
|
}
|
|
|
|
return cellContent;
|
|
})}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|