// ================= Screens: Overview, Brands list ================= const OverviewScreen = ({ setView, tweaks, openContext }) => { const [filter, setFilter] = useStateS({ platform: null, tl: null, paceStatus: null, q: "" }); const brands = window.BRANDS.filter(b => { if (filter.platform && !b.platforms.includes(filter.platform)) return false; if (filter.tl && b.tl !== filter.tl) return false; if (filter.paceStatus && b.pace_status !== filter.paceStatus) return false; if (filter.q && !(`${b.code} ${b.name}`.toLowerCase().includes(filter.q.toLowerCase()))) return false; return true; }); const kpis = useMemoS(() => { const totalDaily = brands.reduce((s, b) => s + b.daily_budget_hkd, 0); const mtd = brands.reduce((s, b) => s + b.mtd_spend, 0); const plan = brands.reduce((s, b) => s + b.plan_mtd, 0); const today = brands.reduce((s, b) => s + b.accounts.reduce((s2, a) => s2 + a.today_spend, 0), 0); const gp = brands.reduce((s, b) => s + b.gp_mtd, 0); const sparkDaily = Array.from({ length: 30 }).map((_, i) => brands.reduce((s, b) => s + (b.daily_series[i] || 0), 0)); return { myBrands: brands.length, totalDaily, today, mtd, plan, pace: mtd / Math.max(1, plan), gp, open: window.ALERTS.length, sparkDaily, }; }, [brands.length]); const paceTone = (p) => p > 1.2 ? "danger" : p < 0.7 ? "warn" : "success"; const paceLabel = (p) => p > 1.2 ? "Over" : p < 0.7 ? "Under" : "On track"; const columns = [ { key: "brand", header: "Brand", minWidth: 220, sort: (a, b) => a.name.localeCompare(b.name), render: (r) => (
{r.code} {r.name}
), }, { key: "platforms", header: "Platforms", width: 120, render: (r) => (
{r.platforms.slice(0, 5).map(p => )}
), }, { key: "daily_budget_hkd", header: "Daily budget", right: true, width: 120, num: true, sort: (a, b) => a.daily_budget_hkd - b.daily_budget_hkd, render: (r) => window.fmt.hkd(r.daily_budget_hkd), }, { key: "mtd_spend", header: "MTD spend", right: true, width: 130, num: true, sort: (a, b) => a.mtd_spend - b.mtd_spend, render: (r) => (
{window.fmt.hkd(r.mtd_spend)}
of {window.fmt.hkdK(r.plan_mtd)}
), }, { key: "pace", header: "Pace", width: 150, sort: (a, b) => a.pace - b.pace, render: (r) => (
{(r.pace * 100).toFixed(0)}%
), }, { key: "spend_7d", header: "7d", right: true, width: 120, num: true, sort: (a, b) => a.spend_7d - b.spend_7d, render: (r) => (
{window.fmt.hkdK(r.spend_7d)}
), }, { key: "spend_30d", header: "30d", right: true, width: 80, num: true, sort: (a, b) => a.spend_30d - b.spend_30d, render: (r) => window.fmt.hkdK(r.spend_30d), }, { key: "non_media_mtd", header: "Non-media MTD", right: true, width: 130, num: true, sort: (a, b) => a.non_media_mtd - b.non_media_mtd, render: (r) => window.fmt.hkdK(r.non_media_mtd), }, { key: "gp_mtd", header: "GP MTD", right: true, width: 110, num: true, sort: (a, b) => a.gp_mtd - b.gp_mtd, render: (r) => ( {window.fmt.hkdK(r.gp_mtd)} ({((r.gp_mtd / Math.max(1, r.mtd_spend)) * 100).toFixed(0)}%) ), }, { key: "last_change", header: "Last change", right: true, width: 100, num: true, sort: (a, b) => a.last_change - b.last_change, render: (r) => {window.fmt.timeAgo(r.last_change)}, }, { key: "alerts", header: "Alerts", width: 72, right: true, num: true, sort: (a, b) => a.alerts - b.alerts, render: (r) => r.alerts > 0 ? {r.alerts} : , }, ]; return (

Hi Vincent — here's your fleet

Wed, Apr 22, 2026 · data refreshed 18m ago via Fivetran
{/* KPIs */}
b.platforms)).size} platforms`}/> a.severity === "danger").length} critical`}/>
{/* Filter bar */}
setFilter(f => ({ ...f, q: e.target.value }))} style={{ width: 200 }}/>
Platform {window.PLATFORMS_LIST.map(p => (
setFilter(f => ({ ...f, platform: f.platform === p.id ? null : p.id }))}> {p.name}
))}
Pace {[["success", "On track"], ["warn", "Under"], ["danger", "Over"]].map(([id, lbl]) => (
setFilter(f => ({ ...f, paceStatus: f.paceStatus === id ? null : id }))}> {lbl}
))}
Team lead {window.PEOPLE.TLS.map(tl => (
setFilter(f => ({ ...f, tl: f.tl === tl ? null : tl }))}> {tl}
))}
{brands.length} of {window.BRANDS.length} brands
r.code} onRowClick={r => setView({ name: "brand", brand: r.code })} onRowContext={(r, e) => openContext(e, r, "brand")} striped={tweaks.tableStyle === "striped"} lined={tweaks.tableStyle === "lined"} /> ); }; window.OverviewScreen = OverviewScreen;