// Reports.jsx 'use strict'; const { useState, useMemo } = React; const REPORT_TYPES = [ { id:'faturamento', label:'Faturamento por Período', icon:'BarChart2', desc:'Receita e despesas nos últimos meses', color:'#FF6B35' }, { id:'servicos', label:'Serviços Mais Realizados', icon:'Wrench', desc:'Ranking dos serviços com maior volume', color:'#3B82F6' }, { id:'clientes', label:'Clientes que Mais Geram Receita',icon:'Users', desc:'Top clientes por valor total de OSs', color:'#8B5CF6' }, { id:'status', label:'OSs por Status', icon:'FileText', desc:'Distribuição das ordens por status', color:'#10B981' }, { id:'tecnicos', label:'Performance por Técnico', icon:'User', desc:'OSs concluídas por técnico', color:'#F59E0B' }, ]; const PIE_COLORS_REPORT = ['#FF6B35','#3B82F6','#8B5CF6','#10B981','#F59E0B','#EC4899']; function ReportCard({ r, active, onClick }) { return (

{r.label}

{r.desc}

); } function Reports({ workOrders, transactions, clients }) { const [activeReport, setActiveReport] = useState('faturamento'); const [period, setPeriod] = useState('6m'); const today = new Date(); const activeR = REPORT_TYPES.find(r => r.id === activeReport); const data = useMemo(() => { // Faturamento por período const byMonth = {}; transactions.filter(t=>t.status==='pago').forEach(t=>{ const mo = t.date?.slice(0,7)||''; if(!byMonth[mo]) byMonth[mo]={month:mo,label:`${t.date?.slice(5,7)}/${t.date?.slice(2,4)}`,receita:0,despesas:0}; if(t.type==='entrada') byMonth[mo].receita+=t.amount; else byMonth[mo].despesas+=t.amount; }); const faturamento = Object.values(byMonth).sort((a,b)=>a.month.localeCompare(b.month)).slice(-6); // Serviços mais realizados const svcCount = {}; workOrders.forEach(os=>(os.items||[]).forEach(item=>{ const k=item.desc||'Sem descrição'; if(!svcCount[k])svcCount[k]={name:k,count:0,total:0}; svcCount[k].count++; svcCount[k].total+=item.total||0; })); const servicos = Object.values(svcCount).sort((a,b)=>b.count-a.count).slice(0,8) .map(s=>({...s,name:s.name.length>22?s.name.slice(0,22)+'…':s.name})); // Clientes top const cliRevenue = {}; workOrders.forEach(os=>{ if(!cliRevenue[os.clientId])cliRevenue[os.clientId]={name:os.clientName,total:0,count:0}; cliRevenue[os.clientId].total+=os.total||0; cliRevenue[os.clientId].count++; }); const topClientes = Object.values(cliRevenue).sort((a,b)=>b.total-a.total).slice(0,6) .map(c=>({...c,name:c.name.split(' ').slice(0,2).join(' ')})); // OS por status const statusData = ['Aberta','Em andamento','Aguardando peça','Concluída','Entregue'].map(s=>({ name:s, value:workOrders.filter(o=>o.status===s).length, fill: {Aberta:'#3B82F6','Em andamento':'#F59E0B','Aguardando peça':'#EC4899',Concluída:'#10B981',Entregue:'#6366F1'}[s] })).filter(d=>d.value>0); // Por técnico const techData = {}; workOrders.filter(o=>o.status==='Concluída'||o.status==='Entregue').forEach(os=>{ const t=os.tech||'Não informado'; if(!techData[t])techData[t]={name:t.split(' ')[0],concluidas:0,total:0}; techData[t].concluidas++; techData[t].total+=os.total||0; }); const tecnicos = Object.values(techData).sort((a,b)=>b.concluidas-a.concluidas); return { faturamento, servicos, topClientes, statusData, tecnicos }; }, [workOrders, transactions]); const renderChart = () => { if (!BarChart) return
Carregando gráficos...
; switch(activeReport) { case 'faturamento': return (
{[ {label:'Receita Total (6m)', value:fmtCurrency(data.faturamento.reduce((s,d)=>s+d.receita,0)), color:'#FF6B35'}, {label:'Despesas Totais (6m)', value:fmtCurrency(data.faturamento.reduce((s,d)=>s+d.despesas,0)), color:'#EF4444'}, {label:'Lucro Bruto (6m)', value:fmtCurrency(data.faturamento.reduce((s,d)=>s+d.receita-d.despesas,0)), color:'#10B981'}, ].map((c,i)=>(

{c.label}

{c.value}

))}
`R$${(v/1000).toFixed(0)}k`} formatTooltip={fmtCurrency}/>
); case 'servicos': return (

Top {data.servicos.length} serviços realizados

`${v}×`}/>
); case 'clientes': return (

Top {data.topClientes.length} clientes por receita gerada

fmtCurrency(v)}/>
{data.topClientes.map((c,i)=>(
{c.name} {c.count} OS
{fmtCurrency(c.total)}
))}
); case 'status': return (
({...s,color:s.fill}))} size={200}/>
{data.statusData.map(s=>(
{s.name} {s.value}
{Math.round((s.value/workOrders.length)*100)}%
))}
); case 'tecnicos': return (

OSs concluídas e receita gerada por técnico

{data.tecnicos.map((t,i)=>(
{t.name.slice(0,2).toUpperCase()}

{t.name}

OSs Concluídas

{t.concluidas}

Receita

{fmtCurrency(t.total)}

))}
); default: return null; } }; return (
{REPORT_TYPES.map(r=>( setActiveReport(r.id)}/> ))}

{activeR?.label}

{activeR?.desc}

Exportar PDF
{renderChart()}
); } Object.assign(window, { Reports });