Skip to content

Commit f64646b

Browse files
authored
Convert People List to DataGrid (#1641)
1 parent 4724459 commit f64646b

File tree

10 files changed

+202
-146
lines changed

10 files changed

+202
-146
lines changed

src/api/schema/typePolicies/typePolicies.base.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export const typePolicies: TypePolicies = {
9090
projects: {},
9191
engagements: {},
9292
progressReports: {},
93+
users: {},
9394
},
9495
},
9596
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { GridColDef } from '@mui/x-data-grid-pro';
2+
import { Merge } from 'type-fest';
3+
import { UserLookupItem } from '../../form/Lookup';
4+
import { Link } from '../../Routing';
5+
import {
6+
columnWithDefaults,
7+
RowLike,
8+
WithValueGetterReturning,
9+
} from '../ColumnTypes/definition.types';
10+
import { textColumn } from '../ColumnTypes/textColumn';
11+
12+
export const UserNameColumn = <
13+
const Input extends Merge<
14+
GridColDef<Row>,
15+
WithValueGetterReturning<UserLookupItem, Row>
16+
>,
17+
Row extends RowLike
18+
>({
19+
valueGetter,
20+
...overrides
21+
}: Input) =>
22+
columnWithDefaults<Row>()(overrides, {
23+
...textColumn<Row>(),
24+
headerName: 'Name',
25+
width: 350,
26+
valueGetter: (...args) => valueGetter(...args).fullName,
27+
renderCell: ({ value, row, colDef, api }) => {
28+
const user = valueGetter(null as never, row, colDef, { current: api });
29+
return <Link to={`/users/${user.id}`}>{value}</Link>;
30+
},
31+
hideable: false,
32+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {
2+
DataGridProProps as DataGridProps,
3+
GridColDef,
4+
GridToolbarColumnsButton,
5+
GridToolbarFilterButton,
6+
} from '@mui/x-data-grid-pro';
7+
import { RoleLabels, RoleList } from '~/api/schema.graphql';
8+
import {
9+
booleanColumn,
10+
getInitialVisibility,
11+
multiEnumColumn,
12+
QuickFilterButton,
13+
QuickFilterResetButton,
14+
QuickFilters,
15+
textColumn,
16+
Toolbar,
17+
useFilterToggle,
18+
} from '../Grid';
19+
import { UserNameColumn } from '../Grid/Columns/UserNameColumn';
20+
import { UserDataGridRowFragment as User } from './userDataGridRow.graphql';
21+
22+
export const UserColumns: Array<GridColDef<User>> = [
23+
UserNameColumn({
24+
field: 'fullName',
25+
headerName: 'Name',
26+
valueGetter: (_, user) => user,
27+
serverFilter: (value) => ({ name: value }),
28+
}),
29+
{
30+
headerName: 'Title',
31+
field: 'title',
32+
width: 350,
33+
...textColumn(),
34+
valueGetter: (_, row) => row.title.value,
35+
},
36+
{
37+
headerName: 'Roles',
38+
description: 'Roles',
39+
field: 'roles',
40+
minWidth: 200,
41+
flex: 1,
42+
...multiEnumColumn(RoleList, RoleLabels),
43+
valueGetter: (_, { roles }) => {
44+
return roles.value;
45+
},
46+
},
47+
{
48+
headerName: 'Pinned',
49+
field: 'pinned',
50+
...booleanColumn(),
51+
valueGetter: (_, row) => row.pinned,
52+
},
53+
];
54+
55+
export const UserInitialState = {
56+
pinnedColumns: {
57+
left: UserColumns.slice(0, 1).map((column) => column.field),
58+
},
59+
columns: {
60+
columnVisibilityModel: {
61+
...getInitialVisibility(UserColumns),
62+
pinned: false,
63+
},
64+
},
65+
} satisfies DataGridProps['initialState'];
66+
67+
export const UserToolbar = () => (
68+
<Toolbar sx={{ justifyContent: 'flex-start', gap: 2 }}>
69+
<GridToolbarColumnsButton />
70+
<GridToolbarFilterButton />
71+
<QuickFilters sx={{ flex: 1 }}>
72+
<QuickFilterResetButton />
73+
<QuickFilterButton {...useFilterToggle('pinned')}>
74+
Pinned
75+
</QuickFilterButton>
76+
</QuickFilters>
77+
</Toolbar>
78+
);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
fragment userDataGridRow on User {
2+
id
3+
...UserLookupItem
4+
roles {
5+
value
6+
}
7+
title {
8+
value
9+
}
10+
email {
11+
value
12+
}
13+
...TogglePin
14+
}

src/scenes/Users/List/UserFilterOptions.tsx

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/scenes/Users/List/UserGrid.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {
2+
DataGridPro as DataGrid,
3+
DataGridProProps as DataGridProps,
4+
} from '@mui/x-data-grid-pro';
5+
import { merge } from 'lodash';
6+
import { useMemo } from 'react';
7+
import {
8+
DefaultDataGridStyles,
9+
flexLayout,
10+
noFooter,
11+
noHeaderFilterButtons,
12+
useDataGridSource,
13+
} from '~/components/Grid';
14+
import {
15+
UserColumns,
16+
UserInitialState,
17+
UserToolbar,
18+
} from '~/components/UserDataGrid/UserColumns';
19+
import { UserDataGridRowFragment as People } from '~/components/UserDataGrid/userDataGridRow.graphql';
20+
import { UsersDocument } from './users.graphql';
21+
22+
export const UserGrid = () => {
23+
const [dataGridProps] = useDataGridSource({
24+
query: UsersDocument,
25+
variables: {},
26+
listAt: 'users',
27+
initialInput: {
28+
sort: 'fullName',
29+
},
30+
});
31+
32+
const slots = useMemo(
33+
() =>
34+
merge({}, DefaultDataGridStyles.slots, dataGridProps.slots, {
35+
toolbar: UserToolbar,
36+
} satisfies DataGridProps['slots']),
37+
[dataGridProps.slots]
38+
);
39+
40+
const slotProps = useMemo(
41+
() => merge({}, DefaultDataGridStyles.slotProps, dataGridProps.slotProps),
42+
[dataGridProps.slotProps]
43+
);
44+
45+
return (
46+
<DataGrid<People>
47+
{...DefaultDataGridStyles}
48+
{...dataGridProps}
49+
slots={slots}
50+
slotProps={slotProps}
51+
columns={UserColumns}
52+
initialState={UserInitialState}
53+
headerFilters
54+
hideFooter
55+
sx={[flexLayout, noHeaderFilterButtons, noFooter]}
56+
/>
57+
);
58+
};

src/scenes/Users/List/UserList.tsx

Lines changed: 18 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,26 @@
1-
import { TabList as ActualTabList, TabContext, TabPanel } from '@mui/lab';
2-
import {
3-
type Tabs as __Tabs,
4-
Divider,
5-
Grid,
6-
Skeleton,
7-
Tab,
8-
Typography,
9-
} from '@mui/material';
10-
import { useRef } from 'react';
1+
import { Paper, Stack, Typography } from '@mui/material';
112
import { Helmet } from 'react-helmet-async';
12-
import { makeStyles } from 'tss-react/mui';
13-
import { User } from '~/api/schema.graphql';
14-
import { useNumberFormatter } from '../../../components/Formatters';
15-
import { ContentContainer } from '../../../components/Layout';
16-
import { List, useListQuery } from '../../../components/List';
17-
import { SortButtonDialog, useSort } from '../../../components/Sort';
18-
import { UserListItemCardLandscape as UserCard } from '../../../components/UserListItemCard';
19-
import { useUserFilters } from './UserFilterOptions';
20-
import { UsersDocument } from './users.graphql';
21-
import { UserSortOptions } from './UserSortOptions';
22-
23-
const TabList = ActualTabList as typeof __Tabs;
24-
25-
const useStyles = makeStyles()(({ spacing, breakpoints }) => ({
26-
options: {
27-
margin: spacing(3, 0),
28-
},
29-
items: {
30-
maxWidth: breakpoints.values.sm,
31-
},
32-
tabPanel: {
33-
overflowY: 'auto',
34-
// allow card shadow to bleed over instead of cutting it off
35-
padding: spacing(0, 0, 0, 2),
36-
margin: spacing(0, 0, 0, -2),
37-
},
38-
total: {
39-
marginTop: spacing(2),
40-
},
41-
}));
3+
import { UserGrid } from './UserGrid';
424

435
export const UserList = () => {
44-
const sort = useSort<User>();
45-
const [filters, setFilters] = useUserFilters();
46-
const list = useListQuery(UsersDocument, {
47-
listAt: (data) => data.users,
48-
variables: {
49-
input: {
50-
...sort.value,
51-
filter: filters.tab === 'pinned' ? { pinned: true } : {},
52-
},
53-
},
54-
});
55-
56-
const { classes } = useStyles();
57-
const formatNumber = useNumberFormatter();
58-
const scrollRef = useRef<HTMLElement>(null);
59-
606
return (
61-
<ContentContainer>
7+
<Stack sx={{ flex: 1, padding: 4, pt: 2 }}>
628
<Helmet title="People" />
63-
<Typography variant="h2" paragraph>
64-
People
65-
</Typography>
66-
<Grid container spacing={1} className={classes.options}>
67-
<Grid item>
68-
<SortButtonDialog {...sort}>
69-
<UserSortOptions />
70-
</SortButtonDialog>
71-
</Grid>
72-
</Grid>
73-
74-
<TabContext value={filters.tab}>
75-
<TabList
76-
onChange={(_e, tab) => setFilters({ ...filters, tab })}
77-
aria-label="user navigation tabs"
78-
className={classes.items}
79-
>
80-
<Tab label="Pinned" value="pinned" />
81-
<Tab label="All" value="all" />
82-
</TabList>
83-
<Divider className={classes.items} />
84-
<TabPanel
85-
value={filters.tab}
86-
className={classes.tabPanel}
87-
ref={scrollRef}
9+
<Stack component="main" sx={{ flex: 1 }}>
10+
<Typography variant="h2" paragraph>
11+
People
12+
</Typography>
13+
<Paper
14+
sx={(theme) => ({
15+
containerType: 'size',
16+
flex: 1,
17+
minHeight: 375,
18+
maxWidth: `${theme.breakpoints.values.lg}px`,
19+
})}
8820
>
89-
<Typography variant="h3" className={classes.total}>
90-
{list.data ? (
91-
<>{formatNumber(list.data.total)} People</>
92-
) : (
93-
<Skeleton width="9ch" />
94-
)}
95-
</Typography>
96-
<List
97-
{...list}
98-
classes={{ container: classes.items }}
99-
renderItem={(item) => <UserCard user={item} />}
100-
renderSkeleton={<UserCard />}
101-
scrollRef={scrollRef}
102-
/>
103-
</TabPanel>
104-
</TabContext>
105-
</ContentContainer>
21+
<UserGrid />
22+
</Paper>
23+
</Stack>
24+
</Stack>
10625
);
10726
};

src/scenes/Users/List/UserSortOptions.stories.tsx

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/scenes/Users/List/UserSortOptions.tsx

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/scenes/Users/List/users.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ query Users($input: UserListInput) {
22
users(input: $input) {
33
items {
44
...UserListItem
5+
...userDataGridRow
56
}
67
...Pagination
78
}

0 commit comments

Comments
 (0)