"use client"; import { LoaderPinwheel, ChevronDown, ChevronRight, Filter, Maximize2, Minimize2, Download } from "lucide-react"; import React, { useEffect, useState, useCallback, 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; 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) => ((filtrosAplicados && filiaisSelecionadas.length > 0) ? filiaisSelecionadas : (opcoesFiliais.length > 0 ? opcoesFiliais : [''])).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)}%` ) : ( - )} ); }) )} {/* 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 [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 = [...new Set(dadosCompletos.map((item: DREItem) => item.filial || item.codfilial).filter(Boolean))].sort() 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); // 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; } 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 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; } } } 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, linhaSelecionada: row.grupo || row.conta || "", excluirCentroCusto: "", excluirCodigoConta: "", codigosCentrosCustoSelecionados: "", codigosContasSelecionadas: "", }; console.log('🎯 Novos filtros para analítico:', novosFiltros); setAnaliticoFiltros(novosFiltros); }, [data]); 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> = {}; // Usar filiais selecionadas se houver filtros aplicados, senão usar todas as opções disponíveis const filiaisDisponiveis = (filtrosAplicados && filiaisSelecionadas.length > 0) ? filiaisSelecionadas : (opcoesFiliais.length > 0 ? opcoesFiliais : [...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 && filiaisDisponiveis.includes(filial)) { if (!valoresPorMesPorFilial[anoMes][filial]) { valoresPorMesPorFilial[anoMes][filial] = 0; } valoresPorMesPorFilial[anoMes][filial] += parseFloat(item.valor); } }); return valoresPorMesPorFilial; }, [mesesDisponiveis, opcoesFiliais, filtrosAplicados, filiaisSelecionadas]); // 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> = {}; // Usar filiais selecionadas se houver filtros aplicados, senão usar todas as opções disponíveis const filiaisDisponiveis = (filtrosAplicados && filiaisSelecionadas.length > 0) ? filiaisSelecionadas : (opcoesFiliais.length > 0 ? opcoesFiliais : [...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, opcoesFiliais, filtrosAplicados, filiaisSelecionadas]); // 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> = {}; // Usar filiais selecionadas se houver filtros aplicados, senão usar todas as opções disponíveis const filiaisDisponiveis = (filtrosAplicados && filiaisSelecionadas.length > 0) ? filiaisSelecionadas : (opcoesFiliais.length > 0 ? opcoesFiliais : Object.keys(valoresPorMesPorFilial[mesesDisponiveis[0] || ""] || {})); // 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, opcoesFiliais, valoresGrupo01PorMesPorFilialMemo, filtrosAplicados, filiaisSelecionadas]); // 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); }); 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 DE LOJA" 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 DE LOJA = CODGRUPO 01 - CODGRUPO 02 const valoresGrupo01 = valoresPorGrupo["01"] || {}; const valoresGrupo02 = valoresPorGrupo["02"] || {}; // Calcular valores por mês para MARGEM DE LOJA 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 DE LOJA const valoresMargemPorMesPorFilial: Record> = {}; const valoresGrupo01PorFilial = gruposPorCodigo["01"] ? calcularValoresPorMesPorFilial(gruposPorCodigo["01"]) : {}; const valoresGrupo02PorFilial = gruposPorCodigo["02"] ? calcularValoresPorMesPorFilial(gruposPorCodigo["02"]) : {}; mesesDisponiveis.forEach(mes => { valoresMargemPorMesPorFilial[mes] = {}; opcoesFiliais.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); // Adicionar linha calculada rows.push({ type: "calculado", level: 0, grupo: "MARGEM DE LOJA", 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, }); } }); return rows; }, [data, mesesDisponiveis, expandedGrupos, opcoesFiliais, filtrosAplicados, filiaisSelecionadas, 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(); }; const exportarXLSX = () => { if (!data.length) { console.log('⚠️ Nenhum dado para exportar'); return; } const dadosCompletosExpandidos = buildHierarchicalData(); 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, }; mesesDisponiveis.forEach(mes => { const valor = row.valoresPorMes?.[mes] || 0; const percentual = row.percentuaisPorMes?.[mes] || 0; linha[`Valor ${mes}`] = valor; linha[`% ${mes}`] = percentual; }); 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 ]; mesesDisponiveis.forEach(() => { colWidths.push({ wch: 15 }); // Valor colWidths.push({ wch: 10 }); // % }); 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': 'Filial', 'Valor': filtros.filial }, { '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) => ((filtrosAplicados && filiaisSelecionadas.length > 0) ? filiaisSelecionadas : (opcoesFiliais.length > 0 ? opcoesFiliais : [''])).map((filial: string) => ( )) )} {/* 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}}
Total %
)} {/* Componente Analítico - Sempre visível, mas só carrega dados após clique */}
); }