/* ============================================================= CoreIQ Office — App shell, sign-in, state ============================================================= */ const { useState: oUseState, useEffect: oUseEffect, useMemo: oUseMemo, useCallback: oUseCallback, useRef: oUseRef, createContext: oCC, useContext: oUC } = React; const OD = window.OFFICE_DATA; const oMoney = window.officeMoney; const findP = window.findOfficeProduct; const findS = window.findOfficeStore; const findSup = window.findOfficeSupplier; const findU = window.findOfficeUser; // ----------------------------------------------------------------- // Office context — shared state // ----------------------------------------------------------------- const OfficeContext = oCC(null); window.useOffice = () => oUC(OfficeContext); function OfficeProvider({ children }) { // Sign-in — persisted so switching store (which reloads the page) doesn't log you out. // In Cognito mode the session is only valid while the stored ID token is unexpired; // an expired/missing token forces a fresh sign-in. const [signedIn, setSignedIn] = oUseState(() => { try { if (window.OFFICE_COGNITO) { const t = sessionStorage.getItem("coreiq-office-idtoken"); if (!t) return false; const body = JSON.parse(atob(t.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"))); if (!body.exp || body.exp * 1000 < Date.now() + 60000) return false; window.OfficeAPI.setToken(t); return true; } return localStorage.getItem("coreiq-office-signedin") === "1"; } catch (_) { return false; } }); oUseEffect(() => { try { localStorage.setItem("coreiq-office-signedin", signedIn ? "1" : "0"); } catch (_) {} }, [signedIn]); const [activeUser, setActiveUser] = oUseState(OD.USERS[0]); // Multi-store (group) switcher: every store the owner controls ACROSS their orgs, // loaded LIVE from /stores. Each store is one (org, site). The active store sets // which org/site all screens query (sent as headers; persisted across the reload // we do on switch). Empty until loaded. const [stores, setStores] = oUseState([]); const [storesLoading, setStoresLoading] = oUseState(true); const [orgError, setOrgError] = oUseState(null); const [activeStoreId, setActiveStoreId] = oUseState(null); // the active store's site id oUseEffect(() => { let alive = true; window.OfficeAPI.listStores() .then(res => { if (!alive) return; const list = (res.stores || []).map(s => ({ id: s.siteId, orgId: s.orgId, code: s.code, name: s.siteName, orgName: s.orgName })); setStores(list); // Reconcile the persisted store against the LIVE list. The saved org can go // stale (e.g. a re-migration moved a site under a different org), and since // every request is scoped by that org, a stale org silently returns ZEROS // across the whole dashboard. Match by site (authoritative), else the org the // API resolved to, else the first store — then re-persist the CURRENT org so // the headers hit the right tenant. const active = window.OfficeAPI.getActiveStore(); const chosen = list.find(s => s.id === active.siteId) || list.find(s => s.orgId === res.defaultOrgId) || list[0] || null; if (chosen && (active.orgId !== chosen.orgId || active.siteId !== chosen.id)) { window.OfficeAPI.setActiveStore(chosen.orgId, chosen.id); // Reload once so every screen re-queries the corrected org (same mechanism // as switchStore). Guard against a reload loop if it can't converge. if (!sessionStorage.getItem("coreiq-store-reconciled")) { sessionStorage.setItem("coreiq-store-reconciled", "1"); window.location.reload(); return; } } setActiveStoreId(chosen ? chosen.id : null); setStoresLoading(false); }) .catch(err => { if (alive) { setOrgError(String(err && err.message || err)); setStoresLoading(false); } }); return () => { alive = false; }; }, []); // Switch the active store → persist + reload so every screen re-queries that org. const switchStore = oUseCallback((store) => { if (!store || store.id === activeStoreId) return; window.OfficeAPI.setActiveStore(store.orgId, store.id); window.location.reload(); }, [activeStoreId]); // Active store as the "scope" the rest of the shell already understands. const scope = activeStoreId || "all"; const setScope = () => {}; // legacy no-op; switching goes through switchStore const scopedStores = stores.filter(s => s.id === activeStoreId); const scopedStoreIds = scopedStores.map(s => s.id); // Navigation const [screen, setScreen] = oUseState("dashboard"); const [screenState, setScreenState] = oUseState({}); // Toasts const [toasts, setToasts] = oUseState([]); const pushToast = oUseCallback((t) => { const id = "t" + Date.now() + Math.random(); setToasts(ts => [...ts, { ...t, id }]); setTimeout(() => setToasts(ts => ts.filter(x => x.id !== id)), 4200); }, []); const dismissToast = (id) => setToasts(ts => ts.filter(t => t.id !== id)); // Right-side drawer (used by store-switcher etc) const [drawer, setDrawer] = oUseState(null); // null | "stores" const value = { signedIn, setSignedIn, activeUser, setActiveUser, scope, setScope, scopedStores, scopedStoreIds, activeStoreId, switchStore, stores, storesLoading, orgError, screen, setScreen, screenState, setScreenState, toasts, pushToast, dismissToast, drawer, setDrawer, }; return {children}; } // ----------------------------------------------------------------- // Icon helper — defaults 16×16 // ----------------------------------------------------------------- function OIcon({ name, width = 16, height = 16, ...rest }) { return ; } window.OIcon = OIcon; // ----------------------------------------------------------------- // Nav definitions // ----------------------------------------------------------------- // MVP menu — only the features wired to the live Office API are shown. The rest // of the back-office (Stores, Buying, Customer, Clinical, Government, Staff, // Finance, Reports, Operations, and the Admin extras) is built in the prototype // but NOT yet wired to the API, so it is hidden until each vertical is wired. // The complete menu is preserved as OFF_NAV_FUTURE below for phased re-enable. const OFF_NAV = [ { section: "Overview", items: [ { key: "dashboard", label: "Dashboard", icon: "dashboard" }, { key: "customers", label: "Customers", icon: "users" }, ]}, { section: "Catalogue & stock", items: [ { key: "catalogue", label: "Product catalogue", icon: "pill" }, { key: "stockOverview", label: "Stock overview", icon: "stock" }, { key: "manageStock", label: "Manage stock", icon: "list" }, { key: "newStock", label: "New stock line", icon: "plus" }, { key: "suppliers", label: "Suppliers", icon: "truck" }, ]}, { section: "History", items: [ { key: "salesHistory", label: "Sales history", icon: "doc" }, { key: "invoices", label: "Wholesaler invoices", icon: "doc" }, { key: "accounts", label: "Account statements", icon: "card" }, { key: "stockHistory", label: "Stock movements", icon: "transfer" }, ]}, { section: "Analytics", items: [ { key: "salesAnalytics", label: "Sales analytics", icon: "chart" }, { key: "inventoryAnalytics", label: "Inventory intelligence", icon: "stock" }, { key: "financialAnalytics", label: "Financial analytics", icon: "card" }, { key: "pricePosition", label: "Price position", icon: "tag" }, { key: "shortageRadar", label: "Supply & shortages", icon: "truck" }, ]}, { section: "Administration", items: [ { key: "storeSetup", label: "Store setup", icon: "settings" }, ]}, ]; // Full back-office menu — future work, re-enable per vertical once wired. const OFF_NAV_FUTURE = [ { section: "Overview", items: [ { key: "dashboard", label: "Dashboard", icon: "dashboard" }, ]}, { section: "Stock", items: [ { key: "stockOverview", label: "Stock overview", icon: "stock" }, { key: "manageStock", label: "Manage stock", icon: "list" }, { key: "newStock", label: "New stock line", icon: "plus" }, { key: "labels", label: "Print labels", icon: "printer" }, { key: "esls", label: "Electronic shelf labels", icon: "tag" }, { key: "stocktake", label: "Stocktake", icon: "clipboard" }, { key: "planner", label: "Floor & shelf planner", icon: "shelf" }, { key: "shelfMarket", label: "Shelf marketplace", icon: "tag" }, ]}, { section: "Stores", items: [ { key: "stores", label: "Stores", icon: "store" }, { key: "transfers", label: "Stock transfers", icon: "transfer" }, ]}, { section: "Buying", items: [ { key: "purchaseOrders", label: "Purchase orders", icon: "doc" }, { key: "receiving", label: "Receiving", icon: "download" }, { key: "ordering", label: "Ordering channels", icon: "truck" }, { key: "priceLists", label: "Price lists", icon: "list" }, { key: "backorders", label: "Backorders", icon: "clock" }, { key: "buyingGroups", label: "Groups & formulary", icon: "users" }, { key: "ediStatus", label: "EDI status", icon: "transfer" }, { key: "invoices", label: "Wholesaler invoices", icon: "doc" }, ]}, { section: "Customer", items: [ { key: "customerProfiles", label: "Customer profiles", icon: "users" }, { key: "scriptsOnFile", label: "Scripts on file", icon: "rx" }, { key: "commsPrefs", label: "Consent & comms", icon: "phone" }, { key: "accounts", label: "Payment accounts", icon: "card" }, { key: "promotions", label: "Promotions", icon: "tag" }, { key: "loyalty", label: "Loyalty", icon: "loyalty" }, { key: "vouchers", label: "Corporate vouchers", icon: "loyalty" }, ]}, { section: "Clinical", items: [ { key: "ironbark", label: "Ironbark · AI", icon: "pill" }, { key: "vaccinations", label: "Vaccinations · AIR", icon: "check-circle" }, { key: "servicesLog", label: "MedsCheck & services", icon: "doc" }, { key: "medProfiles", label: "Patient med profiles", icon: "user" }, ]}, { section: "Government programs", items: [ { key: "govPrograms", label: "Federal · State · PPA", icon: "doc" }, ]}, { section: "Staff", items: [ { key: "staff", label: "Profiles, roster, training", icon: "users" }, ]}, { section: "Finance", items: [ { key: "payments", label: "Payments · Adyen", icon: "card" }, { key: "pbsClaims", label: "PBS claims", icon: "doc" }, { key: "eodRecon", label: "End of day", icon: "clock" }, { key: "invoiceMatching",label: "Invoice matching", icon: "transfer" }, { key: "pnl", label: "P&L by store", icon: "chart" }, { key: "tax", label: "Tax & BAS", icon: "doc" }, ]}, { section: "Reports", items: [ { key: "reports", label: "Reports", icon: "chart" }, { key: "schemas", label: "Report columns", icon: "columns" }, ]}, { section: "Operations", items: [ { key: "operations", label: "Incidents & SLA", icon: "alert" }, ]}, { section: "Admin", items: [ { key: "users", label: "Users & roles", icon: "users" }, { key: "suppliers", label: "Suppliers", icon: "truck" }, { key: "auditLogs", label: "Audit logs", icon: "doc" }, { key: "integrations", label: "Integrations", icon: "transfer" }, { key: "settings", label: "Settings", icon: "settings" }, ]}, ]; // ----------------------------------------------------------------- // Sign-in // ----------------------------------------------------------------- function SignIn() { const { setSignedIn, setActiveUser } = window.useOffice(); const [email, setEmail] = oUseState(window.OFFICE_COGNITO ? "" : "suni.k@coreiq.au"); const [password, setPassword] = oUseState(window.OFFICE_COGNITO ? "" : "•••••••••"); const [error, setError] = oUseState(null); const [loading, setLoading] = oUseState(false); // Real Cognito sign-in when the page carries pool config (production); // the demo-user path remains for local dev where no pool is configured. async function cognitoAuth() { const cfg = window.OFFICE_COGNITO; const res = await fetch(`https://cognito-idp.${cfg.region}.amazonaws.com/`, { method: "POST", headers: { "Content-Type": "application/x-amz-json-1.1", "X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth", }, body: JSON.stringify({ ClientId: cfg.clientId, AuthFlow: "USER_PASSWORD_AUTH", AuthParameters: { USERNAME: email, PASSWORD: password }, }), }); const data = await res.json(); if (!res.ok || !data.AuthenticationResult || !data.AuthenticationResult.IdToken) { throw new Error(data.message || "Sign-in failed — check your email and password."); } const idToken = data.AuthenticationResult.IdToken; window.OfficeAPI.setToken(idToken); try { sessionStorage.setItem("coreiq-office-idtoken", idToken); } catch (_) {} const first = (email.split("@")[0].split(/[._-]/)[0] || "User"); return { id: "cognito", email, first: first[0].toUpperCase() + first.slice(1), last: "", role: "Owner", status: "active", }; } function tryAuth(e) { e.preventDefault(); setError(null); if (window.OFFICE_COGNITO) { setLoading(true); cognitoAuth() .then((user) => { setActiveUser(user); setSignedIn(true); setLoading(false); }) .catch((err) => { setError(String(err.message || err)); setLoading(false); }); return; } const user = OD.USERS.find(u => u.email === email && u.status === "active"); if (!user) { setError("No account matches that email — or it's been suspended."); return; } setLoading(true); setTimeout(() => { setActiveUser(user); setSignedIn(true); setLoading(false); }, 350); } function pickDemoUser(u) { setEmail(u.email); setError(null); } return (
C
CoreIQ Office · Admin web

