// ================= Account detail, Campaign detail ================= const AccountDetailScreen = ({ brandCode, accountId, setView, tweaks, openContext }) => { const brand = window.BRANDS.find(b => b.code === brandCode); const account = brand?.accounts.find(a => a.id === accountId); const [tab, setTab] = useStateS("campaigns"); const [attribution, setAttribution] = useStateS(7); const [expanded, setExpanded] = useStateS({}); if (!account) return null; const daily = Array.from({ length: 30 }).map((_, i) => account.campaigns.reduce((s, c) => s + (c.daily_series[i] || 0), 0)); return (

{account.name}

{account.id} · {brand.name}
{tab === "campaigns" && (
{account.campaigns.map(c => ( setView({ name: "campaign", brand: brandCode, account: accountId, campaign: c.id })} onContextMenu={(e) => { e.preventDefault(); openContext(e, c, "campaign"); }}> {expanded[c.id] && Array.from({ length: 3 }).map((_, i) => ( ))} ))}
Campaign / Ad set / Ad Status Daily MTD spend Impr. CTR CPC Conv Trend
{ e.stopPropagation(); setExpanded(x => ({ ...x, [c.id]: !x[c.id] })); }}> {c.name} {window.fmt.hkd(c.daily_budget)} {window.fmt.hkdK(c.mtd_spend)} {window.fmt.int(c.impressions)} {window.fmt.pct(c.ctr, 2)} {window.fmt.money2(c.cpc)} {window.fmt.int(c.conv)}
├ {c.name.split("_")[1] || "SET"}_{String(i+1).padStart(2,"0")} {window.fmt.hkd(c.daily_budget / 3)} {window.fmt.hkdK(c.mtd_spend / 3)} {window.fmt.int(c.impressions / 3)} {window.fmt.pct(c.ctr * (0.8 + i*0.1), 2)} {window.fmt.money2(c.cpc * (0.9 + i*0.05))} {window.fmt.int(c.conv / 3)}
)} {tab === "performance" && (

Daily spend · 30d

markers show budget / bid changes
v * 0.02 * 50) }, ]} />
)} {tab === "conversions" && (
Attribution:
{[7, 14, 28].map(d => )}
{r.event} }, { key: "count", header: "Count", right: true, num: true, render: r => window.fmt.int(r.count) }, { key: "cpa", header: "CPA", right: true, num: true, render: r => window.fmt.money2(r.cpa) }, { key: "roas", header: "ROAS", right: true, num: true, render: r => `${r.roas.toFixed(2)}x` }, { key: "rate", header: "Rate", right: true, num: true, render: r => window.fmt.pct(r.rate, 2) }, ]} rows={[ { id: 1, event: "purchase", count: Math.round(account.conv * 0.35 * (attribution/7)), cpa: account.cpa * 1.2, roas: account.roas * 1.1, rate: 0.021 * (attribution/7) }, { id: 2, event: "add_to_cart", count: Math.round(account.conv * 1.6 * (attribution/7)), cpa: account.cpa * 0.3, roas: 0, rate: 0.085 * (attribution/7) }, { id: 3, event: "lead", count: Math.round(account.conv * 0.8 * (attribution/7)), cpa: account.cpa * 0.6, roas: 0, rate: 0.055 * (attribution/7) }, { id: 4, event: "view_content", count: Math.round(account.conv * 4.2 * (attribution/7)), cpa: account.cpa * 0.1, roas: 0, rate: 0.210 * (attribution/7) }, { id: 5, event: "sign_up", count: Math.round(account.conv * 0.25 * (attribution/7)), cpa: account.cpa * 1.8, roas: 0, rate: 0.012 * (attribution/7) }, ]} striped={tweaks.tableStyle === "striped"} lined={tweaks.tableStyle === "lined"} /> )} {tab === "creatives" && (
{Array.from({ length: 12 }).map((_, i) => (
{brand.code}_AD_{String(i+1).padStart(3, "0")}
Spend{window.fmt.hkdK(2500 + i*1800)}
CTR{(0.8 + i*0.15).toFixed(2)}%
))}
)} {tab === "changes" && (
{window.CHANGES.filter(c => c.brand_code === brandCode).slice(0, 12).map(c => (
{c.actor.slice(0, 2).toUpperCase()}
{c.actor} {c.action}
{c.prev}{c.next}
{window.fmt.timeAgo(c.hours_ago)}
))}
)} {tab === "targeting" && (

Audiences

{["Lookalike 1%", "Website visitors 30d", "Purchasers 180d", "Interest: Parents", "Interest: Pet owners"].map((a, i) => (
{a} {(120000 + i*45000).toLocaleString()} reach
))}

Geo · demo

Top regions
{[["HK", 62], ["MO", 14], ["SG", 12], ["TW", 8], ["MY", 4]].map(([g, pct]) => (
{g}
{pct}%
))}
Gender · Age
F 55% M 42%O 3%
)}
); }; // ---------- Campaign drill ---------- const CampaignDetailScreen = ({ brandCode, accountId, campaignId, setView, tweaks }) => { const brand = window.BRANDS.find(b => b.code === brandCode); const account = brand?.accounts.find(a => a.id === accountId) || brand?.accounts.find(a => a.campaigns.some(c => c.id === campaignId)); const c = account?.campaigns.find(x => x.id === campaignId); if (!c) return
Campaign not found
; const adsetLabel = c.platform === "google" ? "Ad group" : c.platform === "dv360" || c.platform === "ttd" ? "Insertion order" : "Ad set"; const pace = c.spend / Math.max(1, c.plan_budget * (c.days_elapsed / c.flight_days)); const paceTone = pace > 1.2 ? "danger" : pace < 0.7 ? "warn" : "success"; return (

{c.name}

Bid {c.bid_strategy} Budget {c.budget_type} {window.fmt.hkd(c.daily_budget)}/d Flight {c.start_date} → {c.end_date} Objective {c.objective}

Performance · 30d

spend + impressions + clicks + conversions
v * 0.02 * 80) }, { label: "Conv", color: "#f59e0b", data: c.daily_series.map(v => v * 0.001 * 200) }, ]} />

