Skip to content

Commit 3023c40

Browse files
rdonigianCarsonF
andauthored
Convert Partner List to DataGrid (#1642)
Co-authored-by: Carson Full <[email protected]>
1 parent 8cb1e67 commit 3023c40

File tree

11 files changed

+255
-128
lines changed

11 files changed

+255
-128
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+
partners: {},
9394
users: {},
9495
},
9596
},
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { GridColDef } from '@mui/x-data-grid-pro';
2+
import { Merge } from 'type-fest';
3+
import { PartnerLookupItem } 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 PartnerNameColumn = <
13+
const Input extends Merge<
14+
GridColDef<Row>,
15+
WithValueGetterReturning<PartnerLookupItem, Row>
16+
>,
17+
Row extends RowLike
18+
>({
19+
valueGetter,
20+
...overrides
21+
}: Input) =>
22+
columnWithDefaults<Row>()(overrides, {
23+
...textColumn<Row>(),
24+
headerName: 'Partner',
25+
width: 300,
26+
valueGetter: (...args) =>
27+
valueGetter(...args).organization.value?.name.value,
28+
renderCell: ({ value, row, colDef, api }) => {
29+
const partner = valueGetter(null as never, row, colDef, { current: api });
30+
return <Link to={`/partners/${partner.id}`}>{value}</Link>;
31+
},
32+
hideable: false,
33+
});
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {
2+
DataGridProProps as DataGridProps,
3+
GridColDef,
4+
GridToolbarColumnsButton,
5+
GridToolbarFilterButton,
6+
} from '@mui/x-data-grid-pro';
7+
import {
8+
FinancialReportingTypeLabels,
9+
FinancialReportingTypeList,
10+
PartnerTypeLabels,
11+
PartnerTypeList,
12+
} from '~/api/schema.graphql';
13+
import {
14+
booleanColumn,
15+
getInitialVisibility,
16+
multiEnumColumn,
17+
QuickFilterButton,
18+
QuickFilterResetButton,
19+
QuickFilters,
20+
Toolbar,
21+
useFilterToggle,
22+
} from '../Grid';
23+
import { PartnerNameColumn } from '../Grid/Columns/PartnerNameColumn';
24+
import { PartnerDataGridRowFragment as Partner } from './partnerDataGridRow.graphql';
25+
26+
export const PartnerColumns: Array<GridColDef<Partner>> = [
27+
PartnerNameColumn({
28+
field: 'organization.name',
29+
headerName: 'Name',
30+
valueGetter: (_, partner) => partner,
31+
width: 300,
32+
}),
33+
{
34+
field: 'organization.acronym',
35+
headerName: 'Acronym',
36+
valueGetter: (_, { organization }) => organization.value?.acronym.value,
37+
width: 100,
38+
},
39+
{
40+
field: 'globalInnovationsClient',
41+
headerName: 'GPC',
42+
description: 'Growth Partners Client',
43+
...booleanColumn(),
44+
valueGetter: (_, { globalInnovationsClient }) =>
45+
globalInnovationsClient.value,
46+
},
47+
{
48+
field: 'types',
49+
headerName: 'Roles',
50+
...multiEnumColumn(PartnerTypeList, PartnerTypeLabels),
51+
valueFormatter: (_, { types }) =>
52+
types.value.map((type) => PartnerTypeLabels[type]).join(', '),
53+
valueGetter: (_, { types }) => {
54+
return types.value;
55+
},
56+
width: 300,
57+
},
58+
{
59+
field: 'financialReportingTypes',
60+
headerName: 'Financial Reporting Types',
61+
...multiEnumColumn(
62+
FinancialReportingTypeList,
63+
FinancialReportingTypeLabels
64+
),
65+
valueFormatter: (_, { financialReportingTypes }) =>
66+
financialReportingTypes.value
67+
.map((type) => FinancialReportingTypeLabels[type])
68+
.join(', '),
69+
valueGetter: (_, { financialReportingTypes }) => {
70+
return financialReportingTypes.value;
71+
},
72+
width: 300,
73+
},
74+
{
75+
field: 'pinned',
76+
...booleanColumn(),
77+
headerName: 'Pinned',
78+
},
79+
];
80+
81+
export const PartnerInitialState = {
82+
pinnedColumns: {
83+
left: PartnerColumns.slice(0, 1).map((column) => column.field),
84+
},
85+
columns: {
86+
columnVisibilityModel: {
87+
...getInitialVisibility(PartnerColumns),
88+
isMember: false,
89+
pinned: false,
90+
},
91+
},
92+
} satisfies DataGridProps['initialState'];
93+
94+
export const PartnerToolbar = () => (
95+
<Toolbar sx={{ justifyContent: 'flex-start', gap: 2 }}>
96+
<GridToolbarColumnsButton />
97+
<GridToolbarFilterButton />
98+
<QuickFilters sx={{ flex: 1 }}>
99+
<QuickFilterResetButton />
100+
<QuickFilterButton {...useFilterToggle('pinned')}>
101+
Pinned
102+
</QuickFilterButton>
103+
</QuickFilters>
104+
</Toolbar>
105+
);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
fragment partnerDataGridRow on Partner {
2+
id
3+
...PartnerLookupItem
4+
organization {
5+
value {
6+
acronym {
7+
value
8+
}
9+
}
10+
}
11+
globalInnovationsClient {
12+
value
13+
}
14+
types {
15+
value
16+
}
17+
financialReportingTypes {
18+
value
19+
}
20+
...TogglePin
21+
}

