/* Settings — per-account module customization ======================== Granular, grouped widget visibility (individual KPI tiles, charts, tables) + advertising-platform visibility, per account. Changes persist and reflect LIVE in the Overview + sidebar. RBAC: only super_admin + admin may edit (settings.manage). =================================================================== */ /* Grouped, granular widgets. Each leaf id maps to a piece of the Overview. */ const WIDGET_GROUPS = [ { id:'kpi', label:'KPI scoreboard', icon:'sparkles', tone:'#8351ff', desc:'Individual metric tiles at the top of the Overview.', items:[ { id:'kpi_spend', label:'Amount spent', desc:'Hero spend tile + trend', tone:'#8351ff' }, { id:'kpi_roas', label:'ROAS', desc:'Return on ad spend', tone:'#10b981' }, { id:'kpi_cpa', label:'Cost / result (CPA)', desc:'Average cost per result', tone:'#f59e0b' }, { id:'kpi_cpc', label:'Cost / click (CPC)', desc:'Average cost per click', tone:'#06b6d4' }, { id:'kpi_ctr', label:'Click-through rate', desc:'CTR across campaigns', tone:'#ec4899' }, ]}, { id:'charts', label:'Charts & insights', icon:'zap', tone:'#2f7bff', desc:'Trend, contribution and real-time signals.', items:[ { id:'chart', label:'Spend × ROAS chart', desc:'Daily spend vs return trend', tone:'#2f7bff' }, { id:'platform_mix', label:'Platform mix', desc:'Spend-share donut by platform', tone:'#8351ff' }, { id:'anomalies', label:'Anomalies feed', desc:'Real-time alert cards (24h)', tone:'#f59e0b' }, ]}, { id:'tables', label:'Tables & creative', icon:'columns', tone:'#10b981', desc:'Detailed campaign grid and creative gallery.', items:[ { id:'campaign_table', label:'Campaign table', desc:'Blended campaign grid', tone:'#10b981' }, { id:'creative', label:'Creative preview', desc:'Top creative thumbnails', tone:'#ec4899' }, ]}, ]; /* flattened list (for defaults, counts, lookups) */ const WIDGET_DEFS = WIDGET_GROUPS.flatMap(g=>g.items); const WIDGET_BY_ID = Object.fromEntries(WIDGET_DEFS.map(w=>[w.id,w])); const KPI_WIDGET_IDS = WIDGET_GROUPS.find(g=>g.id==='kpi').items.map(i=>i.id); const TOGGLEABLE_PLATFORMS = ['meta','facebook','instagram','google','tiktok','snapchat','x','linkedin']; function defaultModuleSettings(){ const widgets = {}; WIDGET_DEFS.forEach(w=>widgets[w.id]=true); return { widgets, platforms: TOGGLEABLE_PLATFORMS.slice() }; } const MODULE_LS = 'relay_module_settings_v1'; /* Platforms added after the first release. Per-account settings saved before a platform existed won't list it, so its sidebar tab stays hidden even once its feed goes live. This one-time migration enables newly-added platforms by default on existing saved accounts (users can still toggle them off in Settings). Bump the flag key when introducing another platform. */ const NEW_PLATFORMS = ['x','linkedin']; const MODULE_MIGRATE_LS = 'relay_module_settings_migrated_platforms_v2'; function loadModuleSettings(){ const all = loadLS(MODULE_LS, {}); try { if (!loadLS(MODULE_MIGRATE_LS, false)){ let changed = false; Object.keys(all).forEach(accId=>{ const s = all[accId]; if (s && Array.isArray(s.platforms)){ NEW_PLATFORMS.forEach(pid=>{ if (TOGGLEABLE_PLATFORMS.includes(pid) && !s.platforms.includes(pid)){ s.platforms.push(pid); changed = true; } }); } }); if (changed) saveLS(MODULE_LS, all); saveLS(MODULE_MIGRATE_LS, true); } } catch(e){} return all; } /* Back-compat: older saved settings used a single `kpi` flag → expand it. */ function normalizeWidgets(w){ const out = { ...w }; if (out.kpi !== undefined){ KPI_WIDGET_IDS.forEach(id=>{ if(out[id]===undefined) out[id]=out.kpi; }); delete out.kpi; } return out; } function moduleSettingsFor(all, accountId){ const base = all[accountId] || defaultModuleSettings(); return { ...base, widgets: normalizeWidgets(base.widgets||{}) }; } function SettingsView({ accountId, settings, onChange, canManage, currentUser }){ const acc = ACCOUNTS.find(a=>a.id===accountId); const s = settings || defaultModuleSettings(); const isOn = (id)=> s.widgets[id] !== false; const setWidget = (id,v)=> onChange({ ...s, widgets:{ ...s.widgets, [id]:v } }); const setGroup = (group, v)=>{ const next = { ...s.widgets }; group.items.forEach(it=> next[it.id]=v); onChange({ ...s, widgets: next }); }; const setPlatform = (id)=>{ const on = s.platforms.includes(id); onChange({ ...s, platforms: on ? s.platforms.filter(p=>p!==id) : [...s.platforms, id] }); }; const reset = ()=> onChange(defaultModuleSettings()); const enabledCount = WIDGET_DEFS.filter(w=>isOn(w.id)).length; const lock = canManage ? '' : 'pointer-events-none opacity-50'; return (

Settings

Module visibility for {acc.name} · saved per account
{canManage && Reset to defaults}
{!canManage && (
View only — only an Admin or Super Admin can change module visibility.
)}
Dashboard widgets
{WIDGET_GROUPS.map(group=>{ const onCount = group.items.filter(it=>isOn(it.id)).length; const allOn = onCount===group.items.length; const noneOn = onCount===0; return (
{group.label} {onCount}/{group.items.length}
{group.desc}
{group.items.map(it=>(
{it.label}
{it.desc}
setWidget(it.id,v)} tone={it.tone}/>
))}
); })}
Advertising platforms
Disabled platforms are hidden from the sidebar & tabs for this account.
{TOGGLEABLE_PLATFORMS.map(pid=>{ const p = PLATFORMS.find(x=>x.id===pid); const on = s.platforms.includes(pid); return ( ); })}
Saved per account · {enabledCount}/{WIDGET_DEFS.length} widgets · {s.platforms.length}/{TOGGLEABLE_PLATFORMS.length} platforms enabled
); } Object.assign(window, { SettingsView, WIDGET_GROUPS, WIDGET_DEFS, WIDGET_BY_ID, KPI_WIDGET_IDS, TOGGLEABLE_PLATFORMS, defaultModuleSettings, loadModuleSettings, moduleSettingsFor, normalizeWidgets, MODULE_LS });