// Account Detail — master-detail right pane function AccountHeader({ acc, onClose, onOpen }) { return (
{acc.id} · {acc.business}

{acc.name}

3 ? "red" : acc.ratio > 2 ? "amber" : "default"}/>
); } function MetaStat({ label, value, sub, tone }) { const toneColor = tone === "red" ? "var(--red-400)" : tone === "amber" ? "var(--amber-400)" : "var(--text)"; return (
{label}
{value}
{sub &&
{sub}
}
); } // Tab: Campaigns (tree) function CampaignsTab({ acc, onOpenCampaign, onOpenAdset }) { const camps = window.FABCOM_DATA.campaigns.filter(c => c.account_id === acc.id); const [open, setOpen] = useState(() => new Set(camps.slice(0,2).map(c => c.id))); const toggle = (id) => setOpen(s => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; }); return (
Campaign · Adset · Ad Status Budget Bid
{camps.map(c => { const isOpen = open.has(c.id); const adsets = window.FABCOM_DATA.adsets.filter(a => a.campaign_id === c.id); return (
toggle(c.id)}>
{c.name}
{c.id} · {c.objective}
{window.fmtCCY(c.daily_budget, acc.ccy)}/d
{(c.bid_strategy||"—").replace("LOWEST_COST_","LC_")}
{isOpen && adsets.map(as => { const adsCount = as.ad_ids.length; return (
onOpenAdset(as.id)}>
{as.name}
{as.id} · {adsCount} ads · {(as.audience||"—").split("—")[0].trim()}
{as.daily_budget ? <>{window.fmtCCY(as.daily_budget, acc.ccy)}/d : LT {window.fmtCCY(as.lifetime_budget, acc.ccy)}}
{(as.optimization_goal||"—").slice(0,10)}
); })}
); })}
); } // Tab: Budget Health function BudgetHealthTab({ acc }) { const camps = window.FABCOM_DATA.campaigns.filter(c => c.account_id === acc.id); const rows = camps.map(c => { // fake 30d avg per campaign (proportional to acc's series) const share = c.daily_budget / (acc.daily_budget || 1); const avg30 = Math.max(1, Math.round(acc.avg_30d_spend * share * (0.7 + (c.id.length % 5) * 0.1))); const ratio = c.daily_budget / avg30; const sev = ratio > 3 ? "red" : ratio > 2 ? "amber" : ratio > 0.2 ? "green" : "amber"; const series = acc.series.map((v,i) => Math.round(v * share * (0.8 + ((i+c.id.length)%7)/10))); return { c, avg30, ratio, sev, series }; }).sort((a,b) => b.ratio - a.ratio); return ( {rows.map(({c, avg30, ratio, sev, series}) => ( ))}
Campaign Daily budget 30d avg spend Ratio Trend (30d) Status
{c.name}
{c.id}
{window.fmtCCY(c.daily_budget, acc.ccy)} {window.fmtCCY(avg30, acc.ccy)}
); } // Tab: Recent Changes (7d) function RecentChangesTab({ acc }) { const history = window.FABCOM_DATA.history.filter(h => h.account_id === acc.id && (Date.now() - h.ts) < 7*86400e3 ); if (!history.length) return
No changes in the last 7 days.
; return ( {history.map(h => ( ))}
When Actor Entity Field Before → After
{window.timeAgo(h.ts)}
{window.formatFullTs(h.ts)}
{h.actor.startsWith("Auto") ? "⚙" : h.actor.split(" ").map(x=>x[0]).slice(0,2).join("")}
{h.actor}
{h.entity_kind} {h.entity_name}
{h.field}
); } // Tab: Creatives function CreativesTab({ acc }) { const ads = window.FABCOM_DATA.ads.filter(a => a.account_id === acc.id && a.status === "ACTIVE").slice(0, 18); return (
{ads.map(ad => (
{ad.headline}
{ad.description}
{ad.cta.replace("_"," ")} {ad.id}
))}
); } function AccountDetail({ accountId, onClose, onOpenCampaign, onOpenAdset }) { const acc = window.FABCOM_DATA.accounts.find(a => a.id === accountId); const [tab, setTab] = useState("campaigns"); if (!acc) return null; const camps = window.FABCOM_DATA.campaigns.filter(c => c.account_id === acc.id); const changes7d = window.FABCOM_DATA.history.filter(h => h.account_id === acc.id && (Date.now()-h.ts) < 7*86400e3); const ads = window.FABCOM_DATA.ads.filter(a => a.account_id === acc.id && a.status === "ACTIVE"); return (
{}}/>
{tab === "campaigns" && } {tab === "health" && } {tab === "changes" && } {tab === "creatives" && }
); } Object.assign(window, { AccountDetail });