Skip to content

Commit 2f7072e

Browse files
committed
paginated server-side data grid
1 parent f1c82ff commit 2f7072e

File tree

7 files changed

+119
-62
lines changed

7 files changed

+119
-62
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {
2+
GridColDef,
3+
GridPaginationModel,
4+
GridRowId,
5+
GridRowSelectionModel,
6+
GridSortModel,
7+
GridValidRowModel
8+
} from '@mui/x-data-grid';
9+
import CustomDataGrid from './CustomDataGrid';
10+
11+
interface IServerPaginatedDataGridProps<T extends GridValidRowModel> {
12+
rows: T[];
13+
columns: GridColDef<T>[];
14+
getRowId: (row: T) => GridRowId;
15+
dataTestId: string;
16+
noRowsMessage: string;
17+
rowCount: number;
18+
paginationModel: GridPaginationModel;
19+
setPaginationModel: (model: GridPaginationModel) => void;
20+
sortModel: GridSortModel;
21+
setSortModel: (model: GridSortModel) => void;
22+
onRowClick?: (row: T) => void;
23+
rowSelectionModel?: GridRowSelectionModel;
24+
onRowSelectionModelChange?: (model: GridRowSelectionModel) => void;
25+
checkboxSelection?: boolean;
26+
disableMultipleRowSelection?: boolean;
27+
}
28+
29+
export const ServerPaginatedDataGrid = <T extends GridValidRowModel>({
30+
rows,
31+
columns,
32+
getRowId,
33+
dataTestId,
34+
noRowsMessage,
35+
rowCount,
36+
paginationModel,
37+
setPaginationModel,
38+
sortModel,
39+
setSortModel,
40+
onRowClick,
41+
rowSelectionModel,
42+
onRowSelectionModelChange,
43+
checkboxSelection,
44+
disableMultipleRowSelection
45+
}: IServerPaginatedDataGridProps<T>) => {
46+
return (
47+
<CustomDataGrid
48+
data-testid={dataTestId}
49+
rows={rows}
50+
columns={columns}
51+
getRowId={getRowId}
52+
paginationMode="server"
53+
paginationModel={paginationModel}
54+
onPaginationModelChange={setPaginationModel}
55+
pageSizeOptions={[10, 25, 50]}
56+
sortingMode="server"
57+
sortingOrder={['asc', 'desc']}
58+
sortModel={sortModel}
59+
onSortModelChange={setSortModel}
60+
rowCount={rowCount}
61+
noRowsMessage={noRowsMessage}
62+
localeText={{ noRowsLabel: noRowsMessage }}
63+
disableRowSelectionOnClick
64+
disableColumnSelector
65+
checkboxSelection={checkboxSelection}
66+
disableMultipleRowSelection={disableMultipleRowSelection}
67+
rowSelectionModel={rowSelectionModel}
68+
onRowSelectionModelChange={onRowSelectionModelChange}
69+
onRowClick={(params) => {
70+
onRowClick?.(params.row as T);
71+
}}
72+
sx={{
73+
border: 'none',
74+
'& .MuiDataGrid-columnHeaderTitle': {
75+
fontWeight: 700,
76+
textTransform: 'uppercase'
77+
}
78+
}}
79+
/>
80+
);
81+
};
82+
83+
export default ServerPaginatedDataGrid;

app/src/features/admin/policies/ManagePoliciesPage.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Mock } from 'vitest';
99
import { ManagePoliciesPage } from './ManagePoliciesPage';
1010

