diff --git a/.changeset/permissions-table-filter-by-text.md b/.changeset/permissions-table-filter-by-text.md new file mode 100644 index 0000000000000..57423bdefa33f --- /dev/null +++ b/.changeset/permissions-table-filter-by-text.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed the permissions admin table showing an empty result when the search filter was narrowed while on a later page; the table now resets to the first page whenever the filter changes. diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.spec.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.spec.tsx index 1f8cdec005127..f35cfae314e35 100644 --- a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.spec.tsx +++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.spec.tsx @@ -1,7 +1,7 @@ import type { IPermission, IRole } from '@rocket.chat/core-typings'; import { mockAppRoot } from '@rocket.chat/mock-providers'; import { composeStories } from '@storybook/react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { axe } from 'jest-axe'; @@ -97,3 +97,31 @@ test('should NOT display modal if the permission is access-permissions and has m await userEvent.click(screen.getByRole('checkbox', { name: 'access-permissions - Administrator' })); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); + +test('should reset pagination to the first page when the filter changes', async () => { + const setFilter = jest.fn(); + const setCurrent = jest.fn(); + + render( + , + { + wrapper: mockAppRoot().build(), + }, + ); + + // The mount effect applies the initial empty filter and resets the page once; clear those + // calls so the assertions below only see what the filter change itself triggers. + setFilter.mockClear(); + setCurrent.mockClear(); + + await userEvent.type(screen.getByRole('textbox', { name: 'Search' }), 'admin'); + + await waitFor(() => expect(setFilter).toHaveBeenCalledWith('admin')); + expect(setCurrent).toHaveBeenCalledWith(0); +}); diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.tsx index 64171fb39e39a..5a2f42be0f324 100644 --- a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.tsx +++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTable.tsx @@ -1,14 +1,16 @@ import type { IPermission, IRole } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; import { Pagination, Palette } from '@rocket.chat/fuselage'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import { GenericTable, GenericTableHeader, GenericTableHeaderCell, GenericTableBody } from '@rocket.chat/ui-client'; import type { usePagination } from '@rocket.chat/ui-client'; import { useMethod } from '@rocket.chat/ui-contexts'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import PermissionRow from './PermissionRow'; -import PermissionsTableFilter from './PermissionsTableFilter'; import RoleHeader from './RoleHeader'; +import FilterByText from '../../../../components/FilterByText'; import GenericNoResults from '../../../../components/GenericNoResults'; type PermissionsTableProps = { @@ -27,6 +29,15 @@ const PermissionsTable = ({ roleList, permissions, setFilter, total, paginationD const { current, itemsPerPage, setCurrent, setItemsPerPage, ...paginationProps } = paginationData; + const [text, setText] = useState(''); + const debouncedText = useDebouncedValue(text, 500); + + useEffect(() => { + setFilter(debouncedText); + // Reset to the first page so narrowing the filter doesn't strand the user on a now-empty offset + setCurrent(0); + }, [debouncedText, setFilter, setCurrent]); + const tableCustomStyle = css` // Makes the first column of the table sticky tr > th { @@ -62,7 +73,7 @@ const PermissionsTable = ({ roleList, permissions, setFilter, total, paginationD return ( <> - + setText(event.target.value)} /> {permissions?.length === 0 && } {permissions?.length > 0 && ( <> diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTableFilter.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTableFilter.tsx deleted file mode 100644 index 0ece8a26f16d7..0000000000000 --- a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTableFilter.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Icon, TextInput } from '@rocket.chat/fuselage'; -import { useStableCallback, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import type { ChangeEvent } from 'react'; -import { useState, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; - -/** - * - * TODO: Replaced this by FilterByText, it has the same render - */ -const PermissionsTableFilter = ({ onChange }: { onChange: (debouncedFilter: string) => void }) => { - const { t } = useTranslation(); - const [filter, setFilter] = useState(''); - const debouncedFilter = useDebouncedValue(filter, 500); - - useEffect(() => { - onChange(debouncedFilter); - }, [debouncedFilter, onChange]); - - const handleFilter = useStableCallback(({ currentTarget: { value } }: ChangeEvent) => { - setFilter(value); - }); - - return ( - - } /> - - ); -}; - -export default PermissionsTableFilter; diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/__snapshots__/PermissionsTable.spec.tsx.snap b/apps/meteor/client/views/admin/permissions/PermissionsTable/__snapshots__/PermissionsTable.spec.tsx.snap index b9bed18222882..f7336a8dfbf34 100644 --- a/apps/meteor/client/views/admin/permissions/PermissionsTable/__snapshots__/PermissionsTable.spec.tsx.snap +++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/__snapshots__/PermissionsTable.spec.tsx.snap @@ -6,29 +6,36 @@ exports[`renders Default without crashing 1`] = ` - - + - - - + - - - - - + + + + + + + @@ -1193,29 +1200,36 @@ exports[`renders Empty without crashing 1`] = ` - - + - - - + - - - - - + + + + + + +