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,
|
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 {
|
||||||
|
if (mode !== "sku") {
|
||||||
const itemHit = items.find((i) => i.assetId.toLowerCase() === trimmed);
|
const itemHit = items.find((i) => i.assetId.toLowerCase() === trimmed);
|
||||||
if (itemHit) return { kind: "item", item: itemHit };
|
if (itemHit) return { kind: "item", item: itemHit };
|
||||||
|
}
|
||||||
|
if (mode !== "assetId") {
|
||||||
const skuHitItem = items.find((i) => i.sku.toLowerCase() === trimmed);
|
const skuHitItem = items.find((i) => i.sku.toLowerCase() === trimmed);
|
||||||
if (skuHitItem) return { kind: "item", item: skuHitItem };
|
if (skuHitItem) return { kind: "item", item: skuHitItem };
|
||||||
if (products) {
|
if (products) {
|
||||||
const productHit = products.find((p) => p.sku.toLowerCase() === trimmed);
|
const productHit = products.find((p) => p.sku.toLowerCase() === trimmed);
|
||||||
if (productHit) return { kind: "product", product: productHit };
|
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 && (
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user