Address all code review issues for draft room context menus

Layout bug: flex-1 min-w-20 was nested inside headerInner instead of the
direct flex-child wrapper in MiniDraftGrid, breaking equal column sizing.
Fixed by moving the classes to the outermost element in both the menu and
non-menu paths (matching DraftGridSection's pattern).

MiniDraftGrid improvements:
- Hoist hasHeaderMenu constant above the draftSlots.map() loop
- Add optional connectedTeams prop; disconnected teams render italic +
  muted-foreground, consistent with DraftGridSection
- connectedTeams now wired through the route's miniDraftGrid useMemo

DraftGridSection interface:
- Make onForceAutopick and onForceManualPickOpen optional (?) to match
  every other commissioner callback; add null checks at all three call
  sites (context menu, mobile MoreVertical button, mobile Sheet)
- Gate those two callbacks behind isCommissioner at the route level

Tests (new files):
- app/components/__tests__/MiniDraftGrid.test.tsx — covers layout classes,
  connected/disconnected styling, and all four context menu interactions
- app/components/__tests__/DraftGridSection.test.tsx — covers all three
  context menu surfaces for both commissioner and non-commissioner roles

Storybook: add CommissionerView and DisconnectedTeams stories to
MiniDraftGrid.stories.tsx

https://claude.ai/code/session_017JCShLVs9xZ6FZmyrUFaE1
This commit is contained in:
Claude 2026-04-25 07:11:13 +00:00
parent 3cfd6ca654
commit a2cbedbb82
No known key found for this signature in database
6 changed files with 555 additions and 81 deletions

View file

@ -0,0 +1,198 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { DraftGridSection } from "~/components/draft/DraftGridSection";
const draftSlots = [
{ id: "slot-1", draftOrder: 1, team: { id: "team-1", name: "Alpha", logoUrl: null } },
{ id: "slot-2", draftOrder: 2, team: { id: "team-2", name: "Bravo", logoUrl: null } },
];
const ownerMap = { "team-1": "Alpha", "team-2": "Bravo" };
const draftGrid = [
[
{ pickNumber: 1, round: 1, pickInRound: 1, teamId: "team-1", pick: { participant: { name: "Player A" }, sport: { name: "NFL" } } },
{ pickNumber: 2, round: 1, pickInRound: 2, teamId: "team-2" },
],
];
const baseProps = {
draftSlots,
draftGrid,
currentPick: 2,
teamTimers: {} as Record<string, number | undefined>,
autodraftStatus: {} as Record<string, { isEnabled: boolean; mode: "next_pick" | "while_on"; queueOnly: boolean }>,
connectedTeams: new Set(["team-1", "team-2"]),
isCommissioner: false,
ownerMap,
seasonStatus: "draft" as const,
draftPaused: false,
};
describe("DraftGridSection", () => {
beforeEach(() => vi.clearAllMocks());
describe("Team header context menus", () => {
it("shows no header context menu for non-commissioner", () => {
render(<DraftGridSection {...baseProps} />);
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
expect(screen.queryByText("Adjust Time Bank...")).not.toBeInTheDocument();
expect(screen.queryByText("Set Autodraft...")).not.toBeInTheDocument();
});
it("shows Adjust Time Bank and Set Autodraft for commissioner on right-click", () => {
render(
<DraftGridSection
{...baseProps}
isCommissioner
onAdjustTimeBankOpen={vi.fn()}
onSetAutodraftOpen={vi.fn()}
/>
);
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
expect(screen.getByText("Adjust Time Bank...")).toBeInTheDocument();
expect(screen.getByText("Set Autodraft...")).toBeInTheDocument();
});
it("calls onAdjustTimeBankOpen with correct teamId", async () => {
const user = userEvent.setup();
const onAdjustTimeBankOpen = vi.fn();
render(
<DraftGridSection
{...baseProps}
isCommissioner
onAdjustTimeBankOpen={onAdjustTimeBankOpen}
/>
);
fireEvent.contextMenu(screen.getAllByText("Bravo")[0]);
await user.click(screen.getByText("Adjust Time Bank..."));
expect(onAdjustTimeBankOpen).toHaveBeenCalledWith("team-2");
});
it("calls onSetAutodraftOpen with correct teamId", async () => {
const user = userEvent.setup();
const onSetAutodraftOpen = vi.fn();
render(
<DraftGridSection
{...baseProps}
isCommissioner
onSetAutodraftOpen={onSetAutodraftOpen}
/>
);
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
await user.click(screen.getByText("Set Autodraft..."));
expect(onSetAutodraftOpen).toHaveBeenCalledWith("team-1");
});
});
describe("Current cell context menu", () => {
it("shows no context menu for non-commissioner on current cell", () => {
render(
<DraftGridSection
{...baseProps}
onForceAutopick={vi.fn()}
onForceManualPickOpen={vi.fn()}
/>
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
expect(screen.queryByText("Force Auto Pick")).not.toBeInTheDocument();
});
it("shows Force Auto Pick and Force Manual Pick for commissioner", () => {
render(
<DraftGridSection
{...baseProps}
isCommissioner
onForceAutopick={vi.fn()}
onForceManualPickOpen={vi.fn()}
/>
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
expect(screen.getByText("Force Auto Pick")).toBeInTheDocument();
expect(screen.getByText("Force Manual Pick")).toBeInTheDocument();
});
it("shows no force-pick menu when callbacks absent even for commissioner", () => {
render(<DraftGridSection {...baseProps} isCommissioner />);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
expect(screen.queryByText("Force Auto Pick")).not.toBeInTheDocument();
});
it("calls onForceAutopick with correct args", async () => {
const user = userEvent.setup();
const onForceAutopick = vi.fn();
render(
<DraftGridSection {...baseProps} isCommissioner onForceAutopick={onForceAutopick} />
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
await user.click(screen.getByText("Force Auto Pick"));
expect(onForceAutopick).toHaveBeenCalledWith(2, "team-2");
});
it("calls onForceManualPickOpen with correct args", async () => {
const user = userEvent.setup();
const onForceManualPickOpen = vi.fn();
render(
<DraftGridSection
{...baseProps}
isCommissioner
onForceManualPickOpen={onForceManualPickOpen}
/>
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
await user.click(screen.getByText("Force Manual Pick"));
expect(onForceManualPickOpen).toHaveBeenCalledWith(2, "team-2");
});
});
describe("Picked cell context menu", () => {
it("shows Replace Pick and Roll Back for commissioner on a picked cell", () => {
render(
<DraftGridSection
{...baseProps}
isCommissioner
onReplacePick={vi.fn()}
onRollbackToPick={vi.fn()}
/>
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
expect(screen.getByText("Replace Pick")).toBeInTheDocument();
expect(screen.getByText("Roll Back to This Pick")).toBeInTheDocument();
});
it("shows no context menu on picked cell for non-commissioner", () => {
render(
<DraftGridSection
{...baseProps}
onReplacePick={vi.fn()}
onRollbackToPick={vi.fn()}
/>
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
expect(screen.queryByText("Replace Pick")).not.toBeInTheDocument();
});
it("calls onReplacePick with correct args", async () => {
const user = userEvent.setup();
const onReplacePick = vi.fn();
render(
<DraftGridSection {...baseProps} isCommissioner onReplacePick={onReplacePick} />
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
await user.click(screen.getByText("Replace Pick"));
expect(onReplacePick).toHaveBeenCalledWith(1, "team-1");
});
it("calls onRollbackToPick with correct pickNumber", async () => {
const user = userEvent.setup();
const onRollbackToPick = vi.fn();
render(
<DraftGridSection {...baseProps} isCommissioner onRollbackToPick={onRollbackToPick} />
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
await user.click(screen.getByText("Roll Back to This Pick"));
expect(onRollbackToPick).toHaveBeenCalledWith(1);
});
});
});

View file

@ -0,0 +1,237 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MiniDraftGrid } from "~/components/draft/MiniDraftGrid";
const draftSlots = [
{ id: "slot-1", draftOrder: 1, team: { id: "team-1", name: "Alpha", logoUrl: null } },
{ id: "slot-2", draftOrder: 2, team: { id: "team-2", name: "Bravo", logoUrl: null } },
];
const ownerMap = { "team-1": "Alpha", "team-2": "Bravo" };
// Round 1: team-1 pick 1 (filled), team-2 pick 2 (current/empty)
// Round 2 (snake): team-2 pick 3, team-1 pick 4 — both empty
const draftGrid = [
[
{ pickNumber: 1, round: 1, pickInRound: 1, teamId: "team-1", pick: { participant: { name: "Player A" }, sport: { name: "NFL" } } },
{ pickNumber: 2, round: 1, pickInRound: 2, teamId: "team-2" },
],
[
{ pickNumber: 3, round: 2, pickInRound: 1, teamId: "team-2" },
{ pickNumber: 4, round: 2, pickInRound: 2, teamId: "team-1" },
],
];
const baseProps = {
draftSlots,
draftGrid,
currentPick: 2,
currentRound: 1,
ownerMap,
};
describe("MiniDraftGrid", () => {
beforeEach(() => vi.clearAllMocks());
describe("Team header layout", () => {
it("renders all team names", () => {
render(<MiniDraftGrid {...baseProps} />);
expect(screen.getAllByText("Alpha").length).toBeGreaterThan(0);
expect(screen.getAllByText("Bravo").length).toBeGreaterThan(0);
});
it("non-commissioner headers carry flex sizing classes", () => {
render(<MiniDraftGrid {...baseProps} />);
const alphaHeader = screen.getAllByText("Alpha")[0].closest(".flex-1");
expect(alphaHeader).toHaveClass("flex-1", "min-w-20");
});
it("commissioner headers carry flex sizing classes", () => {
render(
<MiniDraftGrid
{...baseProps}
onAdjustTimeBankOpen={vi.fn()}
onSetAutodraftOpen={vi.fn()}
/>
);
const alphaHeader = screen.getAllByText("Alpha")[0].closest(".flex-1");
expect(alphaHeader).toHaveClass("flex-1", "min-w-20");
});
});
describe("Disconnected team styling", () => {
it("applies italic + muted style to disconnected teams when connectedTeams provided", () => {
render(
<MiniDraftGrid
{...baseProps}
connectedTeams={new Set(["team-1"])}
/>
);
const bravoSpan = screen.getAllByText("Bravo")[0];
expect(bravoSpan).toHaveClass("italic", "text-muted-foreground");
const alphaSpan = screen.getAllByText("Alpha")[0];
expect(alphaSpan).not.toHaveClass("italic");
});
it("treats all teams as connected when connectedTeams is not provided", () => {
render(<MiniDraftGrid {...baseProps} />);
expect(screen.getAllByText("Alpha")[0]).not.toHaveClass("italic");
expect(screen.getAllByText("Bravo")[0]).not.toHaveClass("italic");
});
});
describe("Team header context menus (commissioner)", () => {
it("shows no context menu when callbacks are absent", () => {
render(<MiniDraftGrid {...baseProps} />);
const alphaHeader = screen.getAllByText("Alpha")[0];
fireEvent.contextMenu(alphaHeader);
expect(screen.queryByText("Adjust Time Bank...")).not.toBeInTheDocument();
expect(screen.queryByText("Set Autodraft...")).not.toBeInTheDocument();
});
it("shows Adjust Time Bank and Set Autodraft on right-click when both callbacks provided", () => {
render(
<MiniDraftGrid
{...baseProps}
onAdjustTimeBankOpen={vi.fn()}
onSetAutodraftOpen={vi.fn()}
/>
);
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
expect(screen.getByText("Adjust Time Bank...")).toBeInTheDocument();
expect(screen.getByText("Set Autodraft...")).toBeInTheDocument();
});
it("shows only Adjust Time Bank when only that callback is provided", () => {
render(
<MiniDraftGrid {...baseProps} onAdjustTimeBankOpen={vi.fn()} />
);
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
expect(screen.getByText("Adjust Time Bank...")).toBeInTheDocument();
expect(screen.queryByText("Set Autodraft...")).not.toBeInTheDocument();
});
it("calls onAdjustTimeBankOpen with the correct teamId", async () => {
const user = userEvent.setup();
const onAdjustTimeBankOpen = vi.fn();
render(
<MiniDraftGrid {...baseProps} onAdjustTimeBankOpen={onAdjustTimeBankOpen} />
);
fireEvent.contextMenu(screen.getAllByText("Bravo")[0]);
await user.click(screen.getByText("Adjust Time Bank..."));
expect(onAdjustTimeBankOpen).toHaveBeenCalledWith("team-2");
});
it("calls onSetAutodraftOpen with the correct teamId", async () => {
const user = userEvent.setup();
const onSetAutodraftOpen = vi.fn();
render(
<MiniDraftGrid {...baseProps} onSetAutodraftOpen={onSetAutodraftOpen} />
);
fireEvent.contextMenu(screen.getAllByText("Alpha")[0]);
await user.click(screen.getByText("Set Autodraft..."));
expect(onSetAutodraftOpen).toHaveBeenCalledWith("team-1");
});
});
describe("Current cell context menu (commissioner)", () => {
it("shows Force Auto Pick and Force Manual Pick on right-click of current cell", () => {
render(
<MiniDraftGrid
{...baseProps}
onForceAutopick={vi.fn()}
onForceManualPickOpen={vi.fn()}
/>
);
// Pick 2 is the current cell (team-2, round 1)
const currentCell = screen.getByTitle("Overall Pick #2");
fireEvent.contextMenu(currentCell);
expect(screen.getByText("Force Auto Pick")).toBeInTheDocument();
expect(screen.getByText("Force Manual Pick")).toBeInTheDocument();
});
it("calls onForceAutopick with correct args", async () => {
const user = userEvent.setup();
const onForceAutopick = vi.fn();
render(
<MiniDraftGrid {...baseProps} onForceAutopick={onForceAutopick} />
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
await user.click(screen.getByText("Force Auto Pick"));
expect(onForceAutopick).toHaveBeenCalledWith(2, "team-2");
});
it("calls onForceManualPickOpen with correct args", async () => {
const user = userEvent.setup();
const onForceManualPickOpen = vi.fn();
render(
<MiniDraftGrid {...baseProps} onForceManualPickOpen={onForceManualPickOpen} />
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
await user.click(screen.getByText("Force Manual Pick"));
expect(onForceManualPickOpen).toHaveBeenCalledWith(2, "team-2");
});
it("shows no force-pick menu when no callbacks provided", () => {
render(<MiniDraftGrid {...baseProps} />);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #2"));
expect(screen.queryByText("Force Auto Pick")).not.toBeInTheDocument();
expect(screen.queryByText("Force Manual Pick")).not.toBeInTheDocument();
});
});
describe("Picked cell context menu (commissioner)", () => {
it("shows Replace Pick and Roll Back on right-click of a picked cell", () => {
render(
<MiniDraftGrid
{...baseProps}
onReplacePick={vi.fn()}
onRollbackToPick={vi.fn()}
/>
);
// Pick 1 is already picked (team-1)
const pickedCell = screen.getByTitle("Overall Pick #1");
fireEvent.contextMenu(pickedCell);
expect(screen.getByText("Replace Pick")).toBeInTheDocument();
expect(screen.getByText("Roll Back to This Pick")).toBeInTheDocument();
});
it("calls onReplacePick with correct args", async () => {
const user = userEvent.setup();
const onReplacePick = vi.fn();
render(
<MiniDraftGrid {...baseProps} onReplacePick={onReplacePick} />
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
await user.click(screen.getByText("Replace Pick"));
expect(onReplacePick).toHaveBeenCalledWith(1, "team-1");
});
it("calls onRollbackToPick with correct pickNumber", async () => {
const user = userEvent.setup();
const onRollbackToPick = vi.fn();
render(
<MiniDraftGrid {...baseProps} onRollbackToPick={onRollbackToPick} />
);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
await user.click(screen.getByText("Roll Back to This Pick"));
expect(onRollbackToPick).toHaveBeenCalledWith(1);
});
it("shows no menu on picked cell when no callbacks provided", () => {
render(<MiniDraftGrid {...baseProps} />);
fireEvent.contextMenu(screen.getByTitle("Overall Pick #1"));
expect(screen.queryByText("Replace Pick")).not.toBeInTheDocument();
});
});
describe("Empty grid", () => {
it("renders nothing when draftGrid is empty", () => {
const { container } = render(
<MiniDraftGrid {...baseProps} draftGrid={[]} />
);
expect(container.firstChild).toBeNull();
});
});
});

View file

@ -58,8 +58,8 @@ interface DraftGridSectionProps {
draftPaused?: boolean;
onAdjustTimeBankOpen?: (teamId: string) => void;
onSetAutodraftOpen?: (teamId: string) => void;
onForceAutopick: (pickNumber: number, teamId: string) => void;
onForceManualPickOpen: (pickNumber: number, 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>;
@ -196,7 +196,7 @@ export const DraftGridSection = memo(function DraftGridSection({
const mobileButtons = isCommissioner && (
<>
{!isPicked && isCurrent && (
{!isPicked && isCurrent && (onForceAutopick || onForceManualPickOpen) && (
<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 })}
@ -215,7 +215,7 @@ export const DraftGridSection = memo(function DraftGridSection({
</>
);
if (isCommissioner && !isPicked && isCurrent) {
if (isCommissioner && !isPicked && isCurrent && (onForceAutopick || onForceManualPickOpen)) {
return (
<ContextMenu key={cell.pickNumber}>
<ContextMenuTrigger asChild>
@ -234,23 +234,16 @@ export const DraftGridSection = memo(function DraftGridSection({
</DraftPickCell>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
onClick={() =>
onForceAutopick(cell.pickNumber, cell.teamId)
}
>
Force Auto Pick
</ContextMenuItem>
<ContextMenuItem
onClick={() =>
onForceManualPickOpen(
cell.pickNumber,
cell.teamId
)
}
>
Force Manual Pick
</ContextMenuItem>
{onForceAutopick && (
<ContextMenuItem onClick={() => onForceAutopick(cell.pickNumber, cell.teamId)}>
Force Auto Pick
</ContextMenuItem>
)}
{onForceManualPickOpen && (
<ContextMenuItem onClick={() => onForceManualPickOpen(cell.pickNumber, cell.teamId)}>
Force Manual Pick
</ContextMenuItem>
)}
</ContextMenuContent>
</ContextMenu>
);
@ -354,23 +347,27 @@ export const DraftGridSection = memo(function DraftGridSection({
)}
{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>
{onForceAutopick && (
<Button
onClick={() => {
onForceAutopick(mobileSheet.pickNumber, mobileSheet.teamId);
setMobileSheet(null);
}}
>
Force Auto Pick
</Button>
)}
{onForceManualPickOpen && (
<Button
variant="outline"
onClick={() => {
onForceManualPickOpen(mobileSheet.pickNumber, mobileSheet.teamId);
setMobileSheet(null);
}}
>
Force Manual Pick
</Button>
)}
</>
)}
{mobileSheet?.type === "picked-cell" && (

View file

@ -143,3 +143,33 @@ export const EmptyGrid: Story = {
ownerMap,
},
};
export const CommissionerView: Story = {
name: "Commissioner — All Actions Available (right-click headers and cells)",
args: {
draftSlots,
draftGrid: fullGrid,
currentPick: 6,
currentRound: 2,
ownerMap,
connectedTeams: new Set(["team-1", "team-2", "team-3"]), // team-4 disconnected
onAdjustTimeBankOpen: (teamId) => alert(`Adjust time bank for ${teamId}`),
onSetAutodraftOpen: (teamId) => alert(`Set autodraft for ${teamId}`),
onForceAutopick: (pickNumber, teamId) => alert(`Force autopick ${pickNumber} for ${teamId}`),
onForceManualPickOpen: (pickNumber, teamId) => alert(`Force manual pick ${pickNumber} for ${teamId}`),
onReplacePick: (pickNumber, teamId) => alert(`Replace pick ${pickNumber} for ${teamId}`),
onRollbackToPick: (pickNumber) => alert(`Roll back to pick ${pickNumber}`),
},
};
export const DisconnectedTeams: Story = {
name: "Disconnected Teams",
args: {
draftSlots,
draftGrid: fullGrid,
currentPick: 3,
currentRound: 1,
ownerMap,
connectedTeams: new Set(["team-1"]), // team-2 through team-4 disconnected
},
};

View file

@ -48,6 +48,7 @@ export interface MiniDraftGridProps {
ownerMap?: Record<string, string>;
teamTimers?: Record<string, number | undefined>;
autodraftStatus?: Record<string, { isEnabled: boolean }>;
connectedTeams?: Set<string>;
seasonStatus?: SeasonStatus;
draftPaused?: boolean;
onAdjustTimeBankOpen?: (teamId: string) => void;
@ -66,6 +67,7 @@ export const MiniDraftGrid = memo(function MiniDraftGrid({
ownerMap = {},
teamTimers = {},
autodraftStatus = {},
connectedTeams,
seasonStatus,
draftPaused,
onAdjustTimeBankOpen,
@ -152,51 +154,60 @@ export const MiniDraftGrid = memo(function MiniDraftGrid({
<div className="inline-block min-w-full">
{/* Team header */}
<div className="flex gap-1.5 mb-1.5">
{draftSlots.map((slot) => {
const teamTime = teamTimers[slot.team.id];
const isAutodraft = autodraftStatus[slot.team.id]?.isEnabled ?? false;
{(() => {
const hasHeaderMenu = !!(onAdjustTimeBankOpen || onSetAutodraftOpen);
return draftSlots.map((slot) => {
const teamTime = teamTimers[slot.team.id];
const isAutodraft = autodraftStatus[slot.team.id]?.isEnabled ?? false;
const isConnected = connectedTeams ? connectedTeams.has(slot.team.id) : true;
const headerInner = (
<div className="flex-1 min-w-20 text-center">
<div className="text-xs font-medium truncate px-1 flex items-center justify-center gap-0.5">
{isAutodraft && (
<span className="inline-flex items-center justify-center h-3.5 w-3.5 text-[9px] font-bold text-white bg-black shrink-0">A</span>
)}
<span className="truncate">{ownerMap[slot.team.id] || slot.team.name}</span>
</div>
<div className={`text-xs font-mono px-1 ${getTimerColorClass(teamTime)}`}>
{formatClockTime(teamTime)}
</div>
</div>
);
if (hasHeaderMenu) {
return (
<ContextMenu key={slot.id}>
<ContextMenuTrigger asChild>
<div className="cursor-context-menu">
{headerInner}
</div>
</ContextMenuTrigger>
<ContextMenuContent>
{onAdjustTimeBankOpen && (
<ContextMenuItem onClick={() => onAdjustTimeBankOpen(slot.team.id)}>
Adjust Time Bank...
</ContextMenuItem>
const headerContent = (
<>
<div className="text-xs font-medium truncate px-1 flex items-center justify-center gap-0.5">
{isAutodraft && (
<span className="inline-flex items-center justify-center h-3.5 w-3.5 text-[9px] font-bold text-white bg-black shrink-0">A</span>
)}
{onSetAutodraftOpen && (
<ContextMenuItem onClick={() => onSetAutodraftOpen(slot.team.id)}>
Set Autodraft...
</ContextMenuItem>
)}
</ContextMenuContent>
</ContextMenu>
<span className={`truncate${!isConnected ? " italic text-muted-foreground" : ""}`}>
{ownerMap[slot.team.id] || slot.team.name}
</span>
</div>
<div className={`text-xs font-mono px-1 ${getTimerColorClass(teamTime)}`}>
{formatClockTime(teamTime)}
</div>
</>
);
}
return <div key={slot.id}>{headerInner}</div>;
})}
if (hasHeaderMenu) {
return (
<ContextMenu key={slot.id}>
<ContextMenuTrigger asChild>
<div className="flex-1 min-w-20 text-center cursor-context-menu">
{headerContent}
</div>
</ContextMenuTrigger>
<ContextMenuContent>
{onAdjustTimeBankOpen && (
<ContextMenuItem onClick={() => onAdjustTimeBankOpen(slot.team.id)}>
Adjust Time Bank...
</ContextMenuItem>
)}
{onSetAutodraftOpen && (
<ContextMenuItem onClick={() => onSetAutodraftOpen(slot.team.id)}>
Set Autodraft...
</ContextMenuItem>
)}
</ContextMenuContent>
</ContextMenu>
);
}
return (
<div key={slot.id} className="flex-1 min-w-20 text-center">
{headerContent}
</div>
);
});
})()}
</div>
{/* Rows — clipped to 2-row height, slides to reveal new round */}

View file

@ -953,6 +953,7 @@ export default function DraftRoom() {
ownerMap,
teamTimers,
autodraftStatus,
connectedTeams,
seasonStatus: season.status,
draftPaused: isPaused,
onAdjustTimeBankOpen: isCommissioner ? handleAdjustTimeBankOpen : undefined,
@ -961,7 +962,7 @@ export default function DraftRoom() {
onForceManualPickOpen: isCommissioner ? handleForceManualPickOpen : undefined,
onReplacePick: isCommissioner ? handleReplacePickOpen : undefined,
onRollbackToPick: isCommissioner && !isDraftComplete ? handleRollbackToPick : undefined,
}), [draftSlots, draftGrid, currentPick, currentRound, ownerMap, teamTimers, autodraftStatus, season.status, isPaused, isCommissioner, handleAdjustTimeBankOpen, handleSetAutodraftOpen, handleForceAutopick, handleForceManualPickOpen, handleReplacePickOpen, handleRollbackToPick, isDraftComplete]);
}), [draftSlots, draftGrid, currentPick, currentRound, ownerMap, teamTimers, autodraftStatus, connectedTeams, season.status, isPaused, isCommissioner, handleAdjustTimeBankOpen, handleSetAutodraftOpen, handleForceAutopick, handleForceManualPickOpen, handleReplacePickOpen, handleRollbackToPick, isDraftComplete]);
const availableParticipantsSectionProps = useMemo(() => ({
participants: filteredParticipants,
@ -1003,8 +1004,8 @@ export default function DraftRoom() {
draftPaused: isPaused,
onAdjustTimeBankOpen: isCommissioner ? handleAdjustTimeBankOpen : undefined,
onSetAutodraftOpen: isCommissioner ? handleSetAutodraftOpen : undefined,
onForceAutopick: handleForceAutopick,
onForceManualPickOpen: handleForceManualPickOpen,
onForceAutopick: isCommissioner ? handleForceAutopick : undefined,
onForceManualPickOpen: isCommissioner ? handleForceManualPickOpen : undefined,
onReplacePick: isCommissioner ? handleReplacePickOpen : undefined,
onRollbackToPick: isCommissioner && !isDraftComplete ? handleRollbackToPick : undefined,
};