'use client'; import * as React from "react"; import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, flexRender, } from "@tanstack/react-table"; import { useVirtualizer } from "@tanstack/react-virtual"; import { Card, CardContent } from "@/components/ui/card"; 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 { ChevronUp, ChevronDown, Download } 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; } 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: "" }]); const [isScrolled, setIsScrolled] = React.useState(false); const fetchData = React.useCallback(async () => { // Só faz a requisição se tiver dataInicio e dataFim if (!filtros.dataInicio || !filtros.dataFim) { setData([]); return; } setLoading(true); try { const params = new URLSearchParams({ dataInicio: filtros.dataInicio, dataFim: filtros.dataFim, ...(filtros.centroCusto && { centroCusto: filtros.centroCusto }), ...(filtros.codigoGrupo && { codigoGrupo: filtros.codigoGrupo }), ...(filtros.codigoSubgrupo && { codigoSubgrupo: filtros.codigoSubgrupo, }), ...(filtros.codigoConta && { codigoConta: filtros.codigoConta }), }); const response = await fetch(`/api/analitico?${params}`); if (response.ok) { const result = await response.json(); 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); } }, [filtros]); React.useEffect(() => { fetchData(); }, [fetchData]); const columns = React.useMemo( () => [ { accessorKey: "data_competencia", header: "Data Comp.", filterFn: "advancedText", cell: ({ getValue }: { getValue: () => string }) => { const value = getValue(); return new Date(value).toLocaleDateString('pt-BR'); } }, { accessorKey: "data_vencimento", header: "Data Venc.", filterFn: "advancedText", cell: ({ getValue }: { getValue: () => string }) => { const value = getValue(); return new Date(value).toLocaleDateString('pt-BR'); } }, { accessorKey: "data_caixa", header: "Data Caixa", filterFn: "advancedText", cell: ({ getValue }: { getValue: () => string }) => { const value = getValue(); return new Date(value).toLocaleDateString('pt-BR'); } }, { accessorKey: "codigo_fornecedor", header: "Cód. Fornec.", filterFn: "advancedText" }, { accessorKey: "nome_fornecedor", header: "Fornecedor", filterFn: "advancedText" }, { accessorKey: "codigo_centrocusto", header: "Cód. Centro", filterFn: "advancedText" }, { accessorKey: "codigo_conta", header: "Cód. Conta", filterFn: "advancedText" }, { accessorKey: "conta", header: "Conta", filterFn: "advancedText" }, { accessorKey: "valor", header: "Valor", 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: "historico", header: "Histórico", filterFn: "advancedText" }, { accessorKey: "historico2", header: "Histórico 2", filterFn: "advancedText" }, { accessorKey: "recnum", header: "Recnum", filterFn: "advancedText" }, ], [] ); const filterFns = React.useMemo( () => ({ advancedText: (row: any, columnId: string, filter: any) => { if (!filter) return true; 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, 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(); React.useEffect(() => { const handleScroll = () => { if (!parentRef.current) return; setIsScrolled(parentRef.current.scrollTop > 0); }; const el = parentRef.current; el?.addEventListener("scroll", handleScroll); return () => el?.removeEventListener("scroll", handleScroll); }, []); const applyFilters = () => { const validConditions = conditions.filter((c) => c.column && (c.operator === "empty" || c.operator === "notEmpty" || (c.value ?? "") !== "") ); const filters = validConditions.map((c) => ({ id: c.column, value: { operator: c.operator, value: c.value } })); setColumnFilters(filters); setOpen(false); }; const clearFilters = () => { setConditions([{ column: "", operator: "contains", value: "" }]); setColumnFilters([]); }; const totalValor = data.reduce((sum, item) => { const valor = typeof item.valor === 'string' ? parseFloat(item.valor) : item.valor; return sum + (isNaN(valor) ? 0 : valor); }, 0); const exportToExcel = () => { if (data.length === 0) return; // Preparar dados para exportação const exportData = data.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: data.length }, { Métrica: 'Valor Total', Valor: totalValor }, ]; 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 fileName = `analitico_${timestamp}.xlsx`; // Fazer download XLSX.writeFile(wb, fileName); }; return (

Análise Analítica

Relatório detalhado de transações

) => setGlobalFilter(e.target.value)} className="w-64 bg-white border-gray-300 focus:border-blue-500 focus:ring-blue-500" /> {data.length > 0 && ( )}
{table.getHeaderGroups().map((hg) => ( {hg.headers.map((header) => { const sorted = header.column.getIsSorted(); return ( ); })} ))}
{flexRender(header.column.columnDef.header, header.getContext())}
{sorted === "asc" ? ( ) : sorted === "desc" ? ( ) : (
)}
{loading ? ( ) : virtualRows.length === 0 ? ( ) : ( virtualRows.map((virtualRow) => { const row = table.getRowModel().rows[virtualRow.index]; return ( {row.getVisibleCells().map((cell, cellIndex) => ( ))} ); }) )}
Carregando dados analíticos...
Nenhum dado analítico encontrado para os filtros aplicados.
{flexRender(cell.column.columnDef.cell, cell.getContext())}
{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

)} Filtros Avançados
{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 && (
)}
))}
); }