"use client"; import { LoaderPinwheel, ChevronDown, ChevronRight, Filter } from "lucide-react"; import { useEffect, useState } 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 { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; interface DREItem { codfilial: string; data_competencia: string; data_cai: string; grupo: string; subgrupo: string; centro_custo: string; codigo_conta: number; conta: string; valor: string; codgrupo?: string; isCalculado?: boolean; } interface HierarchicalRow { type: "grupo" | "subgrupo" | "centro_custo" | "conta"; level: number; grupo?: string; subgrupo?: string; centro_custo?: string; conta?: string; codigo_conta?: number; total?: number; isExpanded?: boolean; valoresPorMes?: Record; percentuaisPorMes?: Record; } export default function Teste() { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [expandedGroups, setExpandedGroups] = useState>(new Set()); const [expandedSubgrupos, setExpandedSubgrupos] = useState>( new Set() ); const [expandedCentros, setExpandedCentros] = useState>( new Set() ); const [mesesDisponiveis, setMesesDisponiveis] = useState([]); // Estados para filtros const [filtros, setFiltros] = useState({ periodoDe: "", periodoAte: "", grupo: "Todos", subgrupo: "Todos", centroCusto: "Todos", conta: "Todas", valorMin: "", valorMax: "", buscaTextual: "" }); 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 [opcoesSubgrupos, setOpcoesSubgrupos] = useState([]); const [opcoesCentrosCusto, setOpcoesCentrosCusto] = useState([]); const [opcoesContas, setOpcoesContas] = useState([]); // Estados para analítico const [analiticoFiltros, setAnaliticoFiltros] = useState({ dataInicio: "", dataFim: "", centroCusto: "", codigoGrupo: "", codigoSubgrupo: "", codigoConta: "", }); const [linhaSelecionada, setLinhaSelecionada] = useState(null); useEffect(() => { // Carregar períodos disponíveis da API carregarPeriodosDisponiveis(); // Inicializar filtros com período atual const agora = new Date(); const anoAtual = agora.getFullYear(); const mesAtual = String(agora.getMonth() + 1).padStart(2, '0'); const periodoAtual = `${anoAtual}-${mesAtual}`; setFiltros(prev => ({ ...prev, periodoDe: `${anoAtual}-01`, periodoAte: periodoAtual })); }, []); const carregarPeriodosDisponiveis = async () => { try { const response = await fetch("/api/dre-oracle"); if (!response.ok) { throw new Error(`Erro HTTP: ${response.status}`); } const dadosCompletos = await response.json(); // Extrair períodos únicos dos dados const periodosUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.data_competencia))].sort(); setMesesDisponiveis(periodosUnicos); // Extrair grupos únicos const gruposUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.grupo))].sort(); setOpcoesGrupos(gruposUnicos); // Extrair subgrupos únicos const subgruposUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.subgrupo))].sort(); setOpcoesSubgrupos(subgruposUnicos); // Extrair centros de custo únicos const centrosCustoUnicos = [...new Set(dadosCompletos.map((item: DREItem) => item.centro_custo))].sort(); setOpcoesCentrosCusto(centrosCustoUnicos); // Extrair contas únicas const contasUnicas = [...new Set(dadosCompletos.map((item: DREItem) => item.conta))].sort(); setOpcoesContas(contasUnicas); } catch (error) { console.error("Erro ao carregar períodos:", error); } }; const fetchData = async () => { try { setLoading(true); setError(null); const response = await fetch("/api/dre-oracle"); if (!response.ok) { throw new Error(`Erro ao carregar dados: ${response.status}`); } const result = await response.json(); setData(result); // Extrair meses únicos dos dados const meses = [ ...new Set( result.map((item: DREItem) => { const dataCompetencia = new Date(item.data_competencia); return `${dataCompetencia.getFullYear()}-${String( dataCompetencia.getMonth() + 1 ).padStart(2, "0")}`; }) ), ].sort() as string[]; setMesesDisponiveis(meses); } catch (err) { setError(err instanceof Error ? err.message : "Erro desconhecido"); } finally { setLoading(false); } }; const formatCurrency = (value: string | number) => { const numValue = typeof value === "string" ? parseFloat(value) : value; return numValue.toLocaleString("pt-BR", { style: "currency", currency: "BRL", }); }; const formatCurrencyWithColor = (value: string | number) => { const numValue = typeof value === "string" ? parseFloat(value) : value; const formatted = formatCurrency(value); const isNegative = numValue < 0; return { formatted, isNegative }; }; // Função para extrair códigos dos grupos e subgrupos const extractCodes = (grupo: string, subgrupo?: string) => { const grupoMatch = grupo.match(/^(\d+)/); const codigoGrupo = grupoMatch ? grupoMatch[1] : ""; let codigoSubgrupo = ""; if (subgrupo) { // Primeiro tenta extrair código numérico (ex: "001.008 - LOJA 8" -> "001.008") const subgrupoMatch = subgrupo.match(/^(\d+(?:\.\d+)+)/); if (subgrupoMatch) { codigoSubgrupo = subgrupoMatch[1]; } else { // Se não tem código numérico, usa a descrição completa (ex: "DESPESAS ADMINISTRATIVAS") codigoSubgrupo = subgrupo; } } return { codigoGrupo, codigoSubgrupo }; }; // Função para lidar com clique nas linhas const handleRowClick = (row: HierarchicalRow, mesSelecionado?: string) => { if (!data.length) 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 const { codigoGrupo, codigoSubgrupo } = extractCodes( row.grupo || "", row.subgrupo ); // Criar um identificador único para a linha const linhaId = `${row.type}-${row.grupo || ""}-${row.subgrupo || ""}-${ row.centro_custo || "" }-${row.codigo_conta || ""}`; setLinhaSelecionada(linhaId); // Se um mês específico foi selecionado, usar apenas esse mês const dataInicioFiltro = mesSelecionado || dataInicioStr; const dataFimFiltro = mesSelecionado || dataFimStr; setAnaliticoFiltros({ dataInicio: dataInicioFiltro, dataFim: dataFimFiltro, centroCusto: row.centro_custo || "", codigoGrupo, codigoSubgrupo, codigoConta: row.codigo_conta?.toString() || "", }); }; const toggleGroup = (grupo: string) => { const newExpanded = new Set(expandedGroups); if (newExpanded.has(grupo)) { newExpanded.delete(grupo); } else { newExpanded.add(grupo); } setExpandedGroups(newExpanded); }; const toggleSubgrupo = (subgrupo: string) => { const newExpanded = new Set(expandedSubgrupos); if (newExpanded.has(subgrupo)) { newExpanded.delete(subgrupo); } else { newExpanded.add(subgrupo); } setExpandedSubgrupos(newExpanded); }; const toggleCentro = (centro: string) => { const newExpanded = new Set(expandedCentros); if (newExpanded.has(centro)) { newExpanded.delete(centro); } else { newExpanded.add(centro); } setExpandedCentros(newExpanded); }; const handleFiltroChange = (campo: string, valor: string) => { setFiltros(prev => ({ ...prev, [campo]: valor })); }; const limparFiltros = () => { const agora = new Date(); const anoAtual = agora.getFullYear(); const mesAtual = String(agora.getMonth() + 1).padStart(2, '0'); const periodoAtual = `${anoAtual}-${mesAtual}`; setFiltros({ periodoDe: `${anoAtual}-01`, periodoAte: periodoAtual, grupo: "Todos", subgrupo: "Todos", centroCusto: "Todos", conta: "Todas", valorMin: "", valorMax: "", buscaTextual: "" }); // Limpar dados da tabela setData([]); setDadosFiltrados([]); setFiltrosAplicados(false); setMesesDisponiveis([]); }; const aplicarFiltros = async () => { // Fechar o Sheet primeiro setIsFilterOpen(false); // Aguardar um pouco para a animação de fechamento setTimeout(async () => { try { setLoading(true); setError(null); // Carregar dados da API const response = await fetch("/api/dre-oracle"); if (!response.ok) { throw new Error(`Erro HTTP: ${response.status}`); } const dadosCompletos = await response.json(); // Aplicar filtros nos dados let dadosFiltrados = dadosCompletos; // Filtro por período if (filtros.periodoDe && filtros.periodoAte) { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => { const dataItem = item.data_competencia; return dataItem >= filtros.periodoDe && dataItem <= filtros.periodoAte; }); } // Filtro por grupo if (filtros.grupo !== "Todos") { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => item.grupo === filtros.grupo ); } // Filtro por subgrupo if (filtros.subgrupo !== "Todos") { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => item.subgrupo === filtros.subgrupo ); } // Filtro por centro de custo if (filtros.centroCusto !== "Todos") { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => item.centro_custo === filtros.centroCusto ); } // Filtro por conta if (filtros.conta !== "Todas") { dadosFiltrados = dadosFiltrados.filter((item: DREItem) => item.conta === filtros.conta ); } // Filtro por valor mínimo if (filtros.valorMin) { const valorMin = parseFloat(filtros.valorMin.replace(',', '.')); dadosFiltrados = dadosFiltrados.filter((item: DREItem) => parseFloat(item.valor) >= valorMin ); } // Filtro por valor máximo if (filtros.valorMax) { const valorMax = parseFloat(filtros.valorMax.replace(',', '.')); dadosFiltrados = dadosFiltrados.filter((item: DREItem) => parseFloat(item.valor) <= valorMax ); } // Filtro por busca textual if (filtros.buscaTextual) { const termoBusca = filtros.buscaTextual.toLowerCase(); dadosFiltrados = dadosFiltrados.filter((item: DREItem) => item.grupo.toLowerCase().includes(termoBusca) || item.subgrupo.toLowerCase().includes(termoBusca) || item.centro_custo.toLowerCase().includes(termoBusca) || item.conta.toLowerCase().includes(termoBusca) ); } setData(dadosFiltrados); setDadosFiltrados(dadosFiltrados); setFiltrosAplicados(true); // Extrair meses únicos dos dados filtrados const mesesUnicos = [...new Set(dadosFiltrados.map(item => item.data_competencia))].sort(); setMesesDisponiveis(mesesUnicos); } catch (error) { console.error("Erro ao aplicar filtros:", error); setError(error instanceof Error ? error.message : "Erro desconhecido"); } finally { setLoading(false); } }, 300); // Aguardar 300ms para a animação de fechamento }; const calcularValoresPorMes = (items: DREItem[]): Record => { const valoresPorMes: Record = {}; items.forEach((item) => { const dataCompetencia = new Date(item.data_competencia); const anoMes = `${dataCompetencia.getFullYear()}-${String( dataCompetencia.getMonth() + 1 ).padStart(2, "0")}`; if (!valoresPorMes[anoMes]) { valoresPorMes[anoMes] = 0; } valoresPorMes[anoMes] += parseFloat(item.valor); }); return valoresPorMes; }; // Função para calcular percentuais baseado no grupo 03 como referência const calcularPercentuaisPorMes = ( valoresPorMes: Record, grupo: string ): Record => { const percentuais: Record = {}; // Se for o grupo 03, retorna 100% para todos os meses if (grupo.includes("03")) { Object.keys(valoresPorMes).forEach((mes) => { percentuais[mes] = 100; }); return percentuais; } // Para outros grupos, calcular percentual baseado no grupo 03 Object.keys(valoresPorMes).forEach((mes) => { const valorAtual = valoresPorMes[mes]; // Encontrar o valor do grupo 03 para o mesmo mês const grupo03Items = data.filter((item) => { const dataCompetencia = new Date(item.data_competencia); const anoMes = `${dataCompetencia.getFullYear()}-${String( dataCompetencia.getMonth() + 1 ).padStart(2, "0")}`; return anoMes === mes && item.grupo.includes("03"); }); const valorGrupo03 = grupo03Items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); if (valorGrupo03 !== 0) { percentuais[mes] = (valorAtual / valorGrupo03) * 100; } else { percentuais[mes] = 0; } }); return percentuais; }; const buildHierarchicalData = (): HierarchicalRow[] => { const rows: HierarchicalRow[] = []; // Agrupar por grupo const grupos = data.reduce((acc, item) => { if (!acc[item.grupo]) { acc[item.grupo] = []; } acc[item.grupo].push(item); return acc; }, {} as Record); // Ordenar grupos pelo CODGRUPO numérico const sortedGrupos = Object.entries(grupos).sort(([grupoA, itemsA], [grupoB, itemsB]) => { // Pegar o CODGRUPO do primeiro item de cada grupo const codgrupoA = itemsA[0]?.codgrupo || ""; const codgrupoB = itemsB[0]?.codgrupo || ""; // Se ambos têm CODGRUPO, ordenar numericamente if (codgrupoA && codgrupoB) { return parseInt(codgrupoA) - parseInt(codgrupoB); } // Se apenas um tem CODGRUPO, ele vem primeiro if (codgrupoA && !codgrupoB) return -1; if (!codgrupoA && codgrupoB) return 1; // Se nenhum tem CODGRUPO, ordenar alfabeticamente return grupoA.localeCompare(grupoB); }); sortedGrupos.forEach(([grupo, items]) => { const totalGrupo = items.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do grupo const valoresPorMes = calcularValoresPorMes(items); rows.push({ type: "grupo", level: 0, grupo, total: totalGrupo, isExpanded: expandedGroups.has(grupo), valoresPorMes, percentuaisPorMes: calcularPercentuaisPorMes(valoresPorMes, grupo), }); if (expandedGroups.has(grupo)) { // Agrupar por subgrupo dentro do grupo const subgrupos = items.reduce((acc, item) => { // Se o item originalmente era do grupo 05, agrupar tudo sob o nome do grupo 05 if (item.grupo.includes("05")) { const subgrupoKey = item.grupo; // Usar o nome completo do grupo 05 if (!acc[subgrupoKey]) { acc[subgrupoKey] = []; } acc[subgrupoKey].push(item); } else { // Para outros itens, agrupar normalmente por subgrupo if (!acc[item.subgrupo]) { acc[item.subgrupo] = []; } acc[item.subgrupo].push(item); } return acc; }, {} as Record); // Ordenar subgrupos const sortedSubgrupos = Object.entries(subgrupos).sort(([a], [b]) => a.localeCompare(b) ); sortedSubgrupos.forEach(([subgrupo, subgrupoItems]) => { const totalSubgrupo = subgrupoItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do subgrupo const valoresSubgrupoPorMes = calcularValoresPorMes(subgrupoItems); rows.push({ type: "subgrupo", level: 1, grupo, subgrupo, total: totalSubgrupo, isExpanded: expandedSubgrupos.has(`${grupo}-${subgrupo}`), valoresPorMes: valoresSubgrupoPorMes, percentuaisPorMes: calcularPercentuaisPorMes( valoresSubgrupoPorMes, grupo ), }); if (expandedSubgrupos.has(`${grupo}-${subgrupo}`)) { // Agrupar por centro de custo dentro do subgrupo const centros = subgrupoItems.reduce((acc, item) => { if (!acc[item.centro_custo]) { acc[item.centro_custo] = []; } acc[item.centro_custo].push(item); return acc; }, {} as Record); // Ordenar centros de custo const sortedCentros = Object.entries(centros).sort(([a], [b]) => a.localeCompare(b) ); sortedCentros.forEach(([centro, centroItems]) => { const totalCentro = centroItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha do centro de custo const valoresCentroPorMes = calcularValoresPorMes(centroItems); rows.push({ type: "centro_custo", level: 2, grupo, subgrupo, centro_custo: centro, total: totalCentro, isExpanded: expandedCentros.has( `${grupo}-${subgrupo}-${centro}` ), valoresPorMes: valoresCentroPorMes, percentuaisPorMes: calcularPercentuaisPorMes( valoresCentroPorMes, grupo ), }); if (expandedCentros.has(`${grupo}-${subgrupo}-${centro}`)) { // Agrupar por conta dentro do centro de custo const contas = centroItems.reduce((acc, item) => { if (!acc[item.conta]) { acc[item.conta] = []; } acc[item.conta].push(item); return acc; }, {} as Record); // Ordenar contas const sortedContas = Object.entries(contas).sort(([a], [b]) => a.localeCompare(b) ); sortedContas.forEach(([conta, contaItems]) => { const totalConta = contaItems.reduce( (sum, item) => sum + parseFloat(item.valor), 0 ); // Linha da conta (sem ano/mês no nome) const valoresContaPorMes = calcularValoresPorMes(contaItems); rows.push({ type: "conta", level: 3, grupo, subgrupo, centro_custo: centro, conta, codigo_conta: contaItems[0].codigo_conta, total: totalConta, valoresPorMes: valoresContaPorMes, percentuaisPorMes: calcularPercentuaisPorMes( valoresContaPorMes, grupo ), }); }); } }); } }); } }); return rows; }; const getRowStyle = (row: HierarchicalRow) => { const baseStyle = "transition-all duration-200 hover:bg-gradient-to-r hover:from-blue-50/30 hover:to-indigo-50/30"; // Criar identificador único para a linha const linhaId = `${row.type}-${row.grupo || ""}-${row.subgrupo || ""}-${ row.centro_custo || "" }-${row.codigo_conta || ""}`; const isSelected = linhaSelecionada === linhaId; let style = baseStyle; if (isSelected) { style += " bg-gradient-to-r from-blue-100 to-indigo-100 border-l-4 border-blue-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 "subgrupo": return `${style} bg-gradient-to-r from-gray-50/30 to-blue-50/20 font-semibold text-gray-800`; case "centro_custo": return `${style} bg-gradient-to-r from-gray-50/20 to-gray-100/10 font-medium text-gray-700`; case "conta": return `${style} bg-white font-normal text-gray-600`; default: return style; } }; const getIndentStyle = (level: number) => { return { paddingLeft: `${level * 20}px` }; }; const renderCellContent = (row: HierarchicalRow) => { // Verificar se é um grupo calculado const isCalculado = row.grupo?.includes("CALCULADO") || row.grupo?.includes("FATURAMENTO LÍQUIDO") || row.grupo?.includes("LUCRO BRUTO") || row.grupo?.includes("MARGEM LOJA") || row.grupo?.includes("RESULTADO OPERACIONAL") || row.grupo?.includes("RESULTADO FINANCEIRO") || row.grupo?.includes("OUTRAS RECEITAS / DESPESAS") || row.grupo?.includes("LAIR") || row.grupo?.includes("IR") || row.grupo?.includes("CSLL") || row.grupo?.includes("LUCRO LÍQUIDO") || row.grupo?.includes("EBITDA"); switch (row.type) { case "grupo": return (
); case "subgrupo": return (
); case "centro_custo": return (
); case "conta": return (
); default: return null; } }; // Loading será tratado dentro do componente principal // Error será tratado dentro do componente principal const hierarchicalData = buildHierarchicalData(); return (
{/* Header Section */}

DRE Gerencial

Demonstração do Resultado do Exercício

{/* Botão de Filtro */} Filtros Ajuste os critérios e clique em Pesquisar para atualizar a visão.
{/* Período */}
{/* Grupo */}
{/* Subgrupo */}
{/* Centro de Custo */}
{/* Conta */}
{/* Valor */}
R$ handleFiltroChange('valorMin', e.target.value)} className="pl-8" placeholder="0,00" />
R$ handleFiltroChange('valorMax', e.target.value)} className="pl-8" placeholder="0,00" />
{/* Busca Textual */}
handleFiltroChange('buscaTextual', e.target.value)} placeholder="Pesquise por grupo, subgrupo, centro ou conta" />
{/* 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 */}
Descrição
{mesesDisponiveis.map((mes) => (
{mes}
%
))}
Total
{/* Table Body */}
{hierarchicalData.map((row, index) => (
{renderCellContent(row)}
{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)}%` : "-"}
))}
handleRowClick(row)} title={row.total ? formatCurrency(row.total) : "-"} > {(() => { const { formatted, isNegative } = formatCurrencyWithColor( row.total! ); return ( {formatted} ); })()}
))}
)} {/* Componente Analítico */} {!loading && data.length > 0 && ( )}
); }