import { api, protectedFetch } from "./api.js?v=__ASSET_VERSION__"; import { showToast } from "./toast.js?v=__ASSET_VERSION__"; let lots = []; function findLot(id) { return lots.find((l) => l.id === id); } /** Wires the "From inventory lot" picker (in The Coffee) and the "Draw from lot" action * (in Roast Log — Plan vs. Actual). `getRemotePlanId` is a function since the planner's * remotePlanId is module-local and can change after a sync. */ export function initLotPicker({ state, recompute, flushCurrentPlan, getRemotePlanId }) { const select = document.getElementById("lot-select"); const note = document.getElementById("lot-picker-note"); const consumeRow = document.getElementById("inventory-consume"); const consumeLabel = document.getElementById("inventory-consume-label"); const consumeBtn = document.getElementById("btn-consume"); function renderOptions() { const current = state.plan.inventory.lotId; select.replaceChildren( Object.assign(document.createElement("option"), { value: "", textContent: "— none —" }), ...lots .filter((l) => !l.archived) .map((l) => Object.assign(document.createElement("option"), { value: l.id, textContent: `${l.origin}${l.variety ? ` — ${l.variety}` : ""} (${Math.round(l.remainingWeightG)} g remaining)`, }), ), ); if (current && !findLot(current)) { // The plan references a lot that's now archived/deleted — keep it selectable so the // stored value still displays instead of silently reverting to "— none —". select.append( Object.assign(document.createElement("option"), { value: current, textContent: state.plan.inventory.lotLabel || "Unavailable lot", disabled: true, }), ); } select.value = current || ""; } async function loadLots() { try { const body = await api("/api/inventory"); lots = body.lots; } catch { lots = []; } renderOptions(); updateConsumeRow(); } function updateConsumeRow() { const lotId = state.plan.inventory.lotId; if (!lotId) { consumeRow.classList.add("hidden"); return; } consumeRow.classList.remove("hidden"); const consumed = state.plan.inventory.consumed; if (consumed) { consumeLabel.textContent = `✓ ${Math.round(consumed.weightG)} g drawn from ${consumed.lotLabel} · ${new Date(consumed.atIso).toLocaleDateString()}`; consumeBtn.classList.add("hidden"); return; } consumeBtn.classList.remove("hidden"); const weightG = Number(state.plan.fields["0.4"]); const lot = findLot(lotId); const lotLabel = lot ? lot.origin : state.plan.inventory.lotLabel || "this lot"; if (Number.isFinite(weightG) && weightG > 0) { consumeLabel.textContent = `Charging will draw ${weightG} g from ${lotLabel}.`; consumeBtn.disabled = false; consumeBtn.removeAttribute("title"); } else { consumeLabel.textContent = `Set "Green in" (field 0.4) to draw weight from ${lotLabel}.`; consumeBtn.disabled = true; consumeBtn.title = "Enter a Green in weight on The Coffee first"; } } select.addEventListener("change", () => { const lotId = select.value; state.plan.inventory.lotId = lotId; const lot = findLot(lotId); state.plan.inventory.lotLabel = lot ? `${lot.origin}${lot.variety ? ` — ${lot.variety}` : ""}` : ""; if (lot) { note.textContent = `${Math.round(lot.remainingWeightG)} g remaining in this lot.`; const nameInput = document.querySelector('[name="0.1"]'); if (nameInput && !nameInput.value) { nameInput.value = lot.origin; state.plan.fields["0.1"] = lot.origin; } } else { note.textContent = "Optional — link a lot and the Roast Log can draw the green weight down when you charge."; } updateConsumeRow(); recompute(); }); consumeBtn.addEventListener("click", async () => { consumeBtn.disabled = true; try { if (!(await flushCurrentPlan())) { showToast("Could not sync the plan. Try again.", "fail"); return; } const roastPlanId = getRemotePlanId(); if (!roastPlanId) { showToast("Sync the plan first, then draw from the lot.", "fail"); return; } const weightG = Number(state.plan.fields["0.4"]); const response = await protectedFetch( `/api/inventory/${state.plan.inventory.lotId}/consume`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ weightG, roastPlanId }), }, ); const body = await response.json().catch(() => ({})); if (!response.ok && body.code !== "already_consumed") { showToast(body.error || "Could not draw from the lot.", "fail"); return; } state.plan.inventory.consumed = { lotId: state.plan.inventory.lotId, lotLabel: state.plan.inventory.lotLabel, weightG, atIso: new Date().toISOString(), }; updateConsumeRow(); recompute(); showToast(`${weightG} g drawn from lot.`); await loadLots(); } finally { consumeBtn.disabled = false; } }); loadLots(); return { updateConsumeRow, renderOptions }; }