From a6d713c59765dd92e8cbbf561294dd340d1f0ee0 Mon Sep 17 00:00:00 2001 From: jandsonrj Date: Tue, 19 May 2026 14:03:13 -0300 Subject: [PATCH 1/2] Refactor product data handling to standardize filters and improve pagination in SpeczData component --- frontend/components/SpeczData.js | 49 ++++++++++++++++++++++++++------ frontend/components/TsmData.js | 12 ++++++-- frontend/pages/specz_catalogs.js | 7 +++-- frontend/services/product.js | 5 ---- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/frontend/components/SpeczData.js b/frontend/components/SpeczData.js index 24191cf..7e99305 100644 --- a/frontend/components/SpeczData.js +++ b/frontend/components/SpeczData.js @@ -5,30 +5,53 @@ import PropTypes from 'prop-types' import * as React from 'react' import { useEffect } from 'react' import { useQuery } from 'react-query' -import { getAllProductsSpecz } from '../services/product' +import { getProductsSpecz } from '../services/product' const DataTableWrapper = ({ + filters = {}, + query = '', onSelectionChange = () => {}, clearSelection = false }) => { + const [page, setPage] = React.useState(0) + const [pageSize, setPageSize] = React.useState(25) const [selectedRows, setSelectedRows] = React.useState([]) const { data, isLoading } = useQuery( - ['productData'], - () => getAllProductsSpecz(), + ['productData', { filters, query, page, pageSize }], + () => + getProductsSpecz({ + filters, + page, + page_size: pageSize, + sort: [{ field: 'created_at', sort: 'desc' }], + search: query + }), { + keepPreviousData: true, staleTime: Infinity, refetchInterval: false, retry: false } ) + // Reset paginação quando a busca/filtros mudam + useEffect(() => { + setPage(0) + }, [query, filters]) + const handleSelectionChange = selection => { - const selectedProducts = selection.map( - id => data?.results?.find(product => product.id === id) || {} + const currentPageProducts = data?.results || [] + const previousSelection = selectedRows.filter( + row => !currentPageProducts.some(p => p.id === row.id) ) - setSelectedRows(selectedProducts) - onSelectionChange(selectedProducts) + const newSelectionFromPage = selection + .map(id => currentPageProducts.find(product => product.id === id)) + .filter(Boolean) + + const merged = [...previousSelection, ...newSelectionFromPage] + setSelectedRows(merged) + onSelectionChange(merged) } useEffect(() => { @@ -67,7 +90,7 @@ const DataTableWrapper = ({ return ( row.id || row.unique_key} rows={data?.results || []} columns={columns} loading={isLoading} + paginationMode="server" + page={page} + pageSize={pageSize} + rowCount={data?.count || 0} + onPageChange={newPage => setPage(newPage)} + onPageSizeChange={newPageSize => setPageSize(newPageSize)} + rowsPerPageOptions={[10, 25, 50]} onSelectionModelChange={newSelection => handleSelectionChange(newSelection) } @@ -94,6 +125,8 @@ const DataTableWrapper = ({ } DataTableWrapper.propTypes = { + filters: PropTypes.object, + query: PropTypes.string, onSelectionChange: PropTypes.func, clearSelection: PropTypes.bool } diff --git a/frontend/components/TsmData.js b/frontend/components/TsmData.js index aff4ddc..550e2a8 100644 --- a/frontend/components/TsmData.js +++ b/frontend/components/TsmData.js @@ -1,6 +1,6 @@ import Box from '@mui/material/Box' import Radio from '@mui/material/Radio' -import { DataGrid } from '@mui/x-data-grid' +import { DataGrid, GridToolbarFilterButton } from '@mui/x-data-grid' import moment from 'moment' import PropTypes from 'prop-types' import * as React from 'react' @@ -14,13 +14,17 @@ const DataTableWrapper = ({ selectedProductId }) => { const [page, setPage] = React.useState(0) - const [pageSize, setPageSize] = React.useState(10) + const [pageSize, setPageSize] = React.useState(25) const [selectedRowId, setSelectedRowId] = React.useState(null) React.useEffect(() => { setSelectedRowId(selectedProductId) }, [selectedProductId]) + React.useEffect(() => { + setPage(0) + }, [query, filters]) + const { data, isLoading } = useQuery( ['productData', { filters, query, page, pageSize }], () => @@ -32,6 +36,7 @@ const DataTableWrapper = ({ search: query }), { + keepPreviousData: true, staleTime: Infinity, refetchInterval: false, retry: false @@ -96,12 +101,13 @@ const DataTableWrapper = ({ rowCount={data?.count || 0} onPageChange={newPage => setPage(newPage)} onPageSizeChange={newPageSize => setPageSize(newPageSize)} - rowsPerPageOptions={[10]} + rowsPerPageOptions={[10, 25, 50]} loading={isLoading} localeText={{ noRowsLabel: isLoading ? 'Loading...' : 'No products found' }} onRowClick={params => handleRowSelection(params.row.id)} + components={{ Toolbar: GridToolbarFilterButton }} /> diff --git a/frontend/pages/specz_catalogs.js b/frontend/pages/specz_catalogs.js index 7c7a669..a7f913a 100644 --- a/frontend/pages/specz_catalogs.js +++ b/frontend/pages/specz_catalogs.js @@ -22,6 +22,7 @@ import Typography from '@mui/material/Typography' import { useTheme } from '@mui/system' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' +import SearchField from '../components/SearchField' import SpeczData from '../components/SpeczData' import { getPipelineByName } from '../services/pipeline' import { submitProcess } from '../services/process' @@ -48,6 +49,7 @@ function SpeczCatalogs() { const [fieldErrors] = useState({}) const [selectedProducts, setSelectedProducts] = useState([]) const [outputFormat, setOutputFormat] = useState('parquet') + const [search, setSearch] = useState('') useEffect(() => { const fetchPipelineData = async () => { @@ -264,13 +266,12 @@ function SpeczCatalogs() { 3. Select the Redshift Catalogs to include in your sample: - {/* setSearch(query)} /> */} + setSearch(query)} /> diff --git a/frontend/services/product.js b/frontend/services/product.js index 366d343..eaf691f 100644 --- a/frontend/services/product.js +++ b/frontend/services/product.js @@ -258,11 +258,6 @@ export const getProductConfigFiles = async productId => { } } -export const getAllProductsSpecz = async () => { - const res = await api.get('/api/products-specz/') - return res.data -} - export const getProductsSpecz = ({ filters = {}, search = '', From ba65058231697dd647dfb3fe1420b4761ccfef08 Mon Sep 17 00:00:00 2001 From: jandsonrj Date: Wed, 20 May 2026 19:54:11 -0300 Subject: [PATCH 2/2] Fix server-side pagination resetting to the first page The page-reset effect depended on the "filters" object directly. Since "filters" is recreated on every render (default empty object, or a new object from the parent), the effect ran on every render and called setPage(0), forcing the grid back to page 1 whenever the user navigated. The request for the next page was issued and the backend responded correctly, but the UI snapped back to the first page. Use a serialized key (JSON.stringify) as the effect dependency so it only runs when the search query or filter values actually change. --- frontend/components/SpeczData.js | 8 ++++++-- frontend/components/TsmData.js | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/frontend/components/SpeczData.js b/frontend/components/SpeczData.js index 7e99305..94b5a89 100644 --- a/frontend/components/SpeczData.js +++ b/frontend/components/SpeczData.js @@ -35,10 +35,14 @@ const DataTableWrapper = ({ } ) - // Reset paginação quando a busca/filtros mudam + // Reset paginação quando a busca/filtros mudam. + // Serializa os filtros: como `filters` é um objeto recriado a cada render, + // usar ele direto como dependência dispararia o efeito em todo render, + // travando a paginação sempre na primeira página. + const filtersKey = JSON.stringify(filters) useEffect(() => { setPage(0) - }, [query, filters]) + }, [query, filtersKey]) const handleSelectionChange = selection => { const currentPageProducts = data?.results || [] diff --git a/frontend/components/TsmData.js b/frontend/components/TsmData.js index 550e2a8..4af5205 100644 --- a/frontend/components/TsmData.js +++ b/frontend/components/TsmData.js @@ -21,9 +21,14 @@ const DataTableWrapper = ({ setSelectedRowId(selectedProductId) }, [selectedProductId]) + // Reset paginação quando a busca/filtros mudam. + // Serializa os filtros: como `filters` é um objeto recriado a cada render, + // usar ele direto como dependência dispararia o efeito em todo render, + // travando a paginação sempre na primeira página. + const filtersKey = JSON.stringify(filters) React.useEffect(() => { setPage(0) - }, [query, filters]) + }, [query, filtersKey]) const { data, isLoading } = useQuery( ['productData', { filters, query, page, pageSize }],