"use client"; import * as React from "react"; import { DataGrid, GridToolbar, GridColDef, GridFilterModel } from "@mui/x-data-grid"; 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; }; } // Componente de filtro customizado estilo Excel interface ExcelFilterProps { column: GridColDef; data: any[]; 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, 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 const uniqueValues = React.useMemo(() => { const values = data .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; }, [data, 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 limpar todos os filtros const clearAllFilters = React.useCallback(() => { setColumnFilters({}); setColumnSorts({}); }, []); // 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); } 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]); // 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, 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, 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, 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, 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, columnFilters, columnSorts, handleColumnFilterChange, handleColumnSortChange]); // 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]); // 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 totais das colunas de valores (usando dados filtrados) const columnTotals = React.useMemo(() => { if (!sortedAndFilteredData || sortedAndFilteredData.length === 0) { return { valorRealizado: 0, valorPrevisto: 0, valorConfirmado: 0, valorPago: 0, }; } const valorRealizado = sortedAndFilteredData.reduce((sum, item) => { const valor = typeof item.valor === "string" ? parseFloat(item.valor) : item.valor; return sum + (isNaN(valor) ? 0 : valor); }, 0); const valorPrevisto = sortedAndFilteredData.reduce((sum, item) => { const valor = typeof item.valor_previsto === "string" ? parseFloat(item.valor_previsto) : (item.valor_previsto || 0); return sum + (isNaN(valor) ? 0 : valor); }, 0); const valorConfirmado = sortedAndFilteredData.reduce((sum, item) => { const valor = typeof item.valor_confirmado === "string" ? parseFloat(item.valor_confirmado) : (item.valor_confirmado || 0); return sum + (isNaN(valor) ? 0 : valor); }, 0); const valorPago = sortedAndFilteredData.reduce((sum, item) => { const valor = typeof item.valor_pago === "string" ? parseFloat(item.valor_pago) : (item.valor_pago || 0); return sum + (isNaN(valor) ? 0 : valor); }, 0); return { valorRealizado, valorPrevisto, valorConfirmado, valorPago, }; }, [sortedAndFilteredData]); // 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: data.length }, { Métrica: "Valor Total", Valor: columnTotals.valorRealizado }, { Métrica: "Filtros Aplicados", Valor: "Sim" }, ]; 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: {data.length}

Valor Total: {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorRealizado)}
row.id || `row-${row.recnum || Math.random()}`} initialState={{ sorting: { sortModel: [{ field: "data_vencimento", sort: "desc" }] }, }} 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" }, "& .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", }, }} /> {/* Footer com Totalizadores - Posicionado no lugar do footer nativo */} {sortedAndFilteredData.length > 0 && (
{/* Espaçamento para alinhar com as colunas da tabela */}
{/* Dt Venc */}
{/* Dt Caixa */}
{/* Entidade */}
{/* Cod.Fornec */}
{/* Fornecedor */}
{/* C Custo */}
{/* Cod.Conta */}
{/* Conta */} {/* Totalizadores das colunas de valor */}
{new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorRealizado)}
{new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorPrevisto)}
{new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorConfirmado)}
{new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorPago)}
{/* Espaçamento para o resto */}
{/* Historico */}
{/* Historico 2 */}
{/* Num.Lanc */}
)}
{/* 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 && (
)}
))}
); }