"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 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 { Download, Filter, X, Search, ArrowUpDown, ArrowUp, ArrowDown } 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; 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; } interface AnaliticoProps { filtros: { dataInicio: string; dataFim: string; centroCusto?: string; codigoGrupo?: string; codigoSubgrupo?: string; codigoConta?: string; linhaSelecionada?: string; excluirCentroCusto?: string; excluirCodigoConta?: 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 = ({ column, data, filteredData, onFilterChange, onSortChange, currentFilter = [], currentSort = null, }) => { const [isOpen, setIsOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(""); const [selectedValues, setSelectedValues] = React.useState(currentFilter); const [selectAll, setSelectAll] = React.useState(false); // 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]); // Sincronizar estado local com filtros atuais React.useEffect(() => { setSelectedValues(currentFilter); }, [currentFilter]); // Verificar se todos estão selecionados React.useEffect(() => { setSelectAll(selectedValues.length === filteredValues.length && filteredValues.length > 0); }, [selectedValues, filteredValues]); const handleSelectAll = (checked: boolean) => { if (checked) { setSelectedValues(filteredValues); } else { setSelectedValues([]); } }; const handleValueToggle = (value: string, checked: boolean) => { if (checked) { setSelectedValues([...selectedValues, value]); } else { setSelectedValues(selectedValues.filter((v) => v !== value)); } }; 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 ( Filtrar por "{column.headerName}"
{/* Opções de ordenação */}
Ordenar
{/* Barra de pesquisa */}
setSearchTerm(e.target.value)} className="pl-8 h-8 text-sm" />
{/* Lista de valores com checkboxes */}
{filteredValues.map((value) => (
handleValueToggle(value, checked)} />
))}
{/* Botões de ação */}
); }; export default function AnaliticoComponent({ filtros }: AnaliticoProps) { const [data, setData] = React.useState([]); const [loading, setLoading] = React.useState(false); const [globalFilter, setGlobalFilter] = React.useState(""); const [open, setOpen] = React.useState(false); const [columnFilters, setColumnFilters] = React.useState>({}); const [columnSorts, setColumnSorts] = React.useState>({}); const [conditions, setConditions] = React.useState([ { column: "", operator: "contains", value: "" }, ]); // 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.centroCusto) { params.append('centroCusto', filtrosExternos.centroCusto); } if (filtrosExternos.codigoGrupo) { params.append('codigoGrupo', filtrosExternos.codigoGrupo); } if (filtrosExternos.codigoConta) { params.append('codigoConta', filtrosExternos.codigoConta); } if (filtrosExternos.excluirCentroCusto) { params.append('excluirCentroCusto', filtrosExternos.excluirCentroCusto); } if (filtrosExternos.excluirCodigoConta) { params.append('excluirCodigoConta', filtrosExternos.excluirCodigoConta); } const url = `/api/analitico-oracle?${params.toString()}`; console.log('🌐 Fazendo requisição para:', url); 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)); console.log('🔍 Verificando campos específicos:', { data_vencimento: result[0]?.data_vencimento, data_caixa: result[0]?.data_caixa, entidade: result[0]?.entidade, valor: result[0]?.valor, tipo_valor: typeof result[0]?.valor }); 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]); // Definir colunas do DataGridPro const columns = React.useMemo(() => { const baseColumns = [ { field: "data_vencimento", headerName: "Dt Venc", width: 150, sortable: true, resizable: true, renderCell: (params: any) => { if (!params.value) return "-"; try { return new Date(params.value).toLocaleDateString("pt-BR"); } catch (error) { return params.value; } }, }, { field: "data_caixa", headerName: "Dt Caixa", width: 130, sortable: true, resizable: true, renderCell: (params: any) => { if (!params.value) return "-"; try { return new Date(params.value).toLocaleDateString("pt-BR"); } catch (error) { return params.value; } }, }, { field: "entidade", headerName: "Entidade", width: 100, sortable: true, resizable: true, renderCell: (params: any) => params.value || "-", }, { field: "codigo_fornecedor", headerName: "Cod.Fornec", width: 140, sortable: true, resizable: true, }, { field: "nome_fornecedor", headerName: "Fornecedor", flex: 1, minWidth: 200, sortable: true, resizable: true, }, { field: "codigo_centrocusto", headerName: "C Custo", width: 130, sortable: true, resizable: true, }, { field: "codigo_conta", headerName: "Cod.Conta", width: 150, sortable: true, resizable: true, }, { field: "conta", headerName: "Conta", flex: 1, minWidth: 180, sortable: true, resizable: true, }, { field: "valor", headerName: "Vl.Realizado", type: "number" as const, width: 140, sortable: true, resizable: true, aggregable: true, renderCell: (params: any) => { const value = params.value; if (value === null || value === undefined || value === "") 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 ( {formatted} ); }, }, { field: "valor_previsto", headerName: "Vl.Previsto", type: "number" as const, width: 130, sortable: true, resizable: true, aggregable: true, renderCell: (params: any) => { const value = params.value; if (value === null || value === undefined || value === "" || 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 ( {formatted} ); }, }, { field: "valor_confirmado", headerName: "Vl.Confirmado", type: "number" as const, width: 140, sortable: true, resizable: true, aggregable: true, renderCell: (params: any) => { const value = params.value; if (value === null || value === undefined || value === "" || 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 ( {formatted} ); }, }, { field: "valor_pago", headerName: "Vl.Pago", type: "number" as const, width: 130, sortable: true, resizable: true, aggregable: true, renderCell: (params: any) => { const value = params.value; if (value === null || value === undefined || value === "" || 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 ( {formatted} ); }, }, { field: "historico", headerName: "Historico", flex: 1, minWidth: 250, sortable: true, resizable: true, }, { field: "historico2", headerName: "Historico 2", flex: 1, minWidth: 300, sortable: true, resizable: true, }, { field: "numero_lancamento", headerName: "Num.Lanc", width: 80, sortable: true, resizable: true, renderCell: (params: any) => params.value || "-", }, ]; // Adicionar renderHeader com filtro Excel para todas as colunas return baseColumns.map((col) => ({ ...col, renderHeader: (params: any) => (
{col.headerName}
), })); }, [data, filteredData, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]); // 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 const exportToExcel = () => { if (sortedAndFilteredData.length === 0) return; const exportData = sortedAndFilteredData.map((item) => ({ "Data Competência": item.data_competencia ? new Date(item.data_competencia).toLocaleDateString("pt-BR") : "-", "Data Vencimento": item.data_vencimento ? new Date(item.data_vencimento).toLocaleDateString("pt-BR") : "-", "Data Caixa": item.data_caixa ? new Date(item.data_caixa).toLocaleDateString("pt-BR") : "-", "Código Fornecedor": item.codigo_fornecedor, Fornecedor: item.nome_fornecedor, "Código Centro Custo": item.codigo_centrocusto, "Centro Custo": item.codigo_centrocusto, "Código Conta": item.codigo_conta, Conta: item.conta, Valor: typeof item.valor === "string" ? parseFloat(item.valor) : item.valor, Histórico: item.historico, "Histórico 2": item.historico2, Recnum: item.recnum, })); 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_${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(""); }; return (
{/* Header Section */}

