import { api, protectedFetch } from "./api.js?v=__ASSET_VERSION__"; import { initSideNav, loadNavUser } from "./nav.js?v=__ASSET_VERSION__"; import { blankPlan } from "/shared/fields.js?v=__ASSET_VERSION__"; import { showToast } from "./toast.js?v=__ASSET_VERSION__"; const form = document.getElementById("plan-filters"), body = document.getElementById("plans-body"), status = document.getElementById("plans-status"); const initial = new URLSearchParams(location.search); let page = Math.max(1, Number(initial.get("page")) || 1), generation = 0, total = 0; for (const input of form.elements) if (input.name && initial.has(input.name)) input.value = initial.get(input.name); function node(tag, text, className = "") { const el = document.createElement(tag); el.textContent = text; el.className = className; return el; } async function load() { const current = ++generation; const params = new URLSearchParams(new FormData(form)); params.set("view", "summary"); params.set("page", page); params.set("limit", "25"); status.textContent = "Loading plans…"; try { const data = await api("/api/plans?" + params); if (current !== generation) return; total = data.total; if (page > 1 && (page - 1) * 25 >= total) { page = Math.max(1, Math.ceil(total / 25)); return load(); } const urlParams = new URLSearchParams(params); urlParams.delete("view"); urlParams.delete("limit"); history.replaceState(null, "", "/plans?" + urlParams); body.replaceChildren(); for (const plan of data.plans) { const tr = document.createElement("tr"), title = document.createElement("td"), link = node("a", plan.title); link.href = "/app?plan=" + encodeURIComponent(plan.id); link.className = "plan-title-link"; title.append(link); if (plan.coffee && plan.coffee !== plan.title) title.append(node("div", plan.coffee, "muted")); tr.append( title, ...[ plan.origin, plan.cultivar, plan.process, plan.level, plan.batchG ? plan.batchG + " g" : "—", new Date(plan.updated_at).toLocaleDateString(), ].map((v) => node("td", v || "—")), ); const actions = document.createElement("td"); actions.className = "data-table-actions"; const rename = node("button", "Rename", "ghost-btn small"); rename.type = "button"; rename.setAttribute("aria-label", "Rename " + plan.title); rename.onclick = async () => { const name = prompt("Plan name", plan.name || plan.title); if (name === null) return; try { await api("/api/plans/" + plan.id, { method: "PUT", body: JSON.stringify({ name }), }); await load(); } catch (e) { showToast(e.message, "fail"); } }; const remove = node("button", "Delete", "ghost-btn small"); remove.type = "button"; remove.setAttribute("aria-label", "Delete " + plan.title); remove.onclick = async () => { if (!confirm(`Delete “${plan.title}”?`)) return; try { await api("/api/plans/" + plan.id, { method: "DELETE" }); await load(); } catch (e) { showToast(e.message, "fail"); } }; actions.append(rename, remove); tr.append(actions); body.append(tr); } if (!data.plans.length) { const tr = document.createElement("tr"), td = node("td", "No plans match these filters.", "empty-state"); td.colSpan = 8; tr.append(td); body.append(tr); } status.textContent = total ? `${(page - 1) * 25 + 1}–${Math.min(page * 25, total)} of ${total} plans` : "0 plans"; document.getElementById("plans-prev").disabled = page <= 1; document.getElementById("plans-next").disabled = page * 25 >= total; } catch (e) { if (current === generation) { status.textContent = "Could not load plans. " + e.message; body.replaceChildren(); } } } let timer; form.addEventListener("input", () => { clearTimeout(timer); page = 1; generation++; timer = setTimeout(load, 250); }); form.addEventListener("submit", (e) => { e.preventDefault(); clearTimeout(timer); page = 1; load(); }); document.getElementById("plans-clear").onclick = () => { form.reset(); page = 1; load(); }; document.getElementById("plans-prev").onclick = () => { page--; load(); }; document.getElementById("plans-next").onclick = () => { page++; load(); }; document.getElementById("plans-new").onclick = async (e) => { e.currentTarget.disabled = true; try { const result = await api("/api/plans", { method: "POST", body: JSON.stringify({ plan: blankPlan() }), }); location.assign("/app?plan=" + result.plan.id); } catch (error) { showToast(error.message, "fail"); document.getElementById("plans-new").disabled = false; } }; document.getElementById("btn-logout").addEventListener("click", async () => { await protectedFetch("/api/auth/logout", { method: "POST" }); location.assign("/"); }); initSideNav(); await loadNavUser(); await load();