From ef764c93c0d47ca9418007ec1774ab0e5831dfbb Mon Sep 17 00:00:00 2001 From: Celia Amador Date: Tue, 2 Jun 2026 11:18:09 +0200 Subject: [PATCH 1/2] EDM-3868: Reset page to 1 when filters change Made-with: Cursor --- .../InstallWizard/steps/SelectTargetStep.tsx | 9 +----- .../steps/SpecificationsStep.tsx | 5 +--- .../Device/DevicesPage/DevicesPage.tsx | 5 +++- .../DevicesPage/useDeviceBackendFilters.ts | 12 ++++++++ .../Device/DevicesPage/useDevices.ts | 29 +++++++++++++------ .../src/hooks/useTablePagination.ts | 10 +++++++ 6 files changed, 48 insertions(+), 22 deletions(-) diff --git a/libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx b/libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx index bd7f86d46..5ccc5d9d8 100644 --- a/libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx +++ b/libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx @@ -35,7 +35,6 @@ import FormSelect from '../../../form/FormSelect'; import { getArtifactLabel, getFullArtifactURI } from '../../utils'; import LearnMoreLink from '../../../common/LearnMoreLink'; import { useAppLinks } from '../../../../hooks/useAppLinks'; -import { FilterSearchParams } from '../../../../utils/status/devices'; export const isSelectTargetStepValid = (errors: FormikErrors) => { return !errors.device && !errors.fleet; @@ -51,13 +50,7 @@ const DeviceTarget = () => { isLoading: devicesLoading, isUpdating: devicesUpdating, pagination: devicePagination, - } = useDevicesPaginated({ - textFilters: { - [FilterSearchParams.NameOrAlias]: deviceNameFilter, - }, - onlyDecommissioned: false, - onlyFleetless: true, - }); + } = useDevicesPaginated(deviceNameFilter); const handleDeviceSelect = React.useCallback( async (device: Device) => { diff --git a/libs/ui-components/src/components/Catalog/InstallWizard/steps/SpecificationsStep.tsx b/libs/ui-components/src/components/Catalog/InstallWizard/steps/SpecificationsStep.tsx index baaee2832..b6094d6c2 100644 --- a/libs/ui-components/src/components/Catalog/InstallWizard/steps/SpecificationsStep.tsx +++ b/libs/ui-components/src/components/Catalog/InstallWizard/steps/SpecificationsStep.tsx @@ -240,10 +240,7 @@ const SpecificationsStep = ({ catalogItem, showNewDevice }: SpecificationsStepPr const newDeviceRadioRef = React.useRef(null); const { fleets, isLoading: fleetsLoading } = useFleets({}); - const { devices, isLoading: devicesLoading } = useDevicesPaginated({ - onlyDecommissioned: false, - onlyFleetless: true, - }); + const { devices, isLoading: devicesLoading } = useDevicesPaginated(); const unmanagedFleetsCount = fleets.filter((f) => !f.metadata?.owner).length; diff --git a/libs/ui-components/src/components/Device/DevicesPage/DevicesPage.tsx b/libs/ui-components/src/components/Device/DevicesPage/DevicesPage.tsx index b3a3af267..cdea805dc 100644 --- a/libs/ui-components/src/components/Device/DevicesPage/DevicesPage.tsx +++ b/libs/ui-components/src/components/Device/DevicesPage/DevicesPage.tsx @@ -5,7 +5,7 @@ import { DeviceList } from '@flightctl/types'; import ListPage from '../../ListPage/ListPage'; import ListPageBody from '../../ListPage/ListPageBody'; import { useTranslation } from '../../../hooks/useTranslation'; -import { useTablePagination } from '../../../hooks/useTablePagination'; +import { useResetPaginationOnFilterChange, useTablePagination } from '../../../hooks/useTablePagination'; import { useDevices } from './useDevices'; import { useDeviceBackendFilters } from './useDeviceBackendFilters'; @@ -20,6 +20,7 @@ const DevicesPage = ({ canListER }: { canListER: boolean }) => { const { t } = useTranslation(); const { + filterKey, textFilters, clearTextFilters, setTextFilter, @@ -37,6 +38,8 @@ const DevicesPage = ({ canListER }: { canListER: boolean }) => { const { currentPage, setCurrentPage, onPageFetched, nextContinue, itemCount } = useTablePagination(); + useResetPaginationOnFilterChange(`${filterKey}|${onlyDecommissioned}`, setCurrentPage); + const { devices: data, isLoading: loading, diff --git a/libs/ui-components/src/components/Device/DevicesPage/useDeviceBackendFilters.ts b/libs/ui-components/src/components/Device/DevicesPage/useDeviceBackendFilters.ts index dbf2f6a7c..e1255454e 100644 --- a/libs/ui-components/src/components/Device/DevicesPage/useDeviceBackendFilters.ts +++ b/libs/ui-components/src/components/Device/DevicesPage/useDeviceBackendFilters.ts @@ -16,6 +16,15 @@ const validAppStatuses = Object.values(ApplicationsSummaryStatusType) as string[ const validUpdatedStatuses = Object.values(DeviceUpdatedStatusType) as string[]; const validDeviceStatuses = Object.values(DeviceSummaryStatusType) as string[]; +const getSearchParamsQueryKey = (searchParams: URLSearchParams): string => { + return ( + [...searchParams.entries()] + .map(([key, value]) => `${key}=${value}`) + .sort() + .join('&') || '' + ); +}; + const getNewParams = (currentParams: URLSearchParams, newValues: { [key: string]: string[] }) => { let newParams = [...currentParams.entries()]; const keys = Object.keys(newValues); @@ -156,7 +165,10 @@ export const useDeviceBackendFilters = () => { Object.values(activeStatuses).some((s) => !!s.length) || DEVICE_TEXT_FILTER_KEYS.some((key) => !!textFilters[key]); + const filterKey = React.useMemo(() => getSearchParamsQueryKey(searchParams), [searchParams]); + return { + filterKey, textFilters, setTextFilter, clearTextFilters, diff --git a/libs/ui-components/src/components/Device/DevicesPage/useDevices.ts b/libs/ui-components/src/components/Device/DevicesPage/useDevices.ts index 75f958fdb..729480cdb 100644 --- a/libs/ui-components/src/components/Device/DevicesPage/useDevices.ts +++ b/libs/ui-components/src/components/Device/DevicesPage/useDevices.ts @@ -7,7 +7,11 @@ import { useFetchPeriodically } from '../../../hooks/useFetchPeriodically'; import { FlightCtlLabel } from '../../../types/extraTypes'; import { FilterStatusMap } from './types'; import { PAGE_SIZE } from '../../../constants'; -import { PaginationDetails, useTablePagination } from '../../../hooks/useTablePagination'; +import { + PaginationDetails, + useResetPaginationOnFilterChange, + useTablePagination, +} from '../../../hooks/useTablePagination'; type DevicesEndpointArgs = { /** Free-text filters synced with URL (name/alias, CVE ID, …). */ @@ -164,17 +168,24 @@ export type DevicesPaginatedResult = { /** * Hook for fetching devices with built-in pagination support. - * Use this for paginated tables/modals. + +* Use this for paginated tables/modals of enrolled, fleetless devices. */ -export const useDevicesPaginated = (args: { - textFilters?: Partial>; - ownerFleets?: string[]; - onlyDecommissioned: boolean; - onlyFleetless?: boolean; -}): DevicesPaginatedResult => { +export const useDevicesPaginated = (deviceNameFilter?: string): DevicesPaginatedResult => { const pagination = useTablePagination(); + + useResetPaginationOnFilterChange(deviceNameFilter || '', pagination.setCurrentPage); + + const textFilters = deviceNameFilter + ? { + [FilterSearchParams.NameOrAlias]: deviceNameFilter, + } + : undefined; + const [devicesEndpoint, devicesDebouncing] = useDevicesEndpoint({ - ...args, + textFilters, + onlyDecommissioned: false, + onlyFleetless: true, nextContinue: pagination.nextContinue, }); diff --git a/libs/ui-components/src/hooks/useTablePagination.ts b/libs/ui-components/src/hooks/useTablePagination.ts index 6086c455d..0f4adf96d 100644 --- a/libs/ui-components/src/hooks/useTablePagination.ts +++ b/libs/ui-components/src/hooks/useTablePagination.ts @@ -42,3 +42,13 @@ export const useTablePagination = (): PaginationDetails => return { onPageFetched, currentPage, setCurrentPage, nextContinue, itemCount }; }; + +export const useResetPaginationOnFilterChange = (queryKey: string, setCurrentPage: (page: number) => void) => { + const prevQueryKeyRef = React.useRef(queryKey); + React.useEffect(() => { + if (prevQueryKeyRef.current !== queryKey) { + prevQueryKeyRef.current = queryKey; + setCurrentPage(1); + } + }, [queryKey, setCurrentPage]); +}; From 4d82bf903fc5f034e3c87927ae07bc3107cc8bbb Mon Sep 17 00:00:00 2001 From: Celia Amador Date: Tue, 2 Jun 2026 11:48:13 +0200 Subject: [PATCH 2/2] Encode values. Fix wrong query for devicesPaginated Made-with: Cursor --- .../components/Device/DevicesPage/useDeviceBackendFilters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/ui-components/src/components/Device/DevicesPage/useDeviceBackendFilters.ts b/libs/ui-components/src/components/Device/DevicesPage/useDeviceBackendFilters.ts index e1255454e..d7d87dd21 100644 --- a/libs/ui-components/src/components/Device/DevicesPage/useDeviceBackendFilters.ts +++ b/libs/ui-components/src/components/Device/DevicesPage/useDeviceBackendFilters.ts @@ -19,7 +19,7 @@ const validDeviceStatuses = Object.values(DeviceSummaryStatusType) as string[]; const getSearchParamsQueryKey = (searchParams: URLSearchParams): string => { return ( [...searchParams.entries()] - .map(([key, value]) => `${key}=${value}`) + .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .sort() .join('&') || '' );