import { useEffect, useState } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import type { Bootstrap, Product } from "../../types.js"; import { TYPES, helpers, TODAY_STR } from "../../types.js"; import { api } from "../../api.js"; import { Btn, Field, Input, Select } from "../primitives/index.js"; import { ScanField } from "../ScanField.js"; import { ModalBackdrop, ModalHeader, ModalFooter } from "./AddProductFlow.js"; const AUDIT_MODE_LABELS: Record = { weigh: { title: "Reweigh on a scale", desc: "Place the jar (minus tare) and record the new weight.", }, estimate: { title: "Visual estimate", desc: "Eyeball the remaining amount — quick and approximate.", }, presence: { title: "Confirm presence", desc: "Verify the item is still where you left it. Count units if applicable.", }, }; export function AuditFlow({ data, onClose, product: initialProduct, }: { data: Bootstrap; onClose: () => void; product: Product | null; }) { const qc = useQueryClient(); const overdueFirst = [...data.products] .filter((p) => p.status === "active") .sort((a, b) => helpers.daysSinceCheck(b) - helpers.daysSinceCheck(a)); const [productId, setProductId] = useState(initialProduct?.id ?? overdueFirst[0]?.id ?? ""); const [date, setDate] = useState(TODAY_STR); const [confirmedBy, setConfirmedBy] = useState<"SKU" | "asset" | "visual">("SKU"); const product = data.products.find((p) => p.id === productId); const cfg = product ? TYPES.find((t) => t.id === product.type) : undefined; const initialValueFor = (p: Product | undefined): string => { if (!p) return "0"; if (p.kind === "discrete") { return String(p.countLastAudit ?? p.countOriginal); } return helpers.estimatedRemaining(p, TODAY_STR).toFixed(2); }; const [value, setValue] = useState(initialValueFor(product)); useEffect(() => { setValue(initialValueFor(product)); }, [productId]); // eslint-disable-line react-hooks/exhaustive-deps const audit = useMutation({ mutationFn: () => api.auditProduct(productId, { date, mode: cfg?.auditMode ?? "weigh", value: Number(value), confirmedBy: cfg?.auditMode === "presence" ? confirmedBy : undefined, }), onSuccess: () => { qc.invalidateQueries({ queryKey: ["bootstrap"] }); onClose(); }, }); if (!product) return null; const auditMode = cfg?.auditMode ?? "weigh"; const ml = AUDIT_MODE_LABELS[auditMode] ?? AUDIT_MODE_LABELS.weigh!; const last = helpers.lastAudit(product); const prevValue = product.kind === "discrete" ? product.countLastAudit ?? product.countOriginal : last ? last.value : product.weight; const delta = Number(value) - prevValue; return (
{product.name}
{product.type} · {product.kind} · cadence every {cfg?.cadenceDays}d
LAST CHECKED
{last ? `${helpers.daysSinceCheck(product)}d ago` : "Never"}
{ml.desc}
setValue(e.target.value)} /> setDate(e.target.value)} /> {auditMode === "presence" && ( )}
Was
{prevValue} {cfg?.unit}
Now
{value} {cfg?.unit}
Δ since last
{delta.toFixed(product.kind === "discrete" ? 0 : 2)} {cfg?.unit}
Next audit due in {cfg?.cadenceDays}d
Cancel audit.mutate()} > {audit.isPending ? "Saving…" : "Save audit"}
); }