/* global React, Icon, Spark */ window.Dashboard = function Dashboard({ setPage }) { const m = window.MOCK || {}; const [range, setRange] = React.useState('30D'); function setDashboardPeriod(period) { setRange(period); } window.setDashboardPeriod = setDashboardPeriod; window.renderDashboard = () => setRange(current => current); const tickets = m.tickets || []; const conversations = m.conversations || []; const agents = m.agents || []; const instances = m.instances || []; const runs = m.runs || []; const draftAgents = agents.filter(agent => !agent.isPublished); const openTickets = tickets.filter(t => ['Aberto', 'Em andamento'].includes(t.status)).length; const paused = conversations.filter(c => c.paused).length; const onlineInstances = instances.filter(i => i.status === 'online').length; const attentionInstances = instances.filter(i => i.status !== 'online').length; const activeAgents = agents.filter(a => a.status === 'online').length; const msgCount = conversations.reduce((acc, c) => acc + Number(c.raw?.messages || 0), 0); const latencyValues = runs.map(r => Number(r.latency_ms || 0)).filter(Boolean); const avgLatency = latencyValues.length ? Math.round(latencyValues.reduce((a, b) => a + b, 0) / latencyValues.length) : 0; const agentById = Object.fromEntries(agents.map(a => [a.id, a.name])); const ticketStatuses = ['Aberto', 'Em andamento', 'Resolvido', 'Fechado']; const telemetryRows = runs.slice(0, 7).map(r => { const status = r.error ? 'bad' : Number(r.latency_ms || 0) > 1500 ? 'warn' : 'ok'; return { time: window.fmtTime ? window.fmtTime(r.created_at) : '--', agent: agentById[r.agent_id] || `#${r.agent_id || '-'}`, conversation: r.conversation_id || '--', decision: r.output?.decision || r.output?.action || r.status || 'executar', tool: r.output?.tool || r.input?.tool || '--', latency: r.latency_ms ? `${r.latency_ms} ms` : '--', status, label: r.error || r.status || 'OK', }; }); const kpis = [ { label: 'Tickets abertos', value: String(openTickets), unit: 'na fila', delta: `${tickets.length} total`, dn: openTickets > 0, spark: sparkFrom(openTickets, 2) }, { label: 'Conversas pausadas', value: String(paused), unit: 'humano', delta: `${conversations.length} conversas`, dn: paused > 0, spark: sparkFrom(paused, 4) }, { label: 'SLA 1a resposta', value: tickets.length ? 'ativo' : '--', unit: '', delta: 'CRM online', up: true, spark: [4.1, 3.6, 3.2, 2.9, 2.8, 2.6, 2.5, 2.4] }, { label: 'Mensagens', value: msgCount.toLocaleString('pt-BR'), unit: 'hist.', delta: `${runs.length} runs`, up: true, spark: sparkFrom(msgCount, 6) }, { label: 'Agentes ativos', value: String(activeAgents), unit: `/ ${agents.length}`, delta: `${agents.length} cadastrados`, up: activeAgents > 0, spark: sparkFrom(activeAgents, 8) }, { label: 'Latência IA', value: avgLatency ? String(avgLatency) : '--', unit: avgLatency ? 'ms' : '', delta: latencyValues.length ? `${latencyValues.length} amostras` : 'sem runs', up: true, spark: latencyValues.slice(0, 8).length ? latencyValues.slice(0, 8) : [1, 1, 1, 1, 1, 1, 1, 1] }, ]; return ( <>
Operação - {new Date().toLocaleDateString('pt-BR')} - ao vivo

Dashboard

Visão consolidada da malha de agentes, conexões WhatsApp, fila humana e saúde do orquestrador.

{onlineInstances} conexões ativas
{attentionInstances} requerem ação
{paused} conversas pausadas
{draftAgents.length > 0 && (
{draftAgents.length} bot(s) em rascunho
Complete o setup antes de liberar respostas em mensagens reais.
)}
{kpis.map((k, i) => (
{k.label}
{k.value} {k.unit}
{k.delta} backend
))}
Fila de handoff humano - ao vivo
Conversas pausadas
{(m.handoffQueue || []).length} em espera
{(m.handoffQueue || []).map(h => (
{h.name}
{h.reason} - agente: {h.agent}
{h.wait}
))} {!(m.handoffQueue || []).length &&
Sem handoff pendente.
}
Tickets
Por status
{ticketStatuses.map(status => { const bucket = tickets.filter(t => t.status === status); return ( {bucket.slice(0, 3).map(t => )} {!bucket.length &&
vazio
}
); })}
Atividade do orquestrador
Mensagens & latencia
{['Hoje', '30D', '6M'].map(r => ( ))}
mensagens latencia (ms) {msgCount} mensagens - media {avgLatency || '--'}ms
Conexões WhatsApp
Saude dos canais
{instances.map(i => )} {!instances.length &&
Sem instância registrada.
}
Densidade operacional
Mensagens por hora & dia
baixo alto
Telemetria
Execuções recentes da IA
{telemetryRows.map((r, i) => ( ))} {!telemetryRows.length && }
TimestampAgenteConversaDecisãoFerramentaLatênciaStatus
{r.time} {r.agent} {r.conversation} {r.decision} {r.tool} {r.latency} {r.label}
Sem execucoes recentes.
); }; function sparkFrom(value, seed) { const n = Math.max(1, Number(value || 0)); return Array.from({ length: 8 }, (_, i) => Math.max(1, Math.round(n * (0.6 + ((i + seed) % 5) * 0.12)))); } function KCol({ title, n, children }) { return (
{title}{n}
{children}
); } function KCard({ who, sub, priority }) { return (
{who}{priority ? 'P1' : ''}
{sub}
); } function InstanceRow({ i }) { const m = { tag: i.statusTag || statusTag(i.status), label: i.statusLabel || friendlyWhatsAppStatus(i.status) }; return (
w
{i.name}
{m.label} - {i.queue} mensagens
{m.label}
); } function Barchart({ range, seed = 1 }) { const N = range === 'Hoje' ? 24 : range === '30D' ? 30 : 26; function rand(i) { return Math.abs(Math.sin((i + seed) * 12.9898) * 43758.5453 % 1); } const data = Array.from({ length:N }, (_, i) => Math.max(0.1, 0.45 + Math.sin((i / N) * Math.PI) * 0.4 + (rand(i) - 0.5) * 0.3)); const lat = Array.from({ length:N }, (_, i) => 0.5 + Math.cos((i / N) * Math.PI * 1.6) * 0.18 + (rand(i + 99) - 0.5) * 0.08); const W = 800, H = 220, pad = { l: 36, r: 36, t: 10, b: 26 }; const cw = W - pad.l - pad.r; const ch = H - pad.t - pad.b; const bw = cw / N - 3; const linePts = lat.map((v, i) => `${pad.l + (i + 0.5) * (cw / N)},${pad.t + (1 - v) * ch}`).join(' '); const xLabels = range === 'Hoje' ? ['00', '06', '12', '18', '23'] : range === '30D' ? ['1', '8', '15', '22', '30'] : ['Nov', 'Dez', 'Jan', 'Fev', 'Mar', 'Abr']; return ( {[0.25, 0.5, 0.75, 1].map((g, i) => )} {data.map((v, i) => )} {lat.map((v, i) => )} {[0, 0.5, 1].map((g, i) => {Math.round(g * 620)})} {xLabels.map((l, i) => {l})} ); } function Heatmap({ seed = 1 }) { const days = ['SEG', 'TER', 'QUA', 'QUI', 'SEX', 'SAB', 'DOM']; function intensity(d, h) { const work = h >= 9 && h <= 18 ? 0.55 : 0.12; const weekend = d >= 5 ? -0.2 : 0; return Math.max(0, Math.min(1, work + weekend + Math.sin((d * 7 + h + seed) * 1.3) * 0.18)); } function lvl(v) { if (v < 0.12) return 0; if (v < 0.3) return 1; if (v < 0.5) return 2; if (v < 0.75) return 3; if (v < 0.9) return 4; return 5; } return ( <>
{Array.from({ length:24 }).map((_, h) =>
{h % 6 === 0 ? String(h).padStart(2, '0') : ''}
)} {days.map((d, di) => (
{d}
{Array.from({ length:24 }).map((_, h) => { const v = intensity(di, h); const l = lvl(v); return
; })} ))}
Semana atual densidade calculada por atividade recente
); }