Academy v4: 3 research-backed lessons (30 slides + audio); new bean logo; custom plan/lot names
Test and deploy / test-and-deploy (push) Successful in 59s
Test and deploy / test-and-deploy (push) Successful in 59s
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
ec078116b4
commit
8b80c3fa6f
+47
-6
@@ -28,6 +28,9 @@ const STORAGE_PREFIX = "roastPlannerPlan.v2";
|
||||
let storageKey = null;
|
||||
let remotePlanId = null;
|
||||
let draftSyncedAt = null;
|
||||
// The custom name of the currently loaded plan (server column, not part of the plan JSONB —
|
||||
// see roast_plans.name). Header display prefers this over worksheet field 0.1 when set.
|
||||
let currentPlanName = "";
|
||||
let plans = [];
|
||||
let lastDrawerOpener = null;
|
||||
let deferredInstallPrompt = null;
|
||||
@@ -321,7 +324,7 @@ export function renderFormFromPlan() {
|
||||
}
|
||||
updateBlendVisibility(state.plan.fields["2.1"]);
|
||||
document.getElementById("header-coffee-name").textContent =
|
||||
state.plan.fields["0.1"] || "New plan";
|
||||
currentPlanName || state.plan.fields["0.1"] || "New plan";
|
||||
}
|
||||
|
||||
function fmtOut(id, text) {
|
||||
@@ -389,9 +392,9 @@ function renderLedger() {
|
||||
fmtOut("pa-drop", d(ledger.D));
|
||||
|
||||
document.getElementById("back-coffee-name").textContent =
|
||||
state.plan.fields["0.1"] || "";
|
||||
currentPlanName || state.plan.fields["0.1"] || "";
|
||||
document.getElementById("header-coffee-name").textContent =
|
||||
state.plan.fields["0.1"] || "New plan";
|
||||
currentPlanName || state.plan.fields["0.1"] || "New plan";
|
||||
|
||||
return ledger;
|
||||
}
|
||||
@@ -580,6 +583,7 @@ function syncPlan(snapshot = structuredClone(state.plan)) {
|
||||
const body = await response.json();
|
||||
remotePlanId = body.plan.id;
|
||||
draftSyncedAt = body.plan.updated_at;
|
||||
currentPlanName = body.plan.name ?? currentPlanName;
|
||||
if (storageKey)
|
||||
localStorage.setItem(
|
||||
storageKey,
|
||||
@@ -838,6 +842,11 @@ function wireDrawers() {
|
||||
return { open, closeAll };
|
||||
}
|
||||
|
||||
// The custom `name` column wins over worksheet field 0.1 wherever a plan's title is shown —
|
||||
// mirrors the server's COALESCE(NULLIF(name,''), plan->'fields'->>'0.1') fallback.
|
||||
function planDisplayTitle(plan) {
|
||||
return plan.name || plan.plan?.fields?.["0.1"] || "Untitled plan";
|
||||
}
|
||||
async function loadPlans() {
|
||||
if (!navigator.onLine) return;
|
||||
const response = await fetch("/api/plans");
|
||||
@@ -853,12 +862,42 @@ async function loadPlans() {
|
||||
button.className = "plan-list-item";
|
||||
button.classList.toggle("active", plan.id === remotePlanId);
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = plan.plan?.fields?.["0.1"] || "Untitled plan";
|
||||
title.textContent = planDisplayTitle(plan);
|
||||
const updated = document.createElement("span");
|
||||
updated.textContent = `Updated ${new Date(plan.updated_at).toLocaleDateString()}`;
|
||||
button.append(title, updated);
|
||||
button.addEventListener("click", () => selectPlan(plan));
|
||||
|
||||
const renameBtn = document.createElement("button");
|
||||
renameBtn.type = "button";
|
||||
renameBtn.className = "icon-btn plan-list-rename";
|
||||
renameBtn.style.flexShrink = "0";
|
||||
renameBtn.style.alignSelf = "center";
|
||||
renameBtn.setAttribute("aria-label", "Rename plan");
|
||||
renameBtn.textContent = "✎";
|
||||
// A sibling of the select button, not nested inside it — stopPropagation alone
|
||||
// wouldn't be enough to keep a click here from also selecting the plan otherwise.
|
||||
renameBtn.addEventListener("click", async (event) => {
|
||||
event.stopPropagation();
|
||||
const next = prompt("Plan name", plan.name || "");
|
||||
if (next === null) return;
|
||||
try {
|
||||
await api(`/api/plans/${plan.id}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ name: next.trim() }),
|
||||
});
|
||||
} catch (error) {
|
||||
showToast(error.message, "fail");
|
||||
return;
|
||||
}
|
||||
if (plan.id === remotePlanId) {
|
||||
currentPlanName = next.trim();
|
||||
renderFormFromPlan();
|
||||
renderLedger();
|
||||
}
|
||||
await loadPlans();
|
||||
});
|
||||
|
||||
const deleteBtn = document.createElement("button");
|
||||
deleteBtn.type = "button";
|
||||
deleteBtn.className = "icon-btn plan-list-delete";
|
||||
@@ -871,7 +910,7 @@ async function loadPlans() {
|
||||
deletePlan(plan);
|
||||
});
|
||||
|
||||
item.append(button, deleteBtn);
|
||||
item.append(button, renameBtn, deleteBtn);
|
||||
return item;
|
||||
}),
|
||||
);
|
||||
@@ -880,6 +919,7 @@ async function selectPlan(plan) {
|
||||
if (!(await flushCurrentPlan())) return;
|
||||
remotePlanId = plan.id;
|
||||
draftSyncedAt = plan.updated_at;
|
||||
currentPlanName = plan.name || "";
|
||||
state.plan = { ...blankPlan(), ...plan.plan };
|
||||
history.replaceState(
|
||||
null,
|
||||
@@ -903,6 +943,7 @@ async function selectPlan(plan) {
|
||||
function resetToBlankPlan() {
|
||||
remotePlanId = null;
|
||||
draftSyncedAt = null;
|
||||
currentPlanName = "";
|
||||
state.plan = blankPlan();
|
||||
history.replaceState(null, "", "/app");
|
||||
renderBlend();
|
||||
@@ -922,7 +963,7 @@ async function newPlan() {
|
||||
document.querySelector("[data-close-drawer]")?.click();
|
||||
}
|
||||
async function deletePlan(plan) {
|
||||
const name = plan.plan?.fields?.["0.1"] || "Untitled plan";
|
||||
const name = planDisplayTitle(plan);
|
||||
if (!confirm(`Delete "${name}"? This cannot be undone.`)) return;
|
||||
try {
|
||||
await api(`/api/plans/${plan.id}`, { method: "DELETE" });
|
||||
|
||||
Reference in New Issue
Block a user