Análise Analítica{filtros.linhaSelecionada ? ` - ${filtros.linhaSelecionada}` : ""}

Relatório detalhado de transações

{/* Filtros Externos Ativos - Centralizado */} {(filtrosExternos.dataInicio || filtrosExternos.centroCusto || filtrosExternos.codigoGrupo || filtrosExternos.codigoConta) && (
Filtros aplicados pela tabela DRE Gerencial:
{filtrosExternos.dataInicio && filtrosExternos.dataFim && ( Período: {filtrosExternos.dataInicio} a {filtrosExternos.dataFim} )} {filtrosExternos.centroCusto && ( Centro: {filtrosExternos.centroCusto} )} {filtrosExternos.codigoGrupo && ( Grupo: {filtrosExternos.codigoGrupo} )} {filtrosExternos.codigoConta && ( Conta: {filtrosExternos.codigoConta} )}
)} {/* Controls */}
{/* ) => setGlobalFilter(e.target.value) } className="w-64 bg-white border-gray-300 focus:border-blue-500 focus:ring-blue-500" /> */} {globalFilter && ( )} {data.length > 0 && (
)}
{/* DataGridPro */}

Total de Registros: {sortedAndFilteredData.length}

Valor Total: {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(valorTotal)}
row.id || `row-${row.recnum || Math.random()}`} sx={{ "& .MuiDataGrid-columnHeaders": { position: "sticky", top: 0, backgroundColor: "#f9fafb", zIndex: 2, borderBottom: "1px solid #e5e7eb", }, "& .MuiDataGrid-cell": { borderBottom: "1px solid #f0f0f0", fontSize: "0.875rem", }, "& .MuiDataGrid-virtualScroller": { overflowY: "auto", maxHeight: "calc(40vh - 120px)" }, "& .MuiDataGrid-toolbarContainer": { backgroundColor: "#f8fafc", borderBottom: "1px solid #e5e7eb", padding: "8px 16px", }, // Ocultar todos os ícones nativos das colunas "& .MuiDataGrid-columnHeaderMenuContainer": { display: "none !important", }, "& .MuiDataGrid-columnHeaderMenuButton": { display: "none !important", }, "& .MuiDataGrid-columnHeaderSortIcon": { display: "none !important", }, "& .MuiDataGrid-columnHeaderSortIconContainer": { display: "none !important", }, "& .MuiDataGrid-iconButtonContainer": { display: "none !important", }, "& .MuiDataGrid-columnHeaderSeparator": { display: "none !important", }, "& .MuiDataGrid-columnHeaderSortButton": { display: "none !important", }, // Ocultar qualquer ícone de menu adicional "& .MuiDataGrid-menuIcon": { display: "none !important", }, "& .MuiDataGrid-menuIconButton": { display: "none !important", }, "& .MuiDataGrid-columnHeaderMenuIcon": { display: "none !important", }, "& .MuiDataGrid-columnHeaderMenuIconButton": { display: "none !important", }, "& .MuiDataGrid-menuContainer": { display: "none !important", }, "& .MuiDataGrid-menu": { display: "none !important", }, // Ocultar footer de paginação "& .MuiDataGrid-footerContainer": { display: "none !important", }, "& .MuiDataGrid-pagination": { display: "none !important", }, "& .MuiTablePagination-root": { display: "none !important", }, // Garantir que nosso botão customizado apareça "& .MuiDataGrid-columnHeaderTitleContainer": { width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between", }, }} />
{/* Advanced Filters Dialog */} Filtros Avançados

Estes filtros são aplicados sobre os dados já filtrados pela tabela DRE Gerencial.

{conditions.map((cond, idx) => (
{!(cond.operator === "empty" || cond.operator === "notEmpty") && (
) => { const next = [...conditions]; next[idx].value = e.target.value; setConditions(next); }} placeholder="Digite o valor" className="w-full bg-white border-gray-300" />
)} {conditions.length > 1 && (
)}
))}
); }