Sign in to Office

Multi-store admin, stock and reporting.

setEmail(e.target.value)} autoFocus/>
setPassword(e.target.value)}/>
{error &&
{error}
} {window.OFFICE_COGNITO ? null :
Demo · try a user
}
{window.OFFICE_COGNITO ? null : {OD.USERS.filter(u => u.status === "active").slice(0, 4).map(u => ( ))} }
Forgot password? v0.2 · 26 May 2026
); } // ----------------------------------------------------------------- // Sidebar // ----------------------------------------------------------------- function OffSidebar() { const { screen, setScreen, activeUser, setSignedIn, setDrawer } = window.useOffice(); const badgeFor = (key) => { switch (key) { case "labels": return OD.LABEL_QUEUE.filter(l => l.status === "queued").length; case "stocktake": return OD.STOCKTAKES.filter(s => s.status === "in-progress").length; case "transfers": return OD.TRANSFERS.filter(t => t.status === "pending" || t.status === "in-transit").length; case "accounts": return null; case "promotions": return null; case "users": return OD.USERS.filter(u => u.status === "pending").length; default: return null; } }; return ( ); } // ----------------------------------------------------------------- // Data-source badge — tells you, per screen, whether what you're looking at is // REAL (this org's live Aurora data), real-but-shared (the national reference // catalogue), or still mock prototype data. Keyed by screen so it can never // drift from the truth: a screen is only "live" once it's listed here. // ----------------------------------------------------------------- const SCREEN_SOURCE = { dashboard: "live-org", // real org KPIs from /overview stockOverview: "live-org", // real org inventory from /stock + /overview manageStock: "live-org", // real products; edits PATCH to Aurora → POS suppliers: "live-org", // real suppliers from /suppliers supplierDetail: "live-org", // real supplier profile + its products productDetail: "live-org", // full real product page; edits PATCH to Aurora → POS catalogue: "live-ref", // real, but the NATIONAL shared reference catalogue (not this org's products) // This pharmacy's own real data — to the user it's a seamless continuation, not an "archive". salesHistory: "live-org", invoices: "live-org", accounts: "live-org", stockHistory: "live-org", salesAnalytics: "live-org", inventoryAnalytics: "live-org", financialAnalytics: "live-org", pricePosition: "live-org", shortageRadar: "live-org", }; const DATA_BADGE = { "live-org": { text: "LIVE · this pharmacy", bg: "var(--paid-bg)", fg: "var(--paid-text)", dot: "var(--paid)", title: "Real data for the signed-in organisation, queried live from Aurora." }, "live-ref": { text: "LIVE · national reference", bg: "var(--navy-100)", fg: "var(--navy-800)", dot: "var(--navy-600)", title: "Real data, but the shared national ARTG/PBS reference catalogue — not this organisation's own products." }, "live-archive": { text: "LIVE · legacy archive", bg: "var(--hold-bg)", fg: "var(--hold-text)", dot: "var(--hold)", title: "Real retained data from the legacy Z system (7-year retention archive), queried live from Aurora. Read-only history." }, "demo": { text: "DEMO DATA", bg: "var(--hold-bg)", fg: "var(--hold-text)", dot: "var(--hold)", title: "Mock prototype data. NOT real — this screen is not wired to the database yet." }, }; function DataBadge({ screen }) { const cfg = DATA_BADGE[SCREEN_SOURCE[screen] || "demo"]; return ( {cfg.text} ); } // ----------------------------------------------------------------- // Topbar // ----------------------------------------------------------------- function OffTopbar() { const { scope, stores, storesLoading, screen, setDrawer, drawer } = window.useOffice(); const cur = stores.find(s => s.id === scope); const label = storesLoading ? "Loading stores…" : (cur ? cur.name : "Select store"); const avLabel = cur?.code || "··"; return (
⌘ K
); } // ----------------------------------------------------------------- // Store switcher drawer // ----------------------------------------------------------------- function StoreDrawer() { const { scope, switchStore, setDrawer, stores, storesLoading, orgError } = window.useOffice(); return (
Your stores · live from Aurora
Switch store
{storesLoading &&
Loading stores…
} {orgError &&
Couldn't load stores: {orgError}
} {!storesLoading && !orgError && stores.length === 0 &&
No stores found.
} {stores.map(s => (
{ setDrawer(null); switchStore(s); }} >
{s.code || "··"}
{s.name}
{s.orgName} · live
{scope === s.id && }
))}
); } // ----------------------------------------------------------------- // Toast stack // ----------------------------------------------------------------- function OffToastStack() { const { toasts, dismissToast } = window.useOffice(); return (
{toasts.map(t => (

{t.title}

{t.meta ?

{t.meta}

: null}
))}
); } // ----------------------------------------------------------------- // Screen host // ----------------------------------------------------------------- function OffScreenHost() { const { screen } = window.useOffice(); const Comp = (window.OFFICE_SCREENS || {})[screen]; if (!Comp) { return (
Loading

{screen}

Screen not registered yet
); } return ; } // ----------------------------------------------------------------- // App entry // ----------------------------------------------------------------- function OfficeApp() { const [theme, setTheme] = oUseState(() => { try { return localStorage.getItem("coreiq-office-theme") || "light"; } catch (_) { return "light"; } }); oUseEffect(() => { document.documentElement.setAttribute("data-theme", theme); try { localStorage.setItem("coreiq-office-theme", theme); } catch (_) {} }, [theme]); return ( ); } function OfficeAppInner({ theme, setTheme }) { const { signedIn, drawer } = window.useOffice(); if (!signedIn) return ; return ( <>
{drawer === "stores" && }
); } // Mount (function mount() { const node = document.getElementById("office-root"); if (!node) { window.addEventListener("DOMContentLoaded", mount); return; } const root = ReactDOM.createRoot(node); root.render(); })();