Require asset ID scan in audit and consume modals
Build and push image / build (push) Successful in 47s

No default item selection — modals open with a scan prompt.
Form fields appear only after scanning an asset ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 19:34:31 -04:00
parent e50e8ef1fe
commit bc81cc8d18
2 changed files with 201 additions and 186 deletions
+17 -9
View File
@@ -39,7 +39,7 @@ export function AuditFlow({
.filter((i) => i.status === "active") .filter((i) => i.status === "active")
.sort((a, b) => helpers.daysSinceCheck(b) - helpers.daysSinceCheck(a)); .sort((a, b) => helpers.daysSinceCheck(b) - helpers.daysSinceCheck(a));
const [itemId, setItemId] = useState(initialItem?.id ?? overdueFirst[0]?.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" | "SKU" | "visual">("asset");
@@ -82,17 +82,17 @@ export function AuditFlow({
} }
}; };
if (!item) return null;
const auditMode = cfg?.auditMode ?? "weigh"; const auditMode = cfg?.auditMode ?? "weigh";
const ml = AUDIT_MODE_LABELS[auditMode] ?? AUDIT_MODE_LABELS.weigh!; const ml = AUDIT_MODE_LABELS[auditMode] ?? AUDIT_MODE_LABELS.weigh!;
const last = helpers.lastAudit(item); const last = item ? helpers.lastAudit(item) : null;
const prevValue = const prevValue = item
item.kind === "discrete" ? item.kind === "discrete"
? item.countLastAudit ?? item.countOriginal ? item.countLastAudit ?? item.countOriginal
: last : last
? last.value ? last.value
: item.weight; : item.weight
: 0;
const delta = Number(value) - prevValue; const delta = Number(value) - prevValue;
@@ -108,7 +108,7 @@ export function AuditFlow({
boxShadow: "var(--shadow-lg)", boxShadow: "var(--shadow-lg)",
}} }}
> >
<ModalHeader title={ml.title} eyebrow="" onClose={onClose} /> <ModalHeader title={item ? ml.title : "Audit"} eyebrow="" onClose={onClose} />
<div style={{ padding: 32 }}> <div style={{ padding: 32 }}>
<ScanField <ScanField
@@ -118,6 +118,12 @@ export function AuditFlow({
onMatch={handleScan} onMatch={handleScan}
/> />
{!item ? (
<div style={{ marginTop: 24, textAlign: "center", color: "var(--ink-3)", fontSize: 13, fontStyle: "italic", padding: "24px 0" }}>
Scan an asset ID to continue.
</div>
) : (
<>
<div <div
style={{ style={{
marginTop: 16, marginTop: 16,
@@ -226,6 +232,8 @@ export function AuditFlow({
</div> </div>
</div> </div>
</div> </div>
</>
)}
{error && ( {error && (
<div style={{ marginTop: 14, fontSize: 12, color: "var(--terracotta)" }}>{error}</div> <div style={{ marginTop: 14, fontSize: 12, color: "var(--terracotta)" }}>{error}</div>
@@ -234,14 +242,14 @@ export function AuditFlow({
<ModalFooter> <ModalFooter>
<div style={{ fontSize: 12, color: "var(--ink-3)" }}> <div style={{ fontSize: 12, color: "var(--ink-3)" }}>
Next audit due in {cfg?.cadenceDays}d {item ? `Next audit due in ${cfg?.cadenceDays}d` : ""}
</div> </div>
<div style={{ display: "flex", gap: 8 }}> <div style={{ display: "flex", gap: 8 }}>
<Btn variant="ghost" onClick={onClose}>Cancel</Btn> <Btn variant="ghost" onClick={onClose}>Cancel</Btn>
<Btn <Btn
variant="primary" variant="primary"
icon="check" icon="check"
disabled={audit.isPending} disabled={audit.isPending || !item}
onClick={() => audit.mutate()} onClick={() => audit.mutate()}
> >
{audit.isPending ? "Saving…" : "Save audit"} {audit.isPending ? "Saving…" : "Save audit"}
+12 -5
View File
@@ -22,7 +22,7 @@ export function ConsumeFlow({
const { toast } = useToast(); const { toast } = useToast();
const allItems = enrichItems(data); const allItems = enrichItems(data);
const active = allItems.filter((i) => i.status === "active"); const active = allItems.filter((i) => i.status === "active");
const [itemId, setItemId] = useState(initialItem?.id ?? active[0]?.id ?? ""); const [itemId, setItemId] = useState(initialItem?.id ?? "");
const [rating, setRating] = useState(4); const [rating, setRating] = useState(4);
const [notes, setNotes] = useState(""); const [notes, setNotes] = useState("");
const [date, setDate] = useState(TODAY_STR); const [date, setDate] = useState(TODAY_STR);
@@ -46,9 +46,8 @@ export function ConsumeFlow({
} }
}; };
if (!item) return null; const bin = item ? data.bins.find((b) => b.id === item.binId) : undefined;
const bin = data.bins.find((b) => b.id === item.binId); const lifespan = item ? Math.round((+new Date(date) - +new Date(item.purchaseDate)) / 86_400_000) : 0;
const lifespan = Math.round((+new Date(date) - +new Date(item.purchaseDate)) / 86_400_000);
return ( return (
<ModalBackdrop onClose={onClose}> <ModalBackdrop onClose={onClose}>
@@ -72,6 +71,12 @@ export function ConsumeFlow({
onMatch={handleScan} onMatch={handleScan}
/> />
{!item ? (
<div style={{ marginTop: 24, textAlign: "center", color: "var(--ink-3)", fontSize: 13, fontStyle: "italic", padding: "24px 0" }}>
Scan an asset ID to continue.
</div>
) : (
<>
<div <div
style={{ style={{
marginTop: 16, marginTop: 16,
@@ -150,6 +155,8 @@ export function ConsumeFlow({
/> />
</Field> </Field>
</div> </div>
</>
)}
{error && ( {error && (
<div style={{ marginTop: 14, fontSize: 12, color: "var(--terracotta)" }}>{error}</div> <div style={{ marginTop: 14, fontSize: 12, color: "var(--terracotta)" }}>{error}</div>
@@ -163,7 +170,7 @@ export function ConsumeFlow({
<Btn <Btn
variant="primary" variant="primary"
icon="check" icon="check"
disabled={finish.isPending} disabled={finish.isPending || !item}
onClick={() => finish.mutate()} onClick={() => finish.mutate()}
> >
{finish.isPending ? "Saving…" : "Mark consumed"} {finish.isPending ? "Saving…" : "Mark consumed"}