// Caririaçu Guincho — Componentes compartilhados das tabs de Frota const { useState: useStateCF, useEffect: useEffectCF, useRef: useRefCF } = React; // ── MetricCard ───────────────────────────────────────────────── const MetricCard = ({ label, total, porDia, porKm, color = '#0F172A' }) => (
{label}
{fmtBRL(total)}
{(porDia != null || porKm != null) && (
{porDia != null &&
{fmtBRL(porDia)} / dia
} {porKm != null &&
{fmtBRL(porKm)} / km
}
)}
); // ── FabAdicionar ────────────────────────────────────────────── const FabAdicionar = ({ onClick, icon = '+' }) => ( ); // ── PeriodoPicker ───────────────────────────────────────────── // Default: últimos 6 meses const PeriodoPicker = ({ inicio, fim, onChange, total }) => { const [open, setOpen] = useStateCF(false); const fmt = d => new Date(d).toLocaleDateString('pt-BR'); return (
{total != null && {total} registros · } {fmt(inicio)} — {fmt(fim)}
{open && ( setOpen(false)} title="Filtrar período" width={360}>
onChange({ inicio: e.target.value, fim })} /> onChange({ inicio, fim: e.target.value })} />
{[ { label: 'Mês atual', dias: 30 }, { label: 'Últimos 3 meses', dias: 90 }, { label: 'Últimos 6 meses', dias: 180 }, { label: 'Último ano', dias: 365 }, ].map(p => ( ))}
setOpen(false)}>Aplicar
)}
); }; // ── Util de formatação ──────────────────────────────────────── const fmtBRL = v => 'R$ ' + Number(v || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const fmtKM = v => Number(v || 0).toLocaleString('pt-BR') + ' km'; const fmtKML = v => v == null ? '—' : Number(v).toFixed(2).replace('.', ',') + ' km/L'; // ── GraficoBarrasMensal ────────────────────────────────────── // dados: { '2026-01': { abast: 320, desp: 50, receita: 450 }, ... } // series: array de { key, label, color } const GraficoBarrasMensal = ({ dados, series, height = 200 }) => { const canvasRef = useRefCF(null); const chartRef = useRefCF(null); useEffectCF(() => { if (!canvasRef.current || !window.Chart) return; if (chartRef.current) { chartRef.current.destroy(); chartRef.current = null; } const labels = Object.keys(dados).sort(); const datasets = series.map(s => ({ label: s.label, data: labels.map(m => dados[m]?.[s.key] || 0), backgroundColor: s.color, borderRadius: 4, })); chartRef.current = new window.Chart(canvasRef.current, { type: 'bar', data: { labels: labels.map(m => { const [y, mm] = m.split('-'); return mm + '/' + y.slice(2); }), datasets }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, font: { size: 10 } } } }, scales: { x: { grid: { display: false }, ticks: { font: { size: 10 } } }, y: { ticks: { font: { size: 10 }, callback: v => 'R$ ' + v } }, }, }, }); return () => { if (chartRef.current) { chartRef.current.destroy(); chartRef.current = null; } }; }, [JSON.stringify(dados), JSON.stringify(series)]); return
; }; // ── BadgeLembrete ──────────────────────────────────────────── // count = quantidade de lembretes amarelo+vermelho; clique navega const BadgeLembrete = ({ count, onClick, size = 'sm' }) => { if (!count) return null; const isLarge = size === 'lg'; return ( ); }; // ── BadgeLembreteGlobal ────────────────────────────────────── // Conta lembretes amarelo+vermelho de toda a frota const BadgeLembreteGlobal = ({ onClick }) => { const [count, setCount] = useStateCF(0); useEffectCF(() => { let alive = true; let runId = 0; const calcular = async () => { const myRun = ++runId; const [lembretes, frota] = await Promise.all([ db.list('lembretes', { eq: { status: 'ativo' } }), db.list('frota'), ]); if (!alive || myRun !== runId) return; const fmap = Object.fromEntries(frota.map(g => [g.id, g.km_atual || 0])); let n = 0; for (const l of lembretes) { const st = window.statusLembrete(l, fmap[l.guincho_id]); if (st.estado !== 'ativo') n++; } setCount(n); }; calcular(); const id = setInterval(calcular, 60000); // recalcula a cada minuto return () => { alive = false; clearInterval(id); }; }, []); return ; }; Object.assign(window, { MetricCard, FabAdicionar, PeriodoPicker, GraficoBarrasMensal, BadgeLembrete, BadgeLembreteGlobal, fmtBRL, fmtKM, fmtKML });