brackt/app/components/AutodraftSettings.tsx
Chris Parsons 1ba50828f7
Claude/redesign autodraft queue c4 kp r (#40)
* Redesign autodraft queue system with three-state control and queue-only constraint

Core Logic & Database:
- Add `queue_only` boolean column to `autodraft_settings` (migration 0031)
- Rename autodraft UI states: Off / Next Pick / All Picks (while_on mode maps to All Picks)
- `autoPickForTeam`: respects new `queueOnly` param — skips EV fallback when enabled
- `executeAutoPick`: auto-disables autodraft + emits socket event when queue empties with queueOnly ON (AC3)
- `autodraft-updated` socket event now includes `queueOnly` field

Mobile UI Overhaul:
- Rename "Lobby" tab → "Available" (AC6)
- Add new "Queue" tab to mobile bottom nav with drag-reorder, per-item Draft buttons, and autodraft controls (AC5)
- Controls tab retains commissioner tools, notifications, exit; queue controls moved to Queue tab
- Turn indicator appears on both Available and Queue tabs

Components:
- `AutodraftSettings`: replaces toggle+radio with three-state button group (Off | Next Pick | All Picks) + "Only autodraft from queue" switch (AC1, AC2)
- `QueueSection`: adds `canPick` prop + per-item Draft buttons for instant drafting when on the clock

Desktop (AC4):
- Sidebar QueueSection unchanged in position; gains same three-state controls and Draft buttons

Tests (AC7):
- `autodraft.test.ts`: updated for queueOnly field and socket event shape
- `timer-autodraft.test.ts`: new tests for queue-only constraint, auto-shutoff transitions, and all three autodraft states

https://claude.ai/code/session_01PYhJicAStoJ2u6q6dV1naB

* fix: remove erroneous ?? fallbacks in autodraft socket emissions and add autoPickForTeam tests

- Remove `?? true` default on queueOnly in queue-empty auto-disable socket emit
  (line 488) — was dead code since the column is NOT NULL, but semantically wrong
  and would have caused client-side UI desync if the type ever relaxed
- Remove `?? false` default on the next_pick auto-disable path for consistency
- Add app/models/__tests__/auto-pick.test.ts with 6 tests covering the queueOnly
  constraint: empty queue, all items drafted, partial queue skip, and EV fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add missing queueOnly prop to AutodraftSettings test fixtures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: rewrite AutodraftSettings tests for three-state button group UI

The component was redesigned from a switch + radio buttons to Off/Next Pick/All Picks
buttons with a separate queue-only Switch toggle. Updated 17 stale tests and added 5
new tests covering the queue-only toggle and the All Picks/Off button interactions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-27 22:16:26 -08:00

159 lines
5 KiB
TypeScript

import { useState, useEffect } from "react";
import { Switch } from "~/components/ui/switch";
import { Label } from "~/components/ui/label";
import { Button } from "~/components/ui/button";
type AutodraftMode = "next_pick" | "while_on";
// The three visible states map to isEnabled + mode combinations:
// "off" → isEnabled: false
// "next_pick" → isEnabled: true, mode: "next_pick"
// "all_picks" → isEnabled: true, mode: "while_on"
type AutodraftState = "off" | "next_pick" | "all_picks";
function toAutodraftState(isEnabled: boolean, mode: AutodraftMode): AutodraftState {
if (!isEnabled) return "off";
return mode === "next_pick" ? "next_pick" : "all_picks";
}
interface AutodraftSettingsProps {
seasonId: string;
teamId: string;
isEnabled: boolean;
mode: AutodraftMode;
queueOnly: boolean;
isMyTurn: boolean;
onUpdate: (isEnabled: boolean, mode: AutodraftMode, queueOnly: boolean) => void;
}
export function AutodraftSettings({
seasonId,
teamId,
isEnabled,
mode,
queueOnly,
isMyTurn,
onUpdate,
}: AutodraftSettingsProps) {
const [localState, setLocalState] = useState<AutodraftState>(toAutodraftState(isEnabled, mode));
const [localQueueOnly, setLocalQueueOnly] = useState(queueOnly);
const [isUpdating, setIsUpdating] = useState(false);
// Sync local state with props when they change (from socket events)
useEffect(() => {
setLocalState(toAutodraftState(isEnabled, mode));
}, [isEnabled, mode]);
useEffect(() => {
setLocalQueueOnly(queueOnly);
}, [queueOnly]);
const sendUpdate = async (newState: AutodraftState, newQueueOnly: boolean) => {
if (isUpdating) return;
setIsUpdating(true);
const newEnabled = newState !== "off";
const newMode: AutodraftMode = newState === "all_picks" ? "while_on" : "next_pick";
try {
const formData = new FormData();
formData.append("seasonId", seasonId);
formData.append("teamId", teamId);
formData.append("isEnabled", newEnabled.toString());
formData.append("mode", newMode);
formData.append("queueOnly", newQueueOnly.toString());
const response = await fetch("/api/autodraft/update", {
method: "POST",
body: formData,
});
if (response.ok) {
onUpdate(newEnabled, newMode, newQueueOnly);
} else {
// Revert on error
setLocalState(toAutodraftState(isEnabled, mode));
setLocalQueueOnly(queueOnly);
console.error("Failed to update autodraft settings");
}
} catch (error) {
// Revert on error
setLocalState(toAutodraftState(isEnabled, mode));
setLocalQueueOnly(queueOnly);
console.error("Error updating autodraft settings:", error);
} finally {
setIsUpdating(false);
}
};
const handleStateChange = (newState: AutodraftState) => {
if (isMyTurn || isUpdating) return;
setLocalState(newState);
sendUpdate(newState, localQueueOnly);
};
const handleQueueOnlyChange = async (checked: boolean) => {
if (isMyTurn || isUpdating) return;
setLocalQueueOnly(checked);
sendUpdate(localState, checked);
};
const isDisabled = isMyTurn || isUpdating;
return (
<div className="border-t pt-4 mt-4">
<Label className="text-sm font-semibold mb-3 block">Autodraft</Label>
{/* Three-state button group */}
<div className="flex rounded-lg border overflow-hidden mb-3">
{(["off", "next_pick", "all_picks"] as AutodraftState[]).map((state) => {
const labels: Record<AutodraftState, string> = {
off: "Off",
next_pick: "Next Pick",
all_picks: "All Picks",
};
const isActive = localState === state;
return (
<button
key={state}
type="button"
disabled={isDisabled}
onClick={() => handleStateChange(state)}
className={`flex-1 py-1.5 text-xs font-medium transition-colors ${
isActive
? "bg-electric text-background"
: "bg-transparent text-muted-foreground hover:bg-muted/60"
} ${isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}`}
>
{labels[state]}
</button>
);
})}
</div>
{/* Queue-only toggle — only visible when autodraft is active */}
{localState !== "off" && (
<div className="flex items-center justify-between">
<Label
htmlFor="queue-only-switch"
className={`text-xs ${isDisabled ? "text-muted-foreground" : "cursor-pointer"}`}
>
Only autodraft from queue
</Label>
<Switch
id="queue-only-switch"
checked={localQueueOnly}
onCheckedChange={handleQueueOnlyChange}
disabled={isDisabled}
/>
</div>
)}
{isMyTurn && (
<p className="text-xs text-muted-foreground mt-2">
Autodraft settings are disabled during your pick
</p>
)}
</div>
);
}