"use client"; /* Size exchange — one movement, not a return followed by an issue. What comes back, what goes out, and the staff record updated so nobody hands them the wrong size again next month. */ import { useCallback, useMemo, useState } from "react"; import { useParams, useRouter } from "next/navigation"; import { useDerived, useSnap } from "@/lib/client"; import { isPantItem, isTopItem, key, label, onhand, staffName } from "@/lib/compute"; import MScan from "@/components/MScan"; import { GROUND, INK, MBar, MBody, MChips, MEmpty, MError, MRow, MRule, MSection, MTop } from "@/components/m"; import { useHeld, type Held } from "@/components/MPerson"; export default function MExchange() { const { s, mutate, busy } = useSnap(); const { L, byId } = useDerived(); const router = useRouter(); const id = String(useParams().id || ""); const st = s.staff.find((x) => x.id === id); const held = useHeld(s, id); const [pick, setPick] = useState(null); const [si, setSi] = useState(-1); const [scan, setScan] = useState(false); const [err, setErr] = useState(""); const it = pick ? byId[pick.itemId] : undefined; const stock = useMemo(() => { if (!it) return [] as number[]; return it.sizes.map((_, i) => onhand(s, L, key(it.id, i))); }, [it, s, L]); const onCode = useCallback((raw: string) => { const k = s.barcodes[raw.trim()]; const hit = held.find((h) => h.key === k); if (!hit) { setErr(`${raw.trim()} isn’t something ${st?.first ?? "they"} is holding.`); return; } setPick(hit); setSi(-1); setErr(""); }, [s.barcodes, held, st]); if (!st) return (<>); const commit = async () => { if (!pick || si < 0) return; const r = await mutate<{ size: string }>("issue.exchange", { id: pick.issues[0].id, si, qty: 1 }); if (!r.ok) { setErr(r.error); return; } router.push(`/m/person/${st.id}`); }; const willUpdate = it && (isTopItem(it) || isPantItem(it)); return ( <> setErr("")} /> {!pick ? ( <> t + h.qty, 0)} out`} /> {held.length === 0 ? : held.map((h) => { setPick(h); setSi(-1); }} mark="ink" title={h.name} sub={`${h.qty} held`} />)} {/* Same rule as the return screen: the scan bar is off when nothing is out, so the line offering a scan goes with it. */} {held.length > 0 &&
Or scan the garment they’ve brought back.
} ) : ( <>
Taking back
{it && setSi(i)} disabled={(i) => i === pick.si || stock[i] <= 0} />} {si >= 0 && it && (

{stock[si]} on the shelf in size {it.sizes[si]}. The old garment goes back to stock in the same movement {willUpdate ? `, and ${st.first}’s recorded size becomes ${it.sizes[si]}.` : "."}

)}
)}
{pick ? = 0 && it ? `Exchange for size ${it.sizes[si]}` : "Pick a size"} glyph="check" onClick={commit} disabled={busy || si < 0} /> : setScan(true)} disabled={held.length === 0} />} {scan && { onCode(r); setScan(false); }} onClose={() => setScan(false)} />} ); }