Scope ScanField by mode: asset ID only for audit/consume, SKU only for add inventory
Build and push image / build (push) Successful in 57s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 20:32:52 -04:00
parent c031058d1d
commit 04bf009a83
4 changed files with 26 additions and 14 deletions
+22 -12
View File
@@ -16,6 +16,7 @@ export function ScanField({
onMatch, onMatch,
onScanNoMatch, onScanNoMatch,
matchedLabel, matchedLabel,
mode = "both",
}: { }: {
items: Item[]; items: Item[];
products?: Product[]; products?: Product[];
@@ -25,6 +26,7 @@ export function ScanField({
// open a "create new product" form prefilled with the scanned SKU). // open a "create new product" form prefilled with the scanned SKU).
onScanNoMatch?: (raw: string) => void; onScanNoMatch?: (raw: string) => void;
matchedLabel: string | null; matchedLabel: string | null;
mode?: "both" | "assetId" | "sku";
}) { }) {
const [scan, setScan] = useState(""); const [scan, setScan] = useState("");
const [feedback, setFeedback] = useState<{ type: "matched" | "miss"; text: string } | null>(null); const [feedback, setFeedback] = useState<{ type: "matched" | "miss"; text: string } | null>(null);
@@ -35,7 +37,7 @@ export function ScanField({
setFeedback(null); setFeedback(null);
return; return;
} }
const hit = lookup(trimmed, items, products); const hit = lookup(trimmed, items, products, mode);
if (hit) { if (hit) {
const label = hit.kind === "item" ? hit.item.name : hit.product.sku; const label = hit.kind === "item" ? hit.item.name : hit.product.sku;
onMatch(hit); onMatch(hit);
@@ -50,13 +52,16 @@ export function ScanField({
if (!scan.trim() || feedback?.type === "matched") return; if (!scan.trim() || feedback?.type === "matched") return;
const timer = setTimeout(() => { const timer = setTimeout(() => {
const raw = scan.trim(); const raw = scan.trim();
if (!lookup(raw.toLowerCase(), items, products)) { if (!lookup(raw.toLowerCase(), items, products, mode)) {
if (onScanNoMatch) { if (onScanNoMatch) {
onScanNoMatch(raw); onScanNoMatch(raw);
setScan(""); setScan("");
setFeedback(null); setFeedback(null);
} else { } 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); }, 400);
@@ -64,7 +69,7 @@ export function ScanField({
}, [scan, items, products]); // eslint-disable-line react-hooks/exhaustive-deps }, [scan, items, products]); // eslint-disable-line react-hooks/exhaustive-deps
return ( return (
<Field label="Scan asset id or SKU"> <Field label={mode === "assetId" ? "Scan asset ID" : mode === "sku" ? "Scan SKU" : "Scan asset id or SKU"}>
<div <div
style={{ style={{
display: "flex", display: "flex",
@@ -80,7 +85,7 @@ export function ScanField({
value={scan} value={scan}
onChange={(e) => setScan(e.target.value)} onChange={(e) => setScan(e.target.value)}
onFocus={(e) => e.currentTarget.select()} onFocus={(e) => e.currentTarget.select()}
placeholder="K3F9X2 or SKU-XXXXXX" placeholder={mode === "assetId" ? "123456" : mode === "sku" ? "SKU-XXXXXX" : "123456 or SKU-XXXXXX"}
style={{ style={{
border: "none", border: "none",
outline: "none", outline: "none",
@@ -124,14 +129,19 @@ function lookup(
trimmed: string, trimmed: string,
items: Item[], items: Item[],
products?: Product[], products?: Product[],
mode: "both" | "assetId" | "sku" = "both",
): ScanResult | null { ): ScanResult | null {
const itemHit = items.find((i) => i.assetId.toLowerCase() === trimmed); if (mode !== "sku") {
if (itemHit) return { kind: "item", item: itemHit }; const itemHit = items.find((i) => i.assetId.toLowerCase() === trimmed);
const skuHitItem = items.find((i) => i.sku.toLowerCase() === trimmed); if (itemHit) return { kind: "item", item: itemHit };
if (skuHitItem) return { kind: "item", item: skuHitItem }; }
if (products) { if (mode !== "assetId") {
const productHit = products.find((p) => p.sku.toLowerCase() === trimmed); const skuHitItem = items.find((i) => i.sku.toLowerCase() === trimmed);
if (productHit) return { kind: "product", product: productHit }; 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; return null;
} }
@@ -208,6 +208,7 @@ function SelectProductStep({
onMatch={handleScan} onMatch={handleScan}
onScanNoMatch={creating ? undefined : handleNoMatch} onScanNoMatch={creating ? undefined : handleNoMatch}
matchedLabel={null} matchedLabel={null}
mode="sku"
/> />
{!creating && ( {!creating && (
+2 -2
View File
@@ -41,7 +41,7 @@ export function AuditFlow({
const [itemId, setItemId] = useState(initialItem?.id ?? ""); const [itemId, setItemId] = useState(initialItem?.id ?? "");
const [date, setDate] = useState(TODAY_STR); 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 item = allItems.find((i) => i.id === itemId);
const cfg = item ? TYPES.find((t) => t.id === item.type) : undefined; const cfg = item ? TYPES.find((t) => t.id === item.type) : undefined;
@@ -116,6 +116,7 @@ export function AuditFlow({
products={[]} products={[]}
matchedLabel={item ? `${item.assetId} · ${item.name}` : null} matchedLabel={item ? `${item.assetId} · ${item.name}` : null}
onMatch={handleScan} onMatch={handleScan}
mode="assetId"
/> />
{!item ? ( {!item ? (
@@ -188,7 +189,6 @@ export function AuditFlow({
onChange={(e) => setConfirmedBy(e.target.value as typeof confirmedBy)} onChange={(e) => setConfirmedBy(e.target.value as typeof confirmedBy)}
> >
<option value="asset">Asset id</option> <option value="asset">Asset id</option>
<option value="SKU">SKU label</option>
<option value="visual">Visual ID</option> <option value="visual">Visual ID</option>
</Select> </Select>
</Field> </Field>
@@ -69,6 +69,7 @@ export function ConsumeFlow({
products={[]} products={[]}
matchedLabel={item ? `${item.assetId} · ${item.name}` : null} matchedLabel={item ? `${item.assetId} · ${item.name}` : null}
onMatch={handleScan} onMatch={handleScan}
mode="assetId"
/> />
{!item ? ( {!item ? (