"use client"; import { LoaderPinwheel, ChevronDown, ChevronRight, Filter, Maximize2, Minimize2, Download } from "lucide-react"; import React, { useEffect, useState, useCallback, startTransition, memo } from "react"; import AnaliticoComponent from "./analitico"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import * as XLSX from "xlsx"; import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; interface DREItem { codfilial: string; data_competencia: string; data_cai: string; grupo: string; subgrupo: string; centro_custo: string; codigo_centro_custo: string; codigo_conta: number; conta: string; valor: string; codgrupo?: string; isCalculado?: boolean; entidades?: string; } interface HierarchicalRow { type: "grupo" | "subgrupo" | "centro_custo" | "conta"; level: number; grupo?: string; subgrupo?: string; centro_custo?: string; codigo_centro_custo?: string; conta?: string; codigo_conta?: number; total?: number; isExpanded?: boolean; valoresPorMes?: Record; percentuaisPorMes?: Record; percentualTotal?: number; isCalculado?: boolean; entidades?: string; } // Componente memoizado para linhas da tabela const TableRow = memo(({ row, index, toggleGroup, toggleCentro, handleRowClick, getRowStyle, getIndentStyle, renderCellContent, mesesDisponiveis, formatCurrency, formatCurrencyWithColor }: { row: HierarchicalRow; index: number; toggleGroup: (grupo: string) => void; toggleCentro: (centro: string) => void; handleRowClick: (row: HierarchicalRow, mes?: string) => void; getRowStyle: (row: HierarchicalRow) => string; getIndentStyle: (level: number) => React.CSSProperties; renderCellContent: (row: HierarchicalRow) => React.ReactNode; mesesDisponiveis: string[]; formatCurrency: (value: number) => string; formatCurrencyWithColor: (value: number) => { formatted: string; isNegative: boolean }; }) => { return (
{renderCellContent(row)}
{/* Colunas de valores por mês */} {mesesDisponiveis.map((mes) => ( handleRowClick(row, mes)} title={ row.valoresPorMes && row.valoresPorMes[mes] ? formatCurrency(row.valoresPorMes[mes]) : "-" } > {row.valoresPorMes && row.valoresPorMes[mes] ? (() => { const { formatted, isNegative } = formatCurrencyWithColor(row.valoresPorMes[mes]); return ( {formatted} ); })() : "-"} handleRowClick(row, mes)} title={ row.percentuaisPorMes && row.percentuaisPorMes[mes] !== undefined ? `${row.percentuaisPorMes[mes].toFixed(1)}%` : "-" } > {row.percentuaisPorMes && row.percentuaisPorMes[mes] !== undefined ? `${row.percentuaisPorMes[mes].toFixed(1)}%` : "-"} ))} {/* Coluna Total */} handleRowClick(row)} title={row.total ? formatCurrency(row.total) : "-"} > {(() => { const { formatted, isNegative } = formatCurrencyWithColor( row.total! ); return ( {formatted} ); })()} {/* Coluna Percentual Total */} handleRowClick(row)} title={ row.percentualTotal !== undefined ? `${row.percentualTotal.toFixed(1)}%` : "-" } > {row.percentualTotal !== undefined ? `${row.percentualTotal.toFixed(1)}%` : "-"} ); }); TableRow.displayName = 'TableRow'; export default function Teste() { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [expandedGroups, setExpandedGroups] = useState>(new Set()); const [expandedCentros, setExpandedCentros] = useState>( new Set() ); const [mesesDisponiveis, setMesesDisponiveis] = useState([]); // Estados para filtros const [filtros, setFiltros] = useState({ periodoDe: "", periodoAte: "", grupo: "Todos", subgrupo: "Todos", centroCusto: "Todos", conta: "Todas", valorMin: "", valorMax: "", buscaTextual: "" }); // Estados para multi-seleção const [centrosCustoSelecionados, setCentrosCustoSelecionados] = useState([]); // Estado para armazenar os códigos dos centros de custo const [codigosCentrosCusto, setCodigosCentrosCusto] = useState>({}); const [contasSelecionadas, setContasSelecionadas] = useState([]); // Estado para armazenar os códigos das contas const [codigosContas, setCodigosContas] = useState>({}); const [entidadesSelecionadas, setEntidadesSelecionadas] = useState([]); const [isFilterOpen, setIsFilterOpen] = useState(false); const [dadosFiltrados, setDadosFiltrados] = useState([]); const [filtrosAplicados, setFiltrosAplicados] = useState(false); const [ordemHierarquiaContasPrimeiro, setOrdemHierarquiaContasPrimeiro] = useState(true); // Estados para opções dos filtros const [opcoesGrupos, setOpcoesGrupos] = useState([]); const [opcoesSubgrupos, setOpcoesSubgrupos] = useState([]); const [opcoesCentrosCusto, setOpcoesCentrosCusto] = useState([]); const [opcoesContas, setOpcoesContas] = useState([]); const [opcoesEntidades, setOpcoesEntidades] = useState([]); // Estados para filtros de busca nos campos de seleção const [filtroCentroCusto, setFiltroCentroCusto] = useState(""); const [filtroConta, setFiltroConta] = useState(""); const [filtroEntidade, setFiltroEntidade] = useState(""); // Estados para analítico const [analiticoFiltros, setAnaliticoFiltros] = useState({ dataInicio: "", dataFim: "", centroCusto: "", codigoGrupo: "", codigoSubgrupo: "", codigoConta: "", linhaSelecionada: "", // Adicionar informação da linha selecionada excluirCentroCusto: "", // Para excluir centro de custo específico quando desmarcado excluirCodigoConta: "", // Para excluir código de conta específico quando desmarcado codigosCentrosCustoSelecionados: "", // Códigos dos centros de custo selecionados no filtro codigosContasSelecionadas: "", // Códigos das contas selecionadas no filtro }); const [linhaSelecionada, setLinhaSelecionada] = useState(null); const [isAllExpanded, setIsAllExpanded] = useState(false); // Refs para sincronizar scroll vertical entre coluna fixa e valores const descricaoScrollRef = React.useRef(null); const valoresScrollRef = React.useRef(null); // Função para sincronizar scroll vertical const syncScroll = (source: 'descricao' | 'valores') => { if (source === 'descricao' && descricaoScrollRef.current && valoresScrollRef.current) { valoresScrollRef.current.scrollTop = descricaoScrollRef.current.scrollTop; } else if (source === 'valores' && descricaoScrollRef.current && valoresScrollRef.current) { descricaoScrollRef.current.scrollTop = valoresScrollRef.current.scrollTop; } }; useEffect(() => { // Carregar períodos disponíveis da API carregarPeriodosDisponiveis(); // Inicializar filtros com período atual const agora = new Date(); const anoAtual = agora.getFullYear(); const mesAtual = String(agora.getMonth() + 1).padStart(2, '0'); const periodoAtual = `${anoAtual}-${mesAtual}`; setFiltros(prev => ({ ...prev, periodoDe: `${anoAtual}-01`, periodoAte: periodoAtual })); }, []); const carregarPeriodosDisponiveis = async () => { try { const response = await fetch("/api/dre-oracle"); if (!response.ok) { throw new Error(`Erro HTTP: ${response.status}`); } const dadosCompletos = await response.json(); // Extrair períodos únicos dos dados const periodosUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.data_competencia))].sort() as string[]; setMesesDisponiveis(periodosUnicos); // Extrair grupos únicos const gruposUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.grupo))].sort() as string[]; setOpcoesGrupos(gruposUnicos); // Extrair subgrupos únicos const subgruposUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.subgrupo))].sort() as string[]; setOpcoesSubgrupos(subgruposUnicos); // Extrair centros de custo únicos com nome e código const centrosCustoUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.centro_custo))].sort() as string[]; setOpcoesCentrosCusto(centrosCustoUnicos); // Criar objeto de códigos dos centros de custo // Usar um Map para garantir que pegamos o código correto mesmo com duplicatas const codigos: Record = {}; const codigosPorNome = new Map>(); dadosCompletos.forEach((item: DREItem) => { if (item.centro_custo && item.codigo_centro_custo) { if (!codigosPorNome.has(item.centro_custo)) { codigosPorNome.set(item.centro_custo, new Set()); } codigosPorNome.get(item.centro_custo)!.add(item.codigo_centro_custo); } }); // Para cada centro de custo, usar o código mais comum ou o primeiro encontrado codigosPorNome.forEach((codigosSet, nome) => { const codigosArray = Array.from(codigosSet); // Se houver apenas um código, usar esse if (codigosArray.length === 1) { codigos[nome] = codigosArray[0]; } else { // Se houver múltiplos códigos, verificar qual é mais frequente nos dados const frequencia: Record = {}; dadosCompletos.forEach((item: DREItem) => { if (item.centro_custo === nome && item.codigo_centro_custo) { frequencia[item.codigo_centro_custo] = (frequencia[item.codigo_centro_custo] || 0) + 1; } }); // Pegar o código mais frequente const codigoMaisFrequente = Object.entries(frequencia).sort((a, b) => b[1] - a[1])[0]; codigos[nome] = codigoMaisFrequente ? codigoMaisFrequente[0] : codigosArray[0]; } }); console.log('🗺️ Mapeamento de códigos de centros de custo criado:', codigos); setCodigosCentrosCusto(codigos); // Extrair contas únicas const contasUnicas = [...new Set(dadosCompletos.map((item: DREItem) => item.conta))].sort() as string[]; setOpcoesContas(contasUnicas); // Criar objeto de códigos das contas const codigosContasObj: Record = {}; dadosCompletos.forEach((item: DREItem) => { if (item.conta && item.codigo_conta) { codigosContasObj[item.conta] = item.codigo_conta.toString(); } }); setCodigosContas(codigosContasObj); // Extrair entidades únicas const entidadesUnicas = [...new Set(dadosCompletos.map((item: DREItem) => item.entidades).filter(Boolean))].sort() as string[]; console.log('🏢 Entidades únicas encontradas:', entidadesUnicas); console.log('📊 Total de entidades:', entidadesUnicas.length); setOpcoesEntidades(entidadesUnicas); // Inicializar com todos os itens selecionados, exceto o centro de custo 002.003.017 e conta 100050 const centrosCustoIniciaisSelecionados = centrosCustoUnicos.filter(centro => { const item = dadosCompletos.find((d: DREItem) => d.centro_custo === centro); return item?.codigo_centro_custo !== "002.003.017"; }); const contasIniciaisSelecionadas = contasUnicas.filter(conta => { const item = dadosCompletos.find((d: DREItem) => d.conta === conta); return item?.codigo_conta?.toString() !== "100050"; }); setCentrosCustoSelecionados(centrosCustoIniciaisSelecionados); setContasSelecionadas(contasIniciaisSelecionadas); // Inicializar com todas as entidades selecionadas setEntidadesSelecionadas(entidadesUnicas); } catch (error) { console.error("Erro ao carregar períodos:", error); } }; const fetchData = async () => { try { setLoading(true); setError(null); const response = await fetch("/api/dre-oracle"); if (!response.ok) { throw new Error(`Erro ao carregar dados: ${response.status}`); } const result = await response.json(); setData(result); // Extrair meses únicos dos dados const meses = [ ...new Set( result.map((item: DREItem) => { // Usar diretamente o valor de data_competencia que já vem no formato YYYY-MM return item.data_competencia; }) ), ].sort() as string[]; setMesesDisponiveis(meses); } catch (err) { setError(err instanceof Error ? err.message : "Erro desconhecido"); } finally { setLoading(false); } }; const formatCurrency = (value: string | number) => { const numValue = typeof value === "string" ? parseFloat(value) : value; return numValue.toLocaleString("pt-BR", { style: "currency", currency: "BRL", }); }; const formatCurrencyWithColor = (value: string | number) => { const numValue = typeof value === "string" ? parseFloat(value) : value; const formatted = formatCurrency(value); const isNegative = numValue < 0; return { formatted, isNegative }; }; // Função para extrair códigos dos grupos e subgrupos const extractCodes = (grupo: string, subgrupo?: string) => { const grupoMatch = grupo.match(/^(\d+)/); const codigoGrupo = grupoMatch ? grupoMatch[1] : ""; let codigoSubgrupo = ""; if (subgrupo) { // Primeiro tenta extrair código numérico (ex: "001.008 - LOJA 8" -> "001.008") const subgrupoMatch = subgrupo.match(/^(\d+(?:\.\d+)+)/); if (subgrupoMatch) { codigoSubgrupo = subgrupoMatch[1]; } else { // Se não tem código numérico, usa a descrição completa (ex: "DESPESAS ADMINISTRATIVAS") codigoSubgrupo = subgrupo; } } return { codigoGrupo, codigoSubgrupo }; }; // Função para lidar com clique nas linhas const handleRowClick = (row: HierarchicalRow, mesSelecionado?: string) => { console.log('🖱️ Clique na linha:', row); console.log('📅 Mês selecionado:', mesSelecionado); if (!data.length) { console.log('⚠️ Sem dados disponíveis'); return; } // Pegar todas as datas disponíveis para definir o período const datas = data.map((item) => item.data_competencia); const dataInicio = Math.min(...datas.map((d) => new Date(d).getTime())); const dataFim = Math.max(...datas.map((d) => new Date(d).getTime())); const dataInicioStr = new Date(dataInicio).toISOString().substring(0, 7); // YYYY-MM const dataFimStr = new Date(dataFim).toISOString().substring(0, 7); // YYYY-MM console.log('📅 Datas calculadas:', { dataInicioStr, dataFimStr }); const { codigoGrupo, codigoSubgrupo } = extractCodes( row.grupo || "", row.subgrupo ); console.log('🔍 Códigos extraídos:', { codigoGrupo, codigoSubgrupo }); // Criar um identificador único para a linha const linhaId = `${row.type}-${row.grupo || ""}-${row.subgrupo || ""}-${ row.centro_custo || "" }-${row.codigo_conta || ""}`; setLinhaSelecionada(linhaId); // Se um mês específico foi selecionado, usar apenas esse mês const dataInicioFiltro = mesSelecionado || dataInicioStr; const dataFimFiltro = mesSelecionado || dataFimStr; // Determinar filtros baseado na ordem hierárquica e tipo da linha let centroCustoFiltro = ""; let codigoContaFiltro = ""; if (ordemHierarquiaContasPrimeiro) { // Ordem: Contas → Centros de Custo // SEMPRE filtrar por código da conta, independente do tipo da linha codigoContaFiltro = row.codigo_conta?.toString() || ""; // Se for centro de custo, também filtrar por código do centro de custo centroCustoFiltro = row.type === "centro_custo" ? (row.codigo_centro_custo || "") : ""; } else { // Ordem: Centros de Custo → Contas // SEMPRE filtrar por código do centro de custo, independente do tipo da linha centroCustoFiltro = row.codigo_centro_custo || ""; // Se for conta, também filtrar por código da conta codigoContaFiltro = row.type === "conta" ? (row.codigo_conta?.toString() || "") : ""; } console.log('🎯 Filtros determinados:', { centroCustoFiltro, codigoContaFiltro, ordemHierarquia: ordemHierarquiaContasPrimeiro ? 'Contas→Centros' : 'Centros→Contas', tipoLinha: row.type, rowData: { codigo_conta: row.codigo_conta, codigo_centro_custo: row.codigo_centro_custo, centro_custo: row.centro_custo, conta: row.conta } }); // Determinar exclusões baseado nos filtros aplicados let excluirCentroCusto = ""; let excluirCodigoConta = ""; // Se o centro de custo "002.003.017" não está selecionado, excluir da consulta const centroCusto002003017Selecionado = centrosCustoSelecionados.some(centro => { // Verificar pelo código no mapeamento primeiro const codigoCentro = codigosCentrosCusto[centro]; if (codigoCentro === "002.003.017") { return true; } // Se não encontrar no mapeamento, buscar nos dados const item = data.find((d: DREItem) => d.centro_custo === centro); return item?.codigo_centro_custo === "002.003.017"; }); if (!centroCusto002003017Selecionado) { excluirCentroCusto = "002.003.017"; } // Se a conta "100050" não está selecionada, excluir da consulta const conta100050Selecionada = contasSelecionadas.some(conta => { // Verificar pelo código no mapeamento primeiro const codigoConta = codigosContas[conta]; if (codigoConta === "100050") { return true; } // Se não encontrar no mapeamento, buscar nos dados const item = data.find((d: DREItem) => d.conta === conta); return item?.codigo_conta?.toString() === "100050"; }); if (!conta100050Selecionada) { excluirCodigoConta = "100050"; } // Obter códigos dos centros de custo selecionados no filtro - APENAS CÓDIGOS const codigosCentrosCustoSelecionados = centrosCustoSelecionados .map(centro => { // Primeiro tentar buscar no objeto codigosCentrosCusto (mapeamento) const codigoDoMapeamento = codigosCentrosCusto[centro]; if (codigoDoMapeamento) { console.log(`🔍 Código encontrado no mapeamento para "${centro}": ${codigoDoMapeamento}`); return codigoDoMapeamento; } // Se não encontrar no mapeamento, buscar nos dados const item = data.find((d: DREItem) => d.centro_custo === centro); const codigoEncontrado = item?.codigo_centro_custo; if (codigoEncontrado) { console.log(`🔍 Código encontrado nos dados para "${centro}": ${codigoEncontrado}`); } else { console.warn(`⚠️ Código NÃO encontrado para centro de custo "${centro}"`); } return codigoEncontrado; }) .filter(codigo => codigo && codigo.trim() !== '') // Remover undefined e strings vazias .join(','); console.log('📋 Códigos de centros de custo selecionados:', codigosCentrosCustoSelecionados); // Obter códigos das contas selecionadas no filtro const codigosContasSelecionadas = contasSelecionadas .map(conta => { const item = data.find((d: DREItem) => d.conta === conta); return item?.codigo_conta?.toString(); }) .filter(codigo => codigo) // Remover undefined .join(','); const novosFiltros = { dataInicio: dataInicioFiltro, dataFim: dataFimFiltro, centroCusto: centroCustoFiltro, codigoGrupo, codigoSubgrupo, codigoConta: codigoContaFiltro, linhaSelecionada: row.grupo || row.subgrupo || row.centro_custo || row.conta || "", // Incluir informação da linha selecionada excluirCentroCusto, excluirCodigoConta, codigosCentrosCustoSelecionados, codigosContasSelecionadas, }; console.log('🎯 Novos filtros para analítico:', novosFiltros); console.log('🔍 Verificação final dos filtros:', { centroCusto: novosFiltros.centroCusto, codigoConta: novosFiltros.codigoConta, codigoContaType: typeof novosFiltros.codigoConta, codigoContaLength: novosFiltros.codigoConta?.length, codigosCentrosCustoSelecionados: novosFiltros.codigosCentrosCustoSelecionados, codigosContasSelecionadas: novosFiltros.codigosContasSelecionadas }); setAnaliticoFiltros(novosFiltros); }; const toggleGroup = useCallback((grupo: string) => { setExpandedGroups(prev => { const newExpanded = new Set(prev); if (newExpanded.has(grupo)) { newExpanded.delete(grupo); } else { newExpanded.add(grupo); } return newExpanded; }); }, []); const toggleCentro = useCallback((centro: string) => { setExpandedCentros(prev => { const newExpanded = new Set(prev); if (newExpanded.has(centro)) { newExpanded.delete(centro); } else { newExpanded.add(centro); } return newExpanded; }); }, []); const handleFiltroChange = (campo: string, valor: string) => { setFiltros(prev => ({ ...prev, [campo]: valor })); }; // Funções para multi-seleção const toggleCentroCusto = (centro: string) => { setCentrosCustoSelecionados(prev => { if (prev.includes(centro)) { return prev.filter(c => c !== centro); } else { return [...prev, centro]; } }); }; const toggleConta = (conta: string) => { setContasSelecionadas(prev => { if (prev.includes(conta)) { return prev.filter(c => c !== conta); } else { return [...prev, conta]; } }); }; const selecionarTodosCentros = () => { setCentrosCustoSelecionados(opcoesCentrosCusto); }; const limparCentros = () => { setCentrosCustoSelecionados([]); }; const selecionarTodasContas = () => { setContasSelecionadas(opcoesContas); }; const limparContas = () => { setContasSelecionadas([]); }; const toggleEntidade = (entidade: string) => { setEntidadesSelecionadas(prev => { if (prev.includes(entidade)) { return prev.filter(e => e !== entidade); } else { return [...prev, entidade]; } }); }; const selecionarTodasEntidades = () => { setEntidadesSelecionadas(opcoesEntidades); }; const limparEntidades = () => { setEntidadesSelecionadas([]); }; // Função auxiliar para obter o código do centro de custo const obterCodigoCentroCusto = React.useCallback((nomeCentro: string): string => { if (!data || data.length === 0) { return ''; } // Buscar o primeiro item que corresponde ao nome do centro de custo const item = data.find(item => item.centro_custo === nomeCentro); if (item && item.codigo_centro_custo) { return item.codigo_centro_custo; } return ''; }, [data]); const exportarXLSX = () => { if (!data.length) { console.log('⚠️ Nenhum dado para exportar'); return; } console.log('📊 Exportando TODOS os dados expandidos para XLSX...'); // Criar uma versão completamente expandida dos dados hierárquicos const dadosCompletosExpandidos = buildHierarchicalDataCompleta(); // Preparar dados para exportação const dadosExportacao = dadosCompletosExpandidos.map((row, index) => { const linha: any = { 'Linha': index + 1, 'Tipo': row.type, 'Nível': row.level, 'Grupo': row.grupo || '', 'Centro de Custo': row.centro_custo || '', 'Conta': row.conta || '', 'Código Centro': row.codigo_centro_custo || '', 'Código Conta': row.codigo_conta || '', 'Entidade': row.entidades || '', 'Total': row.total || 0, }; // Adicionar colunas dos meses mesesDisponiveis.forEach(mes => { const valor = row.valoresPorMes?.[mes] || 0; const percentual = row.percentuaisPorMes?.[mes] || 0; linha[`Valor ${mes}`] = valor; linha[`% ${mes}`] = percentual; }); return linha; }); // Criar workbook const wb = XLSX.utils.book_new(); // Criar worksheet principal const ws = XLSX.utils.json_to_sheet(dadosExportacao); // Ajustar largura das colunas const colWidths = [ { wch: 8 }, // Linha { wch: 15 }, // Tipo { wch: 8 }, // Nível { wch: 30 }, // Grupo { wch: 25 }, // Centro de Custo { wch: 35 }, // Conta { wch: 15 }, // Código Centro { wch: 12 }, // Código Conta { wch: 20 }, // Entidade { wch: 15 }, // Total ]; // Adicionar larguras para colunas dos meses mesesDisponiveis.forEach(() => { colWidths.push({ wch: 15 }); // Valor colWidths.push({ wch: 10 }); // % }); ws['!cols'] = colWidths; // Adicionar worksheet ao workbook XLSX.utils.book_append_sheet(wb, ws, 'DRE Gerencial Completo'); // Criar worksheet de resumo const resumoData = [ { 'Informação': 'Período', 'Valor': `${filtros.periodoDe} a ${filtros.periodoAte}` }, { 'Informação': 'Grupo', 'Valor': filtros.grupo }, { 'Informação': 'Subgrupo', 'Valor': filtros.subgrupo }, { 'Informação': 'Centro de Custo', 'Valor': filtros.centroCusto }, { 'Informação': 'Conta', 'Valor': filtros.conta }, { 'Informação': 'Valor Mínimo', 'Valor': filtros.valorMin || 'N/A' }, { 'Informação': 'Valor Máximo', 'Valor': filtros.valorMax || 'N/A' }, { 'Informação': 'Busca Textual', 'Valor': filtros.buscaTextual || 'N/A' }, { 'Informação': 'Ordem Hierárquica', 'Valor': ordemHierarquiaContasPrimeiro ? 'Contas → Centros' : 'Centros → Contas' }, { 'Informação': 'Total de Registros', 'Valor': dadosCompletosExpandidos.length }, { 'Informação': 'Data de Exportação', 'Valor': new Date().toLocaleString('pt-BR') }, ]; const wsResumo = XLSX.utils.json_to_sheet(resumoData); wsResumo['!cols'] = [{ wch: 20 }, { wch: 30 }]; XLSX.utils.book_append_sheet(wb, wsResumo, 'Resumo'); // Gerar nome do arquivo const dataAtual = new Date().toISOString().split('T')[0]; const nomeArquivo = `DRE_Gerencial_Completo_${dataAtual}.xlsx`; // Exportar arquivo XLSX.writeFile(wb, nomeArquivo); console.log('✅ Arquivo XLSX completo exportado:', nomeArquivo); }; // Função para construir dados hierárquicos completamente expandidos const buildHierarchicalDataCompleta = (): HierarchicalRow[] => { const rows: HierarchicalRow[] = []; // Agrupar dados por grupo const grupos = data.reduce((acc, item) => { if (!acc[item.grupo]) { acc[item.grupo] = []; } acc[item.grupo].push(item); return acc; }, {} as Record); // Ordenar grupos por código const sortedGrupos = Object.entries(grupos).sort(([grupoA, itemsA], [grupoB, itemsB]) => { const codigoA = itemsA[0]?.codgrupo || ""; const codigoB = itemsB[0]?.codgrupo || ""; return codigoA.localeCompare(codigoB); }); // Função para calcular valores de grupos calculados usando códigos dos grupos (mesma lógica da função principal) const calcularGrupoCalculadoCompleta = (codigoGrupo: string, gruposData: Record): Record => { const valoresPorMes: Record = {}; // Inicializar valores para todos os meses disponíveis mesesDisponiveis.forEach(mes => { valoresPorMes[mes] = 0; }); // Função auxiliar para obter valor de um grupo por código e mês const obterValorGrupo = (codigoGrupo: string, mes: string): number => { const grupoEncontrado = Object.values(gruposData).find(items => items.length > 0 && items[0].codgrupo === codigoGrupo ); if (!grupoEncontrado) return 0; const itemsMes = grupoEncontrado.filter(item => item.data_competencia === mes); return itemsMes.reduce((sum, item) => sum + parseFloat(item.valor), 0); }; switch (codigoGrupo) { case "03": // Faturamento Líquido = Grupo 01 + 02 mesesDisponiveis.forEach(mes => { const valor01 = obterValorGrupo("01", mes); const valor02 = obterValorGrupo("02", mes); valoresPorMes[mes] = valor01 + valor02; }); break; case "05": // Lucro Bruto = Grupo 03 + 04 mesesDisponiveis.forEach(mes => { const valor03 = obterValorGrupo("03", mes); const valor04 = obterValorGrupo("04", mes); valoresPorMes[mes] = valor03 + valor04; }); break; case "07": // Margem Loja = Grupo 05 + 06 mesesDisponiveis.forEach(mes => { const valor05 = obterValorGrupo("05", mes); const valor06 = obterValorGrupo("06", mes); valoresPorMes[mes] = valor05 + valor06; }); break; case "10": // Resultado Operacional = Grupo 07 + 08 + 09 mesesDisponiveis.forEach(mes => { const valor07 = obterValorGrupo("07", mes); const valor08 = obterValorGrupo("08", mes); const valor09 = obterValorGrupo("09", mes); valoresPorMes[mes] = valor07 + valor08 + valor09; }); break; case "13": // Resultado Financeiro = Grupo 11 + 12 mesesDisponiveis.forEach(mes => { const valor11 = obterValorGrupo("11", mes); const valor12 = obterValorGrupo("12", mes); valoresPorMes[mes] = valor11 + valor12; }); break; case "19": // Resultado Não Operacional = Grupo 14 + 15 + 16 + 17 + 18 mesesDisponiveis.forEach(mes => { const valor14 = obterValorGrupo("14", mes); const valor15 = obterValorGrupo("15", mes); const valor16 = obterValorGrupo("16", mes); const valor17 = obterValorGrupo("17", mes); const valor18 = obterValorGrupo("18", mes); valoresPorMes[mes] = valor14 + valor15 + valor16 + valor17 + valor18; }); break; case "20": // LAIR = Grupo 10 + 13 + 19 mesesDisponiveis.forEach(mes => { const valor10 = obterValorGrupo("10", mes); const valor13 = obterValorGrupo("13", mes); const valor19 = obterValorGrupo("19", mes); valoresPorMes[mes] = valor10 + valor13 + valor19; }); break; case "21": // IR = Se LAIR > 0 calcular 20% e resultado negativo (*-1), se não 0 mesesDisponiveis.forEach(mes => { const valor20 = obterValorGrupo("20", mes); if (valor20 > 0) { valoresPorMes[mes] = (valor20 * 0.20) * -1; } else { valoresPorMes[mes] = 0; } }); break; case "22": // CSLL = Se LAIR > 0 calcular 9% e resultado negativo (*-1), se não 0 mesesDisponiveis.forEach(mes => { const valor20 = obterValorGrupo("20", mes); if (valor20 > 0) { valoresPorMes[mes] = (valor20 * 0.09) * -1; } else { valoresPorMes[mes] = 0; } }); break; case "23": // Lucro Líquido = Grupo 20 + 21 + 22 mesesDisponiveis.forEach(mes => { const valor20 = obterValorGrupo("20", mes); const valor21 = obterValorGrupo("21", mes); const valor22 = obterValorGrupo("22", mes); valoresPorMes[mes] = valor20 + valor21 + valor22; }); break; case "25": // EBITDA = Grupo 20 - (13 + 19 + 24) mesesDisponiveis.forEach(mes => { const valor20 = obterValorGrupo("20", mes); const valor13 = obterValorGrupo("13", mes); const valor19 = obterValorGrupo("19", mes); const valor24 = obterValorGrupo("24", mes); valoresPorMes[mes] = valor20 - (valor13 + valor19 + valor24); }); break; } return valoresPorMes; }; sortedGrupos.forEach(([grupo, items]) => { const totalGrupo = items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Verificar se é um grupo calculado const codigoGrupo = items[0]?.codgrupo || ""; const isCalculado = ["03", "05", "07", "10", "13", "19", "20", "21", "22", "23", "25"].includes(codigoGrupo); let valoresGrupoPorMes; if (isCalculado) { // Usar cálculo específico para grupos calculados valoresGrupoPorMes = calcularGrupoCalculadoCompleta(codigoGrupo, grupos); } else { // Usar cálculo normal para grupos não calculados valoresGrupoPorMes = calcularValoresPorMes(items); } // Calcular total do grupo const totalCalculado = Object.values(valoresGrupoPorMes).reduce((sum, valor) => sum + valor, 0); const totalFinal = isCalculado ? totalCalculado : totalGrupo; // Linha do grupo (Level 0) rows.push({ type: "grupo", level: 0, grupo, total: totalFinal, valoresPorMes: valoresGrupoPorMes, percentuaisPorMes: calcularPercentuaisPorMes(valoresGrupoPorMes, grupo), percentualTotal: calcularPercentualTotal(totalFinal, grupo), isCalculado: isCalculado, }); if (ordemHierarquiaContasPrimeiro) { // ORDEM: Grupos → Contas → Centros de Custo // Agrupar por conta dentro do grupo const contas = items.reduce((acc, item) => { if (!acc[item.conta]) { acc[item.conta] = []; } acc[item.conta].push(item); return acc; }, {} as Record); // Ordenar contas por CODCONTA const sortedContas = Object.entries(contas).sort(([contaA, itemsA], [contaB, itemsB]) => { const codigoA = itemsA[0]?.codigo_conta || 0; const codigoB = itemsB[0]?.codigo_conta || 0; return codigoA - codigoB; }); sortedContas.forEach(([conta, contaItems]) => { const totalConta = contaItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha da conta (Level 1) const valoresContaPorMes = calcularValoresPorMes(contaItems); rows.push({ type: "conta", level: 1, grupo, conta, codigo_conta: contaItems[0].codigo_conta, codigo_centro_custo: contaItems[0].codigo_centro_custo, total: totalConta, valoresPorMes: valoresContaPorMes, percentuaisPorMes: calcularPercentuaisPorMes(valoresContaPorMes, grupo), percentualTotal: calcularPercentualTotal(totalConta, grupo), entidades: contaItems[0]?.entidades || "", }); // Agrupar por centro de custo dentro da conta const centros = contaItems.reduce((acc, item) => { if (!acc[item.centro_custo]) { acc[item.centro_custo] = []; } acc[item.centro_custo].push(item); return acc; }, {} as Record); // Ordenar centros de custo por CODIGOCENTROCUSTO const sortedCentros = Object.entries(centros).sort(([centroA, itemsA], [centroB, itemsB]) => { const codigoA = itemsA[0]?.codigo_centro_custo || ""; const codigoB = itemsB[0]?.codigo_centro_custo || ""; return codigoA.localeCompare(codigoB); }); sortedCentros.forEach(([centro, centroItems]) => { const totalCentro = centroItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do centro de custo (Level 2) const valoresCentroPorMes = calcularValoresPorMes(centroItems); rows.push({ type: "centro_custo", level: 2, grupo, centro_custo: centro, conta, codigo_conta: contaItems[0].codigo_conta, codigo_centro_custo: centroItems[0].codigo_centro_custo, total: totalCentro, valoresPorMes: valoresCentroPorMes, percentuaisPorMes: calcularPercentuaisPorMes(valoresCentroPorMes, grupo), percentualTotal: calcularPercentualTotal(totalCentro, grupo), entidades: centroItems[0]?.entidades || "", }); }); }); } else { // ORDEM ORIGINAL: Grupos → Centros de Custo → Contas // Agrupar por centro de custo dentro do grupo const centros = items.reduce((acc, item) => { if (!acc[item.centro_custo]) { acc[item.centro_custo] = []; } acc[item.centro_custo].push(item); return acc; }, {} as Record); // Ordenar centros de custo por CODIGOCENTROCUSTO const sortedCentros = Object.entries(centros).sort(([centroA, itemsA], [centroB, itemsB]) => { const codigoA = itemsA[0]?.codigo_centro_custo || ""; const codigoB = itemsB[0]?.codigo_centro_custo || ""; return codigoA.localeCompare(codigoB); }); sortedCentros.forEach(([centro, centroItems]) => { const totalCentro = centroItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do centro de custo (Level 1) const valoresCentroPorMes = calcularValoresPorMes(centroItems); rows.push({ type: "centro_custo", level: 1, grupo, centro_custo: centro, codigo_conta: centroItems[0].codigo_conta, codigo_centro_custo: centroItems[0].codigo_centro_custo, total: totalCentro, valoresPorMes: valoresCentroPorMes, percentuaisPorMes: calcularPercentuaisPorMes(valoresCentroPorMes, grupo), percentualTotal: calcularPercentualTotal(totalCentro, grupo), entidades: centroItems[0]?.entidades || "", }); // Agrupar por conta dentro do centro de custo const contas = centroItems.reduce((acc, item) => { if (!acc[item.conta]) { acc[item.conta] = []; } acc[item.conta].push(item); return acc; }, {} as Record); // Ordenar contas por CODCONTA const sortedContas = Object.entries(contas).sort(([contaA, itemsA], [contaB, itemsB]) => { const codigoA = itemsA[0]?.codigo_conta || 0; const codigoB = itemsB[0]?.codigo_conta || 0; return codigoA - codigoB; }); sortedContas.forEach(([conta, contaItems]) => { const totalConta = contaItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha da conta (Level 2) const valoresContaPorMes = calcularValoresPorMes(contaItems); rows.push({ type: "conta", level: 2, grupo, centro_custo: centro, conta, codigo_conta: contaItems[0].codigo_conta, codigo_centro_custo: centroItems[0].codigo_centro_custo, total: totalConta, valoresPorMes: valoresContaPorMes, percentuaisPorMes: calcularPercentuaisPorMes(valoresContaPorMes, grupo), percentualTotal: calcularPercentualTotal(totalConta, grupo), entidades: contaItems[0]?.entidades || "", }); }); }); } }); return rows; }; const toggleExpandAll = useCallback(() => { if (isAllExpanded) { // Recolher tudo - usar startTransition para atualizações não urgentes startTransition(() => { setExpandedGroups(new Set()); setExpandedCentros(new Set()); setIsAllExpanded(false); }); } else { // Expandir todos os grupos e contas usando dados originais - usar startTransition para atualizações não urgentes startTransition(() => { const todosGrupos = [...new Set(data.map(item => item.grupo))]; if (ordemHierarquiaContasPrimeiro) { // Nova ordem: expandir grupos e contas const todasContas = [...new Set(data.map(item => `${item.grupo}-${item.conta}`))]; setExpandedGroups(new Set(todosGrupos)); setExpandedCentros(new Set(todasContas)); } else { // Ordem original: expandir grupos e centros de custo const todosCentros = [...new Set(data.map(item => `${item.grupo}-${item.centro_custo}`))]; setExpandedGroups(new Set(todosGrupos)); setExpandedCentros(new Set(todosCentros)); } setIsAllExpanded(true); }); } }, [isAllExpanded, data, ordemHierarquiaContasPrimeiro]); // Função para recalcular grupos calculados baseado apenas nos dados filtrados const recalcularGruposCalculados = (dadosFiltrados: DREItem[]): DREItem[] => { const gruposCalculados: DREItem[] = []; // Agrupar dados por mês para cálculos const dadosPorMes = dadosFiltrados.reduce((acc, item) => { const mes = item.data_competencia; if (!acc[mes]) acc[mes] = []; acc[mes].push(item); return acc; }, {} as Record); // Para cada mês, criar os grupos calculados Object.keys(dadosPorMes).forEach(mes => { const dadosMes = dadosPorMes[mes]; // Calcular valores por grupo usando código numérico const valoresPorGrupo = dadosMes.reduce((acc: Record, item: DREItem) => { const codgrupo = item.codgrupo || ""; if (!codgrupo) return acc; if (!acc[codgrupo]) acc[codgrupo] = 0; acc[codgrupo] += parseFloat(item.valor); return acc; }, {} as Record); // Função auxiliar para obter valor de um grupo (calculado ou não) const obterValorGrupo = (codigoGrupo: string): number => { // Primeiro, verificar se já foi calculado nos grupos calculados const grupoCalculado = gruposCalculados.find(g => g.codgrupo === codigoGrupo && g.data_competencia === mes); if (grupoCalculado) { return parseFloat(grupoCalculado.valor); } // Se não, buscar nos valores diretos dos grupos return valoresPorGrupo[codigoGrupo] || 0; }; // 03 - Faturamento Líquido (01 + 02) const faturamentoBruto = valoresPorGrupo['01'] || 0; const devolucao = valoresPorGrupo['02'] || 0; const faturamentoLiquido = faturamentoBruto + devolucao; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "03 - FATURAMENTO LÍQUIDO", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "FATURAMENTO LÍQUIDO", valor: faturamentoLiquido.toString(), codgrupo: "03", isCalculado: true }); // 05 - Lucro Bruto (03 + 04) - usar grupo 03 calculado const cmv = valoresPorGrupo['04'] || 0; const valor03 = obterValorGrupo("03"); const lucroBruto = valor03 + cmv; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "05 - LUCRO BRUTO", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "LUCRO BRUTO", valor: lucroBruto.toString(), codgrupo: "05", isCalculado: true }); // 07 - Margem Loja (05 + 06) - usar grupo 05 calculado const receitasGastosDiretos = valoresPorGrupo['06'] || 0; const valor05 = obterValorGrupo("05"); const margemLoja = valor05 + receitasGastosDiretos; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "07 - MARGEM LOJA", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "MARGEM LOJA", valor: margemLoja.toString(), codgrupo: "07", isCalculado: true }); // 10 - Resultado Operacional (07 + 08 + 09) - usar grupo 07 calculado const verba = valoresPorGrupo['08'] || 0; const receitasGastosIndiretos = valoresPorGrupo['09'] || 0; const valor07 = obterValorGrupo("07"); const resultadoOperacional = valor07 + verba + receitasGastosIndiretos; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "10 - RESULTADO OPERACIONAL", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "RESULTADO OPERACIONAL", valor: resultadoOperacional.toString(), codgrupo: "10", isCalculado: true }); // 13 - Resultado Financeiro (11 + 12) const receitaFinanceira = valoresPorGrupo['11'] || 0; const despesaFinanceira = valoresPorGrupo['12'] || 0; const resultadoFinanceiro = receitaFinanceira + despesaFinanceira; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "13 - RESULTADO FINANCEIRO", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "RESULTADO FINANCEIRO", valor: resultadoFinanceiro.toString(), codgrupo: "13", isCalculado: true }); // 19 - Resultado Não Operacional (14 + 15 + 16 + 17 + 18) const prejuizosPerdas = valoresPorGrupo['14'] || 0; const inativas = valoresPorGrupo['15'] || 0; const diretoria = valoresPorGrupo['16'] || 0; const lancamentosSemCC = valoresPorGrupo['17'] || 0; const receitasDespesasNaoOperacional = valoresPorGrupo['18'] || 0; const resultadoNaoOperacional = prejuizosPerdas + inativas + diretoria + lancamentosSemCC + receitasDespesasNaoOperacional; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "19 - RESULTADO NÃO OPERACIONAL", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "RESULTADO NÃO OPERACIONAL", valor: resultadoNaoOperacional.toString(), codgrupo: "19", isCalculado: true }); // 20 - LAIR (10 + 13 + 19) - usar grupos calculados const valor10 = obterValorGrupo("10"); const valor13 = obterValorGrupo("13"); const valor19 = obterValorGrupo("19"); const lair = valor10 + valor13 + valor19; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "20 - LAIR", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "LUCRO ANTES DO IMPOSTO DE RENDA", valor: lair.toString(), codgrupo: "20", isCalculado: true }); // 21 - IR = Se LAIR > 0 calcular 20% e resultado negativo (*-1), se não 0 const valor20_ir = obterValorGrupo("20"); const ir = valor20_ir > 0 ? -(valor20_ir * 0.20) : 0; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "21 - IR", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "IMPOSTO DE RENDA", valor: ir.toString(), codgrupo: "21", isCalculado: true }); // 22 - CSLL = Se LAIR > 0 calcular 9% e resultado negativo (*-1), se não 0 const valor20_csll = obterValorGrupo("20"); const csll = valor20_csll > 0 ? -(valor20_csll * 0.09) : 0; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "22 - CSLL", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "CONTRIBUIÇÃO SOCIAL SOBRE LUCRO LÍQUIDO", valor: csll.toString(), codgrupo: "22", isCalculado: true }); // 23 - Lucro Líquido (20 + 21 + 22) - usar grupos calculados const valor20 = obterValorGrupo("20"); const valor21 = obterValorGrupo("21"); const valor22 = obterValorGrupo("22"); const lucroLiquido = valor20 + valor21 + valor22; gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "23 - LUCRO LÍQUIDO", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "LUCRO LÍQUIDO", valor: lucroLiquido.toString(), codgrupo: "23", isCalculado: true }); // 25 - EBITDA (20 - (13 + 19 + 24)) - usar grupos calculados const despesaTributaria = valoresPorGrupo['24'] || 0; const valor20_ebitda = obterValorGrupo("20"); const valor13_ebitda = obterValorGrupo("13"); const valor19_ebitda = obterValorGrupo("19"); const ebitda = valor20_ebitda - (valor13_ebitda + valor19_ebitda + despesaTributaria); gruposCalculados.push({ codfilial: "001", data_competencia: mes, data_cai: mes, grupo: "25 - EBITDA", subgrupo: "CALCULADO", centro_custo: "CALCULADO", codigo_centro_custo: "", codigo_conta: 0, conta: "EBITDA", valor: ebitda.toString(), codgrupo: "25", isCalculado: true }); }); return gruposCalculados; }; const limparFiltros = () => { const agora = new Date(); const anoAtual = agora.getFullYear(); const mesAtual = String(agora.getMonth() + 1).padStart(2, '0'); const periodoAtual = `${anoAtual}-${mesAtual}`; setFiltros({ periodoDe: `${anoAtual}-01`, periodoAte: periodoAtual, grupo: "Todos", subgrupo: "Todos", centroCusto: "Todos", conta: "Todas", valorMin: "", valorMax: "", buscaTextual: "" }); // Limpar multi-seleções setCentrosCustoSelecionados([]); setContasSelecionadas([]); setEntidadesSelecionadas([]); // Limpar filtros de busca setFiltroCentroCusto(""); setFiltroConta(""); setFiltroEntidade(""); // Limpar dados da tabela setData([]); setDadosFiltrados([]); setFiltrosAplicados(false); setMesesDisponiveis([]); setIsAllExpanded(false); setOrdemHierarquiaContasPrimeiro(false); // Fechar o sheet de filtros setIsFilterOpen(false); // Recarregar opções e selecionar todos novamente carregarPeriodosDisponiveis(); }; const aplicarFiltros = async () => { // Fechar o Sheet primeiro setIsFilterOpen(false); // Aguardar um pouco para a animação de fechamento setTimeout(async () => { try { setLoading(true); setError(null); // Carregar dados da API const response = await fetch("/api/dre-oracle"); if (!response.ok) { throw new Error(`Erro HTTP: ${response.status}`); } const dadosCompletos = await response.json(); // Aplicar filtros nos dados let dadosFiltrados = dadosCompletos; // Filtro por período if (filtros.periodoDe && filtros.periodoAte) { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => { const dataItem = item.data_competencia; return dataItem >= filtros.periodoDe && dataItem <= filtros.periodoAte; }); } // Filtro por grupo if (filtros.grupo !== "Todos") { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => item.grupo === filtros.grupo ); } // Filtro por subgrupo if (filtros.subgrupo !== "Todos") { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => item.subgrupo === filtros.subgrupo ); } // Filtro por centro de custo (multi-seleção) - USAR APENAS CÓDIGO // IMPORTANTE: Preservar grupos calculados (isCalculado ou centro_custo === "CALCULADO") if (centrosCustoSelecionados.length > 0) { // Criar conjunto de códigos esperados dos centros selecionados - APENAS CÓDIGOS const codigosEsperados = new Set(); centrosCustoSelecionados.forEach(centro => { // Buscar o código no mapeamento primeiro const codigoCentro = codigosCentrosCusto[centro]; if (codigoCentro) { codigosEsperados.add(codigoCentro); } else { // Se não encontrar no mapeamento, tentar buscar nos dados carregados const item = dadosCompletos.find((d: DREItem) => d.centro_custo === centro); if (item?.codigo_centro_custo) { codigosEsperados.add(item.codigo_centro_custo); } } }); // Filtrar APENAS pelo código do centro de custo, ignorando o nome // MAS preservar grupos calculados dadosFiltrados = dadosFiltrados.filter((item: DREItem) => { // Preservar grupos calculados (têm centro_custo === "CALCULADO" ou isCalculado === true) if (item.centro_custo === "CALCULADO" || item.isCalculado === true) { return true; } // Para outros itens, verificar pelo código if (!item.codigo_centro_custo) { return false; } return codigosEsperados.has(item.codigo_centro_custo); }); console.log('🏢 Filtro de centros de custo aplicado (APENAS CÓDIGO):', { selecionados: centrosCustoSelecionados, codigosEsperados: Array.from(codigosEsperados), totalFiltrado: dadosFiltrados.length, centrosEncontrados: [...new Set(dadosFiltrados.map((d: DREItem) => d.centro_custo))], codigosEncontrados: [...new Set(dadosFiltrados.map((d: DREItem) => d.codigo_centro_custo).filter(Boolean))], gruposCalculados: dadosFiltrados.filter((d: DREItem) => d.centro_custo === "CALCULADO" || d.isCalculado === true).length }); } // Filtro por conta (multi-seleção) // IMPORTANTE: Preservar grupos calculados (isCalculado ou centro_custo === "CALCULADO") if (contasSelecionadas.length > 0) { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => { // Preservar grupos calculados if (item.centro_custo === "CALCULADO" || item.isCalculado === true) { return true; } // Para outros itens, verificar se a conta está selecionada return contasSelecionadas.includes(item.conta); }); } // Filtro por valor mínimo if (filtros.valorMin) { const valorMin = parseFloat(filtros.valorMin.replace(',', '.')); dadosFiltrados = dadosFiltrados.filter((item: DREItem) => parseFloat(item.valor) >= valorMin ); } // Filtro por valor máximo if (filtros.valorMax) { const valorMax = parseFloat(filtros.valorMax.replace(',', '.')); dadosFiltrados = dadosFiltrados.filter((item: DREItem) => parseFloat(item.valor) <= valorMax ); } // Filtro por busca textual if (filtros.buscaTextual) { const termoBusca = filtros.buscaTextual.toLowerCase(); dadosFiltrados = dadosFiltrados.filter((item: DREItem) => item.grupo.toLowerCase().includes(termoBusca) || item.subgrupo.toLowerCase().includes(termoBusca) || item.centro_custo.toLowerCase().includes(termoBusca) || item.conta.toLowerCase().includes(termoBusca) ); } // Filtro por entidades (multi-seleção) // IMPORTANTE: Preservar grupos calculados (isCalculado ou centro_custo === "CALCULADO") if (entidadesSelecionadas.length > 0) { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => { // Preservar grupos calculados if (item.centro_custo === "CALCULADO" || item.isCalculado === true) { return true; } // Para outros itens, verificar se a entidade está selecionada return item.entidades && entidadesSelecionadas.includes(item.entidades); }); console.log('🏢 Filtro de entidades aplicado:', { selecionadas: entidadesSelecionadas, totalFiltrado: dadosFiltrados.length, entidadesEncontradas: [...new Set(dadosFiltrados.map((d: DREItem) => d.entidades).filter(Boolean))], gruposCalculados: dadosFiltrados.filter((d: DREItem) => d.centro_custo === "CALCULADO" || d.isCalculado === true).length }); } // Remover grupos calculados antigos (que foram calculados com todos os dados) // Eles serão recalculados com base apenas nos dados filtrados dadosFiltrados = dadosFiltrados.filter((item: DREItem) => item.centro_custo !== "CALCULADO" && item.isCalculado !== true ); // Recalcular grupos calculados com base apenas nos dados filtrados const gruposCalculadosRecalculados = recalcularGruposCalculados(dadosFiltrados); // Adicionar os grupos calculados recalculados de volta aos dados filtrados dadosFiltrados = [...dadosFiltrados, ...gruposCalculadosRecalculados]; setData(dadosFiltrados); setDadosFiltrados(dadosFiltrados); setFiltrosAplicados(true); // Extrair meses únicos dos dados filtrados const mesesUnicos = [...new Set(dadosFiltrados.map((item: DREItem) => item.data_competencia))].sort() as string[]; setMesesDisponiveis(mesesUnicos); } catch (error) { console.error("Erro ao aplicar filtros:", error); setError(error instanceof Error ? error.message : "Erro desconhecido"); } finally { setLoading(false); } }, 300); // Aguardar 300ms para a animação de fechamento }; const calcularValoresPorMes = (items: DREItem[]): Record => { const valoresPorMes: Record = {}; items.forEach((item) => { // Usar diretamente o valor de data_competencia que já vem no formato YYYY-MM const anoMes = item.data_competencia; if (!valoresPorMes[anoMes]) { valoresPorMes[anoMes] = 0; } valoresPorMes[anoMes] += parseFloat(item.valor); }); return valoresPorMes; }; // Função para calcular percentuais baseado no grupo 03 como referência const calcularPercentuaisPorMes = ( valoresPorMes: Record, grupo: string ): Record => { const percentuais: Record = {}; // Se for o grupo 03, retorna 100% para todos os meses if (grupo.includes("03")) { Object.keys(valoresPorMes).forEach((mes) => { percentuais[mes] = 100; }); return percentuais; } // Para outros grupos, calcular percentual baseado no grupo 03 Object.keys(valoresPorMes).forEach((mes) => { const valorAtual = valoresPorMes[mes]; // Encontrar o valor do grupo 03 para o mesmo mês const grupo03Items = data.filter((item) => { // Usar diretamente o valor de data_competencia que já vem no formato YYYY-MM return item.data_competencia === mes && item.grupo.includes("03"); }); const valorGrupo03 = grupo03Items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); if (valorGrupo03 !== 0) { percentuais[mes] = (valorAtual / valorGrupo03) * 100; } else { percentuais[mes] = 0; } }); return percentuais; }; // Função para calcular percentual do total baseado no grupo 03 como referência const calcularPercentualTotal = ( total: number, grupo: string ): number => { // Se for o grupo 03, retorna 100% if (grupo.includes("03")) { return 100; } // Calcular o total do grupo 03 const grupo03Items = data.filter((item) => item.grupo.includes("03") ); const totalGrupo03 = grupo03Items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); if (totalGrupo03 !== 0) { return (total / totalGrupo03) * 100; } else { return 0; } }; const buildHierarchicalData = (): HierarchicalRow[] => { const rows: HierarchicalRow[] = []; // Agrupar por grupo const grupos = data.reduce((acc, item) => { if (!acc[item.grupo]) { acc[item.grupo] = []; } acc[item.grupo].push(item); return acc; }, {} as Record); // Ordenar grupos pelo CODGRUPO numérico const sortedGrupos = Object.entries(grupos).sort(([grupoA, itemsA], [grupoB, itemsB]) => { // Pegar o CODGRUPO do primeiro item de cada grupo const codgrupoA = itemsA[0]?.codgrupo || ""; const codgrupoB = itemsB[0]?.codgrupo || ""; // Se ambos têm CODGRUPO, ordenar numericamente if (codgrupoA && codgrupoB) { return parseInt(codgrupoA) - parseInt(codgrupoB); } // Se apenas um tem CODGRUPO, ele vem primeiro if (codgrupoA && !codgrupoB) return -1; if (!codgrupoA && codgrupoB) return 1; // Se nenhum tem CODGRUPO, ordenar alfabeticamente return grupoA.localeCompare(grupoB); }); // Função para calcular valores de grupos calculados usando códigos dos grupos const calcularGrupoCalculado = (codigoGrupo: string, gruposData: Record): Record => { const valoresPorMes: Record = {}; // Inicializar valores para todos os meses disponíveis mesesDisponiveis.forEach(mes => { valoresPorMes[mes] = 0; }); // Função auxiliar para obter valor de um grupo por código e mês const obterValorGrupo = (codigoGrupo: string, mes: string): number => { const grupoEncontrado = Object.values(gruposData).find(items => items.length > 0 && items[0].codgrupo === codigoGrupo ); if (!grupoEncontrado) return 0; const itemsMes = grupoEncontrado.filter(item => item.data_competencia === mes); return itemsMes.reduce((sum, item) => sum + parseFloat(item.valor), 0); }; switch (codigoGrupo) { case "03": // Faturamento Líquido = Grupo 01 + 02 mesesDisponiveis.forEach(mes => { const valor01 = obterValorGrupo("01", mes); const valor02 = obterValorGrupo("02", mes); valoresPorMes[mes] = valor01 + valor02; }); break; case "05": // Lucro Bruto = Grupo 03 + 04 mesesDisponiveis.forEach(mes => { const valor03 = obterValorGrupo("03", mes); const valor04 = obterValorGrupo("04", mes); valoresPorMes[mes] = valor03 + valor04; }); break; case "07": // Margem Loja = Grupo 05 + 06 mesesDisponiveis.forEach(mes => { const valor05 = obterValorGrupo("05", mes); const valor06 = obterValorGrupo("06", mes); valoresPorMes[mes] = valor05 + valor06; }); break; case "10": // Resultado Operacional = Grupo 07 + 08 + 09 mesesDisponiveis.forEach(mes => { const valor07 = obterValorGrupo("07", mes); const valor08 = obterValorGrupo("08", mes); const valor09 = obterValorGrupo("09", mes); valoresPorMes[mes] = valor07 + valor08 + valor09; }); break; case "13": // Resultado Financeiro = Grupo 11 + 12 mesesDisponiveis.forEach(mes => { const valor11 = obterValorGrupo("11", mes); const valor12 = obterValorGrupo("12", mes); valoresPorMes[mes] = valor11 + valor12; }); break; case "19": // Resultado Não Operacional = Grupo 14 + 15 + 16 + 17 + 18 mesesDisponiveis.forEach(mes => { const valor14 = obterValorGrupo("14", mes); const valor15 = obterValorGrupo("15", mes); const valor16 = obterValorGrupo("16", mes); const valor17 = obterValorGrupo("17", mes); const valor18 = obterValorGrupo("18", mes); valoresPorMes[mes] = valor14 + valor15 + valor16 + valor17 + valor18; }); break; case "20": // LAIR = Grupo 10 + 13 + 19 mesesDisponiveis.forEach(mes => { const valor10 = obterValorGrupo("10", mes); const valor13 = obterValorGrupo("13", mes); const valor19 = obterValorGrupo("19", mes); valoresPorMes[mes] = valor10 + valor13 + valor19; }); break; case "21": // IR = Se LAIR > 0 calcular 20% e resultado negativo (*-1), se não 0 mesesDisponiveis.forEach(mes => { const valor20 = obterValorGrupo("20", mes); if (valor20 > 0) { valoresPorMes[mes] = (valor20 * 0.20) * -1; } else { valoresPorMes[mes] = 0; } }); break; case "22": // CSLL = Se LAIR > 0 calcular 9% e resultado negativo (*-1), se não 0 mesesDisponiveis.forEach(mes => { const valor20 = obterValorGrupo("20", mes); if (valor20 > 0) { valoresPorMes[mes] = (valor20 * 0.09) * -1; } else { valoresPorMes[mes] = 0; } }); break; case "23": // Lucro Líquido = Grupo 20 + 21 + 22 mesesDisponiveis.forEach(mes => { const valor20 = obterValorGrupo("20", mes); const valor21 = obterValorGrupo("21", mes); const valor22 = obterValorGrupo("22", mes); valoresPorMes[mes] = valor20 + valor21 + valor22; }); break; case "25": // EBITDA = Grupo 20 - (13 + 19 + 24) mesesDisponiveis.forEach(mes => { const valor20 = obterValorGrupo("20", mes); const valor13 = obterValorGrupo("13", mes); const valor19 = obterValorGrupo("19", mes); const valor24 = obterValorGrupo("24", mes); valoresPorMes[mes] = valor20 - (valor13 + valor19 + valor24); }); break; } return valoresPorMes; }; sortedGrupos.forEach(([grupo, items]) => { const totalGrupo = items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Verificar se é um grupo calculado const codigoGrupo = items[0]?.codgrupo || ""; const isCalculado = ["03", "05", "07", "10", "13", "19", "20", "21", "22", "23", "25"].includes(codigoGrupo); let valoresPorMes; if (isCalculado) { // Usar cálculo específico para grupos calculados valoresPorMes = calcularGrupoCalculado(codigoGrupo, grupos); } else { // Usar cálculo normal para grupos não calculados valoresPorMes = calcularValoresPorMes(items); } // Calcular total do grupo const totalCalculado = Object.values(valoresPorMes).reduce((sum, valor) => sum + valor, 0); const totalFinal = isCalculado ? totalCalculado : totalGrupo; // Linha do grupo rows.push({ type: "grupo", level: 0, grupo, total: totalFinal, isExpanded: expandedGroups.has(grupo), valoresPorMes, percentuaisPorMes: calcularPercentuaisPorMes(valoresPorMes, grupo), percentualTotal: calcularPercentualTotal(totalFinal, grupo), isCalculado: isCalculado, }); if (expandedGroups.has(grupo)) { if (ordemHierarquiaContasPrimeiro) { // ORDEM: Grupos → Contas → Centros de Custo // Agrupar por conta dentro do grupo const contas = items.reduce((acc, item) => { if (!acc[item.conta]) { acc[item.conta] = []; } acc[item.conta].push(item); return acc; }, {} as Record); // Ordenar contas por CODCONTA const sortedContas = Object.entries(contas).sort(([contaA, itemsA], [contaB, itemsB]) => { const codcontaA = itemsA[0]?.codigo_conta || 0; const codcontaB = itemsB[0]?.codigo_conta || 0; if (codcontaA && codcontaB) { return codcontaA - codcontaB; } if (codcontaA && !codcontaB) return -1; if (!codcontaA && codcontaB) return 1; return contaA.localeCompare(contaB); }); sortedContas.forEach(([conta, contaItems]) => { const totalConta = contaItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha da conta (Level 1) const valoresContaPorMes = calcularValoresPorMes(contaItems); rows.push({ type: "conta", level: 1, grupo, conta, codigo_conta: contaItems[0].codigo_conta, codigo_centro_custo: contaItems[0].codigo_centro_custo, // ✅ Adicionar código do centro de custo total: totalConta, isExpanded: expandedCentros.has(`${grupo}-${conta}`), valoresPorMes: valoresContaPorMes, percentuaisPorMes: calcularPercentuaisPorMes( valoresContaPorMes, grupo ), percentualTotal: calcularPercentualTotal(totalConta, grupo), }); if (expandedCentros.has(`${grupo}-${conta}`)) { // Agrupar por centro de custo dentro da conta const centros = contaItems.reduce((acc, item) => { if (!acc[item.centro_custo]) { acc[item.centro_custo] = []; } acc[item.centro_custo].push(item); return acc; }, {} as Record); // Ordenar centros de custo por CODIGOCENTROCUSTO const sortedCentros = Object.entries(centros).sort(([centroA, itemsA], [centroB, itemsB]) => { const codigoA = itemsA[0]?.codigo_centro_custo || ""; const codigoB = itemsB[0]?.codigo_centro_custo || ""; if (codigoA && codigoB) { return codigoA.localeCompare(codigoB); } if (codigoA && !codigoB) return -1; if (!codigoA && codigoB) return 1; return centroA.localeCompare(centroB); }); sortedCentros.forEach(([centro, centroItems]) => { const totalCentro = centroItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do centro de custo (Level 2) const valoresCentroPorMes = calcularValoresPorMes(centroItems); rows.push({ type: "centro_custo", level: 2, grupo, centro_custo: centro, conta, codigo_conta: contaItems[0].codigo_conta, // ✅ Adicionar código da conta codigo_centro_custo: centroItems[0].codigo_centro_custo, total: totalCentro, valoresPorMes: valoresCentroPorMes, percentuaisPorMes: calcularPercentuaisPorMes( valoresCentroPorMes, grupo ), percentualTotal: calcularPercentualTotal(totalCentro, grupo), }); }); } }); } else { // ORDEM ORIGINAL: Grupos → Centros de Custo → Contas // Agrupar por centro de custo dentro do grupo const centros = items.reduce((acc, item) => { if (!acc[item.centro_custo]) { acc[item.centro_custo] = []; } acc[item.centro_custo].push(item); return acc; }, {} as Record); // Ordenar centros de custo por CODIGOCENTROCUSTO const sortedCentros = Object.entries(centros).sort(([centroA, itemsA], [centroB, itemsB]) => { const codigoA = itemsA[0]?.codigo_centro_custo || ""; const codigoB = itemsB[0]?.codigo_centro_custo || ""; if (codigoA && codigoB) { return codigoA.localeCompare(codigoB); } if (codigoA && !codigoB) return -1; if (!codigoA && codigoB) return 1; return centroA.localeCompare(centroB); }); sortedCentros.forEach(([centro, centroItems]) => { const totalCentro = centroItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do centro de custo (Level 1) const valoresCentroPorMes = calcularValoresPorMes(centroItems); rows.push({ type: "centro_custo", level: 1, grupo, centro_custo: centro, codigo_conta: centroItems[0].codigo_conta, // ✅ Adicionar código da conta codigo_centro_custo: centroItems[0].codigo_centro_custo, total: totalCentro, isExpanded: expandedCentros.has(`${grupo}-${centro}`), valoresPorMes: valoresCentroPorMes, percentuaisPorMes: calcularPercentuaisPorMes( valoresCentroPorMes, grupo ), percentualTotal: calcularPercentualTotal(totalCentro, grupo), }); if (expandedCentros.has(`${grupo}-${centro}`)) { // Agrupar por conta dentro do centro de custo const contas = centroItems.reduce((acc, item) => { if (!acc[item.conta]) { acc[item.conta] = []; } acc[item.conta].push(item); return acc; }, {} as Record); // Ordenar contas por CODCONTA const sortedContas = Object.entries(contas).sort(([contaA, itemsA], [contaB, itemsB]) => { const codcontaA = itemsA[0]?.codigo_conta || 0; const codcontaB = itemsB[0]?.codigo_conta || 0; if (codcontaA && codcontaB) { return codcontaA - codcontaB; } if (codcontaA && !codcontaB) return -1; if (!codcontaA && codcontaB) return 1; return contaA.localeCompare(contaB); }); sortedContas.forEach(([conta, contaItems]) => { const totalConta = contaItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha da conta (Level 2) const valoresContaPorMes = calcularValoresPorMes(contaItems); rows.push({ type: "conta", level: 2, grupo, centro_custo: centro, conta, codigo_conta: contaItems[0].codigo_conta, codigo_centro_custo: centroItems[0].codigo_centro_custo, // ✅ Adicionar código do centro de custo total: totalConta, valoresPorMes: valoresContaPorMes, percentuaisPorMes: calcularPercentuaisPorMes( valoresContaPorMes, grupo ), percentualTotal: calcularPercentualTotal(totalConta, grupo), }); }); } }); } } }); return rows; }; const getRowStyle = (row: HierarchicalRow) => { const baseStyle = "transition-all duration-200 hover:bg-gradient-to-r hover:from-blue-50/30 hover:to-indigo-50/30"; // Criar identificador único para a linha const linhaId = `${row.type}-${row.grupo || ""}-${row.subgrupo || ""}-${ row.centro_custo || "" }-${row.codigo_conta || ""}`; const isSelected = linhaSelecionada === linhaId; // Verificar se é um grupo calculado const isCalculado = row.isCalculado === true; let style = baseStyle; if (isSelected) { style += " bg-gradient-to-r from-green-100 to-emerald-100 border-l-4 border-green-500 shadow-lg"; } switch (row.type) { case "grupo": if (isCalculado) { // Destacar grupos calculados com cor azul return `${style} bg-gradient-to-r from-blue-100/80 to-indigo-100/80 font-bold text-gray-900 border-b-2 border-blue-300 shadow-sm`; } return `${style} bg-gradient-to-r from-blue-50/20 to-indigo-50/20 font-bold text-gray-900 border-b-2 border-blue-200`; case "subgrupo": return `${style} bg-gradient-to-r from-gray-50/30 to-blue-50/20 font-semibold text-gray-800`; case "centro_custo": return `${style} bg-gradient-to-r from-gray-50/20 to-gray-100/10 font-medium text-gray-700`; case "conta": return `${style} bg-white font-normal text-gray-600`; default: return style; } }; const getIndentStyle = (level: number) => { return { paddingLeft: `${level * 20}px` }; }; const renderCellContent = (row: HierarchicalRow) => { // Verificar se é um grupo calculado usando a propriedade isCalculado const isCalculado = row.isCalculado === true; switch (row.type) { case "grupo": return (
); case "conta": return (
{ordemHierarquiaContasPrimeiro ? ( // Nova ordem: Conta é level 1 (com botão de toggle) ) : ( // Ordem original: Conta é level 2 (sem botão de toggle)
)}
); case "centro_custo": return (
{ordemHierarquiaContasPrimeiro ? ( // Nova ordem: Centro de custo é level 2 (sem botão de toggle)
) : ( // Ordem original: Centro de custo é level 1 (com botão de toggle) )}
); default: return null; } }; // Loading será tratado dentro do componente principal // Error será tratado dentro do componente principal const hierarchicalData = buildHierarchicalData(); return (
{/* Header Section */}

