// Central de Gestão - Multi-empresa
const { useState: useState_C, useMemo: useMemo_C } = React;
function CentralGestao({ data, onOpenEmpresa, onCreateEmpresa, onDeleteEmpresa, onEditEmpresa }) {
const isMobile = useIsMobile();
const [busca, setBusca] = useState_C('');
const [empresaExcluir, setEmpresaExcluir] = useState_C(null);
const [view, setView] = useState_C('cards'); // cards | tabela
const [dashFiltros, setDashFiltros] = useState_C({
periodo: '6m', // '1m' | '3m' | '6m' | '12m' | 'custom'
segmento: 'todos',
status: 'todos',
dataIni: '',
dataFim: ''
});
const toast = useToast();
const hoje = todayISO();
const empresasComStats = useMemo_C(() => {
return data.empresas.map(e => {
const ls = data.lancamentos[e.id] || [];
const stats = empresaStats(e, ls, hoje);
return { ...e, stats, qtdLanc: ls.length };
});
}, [data.empresas, data.lancamentos, hoje]);
// Lista única de segmentos para o filtro
const segmentos = useMemo_C(() => [...new Set(data.empresas.map(e => e.segmento).filter(Boolean))].sort(), [data.empresas]);
// Empresas que entram no DASHBOARD (KPIs + charts), filtradas por segmento e status
const dashEmpresas = useMemo_C(() => empresasComStats.filter(e =>
(dashFiltros.segmento === 'todos' || e.segmento === dashFiltros.segmento) &&
(dashFiltros.status === 'todos' || e.stats.statusEmpresa === dashFiltros.status)
), [empresasComStats, dashFiltros.segmento, dashFiltros.status]);
const hasDashFilter = dashFiltros.periodo !== '6m' || dashFiltros.segmento !== 'todos' || dashFiltros.status !== 'todos' || dashFiltros.dataIni || dashFiltros.dataFim;
function clearDashFiltros() {
setDashFiltros({ periodo: '6m', segmento: 'todos', status: 'todos', dataIni: '', dataFim: '' });
}
// Lista de empresas (grade) — usa a toolbar própria (busca) + filtros do dashboard
const filtradas = dashEmpresas
.filter(e => !busca || e.nome.toLowerCase().includes(busca.toLowerCase()) || e.cnpj.includes(busca) || (e.documento && e.documento.replace(/\D/g, '').includes(busca.replace(/\D/g, ''))));
// KPIs globais (reagem aos filtros do dashboard)
const kpiTotalEmp = dashEmpresas.length;
const kpiVencidas = dashEmpresas.filter(e => e.stats.statusEmpresa === 'vencido').length;
const kpiVencendo = dashEmpresas.filter(e => e.stats.statusEmpresa === 'vencendo').length;
const kpiEmDia = dashEmpresas.filter(e => e.stats.statusEmpresa === 'em-dia').length;
const totalReceber = dashEmpresas.reduce((s, e) => s + e.stats.aReceber, 0);
const totalPagar = dashEmpresas.reduce((s, e) => s + e.stats.aPagar, 0);
const totalVencido = dashEmpresas.reduce((s, e) => s + e.stats.vencidos, 0);
const saldoConsolidado = dashEmpresas.reduce((s, e) => s + e.stats.saldo, 0);
// Charts data
const statusDist = [
{ label: 'Em dia', value: kpiEmDia, color: '#3b82f6' },
{ label: 'Vencendo', value: kpiVencendo, color: '#f59e0b' },
{ label: 'Vencido', value: kpiVencidas, color: '#ef4444' },
];
// Entradas x Saídas — período selecionado no filtro
const meses = useMemo_C(() => {
const arr = [];
if (dashFiltros.periodo === 'custom' && dashFiltros.dataIni && dashFiltros.dataFim) {
const start = new Date(dashFiltros.dataIni + 'T00:00:00');
const end = new Date(dashFiltros.dataFim + 'T00:00:00');
let cur = new Date(start.getFullYear(), start.getMonth(), 1);
const last = new Date(end.getFullYear(), end.getMonth(), 1);
let guard = 0;
while (cur <= last && guard < 120) {
arr.push({
label: cur.toLocaleDateString('pt-BR', { month: 'short' }).replace('.', ''),
comp: `${String(cur.getMonth() + 1).padStart(2, '0')}/${cur.getFullYear()}`
});
cur = new Date(cur.getFullYear(), cur.getMonth() + 1, 1);
guard++;
}
return arr;
}
const qtdMeses = { '1m': 1, '3m': 3, '6m': 6, '12m': 12 }[dashFiltros.periodo] ?? 6;
const d = new Date();
for (let i = qtdMeses - 1; i >= 0; i--) {
const dt = new Date(d.getFullYear(), d.getMonth() - i, 1);
arr.push({
label: dt.toLocaleDateString('pt-BR', { month: 'short' }).replace('.', ''),
comp: `${String(dt.getMonth() + 1).padStart(2, '0')}/${dt.getFullYear()}`
});
}
return arr;
}, [dashFiltros.periodo, dashFiltros.dataIni, dashFiltros.dataFim]);
const seriesData = useMemo_C(() => {
const allLancs = dashEmpresas.flatMap(e => data.lancamentos[e.id] || []);
const ent = meses.map(m => allLancs.filter(l => l.competencia === m.comp && l.tipo === 'entrada').reduce((s, l) => s + l.valor, 0));
const sai = meses.map(m => allLancs.filter(l => l.competencia === m.comp && l.tipo === 'saida').reduce((s, l) => s + l.valor, 0));
return [
{ name: 'Entradas', color: '#16a34a', points: ent },
{ name: 'Saídas', color: '#dc2626', points: sai },
];
}, [dashEmpresas, meses, data.lancamentos]);
return (
{/* Header */}
Central de Gestão
Visão Geral de Empresas
Gerencie suas {data.empresas.length} empresas em um só lugar
exportConsolidadoXLSX(data.empresas, data.lancamentos, data.portadores, data.centrosCusto)}>
Exportar Consolidado
onCreateEmpresa()}>Nova Empresa
{/* KPIs */}
s + (data.lancamentos[e.id] || []).filter(l => l.tipo === 'entrada' && !l.pago).length, 0)} lançamentos`} />
s + (data.lancamentos[e.id] || []).filter(l => l.tipo === 'saida' && !l.pago).length, 0)} lançamentos`} />
= 0 ? '#16a34a' : '#dc2626'} sub="Realizado (pago/recebido)" />
{/* Barra de filtros do dashboard */}
setDashFiltros({ ...dashFiltros, periodo: e.target.value })} style={{ minWidth: isMobile ? 0 : 140 }} options={[
{ value: "1m", label: "1 mês" },
{ value: "3m", label: "3 meses" },
{ value: "6m", label: "6 meses" },
{ value: "12m", label: "12 meses" },
{ value: "custom", label: "Personalizado" }
]} />
{dashFiltros.periodo === 'custom' && (
<>
setDashFiltros({ ...dashFiltros, dataIni: e.target.value })} />
setDashFiltros({ ...dashFiltros, dataFim: e.target.value })} />
>
)}
setDashFiltros({ ...dashFiltros, segmento: e.target.value })} style={{ minWidth: isMobile ? 0 : 140 }} options={[
{ value: "todos", label: "Todos" },
...segmentos.map(s => ({ value: s, label: s }))
]} />
setDashFiltros({ ...dashFiltros, status: e.target.value })} style={{ minWidth: isMobile ? 0 : 140 }} options={[
{ value: "todos", label: "Todos" },
{ value: "em-dia", label: "Em dia" },
{ value: "vencendo", label: "Vencendo" },
{ value: "vencido", label: "Vencido" }
]} />
{hasDashFilter && Limpar filtros }
{/* Charts row */}
Entradas vs Saídas
{meses.length} {meses.length === 1 ? 'mês' : 'meses'} · {dashFiltros.segmento === 'todos' ? 'todas as empresas' : dashFiltros.segmento}{dashFiltros.status !== 'todos' ? ` · ${statusColor(dashFiltros.status)?.label || dashFiltros.status}` : ''}
m.label)} height={220} />
Status das Empresas
Distribuição por situação financeira
{statusDist.map((s, i) => (
{s.label}
{s.value}
))}
{/* Alert de vencidos */}
{totalVencido > 0 && (
{kpiVencidas} empresa{kpiVencidas !== 1 && 's'} com {formatBRL(totalVencido)}
em pagamentos vencidos. Resolva o quanto antes.
setDashFiltros(f => ({ ...f, status: 'vencido' }))}>Ver vencidas
)}
{/* Toolbar de empresas */}
Empresas
setBusca(e.target.value)} placeholder="Buscar por nome ou CNPJ..."
style={{ border: 'none', outline: 'none', fontSize: 13, padding: '8px 4px', width: 240, fontFamily: 'inherit', background: 'transparent' }} />
setView('cards')} style={{
padding: '6px 10px', border: 'none', borderRadius: 6, cursor: 'pointer',
background: view === 'cards' ? 'var(--c-bg)' : 'transparent', color: 'var(--c-text)'
}}>
setView('tabela')} style={{
padding: '6px 10px', border: 'none', borderRadius: 6, cursor: 'pointer',
background: view === 'tabela' ? 'var(--c-bg)' : 'transparent', color: 'var(--c-text)'
}}>
{/* Grade de empresas */}
{view === 'cards' ? (
{filtradas.map(e => onOpenEmpresa(e.id)} onEdit={() => onEditEmpresa(e)} onDelete={() => setEmpresaExcluir(e)} />)}
) : (
Empresa
Documento
Segmento
Status
A Receber
A Pagar
Vencidos
Saldo
{filtradas.map(e => (
onOpenEmpresa(e.id)}>
{e.nome}
{e.tipoPessoa === 'pf' ? 'PF' : 'PJ'}
{e.documento || e.cnpj}
{e.segmento}
{formatBRL(e.stats.aReceber)}
{formatBRL(e.stats.aPagar)}
0 ? '#dc2626' : 'var(--c-text-muted)', fontWeight: e.stats.vencidos > 0 ? 600 : 400, fontVariantNumeric: 'tabular-nums' }}>{formatBRL(e.stats.vencidos)}
{formatBRL(e.stats.saldo)}
))}
)}
{filtradas.length === 0 && (
onCreateEmpresa()}>Nova Empresa
} />
)}
0
? `Esta empresa possui ${(data.lancamentos[empresaExcluir.id] || []).length} lançamento(s) que serão excluídos permanentemente. Deseja continuar com a exclusão de "${empresaExcluir.nome}"?`
: `Deseja realmente excluir a empresa "${empresaExcluir.nome}"? Esta ação não poderá ser desfeita.`
: ''
}
onConfirmar={async () => {
if (empresaExcluir) {
await onDeleteEmpresa(empresaExcluir.id);
setEmpresaExcluir(null);
}
}}
onCancelar={() => setEmpresaExcluir(null)}
/>
);
}
const th = { padding: '12px 16px', textAlign: 'left', fontSize: 11, fontWeight: 600, color: 'var(--c-text-muted)', textTransform: 'uppercase', letterSpacing: '0.04em' };
const td = { padding: '12px 16px' };
function EmpresaCard({ empresa, onOpen, onEdit, onDelete }) {
const s = empresa.stats;
const c = statusColor(s.statusEmpresa);
const initial = empresa.nome.charAt(0).toUpperCase();
const [menuOpen, setMenuOpen] = React.useState(false);
return (
{ e.currentTarget.style.borderColor = 'var(--c-primary)'; e.currentTarget.style.transform = 'translateY(-1px)'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--c-border)'; e.currentTarget.style.transform = 'none'; setMenuOpen(false); }}
>
{/* status bar */}
{initial}
{empresa.nome}
{empresa.tipoPessoa === 'pf' ? 'PF' : 'PJ'}
{empresa.documento || empresa.cnpj}
{ e.stopPropagation(); setMenuOpen(!menuOpen); }} style={{
background: 'none', border: 'none', padding: 4, cursor: 'pointer', color: 'var(--c-text-muted)', borderRadius: 6
}}>
{menuOpen && (
{ e.stopPropagation(); setMenuOpen(false); onOpen(); }} style={{
background: 'none', border: 'none', padding: '8px 12px', fontSize: 13, cursor: 'pointer',
textAlign: 'left', borderRadius: 6, display: 'flex', alignItems: 'center', gap: 6, color: 'var(--c-text)',
fontFamily: 'inherit'
}}>
Ver
{ e.stopPropagation(); setMenuOpen(false); onEdit(); }} style={{
background: 'none', border: 'none', padding: '8px 12px', fontSize: 13, cursor: 'pointer',
textAlign: 'left', borderRadius: 6, display: 'flex', alignItems: 'center', gap: 6, color: 'var(--c-text)',
fontFamily: 'inherit'
}}>
Editar
{ e.stopPropagation(); setMenuOpen(false); onDelete(); }} style={{
background: 'none', border: 'none', padding: '8px 12px', fontSize: 13, cursor: 'pointer',
textAlign: 'left', borderRadius: 6, display: 'flex', alignItems: 'center', gap: 6, color: '#dc2626',
fontFamily: 'inherit'
}}>
Excluir
)}
{empresa.segmento}
A Receber
{formatBRL(s.aReceber)}
A Pagar
{formatBRL(s.aPagar)}
{s.cntVencido > 0 && (
{s.cntVencido} pendência{s.cntVencido > 1 ? 's' : ''} vencida{s.cntVencido > 1 ? 's' : ''} · {formatBRL(s.vencidos)}
)}
{empresa.qtdLanc} lançamentos
Abrir
);
}
Object.assign(window, { CentralGestao });