From 04bf009a834ee4cdc804f0607d3e2f76b1c9b0fc Mon Sep 17 00:00:00 2001 From: josh Date: Mon, 4 May 2026 20:32:52 -0400 Subject: [PATCH] Scope ScanField by mode: asset ID only for audit/consume, SKU only for add inventory Co-Authored-By: Claude Opus 4.6 --- web/src/components/ScanField.tsx | 34 ++++++++++++------- .../components/modals/AddInventoryFlow.tsx | 1 + web/src/components/modals/AuditFlow.tsx | 4 +-- web/src/components/modals/ConsumeFlow.tsx | 1 + 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/web/src/components/ScanField.tsx b/web/src/components/ScanField.tsx index 4da654d..4392db5 100644 --- a/web/src/components/ScanField.tsx +++ b/web/src/components/ScanField.tsx @@ -16,6 +16,7 @@ export function ScanField({ onMatch, onScanNoMatch, matchedLabel, + mode = "both", }: { items: Item[]; products?: Product[]; @@ -25,6 +26,7 @@ export function ScanField({ // open a "create new product" form prefilled with the scanned SKU). onScanNoMatch?: (raw: string) => void; matchedLabel: string | null; + mode?: "both" | "assetId" | "sku"; }) { const [scan, setScan] = useState(""); const [feedback, setFeedback] = useState<{ type: "matched" | "miss"; text: string } | null>(null); @@ -35,7 +37,7 @@ export function ScanField({ setFeedback(null); return; } - const hit = lookup(trimmed, items, products); + const hit = lookup(trimmed, items, products, mode); if (hit) { const label = hit.kind === "item" ? hit.item.name : hit.product.sku; onMatch(hit); @@ -50,13 +52,16 @@ export function ScanField({ if (!scan.trim() || feedback?.type === "matched") return; const timer = setTimeout(() => { const raw = scan.trim(); - if (!lookup(raw.toLowerCase(), items, products)) { + if (!lookup(raw.toLowerCase(), items, products, mode)) { if (onScanNoMatch) { onScanNoMatch(raw); setScan(""); setFeedback(null); } else { - setFeedback({ type: "miss", text: "No asset id or SKU matches that." }); + const missText = mode === "assetId" ? "No item matches that asset ID." + : mode === "sku" ? "No product matches that SKU." + : "No asset id or SKU matches that."; + setFeedback({ type: "miss", text: missText }); } } }, 400); @@ -64,7 +69,7 @@ export function ScanField({ }, [scan, items, products]); // eslint-disable-line react-hooks/exhaustive-deps return ( - +
setScan(e.target.value)} onFocus={(e) => e.currentTarget.select()} - placeholder="K3F9X2 or SKU-XXXXXX" + placeholder={mode === "assetId" ? "123456" : mode === "sku" ? "SKU-XXXXXX" : "123456 or SKU-XXXXXX"} style={{ border: "none", outline: "none", @@ -124,14 +129,19 @@ function lookup( trimmed: string, items: Item[], products?: Product[], + mode: "both" | "assetId" | "sku" = "both", ): ScanResult | null { - const itemHit = items.find((i) => i.assetId.toLowerCase() === trimmed); - if (itemHit) return { kind: "item", item: itemHit }; - const skuHitItem = items.find((i) => i.sku.toLowerCase() === trimmed); - if (skuHitItem) return { kind: "item", item: skuHitItem }; - if (products) { - const productHit = products.find((p) => p.sku.toLowerCase() === trimmed); - if (productHit) return { kind: "product", product: productHit }; + if (mode !== "sku") { + const itemHit = items.find((i) => i.assetId.toLowerCase() === trimmed); + if (itemHit) return { kind: "item", item: itemHit }; + } + if (mode !== "assetId") { + const skuHitItem = items.find((i) => i.sku.toLowerCase() === trimmed); + if (skuHitItem) return { kind: "item", item: skuHitItem }; + if (products) { + const productHit = products.find((p) => p.sku.toLowerCase() === trimmed); + if (productHit) return { kind: "product", product: productHit }; + } } return null; } diff --git a/web/src/components/modals/AddInventoryFlow.tsx b/web/src/components/modals/AddInventoryFlow.tsx index 2209e4e..c1c617c 100644 --- a/web/src/components/modals/AddInventoryFlow.tsx +++ b/web/src/components/modals/AddInventoryFlow.tsx @@ -208,6 +208,7 @@ function SelectProductStep({ onMatch={handleScan} onScanNoMatch={creating ? undefined : handleNoMatch} matchedLabel={null} + mode="sku" /> {!creating && ( diff --git a/web/src/components/modals/AuditFlow.tsx b/web/src/components/modals/AuditFlow.tsx index 2db097d..56de161 100644 --- a/web/src/components/modals/AuditFlow.tsx +++ b/web/src/components/modals/AuditFlow.tsx @@ -41,7 +41,7 @@ export function AuditFlow({ const [itemId, setItemId] = useState(initialItem?.id ?? ""); const [date, setDate] = useState(TODAY_STR); - const [confirmedBy, setConfirmedBy] = useState<"asset" | "SKU" | "visual">("asset"); + const [confirmedBy, setConfirmedBy] = useState<"asset" | "visual">("asset"); const item = allItems.find((i) => i.id === itemId); const cfg = item ? TYPES.find((t) => t.id === item.type) : undefined; @@ -116,6 +116,7 @@ export function AuditFlow({ products={[]} matchedLabel={item ? `${item.assetId} · ${item.name}` : null} onMatch={handleScan} + mode="assetId" /> {!item ? ( @@ -188,7 +189,6 @@ export function AuditFlow({ onChange={(e) => setConfirmedBy(e.target.value as typeof confirmedBy)} > - diff --git a/web/src/components/modals/ConsumeFlow.tsx b/web/src/components/modals/ConsumeFlow.tsx index 5961048..75c3c5f 100644 --- a/web/src/components/modals/ConsumeFlow.tsx +++ b/web/src/components/modals/ConsumeFlow.tsx @@ -69,6 +69,7 @@ export function ConsumeFlow({ products={[]} matchedLabel={item ? `${item.assetId} · ${item.name}` : null} onMatch={handleScan} + mode="assetId" /> {!item ? (