160 lines
4.8 KiB
JavaScript
160 lines
4.8 KiB
JavaScript
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();
|