"use client"; import { LoaderPinwheel } from "lucide-react"; import { useEffect, useState } from "react"; import AnaliticoComponent from "./analitico"; interface DREItem { codfilial: string; data_competencia: string; data_cai: string; grupo: string; subgrupo: string; centro_custo: string; codigo_conta: number; conta: string; valor: string; } 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(true); 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 analítico const [analiticoFiltros, setAnaliticoFiltros] = useState({ dataInicio: "", dataFim: "", centroCusto: "", codigoGrupo: "", codigoSubgrupo: "", codigoConta: "", }); const [linhaSelecionada, setLinhaSelecionada] = useState(null); useEffect(() => { fetchData(); }, []); const fetchData = async () => { try { setLoading(true); setError(null); const response = await fetch("/api/dre"); 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 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, mas tratar grupo 05 como subgrupo do grupo 04 const grupos = data.reduce((acc, item) => { // Se for grupo 05, adicionar ao grupo 04 como subgrupo if (item.grupo.includes("05")) { // Encontrar grupo 04 existente ou criar um const grupo04Key = Object.keys(acc).find((key) => key.includes("04")); if (grupo04Key) { acc[grupo04Key].push(item); } else { // Se não existe grupo 04, criar um grupo especial const grupo04Nome = Object.keys(acc).find((key) => key.includes("04")) || "04 - GRUPO 04"; if (!acc[grupo04Nome]) { acc[grupo04Nome] = []; } acc[grupo04Nome].push(item); } } else { // Para outros grupos, agrupar normalmente if (!acc[item.grupo]) { acc[item.grupo] = []; } acc[item.grupo].push(item); } return acc; }, {} as Record); // Ordenar grupos const sortedGrupos = Object.entries(grupos).sort(([a], [b]) => a.localeCompare(b) ); 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) => { switch (row.type) { case "grupo": return (
); case "subgrupo": return (
); case "centro_custo": return (
); case "conta": return (
); default: return null; } }; if (loading) { return (
{/*
*/}

DRE Gerencial

Demonstração do Resultado do Exercício

Carregando dados...

Aguarde enquanto processamos as informações

); } if (error) { return (
{/*
*/}

DRE Gerencial

Demonstração do Resultado do Exercício

Erro ao carregar DRE Gerencial

{error}

); } const hierarchicalData = buildHierarchicalData(); return (
{/* Header Section */}
{/*
*/}

DRE Gerencial

Demonstração do Resultado do Exercício

{/* Table Container */}
{/* 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 && ( )}
); }