/* Bento KPI tiles =================================================== */ function BentoKPI({ def, value, delta, sparkData, tone, format, live, variant='standard' }){ const [hover, setHover] = useState(false); const positive = delta > 0; const goodIsUp = def.good === 'up'; const deltaGood = goodIsUp ? positive : !positive; const deltaColor = deltaGood ? 'text-emerald-600 dark:text-emerald-400' : 'text-rose-600 dark:text-rose-400'; const deltaBg = deltaGood ? 'bg-emerald-50 dark:bg-emerald-500/10' : 'bg-rose-50 dark:bg-rose-500/10'; /* ---------- HERO variant: large card with prominent value + chart */ if (variant === 'hero') { return (
setHover(true)} onMouseLeave={()=>setHover(false)} className="relative h-full rounded-2xl border border-ink-200/80 dark:border-white/10 bg-white dark:bg-ink-900/60 shadow-card p-5 overflow-hidden flex flex-col group transition hover:shadow-float">
{def.label} {live && }
{Math.abs(delta).toFixed(1)}%
vs {fmt(value/(1+(delta||0)/100), 'currency')} prior period
−24hNOW
); } /* ---------- COMPACT variant: short tile, inline sparkline ----- */ if (variant === 'compact'){ return (
setHover(true)} onMouseLeave={()=>setHover(false)} className="relative h-full rounded-2xl border border-ink-200/80 dark:border-white/10 bg-white dark:bg-ink-900/60 shadow-card p-3.5 overflow-hidden flex flex-col">
{def.label}{live && }
{Math.abs(delta).toFixed(1)}%
); } /* ---------- STANDARD variant ----------------------------------- */ return (
setHover(true)} onMouseLeave={()=>setHover(false)} className="relative h-full rounded-2xl border border-ink-200/80 dark:border-white/10 bg-white dark:bg-ink-900/60 shadow-card p-4 overflow-hidden flex flex-col group transition hover:shadow-float">
{def.label}
{live && }
{Math.abs(delta).toFixed(1)}%
); } /* ---------- Bento KPI grid (asymmetric, visibility-aware) --------- */ function BentoKPIGrid({ accountId, platformId, onAddCustomCol, widgets }){ const snap = kpiSnapshot(accountId, platformId); const values = { spend:snap.spend, cpa:snap.cpa, roas:snap.roas, cpc:snap.cpc, ctr:snap.ctr }; const tones = { spend:'#8351ff', roas:'#10b981', cpa:'#f59e0b', cpc:'#06b6d4', ctr:'#ec4899' }; const formats = { spend:'currency', roas:'x', cpa:'currency2', cpc:'currency2', ctr:'pct' }; const defs = Object.fromEntries(KPI_DEFS.map(d => [d.id, d])); /* which tiles are enabled (default on); widgets uses kpi_ keys; per-account blocklist (e.g. Al Munawarah: no ROAS) applies last */ const isOn = (id)=> (!widgets || widgets['kpi_'+id] !== false) && !metricHiddenFor(accountId, id); const order = ['spend','roas','cpa','cpc','ctr']; const enabled = order.filter(isOn); if (enabled.length === 0) return null; const tile = (id, variant)=>( ); const heroSpend = isOn('spend'); const rest = enabled.filter(id=>id!=='spend'); /* No hero → uniform responsive grid */ if (!heroSpend){ return (
{enabled.map(id=>
{tile(id,'standard')}
)}
); } /* Hero spend + the rest flowing in a side grid */ return (
2?'row-span-2':'row-span-2 md:row-span-2'}`}> {tile('spend','hero')}
{rest.map((id,i)=>(
{tile(id, i%2===1 ? 'compact' : 'standard')}
))}
); } Object.assign(window, { BentoKPI, BentoKPIGrid });