// Caririaçu Guincho — Tab Despesa const { useState: useStateTD, useEffect: useEffectTD, useMemo: useMemoTD } = React; const TabDespesa = ({ guincho, periodo }) => { const [items, setItems] = useStateTD([]); const [tipos, setTipos] = useStateTD([]); const [edit, setEdit] = useStateTD(null); const [tick, setTick] = useStateTD(0); useEffectTD(() => { sb.from('despesas_veiculo').select('*') .eq('guincho_id', guincho.id) .gte('data', periodo.inicio) .lte('data', periodo.fim + 'T23:59:59') .order('data', { ascending: false }) .then(r => setItems(r.data || [])); }, [guincho.id, periodo.inicio, periodo.fim, tick]); useEffectTD(() => { db.list('tipos_despesa', { eq: { ativo: true }, order: 'nome', asc: true }).then(setTipos); }, []); const metricas = useMemoTD(() => { const total = items.reduce((s, x) => s + Number(x.valor || 0), 0); const dias = Math.max(1, Math.ceil((new Date(periodo.fim) - new Date(periodo.inicio)) / 86400000)); return { total, porDia: total / dias }; }, [items, periodo]); const porTipo = useMemoTD(() => { const map = {}; items.forEach(x => { const k = x.tipo || 'Outros'; map[k] = (map[k] || 0) + Number(x.valor || 0); }); return Object.entries(map).sort((a, b) => b[1] - a[1]); }, [items]); const novo = () => setEdit({ data: new Date().toISOString().slice(0, 16), tipo_id: tipos[0]?.id || null, tipo: tipos[0]?.nome || '', valor: '', km: guincho.km_atual || '', forma_pagto: '', obs: '' }); const salvar = async () => { if (!edit.valor || !edit.tipo) { showToast('Preencha tipo e valor', 'warning'); return; } const payload = { ...edit, guincho_id: guincho.id, valor: parseFloat(edit.valor), km: edit.km ? parseInt(edit.km) : null, }; const result = edit.id ? await db.update('despesas_veiculo', edit.id, payload) : await db.insert('despesas_veiculo', payload); if (!result) return; setEdit(null); setTick(t => t + 1); showToast('Despesa salva', 'success'); }; const excluir = async () => { if (!confirm('Excluir esta despesa? A transação financeira vinculada também será removida.')) return; const ok = await db.remove('despesas_veiculo', edit.id); if (!ok) return; setEdit(null); setTick(t => t + 1); showToast('Despesa excluída', 'info'); }; const maxValor = Math.max(1, ...porTipo.map(p => p[1])); return ( <> {/* Cards */}
{/* Gráfico por tipo (barras horizontais) */}
Despesas por tipo
{porTipo.length === 0 ? (
Sem dados no período.
) : porTipo.map(([tipo, valor]) => (
{tipo}{fmtBRL(valor)}
))}
{/* Lista */}
{items.length === 0 ? (
Nenhuma despesa. Clique no + pra adicionar.
) : items.map(x => (
setEdit({ ...x, data: x.data.slice(0, 16) })} style={{ padding: 12, borderBottom: '1px solid #F1F5F9', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', gap: 12 }}>
{x.tipo || '—'}
{new Date(x.data).toLocaleDateString('pt-BR')} {x.km && ` · ${fmtKM(x.km)}`} {x.obs && ` · ${x.obs}`}
{fmtBRL(x.valor)}
))}
{edit && ( setEdit(null)} title={edit.id ? 'Editar despesa' : 'Nova despesa'} width={460}>
setEdit({ ...edit, data: e.target.value })} /> setEdit({ ...edit, valor: e.target.value })} /> setEdit({ ...edit, km: e.target.value })} />
setEdit({ ...edit, obs: e.target.value })} />
{edit.id ? Excluir :
}
setEdit(null)}>Cancelar Salvar
)} ); }; Object.assign(window, { TabDespesa });