import { useState } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import type { Bootstrap, Item } from "../../types.js"; import { TYPES } from "../../types.js"; import { fmt, TYPE_GLYPHS } from "../../format.js"; import { api } from "../../api.js"; import { Btn, Field, Input, Select } from "../primitives/index.js"; import { ModalBackdrop, ModalHeader, ModalFooter } from "./ModalChrome.js"; const NEW_BRAND = "__new_brand__"; const NEW_SHOP = "__new_shop__"; const NEW_BIN = "__new_bin__"; export function EditInventoryFlow({ data, item, onClose, }: { data: Bootstrap; item: Item; onClose: () => void; }) { const qc = useQueryClient(); const isDiscrete = item.kind === "discrete"; // form.price is total for bulk, per-unit for discrete. Convert at I/O boundaries. const initialPrice = isDiscrete && item.countOriginal > 0 ? item.price / item.countOriginal : item.price; const [form, setForm] = useState({ brandId: item.brandId ?? NEW_BRAND, shopId: item.shopId ?? NEW_SHOP, binId: item.binId ?? NEW_BIN, weight: item.weight, countOriginal: item.countOriginal, unitWeight: item.unitWeight, price: initialPrice, thc: item.thc, cbd: item.cbd, totalCannabinoids: item.totalCannabinoids, purchaseDate: item.purchaseDate, }); const [newBrand, setNewBrand] = useState(""); const [newShopName, setNewShopName] = useState(""); const [newShopLocation, setNewShopLocation] = useState(""); const [newBinName, setNewBinName] = useState(""); const [newBinCapacity, setNewBinCapacity] = useState(10); const [error, setError] = useState(null); const update = (k: K, v: (typeof form)[K]) => setForm((f) => ({ ...f, [k]: v })); const cfg = TYPES.find((t) => t.id === item.type); const totalPrice = isDiscrete ? form.price * form.countOriginal : form.price; const cpg = !isDiscrete && form.weight > 0 ? form.price / form.weight : 0; const save = useMutation({ mutationFn: async () => { let { brandId, shopId, binId } = form; if (brandId === NEW_BRAND) { if (!newBrand.trim()) throw new Error("New brand name required"); const b = await api.createBrand(newBrand.trim()); brandId = b.id; } if (shopId === NEW_SHOP) { if (!newShopName.trim()) throw new Error("New shop name required"); const s = await api.createShop({ name: newShopName.trim(), location: newShopLocation.trim(), }); shopId = s.id; } if (binId === NEW_BIN) { if (!newBinName.trim()) throw new Error("New bin name required"); const b = await api.createBin({ name: newBinName.trim(), capacity: newBinCapacity, }); binId = b.id; } return api.updateInventoryItem(item.id, { brandId, shopId, binId, weight: isDiscrete ? undefined : form.weight, countOriginal: isDiscrete ? form.countOriginal : undefined, unitWeight: isDiscrete ? form.unitWeight : undefined, price: totalPrice, thc: form.thc, cbd: form.cbd, totalCannabinoids: form.totalCannabinoids, purchaseDate: form.purchaseDate, }); }, onSuccess: () => { qc.invalidateQueries({ queryKey: ["bootstrap"] }); onClose(); }, onError: (e: Error) => setError(e.message), }); const isNewBrand = form.brandId === NEW_BRAND; const isNewShop = form.shopId === NEW_SHOP; const isNewBin = form.binId === NEW_BIN; return (
{TYPE_GLYPHS[item.type]} Editing this physical instance of{" "} {item.name} ({item.type} ·{" "} {item.kind}). To change the product (SKU, name, type), edit the catalog entry.
Source
{isNewBrand && ( setNewBrand(e.target.value)} placeholder="e.g. Foxglove Farms" /> )} {isNewShop && ( <> setNewShopName(e.target.value)} placeholder="e.g. Greenleaf Co-op" /> setNewShopLocation(e.target.value)} placeholder="e.g. Capitol Hill" /> )} {isNewBin && ( <> setNewBinName(e.target.value)} placeholder="e.g. A1" /> setNewBinCapacity(Math.max(1, Math.floor(+e.target.value || 1))) } /> )}
Acquisition
{isDiscrete ? ( <> update("countOriginal", +e.target.value)} /> update("unitWeight", +e.target.value)} /> ) : ( update("weight", +e.target.value)} /> )} update("price", +e.target.value)} /> update("purchaseDate", e.target.value)} />
{!isDiscrete && cpg > 0 && (
Cost per {cfg?.unit ?? "g"}:{" "} {fmt.money(cpg)}
)} {isDiscrete && form.price > 0 && form.countOriginal > 0 && (
Total:{" "} {fmt.money(totalPrice)} ({form.countOriginal} × {fmt.money(form.price)})
)}
Cannabinoid profile
update("thc", +e.target.value)} /> update("cbd", +e.target.value)} /> update("totalCannabinoids", +e.target.value)} />
{item.audits.length > 0 && (
{item.audits.length} audit{item.audits.length === 1 ? "" : "s"} on file — audit history is preserved unchanged. Editing the original size only updates the percent-remaining math going forward.
)} {error && (
{error}
)}
Cancel save.mutate()} > {save.isPending ? "Saving…" : "Save changes"}
); }