// Caririaçu Guincho — Tab Geral (dashboard do veículo) const { useState: useStateTG, useEffect: useEffectTG, useMemo: useMemoTG } = React; const TabGeral = ({ guincho, periodo }) => { const [dados, setDados] = useStateTG({ abast: [], desp: [], serv: [], rec: [] }); const [loading, setLoading] = useStateTG(true); useEffectTG(() => { let alive = true; (async () => { setLoading(true); const range = { gte: periodo.inicio, lte: periodo.fim + 'T23:59:59' }; const [abast, desp, serv, rec] = await Promise.all([ sb.from('abastecimentos').select('*') .eq('guincho_id', guincho.id).gte('data', range.gte).lte('data', range.lte), sb.from('despesas_veiculo').select('*') .eq('guincho_id', guincho.id).gte('data', range.gte).lte('data', range.lte), sb.from('manutencoes').select('*') .eq('guincho_id', guincho.id).gte('data', periodo.inicio).lte('data', periodo.fim), sb.from('assistencias').select('*') .eq('guincho_id', guincho.id).eq('status', 'finalizada') .gte('data', range.gte).lte('data', range.lte), ]); if (!alive) return; setDados({ abast: abast.data || [], desp: desp.data || [], serv: serv.data || [], rec: rec.data || [], }); setLoading(false); })(); return () => { alive = false; }; }, [guincho.id, periodo.inicio, periodo.fim]); const metricas = useMemoTG(() => { const sumAbast = dados.abast.reduce((s, x) => s + Number(x.valor_total || 0), 0); const sumDesp = dados.desp.reduce((s, x) => s + Number(x.valor || 0), 0); const sumServ = dados.serv.reduce((s, x) => s + Number(x.valor || 0), 0); const sumRec = dados.rec.reduce((s, x) => s + Number(x.valor || 0), 0); const custo = sumAbast + sumDesp + sumServ; const saldo = sumRec - custo; const dias = Math.max(1, Math.ceil((new Date(periodo.fim) - new Date(periodo.inicio)) / 86400000)); const kms = dados.abast.map(x => Number(x.km)).filter(k => k > 0).sort((a, b) => a - b); const kmRodado = kms.length >= 2 ? kms[kms.length - 1] - kms[0] : 0; const volume = dados.abast.reduce((s, x) => s + Number(x.litros || 0), 0); const consumo = kmRodado > 0 && volume > 0 ? kmRodado / volume : null; return { sumAbast, sumDesp, sumServ, sumRec, custo, saldo, dias, kmRodado, volume, consumo, saldoPorDia: saldo / dias, saldoPorKm: kmRodado ? saldo / kmRodado : null, custoPorDia: custo / dias, custoPorKm: kmRodado ? custo / kmRodado : null, recPorDia: sumRec / dias, recPorKm: kmRodado ? sumRec / kmRodado : null, }; }, [dados, periodo]); const dadosGrafico = useMemoTG(() => { const out = {}; const add = (data, key, val) => { const k = data.slice(0, 7); // YYYY-MM out[k] = out[k] || { abast: 0, desp: 0, receita: 0 }; out[k][key] += Number(val || 0); }; dados.abast.forEach(x => add(x.data, 'abast', x.valor_total)); dados.desp.forEach(x => add(x.data, 'desp', x.valor)); dados.rec.forEach(x => add(x.data, 'receita', x.valor)); return out; }, [dados]); if (loading) return
Carregando…
; return (
{/* 3 cards principais */}
= 0 ? '#10B981' : '#EF4444'} />
{/* Divisão de custos */}
Divisão de custos
{metricas.custo === 0 ? (
Sem custos no período.
) : ( <>
⛽ Abast. {((metricas.sumAbast / metricas.custo) * 100).toFixed(0)}% 🧾 Desp. {((metricas.sumDesp / metricas.custo) * 100).toFixed(0)}% 🔧 Serv. {((metricas.sumServ / metricas.custo) * 100).toFixed(0)}%
)}
{/* Gráfico mensal */}
Despesas × Receitas (mensal)
{Object.keys(dadosGrafico).length === 0 ? (
Sem dados no período.
) : ( )}
{/* Distância + Combustível */}
Distância
{fmtKM(metricas.kmRodado)}
no período · km atual: {fmtKM(guincho.km_atual)}
Combustível
{fmtKML(metricas.consumo)}
{metricas.volume.toFixed(1).replace('.', ',')} L total
); }; Object.assign(window, { TabGeral });