1111
// Types for mock component props
12-
interface MockActivePoliciesListProps {
12+
interface MockPoliciesContainerProps {
1313
policies: IPolicy[];
1414
onSelectPolicy: (id: string | null) => void;
1515
selectedPolicyId: string | null;
@@ -28,8 +28,8 @@ interface MockTeamPoliciesContainerProps {
2828
}
2929

3030
// Mock child components - we test them separately, here we test page logic
31-
vi.mock('./components/ActivePoliciesList', () => ({
32-
ActivePoliciesList: ({ policies, onSelectPolicy, selectedPolicyId }: MockActivePoliciesListProps) => (
31+
vi.mock('./components/PoliciesContainer', () => ({
32+
PoliciesContainer: ({ policies, onSelectPolicy, selectedPolicyId }: MockPoliciesContainerProps) => (
3333
<div data-testid="active-policies-list">
3434
{policies.map((p) => (
3535
<div

app/src/features/admin/policies/ManagePoliciesPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import useDataLoader from 'hooks/useDataLoader';
88
import { toApiPagination, useServerPaginatedDataGrid } from 'hooks/useServerPaginatedDataGrid';
99
import { useCallback, useEffect, useMemo, useState } from 'react';
1010
import { ApiPaginationRequestOptions } from 'types/pagination';
11-
import { ActivePoliciesList } from './components/ActivePoliciesList';
11+
import { PoliciesContainer } from './components/PoliciesContainer';
1212
import { TeamPoliciesContainer } from './components/TeamPoliciesContainer';
1313
import { TeamsContainer } from './components/TeamsContainer';
1414

@@ -109,7 +109,7 @@ export const ManagePoliciesPage = () => {
109109
<>
110110
<PageHeader label="Manage Policies" />
111111
<Box py={4}>
112-
<ActivePoliciesList
112+
<PoliciesContainer
113113
policies={policies.data}
114114
rowCount={policies.rowCount}
115115
paginationModel={policies.paginationModel}

app/src/features/admin/policies/components/ActivePoliciesList.test.tsx renamed to app/src/features/admin/policies/components/PoliciesContainer.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { IPolicy } from 'interfaces/usePoliciesApi.interface';
44
import { MemoryRouter } from 'react-router';
55
import { cleanup, render, waitFor } from 'test-helpers/test-utils';
66
import { Mock } from 'vitest';
7-
import { ActivePoliciesList, IActivePoliciesListProps } from './ActivePoliciesList';
7+
import { PoliciesContainer, IPoliciesContainerProps } from './PoliciesContainer';
88

99
// Types for DataGrid mock
1010
interface MockDataGridProps {
@@ -55,7 +55,7 @@ const mockUseApi = {
5555
}
5656
};
5757

58-
const defaultProps: IActivePoliciesListProps = {
58+
const defaultProps: IPoliciesContainerProps = {
5959
policies: [],
6060
rowCount: 0,
6161
paginationModel: { page: 0, pageSize: 10 },
@@ -69,15 +69,15 @@ const defaultProps: IActivePoliciesListProps = {
6969
onSelectPolicy: vi.fn()
7070
};
7171

72-
const renderComponent = (props: Partial<IActivePoliciesListProps> = {}) => {
72+
const renderComponent = (props: Partial<IPoliciesContainerProps> = {}) => {
7373
return render(
7474
<MemoryRouter initialEntries={['/']}>
75-
<ActivePoliciesList {...defaultProps} {...props} />
75+
<PoliciesContainer {...defaultProps} {...props} />
7676
</MemoryRouter>
7777
);
7878
};
7979

80-
describe('ActivePoliciesList', () => {
80+
describe('PoliciesContainer', () => {
8181
beforeEach(() => {
8282
mockBiohubApi.mockImplementation(() => mockUseApi);
8383
});

app/src/features/admin/policies/components/ActivePoliciesList.tsx renamed to app/src/features/admin/policies/components/PoliciesContainer.tsx

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import TextField from '@mui/material/TextField';
1212
import Toolbar from '@mui/material/Toolbar';
1313
import Typography from '@mui/material/Typography';
1414
import { GridColDef, GridRowSelectionModel } from '@mui/x-data-grid';
15-
import CustomDataGrid from 'components/data-grid/CustomDataGrid';
15+
import ServerPaginatedDataGrid from 'components/data-grid/ServerPaginatedDataGrid';
1616
import EditDialog from 'components/dialog/EditDialog';
1717
import { CustomMenuIconButton } from 'components/toolbar/ActionToolbars';
1818
import { ISnackbarProps } from 'contexts/dialogContext';
@@ -31,9 +31,9 @@ import {
3131
} from './AddPolicyForm';
3232

3333
/**
34-
* Props for the ActivePoliciesList component.
34+
* Props for the PoliciesContainer component.
3535
*/
36-
export interface IActivePoliciesListProps extends IServerPaginationProps {
36+
export interface IPoliciesContainerProps extends IServerPaginationProps {
3737
/** Array of policies to display in the table */
3838
policies: IPolicy[];
3939
/** Callback to refresh the policies list after create/update/delete */
@@ -58,10 +58,10 @@ export interface IActivePoliciesListProps extends IServerPaginationProps {
5858
* - Edit existing policies via dialog
5959
* - Delete policies with confirmation
6060
*
61-
* @param {IActivePoliciesListProps} props - Component props
61+
* @param {IPoliciesContainerProps} props - Component props
6262
* @returns {React.ReactElement} The policies list component
6363
*/
64-
export const ActivePoliciesList: React.FC<React.PropsWithChildren<IActivePoliciesListProps>> = (props) => {
64+
export const PoliciesContainer: React.FC<React.PropsWithChildren<IPoliciesContainerProps>> = (props) => {
6565
const biohubApi = useApi();
6666
const {
6767
policies,
@@ -421,27 +421,21 @@ export const ActivePoliciesList: React.FC<React.PropsWithChildren<IActivePolicie
421421

422422
<Divider flexItem />
423423

424-
<CustomDataGrid
425-
data-testid="active-policies-table"
424+
<ServerPaginatedDataGrid<IPolicy>
425+
dataTestId="active-policies-table"
426426
rows={policies}
427427
columns={columns}
428428
getRowId={(row) => row.policy_id}
429-
paginationMode="server"
429+
noRowsMessage="No Policies"
430+
rowCount={rowCount}
430431
paginationModel={paginationModel}
431-
onPaginationModelChange={setPaginationModel}
432-
pageSizeOptions={[10, 25, 50]}
433-
sortingMode="server"
434-
sortingOrder={['asc', 'desc']}
432+
setPaginationModel={setPaginationModel}
435433
sortModel={sortModel}
436-
onSortModelChange={setSortModel}
437-
rowCount={rowCount}
434+
setSortModel={setSortModel}
438435
rowSelectionModel={rowSelectionModel}
439436
onRowSelectionModelChange={handleRowSelectionChange}
440437
checkboxSelection
441438
disableMultipleRowSelection
442-
disableColumnSelector
443-
disableColumnMenu
444-
localeText={{ noRowsLabel: 'No Policies' }}
445439
/>
446440
</Paper>
447441
</Container>

app/src/features/admin/policies/components/TeamPoliciesContainer.tsx

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Divider from '@mui/material/Divider';
66
import Toolbar from '@mui/material/Toolbar';
77
import Typography from '@mui/material/Typography';
88
import { GridColDef, GridPaginationModel, GridSortModel } from '@mui/x-data-grid';
9-
import CustomDataGrid from 'components/data-grid/CustomDataGrid';
9+
import ServerPaginatedDataGrid from 'components/data-grid/ServerPaginatedDataGrid';
1010
import { CustomMenuIconButton } from 'components/toolbar/ActionToolbars';
1111
import { ISnackbarProps } from 'contexts/dialogContext';
1212
import { APIError } from 'hooks/api/useAxios';
@@ -35,7 +35,7 @@ export interface ITeamPoliciesContainerProps {
3535
setSortModel: (model: GridSortModel) => void;
3636
/** Currently selected team from TeamsContainer (null if none selected) */
3737
selectedTeam: ITeam | null;
38-
/** Currently selected policy from ActivePoliciesList (null if none selected) */
38+
/** Currently selected policy from PoliciesContainer (null if none selected) */
3939
selectedPolicy: IPolicy | null;
4040
/** Callback to refresh the team-policies list after create/delete */
4141
refresh: () => void;
@@ -278,31 +278,17 @@ export const TeamPoliciesContainer: React.FC<ITeamPoliciesContainerProps> = (pro
278278

279279
<Divider flexItem />
280280

281-
<CustomDataGrid
282-
data-testid="team-policies-table"
281+
<ServerPaginatedDataGrid<ITeamPolicyDetails>
282+
dataTestId="team-policies-table"
283283
rows={teamPolicies}
284284
columns={columns}
285285
getRowId={(row) => row.team_policy_id}
286-
paginationMode="server"
286+
noRowsMessage="No Team-Policy Assignments"
287+
rowCount={rowCount}
287288
paginationModel={paginationModel}
288-
onPaginationModelChange={setPaginationModel}
289-
pageSizeOptions={[10, 25, 50]}
290-
sortingMode="server"
291-
sortingOrder={['asc', 'desc']}
289+
setPaginationModel={setPaginationModel}
292290
sortModel={sortModel}
293-
onSortModelChange={setSortModel}
294-
rowCount={rowCount}
295-
disableRowSelectionOnClick
296-
disableColumnSelector
297-
disableColumnMenu
298-
localeText={{ noRowsLabel: 'No Team-Policy Assignments' }}
299-
sx={{
300-
border: 'none',
301-
'& .MuiDataGrid-columnHeaderTitle': {
302-
fontWeight: 700,
303-
textTransform: 'uppercase'
304-
}
305-
}}
291+
setSortModel={setSortModel}
306292
/>
307293
</Box>
308294
);

app/src/features/admin/policies/components/TeamsContainer.tsx

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import TextField from '@mui/material/TextField';
99
import Toolbar from '@mui/material/Toolbar';
1010
import Typography from '@mui/material/Typography';
1111
import { GridColDef, GridRowSelectionModel } from '@mui/x-data-grid';
12-
import CustomDataGrid from 'components/data-grid/CustomDataGrid';
12+
import ServerPaginatedDataGrid from 'components/data-grid/ServerPaginatedDataGrid';
1313
import EditDialog from 'components/dialog/EditDialog';
1414
import { CustomMenuIconButton } from 'components/toolbar/ActionToolbars';
1515
import { ISnackbarProps } from 'contexts/dialogContext';
@@ -394,27 +394,21 @@ export const TeamsContainer: React.FC<ITeamsContainerProps> = (props) => {
394394

395395
<Divider flexItem />
396396

397-
<CustomDataGrid
398-
data-testid="teams-table"
397+
<ServerPaginatedDataGrid<ITeam>
398+
dataTestId="teams-table"
399399
rows={teams}
400400
columns={columns}
401401
getRowId={(row) => row.team_id}
402-
paginationMode="server"
402+
noRowsMessage="No Teams"
403+
rowCount={rowCount}
403404
paginationModel={paginationModel}
404-
onPaginationModelChange={setPaginationModel}
405-
pageSizeOptions={[10, 25, 50]}
406-
sortingMode="server"
407-
sortingOrder={['asc', 'desc']}
405+
setPaginationModel={setPaginationModel}
408406
sortModel={sortModel}
409-
onSortModelChange={setSortModel}
410-
rowCount={rowCount}
407+
setSortModel={setSortModel}
411408
rowSelectionModel={rowSelectionModel}
412409
onRowSelectionModelChange={handleRowSelectionChange}
413410
checkboxSelection
414411
disableMultipleRowSelection
415-
disableColumnSelector
416-
disableColumnMenu
417-
localeText={{ noRowsLabel: 'No Teams' }}
418412
/>
419413
</Box>
420414

0 commit comments

Comments
 (0)