// Workspace por Empresa - 4 abas const { useState: useState_W, useMemo: useMemo_W, useEffect: useEffect_W } = React; function WorkspaceEmpresa({ empresa, lancamentos, portadores, centrosCusto, formasPagamento, onBack, onUpsertLanc, onDeleteLanc, onPayLanc }) { const [aba, setAba] = useState_W('contas'); const [novoLancHeader, setNovoLancHeader] = useState_W(null); const hoje = todayISO(); const stats = useMemo_W(() => empresaStats(empresa, lancamentos, hoje), [empresa, lancamentos, hoje]); return (
{/* Header da empresa */}
{empresa.nome.charAt(0)}

{empresa.nome}

{empresa.cnpj} · {empresa.segmento} {empresa.responsavel && <>·Resp: {empresa.responsavel}}
{/* Tabs */}
{[ { id: 'contas', label: 'Contas a Pagar/Receber', icon: 'list', count: lancamentos.length }, { id: 'portadores', label: 'Portadores', icon: 'bank' }, { id: 'centros', label: 'Centros de Custo', icon: 'target' }, { id: 'relatorio', label: 'Relatório', icon: 'chart' }, ].map(t => ( ))}
{/* Tab content */}
{aba === 'contas' && } {aba === 'portadores' && } {aba === 'centros' && } {aba === 'relatorio' && }
{novoLancHeader && ( setNovoLancHeader(null)}> setNovoLancHeader(null)} onSave={(l) => { onUpsertLanc({ ...l, empresaId: empresa.id }); setNovoLancHeader(null); }} /> )}
); } // ----- Tab 1: Contas ----- function ContasTab({ empresa, lancamentos, portadores, centrosCusto, formasPagamento, onUpsertLanc, onDeleteLanc, onPayLanc }) { const isMobile = useIsMobile(); const [filtros, setFiltros] = useState_W({ tipo: 'todos', status: 'todos', portador: 'todos', centroCusto: 'todos', formaPgto: 'todos', dataIni: '', dataFim: '', busca: '' }); const [novoLanc, setNovoLanc] = useState_W(null); const [pgto, setPgto] = useState_W(null); const [comprovante, setComprovante] = useState_W(null); const [showImport, setShowImport] = useState_W(false); const toast = useToast(); const hoje = todayISO(); const POR_PAGINA = 20; const [pagina, setPagina] = useState_W(1); const [inlineFormOpen, setInlineFormOpen] = useState_W(true); const [fInline, setFInline] = useState_W({ tipo: 'saida', descricao: '', valor: '', vencimento: todayISO(), pago: false, portadorId: portadores[0]?.id || '', centroCustoId: centrosCusto.find(c => c.tipo === 'saida')?.id || centrosCusto[0]?.id || '', formaPagamento: formasPagamento[0] || 'PIX', parcelas: 1 }); const setInlineVal = (k, v) => { setFInline(prev => { const next = { ...prev, [k]: v }; if (k === 'tipo') { next.centroCustoId = centrosCusto.find(c => c.tipo === v)?.id || ''; } return next; }); }; const ccsFiltradosInline = centrosCusto.filter(c => c.tipo === fInline.tipo); const [errosInline, setErrosInline] = useState_W({}); const [salvandoInline, setSalvandoInline] = useState_W(false); function addMonthsString(isoDateStr, numMonths) { if (!isoDateStr) return ''; const d = new Date(isoDateStr + 'T12:00:00Z'); d.setUTCMonth(d.getUTCMonth() + numMonths); return d.toISOString().split('T')[0]; } async function submitInline(e) { e?.preventDefault(); const e_val = {}; if (!fInline.tipo) e_val.tipo = 'Tipo obrigatório'; if (!fInline.descricao?.trim()) e_val.descricao = 'Descrição obrigatória'; const val = parseFloat(String(fInline.valor).replace(',', '.')); if (!val || val <= 0) e_val.valor = 'Valor deve ser maior que zero'; if (!fInline.vencimento) e_val.vencimento = 'Vencimento obrigatório'; if (fInline.pago === null || fInline.pago === undefined) e_val.pago = 'Status pago obrigatório'; if (!fInline.portadorId) e_val.portadorId = 'Portador obrigatório'; if (!fInline.centroCustoId) e_val.centroCustoId = 'Centro de Custo obrigatório'; if (!fInline.formaPagamento) e_val.formaPagamento = 'Forma de Pagamento obrigatória'; if (Object.keys(e_val).length > 0) { setErrosInline(e_val); return; } setErrosInline({}); const numParc = parseInt(fInline.parcelas) || 1; setSalvandoInline(true); try { if (numParc > 1) { const baseVal = Math.floor((val * 100) / numParc) / 100; const diff = Math.round((val - baseVal * numParc) * 100) / 100; const ref = uid('parc'); const payloadArr = []; for (let i = 0; i < numParc; i++) { const vDate = i === 0 ? fInline.vencimento : addMonthsString(fInline.vencimento, i); payloadArr.push({ ...fInline, id: uid('lanc'), valor: i === numParc - 1 ? baseVal + diff : baseVal, vencimento: vDate, competencia: competenciaFromDate(vDate), empresaId: empresa.id, parcelaRef: ref, parcelaNum: i + 1, parcelaTotal: numParc, descricao: `${fInline.descricao} (${i + 1}/${numParc})` }); } await onUpsertLanc(payloadArr); } else { await onUpsertLanc({ id: uid('lanc'), ...fInline, valor: val, empresaId: empresa.id, competencia: competenciaFromDate(fInline.vencimento) }); } setFInline({ tipo: 'saida', descricao: '', valor: '', vencimento: todayISO(), pago: false, portadorId: portadores[0]?.id || '', centroCustoId: centrosCusto.find(c => c.tipo === 'saida')?.id || centrosCusto[0]?.id || '', formaPagamento: formasPagamento[0] || 'PIX', parcelas: 1 }); toast.push('Lançamento cadastrado com sucesso!', 'success'); } finally { setSalvandoInline(false); } } useEffect_W(() => setPagina(1), [filtros]); const portMap = Object.fromEntries(portadores.map(p => [p.id, p])); const ccMap = Object.fromEntries(centrosCusto.map(c => [c.id, c])); const filtrados = lancamentos.filter(l => { if (filtros.tipo !== 'todos' && l.tipo !== filtros.tipo) return false; if (filtros.portador !== 'todos' && l.portadorId !== filtros.portador) return false; if (filtros.centroCusto !== 'todos' && l.centroCustoId !== filtros.centroCusto) return false; if (filtros.formaPgto !== 'todos' && l.formaPagamento !== filtros.formaPgto) return false; if (filtros.dataIni && l.vencimento < filtros.dataIni) return false; if (filtros.dataFim && l.vencimento > filtros.dataFim) return false; if (filtros.busca && !l.descricao.toLowerCase().includes(filtros.busca.toLowerCase())) return false; if (filtros.status !== 'todos') { const s = lancStatus(l, hoje); if (s !== filtros.status) return false; } return true; }).sort((a, b) => a.vencimento.localeCompare(b.vencimento)); const totalFiltrados = filtrados.length; const totalPaginas = Math.ceil(totalFiltrados / POR_PAGINA); const paginados = filtrados.slice((pagina - 1) * POR_PAGINA, pagina * POR_PAGINA); const totalReceber = filtrados.filter(l => l.tipo === 'entrada' && !l.pago).reduce((s, l) => s + (l.saldoRestante !== undefined ? l.saldoRestante : l.valor), 0); const totalPagar = filtrados.filter(l => l.tipo === 'saida' && !l.pago).reduce((s, l) => s + (l.saldoRestante !== undefined ? l.saldoRestante : l.valor), 0); const totalRecebido = filtrados.filter(l => l.tipo === 'entrada').reduce((s, l) => s + (l.totalPago !== undefined ? l.totalPago : (l.pago ? l.valor : 0)), 0); const totalPago = filtrados.filter(l => l.tipo === 'saida').reduce((s, l) => s + (l.totalPago !== undefined ? l.totalPago : (l.pago ? l.valor : 0)), 0); function clearFilters() { setFiltros({ tipo: 'todos', status: 'todos', portador: 'todos', centroCusto: 'todos', formaPgto: 'todos', dataIni: '', dataFim: '', busca: '' }); } const hasFilter = filtros.tipo !== 'todos' || filtros.status !== 'todos' || filtros.portador !== 'todos' || filtros.centroCusto !== 'todos' || filtros.formaPgto !== 'todos' || filtros.dataIni || filtros.dataFim || filtros.busca; return (
{/* mini KPIs */}
l.tipo === 'entrada' && !l.pago).length} lançamentos`} /> l.tipo === 'saida' && !l.pago).length} lançamentos`} /> l.tipo === 'entrada' && (l.pago || l.totalPago > 0)).length} lançamentos`} /> l.tipo === 'saida' && (l.pago || l.totalPago > 0)).length} lançamentos`} />
{/* Rápido Cadastro de Lançamento */}

