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")
|
.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,114 +118,122 @@ export function AuditFlow({
|
|||||||
onMatch={handleScan}
|
onMatch={handleScan}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
{!item ? (
|
||||||
style={{
|
<div style={{ marginTop: 24, textAlign: "center", color: "var(--ink-3)", fontSize: 13, fontStyle: "italic", padding: "24px 0" }}>
|
||||||
marginTop: 16,
|
Scan an asset ID to continue.
|
||||||
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>
|
|
||||||
</div>
|
</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
|
<div
|
||||||
className="serif"
|
|
||||||
style={{
|
style={{
|
||||||
fontSize: 22,
|
marginTop: 16,
|
||||||
color: delta < 0 ? "var(--terracotta)" : "var(--ink)",
|
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>
|
<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 && (
|
{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"}
|
||||||
|
|||||||
@@ -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,84 +71,92 @@ export function ConsumeFlow({
|
|||||||
onMatch={handleScan}
|
onMatch={handleScan}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
{!item ? (
|
||||||
style={{
|
<div style={{ marginTop: 24, textAlign: "center", color: "var(--ink-3)", fontSize: 13, fontStyle: "italic", padding: "24px 0" }}>
|
||||||
marginTop: 16,
|
Scan an asset ID to continue.
|
||||||
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>
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<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
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
marginTop: 16,
|
||||||
gap: 4,
|
padding: 16,
|
||||||
alignItems: "center",
|
background: "var(--bg-2)",
|
||||||
padding: "10px 12px",
|
|
||||||
background: "var(--bg)",
|
|
||||||
border: "1px solid var(--line)",
|
border: "1px solid var(--line)",
|
||||||
borderRadius: "var(--r-md)",
|
borderRadius: "var(--r-md)",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{[1, 2, 3, 4, 5].map((n) => (
|
<div>
|
||||||
<button
|
<div className="serif" style={{ fontSize: 22, fontWeight: 500 }}>
|
||||||
key={n}
|
{item.name}
|
||||||
onClick={() => setRating(n)}
|
</div>
|
||||||
style={{ border: "none", background: "transparent", cursor: "pointer", padding: 2 }}
|
<div style={{ fontSize: 12, color: "var(--ink-3)" }}>
|
||||||
>
|
<span className="mono">{item.assetId}</span> · {helpers.brandName(data, item.brandId)} · {bin?.name} · purchased{" "}
|
||||||
<Icon
|
{fmt.dateShort(item.purchaseDate)}
|
||||||
name="star"
|
</div>
|
||||||
size={20}
|
</div>
|
||||||
color={n <= rating ? "var(--amber)" : "var(--ink-4)"}
|
<div style={{ textAlign: "right" }}>
|
||||||
/>
|
<div className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>LASTED</div>
|
||||||
</button>
|
<div className="serif" style={{ fontSize: 24 }}>{lifespan} days</div>
|
||||||
))}
|
</div>
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
marginLeft: "auto",
|
|
||||||
fontSize: 12,
|
|
||||||
color: "var(--ink-3)",
|
|
||||||
fontFamily: "var(--mono)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{rating}/5
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</Field>
|
|
||||||
</div>
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginTop: 24 }}>
|
||||||
<div style={{ marginTop: 16 }}>
|
<Field label="Date finished">
|
||||||
<Field label="Final notes" hint="Flavor, effects, would you rebuy">
|
<Input type="date" value={date} onChange={(e) => setDate(e.target.value)} />
|
||||||
<Textarea
|
</Field>
|
||||||
value={notes}
|
<Field label="Rating">
|
||||||
onChange={(e) => setNotes(e.target.value)}
|
<div
|
||||||
placeholder="What stood out?"
|
style={{
|
||||||
/>
|
display: "flex",
|
||||||
</Field>
|
gap: 4,
|
||||||
</div>
|
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 && (
|
{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"}
|
||||||
|
|||||||
Reference in New Issue
Block a user