// Shared, principal-agnostic UI kit for the admin and dashboard SPAs. // // Served under both /admin/ and /dashboard/, so everything here must work the // same for a staff operator and a tenant member. Keep it principal-agnostic: // no admin-only or tenant-only logic belongs in this file. Anything that // branches on who the caller is stays in the app that owns that behavior. // // Declarations are intentionally left at classic-script global scope (no IIFE) // so the app bundles, which are IIFEs, can read them as free variables. // Session expired mid-page (async calls get 401): send the user back to login. const client = new APIClient('', { onUnauthorized() { window.location.href = "/login" }, }) function navLink(path, label) { const cur = m.route.get() const active = cur === "/" + path || cur.startsWith("/" + path + "/") return m(m.route.Link, {href: "/" + path, class: active ? "active" : ""}, label) } const tenantNames = {} function getTenantName(id) { if (tenantNames[id]) return tenantNames[id] tenantNames[id] = id client.Tenants_Get({ID: id}).then(res => { tenantNames[id] = res.Tenant.Name; m.redraw() }) return id } function table(cols, rows, empty, onRowClick) { if (!rows || rows.length === 0) return m(".empty", empty || "No items found.") return m("table", [ m("thead", m("tr", cols.map(c => m("th", c.label)))), m("tbody", rows.map(row => m("tr", { onclick: onRowClick ? () => onRowClick(row) : undefined, style: onRowClick ? "cursor:pointer" : "", }, cols.map(c => m("td", {class: c.cls || ""}, c.value(row)))) )), ]) }