import { useState, useEffect } from 'react' import { Wallet, Search, Plus, TrendingUp, TrendingDown, ChevronLeft, ChevronRight, Loader2, Calendar, Edit2, Trash2 } from 'lucide-react' import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts' import api from '../services/api' import { Orcamento } from '../types' import { useLookups } from '../hooks/useLookups' import Modal from '../components/Modal' const statusConfig: Record = { 'dentro_limite': { label: 'Dentro do Limite', class: 'badge-success' }, 'alerta': { label: 'Alerta', class: 'badge-warning' }, 'excedido': { label: 'Excedido', class: 'badge-error' }, 'disponivel': { label: 'Disponível', class: 'badge-info' }, } function computeStatus(orc: Orcamento): string { const planejado = orc.valor_planejado || orc.valor_previsto || 0 const realizado = orc.valor_realizado || 0 if (planejado === 0) return 'disponivel' const pct = realizado / planejado if (pct > 1) return 'excedido' if (pct > 0.85) return 'alerta' if (realizado === 0) return 'disponivel' return 'dentro_limite' } function getValorPlanejado(orc: Orcamento): number { return orc.valor_planejado ?? orc.valor_previsto ?? 0 } const emptyForm = { categoria_id: '', centro_custo_id: '', ano: 2026, mes: 1, valor_planejado: 0, tipo_periodo: 'mensal' } export default function Orcamentos() { const [loading, setLoading] = useState(true) const [orcamentos, setOrcamentos] = useState([]) const [searchTerm, setSearchTerm] = useState('') const [selectedYear, setSelectedYear] = useState(2026) const [selectedMonth, setSelectedMonth] = useState(0) const [showModal, setShowModal] = useState(false) const [editId, setEditId] = useState(null) const [form, setForm] = useState(emptyForm) const [saving, setSaving] = useState(false) const [page, setPage] = useState(1) const perPage = 20 const { categoriaMap, centrosCustoMap, categorias, centrosCusto, loading: lookupsLoading } = useLookups() // Capex/Opex chart data const [investData, setInvestData] = useState([]) useEffect(() => { fetchOrcamentos(); fetchInvestData() }, []) const fetchOrcamentos = async () => { try { const { data } = await api.get('/orcamento') setOrcamentos(data) } catch (err) { console.error('Error fetching orcamentos:', err) } finally { setLoading(false) } } const fetchInvestData = async () => { try { const { data } = await api.get('/orcamento/resumo-investimento', { params: { ano: 2026 } }) setInvestData(data) } catch (err) { console.error('Error fetching invest data:', err) } } const filteredOrcamentos = orcamentos.filter(orc => { const catName = categoriaMap[orc.categoria_id] || orc.categoria || orc.categoria_id || '' const matchesSearch = searchTerm === '' || catName.toLowerCase().includes(searchTerm.toLowerCase()) const matchesYear = orc.ano === selectedYear const matchesMonth = selectedMonth === 0 || orc.mes === selectedMonth return matchesSearch && matchesYear && matchesMonth }) const totalPrevisto = filteredOrcamentos.reduce((acc, orc) => acc + getValorPlanejado(orc), 0) const totalRealizado = filteredOrcamentos.reduce((acc, orc) => acc + (orc.valor_realizado || 0), 0) const economia = totalPrevisto - totalRealizado const months = [ 'Todos', 'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro' ] const formatCurrency = (value: number) => new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value) const getPercentage = (realizado: number, previsto: number) => { if (!previsto) return '0.0' return ((realizado / previsto) * 100).toFixed(1) } const openNew = () => { setEditId(null) setForm(emptyForm) setShowModal(true) } const openEdit = (orc: Orcamento) => { setEditId(orc.id) setForm({ categoria_id: orc.categoria_id, centro_custo_id: orc.centro_custo_id, ano: orc.ano, mes: orc.mes, valor_planejado: getValorPlanejado(orc), tipo_periodo: orc.tipo_periodo || 'mensal' }) setShowModal(true) } const handleSubmit = async () => { setSaving(true) try { const payload: any = { ...form } payload.valor_anual = payload.tipo_periodo === 'mensal' ? payload.valor_planejado * 12 : payload.valor_planejado if (editId) { await api.patch(`/orcamento/${editId}`, payload) } else { await api.post('/orcamento', payload) } setShowModal(false) fetchOrcamentos() } catch (err) { console.error('Error saving:', err) alert('Erro ao salvar orçamento') } finally { setSaving(false) } } const handleDelete = async (id: string) => { if (!confirm('Tem certeza que deseja excluir este orçamento?')) return try { await api.delete(`/orcamento/${id}`) fetchOrcamentos() } catch (err) { console.error('Error deleting:', err) alert('Erro ao excluir') } } if (loading || lookupsLoading) { return (
) } return (

Orçamentos

Gerencie e acompanhe os orçamentos de facilities

Total Planejado

{formatCurrency(totalPrevisto)}

Total Realizado

{formatCurrency(totalRealizado)}

= 0 ? 'bg-gradient-to-br from-green-500 to-emerald-500' : 'bg-gradient-to-br from-red-500 to-rose-500'} text-white`}>

{economia >= 0 ? 'Economia' : 'Excedente'}

{formatCurrency(Math.abs(economia))}

{/* Capex vs Opex Charts */} {investData.length > 0 && (

Orçamento por Tipo de Investimento

`R$ ${(v / 1000).toFixed(0)}k`} /> formatCurrency(value)} />
)}
setSearchTerm(e.target.value)} className="input-field pl-12" />
{filteredOrcamentos.slice((page - 1) * perPage, page * perPage).map((orcamento) => { const planejado = getValorPlanejado(orcamento) const percentage = Number(getPercentage(orcamento.valor_realizado, planejado)) const status = computeStatus(orcamento) const tipoInvest = orcamento.tipo_investimento || '' return ( ) })}
Categoria Tipo Investimento Centro de Custo Período Planejado Período Valor Anual Realizado % Utilizado Status Ações
{categoriaMap[orcamento.categoria_id] || orcamento.categoria || '-'}
{tipoInvest ? ( {tipoInvest} ) : ( - )} {centrosCustoMap[orcamento.centro_custo_id] || '-'}
{months[orcamento.mes]}/{orcamento.ano}
{formatCurrency(planejado)} {orcamento.tipo_periodo === 'anual' ? 'Anual' : 'Mensal'} {formatCurrency(orcamento.valor_anual || (orcamento.tipo_periodo === 'anual' ? planejado : planejado * 12))} {formatCurrency(orcamento.valor_realizado)}
100 ? 'bg-red-500' : percentage > 85 ? 'bg-amber-500' : 'bg-green-500'}`} style={{ width: `${Math.min(percentage, 100)}%` }} />
{percentage}%
{statusConfig[status]?.label || status}
Pág {page}/{Math.ceil(filteredOrcamentos.length / perPage) || 1} — {filteredOrcamentos.length} registros
setShowModal(false)} title={editId ? 'Editar Orçamento' : 'Novo Orçamento'} onSubmit={handleSubmit} submitLabel={editId ? 'Salvar' : 'Criar Orçamento'} loading={saving}>
setForm({ ...form, valor_planejado: Number(e.target.value) })} className="input-field" required /> {form.tipo_periodo === 'mensal' && form.valor_planejado > 0 && (

Valor anual calculado: {formatCurrency(form.valor_planejado * 12)}

)}
) }