'use client' import { useState } from 'react' import { Button } from '@/components/ui/button' import type { CrewDto, QueueDto } from '@/lib/game/save' import { computeDuration, type TaskType } from '@/lib/game/tasks' // Shared crew picker for the four action panels — PR A: an action is queued as a lead + // helpers task, not a one-click. Self-contained: the panel passes the /api/crew CrewState // it fetched (roster + queue, Task 6) and onQueue hands back the picked team; the panel // performs its own fetch with the team fields in the body (Task 5a). The live preview is // Task 1's computeDuration (pure, no DB import) with the default powerOf — the same call // 5a's route makes, so it equals the stored duration. The rows mirror Task 3's queueTask // so the UI never offers a 409 (JC-11's fields make the member↔row check exact). export function TeamBuilder({ type, hopCount, roster, tasks, onQueue }: { type: TaskType hopCount?: number roster: CrewDto[] tasks: QueueDto[] onQueue: (leadCrewId: number, helperCrewIds: number[]) => void }) { const [leadId, setLeadId] = useState(null) const [helperIds, setHelperIds] = useState([]) // Whole-builder blocks: an ACTIVE jump is in transit (all types); in the jump panel any // queued jump is — the second-jump 409 (JC-12). The JUMP branch coalesces find()'s // undefined to null so blocked is strictly QueueDto | null: the roster ternary below tests // truthiness, but the button guard compares to null — undefined slipped past it and // disabled every empty-queue JUMP dispatch (v0.10.1 e2e jump regression). const inTransit = tasks.find((t) => t.type === 'JUMP' && t.status === 'ACTIVE') const queuedJump = type === 'JUMP' ? (tasks.find((t) => t.type === 'JUMP') ?? null) : null const blocked = inTransit ?? queuedJump const lead = roster.find((c) => c.id === leadId) ?? null const helpers = roster.filter((c) => helperIds.includes(c.id)) const duration = lead ? computeDuration(type, hopCount ?? 0, lead, helpers) : null // CrewDto ⊇ CrewMember const leadReason = (c: CrewDto) => { const leads = tasks.find((x) => x.leadCrewId === c.id && x.status === 'PENDING') if (leads) return `${leads.type} ALREADY QUEUED` const helps = tasks.find((x) => x.helperCrewIds.includes(c.id) && x.status === 'PENDING') return helps ? `COMMITTED: ${helps.type}` : null } const helperReason = (c: CrewDto) => { const t = tasks.find((x) => (x.leadCrewId === c.id || x.helperCrewIds.includes(c.id)) && (x.status === 'ACTIVE' || x.status === 'PENDING')) return t ? `BUSY: ${t.type}` : null } return (
PICK TEAM // {type} {blocked ? ( {inTransit ? `IN TRANSIT // ${inTransit.targetLabel}` : 'A JUMP IS ALREADY QUEUED'} ) : ( roster.map((c) => ( )) )}
{duration !== null ? `WILL TAKE ${duration} TURN(S)` : 'PICK A LEAD'}
) }