DRE Gerencial

Demonstração do Resultado do Exercício

{/* Controles */}
{/* Botão de Exportar XLSX */} {/* Botão de Expandir/Recolher */} {/* Botão de Filtro */} Filtros Ajuste os critérios e clique em Pesquisar para atualizar a visão.
{/* Período */}
{/* Grupo
*/} {/* Subgrupo
*/} {/* Centro de Custo */}
{/* Input de filtro para Centro de Custo */} setFiltroCentroCusto(e.target.value)} className="h-8 text-sm" />
{opcoesCentrosCusto .filter(centro => { if (!filtroCentroCusto) return true; const termo = filtroCentroCusto.toLowerCase(); const nomeCompleto = `${centro}${codigosCentrosCusto[centro] ? ` - ${codigosCentrosCusto[centro]}` : ''}`; return nomeCompleto.toLowerCase().includes(termo); }) .map(centro => (
toggleCentroCusto(centro)} />
))}
{centrosCustoSelecionados.length > 0 && (
{centrosCustoSelecionados.length} centro(s) selecionado(s)
)}
{/* Conta */}
{/* Input de filtro para Conta */} setFiltroConta(e.target.value)} className="h-8 text-sm" />
{opcoesContas .filter(conta => { if (!filtroConta) return true; const termo = filtroConta.toLowerCase(); const nomeCompleto = `${conta}${codigosContas[conta] ? ` - ${codigosContas[conta]}` : ''}`; return nomeCompleto.toLowerCase().includes(termo); }) .map(conta => (
toggleConta(conta)} />
))}
{contasSelecionadas.length > 0 && (
{contasSelecionadas.length} conta(s) selecionada(s)
)}
{/* Valor
R$ handleFiltroChange('valorMin', e.target.value)} className="pl-8" placeholder="0,00" />
R$ handleFiltroChange('valorMax', e.target.value)} className="pl-8" placeholder="0,00" />
*/} {/* Busca Textual
handleFiltroChange('buscaTextual', e.target.value)} placeholder="Pesquise por grupo, subgrupo, centro ou conta" />
*/} {/* Entidades */}
{/* Input de filtro para Entidades */} setFiltroEntidade(e.target.value)} className="h-8 text-sm" />
{opcoesEntidades .filter(entidade => { if (!filtroEntidade) return true; const termo = filtroEntidade.toLowerCase(); return entidade.toLowerCase().includes(termo); }) .map(entidade => (
toggleEntidade(entidade)} />
))}
{entidadesSelecionadas.length > 0 && (
{entidadesSelecionadas.length} entidade(s) selecionada(s)
)}
{/* Ordem da Hierarquia */}
setOrdemHierarquiaContasPrimeiro(checked)} />
{ordemHierarquiaContasPrimeiro ? "📄 Contas → 🏢 Centros de Custo" : "🏢 Centros de Custo → 📄 Contas" }
{/* Loading quando aplicando filtros */} {loading && (

Aplicando filtros...

Aguarde enquanto processamos os dados.

)} {/* Erro */} {error && !loading && (

Erro ao carregar dados

{error}

)} {/* Mensagem quando não há dados */} {!filtrosAplicados && !loading && !error && (

Nenhum dado exibido

Clique no botão "Filtros" para definir os critérios de busca e visualizar os dados do DRE.

)} {/* Table Container */} {filtrosAplicados && !loading && !error && (
{/* Container com coluna fixa e scroll horizontal */}
{/* Coluna fixa - Descrição */}
{/* Header fixo da descrição */}
Descrição
{/* Corpo da descrição com scroll vertical */}
syncScroll('descricao')} > {hierarchicalData.map((row, index) => (
{renderCellContent(row)}
))}
{/* Parte com scroll - Valores */}
syncScroll('valores')} > {/* Table Header */} {mesesDisponiveis.map((mes) => ( ))} {/* Table Body */} {hierarchicalData.map((row, index) => ( {/* Colunas de valores por mês */} {mesesDisponiveis.map((mes) => ( ))} {/* Coluna Total */} {/* Coluna Percentual Total */} ))}
{mes} % Total %
handleRowClick(row, mes)} title={ row.valoresPorMes && row.valoresPorMes[mes] ? formatCurrency(row.valoresPorMes[mes]) : "-" } > {row.valoresPorMes && row.valoresPorMes[mes] ? (() => { const { formatted, isNegative } = formatCurrencyWithColor(row.valoresPorMes[mes]); return ( {formatted} ); })() : "-"} handleRowClick(row, mes)} title={ row.percentuaisPorMes && row.percentuaisPorMes[mes] !== undefined ? `${row.percentuaisPorMes[mes].toFixed(1)}%` : "-" } > {row.percentuaisPorMes && row.percentuaisPorMes[mes] !== undefined ? `${row.percentuaisPorMes[mes].toFixed(1)}%` : "-"} handleRowClick(row)} title={row.total ? formatCurrency(row.total) : "-"} > {(() => { const { formatted, isNegative } = formatCurrencyWithColor( row.total! ); return ( {formatted} ); })()} handleRowClick(row)} title={ row.percentualTotal !== undefined ? `${row.percentualTotal.toFixed(1)}%` : "-" } > {row.percentualTotal !== undefined ? `${row.percentualTotal.toFixed(1)}%` : "-"}
)} {/* Componente Analítico - Sempre renderizado para evitar violação das Rules of Hooks */}
); }