Rápido Cadastro de Lançamento

{inlineFormOpen && (
setInlineVal('tipo', e.target.value)} options={[ { value: 'entrada', label: 'Entrada' }, { value: 'saida', label: 'Saída' } ]} style={{ borderColor: errosInline.tipo ? '#dc2626' : undefined }} /> setInlineVal('descricao', e.target.value)} placeholder="Ex: Conta de luz" style={{ borderColor: errosInline.descricao ? '#dc2626' : undefined }} /> setInlineVal('valor', e.target.value)} placeholder="0,00" style={{ borderColor: errosInline.valor ? '#dc2626' : undefined }} /> setInlineVal('parcelas', parseInt(e.target.value) || 1)} /> setInlineVal('vencimento', e.target.value)} style={{ border: errosInline.vencimento ? '1px solid #dc2626' : undefined }} /> setInlineVal('pago', e.target.value === 'pago')} options={[ { value: 'pendente', label: 'Pendente' }, { value: 'pago', label: 'Pago' } ]} style={{ borderColor: errosInline.pago ? '#dc2626' : undefined }} /> setInlineVal('portadorId', e.target.value)} options={[ ...portadores.map(p => ({ value: p.id, label: p.nome })) ]} style={{ borderColor: errosInline.portadorId ? '#dc2626' : undefined }} /> setInlineVal('centroCustoId', e.target.value)} options={[ ...ccsFiltradosInline.map(c => ({ value: c.id, label: c.nome })) ]} style={{ borderColor: errosInline.centroCustoId ? '#dc2626' : undefined }} /> setInlineVal('formaPagamento', e.target.value)} options={[ ...formasPagamento.map(f => ({ value: f, label: f })) ]} style={{ borderColor: errosInline.formaPagamento ? '#dc2626' : undefined }} />
{salvandoInline ? : 'Salvar'}
)}
{/* Filtros */}
setFiltros({ ...filtros, busca: e.target.value })} placeholder="Buscar descrição..." style={{ paddingLeft: 34, width: '100%' }} />
setFiltros({ ...filtros, tipo: e.target.value })} options={[ { value: "todos", label: "Todos" }, { value: "entrada", label: "Entradas" }, { value: "saida", label: "Saídas" } ]} style={{ minWidth: isMobile ? 0 : 140 }} /> setFiltros({ ...filtros, status: e.target.value })} options={[ { value: "todos", label: "Todos" }, { value: "pago", label: "Pago" }, { value: "parcial", label: "Parcial" }, { value: "em-dia", label: "Em dia" }, { value: "vencendo", label: "Vencendo" }, { value: "vencido", label: "Vencido" } ]} style={{ minWidth: isMobile ? 0 : 140 }} /> setFiltros({ ...filtros, portador: e.target.value })} options={[ { value: "todos", label: "Todos" }, ...portadores.map(p => ({ value: p.id, label: p.nome })) ]} style={{ minWidth: isMobile ? 0 : 140 }} /> setFiltros({ ...filtros, centroCusto: e.target.value })} options={[ { value: "todos", label: "Todos" }, ...centrosCusto.map(c => ({ value: c.id, label: c.nome })) ]} style={{ minWidth: isMobile ? 0 : 140 }} /> setFiltros({ ...filtros, formaPgto: e.target.value })} options={[ { value: "todos", label: "Todas" }, ...formasPagamento.map(f => ({ value: f.nome, label: f.nome })) ]} style={{ minWidth: isMobile ? 0 : 140 }} /> setFiltros({ ...filtros, dataIni: e.target.value })} /> setFiltros({ ...filtros, dataFim: e.target.value })} />
{hasFilter && Limpar} setShowImport(true)}>Importar XLSX
{/* Tabela */}
{paginados.map(l => { const s = lancStatus(l, hoje); const c = statusColor(s); const port = portMap[l.portadorId]; const cc = ccMap[l.centroCustoId]; return ( ); })}
Vencimento Tipo Descrição Centro de Custo Portador Forma Pgto. Valor Status Ações
{formatDate(l.vencimento)}
{l.competencia}
{l.tipo === 'entrada' ? 'Entrada' : 'Saída'}
{l.descricao}
{cc?.nome} {port?.nome} {l.formaPagamento}
{l.tipo === 'saida' && '-'}{formatBRL(l.valor)}
{l.statusPg === 'parcial' && (
Falta: {formatBRL(l.saldoRestante)} (pago {formatBRL(l.totalPago)})
)}
{(!l.pago || l.statusPg === 'parcial') && setPgto(l)}>Pagar} {(l.pago || l.statusPg === 'parcial') && setComprovante(l)}>Comprovante}
{totalFiltrados === 0 && ( { setInlineFormOpen(true); setTimeout(() => { const el = document.getElementById('inline-descricao'); if (el) el.focus(); }, 50); }}>Novo lançamento } /> )} {totalPaginas > 1 && (
Mostrando {((pagina-1)*POR_PAGINA)+1}–{Math.min(pagina*POR_PAGINA, totalFiltrados)} de {totalFiltrados} lançamentos
setPagina(p => p - 1)}> ← Anterior {Array.from({ length: Math.min(5, totalPaginas) }, (_, i) => { let p; if (totalPaginas <= 5) p = i + 1; else if (pagina <= 3) p = i + 1; else if (pagina >= totalPaginas - 2) p = totalPaginas - 4 + i; else p = pagina - 2 + i; return ( ); })} setPagina(p => p + 1)}> Próxima →
)}
{novoLanc && ( setNovoLanc(null)}> setNovoLanc(null)} onSave={(l) => { onUpsertLanc({ ...l, empresaId: empresa.id }); toast.push(novoLanc.id ? 'Lançamento atualizado' : 'Lançamento criado'); setNovoLanc(null); }} /> )} {pgto && ( setPgto(null)} onConfirm={(payload) => { onPayLanc(pgto.id, payload); toast.push(`${pgto.tipo === 'entrada' ? 'Recebimento' : 'Pagamento'} confirmado`); setPgto(null); }} /> )} {comprovante && ( setComprovante(null)} /> )} {showImport && ( setShowImport(false)} onImport={(lancsArray) => { lancsArray.forEach(l => { onUpsertLanc({ ...l, empresaId: empresa.id }); }); toast.push(`${lancsArray.length} lançamentos importados`); setShowImport(false); }} /> )}
); } const iconBtn = { background: 'transparent', border: '1px solid var(--c-border)', borderRadius: 6, width: 28, height: 28, cursor: 'pointer', color: 'var(--c-text-muted)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }; class LancamentoErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } render() { if (this.state.hasError) { return (
Ocorreu um erro ao carregar os dados deste lançamento. Por favor, feche e tente novamente.

{String(this.state.error)}
); } return this.props.children; } } // ----- Form de lançamento ----- function LancamentoFormModal({ lanc, portadores, centrosCusto, formasPagamento, onClose, onSave }) { const isMobile = useIsMobile(); const l = lanc || {}; const [f, setF] = useState_W({ id: l.id ?? uid('lanc'), tipo: l.tipo ?? 'saida', descricao: l.descricao ?? '', valor: l.valor ?? '', vencimento: l.vencimento ?? todayISO(), competencia: l.competencia ?? competenciaFromDate(l.vencimento ?? todayISO()), portadorId: l.portadorId ?? portadores[0]?.id, centroCustoId: l.centroCustoId ?? centrosCusto[0]?.id, formaPagamento: l.formaPagamento ?? formasPagamento[0] ?? '', observacao: l.observacao ?? '', pago: l.pago ?? false, pagamento: l.pagamento ?? null, }); const set = (k, v) => setF(prev => ({ ...prev, [k]: v })); const ccsFiltrados = centrosCusto.filter(c => c.tipo === f.tipo); const toast = useToast(); const [isParcelado, setIsParcelado] = useState_L(false); const [numParcelas, setNumParcelas] = useState_L(2); const [erros, setErros] = useState_L({}); const [salvando, setSalvando] = useState_L(false); function addMonthsString(isoDateStr, numMonths) { if (!isoDateStr) return ''; const d = new Date(isoDateStr + 'T12:00:00Z'); d.setUTCMonth(d.getUTCMonth() + numMonths); return d.toISOString().split('T')[0]; } function validarLancamento(f) { const e = {}; if (!f.descricao?.trim()) e.descricao = 'Descrição obrigatória'; const val = parseFloat(String(f.valor).replace(',','.')); if (!val || val <= 0) e.valor = 'Valor deve ser maior que zero'; if (!f.vencimento) e.vencimento = 'Data de vencimento obrigatória'; if (!f.tipo) e.tipo = 'Tipo obrigatório'; return e; } async function submit(e) { e?.preventDefault(); const e_validation = validarLancamento(f); if (Object.keys(e_validation).length > 0) { setErros(e_validation); return; } setErros({}); const cc = ccsFiltrados.find(c => c.id === f.centroCustoId) || ccsFiltrados[0]; const valOriginal = parseFloat(String(f.valor).replace(',','.')); if (isParcelado && !lanc.id && numParcelas > 1) { const baseVal = Math.floor((valOriginal * 100) / numParcelas) / 100; const diff = Math.round((valOriginal - baseVal * numParcelas) * 100) / 100; const parcelas = []; const ref = uid('parc'); for (let i = 0; i < numParcelas; i++) { const vDate = i === 0 ? f.vencimento : addMonthsString(f.vencimento, i); parcelas.push({ ...f, id: uid('lanc'), valor: i === numParcelas - 1 ? baseVal + diff : baseVal, vencimento: vDate, competencia: competenciaFromDate(vDate), centroCustoId: cc.id, parcelaRef: ref, parcelaNum: i + 1, parcelaTotal: numParcelas, descricao: `${f.descricao} (${i + 1}/${numParcelas})` }); } setSalvando(true); try { await onSave(parcelas); } finally { setSalvando(false); } } else { setSalvando(true); try { await onSave({ ...f, valor: valOriginal, centroCustoId: cc.id, competencia: competenciaFromDate(f.vencimento) }); } finally { setSalvando(false); } } } return ( Cancelar {salvando ? <> Salvando... : (lanc.id ? 'Salvar' : 'Criar Lançamento')} }>
{[ { v: 'entrada', label: 'Entrada (a receber)', cor: '#16a34a', icon: 'arrowDown' }, { v: 'saida', label: 'Saída (a pagar)', cor: '#dc2626', icon: 'arrowUp' } ].map(opt => ( ))}
{erros.tipo && {erros.tipo}}
set('descricao', e.target.value)} placeholder="Ex: Conta de luz - Outubro" autoFocus style={{ borderColor: erros.descricao ? '#dc2626' : undefined }} /> {erros.descricao && {erros.descricao}} set('valor', e.target.value)} placeholder="0,00" style={{ borderColor: erros.valor ? '#dc2626' : undefined }} /> {erros.valor && {erros.valor}} {!lanc.id && ( )} {isParcelado && !lanc.id && (
setNumParcelas(parseInt(e.target.value) || 2)} style={{ width: 100 }} /> {numParcelas} parcelas de aprox. {formatBRL((parseFloat(String(f.valor).replace(',','.')) || 0) / numParcelas)} / mês
)} { set('vencimento', e.target.value); set('competencia', competenciaFromDate(e.target.value)); }} style={{ border: erros.vencimento ? '1px solid #dc2626' : undefined }} /> {erros.vencimento && {erros.vencimento}} set('centroCustoId', e.target.value)} options={[ ...ccsFiltrados.map(c => ({ value: c.id, label: c.nome })) ]} /> set('portadorId', e.target.value)} options={[ ...portadores.map(p => ({ value: p.id, label: p.nome })) ]} /> set('formaPagamento', e.target.value)} options={[ ...formasPagamento.map(f => ({ value: f, label: f })) ]} /> set('competencia', e.target.value)} placeholder="MM/AAAA" />