@axinom/mosaic-ui 0.39.0-rc.4 → 0.39.1-feat-gs.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/FormStation/Create/Create.d.ts +1 -1
- package/dist/components/FormStation/Create/Create.d.ts.map +1 -1
- package/dist/components/FormStation/Details/Details.d.ts +2 -2
- package/dist/components/FormStation/Details/Details.d.ts.map +1 -1
- package/dist/components/FormStation/FormContentWrapper/FormContentWrapper.d.ts +11 -0
- package/dist/components/FormStation/FormContentWrapper/FormContentWrapper.d.ts.map +1 -0
- package/dist/components/FormStation/FormStation.d.ts +8 -15
- package/dist/components/FormStation/FormStation.d.ts.map +1 -1
- package/dist/components/FormStation/FormStation.models.d.ts +17 -3
- package/dist/components/FormStation/FormStation.models.d.ts.map +1 -1
- package/dist/components/FormStation/FormStationActions/FormStationActions.d.ts +21 -0
- package/dist/components/FormStation/FormStationActions/FormStationActions.d.ts.map +1 -0
- package/dist/components/FormStation/FormStationContext/FormStationContext.d.ts +13 -0
- package/dist/components/FormStation/FormStationContext/FormStationContext.d.ts.map +1 -0
- package/dist/components/FormStation/FormStationContext/FormStationContextProvider.d.ts +10 -0
- package/dist/components/FormStation/FormStationContext/FormStationContextProvider.d.ts.map +1 -0
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts +12 -0
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -0
- package/dist/components/FormStation/helpers/mergeData.d.ts +7 -0
- package/dist/components/FormStation/helpers/mergeData.d.ts.map +1 -0
- package/dist/components/FormStation/helpers/useChangeSets.d.ts +12 -0
- package/dist/components/FormStation/helpers/useChangeSets.d.ts.map +1 -0
- package/dist/components/FormStation/helpers/useDataProvider.d.ts +14 -0
- package/dist/components/FormStation/helpers/useDataProvider.d.ts.map +1 -0
- package/dist/components/FormStation/helpers/useDebouncedFormikValues.d.ts +7 -0
- package/dist/components/FormStation/helpers/useDebouncedFormikValues.d.ts.map +1 -0
- package/dist/components/FormStation/helpers/useUndo.d.ts +6 -0
- package/dist/components/FormStation/helpers/useUndo.d.ts.map +1 -0
- package/dist/components/FormStation/{useValidationError.d.ts → helpers/useValidationError.d.ts} +1 -1
- package/dist/components/FormStation/helpers/useValidationError.d.ts.map +1 -0
- package/dist/components/FormStation/index.d.ts +1 -1
- package/dist/components/FormStation/index.d.ts.map +1 -1
- package/dist/components/Utils/Postgraphile/getArrayDiff.d.ts.map +1 -1
- package/dist/components/Utils/Postgraphile/getFormDiff.d.ts.map +1 -1
- package/dist/helpers/testing.d.ts +4 -1
- package/dist/helpers/testing.d.ts.map +1 -1
- package/dist/index.es.js +4 -4
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/components/Actions/Action/Action.scss +1 -0
- package/src/components/FormElements/Tags/Tags.tsx +3 -3
- package/src/components/FormStation/Create/Create.stories.tsx +1 -9
- package/src/components/FormStation/Create/Create.tsx +4 -1
- package/src/components/FormStation/Details/Details.tsx +5 -2
- package/src/components/FormStation/FormContentWrapper/FormContentWrapper.scss +66 -0
- package/src/components/FormStation/FormContentWrapper/FormContentWrapper.tsx +77 -0
- package/src/components/FormStation/FormStation.models.ts +29 -3
- package/src/components/FormStation/FormStation.scss +0 -70
- package/src/components/FormStation/FormStation.spec.tsx +2 -1
- package/src/components/FormStation/FormStation.stories.tsx +20 -1
- package/src/components/FormStation/FormStation.tsx +68 -403
- package/src/components/FormStation/FormStationActions/FormStationActions.tsx +132 -0
- package/src/components/FormStation/FormStationContext/FormStationContext.ts +22 -0
- package/src/components/FormStation/FormStationContext/FormStationContextProvider.tsx +86 -0
- package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +85 -0
- package/src/components/FormStation/helpers/mergeData.ts +26 -0
- package/src/components/FormStation/helpers/useChangeSets.ts +70 -0
- package/src/components/FormStation/helpers/useDataProvider.ts +169 -0
- package/src/components/FormStation/helpers/useDebouncedFormikValues.ts +22 -0
- package/src/components/FormStation/helpers/useUndo.ts +43 -0
- package/src/components/FormStation/{useValidationError.tsx → helpers/useValidationError.tsx} +1 -1
- package/src/components/FormStation/index.ts +1 -5
- package/src/components/Utils/Postgraphile/getArrayDiff.ts +7 -6
- package/src/components/Utils/Postgraphile/getFormDiff.ts +2 -1
- package/dist/components/FormStation/StationErrorStateType.d.ts +0 -5
- package/dist/components/FormStation/StationErrorStateType.d.ts.map +0 -1
- package/dist/components/FormStation/useValidationError.d.ts.map +0 -1
- package/src/components/FormStation/StationErrorStateType.tsx +0 -5
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { PropsWithChildren, useEffect, useRef } from 'react';
|
|
2
|
+
import { Data } from '../../../types';
|
|
3
|
+
import { getFormDiff } from '../../Utils';
|
|
4
|
+
import { useChangeSets } from '../helpers/useChangeSets';
|
|
5
|
+
import { useDebouncedFormikValues } from '../helpers/useDebouncedFormikValues';
|
|
6
|
+
import { FormStationContext } from './FormStationContext';
|
|
7
|
+
|
|
8
|
+
interface FormStationContextProviderProps<TValues extends Data> {
|
|
9
|
+
autosave?: boolean;
|
|
10
|
+
autosaveDelay?: number;
|
|
11
|
+
currentValuesRef?: React.MutableRefObject<Partial<TValues>>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const FormStationContextProvider = <TValues extends Data>({
|
|
15
|
+
children,
|
|
16
|
+
autosave = false,
|
|
17
|
+
autosaveDelay,
|
|
18
|
+
currentValuesRef,
|
|
19
|
+
}: PropsWithChildren<
|
|
20
|
+
FormStationContextProviderProps<TValues>
|
|
21
|
+
>): JSX.Element => {
|
|
22
|
+
const {
|
|
23
|
+
debouncedValues,
|
|
24
|
+
initialValues,
|
|
25
|
+
dirty,
|
|
26
|
+
isSubmitting,
|
|
27
|
+
submitForm,
|
|
28
|
+
isValid,
|
|
29
|
+
values,
|
|
30
|
+
} = useDebouncedFormikValues<TValues>(autosaveDelay);
|
|
31
|
+
|
|
32
|
+
if (currentValuesRef) {
|
|
33
|
+
currentValuesRef.current = values;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// We use this to track the last value that was undone so we can avoid pushing it to the changeSets
|
|
37
|
+
const lastUndoneValue = useRef<Partial<TValues>>({});
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
changeSets,
|
|
41
|
+
push: pushChangeSet,
|
|
42
|
+
pop: popChangeSet,
|
|
43
|
+
clear: clearChangeSets,
|
|
44
|
+
getAggregatedChanges,
|
|
45
|
+
} = useChangeSets(initialValues);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (dirty && !isSubmitting && debouncedValues !== lastUndoneValue.current) {
|
|
49
|
+
let previousValues = initialValues;
|
|
50
|
+
|
|
51
|
+
if (!autosave) {
|
|
52
|
+
previousValues = getAggregatedChanges();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const next = getFormDiff(debouncedValues, previousValues);
|
|
56
|
+
const prev: Partial<TValues> = {};
|
|
57
|
+
|
|
58
|
+
Object.keys(next).forEach((key) => {
|
|
59
|
+
prev[key as keyof TValues] = previousValues[key];
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
pushChangeSet({ next, prev });
|
|
63
|
+
|
|
64
|
+
if (autosave && isValid) {
|
|
65
|
+
submitForm();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// This effect should only be run when we have new debounced values. This is done to simplify the logic.
|
|
69
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
|
+
}, [debouncedValues]);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<FormStationContext.Provider
|
|
74
|
+
value={{
|
|
75
|
+
changeSets,
|
|
76
|
+
pushChangeSet,
|
|
77
|
+
popChangeSet,
|
|
78
|
+
clearChangeSets,
|
|
79
|
+
lastUndoneValue,
|
|
80
|
+
autosave,
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
</FormStationContext.Provider>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { FormikValues, useFormikContext } from 'formik';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useHistory } from 'react-router-dom';
|
|
4
|
+
import { IconName } from '../../Icons';
|
|
5
|
+
import {
|
|
6
|
+
PageHeader,
|
|
7
|
+
PageHeaderActionType,
|
|
8
|
+
PageHeaderProps,
|
|
9
|
+
} from '../../PageHeader';
|
|
10
|
+
import { useUndo } from '../helpers/useUndo';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handles showRefresh and cancel buttons based on form states
|
|
14
|
+
*/
|
|
15
|
+
export const FormStationHeader: React.FC<
|
|
16
|
+
Omit<PageHeaderProps, 'title'> & {
|
|
17
|
+
titleProperty?: string;
|
|
18
|
+
defaultTitle?: string;
|
|
19
|
+
cancelNavigationUrl?: string;
|
|
20
|
+
isFormSubmitting?: boolean;
|
|
21
|
+
}
|
|
22
|
+
> = ({
|
|
23
|
+
titleProperty,
|
|
24
|
+
defaultTitle,
|
|
25
|
+
subtitle,
|
|
26
|
+
cancelNavigationUrl,
|
|
27
|
+
isFormSubmitting,
|
|
28
|
+
}) => {
|
|
29
|
+
const { values, resetForm } = useFormikContext<FormikValues>();
|
|
30
|
+
|
|
31
|
+
const history = useHistory();
|
|
32
|
+
|
|
33
|
+
const { undoOnce, undoAll, showUndo } = useUndo();
|
|
34
|
+
|
|
35
|
+
const title =
|
|
36
|
+
titleProperty && values[titleProperty] !== ''
|
|
37
|
+
? values[titleProperty]
|
|
38
|
+
: defaultTitle ?? '';
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<PageHeader
|
|
42
|
+
title={title}
|
|
43
|
+
subtitle={subtitle}
|
|
44
|
+
actions={[
|
|
45
|
+
...(showUndo
|
|
46
|
+
? [
|
|
47
|
+
{
|
|
48
|
+
label: 'Undo Once',
|
|
49
|
+
icon: IconName.Undo,
|
|
50
|
+
actionType: PageHeaderActionType.Context,
|
|
51
|
+
onClick: () => {
|
|
52
|
+
undoOnce();
|
|
53
|
+
},
|
|
54
|
+
disabled: isFormSubmitting,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: 'Undo All',
|
|
58
|
+
icon: IconName.Undo,
|
|
59
|
+
actionType: PageHeaderActionType.Context,
|
|
60
|
+
onClick: () => {
|
|
61
|
+
undoAll();
|
|
62
|
+
},
|
|
63
|
+
disabled: isFormSubmitting,
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
: []),
|
|
67
|
+
...(cancelNavigationUrl // add cancel action if applicable
|
|
68
|
+
? [
|
|
69
|
+
{
|
|
70
|
+
label: 'Cancel',
|
|
71
|
+
icon: IconName.X,
|
|
72
|
+
onClick: () => {
|
|
73
|
+
resetForm();
|
|
74
|
+
// If the form has errors, Navigation needs to be wrapped in a promise or timeout.
|
|
75
|
+
Promise.resolve().then(() =>
|
|
76
|
+
history.push(cancelNavigationUrl),
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
: []),
|
|
82
|
+
]}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Data } from '../../../types';
|
|
2
|
+
import { getFormDiff } from '../../Utils';
|
|
3
|
+
|
|
4
|
+
export const mergeData = <TValues extends Data>(
|
|
5
|
+
initialValues: TValues,
|
|
6
|
+
currentValues: TValues,
|
|
7
|
+
updatedValues?: Partial<TValues>,
|
|
8
|
+
): {
|
|
9
|
+
newInitialValues: TValues;
|
|
10
|
+
newCurrentValues: TValues;
|
|
11
|
+
shouldUpdateCurrentValues: boolean;
|
|
12
|
+
} => {
|
|
13
|
+
const diff = getFormDiff(initialValues, currentValues);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
newInitialValues: {
|
|
17
|
+
...initialValues,
|
|
18
|
+
...updatedValues,
|
|
19
|
+
},
|
|
20
|
+
newCurrentValues: {
|
|
21
|
+
...currentValues,
|
|
22
|
+
...updatedValues,
|
|
23
|
+
},
|
|
24
|
+
shouldUpdateCurrentValues: Object.keys(diff).length > 0,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ChangeSet<T> {
|
|
4
|
+
prev: Partial<T>;
|
|
5
|
+
next: Partial<T>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const useChangeSets = <T>(
|
|
9
|
+
initialValues: T,
|
|
10
|
+
): {
|
|
11
|
+
changeSets: ChangeSet<T>[];
|
|
12
|
+
push: (value: ChangeSet<T>) => number;
|
|
13
|
+
pop: () => ChangeSet<T> | undefined;
|
|
14
|
+
clear: () => Partial<T>;
|
|
15
|
+
getAggregatedChanges: () => T;
|
|
16
|
+
} => {
|
|
17
|
+
const [changeSets, setChangeSets] = useState<ChangeSet<T>[]>([]);
|
|
18
|
+
|
|
19
|
+
const push = useCallback(
|
|
20
|
+
(value: ChangeSet<T>): number => {
|
|
21
|
+
setChangeSets((prev) => [...prev, value]);
|
|
22
|
+
return changeSets.length;
|
|
23
|
+
},
|
|
24
|
+
[changeSets],
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const pop = useCallback((): ChangeSet<T> | undefined => {
|
|
28
|
+
if (changeSets.length > 0) {
|
|
29
|
+
const lastChangeSet = changeSets[changeSets.length - 1];
|
|
30
|
+
setChangeSets((prev) => prev.slice(0, -1));
|
|
31
|
+
return lastChangeSet;
|
|
32
|
+
}
|
|
33
|
+
}, [changeSets]);
|
|
34
|
+
|
|
35
|
+
const clear = useCallback((): T => {
|
|
36
|
+
const allChanges = changeSets
|
|
37
|
+
.slice()
|
|
38
|
+
.reverse()
|
|
39
|
+
.reduce((acc, changeSet) => {
|
|
40
|
+
return {
|
|
41
|
+
...acc,
|
|
42
|
+
...changeSet.prev,
|
|
43
|
+
};
|
|
44
|
+
}, {} as T);
|
|
45
|
+
|
|
46
|
+
setChangeSets([]);
|
|
47
|
+
return { ...initialValues, ...allChanges };
|
|
48
|
+
}, [changeSets, initialValues]);
|
|
49
|
+
|
|
50
|
+
const getAggregatedChanges = useCallback(
|
|
51
|
+
(): T => ({
|
|
52
|
+
...initialValues,
|
|
53
|
+
...changeSets.reduce((acc, changeSet) => {
|
|
54
|
+
return {
|
|
55
|
+
...acc,
|
|
56
|
+
...changeSet.next,
|
|
57
|
+
};
|
|
58
|
+
}, {} as T),
|
|
59
|
+
}),
|
|
60
|
+
[changeSets, initialValues],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
changeSets,
|
|
65
|
+
push,
|
|
66
|
+
pop,
|
|
67
|
+
clear,
|
|
68
|
+
getAggregatedChanges,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { FormikHelpers } from 'formik';
|
|
2
|
+
import {
|
|
3
|
+
Dispatch,
|
|
4
|
+
SetStateAction,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { SaveIndicatorType, setSaveIndicator } from '../../../initialize';
|
|
11
|
+
import { Data } from '../../../types';
|
|
12
|
+
import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
|
|
13
|
+
import { getFormDiff } from '../../Utils';
|
|
14
|
+
import type { ErrorType } from '../../models';
|
|
15
|
+
import type {
|
|
16
|
+
InitialFormData,
|
|
17
|
+
SaveDataFunction,
|
|
18
|
+
StationErrorStateType,
|
|
19
|
+
} from '../FormStation.models';
|
|
20
|
+
import { mergeData } from './mergeData';
|
|
21
|
+
|
|
22
|
+
export type FormStationDataProvider = <TValues extends Data, TSubmitResponse>(
|
|
23
|
+
initialData: InitialFormData<TValues>,
|
|
24
|
+
saveData: SaveDataFunction<TValues, TSubmitResponse>,
|
|
25
|
+
currentDataRef: React.MutableRefObject<TValues>,
|
|
26
|
+
) => {
|
|
27
|
+
onSubmit: (
|
|
28
|
+
values: TValues,
|
|
29
|
+
formikHelpers: FormikHelpers<TValues>,
|
|
30
|
+
initialFormData?: InitialFormData<TValues>,
|
|
31
|
+
) => Promise<TSubmitResponse> | void;
|
|
32
|
+
stationError?: StationErrorStateType;
|
|
33
|
+
setStationError: Dispatch<SetStateAction<StationErrorStateType | undefined>>;
|
|
34
|
+
isFormSubmitting: boolean;
|
|
35
|
+
lastSubmittedResponse: React.MutableRefObject<TSubmitResponse | undefined>;
|
|
36
|
+
initialValues: TValues | null | undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const useDataProvider: FormStationDataProvider = <
|
|
40
|
+
TValues extends Data,
|
|
41
|
+
TSubmitResponse,
|
|
42
|
+
>(
|
|
43
|
+
initialData: InitialFormData<TValues>,
|
|
44
|
+
saveData: SaveDataFunction<TValues, TSubmitResponse>,
|
|
45
|
+
currentDataRef: React.MutableRefObject<TValues>,
|
|
46
|
+
) => {
|
|
47
|
+
const [stationError, setStationError] = useState<StationErrorStateType>();
|
|
48
|
+
const [isFormSubmitting, setIsFormSubmitting] = useState<boolean>(false);
|
|
49
|
+
const lastSubmittedResponse = useRef<TSubmitResponse>();
|
|
50
|
+
const [initialValues, setInitialValues] = useState(
|
|
51
|
+
initialData.data ?? ({} as TValues),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (
|
|
56
|
+
!initialData.loading &&
|
|
57
|
+
initialData.data !== null &&
|
|
58
|
+
initialData.data !== undefined
|
|
59
|
+
) {
|
|
60
|
+
// Form Station tends to re-render when the initialData prop changes even though there are no changes to the data.
|
|
61
|
+
// This check is there to prevent these unnecessary re-renders.
|
|
62
|
+
const diff = getFormDiff(initialData.data as TValues, initialValues);
|
|
63
|
+
if (diff && Object.keys(diff).length > 0) {
|
|
64
|
+
setInitialValues(initialData.data as TValues);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
|
+
}, [initialData.data]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (
|
|
72
|
+
initialData.error ||
|
|
73
|
+
(initialData.data === null && !initialData.loading) ||
|
|
74
|
+
initialData.entityNotFound
|
|
75
|
+
) {
|
|
76
|
+
setStationError({
|
|
77
|
+
...ErrorTypeToStationError(
|
|
78
|
+
initialData.error,
|
|
79
|
+
'An error occurred when trying to load data.',
|
|
80
|
+
'Entity not found',
|
|
81
|
+
),
|
|
82
|
+
type: 'loading',
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
if (stationError?.type === 'loading') {
|
|
86
|
+
// Only clear the error if it is a loading error, which now seems to be cleared.
|
|
87
|
+
setStationError(undefined);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}, [
|
|
91
|
+
initialData.loading,
|
|
92
|
+
initialData.error,
|
|
93
|
+
initialData.entityNotFound,
|
|
94
|
+
initialData.data,
|
|
95
|
+
stationError?.type,
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
const onSubmit = useCallback(
|
|
99
|
+
async (
|
|
100
|
+
values: TValues,
|
|
101
|
+
formikHelpers: FormikHelpers<TValues>,
|
|
102
|
+
initialFormData: InitialFormData<TValues> = initialData,
|
|
103
|
+
) => {
|
|
104
|
+
if (isFormSubmitting) {
|
|
105
|
+
// TODO: Handle this case. Throw an error maybe?
|
|
106
|
+
return undefined as TSubmitResponse;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
setIsFormSubmitting(true);
|
|
111
|
+
setSaveIndicator(SaveIndicatorType.Saving);
|
|
112
|
+
setStationError(undefined);
|
|
113
|
+
if (!initialFormData.loading && saveData) {
|
|
114
|
+
const result = await saveData(
|
|
115
|
+
values,
|
|
116
|
+
{ ...initialFormData, data: initialValues },
|
|
117
|
+
formikHelpers,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (result) {
|
|
121
|
+
lastSubmittedResponse.current = result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const {
|
|
125
|
+
newCurrentValues,
|
|
126
|
+
newInitialValues,
|
|
127
|
+
shouldUpdateCurrentValues,
|
|
128
|
+
} = mergeData<TValues>(values, currentDataRef.current, result ?? {});
|
|
129
|
+
|
|
130
|
+
formikHelpers.resetForm({ values: newInitialValues });
|
|
131
|
+
setInitialValues(newInitialValues);
|
|
132
|
+
|
|
133
|
+
if (shouldUpdateCurrentValues) {
|
|
134
|
+
formikHelpers.setValues(newCurrentValues);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return lastSubmittedResponse.current as TSubmitResponse;
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
setStationError(
|
|
141
|
+
ErrorTypeToStationError(
|
|
142
|
+
error as ErrorType,
|
|
143
|
+
'An error occurred when trying to save data.',
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// We still throw the error, to make sure that navigation or action execution
|
|
148
|
+
// will not continue after a failed save.
|
|
149
|
+
throw error;
|
|
150
|
+
} finally {
|
|
151
|
+
formikHelpers.setSubmitting(false);
|
|
152
|
+
setIsFormSubmitting(false);
|
|
153
|
+
setSaveIndicator(SaveIndicatorType.Inactive);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return undefined as TSubmitResponse;
|
|
157
|
+
},
|
|
158
|
+
[currentDataRef, initialData, initialValues, isFormSubmitting, saveData],
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
onSubmit,
|
|
163
|
+
stationError,
|
|
164
|
+
setStationError,
|
|
165
|
+
isFormSubmitting,
|
|
166
|
+
lastSubmittedResponse,
|
|
167
|
+
initialValues,
|
|
168
|
+
};
|
|
169
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FormikContextType, useFormikContext } from 'formik';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { useDebounce } from '../../../hooks';
|
|
4
|
+
|
|
5
|
+
export const useDebouncedFormikValues = <TValues>(
|
|
6
|
+
autosaveDelay?: number,
|
|
7
|
+
): {
|
|
8
|
+
debouncedValues: TValues;
|
|
9
|
+
} & FormikContextType<TValues> => {
|
|
10
|
+
const formikContext = useFormikContext<TValues>();
|
|
11
|
+
|
|
12
|
+
const [debouncedValues, setValues] = useDebounce(
|
|
13
|
+
formikContext.initialValues,
|
|
14
|
+
autosaveDelay ?? 500,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setValues(formikContext.values);
|
|
19
|
+
}, [setValues, formikContext.values]);
|
|
20
|
+
|
|
21
|
+
return { debouncedValues, ...formikContext };
|
|
22
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { FormikValues, useFormikContext } from 'formik';
|
|
2
|
+
import React, { useCallback } from 'react';
|
|
3
|
+
import { FormStationContext } from '../FormStationContext/FormStationContext';
|
|
4
|
+
|
|
5
|
+
export const useUndo = (): {
|
|
6
|
+
undoOnce: () => void;
|
|
7
|
+
undoAll: () => void;
|
|
8
|
+
showUndo: boolean;
|
|
9
|
+
} => {
|
|
10
|
+
const {
|
|
11
|
+
changeSets,
|
|
12
|
+
popChangeSet,
|
|
13
|
+
clearChangeSets,
|
|
14
|
+
lastUndoneValue,
|
|
15
|
+
autosave,
|
|
16
|
+
} = React.useContext(FormStationContext);
|
|
17
|
+
const { values, setValues, submitForm } = useFormikContext<FormikValues>();
|
|
18
|
+
|
|
19
|
+
const undoAll = useCallback((): void => {
|
|
20
|
+
lastUndoneValue.current = clearChangeSets();
|
|
21
|
+
setValues(lastUndoneValue.current);
|
|
22
|
+
if (autosave) {
|
|
23
|
+
submitForm();
|
|
24
|
+
}
|
|
25
|
+
}, [autosave, clearChangeSets, lastUndoneValue, setValues, submitForm]);
|
|
26
|
+
|
|
27
|
+
const undoOnce = useCallback((): void => {
|
|
28
|
+
const lastChangeSet = popChangeSet();
|
|
29
|
+
|
|
30
|
+
lastUndoneValue.current = {
|
|
31
|
+
...values,
|
|
32
|
+
...lastChangeSet?.prev,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
setValues(lastUndoneValue.current);
|
|
36
|
+
|
|
37
|
+
if (autosave) {
|
|
38
|
+
submitForm();
|
|
39
|
+
}
|
|
40
|
+
}, [autosave, lastUndoneValue, popChangeSet, setValues, submitForm, values]);
|
|
41
|
+
|
|
42
|
+
return { undoOnce, undoAll, showUndo: changeSets.length > 0 };
|
|
43
|
+
};
|
package/src/components/FormStation/{useValidationError.tsx → helpers/useValidationError.tsx}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useFormikContext } from 'formik';
|
|
2
2
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
3
|
-
import { StationErrorStateType } from '
|
|
3
|
+
import type { StationErrorStateType } from '../FormStation.models';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Component that watches for changes in the form validation state
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
export { Create, CreateProps } from './Create/Create';
|
|
2
2
|
export { Details, DetailsProps } from './Details/Details';
|
|
3
|
-
export {
|
|
4
|
-
FormStation,
|
|
5
|
-
FormStationProps,
|
|
6
|
-
ObjectSchemaDefinition,
|
|
7
|
-
} from './FormStation';
|
|
3
|
+
export { FormStation, FormStationProps } from './FormStation';
|
|
8
4
|
export * from './FormStation.models';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isEqual } from 'lodash';
|
|
1
2
|
/**
|
|
2
3
|
* Compares two arrays and returns an object holding an array for the added and another one for the removed items.
|
|
3
4
|
*
|
|
@@ -53,9 +54,9 @@ export function getArrayDiff<T>(
|
|
|
53
54
|
(x: T) =>
|
|
54
55
|
!originalArray.find((e) => {
|
|
55
56
|
if (key) {
|
|
56
|
-
return e[key]
|
|
57
|
+
return isEqual(e[key], x[key]);
|
|
57
58
|
}
|
|
58
|
-
return e
|
|
59
|
+
return isEqual(e, x);
|
|
59
60
|
}),
|
|
60
61
|
);
|
|
61
62
|
|
|
@@ -63,9 +64,9 @@ export function getArrayDiff<T>(
|
|
|
63
64
|
(x: T) =>
|
|
64
65
|
!currentArray.find((e) => {
|
|
65
66
|
if (key && typeof key === 'string') {
|
|
66
|
-
return e[key]
|
|
67
|
+
return isEqual(e[key], x[key]);
|
|
67
68
|
}
|
|
68
|
-
return e
|
|
69
|
+
return isEqual(e, x);
|
|
69
70
|
}),
|
|
70
71
|
);
|
|
71
72
|
|
|
@@ -74,7 +75,7 @@ export function getArrayDiff<T>(
|
|
|
74
75
|
if (key && typeof key === 'string') {
|
|
75
76
|
updated.push(
|
|
76
77
|
...currentArray.filter((x: T) =>
|
|
77
|
-
originalArray.find((e) => e[key]
|
|
78
|
+
originalArray.find((e) => isEqual(e[key], x[key]) && isUpdated(e, x)),
|
|
78
79
|
),
|
|
79
80
|
);
|
|
80
81
|
}
|
|
@@ -87,7 +88,7 @@ function isUpdated<U>(x: U, y: U): boolean {
|
|
|
87
88
|
|
|
88
89
|
if (isObject(x) && isObject(y)) {
|
|
89
90
|
Object.keys(x).forEach((key) => {
|
|
90
|
-
if (x[key]
|
|
91
|
+
if (!isEqual(x[key], y[key])) {
|
|
91
92
|
value = true;
|
|
92
93
|
}
|
|
93
94
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FormikValues } from 'formik';
|
|
2
|
+
import { isEqual } from 'lodash';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Returns an object only containing the differences between the two FormikValues.
|
|
@@ -19,7 +20,7 @@ export function getFormDiff<T extends FormikValues>(
|
|
|
19
20
|
): Partial<T> {
|
|
20
21
|
const updateData = initial
|
|
21
22
|
? Object.keys(current).reduce<T>((result, value) => {
|
|
22
|
-
if (current[value]
|
|
23
|
+
if (!isEqual(current[value], initial[value])) {
|
|
23
24
|
result[value as keyof T] = current[value];
|
|
24
25
|
}
|
|
25
26
|
return result;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StationErrorStateType.d.ts","sourceRoot":"","sources":["../../../src/components/FormStation/StationErrorStateType.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,WAAW,qBAAsB,SAAQ,YAAY;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useValidationError.d.ts","sourceRoot":"","sources":["../../../src/components/FormStation/useValidationError.tsx"],"names":[],"mappings":"AACA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAehE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,qBAAqB,GAAG,SAAS,EAC/C,eAAe,EAAE,KAAK,CAAC,QAAQ,CAC7B,KAAK,CAAC,cAAc,CAAC,qBAAqB,GAAG,SAAS,CAAC,CACxD,GACA;IACD,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,GAAG,CAAC,OAAO,CAAC;CAChC,CAyBA"}
|