"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"; level: number; grupo?: string; codigo_grupo?: string; conta?: string; codigo_conta?: number; total?: number; isExpanded?: boolean; valoresPorMes?: Record; percentuaisPorMes?: Record; percentualTotal?: number; } // Componente memoizado para linhas da tabela const TableRow = memo(({ row, index, handleRowClick, getRowStyle, getIndentStyle, renderCellContent, mesesDisponiveis, formatCurrency, formatCurrencyWithColor, getFixedCellBackground }: { row: HierarchicalRow; index: number; 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 }; getFixedCellBackground: (row: HierarchicalRow) => string; }) => { return ( handleRowClick(row)} >
{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 [expandedGrupos, setExpandedGrupos] = useState>(new Set()); const [mesesDisponiveis, setMesesDisponiveis] = useState([]); // Estados para filtros const [filtros, setFiltros] = useState({ periodoDe: "", periodoAte: "", conta: "Todas", }); // Estados para multi-seleção const [contasSelecionadas, setContasSelecionadas] = useState([]); const [codigosContas, setCodigosContas] = 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 [opcoesContas, setOpcoesContas] = useState([]); // Estados para filtros de busca nos campos de seleção const [filtroConta, setFiltroConta] = useState(""); // Estados para analítico const [analiticoFiltros, setAnaliticoFiltros] = useState({ dataInicio: "", dataFim: "", centroCusto: "", codigoGrupo: "", codigoSubgrupo: "", codigoConta: "", 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 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); // Inicializar com todas as contas selecionadas setContasSelecionadas(contasUnicas); // 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 })); // Inicializar filtros do analítico também setAnaliticoFiltros(prev => ({ ...prev, dataInicio: periodoDeValido, dataFim: periodoAteValido })); } 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 = (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 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 // 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() || ""; } // 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) .join(','); 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, linhaSelecionada: row.grupo || row.conta || "", excluirCentroCusto: "", excluirCodigoConta: "", codigosCentrosCustoSelecionados: "", codigosContasSelecionadas, }; console.log('🎯 Novos filtros para analítico:', novosFiltros); setAnaliticoFiltros(novosFiltros); }; 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 toggleConta = (conta: string) => { setContasSelecionadas(prev => { if (prev.includes(conta)) { return prev.filter(c => c !== conta); } else { return [...prev, conta]; } }); }; const selecionarTodasContas = () => { setContasSelecionadas(opcoesContas); }; const limparContas = () => { setContasSelecionadas([]); }; // Função auxiliar para calcular valores por mês const calcularValoresPorMes = (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; }; // Função para calcular percentuais (simplificada - sem faturamento líquido) const calcularPercentuaisPorMes = ( valoresPorMes: Record ): Record => { const percentuais: Record = {}; // Calcular total geral por mês const totaisPorMes: Record = {}; mesesDisponiveis.forEach(mes => { totaisPorMes[mes] = data .filter(item => item.data_competencia === mes) .reduce((sum, item) => sum + parseFloat(item.valor), 0); }); Object.keys(valoresPorMes).forEach((mes) => { const valorAtual = valoresPorMes[mes]; const totalMes = totaisPorMes[mes] || 0; if (totalMes !== 0) { percentuais[mes] = (valorAtual / totalMes) * 100; } else { percentuais[mes] = 0; } }); return percentuais; }; // Função para calcular percentual do total const calcularPercentualTotal = (total: number): number => { const totalGeral = data.reduce((sum, item) => sum + parseFloat(item.valor), 0); if (totalGeral !== 0) { return (total / totalGeral) * 100; } else { return 0; } }; const buildHierarchicalData = (): 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); // 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]) => { // Calcular total do grupo const totalGrupo = items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); const valoresGrupoPorMes = calcularValoresPorMes(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, percentuaisPorMes: calcularPercentuaisPorMes(valoresGrupoPorMes), percentualTotal: calcularPercentualTotal(totalGrupo), }); 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); // 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, percentuaisPorMes: calcularPercentuaisPorMes(valoresContaPorMes), percentualTotal: calcularPercentualTotal(totalConta), }); }); } }); 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"; 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 "conta": return `${style} bg-white font-normal text-gray-600`; default: return style; } }; const getFixedCellBackground = (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 "conta": return "bg-white"; default: return "bg-white"; } }; const getIndentStyle = (level: number) => { return { paddingLeft: `${level * 20}px` }; }; const renderCellContent = (row: HierarchicalRow) => { switch (row.type) { case "grupo": return (
); case "conta": return (
); default: return null; } }; 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 conta (multi-seleção) if (contasSelecionadas.length > 0) { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => { return contasSelecionadas.includes(item.conta); }); } setDadosFiltrados(dadosFiltrados); setData(dadosFiltrados); setFiltrosAplicados(true); // 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, conta: "Todas", }); setContasSelecionadas([]); setData([]); setDadosFiltrados([]); setFiltrosAplicados(false); setMesesDisponiveis([]); setIsAllExpanded(false); setIsFilterOpen(false); 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': 'Conta', 'Valor': filtros.conta }, { '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); }; const hierarchicalData = 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 */}
{/* 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)
)}
{/* 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) => ( ))} {/* Table Body */} {hierarchicalData.map((row, index) => { const linhaId = `${row.type}-${row.codigo_grupo || ""}-${row.codigo_conta || ""}`; const isSelected = linhaSelecionada === linhaId; return ( ); })}
Descrição {mes} % Total %
)} {/* Componente Analítico - Sempre visível */}
); }