// Dashboard.jsx
'use strict';
const { useState, useMemo } = React;
const PIE_COLORS = { 'Aberta':'#3B82F6','Em andamento':'#F59E0B','Aguardando peça':'#EC4899','Concluída':'#10B981','Entregue':'#6366F1' };
function MetricCard({ label, value, sub, icon, iconBg, trend, trendVal, onClick }) {
const up = trend === 'up';
return (
{trendVal && (
{trendVal}
)}
{value}
{label}
{sub && {sub}
}
);
}
function Dashboard({ workOrders, appointments, clients, transactions, revenueChart, setPage }) {
const stats = useMemo(() => {
const hoje = new Date().toISOString().split('T')[0];
const mesAtual = hoje.slice(0, 7);
const abertas = workOrders.filter(o => o.status === 'Aberta' || o.status === 'Em andamento' || o.status === 'Aguardando peça');
const concMes = workOrders.filter(o => o.status === 'Concluída' && o.entryDate?.startsWith(mesAtual));
const receitaMes = transactions.filter(t => t.type === 'entrada' && t.status === 'pago' && t.date?.startsWith(mesAtual)).reduce((s, t) => s + t.amount, 0);
const ativos = clients.length;
const osStatus = Object.entries(
workOrders.reduce((acc, o) => { acc[o.status] = (acc[o.status]||0)+1; return acc; }, {})
).map(([name, value]) => ({ name, value, fill: PIE_COLORS[name]||'#94A3B8' }));
return { abertas: abertas.length, concMes: concMes.length, receitaMes, ativos, osStatus };
}, [workOrders, transactions, clients]);
const proxAgendamentos = [...appointments]
.filter(a => a.date >= new Date().toISOString().split('T')[0])
.sort((a, b) => (a.date + a.time).localeCompare(b.date + b.time))
.slice(0, 5);
const ultimasOS = [...workOrders]
.sort((a, b) => b.entryDate?.localeCompare(a.entryDate))
.slice(0, 5);
const typeColor = { 'Visita técnica':'#3B82F6','Retirada':'#10B981','Entrega':'#FF6B35','Retorno':'#8B5CF6' };
const equipIcon = { Smartphone:'Smartphone', Notebook:'Laptop', Desktop:'Monitor', TV:'Tv', Tablet:'Smartphone' };
return (
{/* Welcome bar */}
Bom dia, Ademir! 👋
Aqui está o resumo do seu negócio hoje.
{new Date().toLocaleDateString('pt-BR', { weekday:'long', day:'2-digit', month:'long', year:'numeric' })}
{/* KPI cards */}
setPage('os')} />
setPage('os')} />
setPage('financeiro')} />
setPage('clientes')} />
{/* Charts row */}
{/* Revenue chart */}
Receita vs. Despesas
Últimos 6 meses
Receita
Despesas
`R$${(v/1000).toFixed(0)}k`} formatTooltip={fmtCurrency}/>
{/* Pie chart */}
OSs por Status
Total: {workOrders.length} ordens
({...s,color:s.fill}))} size={160}/>
{stats.osStatus.map(s => (
))}
{/* Bottom lists */}
{/* Próximos agendamentos */}
Próximos Agendamentos
{proxAgendamentos.length === 0
?
:
{proxAgendamentos.map(a => (
{a.title}
{fmtDate(a.date)} às {a.time}
{a.type}
))}
}
{/* Últimas OSs */}
Últimas Ordens de Serviço
{ultimasOS.map(os => (
#{os.num}
{os.clientName}
{os.equipment?.brand} {os.equipment?.model}
))}
);
}
Object.assign(window, { Dashboard });