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
+82 -75
View File
@@ -22,7 +22,7 @@ export function ConsumeFlow({
const { toast } = useToast();
const allItems = enrichItems(data);
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 [notes, setNotes] = useState("");
const [date, setDate] = useState(TODAY_STR);
@@ -46,9 +46,8 @@ export function ConsumeFlow({
}
};
if (!item) return null;
const bin = data.bins.find((b) => b.id === item.binId);
const lifespan = Math.round((+new Date(date) - +new Date(item.purchaseDate)) / 86_400_000);
const bin = item ? data.bins.find((b) => b.id === item.binId) : undefined;
const lifespan = item ? Math.round((+new Date(date) - +new Date(item.purchaseDate)) / 86_400_000) : 0;
return (
<ModalBackdrop onClose={onClose}>
@@ -72,84 +71,92 @@ export function ConsumeFlow({
onMatch={handleScan}
/>
<div
style={{
marginTop: 16,
padding: 16,
background: "var(--bg-2)",
border: "1px solid var(--line)",
borderRadius: "var(--r-md)",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<div>
<div className="serif" style={{ fontSize: 22, fontWeight: 500 }}>
{item.name}
</div>
<div style={{ fontSize: 12, color: "var(--ink-3)" }}>
<span className="mono">{item.assetId}</span> · {helpers.brandName(data, item.brandId)} · {bin?.name} · purchased{" "}
{fmt.dateShort(item.purchaseDate)}
</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={{ textAlign: "right" }}>
<div className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>LASTED</div>
<div className="serif" style={{ fontSize: 24 }}>{lifespan} days</div>
</div>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginTop: 24 }}>
<Field label="Date finished">
<Input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
</Field>
<Field label="Rating">
) : (
<>
<div
style={{
display: "flex",
gap: 4,
alignItems: "center",
padding: "10px 12px",
background: "var(--bg)",
marginTop: 16,
padding: 16,
background: "var(--bg-2)",
border: "1px solid var(--line)",
borderRadius: "var(--r-md)",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
{[1, 2, 3, 4, 5].map((n) => (
<button
key={n}
onClick={() => setRating(n)}
style={{ border: "none", background: "transparent", cursor: "pointer", padding: 2 }}
>
<Icon
name="star"
size={20}
color={n <= rating ? "var(--amber)" : "var(--ink-4)"}
/>
</button>
))}
<span
style={{
marginLeft: "auto",
fontSize: 12,
color: "var(--ink-3)",
fontFamily: "var(--mono)",
}}
>
{rating}/5
</span>
<div>
<div className="serif" style={{ fontSize: 22, fontWeight: 500 }}>
{item.name}
</div>
<div style={{ fontSize: 12, color: "var(--ink-3)" }}>
<span className="mono">{item.assetId}</span> · {helpers.brandName(data, item.brandId)} · {bin?.name} · purchased{" "}
{fmt.dateShort(item.purchaseDate)}
</div>
</div>
<div style={{ textAlign: "right" }}>
<div className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>LASTED</div>
<div className="serif" style={{ fontSize: 24 }}>{lifespan} days</div>
</div>
</div>
</Field>
</div>
<div style={{ marginTop: 16 }}>
<Field label="Final notes" hint="Flavor, effects, would you rebuy">
<Textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="What stood out?"
/>
</Field>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginTop: 24 }}>
<Field label="Date finished">
<Input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
</Field>
<Field label="Rating">
<div
style={{
display: "flex",
gap: 4,
alignItems: "center",
padding: "10px 12px",
background: "var(--bg)",
border: "1px solid var(--line)",
borderRadius: "var(--r-md)",
}}
>
{[1, 2, 3, 4, 5].map((n) => (
<button
key={n}
onClick={() => setRating(n)}
style={{ border: "none", background: "transparent", cursor: "pointer", padding: 2 }}
>
<Icon
name="star"
size={20}
color={n <= rating ? "var(--amber)" : "var(--ink-4)"}
/>
</button>
))}
<span
style={{
marginLeft: "auto",
fontSize: 12,
color: "var(--ink-3)",
fontFamily: "var(--mono)",
}}
>
{rating}/5
</span>
</div>
</Field>
</div>
<div style={{ marginTop: 16 }}>
<Field label="Final notes" hint="Flavor, effects, would you rebuy">
<Textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="What stood out?"
/>
</Field>
</div>
</>
)}
{error && (
<div style={{ marginTop: 14, fontSize: 12, color: "var(--terracotta)" }}>{error}</div>
@@ -163,7 +170,7 @@ export function ConsumeFlow({
<Btn
variant="primary"
icon="check"
disabled={finish.isPending}
disabled={finish.isPending || !item}
onClick={() => finish.mutate()}
>
{finish.isPending ? "Saving…" : "Mark consumed"}