// Dashboard View
const Dashboard = ({ leads, onOpenLead }) => {
const ativos = leads.filter((l) => l.stage !== 'Fechado');
const fechados = leads.filter((l) => l.stage === 'Fechado');
const totalPipeline = ativos.reduce((s, l) => s + l.valor, 0);
const totalFechado = fechados.reduce((s, l) => s + l.valor, 0);
const taxaConv = leads.length ? Math.round(fechados.length / leads.length * 100) : 0;
const hoje = new Date().toISOString().split('T')[0];
const semanaAtras = new Date(Date.now() - 7 * 86400000).toISOString().split('T')[0];
const novos = leads.filter((l) => l.entrada >= semanaAtras).length;
const followups = leads.filter((l) => l.followup && l.followup <= hoje).length;
const porEtapa = STAGES.map((s) => ({
stage: s,
count: leads.filter((l) => l.stage === s).length,
valor: leads.filter((l) => l.stage === s).reduce((a, l) => a + l.valor, 0)
}));
const maxCount = Math.max(...porEtapa.map((e) => e.count), 1);
const porOrigem = ORIGENS.map((o) => ({
origem: o,
count: leads.filter((l) => l.origem === o).length
})).filter((o) => o.count > 0).sort((a, b) => b.count - a.count);
const porMes = (() => {
const map = {};
leads.forEach((l) => {
const m = l.entrada.slice(0, 7);
map[m] = (map[m] || 0) + 1;
});
return Object.entries(map).sort().slice(-6).map(([m, c]) => {
const [y, mo] = m.split('-');
const meses = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'];
return { label: meses[parseInt(mo) - 1], count: c };
});
})();
const maxMes = Math.max(...porMes.map((m) => m.count), 1);
const metricCards = [
{ label: 'Total de Leads Ativos', value: ativos.length, sub: `${leads.length} total`, color: '#4f7cff', icon: '◈' },
{ label: 'Valor do Pipeline', value: formatCurrency(totalPipeline), sub: `${formatCurrency(totalFechado)} fechado`, color: '#4ade80', icon: '◉' },
{ label: 'Taxa de Conversão', value: taxaConv, sub: `${fechados.length} de ${leads.length} leads`, color: '#a78bfa', icon: '◈' },
{ label: 'Leads Novos (7d)', value: novos, sub: 'últimos 7 dias', color: '#38bdf8', icon: '◇' },
{ label: 'Follow-ups Pendentes', value: followups, sub: 'até hoje', color: followups > 0 ? '#facc15' : '#4ade80', icon: '◆' }];
return (
Dashboard
Visão geral do funil de vendas
{/* Metric cards */}
{metricCards.map((c, i) =>
{c.icon}
{c.value}
{c.label}
{c.sub}
)}
{/* Por etapa */}
Leads por Etapa
{porEtapa.map(({ stage, count, valor }) =>
{stage}
{count} lead{count !== 1 ? 's' : ''} · {formatCurrency(valor)}
)}
{/* Por mês */}
Entradas por Mês
{porMes.map(({ label, count }) =>
)}
{/* Por origem */}
Origem dos Leads
{porOrigem.map(({ origem, count }) =>
)}
{/* Follow-ups table */}
Follow-ups Pendentes
{['Empresa', 'Responsável', 'Etapa', 'Valor', 'Follow-up'].map((h) =>
| {h} |
)}
{leads.filter((l) => l.followup).sort((a, b) => a.followup.localeCompare(b.followup)).slice(0, 6).map((l) =>
onOpenLead(l)}>
| {l.empresa} |
{l.responsavel} |
{l.stage}
|
{formatCurrency(l.valor)} |
{formatDate(l.followup)} |
)}
);
};
const ds = {
page: { flex: 1, overflowY: 'auto', padding: '24px 28px', background: '#0d1020' },
header: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 24 },
pageTitle: { fontSize: 22, fontWeight: 700, color: '#e8eaf0' },
pageSubtitle: { fontSize: 13, color: '#4a4f6a', marginTop: 3 },
metricsRow: { display: 'flex', gap: 14, marginBottom: 20, flexWrap: 'wrap' },
metricCard: {
flex: '1 1 160px', background: '#151828', border: '1px solid #1e2235', borderRadius: 12,
padding: '16px 18px', display: 'flex', flexDirection: 'column', gap: 4
},
metricIcon: { width: 32, height: 32, borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 16, marginBottom: 4 },
metricValue: { fontSize: 22, fontWeight: 800, color: '#e8eaf0' },
metricLabel: { fontSize: 11, color: '#8b90a7', fontWeight: 500, lineHeight: 1.3 },
metricSub: { fontSize: 10, color: '#4a4f6a', marginTop: 2 },
chartsRow: { display: 'flex', gap: 14, marginBottom: 20, flexWrap: 'wrap' },
chartCard: { flex: '1 1 260px', background: '#151828', border: '1px solid #1e2235', borderRadius: 12, padding: '18px 20px' },
chartTitle: { fontSize: 13, fontWeight: 700, color: '#c7d2ff', letterSpacing: 0.3 },
barBg: { height: 5, background: '#1e2235', borderRadius: 3, overflow: 'hidden' },
barFill: { height: '100%', borderRadius: 3, transition: 'width 0.4s' },
th: { fontSize: 10, color: '#4a4f6a', fontWeight: 700, letterSpacing: 0.8, padding: '6px 10px', textAlign: 'left', borderBottom: '1px solid #1e2235' },
td: { fontSize: 12, color: '#8b90a7', padding: '10px 10px', borderBottom: '1px solid #111525' },
tr: { cursor: 'pointer', transition: 'background 0.1s' },
badge: { fontSize: 10, fontWeight: 700, padding: '2px 8px', borderRadius: 20, display: 'inline-block' }
};
Object.assign(window, { Dashboard });