Fix report pagination and rebuild saved plan navigation
Test and deploy / test-and-deploy (push) Successful in 1m43s
Test and deploy / test-and-deploy (push) Successful in 1m43s
This commit is contained in:
1 parent
824a964f9d
commit
5f3a015ea2
20 files changed
+1480
-809
No files matched your search
@@ -0,0 +1,159 @@
|
||||
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();
|
||||
Reference in new issue
Block a user