Skip to content

Commit 6869e92

Browse files
authored
Merge pull request #1661 from SeedCompany/grid/editing
2 parents 106a8ee + 0f8d9c2 commit 6869e92

File tree

8 files changed

+104
-69
lines changed

8 files changed

+104
-69
lines changed

src/components/EngagementDataGrid/EngagementColumns.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,12 @@ export const EngagementColumns: Array<GridColDef<Engagement>> = [
183183
headerName: 'MOU Start',
184184
field: 'startDate',
185185
...dateColumn(),
186-
valueGetter: dateColumn.valueGetter((_, { startDate }) => startDate.value),
187186
filterable: false,
188187
},
189188
{
190189
headerName: 'MOU End',
191190
field: 'endDate',
192191
...dateColumn(),
193-
valueGetter: dateColumn.valueGetter((_, { endDate }) => endDate.value),
194192
filterable: false,
195193
},
196194
{
Lines changed: 17 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,25 @@
1-
import { useMutation } from '@apollo/client';
2-
import { isEqual } from 'lodash';
3-
import { UpdateLanguageEngagement as UpdateLanguageEngagementInput } from '~/api/schema.graphql';
4-
import { EngagementDataGridRowFragment } from './engagementDataGridRow.graphql';
1+
import { useGridMutation } from '../Grid';
2+
import { EngagementDataGridRowFragmentDoc as EngagementGridRow } from './engagementDataGridRow.graphql';
53
import { UpdateLanguageEngagementGridDocument as UpdateLanguageEngagement } from './UpdateLanguageEngagementGrid.graphql';
64

7-
export const useProcessEngagementUpdate = () => {
8-
const [updateLanguageEngagement] = useMutation(UpdateLanguageEngagement);
9-
10-
return (
11-
updated: EngagementDataGridRowFragment,
12-
prev: EngagementDataGridRowFragment
13-
) => {
14-
if (updated.__typename !== 'LanguageEngagement') {
15-
return updated;
16-
}
17-
18-
if (isEqual(updated, prev)) {
19-
return updated;
5+
export const useProcessEngagementUpdate = () =>
6+
useGridMutation(EngagementGridRow, UpdateLanguageEngagement, (row) => {
7+
if (row.__typename !== 'LanguageEngagement') {
8+
return undefined;
209
}
21-
22-
const input: UpdateLanguageEngagementInput = {
23-
id: updated.id,
24-
milestoneReached: updated.milestoneReached.value,
25-
usingAIAssistedTranslation: updated.usingAIAssistedTranslation.value,
26-
};
27-
// Don't wait for the mutation to finish/error, which allows
28-
// the grid to close the editing state immediately.
29-
// There shouldn't be any business errors from these current changes,
30-
// and network errors are handled with snackbars.
31-
// Additionally, MUI doesn't handle thrown errors either; it just gives
32-
// them straight back to us on the `onProcessRowUpdateError` callback.
33-
void updateLanguageEngagement({
34-
variables: { input },
35-
// Inform Apollo of these async/queued updates.
36-
// This is important because users can make multiple changes
37-
// quickly, since we don't `await` above.
38-
// These optimistic updates are layered/stacked.
39-
// So if two value changes are in flight, the value from the first
40-
// API response isn't presented as the latest value,
41-
// since there is still another optimistic update left.
42-
// Said another way: this prevents the UI from presenting the final change,
43-
// then looking like it reverted to the first change,
44-
// and then flipping back to the final change again.
45-
// This also ensures these pending updates are maintained
46-
// even if the grid is unmounted/remounted.
10+
return {
11+
variables: {
12+
input: {
13+
id: row.id,
14+
milestoneReached: row.milestoneReached.value,
15+
usingAIAssistedTranslation: row.usingAIAssistedTranslation.value,
16+
},
17+
},
4718
optimisticResponse: {
4819
updateLanguageEngagement: {
4920
__typename: 'UpdateLanguageEngagementOutput',
50-
// This is an easy/cheap/hacky way to "unparse" the date scalars
51-
// before writing them to the cache.
52-
// Our read policies expect them to be ISO strings as we receive
53-
// them from the network this way.
54-
// Since our temporal objects have a toJSON, this works fine to revert that.
55-
engagement: JSON.parse(JSON.stringify(updated)),
21+
engagement: row,
5622
},
5723
},
58-
});
59-
60-
return updated;
61-
};
62-
};
24+
};
25+
});

src/components/Grid/ColumnTypes/dateColumn.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import {
55
GridValidRowModel as RowModel,
66
GridValueGetter as ValueGetter,
77
} from '@mui/x-data-grid';
8-
import { Nil } from '@seedcompany/common';
8+
import { isObjectLike, Nil } from '@seedcompany/common';
99
import { DateTime } from 'luxon';
1010
import { DateFilter } from '~/api/schema.graphql';
11-
import { CalendarDate, ISOString } from '~/common';
11+
import { CalendarDate, ISOString, unwrapSecured } from '~/common';
1212
import { GridHeaderAddFilterButton } from '../GridHeaderAddFilterButton';
1313
import { column, RowLike } from './definition.types';
1414

@@ -21,6 +21,7 @@ export const dateColumn = <Row extends RowLike>() =>
2121
column<Row>()({
2222
type: 'date',
2323
valueGetter: dateColumn.valueGetter(defaultValueGetter),
24+
valueSetter: defaultValueSetter,
2425
filterOperators,
2526
renderHeaderFilter: GridHeaderAddFilterButton,
2627
});
@@ -57,7 +58,24 @@ dateColumn.valueGetter =
5758
return value;
5859
};
5960
const defaultValueGetter: DateValueGetterInput = (_, row, column) =>
60-
row[column.field];
61+
unwrapSecured(row[column.field]);
62+
63+
const defaultValueSetter = <R extends RowModel>(
64+
raw: Date | null,
65+
row: R,
66+
column: ColDef<R>
67+
) => {
68+
const value = raw ? CalendarDate.fromJSDate(raw) : null;
69+
const field = row[column.field];
70+
const wrapSecured =
71+
isObjectLike(field) &&
72+
'__typename' in field &&
73+
typeof field.__typename === 'string' &&
74+
field.__typename.startsWith('Secured')
75+
? { ...field, value }
76+
: value;
77+
return { ...row, [column.field]: wrapSecured };
78+
};
6179

6280
const filterOpNameMap: Record<string, keyof DateFilter> = {
6381
after: 'after',

src/components/Grid/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export * from './QuickFilters';
1313
export * from './ColumnTypes/multiEnumColumn';
1414
export * from './ColumnTypes/dateColumn';
1515
export * from './isCellEditable';
16+
export * from './useGridMutation';
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { useMutation } from '@apollo/client';
2+
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
3+
import { isEqual } from 'lodash';
4+
5+
export const useGridMutation = <Row, MutationRes, Vars>(
6+
rowShape: DocumentNode<Row>,
7+
mutation: DocumentNode<MutationRes, Vars>,
8+
makeInput: NoInfer<
9+
(object: Row) =>
10+
| {
11+
variables: Vars;
12+
optimisticResponse: MutationRes;
13+
}
14+
| undefined
15+
>
16+
) => {
17+
const [update] = useMutation(mutation);
18+
19+
return (updatedRow: Row, prevRow: Row) => {
20+
const updatedInput = makeInput(updatedRow);
21+
const prevInput = makeInput(prevRow);
22+
23+
if (!updatedInput || !prevInput) {
24+
return updatedRow;
25+
}
26+
27+
if (isEqual(updatedInput.variables, prevInput.variables)) {
28+
return updatedRow;
29+
}
30+
31+
// Don't wait for the mutation to finish/error, which allows
32+
// the grid to close the editing state immediately.
33+
// There shouldn't be any business errors from these current changes,
34+
// and network errors are handled with snackbars.
35+
// Additionally, MUI doesn't handle thrown errors either; it just gives
36+
// them straight back to us on the `onProcessRowUpdateError` callback.
37+
void update({
38+
variables: updatedInput.variables,
39+
// Inform Apollo of these async/queued updates.
40+
// This is important because users can make multiple changes
41+
// quickly, since we don't `await` above.
42+
// These optimistic updates are layered/stacked.
43+
// So if two value changes are in flight, the value from the first
44+
// API response isn't presented as the latest value,
45+
// since there is still another optimistic update left.
46+
// Said another way: this prevents the UI from presenting the final change,
47+
// then looking like it reverted to the first change,
48+
// and then flipping back to the final change again.
49+
// This also ensures these pending updates are maintained
50+
// even if the grid is unmounted/remounted.
51+
optimisticResponse: updatedInput.optimisticResponse,
52+
});
53+
54+
return updatedRow;
55+
};
56+
};

src/components/PartnersDataGrid/PartnerColumns.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ export const PartnerColumns: Array<GridColDef<Partner>> = [
4141
headerName: 'Start Date',
4242
field: 'startDate',
4343
...dateColumn(),
44-
valueGetter: dateColumn.valueGetter((_, { startDate }) => startDate.value),
4544
width: 130,
4645
},
4746
{

src/components/ProjectDataGrid/ProjectColumns.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,11 @@ export const ProjectColumns: Array<GridColDef<Project>> = [
7878
headerName: 'MOU Start',
7979
field: 'mouStart',
8080
...dateColumn(),
81-
valueGetter: dateColumn.valueGetter((_, { mouStart }) => mouStart.value),
8281
},
8382
{
8483
headerName: 'MOU End',
8584
field: 'mouEnd',
8685
...dateColumn(),
87-
valueGetter: dateColumn.valueGetter((_, { mouEnd }) => mouEnd.value),
8886
},
8987
SensitivityColumn({}),
9088
{

yarn.lock

Lines changed: 9 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)