'use client'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, } from '@/components/ui/sheet'; import { ChevronDown, ChevronRight, Download, Filter, LoaderPinwheel, Maximize2, Minimize2, } from 'lucide-react'; import React, { memo, useCallback, useEffect, useState } from 'react'; import * as XLSX from 'xlsx'; import AnaliticoComponent from './analitico'; interface DREItem { codfilial: string; data_competencia: string; data_cai: string; grupo: string; codigo_grupo: string; codigo_conta: number; conta: string; valor: string; codgrupo?: string; tipo?: string; filial?: string; } interface HierarchicalRow { type: 'grupo' | 'conta' | 'calculado'; level: number; grupo?: string; codigo_grupo?: string; conta?: string; codigo_conta?: number; total?: number; isExpanded?: boolean; valoresPorMes?: Record; valoresPorMesPorFilial?: Record>; // mes -> filial -> valor percentuaisPorMes?: Record; percentuaisPorMesPorFilial?: Record>; // mes -> filial -> percentual percentualTotal?: number; isCalculado?: boolean; } // Componente memoizado para linhas da tabela const TableRow = memo( ({ row, index, handleRowClick, getRowStyle, getIndentStyle, renderCellContent, mesesDisponiveis, opcoesFiliais, filiaisSelecionadas, filtrosAplicados, formatCurrency, formatCurrencyWithColor, getFixedCellBackground, }: { row: HierarchicalRow; index: number; handleRowClick: ( row: HierarchicalRow, mes?: string, filial?: string ) => void; getRowStyle: (row: HierarchicalRow) => string; getIndentStyle: (level: number) => React.CSSProperties; renderCellContent: (row: HierarchicalRow) => React.ReactNode; mesesDisponiveis: string[]; opcoesFiliais: string[]; filiaisSelecionadas: string[]; filtrosAplicados: boolean; formatCurrency: (value: number) => string; formatCurrencyWithColor: (value: number) => { formatted: string; isNegative: boolean; }; getFixedCellBackground: (row: HierarchicalRow) => string; }) => { return ( handleRowClick(row)} >
{renderCellContent(row)}
{/* Colunas de valores por mês e por filial - cada filial tem suas próprias colunas */} {mesesDisponiveis.map((mes) => { const filiaisParaMes = filtrosAplicados && filiaisSelecionadas.length > 0 ? filiaisSelecionadas : opcoesFiliais.length > 0 ? opcoesFiliais : ['']; return ( {/* Colunas de filiais para este mês */} {filiaisParaMes.map((filial: string) => { // Só exibir se a filial estiver selecionada ou se não houver filtros aplicados if ( filtrosAplicados && filiaisSelecionadas.length > 0 && !filiaisSelecionadas.includes(filial) ) { return null; } return ( handleRowClick(row, mes, filial)} title={ filial && row.valoresPorMesPorFilial?.[mes]?.[filial] !== undefined ? formatCurrency( row.valoresPorMesPorFilial[mes][filial] ) : row.valoresPorMes?.[mes] !== undefined ? formatCurrency(row.valoresPorMes[mes]) : '-' } > {filial && row.valoresPorMesPorFilial?.[mes]?.[filial] !== undefined && row.valoresPorMesPorFilial[mes][filial] !== 0 ? ( (() => { const valor = row.valoresPorMesPorFilial[mes][filial]; const { formatted, isNegative } = formatCurrencyWithColor(valor); return ( {formatted} ); })() ) : !filial && row.valoresPorMes?.[mes] !== undefined ? ( (() => { const { formatted, isNegative } = formatCurrencyWithColor(row.valoresPorMes[mes]); return ( {formatted} ); })() ) : ( - )} handleRowClick(row, mes, filial)} title={ filial && row.percentuaisPorMesPorFilial?.[mes]?.[filial] !== undefined ? `${row.percentuaisPorMesPorFilial[mes][ filial ].toFixed(1)}%` : row.percentuaisPorMes?.[mes] !== undefined ? `${row.percentuaisPorMes[mes].toFixed(1)}%` : '-' } > {filial && row.percentuaisPorMesPorFilial?.[mes]?.[filial] !== undefined && row.percentuaisPorMesPorFilial[mes][filial] !== 0 ? ( `${row.percentuaisPorMesPorFilial[mes][filial].toFixed( 1 )}%` ) : !filial && row.percentuaisPorMes?.[mes] !== undefined ? ( `${row.percentuaisPorMes[mes].toFixed(1)}%` ) : ( - )} ); })} {/* Colunas de totalizador para este mês */} handleRowClick(row, mes)} title={ row.valoresPorMes?.[mes] !== undefined ? formatCurrency(row.valoresPorMes[mes]) : '-' } > {row.valoresPorMes?.[mes] !== undefined ? ( (() => { const { formatted, isNegative } = formatCurrencyWithColor( row.valoresPorMes[mes] ); return ( {formatted} ); })() ) : ( - )} handleRowClick(row, mes)} title={ row.percentuaisPorMes?.[mes] !== undefined ? `${row.percentuaisPorMes[mes].toFixed(1)}%` : '-' } > {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() { // Função para ordenar filiais numericamente (1, 2, 3...) ao invés de alfabeticamente (1, 10, 11...) const ordenarFiliaisNumericamente = (filiais: string[]): string[] => { return [...filiais].sort((a, b) => { // Tentar converter para número const numA = parseInt(a, 10); const numB = parseInt(b, 10); // Se ambos são números válidos, ordenar numericamente if (!isNaN(numA) && !isNaN(numB)) { return numA - numB; } // Se apenas A é número, vem primeiro if (!isNaN(numA)) { return -1; } // Se apenas B é número, vem primeiro if (!isNaN(numB)) { return 1; } // Se nenhum é número, ordenar alfabeticamente return a.localeCompare(b); }); }; const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [expandedGrupos, setExpandedGrupos] = useState>(new Set()); const [mesesDisponiveis, setMesesDisponiveis] = useState([]); // Estados para filtros const [filtros, setFiltros] = useState({ periodoDe: '', periodoAte: '', filial: 'Todas', }); // Estados para multi-seleção const [filiaisSelecionadas, setFiliaisSelecionadas] = useState([]); const [isFilterOpen, setIsFilterOpen] = useState(false); const [dadosFiltrados, setDadosFiltrados] = useState([]); const [filtrosAplicados, setFiltrosAplicados] = useState(false); // Estados para opções dos filtros const [opcoesGrupos, setOpcoesGrupos] = useState([]); const [opcoesFiliais, setOpcoesFiliais] = useState([]); // Estados para filtros de busca nos campos de seleção const [filtroFilial, setFiltroFilial] = useState(''); // Estados para analítico const [analiticoFiltros, setAnaliticoFiltros] = useState({ dataInicio: '', dataFim: '', centroCusto: '', codigoGrupo: '', codigoSubgrupo: '', codigoConta: '', codFilial: '', linhaSelecionada: '', excluirCentroCusto: '', excluirCodigoConta: '', codigosCentrosCustoSelecionados: '', codigosContasSelecionadas: '', }); const [linhaSelecionada, setLinhaSelecionada] = useState(null); const [isAllExpanded, setIsAllExpanded] = useState(false); useEffect(() => { // Carregar períodos disponíveis da API carregarPeriodosDisponiveis(); }, []); const carregarPeriodosDisponiveis = async () => { try { const response = await fetch('/api/dre-filial-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 filiais únicas const filiaisUnicas = ordenarFiliaisNumericamente([ ...new Set( dadosCompletos .map((item: DREItem) => item.filial || item.codfilial) .filter(Boolean) ), ] as string[]); setOpcoesFiliais(filiaisUnicas); // Inicializar com todas as filiais selecionadas setFiliaisSelecionadas(filiaisUnicas); // Inicializar filtros de período com o ano corrente const agora = new Date(); const anoAtual = agora.getFullYear(); const mesAtual = String(agora.getMonth() + 1).padStart(2, '0'); const periodoAtual = `${anoAtual}-${mesAtual}`; const primeiroMesAno = `${anoAtual}-01`; // Verificar se os períodos existem nos dados disponíveis const periodoDeValido = periodosUnicos.includes(primeiroMesAno) ? primeiroMesAno : periodosUnicos[0] || primeiroMesAno; const periodoAteValido = periodosUnicos.includes(periodoAtual) ? periodoAtual : periodosUnicos[periodosUnicos.length - 1] || periodoAtual; setFiltros((prev) => ({ ...prev, periodoDe: periodoDeValido, periodoAte: periodoAteValido, })); // NÃO inicializar filtros do analítico - só serão definidos após clique em célula } catch (error) { console.error('Erro ao carregar períodos:', error); } }; const fetchData = async () => { try { setLoading(true); setError(null); const response = await fetch('/api/dre-filial-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) => { return item.data_competencia; }) ), ].sort() as string[]; setMesesDisponiveis(meses); } catch (err) { setError(err instanceof Error ? err.message : 'Erro desconhecido'); } finally { setLoading(false); } }; const formatCurrency = React.useCallback((value: string | number) => { const numValue = typeof value === 'string' ? parseFloat(value) : value; return numValue.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL', }); }, []); const formatCurrencyWithColor = React.useCallback( (value: string | number) => { const numValue = typeof value === 'string' ? parseFloat(value) : value; const formatted = formatCurrency(value); const isNegative = numValue < 0; return { formatted, isNegative }; }, [formatCurrency] ); // Função para lidar com clique nas linhas const handleRowClick = React.useCallback( ( row: HierarchicalRow, mesSelecionado?: string, filialSelecionada?: string ) => { console.log('🖱️ Clique na linha:', row); console.log('📅 Mês selecionado:', mesSelecionado); console.log('🏢 Filial selecionada:', filialSelecionada); // Linhas calculadas não devem abrir o componente analítico if (row.type === 'calculado') { console.log('⚠️ Linha calculada - não abre componente analítico'); return; } 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 // 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 hierarquia [grupo, conta] let codigoGrupoFiltro = ''; let codigoContaFiltro = ''; if (row.type === 'grupo' || row.type === 'conta') { // Buscar o CODGRUPO dos dados originais que correspondem a esta linha const itemsCorrespondentes = data.filter((item: DREItem) => { // Filtrar por período se um mês específico foi selecionado if (mesSelecionado && item.data_competencia !== mesSelecionado) { return false; } // Se é um totalizador (mesSelecionado presente mas filialSelecionada não), não filtrar por filial // Caso contrário, filtrar por filial se especificada if (filialSelecionada) { const itemFilial = item.filial || item.codfilial || ''; if (itemFilial !== filialSelecionada) { return false; } } if (row.type === 'grupo') { return ( item.codigo_grupo === row.codigo_grupo || item.codgrupo === row.codigo_grupo ); } else if (row.type === 'conta') { return ( (item.codigo_grupo === row.codigo_grupo || item.codgrupo === row.codigo_grupo) && item.codigo_conta === row.codigo_conta ); } return false; }); // Pegar o CODGRUPO do primeiro item encontrado if (itemsCorrespondentes.length > 0) { const primeiroItem = itemsCorrespondentes[0]; codigoGrupoFiltro = primeiroItem.codgrupo || primeiroItem.codigo_grupo || ''; } } // Filtrar por conta se for nível conta if (row.type === 'conta') { codigoContaFiltro = row.codigo_conta?.toString() || ''; } // Determinar CODFILIAL baseado na filial selecionada // Se é um totalizador (mesSelecionado presente mas filialSelecionada não), incluir todas as filiais selecionadas let codFilialFiltro = ''; if (filialSelecionada) { // Se a filial selecionada já é um código numérico, usar diretamente // Caso contrário, buscar o CODFILIAL correspondente ao nome da filial nos dados if (/^\d+$/.test(filialSelecionada)) { codFilialFiltro = filialSelecionada; } else { // Buscar o CODFILIAL correspondente ao nome da filial nos dados const itemComFilial = data.find((item: DREItem) => { const itemFilial = item.filial || item.codfilial || ''; return itemFilial === filialSelecionada; }); if (itemComFilial) { codFilialFiltro = itemComFilial.codfilial || filialSelecionada; } else { // Se não encontrar, tentar usar o próprio valor como código codFilialFiltro = filialSelecionada; } } } else if (mesSelecionado) { // Se mesSelecionado está presente mas filialSelecionada não, é um totalizador // Incluir todas as filiais selecionadas (ou todas disponíveis se nenhuma estiver selecionada) const filiaisParaTotalizador = filiaisSelecionadas.length > 0 ? filiaisSelecionadas : opcoesFiliais; // Converter nomes de filiais para códigos CODFILIAL const codigosFiliais = filiaisParaTotalizador .map((filial) => { if (/^\d+$/.test(filial)) { return filial; } else { const itemComFilial = data.find((item: DREItem) => { const itemFilial = item.filial || item.codfilial || ''; return itemFilial === filial; }); return itemComFilial?.codfilial || filial; } }) .filter(Boolean); // Passar como string separada por vírgula para a API processar com IN codFilialFiltro = codigosFiliais.join(','); console.log( '📊 Totalizador: incluindo todas as filiais selecionadas:', codigosFiliais ); } const novosFiltros = { dataInicio: dataInicioFiltro, dataFim: dataFimFiltro, centroCusto: '', // Não aplicável na hierarquia filial codigoGrupo: codigoGrupoFiltro, codigoSubgrupo: '', // Não aplicável na hierarquia filial codigoConta: codigoContaFiltro, codFilial: codFilialFiltro, // Vazio para totalizador = todas as filiais do mês linhaSelecionada: row.grupo || row.conta || '', excluirCentroCusto: '', excluirCodigoConta: '', codigosCentrosCustoSelecionados: '', codigosContasSelecionadas: '', }; console.log('🎯 Novos filtros para analítico:', novosFiltros); console.log( '📊 É totalizador?', mesSelecionado && !filialSelecionada ? 'SIM' : 'NÃO' ); setAnaliticoFiltros(novosFiltros); }, [data, filiaisSelecionadas, opcoesFiliais] ); const toggleGrupo = useCallback((codigoGrupo: string) => { setExpandedGrupos((prev) => { const newExpanded = new Set(prev); if (newExpanded.has(codigoGrupo)) { newExpanded.delete(codigoGrupo); } else { newExpanded.add(codigoGrupo); } return newExpanded; }); }, []); const toggleFilial = (filial: string) => { setFiliaisSelecionadas((prev) => { if (prev.includes(filial)) { return prev.filter((f) => f !== filial); } else { return [...prev, filial]; } }); }; const selecionarTodasFiliais = () => { setFiliaisSelecionadas(opcoesFiliais); }; const limparFiliais = () => { setFiliaisSelecionadas([]); }; // Função auxiliar para calcular valores por mês const calcularValoresPorMes = React.useCallback( (items: DREItem[]): Record => { const valoresPorMes: Record = {}; mesesDisponiveis.forEach((mes) => { valoresPorMes[mes] = 0; }); items.forEach((item) => { const anoMes = item.data_competencia; if (anoMes && valoresPorMes[anoMes] !== undefined) { valoresPorMes[anoMes] += parseFloat(item.valor); } }); return valoresPorMes; }, [mesesDisponiveis] ); // Função auxiliar para calcular valores por mês e por filial const calcularValoresPorMesPorFilial = React.useCallback( (items: DREItem[]): Record> => { const valoresPorMesPorFilial: Record> = {}; // Extrair filiais únicas dos próprios items para evitar dependência de arrays externos const filiaisDisponiveis = [ ...new Set( items.map((item) => item.filial || item.codfilial).filter(Boolean) ), ] as string[]; mesesDisponiveis.forEach((mes) => { valoresPorMesPorFilial[mes] = {}; filiaisDisponiveis.forEach((filial) => { valoresPorMesPorFilial[mes][filial] = 0; }); }); items.forEach((item) => { const anoMes = item.data_competencia; const filial = item.filial || item.codfilial || ''; if (anoMes && valoresPorMesPorFilial[anoMes] && filial) { if (!valoresPorMesPorFilial[anoMes][filial]) { valoresPorMesPorFilial[anoMes][filial] = 0; } valoresPorMesPorFilial[anoMes][filial] += parseFloat(item.valor); } }); return valoresPorMesPorFilial; }, [mesesDisponiveis] ); // Memoizar valores do grupo 01 por mês para evitar recálculos repetidos const valoresGrupo01PorMesMemo = React.useMemo(() => { const valores: Record = {}; mesesDisponiveis.forEach((mes) => { valores[mes] = data .filter((item) => { const codgrupo = item.codgrupo || item.codigo_grupo || ''; return codgrupo === '01' && item.data_competencia === mes; }) .reduce((sum, item) => sum + parseFloat(item.valor), 0); }); return valores; }, [data, mesesDisponiveis]); // Memoizar valores do grupo 01 por mês e por filial const valoresGrupo01PorMesPorFilialMemo = React.useMemo(() => { const valores: Record> = {}; // Extrair filiais únicas dos próprios dados para evitar dependência de arrays externos const filiaisDisponiveis = [ ...new Set( data.map((item) => item.filial || item.codfilial).filter(Boolean) ), ] as string[]; mesesDisponiveis.forEach((mes) => { valores[mes] = {}; filiaisDisponiveis.forEach((filial) => { valores[mes][filial] = data .filter((item) => { const codgrupo = item.codgrupo || item.codigo_grupo || ''; const itemFilial = item.filial || item.codfilial || ''; return ( codgrupo === '01' && item.data_competencia === mes && itemFilial === filial ); }) .reduce((sum, item) => sum + parseFloat(item.valor), 0); }); }); return valores; }, [data, mesesDisponiveis]); // Função para calcular percentuais baseado no CODGRUPO 01 (FATURAMENTO LÍQUIDO) const calcularPercentuaisPorMes = React.useCallback( ( valoresPorMes: Record, codigoGrupo?: string ): Record => { const percentuais: Record = {}; // Se for CODGRUPO 01, sempre retornar 100% if (codigoGrupo === '01') { mesesDisponiveis.forEach((mes) => { percentuais[mes] = 100; }); return percentuais; } // Usar valores memoizados do grupo 01 Object.keys(valoresPorMes).forEach((mes) => { const valorAtual = valoresPorMes[mes]; const valorGrupo01 = valoresGrupo01PorMesMemo[mes] || 0; if (valorGrupo01 !== 0) { percentuais[mes] = (valorAtual / valorGrupo01) * 100; } else { percentuais[mes] = 0; } }); return percentuais; }, [mesesDisponiveis, valoresGrupo01PorMesMemo] ); // Função para calcular percentuais por mês e por filial baseado no CODGRUPO 01 const calcularPercentuaisPorMesPorFilial = React.useCallback( ( valoresPorMesPorFilial: Record>, codigoGrupo?: string ): Record> => { const percentuaisPorMesPorFilial: Record< string, Record > = {}; // Extrair filiais únicas dos próprios valoresPorMesPorFilial para evitar dependência de arrays externos const primeiroMes = mesesDisponiveis[0] || ''; const filiaisDisponiveis = Object.keys( valoresPorMesPorFilial[primeiroMes] || {} ); // Se for CODGRUPO 01, sempre retornar 100% para todas as filiais if (codigoGrupo === '01') { mesesDisponiveis.forEach((mes) => { percentuaisPorMesPorFilial[mes] = {}; filiaisDisponiveis.forEach((filial) => { percentuaisPorMesPorFilial[mes][filial] = 100; }); }); return percentuaisPorMesPorFilial; } // Usar valores memoizados do grupo 01 mesesDisponiveis.forEach((mes) => { percentuaisPorMesPorFilial[mes] = {}; filiaisDisponiveis.forEach((filial) => { const valorAtual = valoresPorMesPorFilial[mes]?.[filial] || 0; const valorGrupo01 = valoresGrupo01PorMesPorFilialMemo[mes]?.[filial] || 0; if (valorGrupo01 !== 0) { percentuaisPorMesPorFilial[mes][filial] = (valorAtual / valorGrupo01) * 100; } else { percentuaisPorMesPorFilial[mes][filial] = 0; } }); }); return percentuaisPorMesPorFilial; }, [mesesDisponiveis, valoresGrupo01PorMesPorFilialMemo] ); // Memoizar total do grupo 01 const totalGrupo01Memo = React.useMemo(() => { return data .filter((item) => { const codgrupo = item.codgrupo || item.codigo_grupo || ''; return codgrupo === '01'; }) .reduce((sum, item) => sum + parseFloat(item.valor), 0); }, [data]); // Função para calcular percentual do total baseado no CODGRUPO 01 const calcularPercentualTotal = React.useCallback( (total: number, codigoGrupo?: string): number => { // Se for CODGRUPO 01, sempre retornar 100% if (codigoGrupo === '01') { return 100; } // Usar total memoizado do grupo 01 if (totalGrupo01Memo !== 0) { return (total / totalGrupo01Memo) * 100; } else { return 0; } }, [totalGrupo01Memo] ); const buildHierarchicalData = React.useCallback((): HierarchicalRow[] => { const rows: HierarchicalRow[] = []; // Hierarquia simplificada: [grupo, conta] // Agrupar por CODGRUPO const gruposPorCodigo = data.reduce((acc, item) => { const codgrupo = item.codgrupo || item.codigo_grupo || ''; if (!codgrupo) return acc; if (!acc[codgrupo]) { acc[codgrupo] = []; } acc[codgrupo].push(item); return acc; }, {} as Record); // Calcular valores por grupo para linhas calculadas const valoresPorGrupo: Record> = {}; Object.keys(gruposPorCodigo).forEach((codgrupo) => { valoresPorGrupo[codgrupo] = calcularValoresPorMes( gruposPorCodigo[codgrupo] ); }); // Ordenar por CODGRUPO (numericamente) const sortedGrupos = Object.entries(gruposPorCodigo).sort( ([codA], [codB]) => { const numA = parseInt(codA) || 0; const numB = parseInt(codB) || 0; if (numA !== numB) { return numA - numB; } return codA.localeCompare(codB); } ); // Variáveis para armazenar valores da MARGEM BRUTA (para uso no RESULTADO LOJA) let valoresMargemBrutaPorMes: Record | null = null; let valoresMargemBrutaPorMesPorFilial: Record< string, Record > | null = null; let totalMargemBruta: number | null = null; sortedGrupos.forEach(([codgrupo, items], index) => { // Calcular total do grupo const totalGrupo = items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); const valoresGrupoPorMes = calcularValoresPorMes(items); const valoresGrupoPorMesPorFilial = calcularValoresPorMesPorFilial(items); // Linha do grupo (Level 0) rows.push({ type: 'grupo', level: 0, grupo: items[0]?.grupo || codgrupo, codigo_grupo: codgrupo, total: totalGrupo, isExpanded: expandedGrupos.has(codgrupo), valoresPorMes: valoresGrupoPorMes, valoresPorMesPorFilial: valoresGrupoPorMesPorFilial, percentuaisPorMes: calcularPercentuaisPorMes( valoresGrupoPorMes, codgrupo ), percentuaisPorMesPorFilial: calcularPercentuaisPorMesPorFilial( valoresGrupoPorMesPorFilial, codgrupo ), percentualTotal: calcularPercentualTotal(totalGrupo, codgrupo), }); if (expandedGrupos.has(codgrupo)) { // Agrupar por conta dentro do grupo const contas = items.reduce((acc, item) => { const conta = item.conta || ''; if (!conta) return acc; if (!acc[conta]) { acc[conta] = []; } acc[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; } return contaA.localeCompare(contaB); } ); sortedContas.forEach(([conta, contaItems]) => { const totalConta = contaItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); const valoresContaPorMes = calcularValoresPorMes(contaItems); const valoresContaPorMesPorFilial = calcularValoresPorMesPorFilial(contaItems); // Linha da conta (Level 1) rows.push({ type: 'conta', level: 1, grupo: items[0]?.grupo || codgrupo, codigo_grupo: codgrupo, conta, codigo_conta: contaItems[0]?.codigo_conta, total: totalConta, isExpanded: false, valoresPorMes: valoresContaPorMes, valoresPorMesPorFilial: valoresContaPorMesPorFilial, percentuaisPorMes: calcularPercentuaisPorMes( valoresContaPorMes, codgrupo ), percentuaisPorMesPorFilial: calcularPercentuaisPorMesPorFilial( valoresContaPorMesPorFilial, codgrupo ), percentualTotal: calcularPercentualTotal(totalConta, codgrupo), }); }); } // Adicionar linha calculada "MARGEM BRUTA" após o grupo 02 // Verificar se é o último grupo ou se o próximo grupo é maior que 02 const proximoCodigo = sortedGrupos[index + 1]?.[0]; const proximoNumero = proximoCodigo ? parseInt(proximoCodigo) : 999; if ( codgrupo === '02' || (parseInt(codgrupo) === 2 && proximoNumero > 2) ) { // Calcular MARGEM BRUTA = CODGRUPO 01 + CODGRUPO 02 const valoresGrupo01 = valoresPorGrupo['01'] || {}; const valoresGrupo02 = valoresPorGrupo['02'] || {}; // Calcular valores por mês para MARGEM BRUTA const valoresMargemPorMes: Record = {}; mesesDisponiveis.forEach((mes) => { const valor01 = valoresGrupo01[mes] || 0; const valor02 = valoresGrupo02[mes] || 0; valoresMargemPorMes[mes] = valor01 + valor02; }); // Calcular valores por mês e por filial para MARGEM BRUTA const valoresMargemPorMesPorFilial: Record< string, Record > = {}; const valoresGrupo01PorFilial = gruposPorCodigo['01'] ? calcularValoresPorMesPorFilial(gruposPorCodigo['01']) : {}; const valoresGrupo02PorFilial = gruposPorCodigo['02'] ? calcularValoresPorMesPorFilial(gruposPorCodigo['02']) : {}; // Extrair filiais únicas dos valores calculados const primeiroMes = mesesDisponiveis[0] || ''; const filiaisDisponiveis = [ ...new Set([ ...Object.keys(valoresGrupo01PorFilial[primeiroMes] || {}), ...Object.keys(valoresGrupo02PorFilial[primeiroMes] || {}), ]), ]; mesesDisponiveis.forEach((mes) => { valoresMargemPorMesPorFilial[mes] = {}; filiaisDisponiveis.forEach((filial) => { const valor01 = valoresGrupo01PorFilial[mes]?.[filial] || 0; const valor02 = valoresGrupo02PorFilial[mes]?.[filial] || 0; valoresMargemPorMesPorFilial[mes][filial] = valor01 + valor02; }); }); // Calcular total const totalMargem = Object.values(valoresMargemPorMes).reduce( (sum, val) => sum + val, 0 ); // Armazenar valores da MARGEM BRUTA para uso posterior no RESULTADO LOJA valoresMargemBrutaPorMes = valoresMargemPorMes; valoresMargemBrutaPorMesPorFilial = valoresMargemPorMesPorFilial; totalMargemBruta = totalMargem; // Adicionar linha calculada rows.push({ type: 'calculado', level: 0, grupo: 'MARGEM BRUTA', codigo_grupo: 'MARGEM', total: totalMargem, isExpanded: false, valoresPorMes: valoresMargemPorMes, valoresPorMesPorFilial: valoresMargemPorMesPorFilial, percentuaisPorMes: calcularPercentuaisPorMes( valoresMargemPorMes, 'MARGEM' ), percentuaisPorMesPorFilial: calcularPercentuaisPorMesPorFilial( valoresMargemPorMesPorFilial, 'MARGEM' ), percentualTotal: calcularPercentualTotal(totalMargem, 'MARGEM'), isCalculado: true, }); } // Adicionar linha calculada "RESULTADO LOJA" após o grupo 04 (DESPESAS) // Verificar se é o último grupo ou se o próximo grupo é maior que 04 const proximoCodigoDespesas = sortedGrupos[index + 1]?.[0]; const proximoNumeroDespesas = proximoCodigoDespesas ? parseInt(proximoCodigoDespesas) : 999; if ( (codgrupo === '04' || (parseInt(codgrupo) === 4 && proximoNumeroDespesas > 4)) && valoresMargemBrutaPorMes !== null && valoresMargemBrutaPorMesPorFilial !== null && totalMargemBruta !== null ) { // Calcular RESULTADO LOJA = MARGEM BRUTA + DESPESAS (grupo 04) const valoresGrupo04 = valoresPorGrupo['04'] || {}; // Calcular valores por mês para RESULTADO LOJA const valoresResultadoPorMes: Record = {}; mesesDisponiveis.forEach((mes) => { const valorMargem = valoresMargemBrutaPorMes![mes] || 0; const valorDespesas = valoresGrupo04[mes] || 0; valoresResultadoPorMes[mes] = valorMargem + valorDespesas; }); // Calcular valores por mês e por filial para RESULTADO LOJA const valoresResultadoPorMesPorFilial: Record< string, Record > = {}; const valoresGrupo04PorFilial = gruposPorCodigo['04'] ? calcularValoresPorMesPorFilial(gruposPorCodigo['04']) : {}; // Extrair filiais únicas dos valores calculados const primeiroMes = mesesDisponiveis[0] || ''; const filiaisDisponiveis = [ ...new Set([ ...Object.keys( valoresMargemBrutaPorMesPorFilial![primeiroMes] || {} ), ...Object.keys(valoresGrupo04PorFilial[primeiroMes] || {}), ]), ]; mesesDisponiveis.forEach((mes) => { valoresResultadoPorMesPorFilial[mes] = {}; filiaisDisponiveis.forEach((filial) => { const valorMargem = valoresMargemBrutaPorMesPorFilial![mes]?.[filial] || 0; const valorDespesas = valoresGrupo04PorFilial[mes]?.[filial] || 0; valoresResultadoPorMesPorFilial[mes][filial] = valorMargem + valorDespesas; }); }); // Calcular total const totalResultado = Object.values(valoresResultadoPorMes).reduce( (sum, val) => sum + val, 0 ); // Adicionar linha calculada rows.push({ type: 'calculado', level: 0, grupo: 'RESULTADO LOJA', codigo_grupo: 'RESULTADO', total: totalResultado, isExpanded: false, valoresPorMes: valoresResultadoPorMes, valoresPorMesPorFilial: valoresResultadoPorMesPorFilial, percentuaisPorMes: calcularPercentuaisPorMes( valoresResultadoPorMes, 'RESULTADO' ), percentuaisPorMesPorFilial: calcularPercentuaisPorMesPorFilial( valoresResultadoPorMesPorFilial, 'RESULTADO' ), percentualTotal: calcularPercentualTotal(totalResultado, 'RESULTADO'), isCalculado: true, }); } }); return rows; }, [ data, mesesDisponiveis, expandedGrupos, calcularValoresPorMes, calcularValoresPorMesPorFilial, calcularPercentuaisPorMes, calcularPercentuaisPorMesPorFilial, calcularPercentualTotal, ]); const getRowStyle = React.useCallback( (row: HierarchicalRow) => { const baseStyle = 'transition-all duration-200 hover:bg-gradient-to-r hover:from-blue-50/30 hover:to-indigo-50/30'; const linhaId = `${row.type}-${row.codigo_grupo || ''}-${ row.codigo_conta || '' }`; const isSelected = linhaSelecionada === linhaId; 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': 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 'calculado': return `${style} bg-gradient-to-r from-purple-50/30 to-pink-50/30 font-bold text-purple-900 border-b-2 border-purple-300 italic`; case 'conta': return `${style} bg-white font-normal text-gray-600`; default: return style; } }, [linhaSelecionada] ); const getFixedCellBackground = React.useCallback( (row: HierarchicalRow): string => { const linhaId = `${row.type}-${row.codigo_grupo || ''}-${ row.codigo_conta || '' }`; const isSelected = linhaSelecionada === linhaId; if (isSelected) { return 'bg-gradient-to-r from-green-100 to-emerald-100'; } switch (row.type) { case 'grupo': return 'bg-gradient-to-r from-blue-50 to-indigo-50'; case 'calculado': return 'bg-gradient-to-r from-purple-50 to-pink-50'; case 'conta': return 'bg-white'; default: return 'bg-white'; } }, [linhaSelecionada] ); const getIndentStyle = React.useCallback((level: number) => { return { paddingLeft: `${level * 20}px` }; }, []); const renderCellContent = React.useCallback( (row: HierarchicalRow) => { switch (row.type) { case 'grupo': return (
); case 'calculado': return (
{/* = */}
{row.grupo}
); case 'conta': return (
); default: return null; } }, [toggleGrupo, handleRowClick] ); const toggleExpandAll = () => { if (isAllExpanded) { setExpandedGrupos(new Set()); setIsAllExpanded(false); } else { const todosGrupos = [ ...new Set( data.map((item) => item.codgrupo || item.codigo_grupo).filter(Boolean) ), ]; setExpandedGrupos(new Set(todosGrupos)); setIsAllExpanded(true); } }; const aplicarFiltros = async () => { setIsFilterOpen(false); setTimeout(async () => { try { setLoading(true); setError(null); const response = await fetch('/api/dre-filial-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 filial (multi-seleção) if (filiaisSelecionadas.length > 0) { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => { const filialItem = item.filial || item.codfilial || ''; return filiaisSelecionadas.includes(filialItem); }); } setDadosFiltrados(dadosFiltrados); setData(dadosFiltrados); setFiltrosAplicados(true); // Limpar filtros do analítico ao aplicar novos filtros na tabela setAnaliticoFiltros({ dataInicio: '', dataFim: '', centroCusto: '', codigoGrupo: '', codigoSubgrupo: '', codigoConta: '', codFilial: '', linhaSelecionada: '', excluirCentroCusto: '', excluirCodigoConta: '', codigosCentrosCustoSelecionados: '', codigosContasSelecionadas: '', }); // Extrair meses únicos dos dados filtrados const meses = [ ...new Set( dadosFiltrados.map((item: DREItem) => item.data_competencia) ), ].sort() as string[]; setMesesDisponiveis(meses); } catch (err) { setError(err instanceof Error ? err.message : 'Erro desconhecido'); } finally { setLoading(false); } }, 100); }; 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, filial: 'Todas', }); setFiliaisSelecionadas([]); setData([]); setDadosFiltrados([]); setFiltrosAplicados(false); setMesesDisponiveis([]); setIsAllExpanded(false); setIsFilterOpen(false); // Limpar filtros do analítico também setAnaliticoFiltros({ dataInicio: '', dataFim: '', centroCusto: '', codigoGrupo: '', codigoSubgrupo: '', codigoConta: '', codFilial: '', linhaSelecionada: '', excluirCentroCusto: '', excluirCodigoConta: '', codigosCentrosCustoSelecionados: '', codigosContasSelecionadas: '', }); carregarPeriodosDisponiveis(); }; // Função para construir dados hierárquicos completamente expandidos (para exportação) const buildHierarchicalDataCompleta = React.useCallback((): HierarchicalRow[] => { const rows: HierarchicalRow[] = []; // Hierarquia simplificada: [grupo, conta] // Agrupar por CODGRUPO const gruposPorCodigo = data.reduce((acc, item) => { const codgrupo = item.codgrupo || item.codigo_grupo || ''; if (!codgrupo) return acc; if (!acc[codgrupo]) { acc[codgrupo] = []; } acc[codgrupo].push(item); return acc; }, {} as Record); // Calcular valores por grupo para linhas calculadas const valoresPorGrupo: Record> = {}; Object.keys(gruposPorCodigo).forEach((codgrupo) => { valoresPorGrupo[codgrupo] = calcularValoresPorMes( gruposPorCodigo[codgrupo] ); }); // Ordenar por CODGRUPO (numericamente) const sortedGrupos = Object.entries(gruposPorCodigo).sort( ([codA], [codB]) => { const numA = parseInt(codA) || 0; const numB = parseInt(codB) || 0; if (numA !== numB) { return numA - numB; } return codA.localeCompare(codB); } ); // Variáveis para armazenar valores da MARGEM BRUTA (para uso no RESULTADO LOJA) let valoresMargemBrutaPorMes: Record | null = null; let valoresMargemBrutaPorMesPorFilial: Record< string, Record > | null = null; let totalMargemBruta: number | null = null; sortedGrupos.forEach(([codgrupo, items], index) => { // Calcular total do grupo const totalGrupo = items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); const valoresGrupoPorMes = calcularValoresPorMes(items); const valoresGrupoPorMesPorFilial = calcularValoresPorMesPorFilial(items); // Linha do grupo (Level 0) rows.push({ type: 'grupo', level: 0, grupo: items[0]?.grupo || codgrupo, codigo_grupo: codgrupo, total: totalGrupo, isExpanded: true, // Sempre expandido na exportação valoresPorMes: valoresGrupoPorMes, valoresPorMesPorFilial: valoresGrupoPorMesPorFilial, percentuaisPorMes: calcularPercentuaisPorMes( valoresGrupoPorMes, codgrupo ), percentuaisPorMesPorFilial: calcularPercentuaisPorMesPorFilial( valoresGrupoPorMesPorFilial, codgrupo ), percentualTotal: calcularPercentualTotal(totalGrupo, codgrupo), }); // SEMPRE incluir todas as contas (não depende de expandedGrupos) // Agrupar por conta dentro do grupo const contas = items.reduce((acc, item) => { const conta = item.conta || ''; if (!conta) return acc; if (!acc[conta]) { acc[conta] = []; } acc[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; } return contaA.localeCompare(contaB); } ); sortedContas.forEach(([conta, contaItems]) => { const totalConta = contaItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); const valoresContaPorMes = calcularValoresPorMes(contaItems); const valoresContaPorMesPorFilial = calcularValoresPorMesPorFilial(contaItems); // Linha da conta (Level 1) rows.push({ type: 'conta', level: 1, grupo: items[0]?.grupo || codgrupo, codigo_grupo: codgrupo, conta, codigo_conta: contaItems[0]?.codigo_conta, total: totalConta, isExpanded: false, valoresPorMes: valoresContaPorMes, valoresPorMesPorFilial: valoresContaPorMesPorFilial, percentuaisPorMes: calcularPercentuaisPorMes( valoresContaPorMes, codgrupo ), percentuaisPorMesPorFilial: calcularPercentuaisPorMesPorFilial( valoresContaPorMesPorFilial, codgrupo ), percentualTotal: calcularPercentualTotal(totalConta, codgrupo), }); }); // Adicionar linha calculada "MARGEM BRUTA" após o grupo 02 // Verificar se é o último grupo ou se o próximo grupo é maior que 02 const proximoCodigo = sortedGrupos[index + 1]?.[0]; const proximoNumero = proximoCodigo ? parseInt(proximoCodigo) : 999; if ( codgrupo === '02' || (parseInt(codgrupo) === 2 && proximoNumero > 2) ) { // Calcular MARGEM BRUTA = CODGRUPO 01 + CODGRUPO 02 const valoresGrupo01 = valoresPorGrupo['01'] || {}; const valoresGrupo02 = valoresPorGrupo['02'] || {}; // Calcular valores por mês para MARGEM BRUTA const valoresMargemPorMes: Record = {}; mesesDisponiveis.forEach((mes) => { const valor01 = valoresGrupo01[mes] || 0; const valor02 = valoresGrupo02[mes] || 0; valoresMargemPorMes[mes] = valor01 + valor02; }); // Calcular valores por mês e por filial para MARGEM BRUTA const valoresMargemPorMesPorFilial: Record< string, Record > = {}; const valoresGrupo01PorFilial = gruposPorCodigo['01'] ? calcularValoresPorMesPorFilial(gruposPorCodigo['01']) : {}; const valoresGrupo02PorFilial = gruposPorCodigo['02'] ? calcularValoresPorMesPorFilial(gruposPorCodigo['02']) : {}; // Extrair filiais únicas dos valores calculados const primeiroMes = mesesDisponiveis[0] || ''; const filiaisDisponiveis = [ ...new Set([ ...Object.keys(valoresGrupo01PorFilial[primeiroMes] || {}), ...Object.keys(valoresGrupo02PorFilial[primeiroMes] || {}), ]), ]; mesesDisponiveis.forEach((mes) => { valoresMargemPorMesPorFilial[mes] = {}; filiaisDisponiveis.forEach((filial) => { const valor01 = valoresGrupo01PorFilial[mes]?.[filial] || 0; const valor02 = valoresGrupo02PorFilial[mes]?.[filial] || 0; valoresMargemPorMesPorFilial[mes][filial] = valor01 + valor02; }); }); // Calcular total const totalMargem = Object.values(valoresMargemPorMes).reduce( (sum, val) => sum + val, 0 ); // Armazenar valores da MARGEM BRUTA para uso posterior no RESULTADO LOJA valoresMargemBrutaPorMes = valoresMargemPorMes; valoresMargemBrutaPorMesPorFilial = valoresMargemPorMesPorFilial; totalMargemBruta = totalMargem; // Adicionar linha calculada rows.push({ type: 'calculado', level: 0, grupo: 'MARGEM BRUTA', codigo_grupo: 'MARGEM', total: totalMargem, isExpanded: false, valoresPorMes: valoresMargemPorMes, valoresPorMesPorFilial: valoresMargemPorMesPorFilial, percentuaisPorMes: calcularPercentuaisPorMes( valoresMargemPorMes, 'MARGEM' ), percentuaisPorMesPorFilial: calcularPercentuaisPorMesPorFilial( valoresMargemPorMesPorFilial, 'MARGEM' ), percentualTotal: calcularPercentualTotal(totalMargem, 'MARGEM'), isCalculado: true, }); } // Adicionar linha calculada "RESULTADO LOJA" após o grupo 04 (DESPESAS) // Verificar se é o último grupo ou se o próximo grupo é maior que 04 const proximoCodigoDespesas = sortedGrupos[index + 1]?.[0]; const proximoNumeroDespesas = proximoCodigoDespesas ? parseInt(proximoCodigoDespesas) : 999; if ( (codgrupo === '04' || (parseInt(codgrupo) === 4 && proximoNumeroDespesas > 4)) && valoresMargemBrutaPorMes !== null && valoresMargemBrutaPorMesPorFilial !== null && totalMargemBruta !== null ) { // Calcular RESULTADO LOJA = MARGEM BRUTA + DESPESAS (grupo 04) const valoresGrupo04 = valoresPorGrupo['04'] || {}; // Calcular valores por mês para RESULTADO LOJA const valoresResultadoPorMes: Record = {}; mesesDisponiveis.forEach((mes) => { const valorMargem = valoresMargemBrutaPorMes![mes] || 0; const valorDespesas = valoresGrupo04[mes] || 0; valoresResultadoPorMes[mes] = valorMargem + valorDespesas; }); // Calcular valores por mês e por filial para RESULTADO LOJA const valoresResultadoPorMesPorFilial: Record< string, Record > = {}; const valoresGrupo04PorFilial = gruposPorCodigo['04'] ? calcularValoresPorMesPorFilial(gruposPorCodigo['04']) : {}; // Extrair filiais únicas dos valores calculados const primeiroMes = mesesDisponiveis[0] || ''; const filiaisDisponiveis = [ ...new Set([ ...Object.keys( valoresMargemBrutaPorMesPorFilial![primeiroMes] || {} ), ...Object.keys(valoresGrupo04PorFilial[primeiroMes] || {}), ]), ]; mesesDisponiveis.forEach((mes) => { valoresResultadoPorMesPorFilial[mes] = {}; filiaisDisponiveis.forEach((filial) => { const valorMargem = valoresMargemBrutaPorMesPorFilial![mes]?.[filial] || 0; const valorDespesas = valoresGrupo04PorFilial[mes]?.[filial] || 0; valoresResultadoPorMesPorFilial[mes][filial] = valorMargem + valorDespesas; }); }); // Calcular total const totalResultado = Object.values(valoresResultadoPorMes).reduce( (sum, val) => sum + val, 0 ); // Adicionar linha calculada rows.push({ type: 'calculado', level: 0, grupo: 'RESULTADO LOJA', codigo_grupo: 'RESULTADO', total: totalResultado, isExpanded: false, valoresPorMes: valoresResultadoPorMes, valoresPorMesPorFilial: valoresResultadoPorMesPorFilial, percentuaisPorMes: calcularPercentuaisPorMes( valoresResultadoPorMes, 'RESULTADO' ), percentuaisPorMesPorFilial: calcularPercentuaisPorMesPorFilial( valoresResultadoPorMesPorFilial, 'RESULTADO' ), percentualTotal: calcularPercentualTotal( totalResultado, 'RESULTADO' ), isCalculado: true, }); } }); return rows; }, [ data, mesesDisponiveis, calcularValoresPorMes, calcularValoresPorMesPorFilial, calcularPercentuaisPorMes, calcularPercentuaisPorMesPorFilial, calcularPercentualTotal, ]); const exportarXLSX = () => { if (!data.length) { console.log('⚠️ Nenhum dado para exportar'); return; } console.log('📊 Exportando TODOS os dados expandidos para XLSX...'); const dadosCompletosExpandidos = buildHierarchicalDataCompleta(); // Determinar filiais para exportação (mesma lógica da tabela) const filiaisParaExportacao = ordenarFiliaisNumericamente( filtrosAplicados && filiaisSelecionadas.length > 0 ? filiaisSelecionadas : opcoesFiliais.length > 0 ? opcoesFiliais : [''] ); const dadosExportacao = dadosCompletosExpandidos.map((row, index) => { const linha: any = { Linha: index + 1, Tipo: row.type, Nível: row.level, Grupo: row.grupo || '', 'Código Grupo': row.codigo_grupo || '', Conta: row.conta || '', 'Código Conta': row.codigo_conta || '', Total: row.total || 0, }; // Para cada mês, adicionar colunas para cada filial e depois o totalizador mesesDisponiveis.forEach((mes) => { // Colunas por filial filiaisParaExportacao.forEach((filial: string) => { // Só incluir se a filial estiver selecionada ou se não houver filtros aplicados if ( !filtrosAplicados || filiaisSelecionadas.length === 0 || filiaisSelecionadas.includes(filial) ) { const valorFilial = filial && row.valoresPorMesPorFilial?.[mes]?.[filial] !== undefined ? row.valoresPorMesPorFilial[mes][filial] : 0; const percentualFilial = filial && row.percentuaisPorMesPorFilial?.[mes]?.[filial] !== undefined ? row.percentuaisPorMesPorFilial[mes][filial] : 0; linha[`${mes} - Filial ${filial} - Valor`] = valorFilial; linha[`${mes} - Filial ${filial} - %`] = percentualFilial; } }); // Totalizador do mês const valorTotal = row.valoresPorMes?.[mes] || 0; const percentualTotal = row.percentuaisPorMes?.[mes] || 0; linha[`${mes} - Total - Valor`] = valorTotal; linha[`${mes} - Total - %`] = percentualTotal; }); return linha; }); const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet(dadosExportacao); const colWidths = [ { wch: 8 }, // Linha { wch: 15 }, // Tipo { wch: 8 }, // Nível { wch: 25 }, // Grupo { wch: 15 }, // Código Grupo { wch: 35 }, // Conta { wch: 12 }, // Código Conta { wch: 15 }, // Total ]; // Larguras das colunas por mês (filiais + totalizador) mesesDisponiveis.forEach(() => { filiaisParaExportacao.forEach((filial: string) => { if ( !filtrosAplicados || filiaisSelecionadas.length === 0 || filiaisSelecionadas.includes(filial) ) { colWidths.push({ wch: 18 }); // Valor Filial colWidths.push({ wch: 12 }); // % Filial } }); colWidths.push({ wch: 18 }); // Valor Total colWidths.push({ wch: 12 }); // % Total }); ws['!cols'] = colWidths; XLSX.utils.book_append_sheet(wb, ws, 'DRE Filial Completo'); const resumoData = [ { Informação: 'Período', Valor: `${filtros.periodoDe} a ${filtros.periodoAte}`, }, { Informação: 'Filiais', Valor: filiaisSelecionadas.length > 0 ? filiaisSelecionadas.join(', ') : 'Todas', }, { 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'); const dataAtual = new Date().toISOString().split('T')[0]; const nomeArquivo = `DRE_Filial_Completo_${dataAtual}.xlsx`; XLSX.writeFile(wb, nomeArquivo); console.log('✅ Arquivo XLSX completo exportado:', nomeArquivo); }; // Memoizar dados hierárquicos para evitar recálculos desnecessários const hierarchicalData = React.useMemo(() => { if (!data.length || !mesesDisponiveis.length) { return []; } return buildHierarchicalData(); }, [buildHierarchicalData]); return (
{/* Header Section */}

Despesa Filial

Demonstração do Resultado do Exercício

{/* Controles */}
Filtros Ajuste os critérios e clique em Pesquisar para atualizar a visão.
{/* Período */}
{/* Filial */}
setFiltroFilial(e.target.value)} className="h-8 text-sm" />
{opcoesFiliais .filter((filial) => { if (!filtroFilial) return true; const termo = filtroFilial.toLowerCase(); return filial.toLowerCase().includes(termo); }) .map((filial) => (
toggleFilial(filial)} />
))}
{filiaisSelecionadas.length > 0 && (
{filiaisSelecionadas.length} filial(is) selecionada(s)
)}
{/* 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 && (
{/* Table Header */} {mesesDisponiveis.map((mes) => { const filiaisParaMes = filtrosAplicados && filiaisSelecionadas.length > 0 ? filiaisSelecionadas : opcoesFiliais.length > 0 ? opcoesFiliais : ['']; return ( {/* Cabeçalhos de filiais para este mês */} {filiaisParaMes.map((filial: string) => ( ))} {/* Cabeçalhos de totalizador para este mês */} ); })} {/* Table Body */} {hierarchicalData.map((row, index) => { const linhaId = `${row.type}-${row.codigo_grupo || ''}-${ row.codigo_conta || '' }`; const isSelected = linhaSelecionada === linhaId; return ( ); })}
Descrição {mes} {filial && ( <>
Filial - {filial} )}
% {filial && ( <>
Filial - {filial} )}
{mes}
Total
%
Total
Total %
)} {/* Componente Analítico - Sempre visível, mas só carrega dados após clique */}
); }