More draft room fixes.

This commit is contained in:
Chris Parsons 2026-04-17 23:38:29 -07:00
parent 44ad954dfe
commit a4eaec865b
4 changed files with 346 additions and 294 deletions

View file

@ -1,12 +1,7 @@
import { useState } from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "~/components/ui/accordion";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
interface DraftSidebarProps { interface DraftSidebarProps {
@ -26,17 +21,19 @@ export function DraftSidebar({
settingsSection, settingsSection,
className, className,
}: DraftSidebarProps) { }: DraftSidebarProps) {
const [queueOpen, setQueueOpen] = useState(true);
const [picksOpen, setPicksOpen] = useState(true);
if (collapsed) { if (collapsed) {
return ( return (
<div <div
className={cn( className={cn(
"relative flex-shrink-0 bg-card border-r border-border transition-all duration-300 flex flex-col", "relative flex-shrink-0 bg-card border-r border-border transition-all duration-300 flex flex-col h-full",
"w-12 hidden lg:block", // Hide completely on mobile when collapsed "w-12 hidden lg:flex",
className className
)} )}
> >
<div className="flex-1" /> <div className="flex-1" />
{/* Expand button at bottom to match hide button position */}
<div className="border-t border-border p-2 flex-shrink-0"> <div className="border-t border-border p-2 flex-shrink-0">
<Button <Button
variant="ghost" variant="ghost"
@ -65,45 +62,50 @@ export function DraftSidebar({
className={cn( className={cn(
"relative flex-shrink-0 bg-card border-r border-border transition-all duration-300 flex flex-col", "relative flex-shrink-0 bg-card border-r border-border transition-all duration-300 flex flex-col",
"w-[300px]", "w-[300px]",
// Mobile: fixed overlay, Desktop: normal sidebar
"fixed inset-y-0 left-0 z-50 lg:relative lg:z-auto", "fixed inset-y-0 left-0 z-50 lg:relative lg:z-auto",
className className
)} )}
> >
<Accordion <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
type="multiple"
defaultValue={["queue", "recent-picks"]}
className="flex-1 overflow-hidden flex flex-col"
>
{/* Queue Section */} {/* Queue Section */}
<AccordionItem value="queue" className="border-b flex-shrink-0"> <div className={cn("flex flex-col border-b", queueOpen ? "flex-1 min-h-0" : "flex-shrink-0")}>
<AccordionTrigger className="px-4 py-3 hover:no-underline bg-muted/50 hover:bg-muted"> <button
onClick={() => setQueueOpen((o) => !o)}
className="px-4 py-3 bg-muted/50 hover:bg-muted flex items-center justify-between w-full flex-shrink-0"
>
<h2 className="font-semibold text-sm">My Queue</h2> <h2 className="font-semibold text-sm">My Queue</h2>
</AccordionTrigger> <ChevronDown className={cn("h-4 w-4 transition-transform", queueOpen && "rotate-180")} />
<AccordionContent className="max-h-[35vh] overflow-y-auto pb-0"> </button>
{queueSection} {queueOpen && (
</AccordionContent> <div className="flex-1 overflow-y-auto min-h-0">
</AccordionItem> {queueSection}
</div>
)}
</div>
{/* Recent Picks Section */} {/* Picks Section */}
<AccordionItem value="recent-picks" className="border-0"> <div className={cn("flex flex-col", picksOpen ? "flex-1 min-h-0" : "flex-shrink-0")}>
<AccordionTrigger className="px-4 py-3 hover:no-underline bg-muted/50 hover:bg-muted"> <button
onClick={() => setPicksOpen((o) => !o)}
className="px-4 py-3 bg-muted/50 hover:bg-muted flex items-center justify-between w-full flex-shrink-0"
>
<h2 className="font-semibold text-sm">Picks</h2> <h2 className="font-semibold text-sm">Picks</h2>
</AccordionTrigger> <ChevronDown className={cn("h-4 w-4 transition-transform", picksOpen && "rotate-180")} />
<AccordionContent className="pb-0 overflow-y-auto max-h-[45vh]"> </button>
{recentPicksSection} {picksOpen && (
</AccordionContent> <div className="flex-1 overflow-y-auto min-h-0">
</AccordionItem> {recentPicksSection}
</Accordion> </div>
)}
</div>
</div>
{/* Settings section (e.g. notification toggle) */}
{settingsSection && ( {settingsSection && (
<div className="border-t border-border px-4 py-3 flex-shrink-0"> <div className="border-t border-border px-4 py-3 flex-shrink-0">
{settingsSection} {settingsSection}
</div> </div>
)} )}
{/* Collapse button at bottom */}
<div className="border-t border-border p-2 flex-shrink-0"> <div className="border-t border-border p-2 flex-shrink-0">
<Button <Button
variant="ghost" variant="ghost"

View file

@ -3,7 +3,7 @@ import { useVirtualizer } from "@tanstack/react-virtual";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { Checkbox } from "~/components/ui/checkbox"; import { Checkbox } from "~/components/ui/checkbox";
import { MiniDraftGrid } from "~/components/draft/MiniDraftGrid"; import { MiniDraftGrid, type MiniDraftGridProps } from "~/components/draft/MiniDraftGrid";
import { import {
Popover, Popover,
PopoverTrigger, PopoverTrigger,
@ -133,32 +133,7 @@ interface AvailableParticipantsSectionProps {
}; };
}>; }>;
participantRanks: Map<string, { overallRank: number; sportRank: number }>; participantRanks: Map<string, { overallRank: number; sportRank: number }>;
miniDraftGrid?: { miniDraftGrid?: MiniDraftGridProps;
draftSlots: Array<{
id: string;
draftOrder: number;
team: { id: string; name: string; logoUrl?: string | null };
}>;
draftGrid: Array<
Array<{
pickNumber: number;
round: number;
pickInRound: number;
teamId: string;
pick?: {
participant: { name: string };
sport: { name: string };
};
}>
>;
currentPick: number;
currentRound: number;
ownerMap?: Record<string, string>;
onForceAutopick?: (pickNumber: number, teamId: string) => void;
onForceManualPickOpen?: (pickNumber: number, teamId: string) => void;
onReplacePick?: (pickNumber: number, teamId: string) => void;
onRollbackToPick?: (pickNumber: number) => void;
};
searchQuery: string; searchQuery: string;
sportFilters: string[]; sportFilters: string[];
hideDrafted: boolean; hideDrafted: boolean;
@ -293,116 +268,111 @@ export const AvailableParticipantsSection = memo(function AvailableParticipantsS
</div> </div>
)} )}
<div className="px-4 pt-4 pb-2 flex-shrink-0"> <div className="px-4 pt-4 pb-2 flex-shrink-0">
<div className="space-y-2"> <div className="flex flex-wrap gap-2 items-center">
<input <input
type="text" type="text"
placeholder="Search participants..." placeholder="Search participants..."
value={searchQuery} value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)} onChange={(e) => onSearchChange(e.target.value)}
className="w-full px-3 py-2 border rounded-md text-base md:text-sm bg-background text-foreground" className="flex-1 min-w-0 px-3 py-2 border rounded-md text-base md:text-sm bg-background text-foreground"
/> />
<div className="flex flex-wrap gap-2"> <div className="md:hidden shrink-0">
<div className="flex-1 min-w-[120px]"> <Sheet>
<div className="md:hidden"> <SheetTrigger asChild>
<Sheet> <Button
<SheetTrigger asChild> variant="outline"
<Button aria-label={triggerAriaLabel}
variant="outline" className="justify-between text-base font-normal"
aria-label={triggerAriaLabel} >
className="w-full justify-between text-base font-normal" <span className="truncate">{triggerText}</span>
> <ChevronDown className="h-4 w-4 shrink-0 opacity-50" />
<span className="truncate">{triggerText}</span> </Button>
<ChevronDown className="h-4 w-4 shrink-0 opacity-50" /> </SheetTrigger>
</Button> <SheetContent side="bottom" className="max-h-[70vh] pt-12" aria-describedby={undefined}>
</SheetTrigger> <SheetTitle className="sr-only">Filter by sport</SheetTitle>
<SheetContent side="bottom" className="max-h-[70vh] pt-12" aria-describedby={undefined}> <SportFilterContent
<SheetTitle className="sr-only">Filter by sport</SheetTitle> sportsForDropdown={sportsForDropdown}
<SportFilterContent sportFilterSet={sportFilterSet}
sportsForDropdown={sportsForDropdown} hideCompletedSports={hideCompletedSports}
sportFilterSet={sportFilterSet} hasTeam={hasTeam}
hideCompletedSports={hideCompletedSports} variant="sheet"
hasTeam={hasTeam} onToggleSport={handleToggleSport}
variant="sheet" onHideCompletedSportsChange={onHideCompletedSportsChange}
onToggleSport={handleToggleSport}
onHideCompletedSportsChange={onHideCompletedSportsChange}
/>
<SheetFooter className="px-4 pb-8 flex-row gap-2">
{hasActiveFilters && (
<Button
variant="outline"
className="flex-1"
onClick={handleReset}
>
Reset
</Button>
)}
<SheetClose asChild>
<Button className="flex-1">Done</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
<div className="hidden md:block">
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
aria-label={triggerAriaLabel}
className="w-full justify-between text-sm font-normal"
>
<span className="truncate">{triggerText}</span>
<ChevronDown className="h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-72 p-0" align="start">
{hasActiveFilters && (
<div className="flex justify-end px-2 pt-2">
<Button
variant="ghost"
size="sm"
className="h-auto px-2 py-1 text-xs"
onClick={handleReset}
>
Reset
</Button>
</div>
)}
<SportFilterContent
sportsForDropdown={sportsForDropdown}
sportFilterSet={sportFilterSet}
hideCompletedSports={hideCompletedSports}
hasTeam={hasTeam}
variant="popover"
onToggleSport={handleToggleSport}
onHideCompletedSportsChange={onHideCompletedSportsChange}
/>
</PopoverContent>
</Popover>
</div>
</div>
<label className="flex items-center gap-2 text-sm cursor-pointer whitespace-nowrap px-3 py-2 border rounded-md">
<Checkbox
checked={!hideDrafted}
onCheckedChange={(checked) => onHideDraftedChange(checked === false)}
/>
<span>Show Drafted</span>
</label>
{hasTeam && eligibility && (
<label className="flex items-center gap-2 text-sm cursor-pointer whitespace-nowrap px-3 py-2 border rounded-md">
<Checkbox
checked={!hideIneligible}
onCheckedChange={(checked) => onHideIneligibleChange(checked === false)}
/> />
<span>Show Ineligible</span> <SheetFooter className="px-4 pb-8 flex-row gap-2">
</label> {hasActiveFilters && (
)} <Button
variant="outline"
className="flex-1"
onClick={handleReset}
>
Reset
</Button>
)}
<SheetClose asChild>
<Button className="flex-1">Done</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
</div> </div>
<div className="hidden md:block shrink-0">
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
aria-label={triggerAriaLabel}
className="w-[160px] justify-between text-sm font-normal"
>
<span className="truncate">{triggerText}</span>
<ChevronDown className="h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-72 p-0" align="start">
{hasActiveFilters && (
<div className="flex justify-end px-2 pt-2">
<Button
variant="ghost"
size="sm"
className="h-auto px-2 py-1 text-xs"
onClick={handleReset}
>
Reset
</Button>
</div>
)}
<SportFilterContent
sportsForDropdown={sportsForDropdown}
sportFilterSet={sportFilterSet}
hideCompletedSports={hideCompletedSports}
hasTeam={hasTeam}
variant="popover"
onToggleSport={handleToggleSport}
onHideCompletedSportsChange={onHideCompletedSportsChange}
/>
</PopoverContent>
</Popover>
</div>
<label className="flex items-center gap-2 text-sm cursor-pointer whitespace-nowrap px-3 py-2 border rounded-md shrink-0">
<Checkbox
checked={!hideDrafted}
onCheckedChange={(checked) => onHideDraftedChange(checked === false)}
/>
<span>Show Drafted</span>
</label>
{hasTeam && eligibility && (
<label className="flex items-center gap-2 text-sm cursor-pointer whitespace-nowrap px-3 py-2 border rounded-md shrink-0">
<Checkbox
checked={!hideIneligible}
onCheckedChange={(checked) => onHideIneligibleChange(checked === false)}
/>
<span>Show Ineligible</span>
</label>
)}
</div> </div>
</div> </div>

View file

@ -1,4 +1,4 @@
import { memo, useMemo } from "react"; import { memo, useState, useEffect, useRef } from "react";
import { DraftPickCell } from "~/components/draft/DraftPickCell"; import { DraftPickCell } from "~/components/draft/DraftPickCell";
import type { SeasonStatus } from "~/models/season"; import type { SeasonStatus } from "~/models/season";
import { import {
@ -7,8 +7,17 @@ import {
ContextMenuItem, ContextMenuItem,
ContextMenuTrigger, ContextMenuTrigger,
} from "~/components/ui/context-menu"; } from "~/components/ui/context-menu";
import { formatClockTime, getTimerColorClass } from "~/lib/draft-timer";
interface MiniDraftGridProps { const ROW_GAP = 6; // gap-1.5
const ANIMATION_MS = 300;
function getRoundIndices(round: number): [number, number] {
if (round <= 2) return [0, 1];
return [round - 2, round - 1];
}
export interface MiniDraftGridProps {
draftSlots: Array<{ draftSlots: Array<{
id: string; id: string;
draftOrder: number; draftOrder: number;
@ -37,6 +46,8 @@ interface MiniDraftGridProps {
currentPick: number; currentPick: number;
currentRound: number; currentRound: number;
ownerMap?: Record<string, string>; ownerMap?: Record<string, string>;
teamTimers?: Record<string, number | undefined>;
autodraftStatus?: Record<string, { isEnabled: boolean }>;
seasonStatus?: SeasonStatus; seasonStatus?: SeasonStatus;
draftPaused?: boolean; draftPaused?: boolean;
onForceAutopick?: (pickNumber: number, teamId: string) => void; onForceAutopick?: (pickNumber: number, teamId: string) => void;
@ -51,6 +62,8 @@ export const MiniDraftGrid = memo(function MiniDraftGrid({
currentPick, currentPick,
currentRound, currentRound,
ownerMap = {}, ownerMap = {},
teamTimers = {},
autodraftStatus = {},
seasonStatus, seasonStatus,
draftPaused, draftPaused,
onForceAutopick, onForceAutopick,
@ -58,139 +71,204 @@ export const MiniDraftGrid = memo(function MiniDraftGrid({
onReplacePick, onReplacePick,
onRollbackToPick, onRollbackToPick,
}: MiniDraftGridProps) { }: MiniDraftGridProps) {
const roundsToShow = useMemo(() => { const [displayedIndices, setDisplayedIndices] = useState<[number, number]>(() => getRoundIndices(currentRound));
if (currentRound <= 1) return [0, 1]; const [extraIndex, setExtraIndex] = useState<number | null>(null);
return [currentRound - 2, currentRound - 1]; const [sliding, setSliding] = useState(false);
const animGenRef = useRef(0);
const prevRoundRef = useRef(currentRound);
const firstRowRef = useRef<HTMLDivElement>(null);
const [rowHeight, setRowHeight] = useState(0);
useEffect(() => {
if (firstRowRef.current) {
setRowHeight(firstRowRef.current.getBoundingClientRect().height);
}
}, [draftGrid]);
useEffect(() => {
const prev = prevRoundRef.current;
prevRoundRef.current = currentRound;
const newIndices = getRoundIndices(currentRound);
const prevIndices = getRoundIndices(prev);
if (newIndices[0] === prevIndices[0]) return;
// Non-sequential jump or animation already running: snap and invalidate any in-flight animation
if (newIndices[0] !== prevIndices[0] + 1 || animGenRef.current > 0) {
animGenRef.current++; // invalidate any in-flight animation closure
setDisplayedIndices(newIndices);
setExtraIndex(null);
setSliding(false);
return;
}
const gen = ++animGenRef.current;
setExtraIndex(newIndices[1]);
// Two rAFs: first lets React flush the extra row to DOM, second starts the CSS transition
requestAnimationFrame(() => {
requestAnimationFrame(() => {
setSliding(true);
setTimeout(() => {
if (animGenRef.current !== gen) return; // interrupted by snap
setDisplayedIndices(newIndices);
setExtraIndex(null);
setSliding(false);
animGenRef.current = 0;
}, ANIMATION_MS + 20);
});
});
}, [currentRound]); }, [currentRound]);
if (draftGrid.length === 0) return null; if (draftGrid.length === 0) return null;
// h-14 (56px) + gap-1.5 (6px) fallback until first measurement
const effectiveRowHeight = rowHeight > 0 ? rowHeight : 56;
const roundIndicesToRender: number[] = extraIndex !== null
? [displayedIndices[0], displayedIndices[1], extraIndex]
: [displayedIndices[0], displayedIndices[1]];
return ( return (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<div className="inline-block min-w-full"> <div className="inline-block min-w-full">
{/* Team header */}
<div className="flex gap-1.5 mb-1.5"> <div className="flex gap-1.5 mb-1.5">
<div className="w-7 flex-shrink-0" /> {draftSlots.map((slot) => {
{draftSlots.map((slot) => ( const teamTime = teamTimers[slot.team.id];
<div key={slot.id} className="flex-1 min-w-20 text-center"> const isAutodraft = autodraftStatus[slot.team.id]?.isEnabled ?? false;
<div className="text-xs font-medium truncate px-1 text-muted-foreground">
{ownerMap[slot.team.id] || slot.team.name}
</div>
</div>
))}
</div>
<div className="space-y-1.5">
{roundsToShow.map((roundIndex) => {
if (roundIndex >= draftGrid.length) return null;
const roundPicks = draftGrid[roundIndex];
const round = roundIndex + 1;
const isEvenRound = round % 2 === 0;
const displayPicks = isEvenRound
? [...roundPicks].toReversed()
: roundPicks;
return ( return (
<div key={round} className="flex gap-1.5 items-stretch"> <div key={slot.id} className="flex-1 min-w-20 text-center">
<div className="w-7 flex-shrink-0 flex items-center justify-center"> <div className="text-xs font-medium truncate px-1 flex items-center justify-center gap-0.5">
<span className="text-xs font-mono text-muted-foreground">R{round}</span> {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>
{displayPicks.map((cell) => {
const isCurrent = cell.pickNumber === currentPick;
const isPicked = !!cell.pick;
const cellState = isPicked
? "picked"
: isCurrent
? "current"
: "upcoming";
const pickData = cell.pick
? {
participant: { name: cell.pick.participant.name },
sport: { name: cell.pick.sport.name },
}
: undefined;
if (!isPicked && isCurrent && (onForceAutopick || onForceManualPickOpen)) {
return (
<ContextMenu key={cell.pickNumber}>
<ContextMenuTrigger asChild>
<DraftPickCell
pickNumber={cell.pickNumber}
round={cell.round}
pickInRound={cell.pickInRound}
state={cellState}
pick={pickData}
seasonStatus={seasonStatus}
draftPaused={draftPaused}
className="[&>div]:h-10 cursor-context-menu"
/>
</ContextMenuTrigger>
<ContextMenuContent>
{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>
);
}
if (isPicked && (onReplacePick || onRollbackToPick)) {
return (
<ContextMenu key={cell.pickNumber}>
<ContextMenuTrigger asChild>
<DraftPickCell
pickNumber={cell.pickNumber}
round={cell.round}
pickInRound={cell.pickInRound}
state={cellState}
pick={pickData}
seasonStatus={seasonStatus}
draftPaused={draftPaused}
className="[&>div]:h-10 cursor-context-menu"
/>
</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 (
<DraftPickCell
key={cell.pickNumber}
pickNumber={cell.pickNumber}
round={cell.round}
pickInRound={cell.pickInRound}
state={cellState}
pick={pickData}
seasonStatus={seasonStatus}
draftPaused={draftPaused}
className="[&>div]:h-10"
/>
);
})}
</div> </div>
); );
})} })}
</div> </div>
{/* Rows — clipped to 2-row height, slides to reveal new round */}
<div style={{ height: `${effectiveRowHeight * 2 + ROW_GAP}px`, overflow: "hidden" }}>
<div
style={{
display: "flex",
flexDirection: "column",
gap: `${ROW_GAP}px`,
transform: sliding ? `translateY(-${effectiveRowHeight + ROW_GAP}px)` : "translateY(0)",
transition: sliding ? `transform ${ANIMATION_MS}ms ease-in-out` : "none",
}}
>
{roundIndicesToRender.map((roundIndex) => {
if (roundIndex >= draftGrid.length) return null;
const roundPicks = draftGrid[roundIndex];
const round = roundIndex + 1;
const isEvenRound = round % 2 === 0;
const displayPicks = isEvenRound ? [...roundPicks].toReversed() : roundPicks;
return (
<div key={round} ref={roundIndex === displayedIndices[0] ? firstRowRef : undefined} className="flex gap-1.5 items-stretch flex-shrink-0">
{displayPicks.map((cell) => {
const isCurrent = cell.pickNumber === currentPick;
const isPicked = !!cell.pick;
const cellState = isPicked ? "picked" : isCurrent ? "current" : "upcoming";
const pickData = cell.pick
? {
participant: { name: cell.pick.participant.name },
sport: { name: cell.pick.sport.name },
}
: undefined;
if (!isPicked && isCurrent && (onForceAutopick || onForceManualPickOpen)) {
return (
<ContextMenu key={cell.pickNumber}>
<ContextMenuTrigger asChild>
<DraftPickCell
pickNumber={cell.pickNumber}
round={cell.round}
pickInRound={cell.pickInRound}
state={cellState}
pick={pickData}
seasonStatus={seasonStatus}
draftPaused={draftPaused}
className="cursor-context-menu"
/>
</ContextMenuTrigger>
<ContextMenuContent>
{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>
);
}
if (isPicked && (onReplacePick || onRollbackToPick)) {
return (
<ContextMenu key={cell.pickNumber}>
<ContextMenuTrigger asChild>
<DraftPickCell
pickNumber={cell.pickNumber}
round={cell.round}
pickInRound={cell.pickInRound}
state={cellState}
pick={pickData}
seasonStatus={seasonStatus}
draftPaused={draftPaused}
className="cursor-context-menu"
/>
</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 (
<DraftPickCell
key={cell.pickNumber}
pickNumber={cell.pickNumber}
round={cell.round}
pickInRound={cell.pickInRound}
state={cellState}
pick={pickData}
seasonStatus={seasonStatus}
draftPaused={draftPaused}
/>
);
})}
</div>
);
})}
</div>
</div>
</div> </div>
</div> </div>
); );

View file

@ -539,7 +539,7 @@ export default function DraftRoom() {
const stored = localStorage.getItem("draftSidebarCollapsed"); const stored = localStorage.getItem("draftSidebarCollapsed");
return stored ? JSON.parse(stored) : false; return stored ? JSON.parse(stored) : false;
}); });
const [activeTab, setActiveTab] = useState<"participants" | "board" | "rosters" | "summary">("board"); const [activeTab, setActiveTab] = useState<"participants" | "board" | "rosters" | "summary">("participants");
const [mobileTab, setMobileTab] = useState<"available" | "queue" | "board" | "teams" | "controls">( const [mobileTab, setMobileTab] = useState<"available" | "queue" | "board" | "teams" | "controls">(
!userTeam && isCommissioner ? "board" : "available" !userTeam && isCommissioner ? "board" : "available"
); );
@ -1509,13 +1509,15 @@ export default function DraftRoom() {
currentPick, currentPick,
currentRound, currentRound,
ownerMap, ownerMap,
teamTimers,
autodraftStatus,
seasonStatus: season.status, seasonStatus: season.status,
draftPaused: isPaused, draftPaused: isPaused,
onForceAutopick: isCommissioner ? handleForceAutopick : undefined, onForceAutopick: isCommissioner ? handleForceAutopick : undefined,
onForceManualPickOpen: isCommissioner ? handleForceManualPickOpen : undefined, onForceManualPickOpen: isCommissioner ? handleForceManualPickOpen : undefined,
onReplacePick: isCommissioner ? handleReplacePickOpen : undefined, onReplacePick: isCommissioner ? handleReplacePickOpen : undefined,
onRollbackToPick: isCommissioner && !isDraftComplete ? handleRollbackToPick : undefined, onRollbackToPick: isCommissioner && !isDraftComplete ? handleRollbackToPick : undefined,
}), [draftSlots, draftGrid, currentPick, currentRound, ownerMap, season.status, isPaused, isCommissioner, handleForceAutopick, handleForceManualPickOpen, handleReplacePickOpen, handleRollbackToPick, isDraftComplete]); }), [draftSlots, draftGrid, currentPick, currentRound, ownerMap, teamTimers, autodraftStatus, season.status, isPaused, isCommissioner, handleForceAutopick, handleForceManualPickOpen, handleReplacePickOpen, handleRollbackToPick, isDraftComplete]);
const availableParticipantsSectionProps = useMemo(() => ({ const availableParticipantsSectionProps = useMemo(() => ({
participants: filteredParticipants, participants: filteredParticipants,
@ -1738,7 +1740,7 @@ export default function DraftRoom() {
} }
> >
<TabsList> <TabsList>
<TabsTrigger value="participants">Available Participants</TabsTrigger> <TabsTrigger value="participants">Participants</TabsTrigger>
<TabsTrigger value="board">Draft Board</TabsTrigger> <TabsTrigger value="board">Draft Board</TabsTrigger>
<TabsTrigger value="rosters">Rosters</TabsTrigger> <TabsTrigger value="rosters">Rosters</TabsTrigger>
<TabsTrigger value="summary">Summary</TabsTrigger> <TabsTrigger value="summary">Summary</TabsTrigger>
@ -1852,19 +1854,19 @@ export default function DraftRoom() {
/> />
)} )}
<div className="flex-1 overflow-hidden"> <div className="flex-1 overflow-hidden relative">
{activeTab === "participants" && ( <div className={`absolute inset-0 ${activeTab === "participants" ? "" : "hidden"}`}>
<AvailableParticipantsSection {...availableParticipantsSectionProps} /> <AvailableParticipantsSection {...availableParticipantsSectionProps} />
)} </div>
{activeTab === "board" && ( <div className={`absolute inset-0 ${activeTab === "board" ? "" : "hidden"}`}>
<DraftGridSection {...draftGridSectionProps} /> <DraftGridSection {...draftGridSectionProps} />
)} </div>
{activeTab === "rosters" && ( <div className={`absolute inset-0 ${activeTab === "rosters" ? "" : "hidden"}`}>
<TeamRosterView {...rosterViewProps} /> <TeamRosterView {...rosterViewProps} />
)} </div>
{activeTab === "summary" && ( <div className={`absolute inset-0 ${activeTab === "summary" ? "" : "hidden"}`}>
<DraftSummaryView {...summaryViewProps} /> <DraftSummaryView {...summaryViewProps} />
)} </div>
</div> </div>
</div> </div>
)} )}