Scope ScanField by mode: asset ID only for audit/consume, SKU only for add inventory
Build and push image / build (push) Successful in 57s
Build and push image / build (push) Successful in 57s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 (
|
||||
<Field label="Scan asset id or SKU">
|
||||
<Field label={mode === "assetId" ? "Scan asset ID" : mode === "sku" ? "Scan SKU" : "Scan asset id or SKU"}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -80,7 +85,7 @@ export function ScanField({
|
||||
value={scan}
|
||||
onChange={(e) => 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;
|
||||
}
|
||||
|
||||
@@ -208,6 +208,7 @@ function SelectProductStep({
|
||||
onMatch={handleScan}
|
||||
onScanNoMatch={creating ? undefined : handleNoMatch}
|
||||
matchedLabel={null}
|
||||
mode="sku"
|
||||
/>
|
||||
|
||||
{!creating && (
|
||||
|
||||
@@ -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)}
|
||||
>
|
||||
<option value="asset">Asset id</option>
|
||||
<option value="SKU">SKU label</option>
|
||||
<option value="visual">Visual ID</option>
|
||||
</Select>
|
||||
</Field>
|
||||
|
||||
@@ -69,6 +69,7 @@ export function ConsumeFlow({
|
||||
products={[]}
|
||||
matchedLabel={item ? `${item.assetId} · ${item.name}` : null}
|
||||
onMatch={handleScan}
|
||||
mode="assetId"
|
||||
/>
|
||||
|
||||
{!item ? (
|
||||
|
||||
Reference in New Issue
Block a user