fix: correções de inicialização da rota /dre-filial
This commit is contained in:
parent
ccedcebfd0
commit
c5090a29a6
|
|
@ -0,0 +1,255 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { executeOracleQuery } from '@/db/oracle';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
console.log('🔄 Buscando dados analíticos do Oracle (Filial)...');
|
||||
console.log('📋 URL completa:', request.url);
|
||||
console.log('🔍 Query params:', request.nextUrl.searchParams.toString());
|
||||
console.log('📊 Headers:', Object.fromEntries(request.headers.entries()));
|
||||
|
||||
// Extrair parâmetros de query
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const dataInicio = searchParams.get('dataInicio');
|
||||
const dataFim = searchParams.get('dataFim');
|
||||
const centroCusto = searchParams.get('centroCusto');
|
||||
const codigoGrupo = searchParams.get('codigoGrupo');
|
||||
const codigoSubgrupo = searchParams.get('codigoSubgrupo');
|
||||
const codigoConta = searchParams.get('codigoConta');
|
||||
|
||||
// Parâmetros para exclusão de valores específicos
|
||||
const excluirCentroCusto = searchParams.get('excluirCentroCusto');
|
||||
const excluirCodigoConta = searchParams.get('excluirCodigoConta');
|
||||
|
||||
// Novos parâmetros para códigos selecionados no filtro
|
||||
const codigosCentrosCustoSelecionados = searchParams.get('codigosCentrosCustoSelecionados');
|
||||
const codigosContasSelecionadas = searchParams.get('codigosContasSelecionadas');
|
||||
|
||||
console.log('🎯 Filtros recebidos na API:', {
|
||||
dataInicio,
|
||||
dataFim,
|
||||
centroCusto,
|
||||
codigoGrupo,
|
||||
codigoSubgrupo,
|
||||
codigoConta,
|
||||
excluirCentroCusto,
|
||||
excluirCodigoConta,
|
||||
codigosCentrosCustoSelecionados,
|
||||
codigosContasSelecionadas
|
||||
});
|
||||
console.log('🔍 Verificação específica de centroCusto:', {
|
||||
centroCusto,
|
||||
tipo: typeof centroCusto,
|
||||
vazio: centroCusto === null || centroCusto === undefined || centroCusto === '',
|
||||
codigosCentrosCustoSelecionados,
|
||||
codigosCentrosCustoSelecionadosVazio: !codigosCentrosCustoSelecionados || codigosCentrosCustoSelecionados === ''
|
||||
});
|
||||
|
||||
// Construir query SQL com filtros usando a view VB_DRE_FILIAL_DESPESA_ANALITICO
|
||||
let sql = `SELECT * FROM VB_DRE_FILIAL_DESPESA_ANALITICO WHERE 1=1`;
|
||||
const params: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
// Filtro por período (usando ANOMESCOMP)
|
||||
if (dataInicio && dataFim) {
|
||||
sql += ` AND ANOMESCOMP >= :${paramIndex} AND ANOMESCOMP <= :${paramIndex + 1}`;
|
||||
params.push(dataInicio, dataFim);
|
||||
paramIndex += 2;
|
||||
console.log('📅 Adicionando filtro de período:', dataInicio, 'a', dataFim);
|
||||
}
|
||||
|
||||
// Filtro por código do grupo
|
||||
if (codigoGrupo) {
|
||||
sql += ` AND CODGRUPO = :${paramIndex}`;
|
||||
params.push(codigoGrupo);
|
||||
paramIndex++;
|
||||
console.log('📊 Adicionando filtro de grupo:', codigoGrupo);
|
||||
}
|
||||
|
||||
// Filtro por subgrupo (DIRETO, INDIRETO, SEM CC, etc.)
|
||||
if (codigoSubgrupo) {
|
||||
sql += ` AND SUBGRUPO = :${paramIndex}`;
|
||||
params.push(codigoSubgrupo);
|
||||
paramIndex++;
|
||||
console.log('📊 Adicionando filtro de subgrupo:', codigoSubgrupo);
|
||||
}
|
||||
|
||||
// Filtro por código da conta
|
||||
if (codigoConta) {
|
||||
sql += ` AND CODCONTA = :${paramIndex}`;
|
||||
params.push(codigoConta);
|
||||
paramIndex++;
|
||||
console.log('💰 Adicionando filtro de conta:', codigoConta);
|
||||
}
|
||||
|
||||
// Filtro por códigos de centros de custo selecionados no filtro
|
||||
// Se houver codigosCentrosCustoSelecionados E centroCusto individual, incluir ambos
|
||||
console.log('🔍 Antes de aplicar filtro de centro de custo:', {
|
||||
codigosCentrosCustoSelecionados,
|
||||
centroCusto,
|
||||
codigosCentrosCustoSelecionadosVazio: !codigosCentrosCustoSelecionados || codigosCentrosCustoSelecionados.trim() === '',
|
||||
centroCustoVazio: !centroCusto || centroCusto.trim() === '',
|
||||
centroCustoLength: centroCusto?.length,
|
||||
codigosCentrosCustoSelecionadosLength: codigosCentrosCustoSelecionados?.length
|
||||
});
|
||||
|
||||
// IMPORTANTE: Quando centroCusto individual é fornecido (clique na célula), ele tem PRIORIDADE ABSOLUTA
|
||||
// e deve filtrar APENAS por ele, ignorando codigosCentrosCustoSelecionados do filtro geral
|
||||
if (centroCusto && centroCusto.trim() !== '') {
|
||||
// Quando há centroCusto individual (clique na célula), usar APENAS ele
|
||||
// Ignorar codigosCentrosCustoSelecionados do filtro geral para garantir filtro preciso
|
||||
sql += ` AND CODIGOCENTROCUSTO = :${paramIndex}`;
|
||||
params.push(centroCusto);
|
||||
paramIndex++;
|
||||
console.log('🏢 PRIORIDADE: Filtrando APENAS por centroCusto individual (clique na célula):', centroCusto);
|
||||
console.log('⚠️ Ignorando codigosCentrosCustoSelecionados do filtro geral quando há centroCusto individual');
|
||||
console.log('📝 SQL após adicionar filtro =:', sql.substring(0, 200) + '...');
|
||||
} else if (codigosCentrosCustoSelecionados && codigosCentrosCustoSelecionados.trim() !== '') {
|
||||
// Se só codigosCentrosCustoSelecionados existe (sem clique na célula), usar ele
|
||||
const codigosArray = codigosCentrosCustoSelecionados.split(',').filter(c => c.trim() !== '');
|
||||
if (codigosArray.length > 0) {
|
||||
const placeholders = codigosArray.map(() => `:${paramIndex++}`).join(',');
|
||||
sql += ` AND CODIGOCENTROCUSTO IN (${placeholders})`;
|
||||
params.push(...codigosArray);
|
||||
console.log('🏢 Filtrando por códigos de centros de custo selecionados (filtro geral):', codigosArray);
|
||||
console.log('📝 SQL após adicionar filtro IN:', sql.substring(0, 200) + '...');
|
||||
}
|
||||
} else {
|
||||
console.log('⚠️ Nenhum filtro de centro de custo aplicado - ambos estão vazios');
|
||||
}
|
||||
|
||||
// Exclusão de centro de custo específico (quando desmarcado)
|
||||
// Só aplicar se não houver codigosCentrosCustoSelecionados, para evitar conflito
|
||||
if (excluirCentroCusto && !codigosCentrosCustoSelecionados) {
|
||||
sql += ` AND CODIGOCENTROCUSTO != :${paramIndex}`;
|
||||
params.push(excluirCentroCusto);
|
||||
paramIndex++;
|
||||
console.log('🚫 Excluindo centro de custo:', excluirCentroCusto);
|
||||
}
|
||||
|
||||
// Exclusão de código de conta específico (quando desmarcado)
|
||||
if (excluirCodigoConta) {
|
||||
sql += ` AND CODCONTA != :${paramIndex}`;
|
||||
params.push(excluirCodigoConta);
|
||||
paramIndex++;
|
||||
console.log('🚫 Excluindo código de conta:', excluirCodigoConta);
|
||||
}
|
||||
|
||||
// Filtro por códigos de contas selecionadas no filtro
|
||||
if (codigosContasSelecionadas) {
|
||||
const codigosArray = codigosContasSelecionadas.split(',');
|
||||
const placeholders = codigosArray.map(() => `:${paramIndex++}`).join(',');
|
||||
sql += ` AND CODCONTA IN (${placeholders})`;
|
||||
params.push(...codigosArray);
|
||||
console.log('💰 Filtrando por códigos de contas:', codigosArray);
|
||||
}
|
||||
|
||||
sql += ` ORDER BY DTVENC, CODFORNEC, CODCONTA`;
|
||||
|
||||
// Log detalhado da query SQL final
|
||||
console.log('═══════════════════════════════════════════════════════════════');
|
||||
console.log('🗄️ QUERY SQL FINAL:');
|
||||
console.log('═══════════════════════════════════════════════════════════════');
|
||||
console.log(sql);
|
||||
console.log('═══════════════════════════════════════════════════════════════');
|
||||
console.log('📋 PARÂMETROS FINAIS (na ordem dos placeholders :1, :2, :3, ...):');
|
||||
console.log('═══════════════════════════════════════════════════════════════');
|
||||
params.forEach((param, index) => {
|
||||
console.log(` :${index + 1} = ${param} (${typeof param})`);
|
||||
});
|
||||
console.log('═══════════════════════════════════════════════════════════════');
|
||||
console.log('📊 RESUMO DOS FILTROS APLICADOS:');
|
||||
console.log('═══════════════════════════════════════════════════════════════');
|
||||
console.log({
|
||||
temPeriodo: dataInicio && dataFim,
|
||||
periodo: dataInicio && dataFim ? `${dataInicio} a ${dataFim}` : 'N/A',
|
||||
temCodigoGrupo: !!codigoGrupo,
|
||||
codigoGrupo: codigoGrupo || 'N/A',
|
||||
temCodigoSubgrupo: !!codigoSubgrupo,
|
||||
codigoSubgrupo: codigoSubgrupo || 'N/A',
|
||||
temCentroCusto: !!centroCusto,
|
||||
centroCusto: centroCusto || 'N/A',
|
||||
temCodigosCentrosCustoSelecionados: !!codigosCentrosCustoSelecionados,
|
||||
codigosCentrosCustoSelecionados: codigosCentrosCustoSelecionados || 'N/A',
|
||||
temCodigoConta: !!codigoConta,
|
||||
codigoConta: codigoConta || 'N/A',
|
||||
temCodigosContasSelecionadas: !!codigosContasSelecionadas,
|
||||
codigosContasSelecionadas: codigosContasSelecionadas || 'N/A',
|
||||
temExcluirCentroCusto: !!excluirCentroCusto,
|
||||
excluirCentroCusto: excluirCentroCusto || 'N/A',
|
||||
temExcluirCodigoConta: !!excluirCodigoConta,
|
||||
excluirCodigoConta: excluirCodigoConta || 'N/A'
|
||||
});
|
||||
console.log('═══════════════════════════════════════════════════════════════');
|
||||
|
||||
// Se há centroCusto individual, destacar especialmente
|
||||
if (centroCusto && centroCusto.trim() !== '') {
|
||||
console.log('🎯 FILTRO INDIVIDUAL DE CENTRO DE CUSTO ATIVO (clique na célula)');
|
||||
console.log(` Centro de Custo: ${centroCusto}`);
|
||||
console.log(' ⚠️ Este filtro tem PRIORIDADE sobre codigosCentrosCustoSelecionados');
|
||||
}
|
||||
console.log('═══════════════════════════════════════════════════════════════');
|
||||
|
||||
const data = await executeOracleQuery(sql, params);
|
||||
|
||||
console.log('✅ Query executada com sucesso:', data.length, 'registros encontrados');
|
||||
console.log('📝 Primeiros 3 registros:', data.slice(0, 3));
|
||||
|
||||
// Transformar os dados do Oracle para o formato esperado pelo componente
|
||||
// Usando a view VB_DRE_FILIAL_DESPESA_ANALITICO
|
||||
const transformedData = data.map((item: any) => {
|
||||
return {
|
||||
codigo_grupo: item.CODGRUPO || "",
|
||||
codigo_subgrupo: item.SUBGRUPO || "", // SUBGRUPO existe na nova view
|
||||
codigo_fornecedor: item.CODFORNEC || "",
|
||||
nome_fornecedor: item.FORNECEDOR || "",
|
||||
id: item.NUMLANC || 0,
|
||||
codfilial: item.FILIAL || "001", // Usar FILIAL da view
|
||||
recnum: item.NUMLANC || 0,
|
||||
data_competencia: item.ANOMESCOMP || "",
|
||||
data_vencimento: item.DTVENC ? new Date(item.DTVENC).toISOString().split('T')[0] : "",
|
||||
data_pagamento: item.DTPAGTO ? new Date(item.DTPAGTO).toISOString().split('T')[0] : "",
|
||||
data_caixa: item.DTCAIXA ? new Date(item.DTCAIXA).toISOString().split('T')[0] : "",
|
||||
codigo_conta: item.CODCONTA || "",
|
||||
conta: item.CONTA || "",
|
||||
codigo_centrocusto: item.CODIGOCENTROCUSTO || "",
|
||||
centro_custo: item.CENTROCUSTO || "",
|
||||
valor: item.VLREALIZADO !== null && item.VLREALIZADO !== undefined ? Number(item.VLREALIZADO) : 0,
|
||||
historico: item.HISTORICO || "",
|
||||
historico2: item.HISTORICO2 || "",
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
// Campos adicionais do Oracle
|
||||
entidade: item.ENTIDADE || "",
|
||||
tipo_parceiro: item.TIPOPARCEIRO || "",
|
||||
valor_previsto: item.VLPREVISTO !== null && item.VLPREVISTO !== undefined ? Number(item.VLPREVISTO) : 0,
|
||||
valor_confirmado: item.VLCONFIRMADO !== null && item.VLCONFIRMADO !== undefined ? Number(item.VLCONFIRMADO) : 0,
|
||||
valor_pago: item.VLPAGO !== null && item.VLPAGO !== undefined ? Number(item.VLPAGO) : 0,
|
||||
numero_lancamento: item.NUMLANC || 0,
|
||||
ano_mes_comp: item.ANOMESCOMP || "",
|
||||
codgrupo: item.CODGRUPO || "",
|
||||
// Novos campos
|
||||
data_lancamento: item.DTLANC ? new Date(item.DTLANC).toISOString().split('T')[0] : "",
|
||||
data_compensacao: item.DTCOMPENSACAO ? new Date(item.DTCOMPENSACAO).toISOString().split('T')[0] : "",
|
||||
data_pagto: item.DTPAGTO ? new Date(item.DTPAGTO).toISOString().split('T')[0] : ""
|
||||
};
|
||||
});
|
||||
|
||||
console.log('🔄 Dados transformados:', transformedData.length, 'registros');
|
||||
console.log('📝 Primeiros 3 transformados:', transformedData.slice(0, 3));
|
||||
|
||||
return NextResponse.json(transformedData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erro ao buscar dados analíticos do Oracle (Filial):', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erro desconhecido',
|
||||
details: error instanceof Error ? error.stack : undefined
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { executeOracleQuery } from '@/db/oracle';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
console.log('🔄 Buscando dados DRE do Oracle (Filial) - Views VB_DRE_FILIAL_*...');
|
||||
|
||||
// Query para buscar dados das views VB_DRE_FILIAL_CMV, VB_DRE_FILIAL_DESPESA e VB_DRE_FILIAL_FATLIQ
|
||||
const sql = `SELECT *
|
||||
FROM (
|
||||
SELECT 'CMV' AS TIPO,
|
||||
"DATA",
|
||||
CODGRUPO, GRUPO, FILIAL, CODCONTA, CONTA, VALOR
|
||||
FROM SEVEN.VB_DRE_FILIAL_CMV
|
||||
UNION ALL
|
||||
SELECT 'DESPESA' AS TIPO,
|
||||
"DATA",
|
||||
CODGRUPO, GRUPO, FILIAL, CODCONTA, CONTA, VALOR
|
||||
FROM SEVEN.VB_DRE_FILIAL_DESPESA
|
||||
UNION ALL
|
||||
SELECT 'FATLIQ' AS TIPO,
|
||||
"DATA",
|
||||
CODGRUPO, GRUPO, FILIAL, CODCONTA, CONTA, VALOR
|
||||
FROM SEVEN.VB_DRE_FILIAL_FATLIQ
|
||||
) X
|
||||
ORDER BY CODGRUPO, CODCONTA`;
|
||||
|
||||
const data = await executeOracleQuery(sql);
|
||||
|
||||
console.log('✅ Query executada com sucesso:', data.length, 'registros encontrados');
|
||||
|
||||
// Debug: Verificar estrutura dos dados
|
||||
if (data.length > 0) {
|
||||
console.log('🔍 Primeiro registro do Oracle:', Object.keys(data[0]));
|
||||
console.log('🔍 Primeiro registro completo:', data[0]);
|
||||
console.log('🔍 Valores únicos de CODGRUPO:', [...new Set(data.map((item: any) => item.CODGRUPO).filter(Boolean))]);
|
||||
console.log('🔍 Valores únicos de FILIAL:', [...new Set(data.map((item: any) => item.FILIAL).filter(Boolean))]);
|
||||
}
|
||||
|
||||
// Transformar os dados do Oracle para o formato esperado pelo componente
|
||||
const transformedData = data.map((item: any) => {
|
||||
// Converter DATA para formato YYYY-MM se necessário
|
||||
let dataCompetencia = item.DATA;
|
||||
if (dataCompetencia) {
|
||||
// Se DATA for uma string no formato YYYY-MM, usar diretamente
|
||||
if (typeof dataCompetencia === 'string' && dataCompetencia.match(/^\d{4}-\d{2}$/)) {
|
||||
// Já está no formato correto
|
||||
} else if (dataCompetencia instanceof Date) {
|
||||
// Se for Date, converter para YYYY-MM
|
||||
const year = dataCompetencia.getFullYear();
|
||||
const month = String(dataCompetencia.getMonth() + 1).padStart(2, '0');
|
||||
dataCompetencia = `${year}-${month}`;
|
||||
} else {
|
||||
// Tentar converter string de data para YYYY-MM
|
||||
try {
|
||||
const date = new Date(dataCompetencia);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
dataCompetencia = `${year}-${month}`;
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Erro ao converter DATA:', dataCompetencia);
|
||||
dataCompetencia = "2023-03"; // Valor padrão
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dataCompetencia = "2023-03"; // Valor padrão
|
||||
}
|
||||
|
||||
return {
|
||||
codfilial: item.FILIAL || "001",
|
||||
data_competencia: dataCompetencia,
|
||||
data_cai: dataCompetencia,
|
||||
grupo: item.GRUPO || "", // GRUPO (nome)
|
||||
codigo_grupo: item.CODGRUPO || "", // CODGRUPO (código)
|
||||
codigo_conta: parseInt(item.CODCONTA) || 0, // Converter CODCONTA para número
|
||||
conta: item.CONTA || "", // CONTA
|
||||
valor: item.VALOR?.toString() || "0", // Converter VALOR para string
|
||||
codgrupo: item.CODGRUPO || "", // CODGRUPO
|
||||
tipo: item.TIPO || "", // TIPO (CMV, DESPESA, FATLIQ)
|
||||
filial: item.FILIAL || "", // FILIAL
|
||||
};
|
||||
});
|
||||
|
||||
console.log('✅ Dados transformados:', transformedData.length, 'registros');
|
||||
console.log('🔍 Exemplo de registro transformado:', transformedData[0]);
|
||||
|
||||
return NextResponse.json(transformedData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erro ao buscar dados DRE do Oracle:', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erro desconhecido',
|
||||
details: error instanceof Error ? error.stack : undefined
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,997 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { DataGridPremium, GridToolbar, GridColDef, GridFilterModel } from "@mui/x-data-grid-premium";
|
||||
import { LicenseInfo } from '@mui/x-license-pro';
|
||||
|
||||
// Garantir que a licença seja aplicada no componente atualização de licença
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
const PERPETUAL_LICENSE_KEY = 'e0d9bb8070ce0054c9d9ecb6e82cb58fTz0wLEU9MzI0NzIxNDQwMDAwMDAsUz1wcmVtaXVtLExNPXBlcnBldHVhbCxLVj0y';
|
||||
LicenseInfo.setLicenseKey(PERPETUAL_LICENSE_KEY);
|
||||
console.log('✅ Licença MUI X aplicada no componente Analítico');
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Erro ao aplicar licença no componente:', error);
|
||||
}
|
||||
}
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer";
|
||||
import { Download, Filter, X, Search, ArrowUpDown, ArrowUp, ArrowDown, Maximize2, Minimize2 } from "lucide-react";
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
interface AnaliticoItem {
|
||||
codigo_grupo: string;
|
||||
codigo_subgrupo: string;
|
||||
codigo_fornecedor: string;
|
||||
nome_fornecedor: string;
|
||||
id: number;
|
||||
codfilial: string;
|
||||
recnum: number;
|
||||
data_competencia: string;
|
||||
data_vencimento: string;
|
||||
data_pagamento: string;
|
||||
data_caixa: string;
|
||||
codigo_conta: string;
|
||||
conta: string;
|
||||
codigo_centrocusto: string;
|
||||
centro_custo?: string;
|
||||
valor: number;
|
||||
historico: string;
|
||||
historico2: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
// Campos adicionais do Oracle
|
||||
entidade?: string;
|
||||
tipo_parceiro?: string;
|
||||
valor_previsto?: number;
|
||||
valor_confirmado?: number;
|
||||
valor_pago?: number;
|
||||
numero_lancamento?: number;
|
||||
ano_mes_comp?: string;
|
||||
codgrupo?: string;
|
||||
grupo?: string;
|
||||
filial?: string;
|
||||
tipo?: string;
|
||||
// Novos campos
|
||||
data_lancamento?: string;
|
||||
data_compensacao?: string;
|
||||
data_pagto?: string;
|
||||
}
|
||||
|
||||
interface AnaliticoProps {
|
||||
filtros: {
|
||||
dataInicio: string;
|
||||
dataFim: string;
|
||||
codigoGrupo?: string;
|
||||
codigoConta?: string;
|
||||
linhaSelecionada?: string;
|
||||
excluirCodigoConta?: string;
|
||||
codigosContasSelecionadas?: string;
|
||||
filial?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Componente de filtro customizado estilo Excel
|
||||
interface ExcelFilterProps {
|
||||
column: GridColDef;
|
||||
data: any[];
|
||||
filteredData: any[]; // Dados filtrados para mostrar apenas valores disponíveis
|
||||
onFilterChange: (field: string, values: string[]) => void;
|
||||
onSortChange: (field: string, direction: 'asc' | 'desc' | null) => void;
|
||||
currentFilter?: string[];
|
||||
currentSort?: 'asc' | 'desc' | null;
|
||||
}
|
||||
|
||||
const ExcelFilter: React.FC<ExcelFilterProps> = ({
|
||||
column,
|
||||
data,
|
||||
filteredData,
|
||||
onFilterChange,
|
||||
onSortChange,
|
||||
currentFilter = [],
|
||||
currentSort = null,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const [searchTerm, setSearchTerm] = React.useState("");
|
||||
const [selectedValues, setSelectedValues] = React.useState<string[]>(currentFilter);
|
||||
const [selectAll, setSelectAll] = React.useState(false);
|
||||
|
||||
// Sincronizar selectedValues com currentFilter quando ele mudar
|
||||
React.useEffect(() => {
|
||||
setSelectedValues(currentFilter);
|
||||
}, [currentFilter]);
|
||||
|
||||
// Obter valores únicos da coluna baseado nos dados filtrados
|
||||
const uniqueValues = React.useMemo(() => {
|
||||
const values = filteredData
|
||||
.map((row) => {
|
||||
const value = row[column.field];
|
||||
if (value === null || value === undefined) return "";
|
||||
return String(value);
|
||||
})
|
||||
.filter((value, index, self) => self.indexOf(value) === index && value !== "")
|
||||
.sort();
|
||||
|
||||
return values;
|
||||
}, [filteredData, column.field]);
|
||||
|
||||
// Filtrar valores baseado na busca
|
||||
const filteredValues = React.useMemo(() => {
|
||||
if (!searchTerm) return uniqueValues;
|
||||
return uniqueValues.filter((value) =>
|
||||
value.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
}, [uniqueValues, searchTerm]);
|
||||
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
if (checked) {
|
||||
setSelectedValues(filteredValues);
|
||||
setSelectAll(true);
|
||||
} else {
|
||||
setSelectedValues([]);
|
||||
setSelectAll(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleValueToggle = (value: string, checked: boolean) => {
|
||||
let newValues: string[];
|
||||
if (checked) {
|
||||
newValues = [...selectedValues, value];
|
||||
} else {
|
||||
newValues = selectedValues.filter((v) => v !== value);
|
||||
}
|
||||
setSelectedValues(newValues);
|
||||
setSelectAll(newValues.length === filteredValues.length && filteredValues.length > 0);
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
onFilterChange(column.field, selectedValues);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setSelectedValues([]);
|
||||
setSelectAll(false);
|
||||
onFilterChange(column.field, []);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleSort = (direction: 'asc' | 'desc') => {
|
||||
onSortChange(column.field, direction);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 hover:bg-gray-200 rounded-sm"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<ArrowUpDown className="h-3 w-3 text-gray-600" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-sm font-medium">
|
||||
Filtrar por "{column.headerName}"
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Opções de ordenação */}
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs font-medium text-gray-600">Ordenar</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-xs"
|
||||
onClick={() => handleSort('asc')}
|
||||
>
|
||||
<ArrowUp className="h-3 w-3 mr-1" />
|
||||
A a Z
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-xs"
|
||||
onClick={() => handleSort('desc')}
|
||||
>
|
||||
<ArrowDown className="h-3 w-3 mr-1" />
|
||||
Z a A
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 text-xs text-red-600 hover:text-red-700"
|
||||
onClick={handleClear}
|
||||
>
|
||||
<X className="h-3 w-3 mr-1" />
|
||||
Limpar Filtro
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Barra de pesquisa */}
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="Pesquisar"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-8 h-8 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Lista de valores com checkboxes */}
|
||||
<div className="max-h-60 overflow-y-auto border rounded-md">
|
||||
<div className="p-2">
|
||||
<div className="flex items-center space-x-2 py-1">
|
||||
<Checkbox
|
||||
id="select-all"
|
||||
checked={selectAll}
|
||||
onCheckedChange={handleSelectAll}
|
||||
/>
|
||||
<label htmlFor="select-all" className="text-sm font-medium">
|
||||
(Selecionar Tudo)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{filteredValues.map((value) => (
|
||||
<div key={value} className="flex items-center space-x-2 py-1">
|
||||
<Checkbox
|
||||
id={`value-${value}`}
|
||||
checked={selectedValues.includes(value)}
|
||||
onCheckedChange={(checked: boolean) => handleValueToggle(value, checked)}
|
||||
/>
|
||||
<label htmlFor={`value-${value}`} className="text-sm">
|
||||
{value}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Botões de ação */}
|
||||
<DialogFooter className="flex space-x-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setIsOpen(false)}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleApply}>
|
||||
OK
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
|
||||
const [data, setData] = React.useState<AnaliticoItem[]>([]);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [globalFilter, setGlobalFilter] = React.useState("");
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
||||
const [columnFilters, setColumnFilters] = React.useState<Record<string, string[]>>({});
|
||||
const [columnSorts, setColumnSorts] = React.useState<Record<string, 'asc' | 'desc' | null>>({});
|
||||
const [conditions, setConditions] = React.useState([
|
||||
{ column: "", operator: "contains", value: "" },
|
||||
]);
|
||||
|
||||
// Estados para o card de agregação customizado (simplificado)
|
||||
const [aggregationCardRef, setAggregationCardRef] = React.useState<HTMLDivElement | null>(null);
|
||||
|
||||
// Estado para armazenar filtros externos (vindos do teste.tsx)
|
||||
const [filtrosExternos, setFiltrosExternos] = React.useState(filtros);
|
||||
|
||||
// Funções para gerenciar filtros customizados
|
||||
const handleColumnFilterChange = React.useCallback((field: string, values: string[]) => {
|
||||
setColumnFilters(prev => ({
|
||||
...prev,
|
||||
[field]: values
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleColumnSortChange = React.useCallback((field: string, direction: 'asc' | 'desc' | null) => {
|
||||
setColumnSorts(prev => ({
|
||||
...prev,
|
||||
[field]: direction
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Função para contar filtros aplicados (apenas filtros internos do modal customizado)
|
||||
const getFilterCount = React.useCallback(() => {
|
||||
let count = 0;
|
||||
|
||||
// Contar filtros de coluna (filtros do modal customizado)
|
||||
count += Object.keys(columnFilters).length;
|
||||
|
||||
// Contar filtro global (se aplicável)
|
||||
if (globalFilter && globalFilter.trim() !== "") {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}, [columnFilters, globalFilter]);
|
||||
|
||||
// Função para limpar todos os filtros internos (mantém filtros externos)
|
||||
const clearAllFilters = React.useCallback(() => {
|
||||
setColumnFilters({});
|
||||
setColumnSorts({});
|
||||
setGlobalFilter("");
|
||||
}, []);
|
||||
|
||||
// Atualizar filtros externos quando os props mudarem
|
||||
React.useEffect(() => {
|
||||
console.log('🔄 Analítico - useEffect dos filtros chamado');
|
||||
console.log('📋 Filtros recebidos via props:', filtros);
|
||||
setFiltrosExternos(filtros);
|
||||
}, [filtros]);
|
||||
|
||||
const fetchData = React.useCallback(async () => {
|
||||
console.log('🔄 Analítico - fetchData chamado');
|
||||
console.log('📋 Filtros externos recebidos:', filtrosExternos);
|
||||
|
||||
if (!filtrosExternos.dataInicio || !filtrosExternos.dataFim) {
|
||||
console.log('⚠️ Sem dataInicio ou dataFim, limpando dados');
|
||||
setData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (filtrosExternos.dataInicio) {
|
||||
params.append('dataInicio', filtrosExternos.dataInicio);
|
||||
}
|
||||
if (filtrosExternos.dataFim) {
|
||||
params.append('dataFim', filtrosExternos.dataFim);
|
||||
}
|
||||
if (filtrosExternos.codigoGrupo) {
|
||||
params.append('codigoGrupo', filtrosExternos.codigoGrupo);
|
||||
}
|
||||
if (filtrosExternos.codigoConta) {
|
||||
params.append('codigoConta', filtrosExternos.codigoConta);
|
||||
}
|
||||
if (filtrosExternos.filial) {
|
||||
params.append('filial', filtrosExternos.filial);
|
||||
}
|
||||
if (filtrosExternos.excluirCodigoConta) {
|
||||
params.append('excluirCodigoConta', filtrosExternos.excluirCodigoConta);
|
||||
}
|
||||
if (filtrosExternos.codigosContasSelecionadas) {
|
||||
params.append('codigosContasSelecionadas', filtrosExternos.codigosContasSelecionadas);
|
||||
}
|
||||
|
||||
const url = `/api/analitico-filial-oracle?${params.toString()}`;
|
||||
console.log('🌐 Fazendo requisição para:', url);
|
||||
console.log('📋 Parâmetros enviados:', {
|
||||
dataInicio: filtrosExternos.dataInicio,
|
||||
dataFim: filtrosExternos.dataFim,
|
||||
codigoGrupo: filtrosExternos.codigoGrupo,
|
||||
codigoConta: filtrosExternos.codigoConta,
|
||||
filial: filtrosExternos.filial,
|
||||
codigosContasSelecionadas: filtrosExternos.codigosContasSelecionadas
|
||||
});
|
||||
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
console.log('✅ Resposta da API recebida:', result.length, 'registros');
|
||||
console.log('📝 Primeiros 2 registros:', result.slice(0, 2));
|
||||
setData(result as AnaliticoItem[]);
|
||||
} else {
|
||||
console.error("❌ Erro ao buscar dados:", await response.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Erro ao buscar dados:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [filtrosExternos]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
// Filtrar dados baseado nos filtros de coluna
|
||||
const filteredData = React.useMemo(() => {
|
||||
if (!data || data.length === 0) return data;
|
||||
|
||||
return data.filter((row) => {
|
||||
return Object.entries(columnFilters).every(([field, filterValues]) => {
|
||||
if (!filterValues || filterValues.length === 0) return true;
|
||||
|
||||
const cellValue = (row as any)[field];
|
||||
const stringValue = cellValue === null || cellValue === undefined ? "" : String(cellValue);
|
||||
|
||||
return filterValues.includes(stringValue);
|
||||
});
|
||||
}).map((row, index) => ({
|
||||
...row,
|
||||
id: `filtered-${row.id || row.recnum || index}` // Garantir ID único e estável
|
||||
}));
|
||||
}, [data, columnFilters]);
|
||||
|
||||
// Função para renderizar header com filtro Excel
|
||||
const renderHeaderWithFilter = React.useCallback((column: GridColDef) => {
|
||||
return (params: any) => (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span className="text-sm font-medium">{column.headerName}</span>
|
||||
<div className="flex items-center">
|
||||
<ExcelFilter
|
||||
column={column}
|
||||
data={data}
|
||||
filteredData={filteredData}
|
||||
onFilterChange={handleColumnFilterChange}
|
||||
onSortChange={handleColumnSortChange}
|
||||
currentFilter={columnFilters[column.field] || []}
|
||||
currentSort={columnSorts[column.field] || null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [data, filteredData, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]);
|
||||
|
||||
// Definir colunas do DataGridPro na ordem solicitada
|
||||
const columns = React.useMemo(() => {
|
||||
const dateCellRenderer = (params: any) => {
|
||||
if (!params.value) return "-";
|
||||
try {
|
||||
return new Date(params.value).toLocaleDateString("pt-BR");
|
||||
} catch (error) {
|
||||
return params.value;
|
||||
}
|
||||
};
|
||||
|
||||
const currencyCellRenderer = (params: any, showZero: boolean = false) => {
|
||||
const value = params.value;
|
||||
if (value === null || value === undefined || value === "") return "-";
|
||||
if (!showZero && value === 0) return "-";
|
||||
const numValue = typeof value === "string" ? parseFloat(value) : Number(value);
|
||||
if (isNaN(numValue)) return "-";
|
||||
const formatted = new Intl.NumberFormat("pt-BR", {
|
||||
style: "currency",
|
||||
currency: "BRL",
|
||||
}).format(numValue);
|
||||
return (
|
||||
<span className={`text-sm font-semibold ${numValue < 0 ? "text-red-600" : "text-gray-900"}`}>
|
||||
{formatted}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const baseColumns = [
|
||||
{
|
||||
field: "ano_mes_comp",
|
||||
headerName: "Ano/Mês Comp",
|
||||
width: 110,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
renderCell: (params: any) => params.value || "-",
|
||||
},
|
||||
{
|
||||
field: "filial",
|
||||
headerName: "Filial",
|
||||
width: 90,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
renderCell: (params: any) => params.value || "-",
|
||||
},
|
||||
{
|
||||
field: "codigo_grupo",
|
||||
headerName: "Cod.Grupo",
|
||||
width: 100,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
field: "grupo",
|
||||
headerName: "Grupo",
|
||||
width: 200,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
field: "codigo_conta",
|
||||
headerName: "Cod.Conta",
|
||||
width: 100,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
field: "conta",
|
||||
headerName: "Conta",
|
||||
width: 200,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
},
|
||||
{
|
||||
field: "valor",
|
||||
headerName: "VI.Realizado",
|
||||
type: "number" as const,
|
||||
width: 120,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
renderCell: (params: any) => currencyCellRenderer(params, true),
|
||||
},
|
||||
{
|
||||
field: "tipo",
|
||||
headerName: "Tipo",
|
||||
width: 95,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
renderCell: (params: any) => params.value || "-",
|
||||
},
|
||||
];
|
||||
|
||||
// Adicionar renderHeader com filtro Excel para todas as colunas
|
||||
return baseColumns.map((col) => ({
|
||||
...col,
|
||||
renderHeader: renderHeaderWithFilter(col),
|
||||
}));
|
||||
}, [renderHeaderWithFilter]);
|
||||
|
||||
// Ordenar dados baseado na ordenação de coluna
|
||||
const sortedAndFilteredData = React.useMemo(() => {
|
||||
if (!filteredData || filteredData.length === 0) return filteredData;
|
||||
|
||||
const sortField = Object.keys(columnSorts).find(field => columnSorts[field] !== null);
|
||||
if (!sortField || !columnSorts[sortField]) return filteredData;
|
||||
|
||||
return [...filteredData].sort((a, b) => {
|
||||
const aValue = (a as any)[sortField];
|
||||
const bValue = (b as any)[sortField];
|
||||
|
||||
// Converter para string para comparação
|
||||
const aString = aValue === null || aValue === undefined ? "" : String(aValue);
|
||||
const bString = bValue === null || bValue === undefined ? "" : String(bValue);
|
||||
|
||||
if (columnSorts[sortField] === 'asc') {
|
||||
return aString.localeCompare(bString);
|
||||
} else {
|
||||
return bString.localeCompare(aString);
|
||||
}
|
||||
});
|
||||
}, [filteredData, columnSorts]);
|
||||
|
||||
// Calcular valor total dos dados filtrados
|
||||
const valorTotal = React.useMemo(() => {
|
||||
return sortedAndFilteredData.reduce((sum, item) => sum + (Number(item.valor) || 0), 0);
|
||||
}, [sortedAndFilteredData]);
|
||||
|
||||
// Limpar filtros de colunas que não têm mais valores disponíveis
|
||||
React.useEffect(() => {
|
||||
const updatedFilters = { ...columnFilters };
|
||||
let hasChanges = false;
|
||||
|
||||
Object.keys(columnFilters).forEach(field => {
|
||||
const currentFilterValues = columnFilters[field] || [];
|
||||
if (currentFilterValues.length === 0) return;
|
||||
|
||||
// Obter valores únicos disponíveis para esta coluna nos dados filtrados
|
||||
const availableValues = filteredData
|
||||
.map(row => {
|
||||
const value = (row as any)[field];
|
||||
return value === null || value === undefined ? "" : String(value);
|
||||
})
|
||||
.filter((value, index, self) => self.indexOf(value) === index && value !== "");
|
||||
|
||||
// Filtrar apenas os valores que ainda estão disponíveis
|
||||
const validFilterValues = currentFilterValues.filter(value =>
|
||||
availableValues.includes(value)
|
||||
);
|
||||
|
||||
if (validFilterValues.length !== currentFilterValues.length) {
|
||||
if (validFilterValues.length === 0) {
|
||||
delete updatedFilters[field];
|
||||
} else {
|
||||
updatedFilters[field] = validFilterValues;
|
||||
}
|
||||
hasChanges = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasChanges) {
|
||||
setColumnFilters(updatedFilters);
|
||||
}
|
||||
}, [filteredData, columnFilters]);
|
||||
|
||||
// Exportação XLSX - Exporta exatamente as colunas e valores da grid
|
||||
const exportToExcel = () => {
|
||||
if (sortedAndFilteredData.length === 0) return;
|
||||
|
||||
// Funções auxiliares para formatar valores exatamente como na grid
|
||||
const formatCurrencyValue = (value: any, showZero: boolean = false): string | number => {
|
||||
if (value === null || value === undefined || value === "") return "-";
|
||||
const numValue = typeof value === "string" ? parseFloat(value) : Number(value);
|
||||
if (isNaN(numValue)) return "-";
|
||||
if (!showZero && numValue === 0) return "-";
|
||||
// Para Excel, retornar o número formatado como string (mantém o formato de moeda)
|
||||
return new Intl.NumberFormat("pt-BR", {
|
||||
style: "currency",
|
||||
currency: "BRL",
|
||||
}).format(numValue);
|
||||
};
|
||||
|
||||
const formatCellValue = (column: GridColDef, item: any): any => {
|
||||
const value = item[column.field];
|
||||
|
||||
// Se a coluna tem renderCell, aplicar a mesma lógica
|
||||
if (column.renderCell) {
|
||||
// Para valores monetários
|
||||
if (column.field === "valor") {
|
||||
return formatCurrencyValue(value, true);
|
||||
}
|
||||
|
||||
// Para campos que retornam "-" se vazios
|
||||
if (column.field === "filial" || column.field === "ano_mes_comp" ||
|
||||
column.field === "tipo") {
|
||||
return value || "-";
|
||||
}
|
||||
}
|
||||
|
||||
// Valor padrão
|
||||
return value ?? "";
|
||||
};
|
||||
|
||||
// Criar dados de exportação usando as colunas da grid na ordem exata
|
||||
const exportData = sortedAndFilteredData.map((item) => {
|
||||
const row: Record<string, any> = {};
|
||||
|
||||
// Iterar sobre as colunas na ordem da grid
|
||||
columns.forEach((column) => {
|
||||
const headerName = column.headerName || column.field;
|
||||
row[headerName] = formatCellValue(column, item);
|
||||
});
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
const wb = XLSX.utils.book_new();
|
||||
const ws = XLSX.utils.json_to_sheet(exportData);
|
||||
|
||||
const resumoData = [
|
||||
{ Métrica: "Total de Registros", Valor: sortedAndFilteredData.length },
|
||||
{ Métrica: "Valor Total", Valor: valorTotal },
|
||||
{ Métrica: "Filtros Aplicados", Valor: Object.keys(columnFilters).length > 0 ? "Sim" : "Não" },
|
||||
];
|
||||
const wsResumo = XLSX.utils.json_to_sheet(resumoData);
|
||||
|
||||
XLSX.utils.book_append_sheet(wb, ws, "Dados Analíticos");
|
||||
XLSX.utils.book_append_sheet(wb, wsResumo, "Resumo");
|
||||
|
||||
const now = new Date();
|
||||
const timestamp = now.toISOString().slice(0, 19).replace(/:/g, "-");
|
||||
const fileName = `analitico_filial_${timestamp}.xlsx`;
|
||||
|
||||
XLSX.writeFile(wb, fileName);
|
||||
};
|
||||
|
||||
// Aplicar filtros avançados
|
||||
const applyFilters = () => {
|
||||
// Implementar lógica de filtros avançados se necessário
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const clearFilters = () => {
|
||||
setConditions([{ column: "", operator: "contains", value: "" }]);
|
||||
setGlobalFilter("");
|
||||
};
|
||||
|
||||
// Função para renderizar o conteúdo principal do componente (reutilizável)
|
||||
const renderAnaliticoContent = (isMaximized: boolean = false) => {
|
||||
return (
|
||||
<>
|
||||
{/* Filtros Externos Ativos - Apenas quando maximizado */}
|
||||
{isMaximized && (filtrosExternos.dataInicio || filtrosExternos.codigoGrupo || filtrosExternos.codigoConta) && (
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||
<span className="text-sm font-medium text-blue-900">Filtros aplicados pela tabela DRE Filial:</span>
|
||||
<div className="flex flex-wrap gap-2 text-xs text-blue-800">
|
||||
{filtrosExternos.dataInicio && filtrosExternos.dataFim && (
|
||||
<span className="px-2 py-1 bg-blue-100 rounded">
|
||||
Período: {filtrosExternos.dataInicio} a {filtrosExternos.dataFim}
|
||||
</span>
|
||||
)}
|
||||
{filtrosExternos.codigoGrupo && (
|
||||
<span className="px-2 py-1 bg-blue-100 rounded">
|
||||
Grupo: {filtrosExternos.codigoGrupo}
|
||||
</span>
|
||||
)}
|
||||
{filtrosExternos.codigoConta && (
|
||||
<span className="px-2 py-1 bg-blue-100 rounded">
|
||||
Conta: {filtrosExternos.codigoConta}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Controls - Apenas quando maximizado */}
|
||||
{isMaximized && (
|
||||
<div className="flex gap-2 flex-wrap mb-4">
|
||||
{data.length > 0 && (
|
||||
<Button
|
||||
onClick={clearAllFilters}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center gap-2 bg-white border-gray-300 hover:bg-red-50 hover:border-red-300 text-gray-700"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
Limpar Filtros
|
||||
{getFilterCount() > 0 && (
|
||||
<span className="bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center font-semibold">
|
||||
{getFilterCount()}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={exportToExcel}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={sortedAndFilteredData.length === 0}
|
||||
className="flex items-center gap-2 bg-white border-gray-300 hover:bg-green-50 hover:border-green-300 text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Exportar XLSX
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* DataGridPro */}
|
||||
<Card className={`w-full shadow-lg rounded-2xl ${isMaximized ? 'h-[calc(96vh-280px)]' : 'h-[40vh]'}`} style={{ overflowAnchor: 'none' }}>
|
||||
<CardContent className="p-4 h-full" style={{ overflowAnchor: 'none' }}>
|
||||
<div className="flex items-center gap-6 mb-3 pb-3 border-b border-gray-200">
|
||||
<div className="text-sm font-medium text-gray-700">
|
||||
<span className="font-semibold">Total de Registros:</span>{" "}
|
||||
<span className="text-blue-600 font-bold">{sortedAndFilteredData.length}</span>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-700">
|
||||
<span className="font-semibold">Valor Total:</span>{" "}
|
||||
<span className={`font-bold ${valorTotal < 0 ? 'text-red-600' : 'text-gray-900'}`}>
|
||||
{new Intl.NumberFormat("pt-BR", {
|
||||
style: "currency",
|
||||
currency: "BRL",
|
||||
}).format(valorTotal)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ height: "calc(100% - 2rem)", width: "100%", position: "relative" }}>
|
||||
<DataGridPremium
|
||||
key={`datagrid-${sortedAndFilteredData.length}-${Object.keys(columnFilters).length}`}
|
||||
rows={sortedAndFilteredData}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
disableRowSelectionOnClick
|
||||
density="compact"
|
||||
slots={{ toolbar: GridToolbar }}
|
||||
disableColumnMenu={true}
|
||||
disableColumnSorting={true}
|
||||
pagination={false}
|
||||
disableVirtualization={false}
|
||||
getRowId={(row: any) => row.id || `row-${row.recnum || Math.random()}`}
|
||||
sx={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
"& .MuiDataGrid-root": {
|
||||
border: "none",
|
||||
},
|
||||
"& .MuiDataGrid-columnHeaders": {
|
||||
backgroundColor: "#f9fafb",
|
||||
borderBottom: "1px solid #e5e7eb",
|
||||
},
|
||||
"& .MuiDataGrid-columnHeader": {
|
||||
backgroundColor: "#f9fafb !important",
|
||||
fontWeight: 600,
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
"& .MuiDataGrid-cell": {
|
||||
borderBottom: "1px solid #f0f0f0",
|
||||
fontSize: "0.875rem",
|
||||
},
|
||||
"& .MuiDataGrid-virtualScroller": {
|
||||
scrollbarWidth: "thin",
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "8px",
|
||||
height: "8px",
|
||||
},
|
||||
"&::-webkit-scrollbar-track": {
|
||||
background: "#f1f1f1",
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
background: "#888",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb:hover": {
|
||||
background: "#555",
|
||||
},
|
||||
},
|
||||
"& .MuiDataGrid-toolbarContainer": {
|
||||
backgroundColor: "#f8fafc",
|
||||
borderBottom: "1px solid #e5e7eb",
|
||||
padding: "8px 16px",
|
||||
},
|
||||
"& .MuiDataGrid-columnHeaderMenuContainer": {
|
||||
display: "none !important",
|
||||
},
|
||||
"& .MuiDataGrid-columnHeaderMenuButton": {
|
||||
display: "none !important",
|
||||
},
|
||||
"& .MuiDataGrid-columnHeaderSortIcon": {
|
||||
display: "none !important",
|
||||
},
|
||||
"& .MuiDataGrid-footerContainer": {
|
||||
display: "none !important",
|
||||
},
|
||||
"& .MuiDataGrid-columnHeaderTitleContainer": {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-none mx-auto p-2" style={{ overflowAnchor: 'none' }}>
|
||||
{/* Header Section */}
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
Análise Analítica{filtros.linhaSelecionada ? ` - ${filtros.linhaSelecionada}` : ""}
|
||||
</h1>
|
||||
<p className="text-sm text-gray-500">
|
||||
Relatório detalhado de transações
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Filtros Externos Ativos - Centralizado */}
|
||||
{(filtrosExternos.dataInicio || filtrosExternos.codigoGrupo || filtrosExternos.codigoConta) && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||
<span className="text-sm font-medium text-blue-900">Filtros aplicados pela tabela DRE Filial:</span>
|
||||
<div className="flex flex-wrap gap-2 text-xs text-blue-800">
|
||||
{filtrosExternos.dataInicio && filtrosExternos.dataFim && (
|
||||
<span className="px-2 py-1 bg-blue-100 rounded">
|
||||
Período: {filtrosExternos.dataInicio} a {filtrosExternos.dataFim}
|
||||
</span>
|
||||
)}
|
||||
{filtrosExternos.codigoGrupo && (
|
||||
<span className="px-2 py-1 bg-blue-100 rounded">
|
||||
Grupo: {filtrosExternos.codigoGrupo}
|
||||
</span>
|
||||
)}
|
||||
{filtrosExternos.codigoConta && (
|
||||
<span className="px-2 py-1 bg-blue-100 rounded">
|
||||
Conta: {filtrosExternos.codigoConta}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
{data.length > 0 && (
|
||||
<Button
|
||||
onClick={clearAllFilters}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center gap-2 bg-white border-gray-300 hover:bg-red-50 hover:border-red-300 text-gray-700"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
Limpar Filtros
|
||||
{getFilterCount() > 0 && (
|
||||
<span className="bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center font-semibold">
|
||||
{getFilterCount()}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={exportToExcel}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={sortedAndFilteredData.length === 0}
|
||||
className="flex items-center gap-2 bg-white border-gray-300 hover:bg-green-50 hover:border-green-300 text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
Exportar XLSX
|
||||
</Button>
|
||||
<Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center gap-2 bg-white border-gray-300 hover:bg-blue-50 hover:border-blue-300 text-gray-700"
|
||||
>
|
||||
<Maximize2 className="h-4 w-4" />
|
||||
Maximizar
|
||||
</Button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="max-h-[96vh] h-[96vh]">
|
||||
<DrawerHeader className="flex-shrink-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<DrawerTitle className="text-2xl font-bold text-gray-900">
|
||||
Análise Analítica{filtros.linhaSelecionada ? ` - ${filtros.linhaSelecionada}` : ""}
|
||||
</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Relatório detalhado de transações - Versão Maximizada
|
||||
</DrawerDescription>
|
||||
</div>
|
||||
<DrawerClose asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center gap-2 bg-white border-gray-300 hover:bg-red-50 hover:border-red-300 text-gray-700"
|
||||
>
|
||||
<Minimize2 className="h-4 w-4" />
|
||||
Minimizar
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
</div>
|
||||
</DrawerHeader>
|
||||
<div className="flex-1 overflow-y-auto px-4 pb-4">
|
||||
{renderAnaliticoContent(true)}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Conteúdo Principal - Versão Normal */}
|
||||
{renderAnaliticoContent(false)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import Teste from './teste';
|
||||
|
||||
export default function DreFilialPage() {
|
||||
return (
|
||||
<div className="w-full min-h-screen p-1">
|
||||
<Teste />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue