"use client"; import * as React from "react"; import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, ColumnFiltersState, ColumnSizingState, } from "@tanstack/react-table"; import { useVirtualizer } from "@tanstack/react-virtual"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from "@/components/ui/select"; import { Download, Filter, X } 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; // Adicionar propriedade para linha selecionada }; } export default function AnaliticoComponent({ filtros }: AnaliticoProps) { const [data, setData] = React.useState([]); const [loading, setLoading] = React.useState(false); const [globalFilter, setGlobalFilter] = React.useState(""); const [columnFilters, setColumnFilters] = React.useState([]); const [columnSizing, setColumnSizing] = React.useState({}); const [open, setOpen] = React.useState(false); const [conditions, setConditions] = React.useState([ { column: "", operator: "contains", value: "" }, ]); // Estado para armazenar filtros externos (vindos do teste.tsx) const [filtrosExternos, setFiltrosExternos] = React.useState(filtros); // Atualizar filtros externos quando os props mudarem, mas preservar filtros internos React.useEffect(() => { console.log('🔄 Analítico - useEffect dos filtros chamado'); console.log('📋 Filtros recebidos via props:', filtros); console.log('📋 Filtros externos atuais:', filtrosExternos); console.log('🔍 Detalhes dos filtros:', { dataInicio: filtros.dataInicio, dataFim: filtros.dataFim, centroCusto: filtros.centroCusto, codigoGrupo: filtros.codigoGrupo, codigoConta: filtros.codigoConta, linhaSelecionada: filtros.linhaSelecionada }); setFiltrosExternos(filtros); }, [filtros, filtrosExternos]); const fetchData = React.useCallback(async () => { console.log('🔄 Analítico - fetchData chamado'); console.log('📋 Filtros externos recebidos:', filtrosExternos); // Só faz a requisição se tiver dataInicio e dataFim nos filtros externos if (!filtrosExternos.dataInicio || !filtrosExternos.dataFim) { console.log('⚠️ Sem dataInicio ou dataFim, limpando dados'); setData([]); return; } setLoading(true); try { // Construir URL com parâmetros de query 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); console.log('📋 Parâmetros enviados:', { dataInicio: filtrosExternos.dataInicio, dataFim: filtrosExternos.dataFim, centroCusto: filtrosExternos.centroCusto, codigoGrupo: filtrosExternos.codigoGrupo, codigoConta: filtrosExternos.codigoConta }); 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 3 registros:', result.slice(0, 3)); 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]); const columns = React.useMemo( () => [ { accessorKey: "data_vencimento", header: "Data de Vencimento", filterFn: "advancedText", size: 140, minSize: 100, maxSize: 200, cell: ({ getValue }: { getValue: () => string }) => { const value = getValue(); return new Date(value).toLocaleDateString("pt-BR"); }, }, { accessorKey: "data_caixa", header: "Data de Caixa", filterFn: "advancedText", size: 120, minSize: 100, maxSize: 180, cell: ({ getValue }: { getValue: () => string }) => { const value = getValue(); return new Date(value).toLocaleDateString("pt-BR"); }, }, { accessorKey: "entidade", header: "Entidade", filterFn: "advancedText", size: 120, minSize: 80, maxSize: 200, cell: ({ getValue }: { getValue: () => string }) => { const value = getValue(); return value || "-"; }, }, { accessorKey: "codigo_fornecedor", header: "Código do Fornecedor", filterFn: "advancedText", size: 120, minSize: 100, maxSize: 150, }, { accessorKey: "nome_fornecedor", header: "Nome do Fornecedor", filterFn: "advancedText", size: 220, minSize: 150, maxSize: 400, }, { accessorKey: "codigo_centrocusto", header: "Centro de Custo", filterFn: "advancedText", size: 140, minSize: 100, maxSize: 200, }, { accessorKey: "codigo_conta", header: "Código da Conta", filterFn: "advancedText", size: 130, minSize: 100, maxSize: 180, }, { accessorKey: "conta", header: "Nome da Conta", filterFn: "advancedText", size: 160, minSize: 120, maxSize: 300, }, { accessorKey: "valor", header: "Valor Realizado", filterFn: "advancedText", size: 130, minSize: 100, maxSize: 200, cell: ({ getValue }: { getValue: () => number }) => { const value = getValue(); const formatted = new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(value); const isNegative = value < 0; return ( {formatted} ); }, }, { accessorKey: "valor_previsto", header: "Valor Previsto", filterFn: "advancedText", size: 120, minSize: 100, maxSize: 180, cell: ({ getValue }: { getValue: () => number }) => { const value = getValue(); if (!value || value === 0) return "-"; const formatted = new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(value); const isNegative = value < 0; return ( {formatted} ); }, }, { accessorKey: "valor_confirmado", header: "Valor Confirmado", filterFn: "advancedText", size: 130, minSize: 100, maxSize: 200, cell: ({ getValue }: { getValue: () => number }) => { const value = getValue(); if (!value || value === 0) return "-"; const formatted = new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(value); const isNegative = value < 0; return ( {formatted} ); }, }, { accessorKey: "valor_pago", header: "Valor Pago", filterFn: "advancedText", size: 140, minSize: 100, maxSize: 200, cell: ({ getValue }: { getValue: () => number }) => { const value = getValue(); if (!value || value === 0) return "-"; const formatted = new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(value); const isNegative = value < 0; return ( {formatted} ); }, }, { accessorKey: "historico", header: "Histórico", filterFn: "advancedText", size: 320, minSize: 200, maxSize: 500, }, { accessorKey: "historico2", header: "Histórico 2", filterFn: "advancedText", size: 500, minSize: 300, maxSize: 800, }, { accessorKey: "numero_lancamento", header: "Número do Lançamento", filterFn: "advancedText", size: 30, minSize: 20, maxSize: 50, cell: ({ getValue }: { getValue: () => number }) => { const value = getValue(); return value || "-"; }, }, ], [] ); const filterFns = React.useMemo( () => ({ advancedText: (row: any, columnId: string, filters: ColumnFiltersState) => { if (!filters || filters.length === 0) return true; // Se veio um único filtro (objeto), transforma em array const conds = Array.isArray(filters) ? filters : [filters]; // A coluna deve atender a todas as condições aplicáveis a ela return conds.every((filter: any) => { const raw = row.getValue(columnId); const v = raw == null ? "" : String(raw); const op = filter.operator; const q = (filter.value ?? "").toString(); const a = v.toLowerCase(); const b = q.toLowerCase(); switch (op) { case "contains": return a.includes(b); case "equals": return a === b; case "startsWith": return a.startsWith(b); case "endsWith": return a.endsWith(b); case "empty": return a.length === 0; case "notEmpty": return a.length > 0; default: return true; } }); }, }), [] ); const table = useReactTable({ data, columns: columns as any, state: { globalFilter, columnFilters, columnSizing }, onGlobalFilterChange: setGlobalFilter, onColumnFiltersChange: setColumnFilters, onColumnSizingChange: setColumnSizing, filterFns, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), enableColumnResizing: true, columnResizeMode: 'onChange', }); const parentRef = React.useRef(null); const rowVirtualizer = useVirtualizer({ count: table.getRowModel().rows.length, getScrollElement: () => parentRef.current, estimateSize: () => 36, overscan: 20, }); const virtualRows = rowVirtualizer.getVirtualItems(); const applyFilters = () => { // Agrupar múltiplas condições por coluna const grouped: Record = {}; conditions.forEach((c) => { if ( c.column && (c.operator === "empty" || c.operator === "notEmpty" || (c.value ?? "") !== "") ) { if (!grouped[c.column]) grouped[c.column] = []; grouped[c.column].push({ operator: c.operator, value: c.value }); } }); // Converte em formato aceito pelo TanStack const filters = Object.keys(grouped).map((col) => ({ id: col, value: grouped[col], })); setColumnFilters(filters); setOpen(false); }; const clearFilters = () => { setConditions([{ column: "", operator: "contains", value: "" }]); setColumnFilters([]); setGlobalFilter(""); // Não limpar os filtros externos - eles vêm do teste.tsx }; const [totalValor, setTotalValor] = React.useState(0); React.useEffect(() => { // Usar dados filtrados da tabela em vez dos dados originais const filteredData = table.getRowModel().rows.map((row) => row.original); const newTotal = filteredData.reduce((sum, item) => { const valor = typeof item.valor === "string" ? parseFloat(item.valor) : item.valor; return sum + (isNaN(valor) ? 0 : valor); }, 0); console.log("🔄 Calculando total:", { totalRows: table.getRowModel().rows.length, originalDataLength: data.length, newTotal, columnFilters: columnFilters.length, globalFilter, }); setTotalValor(newTotal); }, [table, data, columnFilters, globalFilter]); // Calcular totais das colunas de valores para o footer - EXATAMENTE o mesmo padrão do Valor Total const columnTotals = React.useMemo(() => { // Garantir que sempre temos dados para calcular if (!data || data.length === 0) { console.log("⚠️ Sem dados para calcular totais das colunas"); return { valorRealizado: 0, valorPrevisto: 0, valorConfirmado: 0, valorPago: 0, }; } // Usar EXATAMENTE a mesma lógica do totalValor const filteredData = table.getRowModel().rows.map((row) => row.original); console.log("🔄 Calculando totais das colunas - Dados filtrados:", { totalRows: filteredData.length, originalDataLength: data.length, columnFilters: columnFilters.length, globalFilter, firstItem: filteredData[0] }); const valorRealizado = filteredData.reduce((sum, item) => { const valor = typeof item.valor === "string" ? parseFloat(item.valor) : item.valor; return sum + (isNaN(valor) ? 0 : valor); }, 0); const valorPrevisto = filteredData.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 = filteredData.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 = filteredData.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); console.log("🔄 Calculando totais das colunas:", { totalRows: table.getRowModel().rows.length, valorRealizado, valorPrevisto, valorConfirmado, valorPago, columnFilters: columnFilters.length, globalFilter, }); return { valorRealizado, valorPrevisto, valorConfirmado, valorPago, }; }, [table, columnFilters, globalFilter, data]); const exportToExcel = () => { if (data.length === 0) return; // Usar dados filtrados da tabela em vez dos dados originais const filteredData = table.getRowModel().rows.map((row) => row.original); if (filteredData.length === 0) { alert("Nenhum dado filtrado para exportar"); return; } // Preparar dados para exportação const exportData = filteredData.map((item) => ({ "Data Competência": new Date(item.data_competencia).toLocaleDateString( "pt-BR" ), "Data Vencimento": new Date(item.data_vencimento).toLocaleDateString( "pt-BR" ), "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, // Assumindo que é o mesmo valor "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, })); // Criar workbook const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet(exportData); // Adicionar resumo na segunda aba const resumoData = [ { Métrica: "Total de Registros", Valor: filteredData.length }, { Métrica: "Valor Total", Valor: totalValor }, { Métrica: "Filtros Aplicados", Valor: columnFilters.length > 0 || globalFilter ? "Sim" : "Não", }, ]; const wsResumo = XLSX.utils.json_to_sheet(resumoData); // Adicionar abas ao workbook XLSX.utils.book_append_sheet(wb, ws, "Dados Analíticos"); XLSX.utils.book_append_sheet(wb, wsResumo, "Resumo"); // Gerar nome do arquivo com data e hora const now = new Date(); const timestamp = now.toISOString().slice(0, 19).replace(/:/g, "-"); const hasFilters = columnFilters.length > 0 || globalFilter; const fileName = `analitico${ hasFilters ? "_filtrado" : "" }_${timestamp}.xlsx`; // Fazer download XLSX.writeFile(wb, fileName); }; return (
{/* Header Section */}

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

