/** The guided queue — one task at a time, scanned and confirmed, no skipping. */ import { useState } from "react"; import { Text } from "@lotics/ui/text"; import { colors, solid } from "@lotics/ui/colors"; import { Accordion, AccordionContent, AccordionHeader, AccordionMeta, AccordionTitle } from "@lotics/ui/accordion"; import { Status } from "@lotics/ui/status"; import { Box } from "@lotics/ui/box"; import { Button } from "@lotics/ui/button"; import { ChoiceStrip } from "@lotics/ui/choice_strip"; import { NumberInput } from "@lotics/ui/number_input"; import { Divider } from "@lotics/ui/divider"; import { Popover, PopoverContent, PopoverFooter, PopoverTrigger } from "@lotics/ui/popover"; import { Progress } from "@lotics/ui/progress"; import { VerifyField } from "@lotics/ui/verify_field"; import { ScrollArea } from "@lotics/ui/scroll_area"; import { Stack } from "@lotics/ui/stack"; import { Stepper, Step, type StepStatus } from "@lotics/ui/stepper"; import { RegionState } from "@lotics/ui/region_state"; // ───────────────────────────────────────────────────────────────────────────── // Template, Guided queue — a warehouse PICK RUN. The work is a SEQUENCE of // physical tasks, not a list to browse: the run hands the operator the next bin, // they scan to verify they're at the right shelf, confirm the count, and the // next task takes its place. A single focus column — the CURRENT task is the only // thing in view (where to walk, what to take, how many); the whole PICK PATH is a // COLLAPSIBLE section below, collapsed by default, so position is one tap away and // never competes with the task for the screen. Scan-to-verify, can't-skip // ordering, and a short-pick exception that flags-and-advances without stalling. // ───────────────────────────────────────────────────────────────────────────── interface PickLine { id: string; zone: string; bin: string; sku: string; item: string; qty: number; status: "pending" | "picked" | "short"; /** Actual units taken — = qty when picked, < qty when short. */ picked?: number; } // Ordered by pick path — the run walks the operator down the aisles in sequence. const WAVE: PickLine[] = [ { id: "L1", zone: "Aisle A", bin: "A-12-03", sku: "RM-0012", item: "Kraft paper 175gsm, 1.6m", qty: 8, status: "pending" }, { id: "L2", zone: "Aisle A", bin: "A-14-08", sku: "RM-0018", item: "Medium paper 115gsm, 1.4m", qty: 12, status: "pending" }, { id: "L3", zone: "Aisle B", bin: "B-03-01", sku: "FG-0203", item: "Carton box 600×400×400, 5-ply", qty: 50, status: "pending" }, { id: "L4", zone: "Aisle B", bin: "B-05-07", sku: "FG-0218", item: "Carton box 350×250×200, 3-ply", qty: 40, status: "pending" }, { id: "L5", zone: "Aisle C", bin: "C-01-02", sku: "SUP-0007", item: "Clear packing tape 48mm", qty: 24, status: "pending" }, { id: "L6", zone: "Aisle C", bin: "C-02-09", sku: "SUP-0011", item: "PP strapping 12mm, 10kg roll", qty: 6, status: "pending" }, { id: "L7", zone: "Aisle C", bin: "C-04-01", sku: "FG-0226", item: "Offset box 250×180×90, 4-color", qty: 30, status: "pending" }, ]; const SHORT_REASONS = [ { label: "Out of stock", value: "oos" }, { label: "Damaged", value: "damaged" }, { label: "Location empty", value: "empty" }, ]; // The exception branch — record fewer units than asked + a reason, then advance. // A short never blocks the run; the shortfall is flagged for backfill. function ShortGate({ line, onShort }: { line: PickLine; onShort: (actual: number) => void }) { const [open, setOpen] = useState(false); const [actual, setActual] = useState(0); const [reason, setReason] = useState("oos"); return (