@axinom/mosaic-ui 0.42.0 → 0.43.0-rc.1
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/FormGrid/FormGrid.d.ts +7 -0
- package/dist/components/FormStation/FormGrid/FormGrid.d.ts.map +1 -0
- package/dist/components/FormStation/FormGrid/index.d.ts +2 -0
- package/dist/components/FormStation/FormGrid/index.d.ts.map +1 -0
- package/dist/components/FormStation/FormStation.d.ts +3 -12
- package/dist/components/FormStation/FormStation.d.ts.map +1 -1
- package/dist/components/FormStation/FormStation.models.d.ts +16 -2
- 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/FormStationActions/index.d.ts +2 -0
- package/dist/components/FormStation/FormStationActions/index.d.ts.map +1 -0
- package/dist/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.d.ts +11 -0
- package/dist/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.d.ts.map +1 -0
- package/dist/components/FormStation/FormStationContentWrapper/index.d.ts +2 -0
- package/dist/components/FormStation/FormStationContentWrapper/index.d.ts.map +1 -0
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts +11 -0
- package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -0
- package/dist/components/FormStation/FormStationHeader/index.d.ts +2 -0
- package/dist/components/FormStation/FormStationHeader/index.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/{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 +2 -1
- package/dist/components/FormStation/index.d.ts.map +1 -1
- package/dist/components/Tabs/Tab/CustomTab.d.ts +3 -0
- package/dist/components/Tabs/Tab/CustomTab.d.ts.map +1 -0
- package/dist/components/Tabs/Tab/index.d.ts +2 -0
- package/dist/components/Tabs/Tab/index.d.ts.map +1 -0
- package/dist/components/Tabs/TabList/CustomTabList.d.ts +3 -0
- package/dist/components/Tabs/TabList/CustomTabList.d.ts.map +1 -0
- package/dist/components/Tabs/TabList/ScrollContainer/ScrollContainer.d.ts +3 -0
- package/dist/components/Tabs/TabList/ScrollContainer/ScrollContainer.d.ts.map +1 -0
- package/dist/components/Tabs/TabList/ScrollContainer/index.d.ts +2 -0
- package/dist/components/Tabs/TabList/ScrollContainer/index.d.ts.map +1 -0
- package/dist/components/Tabs/TabList/ScrollContainer/useScroll.d.ts +10 -0
- package/dist/components/Tabs/TabList/ScrollContainer/useScroll.d.ts.map +1 -0
- package/dist/components/Tabs/TabList/index.d.ts +2 -0
- package/dist/components/Tabs/TabList/index.d.ts.map +1 -0
- package/dist/components/Tabs/TabPanel/CustomTabPanel.d.ts +3 -0
- package/dist/components/Tabs/TabPanel/CustomTabPanel.d.ts.map +1 -0
- package/dist/components/Tabs/TabPanel/index.d.ts +2 -0
- package/dist/components/Tabs/TabPanel/index.d.ts.map +1 -0
- package/dist/components/Tabs/index.d.ts +5 -0
- package/dist/components/Tabs/index.d.ts.map +1 -0
- 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 +4 -3
- package/src/components/FormStation/FormGrid/FormGrid.scss +10 -0
- package/src/components/FormStation/FormGrid/FormGrid.tsx +25 -0
- package/src/components/FormStation/FormGrid/index.ts +1 -0
- package/src/components/FormStation/FormStation.models.ts +28 -2
- package/src/components/FormStation/FormStation.scss +1 -117
- package/src/components/FormStation/FormStation.stories.tsx +166 -1
- package/src/components/FormStation/FormStation.tsx +39 -388
- package/src/components/FormStation/FormStationActions/FormStationActions.tsx +130 -0
- package/src/components/FormStation/FormStationActions/index.ts +1 -0
- package/src/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.scss +66 -0
- package/src/components/FormStation/FormStationContentWrapper/FormStationContentWrapper.tsx +76 -0
- package/src/components/FormStation/FormStationContentWrapper/index.ts +1 -0
- package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +88 -0
- package/src/components/FormStation/FormStationHeader/index.ts +1 -0
- package/src/components/FormStation/helpers/useDataProvider.ts +124 -0
- package/src/components/FormStation/{useValidationError.tsx → helpers/useValidationError.tsx} +2 -1
- package/src/components/FormStation/index.ts +2 -5
- package/src/components/List/ListRow/Renderers/ExternalLinkRenderer/ExternalLinkRenderer.tsx +1 -1
- package/src/components/Tabs/Tab/CustomTab.scss +42 -0
- package/src/components/Tabs/Tab/CustomTab.tsx +34 -0
- package/src/components/Tabs/Tab/index.ts +1 -0
- package/src/components/Tabs/TabList/CustomTabList.scss +7 -0
- package/src/components/Tabs/TabList/CustomTabList.tsx +15 -0
- package/src/components/Tabs/TabList/ScrollContainer/ScrollContainer.scss +34 -0
- package/src/components/Tabs/TabList/ScrollContainer/ScrollContainer.tsx +39 -0
- package/src/components/Tabs/TabList/ScrollContainer/index.ts +1 -0
- package/src/components/Tabs/TabList/ScrollContainer/useScroll.ts +114 -0
- package/src/components/Tabs/TabList/index.ts +1 -0
- package/src/components/Tabs/TabPanel/CustomTabPanel.scss +10 -0
- package/src/components/Tabs/TabPanel/CustomTabPanel.tsx +26 -0
- package/src/components/Tabs/TabPanel/index.ts +1 -0
- package/src/components/Tabs/Tabs.stories.tsx +108 -0
- package/src/components/Tabs/index.ts +4 -0
- 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
|
@@ -1,51 +1,22 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
Formik,
|
|
5
|
-
FormikHelpers,
|
|
6
|
-
FormikValues,
|
|
7
|
-
useFormikContext,
|
|
8
|
-
} from 'formik';
|
|
9
|
-
import React, {
|
|
10
|
-
PropsWithChildren,
|
|
11
|
-
useCallback,
|
|
12
|
-
useEffect,
|
|
13
|
-
useMemo,
|
|
14
|
-
useRef,
|
|
15
|
-
useState,
|
|
16
|
-
} from 'react';
|
|
17
|
-
import { useHistory } from 'react-router-dom';
|
|
2
|
+
import { Formik, FormikValues } from 'formik';
|
|
3
|
+
import React, { PropsWithChildren } from 'react';
|
|
18
4
|
import { OptionalObjectSchema } from 'yup/lib/object';
|
|
19
|
-
import { SaveIndicatorType, setSaveIndicator } from '../../initialize';
|
|
20
5
|
import { Data } from '../../types/data';
|
|
21
|
-
import {
|
|
22
|
-
import { Actions, ActionsProps } from '../Actions';
|
|
23
|
-
import { isNavigationAction } from '../Actions/Action/Action';
|
|
24
|
-
import { IconName } from '../Icons';
|
|
25
|
-
import { MessageBar } from '../MessageBar';
|
|
6
|
+
import { StationMessage } from '../models';
|
|
26
7
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
import { FormActionData, InitialFormData } from './FormStation.models';
|
|
8
|
+
FormActionData,
|
|
9
|
+
InitialFormData,
|
|
10
|
+
ObjectSchemaDefinition,
|
|
11
|
+
SaveDataFunction,
|
|
12
|
+
} from './FormStation.models';
|
|
33
13
|
import classes from './FormStation.scss';
|
|
14
|
+
import { FormStationAction } from './FormStationActions';
|
|
15
|
+
import { FormStationContentWrapper } from './FormStationContentWrapper';
|
|
16
|
+
import { FormStationHeader } from './FormStationHeader';
|
|
34
17
|
import { SaveOnNavigate } from './SaveOnNavigate/SaveOnNavigate';
|
|
35
|
-
import {
|
|
36
|
-
import { useValidationError } from './useValidationError';
|
|
37
|
-
|
|
38
|
-
export type ObjectSchemaDefinition<
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
-
T extends Data | null = any,
|
|
41
|
-
> = {
|
|
42
|
-
// TODO: Adding 'any' here since there are a couple of open issues regarding generics in the latest version of yup.
|
|
43
|
-
// https://github.com/jquense/yup/issues/1159
|
|
44
|
-
// https://github.com/jquense/yup/issues/1247
|
|
45
|
-
// Consider revisiting once 'yup' has addressed these issues.
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
-
[field in keyof T]: any;
|
|
48
|
-
};
|
|
18
|
+
import { useDataProvider } from './helpers/useDataProvider';
|
|
19
|
+
import { useValidationError } from './helpers/useValidationError';
|
|
49
20
|
|
|
50
21
|
export interface FormStationProps<
|
|
51
22
|
TValues extends Data = FormikValues,
|
|
@@ -89,31 +60,11 @@ export interface FormStationProps<
|
|
|
89
60
|
* Called whenever the form needs to be saved.
|
|
90
61
|
* This method needs to throw an exception in case the saving did not succeed.
|
|
91
62
|
*/
|
|
92
|
-
saveData:
|
|
93
|
-
/** The current values of the form */
|
|
94
|
-
values: TValues,
|
|
95
|
-
/** The initial values of the form */
|
|
96
|
-
initialData: InitialFormData<TValues>,
|
|
97
|
-
/** The Formik state helpers */
|
|
98
|
-
formikHelpers: FormikHelpers<TValues>,
|
|
99
|
-
) => Promise<TSubmitResponse> | void;
|
|
63
|
+
saveData: SaveDataFunction<TValues, TSubmitResponse>;
|
|
100
64
|
/** CSS Class name for additional styles */
|
|
101
65
|
className?: string;
|
|
102
66
|
}
|
|
103
67
|
|
|
104
|
-
interface FormActionProps<T, Y> extends Omit<ActionsProps, 'actions'> {
|
|
105
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
-
validationSchema?: OptionalObjectSchema<ObjectSchemaDefinition<any>>;
|
|
107
|
-
actions?: FormActionData<T, Y>[];
|
|
108
|
-
setStationError: (error: StationErrorStateType) => void;
|
|
109
|
-
setValidationError: () => void;
|
|
110
|
-
submitResponse?: React.MutableRefObject<Y | undefined>;
|
|
111
|
-
alwaysSubmitBeforeAction?: boolean;
|
|
112
|
-
className?: string;
|
|
113
|
-
isFormSubmitting?: boolean;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
68
|
export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
118
69
|
titleProperty,
|
|
119
70
|
defaultTitle,
|
|
@@ -134,138 +85,19 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
134
85
|
}: PropsWithChildren<
|
|
135
86
|
FormStationProps<TValues, TSubmitResponse>
|
|
136
87
|
>): JSX.Element => {
|
|
137
|
-
const
|
|
88
|
+
const {
|
|
89
|
+
onSubmit,
|
|
90
|
+
stationError,
|
|
91
|
+
setStationError,
|
|
92
|
+
isFormSubmitting,
|
|
93
|
+
lastSubmittedResponse,
|
|
94
|
+
} = useDataProvider(initialData, saveData);
|
|
138
95
|
|
|
139
96
|
const { setValidationError, validationWatcher } = useValidationError(
|
|
140
97
|
stationError,
|
|
141
98
|
setStationError,
|
|
142
99
|
);
|
|
143
100
|
|
|
144
|
-
const submitResponse = useRef<TSubmitResponse>();
|
|
145
|
-
const [isFormSubmitting, setIsFormSubmitting] = useState<boolean>(false);
|
|
146
|
-
|
|
147
|
-
const onSubmit = useCallback(
|
|
148
|
-
async (
|
|
149
|
-
values: TValues,
|
|
150
|
-
formikHelpers: FormikHelpers<TValues>,
|
|
151
|
-
): Promise<void> => {
|
|
152
|
-
if (isFormSubmitting) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
setIsFormSubmitting(true);
|
|
158
|
-
setSaveIndicator(SaveIndicatorType.Saving);
|
|
159
|
-
setStationError(undefined);
|
|
160
|
-
if (!initialData.loading && saveData) {
|
|
161
|
-
const response = await saveData(values, initialData, formikHelpers);
|
|
162
|
-
if (response) {
|
|
163
|
-
submitResponse.current = response;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
} catch (error) {
|
|
167
|
-
setStationError(
|
|
168
|
-
ErrorTypeToStationError(
|
|
169
|
-
error as ErrorType,
|
|
170
|
-
'An error occurred when trying to save data.',
|
|
171
|
-
),
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
setSaveIndicator(SaveIndicatorType.Dirty);
|
|
175
|
-
|
|
176
|
-
// We still throw the error, to make sure that navigation or action execution
|
|
177
|
-
// will not continue after a failed save.
|
|
178
|
-
throw error;
|
|
179
|
-
} finally {
|
|
180
|
-
formikHelpers.setSubmitting(false);
|
|
181
|
-
setIsFormSubmitting(false);
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
[isFormSubmitting, initialData, saveData, setStationError],
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
useEffect(() => {
|
|
188
|
-
if (
|
|
189
|
-
initialData.error ||
|
|
190
|
-
(initialData.data === null && !initialData.loading) ||
|
|
191
|
-
initialData.entityNotFound
|
|
192
|
-
) {
|
|
193
|
-
const stationError = {
|
|
194
|
-
...ErrorTypeToStationError(
|
|
195
|
-
initialData.error,
|
|
196
|
-
'An error occurred when trying to load data.',
|
|
197
|
-
'Entity not found',
|
|
198
|
-
),
|
|
199
|
-
type: 'loading',
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
setStationError(stationError);
|
|
203
|
-
} else {
|
|
204
|
-
if (stationError?.type === 'loading') {
|
|
205
|
-
// Only clear the error if it is a loading error, which now seems to be cleared.
|
|
206
|
-
setStationError(undefined);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}, [
|
|
210
|
-
initialData.loading,
|
|
211
|
-
initialData.error,
|
|
212
|
-
initialData.entityNotFound,
|
|
213
|
-
initialData.data,
|
|
214
|
-
stationError?.type,
|
|
215
|
-
]);
|
|
216
|
-
|
|
217
|
-
const getContent = (): JSX.Element | undefined => {
|
|
218
|
-
if (initialData.loading) {
|
|
219
|
-
// TODO: Loading skeleton of the page
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (
|
|
224
|
-
initialData.error ||
|
|
225
|
-
initialData.data === null ||
|
|
226
|
-
initialData.entityNotFound
|
|
227
|
-
) {
|
|
228
|
-
// Error on loading - we can't show the form
|
|
229
|
-
return <div className={classes.loadingError}></div>;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Loading successful
|
|
233
|
-
return (
|
|
234
|
-
<>
|
|
235
|
-
<SaveOnNavigate
|
|
236
|
-
isSubmitting={isFormSubmitting}
|
|
237
|
-
onNavigationCancelled={setValidationError}
|
|
238
|
-
/>
|
|
239
|
-
<div className={classes.main}>
|
|
240
|
-
<div
|
|
241
|
-
className={clsx(classes.formWrapper, {
|
|
242
|
-
[classes.hasMessage]: stationMessage,
|
|
243
|
-
})}
|
|
244
|
-
>
|
|
245
|
-
{stationMessage && (
|
|
246
|
-
<MessageBar {...stationMessage}>{stationMessage.body}</MessageBar>
|
|
247
|
-
)}
|
|
248
|
-
<div
|
|
249
|
-
className={clsx(classes.formContainer, {
|
|
250
|
-
[classes.paddedContent]: !edgeToEdgeContent,
|
|
251
|
-
})}
|
|
252
|
-
>
|
|
253
|
-
<Form>
|
|
254
|
-
<>
|
|
255
|
-
{children}
|
|
256
|
-
{/* Adding a invisible text input here to prevent the browser from submitting on "Enter" when there is only a single text input field in the form
|
|
257
|
-
See: https://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2 */}
|
|
258
|
-
<input type="text" style={{ display: 'none' }} />
|
|
259
|
-
</>
|
|
260
|
-
</Form>
|
|
261
|
-
</div>
|
|
262
|
-
</div>
|
|
263
|
-
{infoPanel}
|
|
264
|
-
</div>
|
|
265
|
-
</>
|
|
266
|
-
);
|
|
267
|
-
};
|
|
268
|
-
|
|
269
101
|
return (
|
|
270
102
|
<div
|
|
271
103
|
className={clsx(
|
|
@@ -290,217 +122,36 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
|
|
|
290
122
|
defaultTitle={defaultTitle}
|
|
291
123
|
subtitle={subtitle}
|
|
292
124
|
cancelNavigationUrl={cancelNavigationUrl}
|
|
125
|
+
className={classes.header}
|
|
293
126
|
/>
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
127
|
+
<SaveOnNavigate
|
|
128
|
+
isSubmitting={isFormSubmitting}
|
|
129
|
+
onNavigationCancelled={setValidationError}
|
|
130
|
+
/>
|
|
131
|
+
<FormStationContentWrapper
|
|
132
|
+
stationMessage={stationMessage}
|
|
133
|
+
edgeToEdgeContent={edgeToEdgeContent}
|
|
134
|
+
infoPanel={infoPanel}
|
|
135
|
+
initialData={initialData}
|
|
136
|
+
stationError={stationError}
|
|
137
|
+
setStationError={setStationError}
|
|
138
|
+
>
|
|
139
|
+
{children}
|
|
140
|
+
</FormStationContentWrapper>
|
|
141
|
+
{(alwaysShowActionsPanel || (actions?.length ?? 0) > 0) && (
|
|
307
142
|
<FormStationAction<TValues, TSubmitResponse>
|
|
308
143
|
actions={actions}
|
|
309
144
|
width={actionsWidth}
|
|
310
145
|
validationSchema={validationSchema}
|
|
311
146
|
setStationError={setStationError}
|
|
312
147
|
setValidationError={setValidationError}
|
|
313
|
-
submitResponse={
|
|
148
|
+
submitResponse={lastSubmittedResponse}
|
|
314
149
|
alwaysSubmitBeforeAction={alwaysSubmitBeforeAction}
|
|
315
150
|
className={classes.actionsPanel}
|
|
316
151
|
/>
|
|
317
|
-
)
|
|
152
|
+
)}
|
|
318
153
|
</>
|
|
319
154
|
</Formik>
|
|
320
155
|
</div>
|
|
321
156
|
);
|
|
322
157
|
};
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Saves the form before the action is performed.
|
|
326
|
-
*/
|
|
327
|
-
const FormStationAction = <T, Y>(
|
|
328
|
-
props: PropsWithChildren<FormActionProps<T, Y>>,
|
|
329
|
-
): JSX.Element => {
|
|
330
|
-
const {
|
|
331
|
-
actions,
|
|
332
|
-
validationSchema,
|
|
333
|
-
setStationError,
|
|
334
|
-
setValidationError,
|
|
335
|
-
submitResponse,
|
|
336
|
-
alwaysSubmitBeforeAction,
|
|
337
|
-
className = '',
|
|
338
|
-
} = props;
|
|
339
|
-
const {
|
|
340
|
-
submitForm,
|
|
341
|
-
resetForm,
|
|
342
|
-
values,
|
|
343
|
-
validateForm,
|
|
344
|
-
isValid,
|
|
345
|
-
dirty,
|
|
346
|
-
isSubmitting,
|
|
347
|
-
} = useFormikContext<T>();
|
|
348
|
-
|
|
349
|
-
const updatedActions = useMemo(() => {
|
|
350
|
-
return actions?.map((action) => {
|
|
351
|
-
const { onActionSelected } = action;
|
|
352
|
-
|
|
353
|
-
return isNavigationAction(action)
|
|
354
|
-
? action
|
|
355
|
-
: {
|
|
356
|
-
...action,
|
|
357
|
-
isDisabled: action.isDisabled || isSubmitting,
|
|
358
|
-
onActionSelected: () => {
|
|
359
|
-
(async () => {
|
|
360
|
-
//TODO: Busy indicator (disable form?)
|
|
361
|
-
if (dirty || alwaysSubmitBeforeAction) {
|
|
362
|
-
// We can't rely on 'isValid' alone, since that will only evaluate if the form is touched or 'submitForm' is called.
|
|
363
|
-
// On a create station though the from might not be touched but still being invalid.
|
|
364
|
-
// Also the validation on submitForm in here will not help, since the value of isValid is already bound to that method and will not change
|
|
365
|
-
// anymore while this method executes - even if it changes on the context!
|
|
366
|
-
if (
|
|
367
|
-
!isValid ||
|
|
368
|
-
(await validationSchema?.isValid(values)) === false
|
|
369
|
-
) {
|
|
370
|
-
setValidationError();
|
|
371
|
-
// Making sure that the fields will actually show the validation messages (they won't if the from was not touched yet - e.g. on a create station)
|
|
372
|
-
validateForm();
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
try {
|
|
377
|
-
await submitForm();
|
|
378
|
-
|
|
379
|
-
// Committing the changed values to the form to get the current version as the new "reset" state.
|
|
380
|
-
resetForm({ values });
|
|
381
|
-
} catch (error) {
|
|
382
|
-
// we will abort the action if saving is not successful
|
|
383
|
-
// a station error is already set by the "onSubmit"
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
try {
|
|
389
|
-
const result =
|
|
390
|
-
onActionSelected &&
|
|
391
|
-
(await onActionSelected({
|
|
392
|
-
values,
|
|
393
|
-
submitResponse: submitResponse?.current,
|
|
394
|
-
}));
|
|
395
|
-
if (result !== undefined) {
|
|
396
|
-
setStationError(
|
|
397
|
-
ErrorTypeToStationError(
|
|
398
|
-
result,
|
|
399
|
-
'An error occurred when trying to execute the action.',
|
|
400
|
-
),
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
} catch (error) {
|
|
404
|
-
const stationError = ErrorTypeToStationError(
|
|
405
|
-
error as ErrorType,
|
|
406
|
-
'An error occurred when trying to execute the action.',
|
|
407
|
-
);
|
|
408
|
-
setStationError(stationError);
|
|
409
|
-
}
|
|
410
|
-
})();
|
|
411
|
-
},
|
|
412
|
-
};
|
|
413
|
-
});
|
|
414
|
-
}, [
|
|
415
|
-
actions,
|
|
416
|
-
alwaysSubmitBeforeAction,
|
|
417
|
-
dirty,
|
|
418
|
-
isSubmitting,
|
|
419
|
-
isValid,
|
|
420
|
-
resetForm,
|
|
421
|
-
setStationError,
|
|
422
|
-
setValidationError,
|
|
423
|
-
submitForm,
|
|
424
|
-
submitResponse,
|
|
425
|
-
validateForm,
|
|
426
|
-
validationSchema,
|
|
427
|
-
values,
|
|
428
|
-
]);
|
|
429
|
-
|
|
430
|
-
return (
|
|
431
|
-
<div className={className}>
|
|
432
|
-
<Actions actions={updatedActions} />
|
|
433
|
-
</div>
|
|
434
|
-
);
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Handles showRefresh and cancel buttons based on form states
|
|
439
|
-
*/
|
|
440
|
-
const FormStationHeader: React.FC<
|
|
441
|
-
Omit<PageHeaderProps, 'title'> & {
|
|
442
|
-
titleProperty?: string;
|
|
443
|
-
defaultTitle?: string;
|
|
444
|
-
cancelNavigationUrl?: string;
|
|
445
|
-
}
|
|
446
|
-
> = ({ titleProperty, defaultTitle, subtitle, cancelNavigationUrl }) => {
|
|
447
|
-
const { dirty, resetForm, values } = useFormikContext<FormikValues>();
|
|
448
|
-
|
|
449
|
-
useEffect(() => {
|
|
450
|
-
// Set the save indicator to dirty depending on the form state
|
|
451
|
-
if (dirty) {
|
|
452
|
-
setSaveIndicator(SaveIndicatorType.Dirty);
|
|
453
|
-
} else {
|
|
454
|
-
setSaveIndicator(SaveIndicatorType.Inactive);
|
|
455
|
-
}
|
|
456
|
-
return () => {
|
|
457
|
-
// The form is not always considered "not dirty" after the save
|
|
458
|
-
// so this code will make sure that the indicator is set to inactive
|
|
459
|
-
// when the station is left.
|
|
460
|
-
setSaveIndicator(SaveIndicatorType.Inactive);
|
|
461
|
-
};
|
|
462
|
-
}, [dirty]);
|
|
463
|
-
|
|
464
|
-
const history = useHistory();
|
|
465
|
-
|
|
466
|
-
const title =
|
|
467
|
-
titleProperty && values[titleProperty] !== ''
|
|
468
|
-
? values[titleProperty]
|
|
469
|
-
: defaultTitle ?? '';
|
|
470
|
-
|
|
471
|
-
return (
|
|
472
|
-
<PageHeader
|
|
473
|
-
title={title}
|
|
474
|
-
subtitle={subtitle}
|
|
475
|
-
actions={[
|
|
476
|
-
...(dirty === true // add undo action if form as been altered
|
|
477
|
-
? [
|
|
478
|
-
{
|
|
479
|
-
label: 'Undo Changes',
|
|
480
|
-
icon: IconName.Undo,
|
|
481
|
-
actionType: PageHeaderActionType.Context,
|
|
482
|
-
onClick: () => {
|
|
483
|
-
resetForm();
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
]
|
|
487
|
-
: []),
|
|
488
|
-
...(cancelNavigationUrl // add cancel action if applicable
|
|
489
|
-
? [
|
|
490
|
-
{
|
|
491
|
-
label: 'Cancel',
|
|
492
|
-
icon: IconName.X,
|
|
493
|
-
onClick: () => {
|
|
494
|
-
resetForm();
|
|
495
|
-
// If the form has errors, Navigation needs to be wrapped in a promise or timeout.
|
|
496
|
-
Promise.resolve().then(() =>
|
|
497
|
-
history.push(cancelNavigationUrl),
|
|
498
|
-
);
|
|
499
|
-
},
|
|
500
|
-
},
|
|
501
|
-
]
|
|
502
|
-
: []),
|
|
503
|
-
]}
|
|
504
|
-
/>
|
|
505
|
-
);
|
|
506
|
-
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useFormikContext } from 'formik';
|
|
2
|
+
import React, { PropsWithChildren, useMemo } from 'react';
|
|
3
|
+
import { OptionalObjectSchema } from 'yup/lib/object';
|
|
4
|
+
import { ObjectSchemaDefinition } from '..';
|
|
5
|
+
import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
|
|
6
|
+
import { Actions, ActionsProps } from '../../Actions';
|
|
7
|
+
import { isNavigationAction } from '../../Actions/Action/Action';
|
|
8
|
+
import { ErrorType } from '../../models';
|
|
9
|
+
import { FormActionData, StationErrorStateType } from '../FormStation.models';
|
|
10
|
+
|
|
11
|
+
interface FormActionProps<T, Y> extends Omit<ActionsProps, 'actions'> {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
validationSchema?: OptionalObjectSchema<ObjectSchemaDefinition<any>>;
|
|
14
|
+
actions?: FormActionData<T, Y>[];
|
|
15
|
+
setStationError: (error: StationErrorStateType) => void;
|
|
16
|
+
setValidationError: () => void;
|
|
17
|
+
submitResponse?: React.MutableRefObject<Y | undefined>;
|
|
18
|
+
alwaysSubmitBeforeAction?: boolean;
|
|
19
|
+
className?: string;
|
|
20
|
+
isFormSubmitting?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Saves the form before the action is performed.
|
|
25
|
+
*/
|
|
26
|
+
export const FormStationAction = <T, Y>(
|
|
27
|
+
props: PropsWithChildren<FormActionProps<T, Y>>,
|
|
28
|
+
): JSX.Element => {
|
|
29
|
+
const {
|
|
30
|
+
actions,
|
|
31
|
+
validationSchema,
|
|
32
|
+
setStationError,
|
|
33
|
+
setValidationError,
|
|
34
|
+
submitResponse,
|
|
35
|
+
alwaysSubmitBeforeAction,
|
|
36
|
+
className = '',
|
|
37
|
+
} = props;
|
|
38
|
+
const {
|
|
39
|
+
submitForm,
|
|
40
|
+
resetForm,
|
|
41
|
+
values,
|
|
42
|
+
validateForm,
|
|
43
|
+
isValid,
|
|
44
|
+
dirty,
|
|
45
|
+
isSubmitting,
|
|
46
|
+
} = useFormikContext<T>();
|
|
47
|
+
|
|
48
|
+
const updatedActions = useMemo(() => {
|
|
49
|
+
return actions?.map((action) => {
|
|
50
|
+
const { onActionSelected } = action;
|
|
51
|
+
|
|
52
|
+
return isNavigationAction(action)
|
|
53
|
+
? action
|
|
54
|
+
: {
|
|
55
|
+
...action,
|
|
56
|
+
isDisabled: action.isDisabled || isSubmitting,
|
|
57
|
+
onActionSelected: () => {
|
|
58
|
+
(async () => {
|
|
59
|
+
//TODO: Busy indicator (disable form?)
|
|
60
|
+
if (dirty || alwaysSubmitBeforeAction) {
|
|
61
|
+
// We can't rely on 'isValid' alone, since that will only evaluate if the form is touched or 'submitForm' is called.
|
|
62
|
+
// On a create station though the from might not be touched but still being invalid.
|
|
63
|
+
// Also the validation on submitForm in here will not help, since the value of isValid is already bound to that method and will not change
|
|
64
|
+
// anymore while this method executes - even if it changes on the context!
|
|
65
|
+
if (
|
|
66
|
+
!isValid ||
|
|
67
|
+
(await validationSchema?.isValid(values)) === false
|
|
68
|
+
) {
|
|
69
|
+
setValidationError();
|
|
70
|
+
// Making sure that the fields will actually show the validation messages (they won't if the from was not touched yet - e.g. on a create station)
|
|
71
|
+
validateForm();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await submitForm();
|
|
77
|
+
|
|
78
|
+
// Committing the changed values to the form to get the current version as the new "reset" state.
|
|
79
|
+
resetForm({ values });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
// we will abort the action if saving is not successful
|
|
82
|
+
// a station error is already set by the "onSubmit"
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const result =
|
|
89
|
+
onActionSelected &&
|
|
90
|
+
(await onActionSelected({
|
|
91
|
+
values,
|
|
92
|
+
submitResponse: submitResponse?.current,
|
|
93
|
+
}));
|
|
94
|
+
if (result !== undefined) {
|
|
95
|
+
setStationError(
|
|
96
|
+
ErrorTypeToStationError(
|
|
97
|
+
result,
|
|
98
|
+
'An error occurred when trying to execute the action.',
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const stationError = ErrorTypeToStationError(
|
|
104
|
+
error as ErrorType,
|
|
105
|
+
'An error occurred when trying to execute the action.',
|
|
106
|
+
);
|
|
107
|
+
setStationError(stationError);
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}, [
|
|
114
|
+
actions,
|
|
115
|
+
alwaysSubmitBeforeAction,
|
|
116
|
+
dirty,
|
|
117
|
+
isSubmitting,
|
|
118
|
+
isValid,
|
|
119
|
+
resetForm,
|
|
120
|
+
setStationError,
|
|
121
|
+
setValidationError,
|
|
122
|
+
submitForm,
|
|
123
|
+
submitResponse,
|
|
124
|
+
validateForm,
|
|
125
|
+
validationSchema,
|
|
126
|
+
values,
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
return <Actions actions={updatedActions} className={className} />;
|
|
130
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './FormStationActions';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
@import '../../../styles/common.scss';
|
|
2
|
+
|
|
3
|
+
// Extend all components, except PageHeader, to the bottom
|
|
4
|
+
// Enable scrolling children
|
|
5
|
+
.children {
|
|
6
|
+
overflow-y: auto;
|
|
7
|
+
display: grid;
|
|
8
|
+
grid-template-columns: 1fr auto;
|
|
9
|
+
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
|
|
12
|
+
.main {
|
|
13
|
+
display: grid;
|
|
14
|
+
grid: 1fr / minmax(740px, 1fr) auto;
|
|
15
|
+
|
|
16
|
+
overflow-y: auto;
|
|
17
|
+
scrollbar-width: thin; //for Firefox only
|
|
18
|
+
|
|
19
|
+
.formWrapper {
|
|
20
|
+
display: grid;
|
|
21
|
+
grid: 1fr / 1fr;
|
|
22
|
+
|
|
23
|
+
&.hasMessage {
|
|
24
|
+
grid: min-content 1fr / 1fr;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//Custom scrollbar for Chrome, Safari and Edge
|
|
30
|
+
::-webkit-scrollbar {
|
|
31
|
+
width: var(--scrollbar-size, $scrollbar-size);
|
|
32
|
+
height: var(--scrollbar-size, $scrollbar-size);
|
|
33
|
+
}
|
|
34
|
+
::-webkit-scrollbar-track {
|
|
35
|
+
background: var(--scrollbar-track-color, $scrollbar-track-color);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
::-webkit-scrollbar-thumb {
|
|
39
|
+
background: var(--scrollbar-thumb-color, $scrollbar-thumb-color);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
::-webkit-scrollbar-thumb:hover {
|
|
43
|
+
background: var(
|
|
44
|
+
--scrollbar-thumb-hover-color,
|
|
45
|
+
$scrollbar-thumb-hover-color
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
::-webkit-scrollbar-corner {
|
|
50
|
+
background: var(--scrollbar-corner-color, $scrollbar-corner-color);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.formContainer {
|
|
54
|
+
padding: 30px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.loadingError {
|
|
58
|
+
display: grid;
|
|
59
|
+
grid: 1fr / 1fr;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.errorMessage {
|
|
64
|
+
grid-row: 2 / 3;
|
|
65
|
+
grid-column: 1 / -1;
|
|
66
|
+
}
|