/* === value-tracker.jsx ===============================================
RoAI Value Tracker — full configurable surface.
Replaces the old PreviewRoAI in preview.jsx.
Features:
- Period selector (3m / 6m / 12m / Tudo)
- Alert banner for overdue plans
- 3 hero KPI cards (Pipeline Total / Realizado / Taxa de Conversão)
- Pipeline de Valor (5 status cards)
- Charts: Ganhos por Status horizontal bars + Composição donut
- Summary panels: Ganhos by status / Planos by status
- Planos de Ação Kanban with drag/drop + add/edit
- Side panel: Assistente de Valor (chat + quick actions)
===================================================================== */
const VT_COLUMNS = [
{ id: 'backlog', label: 'Backlog' },
{ id: 'planejado', label: 'Planejado' },
{ id: 'emAndamento', label: 'Em Andamento' },
{ id: 'concluido', label: 'Concluído' },
{ id: 'cancelado', label: 'Cancelado' },
];
function PreviewRoAI({ state, onSetState }) {
const vt = state.valueTracker;
const setVT = (patch) => onSetState({
...state,
valueTracker: { ...vt, ...(typeof patch === 'function' ? patch(vt) : patch) }
});
/* Assistant collapsed by default — the browser-body frame is narrow */
const [collapsedAssistant, setCollapsedAssistant] = React.useState(true);
/* F5 — abre o drill-down (desabilitado em modo edição p/ não conflitar
com a edição inline dos números). */
const openDD = (opts) => {
if (state.editMode) return;
if (window.openDrilldown) window.openDrilldown(onSetState, state, opts);
};
return (
{vt.alert && }
{!collapsedAssistant && (
setCollapsedAssistant(true)}
/>
)}
{collapsedAssistant && (
)}
);
}
/* ==================================================================
HEADER
================================================================== */
function VTHeader({ state, setVT }) {
const vt = state.valueTracker;
const periods = [
{ id: '3m', label: '3 meses' },
{ id: '6m', label: '6 meses' },
{ id: '12m', label: '12 meses' },
{ id: 'all', label: 'Tudo' },
];
return (
RoAI - Value Tracker
{periods.map(p => (
))}
);
}
/* ==================================================================
ALERT BANNER
================================================================== */
function VTAlert({ alert }) {
return (
{alert.message}
);
}
/* ==================================================================
HERO KPIs (3 cards)
================================================================== */
function VTHeroKPIs({ vt, setVT, state, openDD }) {
const k = vt.kpis;
const drill = (title, metric) => (openDD ? () => openDD({ title, metric }) : undefined);
return (
{k.pipelineTotal.label}ver detalhes →
{' · '}
{k.realizado.label}ver detalhes →
{k.conversao.label}ver detalhes →
);
}
function VTConversionDonut({ pct }) {
const r = 16;
const C = 2 * Math.PI * r;
const dash = (pct / 100) * C;
return (
);
}
/* ==================================================================
PIPELINE DE VALOR (5 status cards)
================================================================== */
function VTPipelineValor({ vt, setVT, state, openDD }) {
return (
Pipeline de Valor
{vt.pipelineValor.map((p, i) => (
openDD({ title: p.label, metric: 'saving' }) : undefined}
title="Ver detalhamento">
))}
);
}
/* ==================================================================
CHARTS: Ganhos por Status (horizontal) + Composição (donut)
================================================================== */
function VTCharts({ vt, state, openDD }) {
const STATUS_COLOR = {
realizado: { color: '#10B981', label: 'Realizado' },
bloqueado: { color: '#EF4444', label: 'Bloqueado (vencido)' },
emAndamento: { color: '#F59E0B', label: 'Em andamento' },
semPlano: { color: '#9CA3AF', label: 'Sem plano' },
};
const maxValor = Math.max(...vt.ganhos.map(g => g.valor));
return (
Ganhos Identificados por Status
{vt.ganhos.map((g, i) => {
const stat = STATUS_COLOR[g.status] || STATUS_COLOR.semPlano;
const pct = (g.valor / maxValor) * 100;
return (
openDD({ title: g.label, metric: 'saving' }) : undefined}
title="Ver detalhamento">
{g.label}
);
})}
{Object.values(STATUS_COLOR).map((s, i) => (
{s.label}
))}
Composição do Pipeline
{vt.composicao.map((c, i) => (
{c.label}
))}
);
}
function VTDonut({ data }) {
const size = 160;
const r = 60;
const cx = size / 2, cy = size / 2;
const total = data.reduce((s, d) => s + d.pct, 0);
let acc = 0;
return (
);
}
/* ==================================================================
SUMMARY by status (Ganhos / Planos)
================================================================== */
function VTSummary({ vt, state }) {
return (
);
}
function SummaryPanel({ title, items, idPrefix }) {
const total = items.reduce((s, x) => s + x.count, 0);
const max = Math.max(...items.map(x => x.count));
return (
{title}
Total: {total}
{items.map((it, i) => (
))}
);
}
Object.assign(window, { PreviewRoAI, VT_COLUMNS });