Pacing

{(pace * 100).toFixed(0)}%
{window.fmt.hkdK(c.spend)} of {window.fmt.hkdK(c.plan_budget)}
Days remaining
{c.days_remaining} / {c.flight_days}d
EOM forecast
{window.fmt.hkdK(c.spend / Math.max(1, c.days_elapsed) * c.flight_days)}

{adsetLabel}s breakdown

{r.name} }, { key: "status", header: "Status", width: 90, render: r => }, { key: "spend", header: "MTD spend", right: true, num: true, width: 110, render: r => window.fmt.hkdK(r.spend) }, { key: "impr", header: "Impr.", right: true, num: true, width: 100, render: r => window.fmt.int(r.impr) }, { key: "ctr", header: "CTR", right: true, num: true, width: 80, render: r => window.fmt.pct(r.ctr, 2) }, { key: "cpa", header: "CPA", right: true, num: true, width: 80, render: r => window.fmt.money2(r.cpa) }, { key: "spark", header: "Trend", width: 90, render: r => v * r.mul)}/> }, ]} rows={Array.from({ length: 4 }).map((_, i) => ({ id: i, name: `${c.name.split("_")[1] || "SET"}_${String(i+1).padStart(2, "0")}`, status: i === 3 ? { label: "Paused", tone: "info" } : c.status, spend: c.mtd_spend * [0.38, 0.30, 0.22, 0.10][i], impr: Math.round(c.impressions * [0.38, 0.30, 0.22, 0.10][i]), ctr: c.ctr * [1.1, 0.95, 1.2, 0.7][i], cpa: c.cpa * [0.9, 1.1, 0.8, 1.6][i], mul: [1.1, 0.95, 1.2, 0.6][i], }))} striped={tweaks.tableStyle === "striped"} lined={tweaks.tableStyle === "lined"} /> ); }; window.AccountDetailScreen = AccountDetailScreen; window.CampaignDetailScreen = CampaignDetailScreen;