// App shell — nav, header, routing, context menu, drill flow const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA, useRef: useRefA, useCallback: useCallbackA } = React; function Sidebar({ route, onNav }) { const items = [ { id:"overview", label:"Overview", icon:"dashboard" }, { id:"accounts", label:"Accounts", icon:"accounts" }, { id:"changes", label:"Changes feed", icon:"feed" }, { id:"alerts", label:"Alerts", icon:"alerts", badge: window.FABCOM_DATA.alerts.filter(a=>a.severity==="red").length }, ]; return ( ); } function TopBar({ onToggleTheme, theme }) { return (
dash.fabcom.app Meta Ads
Last sync {window.timeAgo(window.FABCOM_DATA.meta.last_refreshed)} · BQ fabcom-etl-core.meta_ads_settings
); } // Alerts standalone page function AlertsPage({ onInvestigate }) { const data = window.FABCOM_DATA; const red = data.alerts.filter(a => a.severity === "red"); const amber = data.alerts.filter(a => a.severity === "amber"); const Section = ({title, items, tone}) => (
{title}
{items.length}
{items.map(al => { const acc = data.accounts.find(a => a.id === al.account_id); return (
{al.title}
{al.kind.replace("_"," ")}
{al.detail}
{al.kind === "big_edit" ? : (<>Current {window.fmtHKD(window.toHKD(al.value, acc.ccy))}·Baseline {window.fmtHKD(window.toHKD(al.baseline, acc.ccy))})}
); })} {items.length === 0 &&
No {tone} alerts.
}
); return (
Alerts · thresholds configured in ops/budget_rules.yaml

Budget misconfig alerts

); } function BQModal({ account, onClose }) { const sql = `-- Daily settings for account ${account.id} (${account.name}) SELECT a.account_id, a.account_name, a.currency, a.balance, a.spend_cap, c.campaign_id, c.name AS campaign, c.status, c.objective, c.daily_budget, s.adset_id, s.name AS adset, s.daily_budget AS adset_daily, s.bid_strategy, s.optimization_goal FROM \`fabcom-etl-core.meta_ads_settings.accounts\` a JOIN \`fabcom-etl-core.meta_ads_settings.campaigns\` c USING (account_id) JOIN \`fabcom-etl-core.meta_ads_settings.adsets\` s USING (campaign_id) WHERE a.account_id = '${account.id}' AND c.status = 'ACTIVE' ORDER BY c.daily_budget DESC;`; return (
e.stopPropagation()} style={{boxShadow:"var(--shadow-lg)"}}>
BigQuery snippet · {account.name}
{sql}
⌘C also copies
); } function App(){ // Read route from hash const readHash = () => { const h = (location.hash || "#overview").replace(/^#/,""); const [p, qs] = h.split("?"); const params = new URLSearchParams(qs || ""); return { page: p || "overview", params }; }; const [route, setRoute] = useStateA(readHash()); useEffectA(() => { const onHash = () => setRoute(readHash()); window.addEventListener("hashchange", onHash); return () => window.removeEventListener("hashchange", onHash); }, []); const go = (page, params = {}) => { const p = new URLSearchParams(params).toString(); location.hash = "#" + page + (p ? "?" + p : ""); }; const [filters, setFilters] = useStateA(() => ({ query: route.params.get("q") || "", business: route.params.get("biz") || "", currency: route.params.get("ccy") || "", status: route.params.get("s") || "", })); // keep URL in sync for filters on overview useEffectA(() => { if (route.page !== "overview" && route.page !== "accounts") return; const p = new URLSearchParams(); if (filters.query) p.set("q", filters.query); if (filters.business) p.set("biz", filters.business); if (filters.currency) p.set("ccy", filters.currency); if (filters.status) p.set("s", filters.status); if (route.params.get("acc")) p.set("acc", route.params.get("acc")); const nh = "#" + route.page + (p.toString() ? "?"+p.toString() : ""); if (location.hash !== nh) history.replaceState(null, "", nh); }, [filters, route.page]); // Drill stack in right pane: [{kind: 'account'|'campaign'|'adset', id}, ...] const [stack, setStack] = useStateA(() => { const acc = route.params.get("acc"); return acc ? [{kind:"account", id: acc}] : []; }); const current = stack[stack.length - 1]; const openAccount = (id) => setStack([{kind:"account", id}]); const openCampaign = (id) => setStack(s => [...s, {kind:"campaign", id}]); const openAdset = (id) => setStack(s => [...s, {kind:"adset", id}]); const popDetail = () => setStack([]); const back = (targetKind, id) => { if (!targetKind) { setStack(s => s.slice(0, -1)); return; } setStack(s => { const i = s.findIndex(x => x.kind === targetKind && x.id === id); return i >= 0 ? s.slice(0, i+1) : s; }); }; useEffectA(() => { // persist selected account in URL if ((route.page === "overview" || route.page === "accounts") && stack[0]?.kind === "account") { const p = new URLSearchParams(location.hash.split("?")[1] || ""); p.set("acc", stack[0].id); history.replaceState(null, "", "#" + route.page + "?" + p.toString()); } }, [stack, route.page]); // Context menu const [ctx, setCtx] = useStateA(null); // {x,y,account} const [toast, setToast] = useStateA(null); const [bqModal, setBqModal] = useStateA(null); const onContextMenu = (e, account) => { setCtx({x: e.clientX, y: e.clientY, account}); }; // Cmd-K to focus search (simplified: just shows a toast) useEffectA(() => { const onKey = (e) => { if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); const el = document.querySelector('input[placeholder="Search accounts…"]'); if (el) el.focus(); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); // Theme toggle (via Tweaks protocol) const toggleTheme = () => { const cur = document.documentElement.classList.contains("dark") ? "dark" : "light"; const nxt = cur === "dark" ? "light" : "dark"; document.documentElement.classList.remove("dark","light"); document.documentElement.classList.add(nxt); try { window.parent.postMessage({ type:"__edit_mode_set_keys", edits: { theme: nxt } }, "*"); } catch(e){} }; const theme = document.documentElement.classList.contains("dark") ? "dark" : "light"; // Main layout: sidebar | workarea const showRightPane = stack.length > 0 && (route.page === "overview" || route.page === "accounts"); return (
{ setStack([]); go(p); }}/>
{(route.page === "overview" || route.page === "accounts") && (
openAccount(al.account_id)} />
{showRightPane && (
{current.kind === "account" && ( )} {current.kind === "campaign" && ( )} {current.kind === "adset" && ( )}
)}
)} {route.page === "changes" && { go("overview"); openAccount(id); }}/>} {route.page === "alerts" && { go("overview"); openAccount(al.account_id); }}/>}
{ctx && ( setCtx(null)} items={[ { icon:"arrow-right", label:"Open account detail", onClick: () => openAccount(ctx.account.id), hint:"Enter" }, { sep:true }, { icon:"copy", label:"Copy BQ query for this account", onClick: () => setBqModal(ctx.account), hint:"⌘⇧C" }, { icon:"copy", label:"Copy account ID", onClick: () => { navigator.clipboard && navigator.clipboard.writeText(ctx.account.id); setToast("Copied " + ctx.account.id); } }, { icon:"link", label:"Copy shareable URL", onClick: () => { navigator.clipboard && navigator.clipboard.writeText(location.origin + location.pathname + "#overview?acc=" + ctx.account.id); setToast("URL copied"); } }, { sep:true }, { icon:"sheet", label:"Export this account to Sheets", onClick: () => setToast("Exporting " + ctx.account.name + "…") }, { icon:"external", label:"Open in Meta Ads Manager", onClick: () => setToast("Opening Ads Manager…") }, ]}/> )} {bqModal && setBqModal(null)}/>} {toast && setToast(null)}/>}
); } // Mount only after data is loaded (handled by data_loader.jsx) const __mount = () => ReactDOM.createRoot(document.getElementById("root")).render(); if (window.FABCOM_DATA) __mount(); else window.__fabcomMount = __mount;