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:
parent
3cfd6ca654
commit
a2cbedbb82
6 changed files with 555 additions and 81 deletions
198
app/components/__tests__/DraftGridSection.test.tsx
Normal file
198
app/components/__tests__/DraftGridSection.test.tsx
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
237
app/components/__tests__/MiniDraftGrid.test.tsx
Normal file
237
app/components/__tests__/MiniDraftGrid.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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" && (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue