Require asset ID scan in audit and consume modals
Build and push image / build (push) Successful in 47s
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:
@@ -39,7 +39,7 @@ export function AuditFlow({
|
||||
.filter((i) => i.status === "active")
|
||||
.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 [confirmedBy, setConfirmedBy] = useState<"asset" | "SKU" | "visual">("asset");
|
||||
|
||||
@@ -82,17 +82,17 @@ export function AuditFlow({
|
||||
}
|
||||
};
|
||||
|
||||
if (!item) return null;
|
||||
const auditMode = cfg?.auditMode ?? "weigh";
|
||||
const ml = AUDIT_MODE_LABELS[auditMode] ?? AUDIT_MODE_LABELS.weigh!;
|
||||
|
||||
const last = helpers.lastAudit(item);
|
||||
const prevValue =
|
||||
item.kind === "discrete"
|
||||
const last = item ? helpers.lastAudit(item) : null;
|
||||
const prevValue = item
|
||||
? item.kind === "discrete"
|
||||
? item.countLastAudit ?? item.countOriginal
|
||||
: last
|
||||
? last.value
|
||||
: item.weight;
|
||||
: item.weight
|
||||
: 0;
|
||||
|
||||
const delta = Number(value) - prevValue;
|
||||
|
||||
@@ -108,7 +108,7 @@ export function AuditFlow({
|
||||
boxShadow: "var(--shadow-lg)",
|
||||
}}
|
||||
>
|
||||
<ModalHeader title={ml.title} eyebrow="" onClose={onClose} />
|
||||
<ModalHeader title={item ? ml.title : "Audit"} eyebrow="" onClose={onClose} />
|
||||
|
||||
<div style={{ padding: 32 }}>
|
||||
<ScanField
|
||||
@@ -118,114 +118,122 @@ export function AuditFlow({
|
||||
onMatch={handleScan}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
padding: 16,
|
||||
background: "var(--bg-2)",
|
||||
border: "1px solid var(--line)",
|
||||
borderRadius: "var(--r-md)",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<div>
|
||||
<div className="serif" style={{ fontSize: 20, fontWeight: 500 }}>
|
||||
{item.name}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: "var(--ink-3)" }}>
|
||||
<span className="mono">{item.assetId}</span> · {item.type} · {item.kind} · cadence every {cfg?.cadenceDays}d
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<div className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>LAST CHECKED</div>
|
||||
<div className="serif" style={{ fontSize: 18 }}>
|
||||
{last ? `${helpers.daysSinceCheck(item)}d ago` : "Never"}
|
||||
</div>
|
||||
</div>
|
||||
{!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 style={{ fontSize: 12, color: "var(--ink-2)", marginTop: 10, fontStyle: "italic" }}>
|
||||
{ml.desc}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: auditMode === "presence" ? "1fr 1fr 1fr" : "1fr 1fr",
|
||||
gap: 16,
|
||||
marginTop: 24,
|
||||
}}
|
||||
>
|
||||
<Field
|
||||
label={
|
||||
item.kind === "discrete"
|
||||
? `Count now (${cfg?.unit})`
|
||||
: auditMode === "weigh"
|
||||
? `Weight now (${cfg?.unit})`
|
||||
: `Estimate now (${cfg?.unit})`
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
step={item.kind === "discrete" ? "1" : "0.1"}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Date">
|
||||
<Input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
|
||||
</Field>
|
||||
{auditMode === "presence" && (
|
||||
<Field label="Confirmed by">
|
||||
<Select
|
||||
value={confirmedBy}
|
||||
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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: 20,
|
||||
padding: 14,
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--line)",
|
||||
borderRadius: "var(--r-md)",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr 1fr",
|
||||
gap: 16,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div className="smallcaps" style={{ color: "var(--ink-3)" }}>Was</div>
|
||||
<div className="serif" style={{ fontSize: 22 }}>
|
||||
{prevValue} {cfg?.unit}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="smallcaps" style={{ color: "var(--ink-3)" }}>Now</div>
|
||||
<div className="serif" style={{ fontSize: 22, color: "var(--sage)" }}>
|
||||
{value} {cfg?.unit}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="smallcaps" style={{ color: "var(--ink-3)" }}>Δ since last</div>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className="serif"
|
||||
style={{
|
||||
fontSize: 22,
|
||||
color: delta < 0 ? "var(--terracotta)" : "var(--ink)",
|
||||
marginTop: 16,
|
||||
padding: 16,
|
||||
background: "var(--bg-2)",
|
||||
border: "1px solid var(--line)",
|
||||
borderRadius: "var(--r-md)",
|
||||
}}
|
||||
>
|
||||
{delta.toFixed(item.kind === "discrete" ? 0 : 2)} {cfg?.unit}
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<div>
|
||||
<div className="serif" style={{ fontSize: 20, fontWeight: 500 }}>
|
||||
{item.name}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: "var(--ink-3)" }}>
|
||||
<span className="mono">{item.assetId}</span> · {item.type} · {item.kind} · cadence every {cfg?.cadenceDays}d
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<div className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>LAST CHECKED</div>
|
||||
<div className="serif" style={{ fontSize: 18 }}>
|
||||
{last ? `${helpers.daysSinceCheck(item)}d ago` : "Never"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: "var(--ink-2)", marginTop: 10, fontStyle: "italic" }}>
|
||||
{ml.desc}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: auditMode === "presence" ? "1fr 1fr 1fr" : "1fr 1fr",
|
||||
gap: 16,
|
||||
marginTop: 24,
|
||||
}}
|
||||
>
|
||||
<Field
|
||||
label={
|
||||
item.kind === "discrete"
|
||||
? `Count now (${cfg?.unit})`
|
||||
: auditMode === "weigh"
|
||||
? `Weight now (${cfg?.unit})`
|
||||
: `Estimate now (${cfg?.unit})`
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
step={item.kind === "discrete" ? "1" : "0.1"}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Date">
|
||||
<Input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
|
||||
</Field>
|
||||
{auditMode === "presence" && (
|
||||
<Field label="Confirmed by">
|
||||
<Select
|
||||
value={confirmedBy}
|
||||
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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: 20,
|
||||
padding: 14,
|
||||
background: "var(--surface)",
|
||||
border: "1px solid var(--line)",
|
||||
borderRadius: "var(--r-md)",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr 1fr",
|
||||
gap: 16,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div className="smallcaps" style={{ color: "var(--ink-3)" }}>Was</div>
|
||||
<div className="serif" style={{ fontSize: 22 }}>
|
||||
{prevValue} {cfg?.unit}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="smallcaps" style={{ color: "var(--ink-3)" }}>Now</div>
|
||||
<div className="serif" style={{ fontSize: 22, color: "var(--sage)" }}>
|
||||
{value} {cfg?.unit}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="smallcaps" style={{ color: "var(--ink-3)" }}>Δ since last</div>
|
||||
<div
|
||||
className="serif"
|
||||
style={{
|
||||
fontSize: 22,
|
||||
color: delta < 0 ? "var(--terracotta)" : "var(--ink)",
|
||||
}}
|
||||
>
|
||||
{delta.toFixed(item.kind === "discrete" ? 0 : 2)} {cfg?.unit}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div style={{ marginTop: 14, fontSize: 12, color: "var(--terracotta)" }}>{error}</div>
|
||||
@@ -234,14 +242,14 @@ export function AuditFlow({
|
||||
|
||||
<ModalFooter>
|
||||
<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 style={{ display: "flex", gap: 8 }}>
|
||||
<Btn variant="ghost" onClick={onClose}>Cancel</Btn>
|
||||
<Btn
|
||||
variant="primary"
|
||||
icon="check"
|
||||
disabled={audit.isPending}
|
||||
disabled={audit.isPending || !item}
|
||||
onClick={() => audit.mutate()}
|
||||
>
|
||||
{audit.isPending ? "Saving…" : "Save audit"}
|
||||
|
||||
Reference in New Issue
Block a user