Relatório detalhado de transações

{/* Controls */}
) => setGlobalFilter(e.target.value) } className="w-64 bg-white border-gray-300 focus:border-blue-500 focus:ring-blue-500" /> {(columnFilters.length > 0 || globalFilter) && ( )} {data.length > 0 && ( )}
{/* Filtros Externos Ativos */} {(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} )}
)}
{/* Table Container */}
{/* Table Header */}
{table.getHeaderGroups().map(headerGroup => ( headerGroup.headers.map(header => (
{header.isPlaceholder ? null : (
{String(header.column.columnDef.header)} {header.column.getCanResize() && (
)}
)}
)) ))}
{/* Table Body */}
{loading ? (

Carregando dados...

) : virtualRows.length === 0 ? (

Nenhum dado encontrado

) : (
{virtualRows.map((virtualRow) => { const row = table.getRowModel().rows[virtualRow.index]; return (
{row.getVisibleCells().map(cell => (
{String(cell.renderValue() || '')}
))}
); })}
)}
{/* Footer com Totalizador das Colunas */} {data.length > 0 && (
{table.getHeaderGroups().map(headerGroup => ( headerGroup.headers.map((header, index) => (
{index === 0 && ( TOTAL: {table.getRowModel().rows.length} registros )} {index === 8 && ( {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorRealizado)} )} {index === 9 && ( {columnTotals.valorPrevisto !== 0 ? ( {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorPrevisto)} ) : ( - )} )} {index === 10 && ( {columnTotals.valorConfirmado !== 0 ? ( {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorConfirmado)} ) : ( - )} )} {index === 11 && ( {columnTotals.valorPago !== 0 ? ( {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorPago)} ) : ( - )} )}
)) ))}
)} {/* Summary Footer - Integrado */} { // data.length > 0 && ( //
//
//
//
//

// Total de Registros:{" "} // // {table.getRowModel().rows.length} // //

//

// Transações encontradas //

//
//
//
//

// // Valor Total:{" "} // {new Intl.NumberFormat("pt-BR", { // style: "currency", // currency: "BRL", // }).format(totalValor)} // //

//

// Soma de todos os valores //

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