"use client"; import * as React from "react"; import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, ColumnFiltersState, } 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; }; } 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 [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); 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); 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", cell: ({ getValue }: { getValue: () => string }) => { const value = getValue(); return new Date(value).toLocaleDateString("pt-BR"); }, }, { accessorKey: "data_caixa", header: "Data de Caixa", filterFn: "advancedText", cell: ({ getValue }: { getValue: () => string }) => { const value = getValue(); return new Date(value).toLocaleDateString("pt-BR"); }, }, { accessorKey: "entidade", header: "Entidade", filterFn: "advancedText", cell: ({ getValue }: { getValue: () => string }) => { const value = getValue(); return value || "-"; }, }, { accessorKey: "codigo_fornecedor", header: "Código do Fornecedor", filterFn: "advancedText", }, { accessorKey: "nome_fornecedor", header: "Nome do Fornecedor", filterFn: "advancedText", }, { accessorKey: "codigo_centrocusto", header: "Centro de Custo", filterFn: "advancedText", }, { accessorKey: "codigo_conta", header: "Código da Conta", filterFn: "advancedText", }, { accessorKey: "conta", header: "Nome da Conta", filterFn: "advancedText", }, { accessorKey: "valor", header: "Valor Realizado", filterFn: "advancedText", 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", 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", 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", 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", }, { accessorKey: "historico2", header: "Histórico 2", filterFn: "advancedText", }, { accessorKey: "numero_lancamento", header: "Número do Lançamento", filterFn: "advancedText", 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) => { 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 }, onGlobalFilterChange: setGlobalFilter, onColumnFiltersChange: setColumnFilters, filterFns, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), }); 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(() => { // Usar EXATAMENTE a mesma lógica do totalValor const filteredData = table.getRowModel().rows.map((row) => row.original); 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]); 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

Relatório detalhado de transações

{/* 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} )}
)} {/* 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 && ( )}
{/* Table Container */}
{/* Table Header */}
Data de Vencimento
Data de Caixa
Entidade
Código do Fornecedor
Nome do Fornecedor
Centro de Custo
Código da Conta
Nome da Conta
Valor Realizado
Valor Previsto
Valor Confirmado
Valor Pago
Histórico
Histórico 2
Número do Lançamento
{/* Table Body */}
{loading ? (

Carregando dados...

) : virtualRows.length === 0 ? (

Nenhum dado encontrado

) : (
{virtualRows.map((virtualRow) => { const row = table.getRowModel().rows[virtualRow.index]; return (
{new Date( row.original.data_vencimento ).toLocaleDateString("pt-BR")}
{new Date(row.original.data_caixa).toLocaleDateString( "pt-BR" )}
{row.original.entidade || "-"}
{row.original.codigo_fornecedor}
{row.original.nome_fornecedor}
{row.original.codigo_centrocusto}
{row.original.codigo_conta}
{row.original.conta}
{new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(row.original.valor)}
{row.original.valor_previsto && row.original.valor_previsto !== 0 ? ( {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(row.original.valor_previsto)} ) : ( - )}
{row.original.valor_confirmado && row.original.valor_confirmado !== 0 ? ( {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(row.original.valor_confirmado)} ) : ( - )}
{row.original.valor_pago && row.original.valor_pago !== 0 ? ( {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(row.original.valor_pago)} ) : ( - )}
{row.original.historico}
{row.original.historico2}
{row.original.numero_lancamento || "-"}
); })}
)}
{/* Footer com Totalizador das Colunas */} {data.length > 0 && (
TOTAL: {table.getRowModel().rows.length} registros
{new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorRealizado)}
{columnTotals.valorPrevisto !== 0 ? ( {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorPrevisto)} ) : ( - )}
{columnTotals.valorConfirmado !== 0 ? ( {new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }).format(columnTotals.valorConfirmado)} ) : ( - )}
{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 && (
)}
))}
); }