src/components/UserDataGrid/UserColumns.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ export const UserColumns: Array<GridColDef<User>> = [
3737
headerName: 'Roles',
3838
description: 'Roles',
3939
field: 'roles',
40-
minWidth: 200,
41-
flex: 1,
40+
width: 200,
4241
...multiEnumColumn(RoleList, RoleLabels),
4342
valueGetter: (_, { roles }) => {
4443
return roles.value;

src/scenes/Partners/List/PartnerFilterOptions.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.
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+
PartnerColumns,
16+
PartnerInitialState,
17+
PartnerToolbar,
18+
} from '~/components/PartnersDataGrid/PartnerColumns';
19+
import { PartnerDataGridRowFragment as Partner } from '~/components/PartnersDataGrid/partnerDataGridRow.graphql';
20+
import { PartnersDocument } from './PartnerList.graphql';
21+
22+
export const PartnerGrid = () => {
23+
const [dataGridProps] = useDataGridSource({
24+
query: PartnersDocument,
25+
variables: { input: {} },
26+
listAt: 'partners',
27+
initialInput: {
28+
sort: 'organization.name',
29+
},
30+
});
31+
32+
const slots = useMemo(
33+
() =>
34+
merge({}, DefaultDataGridStyles.slots, dataGridProps.slots, {
35+
toolbar: PartnerToolbar,
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<Partner>
47+
{...DefaultDataGridStyles}
48+
{...dataGridProps}
49+
slots={slots}
50+
slotProps={slotProps}
51+
columns={PartnerColumns}
52+
initialState={PartnerInitialState}
53+
headerFilters
54+
hideFooter
55+
sx={[flexLayout, noHeaderFilterButtons, noFooter]}
56+
/>
57+
);
58+
};

src/scenes/Partners/List/PartnerList.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ query Partners($input: PartnerListInput!) {
22
partners(input: $input) {
33
items {
44
...PartnerListItem
5+
...partnerDataGridRow
56
}
67
...Pagination
78
}
Lines changed: 22 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,29 @@
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 { useNumberFormatter } from '../../../components/Formatters';
14-
import { ContentContainer } from '../../../components/Layout';
15-
import { List, useListQuery } from '../../../components/List';
16-
import { PartnerListItemCard as PartnerCard } from '../../../components/PartnerListItemCard';
17-
import { SortButtonDialog, useSort } from '../../../components/Sort';
18-
import { usePartnerFilters } from './PartnerFilterOptions';
19-
import { PartnersDocument } from './PartnerList.graphql';
20-
import { PartnerSort, PartnerSortOptions } from './PartnerSortOptions';
21-
22-
const TabList = ActualTabList as typeof __Tabs;
23-
24-
const useStyles = makeStyles()(({ spacing, breakpoints }) => ({
25-
options: {
26-
margin: spacing(3, 0),
27-
},
28-
items: {
29-
maxWidth: breakpoints.values.sm,
30-
},
31-
tabPanel: {
32-
overflowY: 'auto',
33-
// allow card shadow to bleed over instead of cutting it off
34-
padding: spacing(0, 0, 0, 2),
35-
margin: spacing(0, 0, 0, -2),
36-
},
37-
total: {
38-
marginTop: spacing(2),
39-
},
40-
}));
3+
import { PartnerGrid } from './PartnerGrid';
414

425
export const PartnerList = () => {
43-
const sort = useSort<PartnerSort>('name');
44-
const [filters, setFilters] = usePartnerFilters();
45-
const list = useListQuery(PartnersDocument, {
46-
listAt: (data) => data.partners,
47-
variables: {
48-
input: {
49-
...sort.value,
50-
filter: filters.tab === 'pinned' ? { pinned: true } : {},
51-
},
52-
},
53-
});
54-
55-
const { classes } = useStyles();
56-
const formatNumber = useNumberFormatter();
57-
const scrollRef = useRef<HTMLElement>(null);
58-
596
return (
60-
<ContentContainer>
7+
<Stack sx={{ flex: 1, padding: 4, pt: 2 }}>
618
<Helmet title="Partners" />
62-
<Typography variant="h2" paragraph>
63-
Partners
64-
</Typography>
65-
<Grid container spacing={1} className={classes.options}>
66-
<Grid item>
67-
<SortButtonDialog {...sort}>
68-
<PartnerSortOptions />
69-
</SortButtonDialog>
70-
</Grid>
71-
</Grid>
72-
73-
<TabContext value={filters.tab}>
74-
<TabList
75-
onChange={(_e, tab) => setFilters({ ...filters, tab })}
76-
aria-label="partner navigation tabs"
77-
className={classes.items}
78-
>
79-
<Tab label="Pinned" value="pinned" />
80-
<Tab label="All" value="all" />
81-
</TabList>
82-
<Divider className={classes.items} />
83-
<TabPanel
84-
value={filters.tab}
85-
className={classes.tabPanel}
86-
ref={scrollRef}
87-
>
88-
<Typography variant="h3" className={classes.total}>
89-
{list.data ? (
90-
`${formatNumber(list.data.total)} Partners`
91-
) : (
92-
<Skeleton width="10ch" />
93-
)}
94-
</Typography>
95-
<List
96-
{...list}
97-
classes={{ container: classes.items }}
98-
renderItem={(item) => <PartnerCard partner={item} />}
99-
renderSkeleton={<PartnerCard />}
100-
scrollRef={scrollRef}
101-
/>
102-
</TabPanel>
103-
</TabContext>
104-
</ContentContainer>
9+
<Stack component="main" sx={{ flex: 1 }}>
10+
<Typography variant="h2" paragraph>
11+
Partners
12+
</Typography>
13+
<Stack sx={{ flex: 1, containerType: 'size' }}>
14+
<Paper
15+
sx={{
16+
flex: 1,
17+
minHeight: 375,
18+
maxHeight: '100cqh',
19+
width: 'min-content',
20+
maxWidth: '100cqw',
21+
}}
22+
>
23+
<PartnerGrid />
24+
</Paper>
25+
</Stack>
26+
</Stack>
27+
</Stack>
10528
);
10629
};

src/scenes/Partners/List/PartnerSortOptions.tsx

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

0 commit comments

Comments
 (0)