/* 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.
}
{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
| Timestamp | Agente | Conversa | Decisão | Ferramenta | Latência | Status |
{telemetryRows.map((r, i) => (
| {r.time} |
{r.agent} |
{r.conversation} |
{r.decision} |
{r.tool} |
{r.latency} |
{r.label} |
))}
{!telemetryRows.length && | 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 (
);
}
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 (
);
}
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
>
);
}