/* AAU CRM — Marketing Hub part 1: Overview (Command Center) + Data Sources */ function MarketingOverview() { const t = useTimeRange('30d'); const [dim, setDim] = useState('course'); const [metric, setMetric] = useState('spend'); const { total } = AAU.mktSummary('channel'); const b = AAU.mktBudget; const revenue = total.won * 14900000; const roas = revenue / total.spend; const pacePlan = Math.round(b.pacingDay / b.daysInMonth * 100); const paceReal = Math.round(b.spentMTD / b.monthlyBudget * 100); const kpis = [ { l: 'Tổng chi (Spend)', v: AAU.fmtVNDm(total.spend), d: 12, dd: 'up' }, { l: 'Σ Lead (org + ads)', v: total.leads, d: 18, dd: 'up', sub: total.leadsOrg + ' organic · ' + total.leadsAds + ' ads' }, { l: 'CPL trung bình', v: AAU.fmtVND(total.cplBlended), d: 9, dd: 'down', crm: true }, { l: 'Cost / Message', v: AAU.fmtVND(total.costMsg), d: 5, dd: 'down' }, { l: 'Doanh thu attributed', v: AAU.fmtVNDm(revenue), d: 15, dd: 'up', crm: true }, { l: 'ROAS', v: roas.toFixed(1) + '×', d: 7, dd: 'up', crm: true }, ]; const mt = AAU.mktTrend; const metricCfg = { spend: { data: mt.spend, color: '#0084ff', fmt: v => v.toFixed(1) + 'tr', label: 'Chi tiêu (triệu ₫)' }, leads: { data: mt.leads, color: '#7c3aed', fmt: v => Math.round(v), label: 'Lead' }, cpl: { data: mt.cpl, color: '#0a9e6e', fmt: v => v.toFixed(2) + 'tr', label: 'CPL (triệu ₫)' }, msgs: { data: mt.msgs, color: '#d97706', fmt: v => Math.round(v), label: 'Messages' }, }[metric]; const split = AAU.channelSplit; const donut = split.map(s => ({ l: AAU.mktChannelById(s.channel).label, v: s.leads, color: AAU.mktChannelById(s.channel).color })); return (
} />
{kpis.map((k, i) => (
{k.crm && }{k.l}
{k.v}
{t.compare &&
{k.d}%
} {k.sub &&
{k.sub}
}
))}
}>
{AAU.fmtVNDm(b.spentMTD)}
/ {AAU.fmtVNDm(b.monthlyBudget)} mục tiêu
pacePlan + 5 ? 'critical' : 'success'}>{paceReal > pacePlan ? 'Vượt pace' : 'Đúng pace'}
pacePlan + 5 ? '#c4320a' : '#0084ff'} height={10} />
Đã tiêu {paceReal}%Kế hoạch theo ngày {pacePlan}%
Mục tiêu CPL{AAU.fmtVND(b.cplTarget)}
Ngưỡng cảnh báo CPL{AAU.fmtVND(b.cplAlert)}
Mục tiêu ROAS{b.roasTarget.toFixed(1)}×
navigate('/marketing/sources')}>Source Performance}>
({ l: d.l, color: d.color }))} />
}>
CPL = Spend ÷ lead-ads · Cost/Msg = Spend ÷ messages · CAC = Spend ÷ Won (số Won lấy từ pipeline CRM). Lead organic không tính vào CPL.
); } function DataSources() { const t = useTimeRange('30d'); const cons = AAU.connections; const connected = cons.filter(c => c.status === 'connected').length; const totLeads = cons.reduce((s, c) => s + c.leads, 0); const totPosts = cons.reduce((s, c) => s + c.posts, 0); const totSpend = cons.reduce((s, c) => s + c.spend, 0); const statusMap = { connected: { tone: 'success', label: 'Đã kết nối' }, warning: { tone: 'warning', label: 'Cần chú ý' }, not_connected: { tone: 'neutral', label: 'Chưa kết nối' } }; return (
} />
{cons.map(c => { const sm = statusMap[c.status]; return ( ); })}
NguồnTài khoảnTrạng tháiLoại dữ liệuSync gần nhất BàiLeadChi 30N
{c.account}{c.note &&
{c.note}
}
{sm.label}
{c.kinds.map(k => {k})}
{c.lastSync} {c.posts || '—'} {c.leads || '—'} {c.spend ? AAU.fmtVNDm(c.spend) : '—'} {c.status === 'not_connected' ? : }
{AAU.integrations.map((it, i) => (
{it.status === 'connected' ? 'Đã nối' : 'Chưa nối'}
{it.name}
{it.desc}
{it.status !== 'connected' && }
))}
); } Object.assign(window, { MarketingOverview, DataSources });