Add green-bean inventory and cupping features
Ports inventory management and SCA-style cupping scoring from hope_roaster: lot tracking with audit-logged consumption, cupping sessions with server-authoritative scoring and a live radar chart, and links from the planner (draw-from-lot, open-cupping-session). Also fixes the Blend/Single-origin toggle layout, replaces tooltips with an in-context "why" teaching layer, and stages the planner UI into pre-roast vs. post-roast phases.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// Shared side-nav behavior (collapse/expand, mobile drawer) for every authenticated page
|
||||
// (planner, account, admin) so each page's own script doesn't reimplement it.
|
||||
export function initSideNav() {
|
||||
const nav = document.getElementById("side-nav");
|
||||
const hamburger = document.getElementById("nav-hamburger");
|
||||
const collapseBtn = document.getElementById("nav-collapse");
|
||||
const workspace = document.getElementById("app-workspace");
|
||||
const overlay = document.getElementById("drawer-overlay");
|
||||
const COLLAPSE_KEY = "roastPlannerNavCollapsed";
|
||||
|
||||
function closeMobileNav() {
|
||||
nav.classList.remove("mobile-open");
|
||||
hamburger?.setAttribute("aria-expanded", "false");
|
||||
if (!document.querySelector(".drawer:not(.hidden)"))
|
||||
overlay?.classList.add("hidden");
|
||||
}
|
||||
hamburger?.addEventListener("click", () => {
|
||||
const open = nav.classList.toggle("mobile-open");
|
||||
hamburger.setAttribute("aria-expanded", String(open));
|
||||
overlay?.classList.toggle("hidden", !open);
|
||||
});
|
||||
overlay?.addEventListener("click", closeMobileNav);
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") closeMobileNav();
|
||||
});
|
||||
|
||||
function applyCollapsed(collapsed) {
|
||||
nav.classList.toggle("collapsed", collapsed);
|
||||
workspace?.classList.toggle("nav-collapsed", collapsed);
|
||||
if (collapseBtn) {
|
||||
collapseBtn.textContent = collapsed ? "»" : "«";
|
||||
collapseBtn.setAttribute(
|
||||
"aria-label",
|
||||
collapsed ? "Expand navigation" : "Collapse navigation",
|
||||
);
|
||||
}
|
||||
}
|
||||
applyCollapsed(localStorage.getItem(COLLAPSE_KEY) === "true");
|
||||
collapseBtn?.addEventListener("click", () => {
|
||||
const next = !nav.classList.contains("collapsed");
|
||||
applyCollapsed(next);
|
||||
localStorage.setItem(COLLAPSE_KEY, String(next));
|
||||
});
|
||||
return { closeMobileNav };
|
||||
}
|
||||
|
||||
/** Populates the nav's user chip and admin link; redirects to /login when signed out. */
|
||||
export async function loadNavUser() {
|
||||
const response = await fetch("/api/auth/me");
|
||||
if (!response.ok) {
|
||||
location.replace("/login");
|
||||
return null;
|
||||
}
|
||||
const { user } = await response.json();
|
||||
const emailEl = document.getElementById("account-email");
|
||||
if (emailEl) emailEl.textContent = user.email;
|
||||
const avatarEl = document.getElementById("nav-user-avatar");
|
||||
if (avatarEl) avatarEl.textContent = user.email.charAt(0).toUpperCase();
|
||||
if (user.role === "admin")
|
||||
document.getElementById("nav-admin")?.classList.remove("hidden");
|
||||
return user;
|
||||
}
|
||||
Reference in New Issue
Block a user