// ── Manual receipt creation ──────────────────────────────────────────────────── function ManualReceiptCreate() { const [mode, setMode] = React.useState("upload"); // upload|manual const [chains, setChains] = React.useState([]); const storeNames = useStoreNames(); const [saveState, setSaveState] = React.useState("idle"); // idle|saving|error const [saveError, setSaveError] = React.useState(null); const keyCounter = React.useRef(0); const [form, setForm] = React.useState(() => ({ store_name: "", store_address: "", date: todayDMY(), time: nowHM(), payment_method: "", chain: "unknown", items: [{ _key: 1, name: "", amount_eur: "" }], total_eur: "", })); React.useEffect(() => { fetch("/api/chains") .then((r) => r.json()) .then(({ chains }) => setChains(chains.map((c) => c.name))) .catch(() => {}); }, []); function setField(field, value) { setForm((f) => ({ ...f, [field]: value })); } function setItemField(key, field, value) { setForm((f) => ({ ...f, items: f.items.map((item) => item._key === key ? { ...item, [field]: value } : item, ), })); } function addItem() { setForm((f) => ({ ...f, items: [ ...f.items, { _key: ++keyCounter.current, name: "", amount_eur: "" }, ], })); } function deleteItem(key) { setForm((f) => ({ ...f, items: f.items.filter((item) => item._key !== key), })); } const itemsSumCents = (form.items || []).reduce( (acc, item) => acc + Math.round((parseFloat(item.amount_eur) || 0) * 100), 0, ); const itemsSum = itemsSumCents / 100; const totalCents = Math.round((parseFloat(form.total_eur) || 0) * 100); const totalMismatch = form.total_eur !== "" && Math.abs(totalCents - itemsSumCents) > 1; async function handleSave() { setSaveState("saving"); setSaveError(null); const items = form.items .filter((item) => item.name.trim()) .map(({ _key, ...item }) => { const eur = parseFloat(item.amount_eur) || 0; return { name: item.name.trim(), amount_eur: eur, amount_cents: Math.round(eur * 100), }; }); const total = parseFloat(form.total_eur); const effectiveTotal = isNaN(total) ? itemsSum : total; const payload = { source: "manual", chain: form.chain || "unknown", store_name: form.store_name.trim() || null, store_address: form.store_address.trim() || null, date: form.date.trim(), time: form.time.trim(), payment_method: form.payment_method.trim() || null, items, item_count: items.length, total_eur: effectiveTotal, total_cents: Math.round(effectiveTotal * 100), }; try { const res = await fetch("/api/receipts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!res.ok) { const detail = await res.json().catch(() => ({})); throw new Error(detail.detail || `HTTP ${res.status}`); } const { id } = await res.json(); window.location.hash = `#/receipts/${id}`; } catch (e) { setSaveState("error"); setSaveError(e.message); } } return (
Il totale differisce dalla somma degli articoli (€ {itemsSum.toFixed(2)}).
)}{saveError || "Salvataggio fallito"}
)} > )}