@abgov/nx-adsp 5.9.0-beta.5 → 5.9.0-beta.6
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/package.json +1 -1
- package/src/generators/react-form/files/__fileName__/__fileName__.module.css__tmpl__ +10 -0
- package/src/generators/react-form/files/__fileName__/__fileName__.slice.ts__tmpl__ +209 -5
- package/src/generators/react-form/files/__fileName__/__fileName__.tsx__tmpl__ +50 -1
- package/src/generators/react-form/includes/input-template.ejs +26 -0
- package/src/generators/react-form/react-form.js +25 -2
- package/src/generators/react-form/react-form.js.map +1 -1
- package/src/generators/react-form/react-forms.spec.ts +7 -2
- package/src/utils/form.d.ts +2 -0
- package/src/utils/form.js.map +1 -1
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
> fieldset {
|
|
5
5
|
display: none;
|
|
6
|
+
padding-bottom: 64px;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
> fieldset.show {
|
|
@@ -10,6 +11,15 @@
|
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
.formFile {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
|
|
18
|
+
> a {
|
|
19
|
+
display: none;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
.formActions {
|
|
14
24
|
display: flex;
|
|
15
25
|
margin-top: 16px;
|
|
@@ -13,6 +13,9 @@ import { CONFIG_FEATURE_KEY, ConfigState } from '../config.slice';
|
|
|
13
13
|
export const <%= constantName %>_FEATURE_KEY = '<%= fileName %>';
|
|
14
14
|
const FORM_DEFINITION_ID = '<%= id %>';
|
|
15
15
|
const FORM_SERVICE_ID = 'urn:ads:platform:form-service';
|
|
16
|
+
const FILE_TYPE_ID = '<%= fileName %>-files';
|
|
17
|
+
const FILE_SERVICE_ID = 'urn:ads:platform:file-service';
|
|
18
|
+
const FILE_SERVICE_RESOURCE_PREFIX = `${FILE_SERVICE_ID}:v1:`;
|
|
16
19
|
|
|
17
20
|
/*
|
|
18
21
|
* Update these interfaces according to your requirements.
|
|
@@ -38,15 +41,26 @@ const required: Record<string, string[]> = {
|
|
|
38
41
|
type FormStatus = 'draft' | 'locked' | 'submitted';
|
|
39
42
|
type SectionCompletion = 'complete' | 'incomplete' | null;
|
|
40
43
|
|
|
44
|
+
export interface FileInformation {
|
|
45
|
+
urn?: string;
|
|
46
|
+
filename: string;
|
|
47
|
+
progress: number;
|
|
48
|
+
size: number;
|
|
49
|
+
dataUri?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
export interface <%= className %>State {
|
|
42
53
|
formId: string;
|
|
43
54
|
status: FormStatus;
|
|
44
55
|
step: number;
|
|
45
56
|
values: <%= className %>;
|
|
57
|
+
files: Record<string, FileInformation>;
|
|
46
58
|
errors: Record<string, Record<string, boolean>>;
|
|
47
59
|
complete: Record<string, SectionCompletion>;
|
|
48
60
|
busy: {
|
|
49
61
|
loading: boolean;
|
|
62
|
+
loadingFiles: boolean;
|
|
63
|
+
uploadingFile: boolean;
|
|
50
64
|
saving: boolean;
|
|
51
65
|
submitting: boolean;
|
|
52
66
|
};
|
|
@@ -88,6 +102,13 @@ function areSectionsComplete(
|
|
|
88
102
|
return complete;
|
|
89
103
|
}
|
|
90
104
|
|
|
105
|
+
function getFileUrl(directory: Record<string, string>, urn: string): URL {
|
|
106
|
+
return new URL(
|
|
107
|
+
urn.replace(FILE_SERVICE_RESOURCE_PREFIX, 'v1'),
|
|
108
|
+
`${directory[FILE_SERVICE_ID]}/file/v1`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
91
112
|
/**
|
|
92
113
|
* Export an effect using createAsyncThunk from
|
|
93
114
|
* the Redux Toolkit: https://redux-toolkit.js.org/api/createAsyncThunk
|
|
@@ -107,7 +128,7 @@ function areSectionsComplete(
|
|
|
107
128
|
*/
|
|
108
129
|
export const initializeForm = createAsyncThunk(
|
|
109
130
|
'<%= propertyName %>/initialize',
|
|
110
|
-
async (_, { getState }) => {
|
|
131
|
+
async (_, { dispatch, getState }) => {
|
|
111
132
|
const state = getState();
|
|
112
133
|
const { user }: UserState = state['user'];
|
|
113
134
|
if (!user) {
|
|
@@ -160,18 +181,80 @@ export const initializeForm = createAsyncThunk(
|
|
|
160
181
|
const { data } = await axios.get<{
|
|
161
182
|
id: string;
|
|
162
183
|
data: <%= className %>;
|
|
184
|
+
files: Record<string, string>;
|
|
163
185
|
}>(`${directory[FORM_SERVICE_ID]}/form/v1/forms/${form.id}/data`, {
|
|
164
186
|
headers: {
|
|
165
187
|
Authorization: `Bearer ${user.access_token}`,
|
|
166
188
|
},
|
|
167
189
|
});
|
|
168
190
|
|
|
191
|
+
dispatch(getFileMetadata(data.files));
|
|
192
|
+
|
|
169
193
|
const complete = areSectionsComplete(data.data);
|
|
170
194
|
|
|
171
195
|
return { ...form, ...data, complete };
|
|
172
196
|
}
|
|
173
197
|
);
|
|
174
198
|
|
|
199
|
+
export const getFileMetadata = createAsyncThunk(
|
|
200
|
+
'<%= propertyName %>/get-file-metadata',
|
|
201
|
+
async (files: Record<string, string>, { getState }) => {
|
|
202
|
+
const state = getState();
|
|
203
|
+
const { user }: UserState = state['user'];
|
|
204
|
+
const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
|
|
205
|
+
|
|
206
|
+
const metadata: Record<string, FileInformation> = {};
|
|
207
|
+
for (const [key, urn] of Object.entries(files)) {
|
|
208
|
+
const { data } = await axios.get<FileInformation>(
|
|
209
|
+
getFileUrl(directory, urn).href,
|
|
210
|
+
{
|
|
211
|
+
headers: { Authorization: `Bearer ${user.access_token}` },
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
metadata[key] = {
|
|
216
|
+
urn: data.urn,
|
|
217
|
+
filename: data.filename,
|
|
218
|
+
size: data.size,
|
|
219
|
+
progress: 100,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return metadata;
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
export const downloadFile = createAsyncThunk(
|
|
228
|
+
'<%= propertyName %>/download-file',
|
|
229
|
+
async (
|
|
230
|
+
{ key, file }: { key: string; file: FileInformation },
|
|
231
|
+
{ getState }
|
|
232
|
+
) => {
|
|
233
|
+
const state = getState();
|
|
234
|
+
const { user }: UserState = state['user'];
|
|
235
|
+
const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
|
|
236
|
+
|
|
237
|
+
let uri = file.dataUri;
|
|
238
|
+
if (!uri) {
|
|
239
|
+
const { data } = await axios.get(
|
|
240
|
+
`${getFileUrl(directory, file.urn).href}/download`,
|
|
241
|
+
{
|
|
242
|
+
responseType: 'arraybuffer',
|
|
243
|
+
headers: { Authorization: `Bearer ${user.access_token}` },
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
uri = URL.createObjectURL(new Blob([data]));
|
|
247
|
+
|
|
248
|
+
const anchor = document.createElement('a');
|
|
249
|
+
anchor.href = uri;
|
|
250
|
+
anchor.setAttribute('download', file.filename);
|
|
251
|
+
anchor.click();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return uri;
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
|
|
175
258
|
const formSaved = createAction(
|
|
176
259
|
'<%= propertyName %>/saved',
|
|
177
260
|
(values: <%= className %>) => ({ payload: values })
|
|
@@ -185,7 +268,13 @@ const formSaveRejected = createAction(
|
|
|
185
268
|
const queueSaveForm = createAsyncThunk(
|
|
186
269
|
'<%= propertyName %>/queue-save',
|
|
187
270
|
debounce(
|
|
188
|
-
async (
|
|
271
|
+
async (
|
|
272
|
+
{
|
|
273
|
+
values,
|
|
274
|
+
files,
|
|
275
|
+
}: { values: <%= className %>; files: Record<string, FileInformation> },
|
|
276
|
+
{ dispatch, getState }
|
|
277
|
+
) => {
|
|
189
278
|
const state = getState();
|
|
190
279
|
const { user }: UserState = state['user'];
|
|
191
280
|
const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
|
|
@@ -194,7 +283,16 @@ const queueSaveForm = createAsyncThunk(
|
|
|
194
283
|
try {
|
|
195
284
|
await axios.put(
|
|
196
285
|
`${directory[FORM_SERVICE_ID]}/form/v1/forms/${formId}/data`,
|
|
197
|
-
{
|
|
286
|
+
{
|
|
287
|
+
data: values,
|
|
288
|
+
files: Object.entries(files).reduce((fileValues, [key, file]) => {
|
|
289
|
+
if (file) {
|
|
290
|
+
fileValues[key] = file.urn;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return fileValues;
|
|
294
|
+
}, {} as Record<string, string>),
|
|
295
|
+
},
|
|
198
296
|
{ headers: { Authorization: `Bearer ${user.access_token}` } }
|
|
199
297
|
);
|
|
200
298
|
|
|
@@ -215,7 +313,7 @@ const queueSaveForm = createAsyncThunk(
|
|
|
215
313
|
|
|
216
314
|
export const updateForm = createAsyncThunk(
|
|
217
315
|
'<%= propertyName %>/update',
|
|
218
|
-
async (values: <%= className %>, { dispatch }) => {
|
|
316
|
+
async (values: <%= className %>, { dispatch, getState }) => {
|
|
219
317
|
const errors: Record<string, Record<string, boolean>> = {};
|
|
220
318
|
let hasError = false;
|
|
221
319
|
Object.entries(rules).forEach(([section, sectionRules]) => {
|
|
@@ -236,13 +334,73 @@ export const updateForm = createAsyncThunk(
|
|
|
236
334
|
const complete = areSectionsComplete(values, errors);
|
|
237
335
|
|
|
238
336
|
if (!hasError) {
|
|
239
|
-
|
|
337
|
+
const { files }: <%= className %>State = getState()[<%= constantName %>_FEATURE_KEY];
|
|
338
|
+
dispatch(queueSaveForm({ values, files }));
|
|
240
339
|
}
|
|
241
340
|
|
|
242
341
|
return { values, complete, errors };
|
|
243
342
|
}
|
|
244
343
|
);
|
|
245
344
|
|
|
345
|
+
const setFileProgress = createAction(
|
|
346
|
+
'<%= propertyName %>/set-file-progress',
|
|
347
|
+
(key: string, progress: number) => {
|
|
348
|
+
return { payload: { key, progress } };
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
export const updateFile = createAsyncThunk(
|
|
353
|
+
'<%= propertyName %>/update-file',
|
|
354
|
+
async (
|
|
355
|
+
{ key, old, file }: { key: string; old?: FileInformation; file?: File },
|
|
356
|
+
{ dispatch, getState }
|
|
357
|
+
) => {
|
|
358
|
+
const state = getState();
|
|
359
|
+
const { user }: UserState = state['user'];
|
|
360
|
+
const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
|
|
361
|
+
const { formId }: <%= className %>State = state[<%= constantName %>_FEATURE_KEY];
|
|
362
|
+
|
|
363
|
+
let newFile: FileInformation = null;
|
|
364
|
+
if (file) {
|
|
365
|
+
const formData = new FormData();
|
|
366
|
+
formData.append('type', FILE_TYPE_ID);
|
|
367
|
+
formData.append('recordId', formId);
|
|
368
|
+
formData.append('file', file);
|
|
369
|
+
|
|
370
|
+
const { data } = await axios.post<Omit<FileInformation, 'progress'>>(
|
|
371
|
+
`${directory[FILE_SERVICE_ID]}/file/v1/files`,
|
|
372
|
+
formData,
|
|
373
|
+
{
|
|
374
|
+
headers: { Authorization: `Bearer ${user.access_token}` },
|
|
375
|
+
onUploadProgress: ({ loaded, total }: ProgressEvent) => {
|
|
376
|
+
const progress = Math.floor((loaded * 100) / total);
|
|
377
|
+
dispatch(setFileProgress(key, progress));
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
newFile = {
|
|
383
|
+
urn: data.urn,
|
|
384
|
+
filename: data.filename,
|
|
385
|
+
size: data.size,
|
|
386
|
+
progress: 100,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (old?.urn) {
|
|
391
|
+
await axios.delete(getFileUrl(directory, old.urn).href, {
|
|
392
|
+
headers: { Authorization: `Bearer ${user.access_token}` },
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const { values, files }: <%= className %>State = getState()[<%= constantName %>_FEATURE_KEY];
|
|
397
|
+
|
|
398
|
+
dispatch(queueSaveForm({ values, files: { ...files, [key]: newFile } }));
|
|
399
|
+
|
|
400
|
+
return { key, file: newFile };
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
|
|
246
404
|
export const submitForm = createAsyncThunk(
|
|
247
405
|
'<%= propertyName %>/submit',
|
|
248
406
|
async (_, { getState }) => {
|
|
@@ -269,10 +427,13 @@ export const initial<%= className %>State: <%= className %>State = {
|
|
|
269
427
|
status: null,
|
|
270
428
|
step: 1,
|
|
271
429
|
values: {} as <%= className %>,
|
|
430
|
+
files: {},
|
|
272
431
|
errors: {},
|
|
273
432
|
complete: {},
|
|
274
433
|
busy: {
|
|
275
434
|
loading: false,
|
|
435
|
+
loadingFiles: false,
|
|
436
|
+
uploadingFile: false,
|
|
276
437
|
saving: false,
|
|
277
438
|
submitting: false,
|
|
278
439
|
},
|
|
@@ -302,11 +463,49 @@ export const <%= propertyName %>Slice = createSlice({
|
|
|
302
463
|
.addCase(initializeForm.rejected, (state) => {
|
|
303
464
|
state.busy.loading = false;
|
|
304
465
|
})
|
|
466
|
+
.addCase(getFileMetadata.pending, (state) => {
|
|
467
|
+
state.busy.loadingFiles = true;
|
|
468
|
+
})
|
|
469
|
+
.addCase(getFileMetadata.fulfilled, (state, action) => {
|
|
470
|
+
state.busy.loadingFiles = false;
|
|
471
|
+
state.files = action.payload;
|
|
472
|
+
})
|
|
473
|
+
.addCase(getFileMetadata.rejected, (state) => {
|
|
474
|
+
state.busy.loadingFiles = false;
|
|
475
|
+
})
|
|
476
|
+
.addCase(downloadFile.fulfilled, (state, action) => {
|
|
477
|
+
if (state.files[action.meta.arg.key]) {
|
|
478
|
+
state.files[action.meta.arg.key].dataUri = action.payload;
|
|
479
|
+
}
|
|
480
|
+
})
|
|
305
481
|
.addCase(updateForm.fulfilled, (state, action) => {
|
|
306
482
|
state.values = action.payload.values;
|
|
307
483
|
state.complete = action.payload.complete;
|
|
308
484
|
state.errors = action.payload.errors;
|
|
309
485
|
})
|
|
486
|
+
.addCase(updateFile.pending, (state, action) => {
|
|
487
|
+
const file = action.meta.arg.file;
|
|
488
|
+
state.busy.uploadingFile = true;
|
|
489
|
+
state.files[action.meta.arg.key] = file
|
|
490
|
+
? {
|
|
491
|
+
filename: file.name,
|
|
492
|
+
size: file.size,
|
|
493
|
+
progress: 0,
|
|
494
|
+
}
|
|
495
|
+
: null;
|
|
496
|
+
})
|
|
497
|
+
.addCase(updateFile.fulfilled, (state, action) => {
|
|
498
|
+
state.busy.uploadingFile = false;
|
|
499
|
+
state.files[action.payload.key] = action.payload.file;
|
|
500
|
+
})
|
|
501
|
+
.addCase(updateFile.rejected, (state) => {
|
|
502
|
+
state.busy.uploadingFile = false;
|
|
503
|
+
})
|
|
504
|
+
.addCase(setFileProgress, (state, action) => {
|
|
505
|
+
if (state.files[action.payload.key]) {
|
|
506
|
+
state.files[action.payload.key].progress = action.payload.progress;
|
|
507
|
+
}
|
|
508
|
+
})
|
|
310
509
|
.addCase(queueSaveForm.pending, (state) => {
|
|
311
510
|
state.busy.saving = true;
|
|
312
511
|
})
|
|
@@ -393,6 +592,11 @@ export const getFormValues = createSelector(
|
|
|
393
592
|
(state) => state.values
|
|
394
593
|
);
|
|
395
594
|
|
|
595
|
+
export const getFormFiles = createSelector(
|
|
596
|
+
get<%= className %>State,
|
|
597
|
+
(state) => state.files
|
|
598
|
+
);
|
|
599
|
+
|
|
396
600
|
export const getFormErrors = createSelector(
|
|
397
601
|
get<%= className %>State,
|
|
398
602
|
(state) => state.errors
|
|
@@ -6,9 +6,12 @@ import {
|
|
|
6
6
|
GoADivider,
|
|
7
7
|
GoADropdown,
|
|
8
8
|
GoADropdownItem,
|
|
9
|
+
GoAFileUploadCard,
|
|
10
|
+
GoAFileUploadInput,
|
|
9
11
|
GoAFormItem,
|
|
10
12
|
GoAFormStep,
|
|
11
13
|
GoAFormStepper,
|
|
14
|
+
GoAIconButton,
|
|
12
15
|
GoAInput,
|
|
13
16
|
GoAInputDate,
|
|
14
17
|
GoAInputDateTime,
|
|
@@ -17,14 +20,20 @@ import {
|
|
|
17
20
|
GoASpinner,
|
|
18
21
|
GoATextArea,
|
|
19
22
|
} from '@abgov/react-components';
|
|
20
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
FunctionComponent,
|
|
25
|
+
useEffect,
|
|
26
|
+
useRef,
|
|
27
|
+
} from 'react';
|
|
21
28
|
import { useDispatch, useSelector } from 'react-redux';
|
|
22
29
|
import { AppDispatch } from '../../store';
|
|
23
30
|
import {
|
|
31
|
+
FileInformation,
|
|
24
32
|
getFormBusy,
|
|
25
33
|
getFormCanSubmit,
|
|
26
34
|
getFormComplete,
|
|
27
35
|
getFormErrors,
|
|
36
|
+
getFormFiles,
|
|
28
37
|
getFormInReview,
|
|
29
38
|
getFormStatus,
|
|
30
39
|
getFormStep,
|
|
@@ -33,6 +42,8 @@ import {
|
|
|
33
42
|
submitForm,
|
|
34
43
|
<%= propertyName %>Actions,
|
|
35
44
|
updateForm,
|
|
45
|
+
updateFile,
|
|
46
|
+
downloadFile,
|
|
36
47
|
} from './<%= fileName %>.slice';
|
|
37
48
|
import styles from './<%= fileName %>.module.css';
|
|
38
49
|
|
|
@@ -50,13 +61,44 @@ function onArrayItemChange(
|
|
|
50
61
|
onChange({ ...value, [arrayProperty]: updatedArray });
|
|
51
62
|
}
|
|
52
63
|
|
|
64
|
+
interface FormFileProps {
|
|
65
|
+
property: string;
|
|
66
|
+
file: FileInformation;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const FormFile: FunctionComponent<FormFileProps> = ({ property, file }) => {
|
|
70
|
+
const anchorRef = useRef<HTMLAnchorElement>();
|
|
71
|
+
const dispatch = useDispatch<AppDispatch>();
|
|
72
|
+
return (
|
|
73
|
+
<div className={styles.formFile}>
|
|
74
|
+
<span>{file.filename}</span>
|
|
75
|
+
<GoAIconButton
|
|
76
|
+
ml="s"
|
|
77
|
+
icon="download"
|
|
78
|
+
onClick={() => {
|
|
79
|
+
if (file.dataUri) {
|
|
80
|
+
anchorRef.current.click();
|
|
81
|
+
} else {
|
|
82
|
+
dispatch(downloadFile({ key: property, file }));
|
|
83
|
+
}
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
<a ref={anchorRef} href={file.dataUri} download={file.filename}>
|
|
87
|
+
Download
|
|
88
|
+
</a>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
53
93
|
interface FieldSetProps {
|
|
54
94
|
className?: string;
|
|
55
95
|
inReview: boolean;
|
|
56
96
|
isReadOnly: boolean;
|
|
57
97
|
value: Record<string, unknown>;
|
|
98
|
+
files: Record<string, FileInformation>;
|
|
58
99
|
errors: Record<string, boolean>;
|
|
59
100
|
onChange: (value: Record<string, unknown>) => void;
|
|
101
|
+
onFileChange: (key: string, old?: FileInformation, file?: File) => void;
|
|
60
102
|
onEdit: () => void;
|
|
61
103
|
}
|
|
62
104
|
|
|
@@ -66,8 +108,10 @@ const <%= section.className %>FieldSet: FunctionComponent<FieldSetProps> = ({
|
|
|
66
108
|
inReview,
|
|
67
109
|
isReadOnly,
|
|
68
110
|
value,
|
|
111
|
+
files,
|
|
69
112
|
errors,
|
|
70
113
|
onChange,
|
|
114
|
+
onFileChange,
|
|
71
115
|
onEdit,
|
|
72
116
|
}) => {
|
|
73
117
|
return (
|
|
@@ -95,6 +139,7 @@ export const <%= className %>Form: FunctionComponent = () => {
|
|
|
95
139
|
const formStatus = useSelector(getFormStatus);
|
|
96
140
|
const formStep = useSelector(getFormStep);
|
|
97
141
|
const formData = useSelector(getFormValues);
|
|
142
|
+
const formFiles = useSelector(getFormFiles);
|
|
98
143
|
const formErrors = useSelector(getFormErrors);
|
|
99
144
|
const formBusy = useSelector(getFormBusy);
|
|
100
145
|
const formComplete = useSelector(getFormComplete);
|
|
@@ -137,10 +182,14 @@ export const <%= className %>Form: FunctionComponent = () => {
|
|
|
137
182
|
inReview={inReview}
|
|
138
183
|
isReadOnly={formStatus !== 'draft'}
|
|
139
184
|
value={formData.<%= sectionKey %> as any || {}}
|
|
185
|
+
files={formFiles}
|
|
140
186
|
errors={formErrors['<%= sectionKey %>'] || {}}
|
|
141
187
|
onChange={(value) =>
|
|
142
188
|
dispatch(updateForm({ ...formData, '<%= sectionKey %>': value as any }))
|
|
143
189
|
}
|
|
190
|
+
onFileChange={(key, old, file) =>
|
|
191
|
+
dispatch(updateFile({ key, old, file }))
|
|
192
|
+
}
|
|
144
193
|
onEdit={() => dispatch(<%= propertyName %>Actions.setStep(<%= idx + 1 %>))}
|
|
145
194
|
/>
|
|
146
195
|
<%_ }); _%>
|
|
@@ -44,6 +44,32 @@
|
|
|
44
44
|
value={<%= valueVar %>.<%= key %> ? new Date(<%= valueVar %>.<%= key %> as string) : null}
|
|
45
45
|
onChange={(name, updated) => <%- inArray ? `onArrayItemChange(onChange, value, '${parent}', idx, ` : 'onChange(' %>{ ...<%= valueVar %>, [name]: (updated as Date)?.toISOString() })}
|
|
46
46
|
/>
|
|
47
|
+
<%_ break;
|
|
48
|
+
case 'uri': _%>
|
|
49
|
+
<% if (!inArray) { %>
|
|
50
|
+
{files['<%= key %>'] ? (
|
|
51
|
+
isReadOnly || inReview ? (
|
|
52
|
+
<FormFile property="<%= key %>" file={files['<%= key %>']} />
|
|
53
|
+
) : (
|
|
54
|
+
<GoAFileUploadCard
|
|
55
|
+
filename={files['<%= key %>'].filename}
|
|
56
|
+
size={files['<%= key %>'].size}
|
|
57
|
+
progress={files['<%= key %>'].progress}
|
|
58
|
+
onDelete={() => onFileChange('<%= key %>', files['<%= key %>'])}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
) : (
|
|
62
|
+
!isReadOnly &&
|
|
63
|
+
!inReview && (
|
|
64
|
+
<GoAFileUploadInput
|
|
65
|
+
variant="dragdrop"
|
|
66
|
+
onSelectFile={(file) =>
|
|
67
|
+
onFileChange('<%= key %>', files['<%= key %>'], file)
|
|
68
|
+
}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
)}
|
|
72
|
+
<% } %>
|
|
47
73
|
<%_ break;
|
|
48
74
|
default: _%>
|
|
49
75
|
<% if (property.maxLength > 100) { %>
|
|
@@ -20,13 +20,13 @@ function getFormDefinition(configurationServiceUrl, token) {
|
|
|
20
20
|
if (choices.length < 1) {
|
|
21
21
|
throw new Error('No form definitions with data schema found.');
|
|
22
22
|
}
|
|
23
|
-
const
|
|
23
|
+
const { definition } = yield (0, enquirer_1.prompt)({
|
|
24
24
|
type: 'autocomplete',
|
|
25
25
|
name: 'definition',
|
|
26
26
|
message: 'Which form definition do you want to generate a component for?',
|
|
27
27
|
choices,
|
|
28
28
|
});
|
|
29
|
-
const formDefinition = definitions.find((r) => r.name ===
|
|
29
|
+
const formDefinition = definitions.find((r) => r.name === definition);
|
|
30
30
|
const general = {
|
|
31
31
|
type: 'object',
|
|
32
32
|
className: 'General',
|
|
@@ -46,6 +46,29 @@ function getFormDefinition(configurationServiceUrl, token) {
|
|
|
46
46
|
if (addGeneral) {
|
|
47
47
|
formDefinition.dataSchema.properties = Object.assign({ general }, formDefinition.dataSchema.properties);
|
|
48
48
|
}
|
|
49
|
+
const fileTypeName = `${formDefinition.name} files`;
|
|
50
|
+
const fileTypeId = `${(0, devkit_1.names)(formDefinition.name).fileName}-files`;
|
|
51
|
+
const { addFileType } = yield (0, enquirer_1.prompt)({
|
|
52
|
+
type: 'confirm',
|
|
53
|
+
name: 'addFileType',
|
|
54
|
+
message: `Create file type (${fileTypeName}) for form files?`,
|
|
55
|
+
});
|
|
56
|
+
if (addFileType) {
|
|
57
|
+
yield axios_1.default.patch(new URL('configuration/v2/configuration/platform/file-service', configurationServiceUrl).href, {
|
|
58
|
+
operation: 'UPDATE',
|
|
59
|
+
update: {
|
|
60
|
+
[fileTypeId]: {
|
|
61
|
+
id: fileTypeId,
|
|
62
|
+
name: fileTypeName,
|
|
63
|
+
readRoles: formDefinition.assessorRoles,
|
|
64
|
+
updateRoles: formDefinition.applicantRoles,
|
|
65
|
+
anonymousRead: false,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}, {
|
|
69
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
49
72
|
return formDefinition;
|
|
50
73
|
});
|
|
51
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-form.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/react-form/react-form.ts"],"names":[],"mappings":";;;AAAA,wCAKsB;AACtB,yCAMsB;AACtB,iCAA0B;AAC1B,uCAAkC;AAClC,6BAA6B;AAE7B,2CAAyE;AAEzE,SAAe,iBAAiB,CAC9B,uBAA+B,EAC/B,KAAa;;QAEb,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,eAAK,CAAC,GAAG,CAC9B,IAAI,GAAG,CACL,6DAA6D,EAC7D,uBAAuB,CACxB,CAAC,IAAI,EACN;YACE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;SAC9C,CACF,CAAC;QAEF,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,WAAW;aACxB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;aAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAClB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;QAEtC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE;YACtB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;SAChE;QAED,MAAM,
|
|
1
|
+
{"version":3,"file":"react-form.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/react-form/react-form.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,6 +48,8 @@ const formDefinition: FormDefinition = {
|
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
|
+
assessorRoles: [],
|
|
52
|
+
applicantRoles: [],
|
|
51
53
|
};
|
|
52
54
|
|
|
53
55
|
jest.mock('@abgov/nx-oc');
|
|
@@ -69,7 +71,8 @@ axiosMock.get.mockResolvedValueOnce({
|
|
|
69
71
|
|
|
70
72
|
jest.mock('enquirer', () => ({ prompt: jest.fn() }));
|
|
71
73
|
const promptMock = prompt as jest.Mock;
|
|
72
|
-
promptMock.
|
|
74
|
+
promptMock.mockResolvedValueOnce({ definition: 'Some Intake' });
|
|
75
|
+
promptMock.mockResolvedValueOnce({ addFileType: false });
|
|
73
76
|
|
|
74
77
|
describe('React Form Generator', () => {
|
|
75
78
|
const options: Schema = {
|
|
@@ -91,7 +94,9 @@ describe('React Form Generator', () => {
|
|
|
91
94
|
});
|
|
92
95
|
|
|
93
96
|
await generator(host, options);
|
|
94
|
-
expect(
|
|
97
|
+
expect(
|
|
98
|
+
host.exists('apps/test/src/app/some-intake/some-intake.tsx')
|
|
99
|
+
).toBeTruthy();
|
|
95
100
|
expect(
|
|
96
101
|
host.exists('apps/test/src/app/some-intake/some-intake.slice.ts')
|
|
97
102
|
).toBeTruthy();
|
package/src/utils/form.d.ts
CHANGED
package/src/utils/form.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/form.ts"],"names":[],"mappings":";;;;AAAA,yEAAoD;
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/form.ts"],"names":[],"mappings":";;;;AAAA,yEAAoD;AAUpD,SAAsB,qBAAqB,CAAC,EAC1C,IAAI,EACJ,UAAU,GACK;;QACf,MAAM,KAAK,GAAG,MAAM,IAAA,mCAAO,EAAC,UAAU,EAAE,IAAI,EAAE;YAC5C,oBAAoB,EAAE,KAAK;YAC3B,aAAa,EAAE,EAAE;YACjB,MAAM,EAAE,KAAK;SACd,CAAC,CAAC;QAEH,OAAO,KAAK,CAAC;IACf,CAAC;CAAA;AAXD,sDAWC"}
|