@abgov/nx-adsp 5.8.0-beta.4 → 5.8.0-beta.5
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__ +43 -1
- package/src/generators/react-form/files/__fileName__/__fileName__.slice.spec.ts__tmpl__ +23 -0
- package/src/generators/react-form/files/__fileName__/__fileName__.slice.ts__tmpl__ +109 -31
- package/src/generators/react-form/files/__fileName__/__fileName__.tsx__tmpl__ +168 -41
package/package.json
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
.form {
|
|
2
|
+
> fieldset {
|
|
3
|
+
display: none;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
> fieldset.show {
|
|
7
|
+
display: block;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
.formActions {
|
|
2
12
|
display: flex;
|
|
3
13
|
margin-top: 16px;
|
|
@@ -5,8 +15,40 @@
|
|
|
5
15
|
> * {
|
|
6
16
|
margin-left: 16px;
|
|
7
17
|
}
|
|
18
|
+
|
|
19
|
+
.save {
|
|
20
|
+
margin-right: auto;
|
|
21
|
+
display: flex;
|
|
22
|
+
opacity: 0;
|
|
23
|
+
transition-property: opacity;
|
|
24
|
+
transition-duration: 500ms;
|
|
25
|
+
|
|
26
|
+
> * {
|
|
27
|
+
margin-top: auto;
|
|
28
|
+
margin-bottom: auto;
|
|
29
|
+
margin-right: 8px;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
8
32
|
|
|
33
|
+
.save[data-show='true'] {
|
|
34
|
+
opacity: 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.sectionActions {
|
|
39
|
+
display: flex;
|
|
40
|
+
|
|
9
41
|
> *:first-child {
|
|
10
|
-
margin-
|
|
42
|
+
margin-right: auto;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.load {
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: row;
|
|
49
|
+
padding-top: 64px;
|
|
50
|
+
padding-bottom: 64px;
|
|
51
|
+
> * {
|
|
52
|
+
margin: auto;
|
|
11
53
|
}
|
|
12
54
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
initial<%= className %>State,
|
|
3
|
+
<%= propertyName %>Actions,
|
|
4
|
+
<%= propertyName %>Reducer,
|
|
5
|
+
} from './<%= fileName %>.slice';
|
|
6
|
+
|
|
7
|
+
describe('<%= name %> slice', () => {
|
|
8
|
+
describe('<%= propertyName %>Reducer', () => {
|
|
9
|
+
it('can handle initial state', () => {
|
|
10
|
+
expect(<%= propertyName %>Reducer(undefined, { type: '' })).toEqual(
|
|
11
|
+
initial<%= className %>State
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('can handle set step action', () => {
|
|
16
|
+
expect(
|
|
17
|
+
<%= propertyName %>Reducer(undefined, <%= propertyName %>Actions.setStep(2))
|
|
18
|
+
).toMatchObject({ step: 2 });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// TODO: Add state unit tests.
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PayloadAction,
|
|
2
3
|
createAction,
|
|
3
4
|
createAsyncThunk,
|
|
4
5
|
createSelector,
|
|
@@ -15,10 +16,10 @@ const FORM_SERVICE_URL = '<%= formServiceUrl %>';
|
|
|
15
16
|
/*
|
|
16
17
|
* Update these interfaces according to your requirements.
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
<%- interfaceDefinition %>
|
|
20
|
+
<% const sections = Object.entries(dataSchema.properties); %>
|
|
20
21
|
const rules: Record<string, Record<string, RegExp>> = {
|
|
21
|
-
<%_
|
|
22
|
+
<%_ sections.forEach(function([sectionKey, section]) { _%>
|
|
22
23
|
<%= sectionKey %>: {
|
|
23
24
|
<%_ Object.entries(section.properties).filter(([_, { pattern }]) => !!pattern).forEach(function([key, value]) { _%>
|
|
24
25
|
<%= key %>: new RegExp('<%- value.pattern %>')
|
|
@@ -28,17 +29,21 @@ const rules: Record<string, Record<string, RegExp>> = {
|
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
const required: Record<string, string[]> = {
|
|
31
|
-
<%_
|
|
32
|
+
<%_ sections.forEach(function([sectionKey, section]) { _%>
|
|
32
33
|
<%= sectionKey %>: [<%- section.required?.map(req => `'${req}'`) || "" %>],
|
|
33
34
|
<%_ }); _%>
|
|
34
35
|
};
|
|
35
36
|
|
|
37
|
+
type FormStatus = 'draft' | 'locked' | 'submitted';
|
|
38
|
+
type SectionCompletion = 'complete' | 'incomplete' | null;
|
|
39
|
+
|
|
36
40
|
export interface <%= className %>State {
|
|
37
41
|
formId: string;
|
|
42
|
+
status: FormStatus;
|
|
43
|
+
step: number;
|
|
38
44
|
values: <%= className %>;
|
|
39
45
|
errors: Record<string, Record<string, boolean>>;
|
|
40
|
-
complete: Record<string,
|
|
41
|
-
review: boolean;
|
|
46
|
+
complete: Record<string, SectionCompletion>;
|
|
42
47
|
busy: {
|
|
43
48
|
loading: boolean;
|
|
44
49
|
saving: boolean;
|
|
@@ -46,6 +51,41 @@ export interface <%= className %>State {
|
|
|
46
51
|
};
|
|
47
52
|
}
|
|
48
53
|
|
|
54
|
+
function areSectionsComplete(
|
|
55
|
+
values: <%= className %>,
|
|
56
|
+
errors: Record<string, Record<string, boolean>> = {}
|
|
57
|
+
): Record<string, SectionCompletion> {
|
|
58
|
+
const complete: Record<string, SectionCompletion> = {};
|
|
59
|
+
Object.entries(required).forEach(([section, requiredValues]) => {
|
|
60
|
+
const sectionValue = values[section] || {};
|
|
61
|
+
|
|
62
|
+
const hasValues = requiredValues
|
|
63
|
+
.map(
|
|
64
|
+
(required) =>
|
|
65
|
+
sectionValue[required] !== undefined &&
|
|
66
|
+
sectionValue[required] !== null &&
|
|
67
|
+
sectionValue[required] !== ''
|
|
68
|
+
)
|
|
69
|
+
.filter((hasValue) => hasValue);
|
|
70
|
+
|
|
71
|
+
const hasError =
|
|
72
|
+
Object.values(errors[section] || {}).filter((error) => error).length > 0;
|
|
73
|
+
|
|
74
|
+
// Note that sections with no required values are considered complete.
|
|
75
|
+
let completion: SectionCompletion = null;
|
|
76
|
+
if (!hasError && hasValues.length === requiredValues.length) {
|
|
77
|
+
completion = 'complete';
|
|
78
|
+
} else {
|
|
79
|
+
// This means partial completion.
|
|
80
|
+
completion = 'incomplete';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
complete[section] = completion;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return complete;
|
|
87
|
+
}
|
|
88
|
+
|
|
49
89
|
/**
|
|
50
90
|
* Export an effect using createAsyncThunk from
|
|
51
91
|
* the Redux Toolkit: https://redux-toolkit.js.org/api/createAsyncThunk
|
|
@@ -71,7 +111,7 @@ export const initializeForm = createAsyncThunk(
|
|
|
71
111
|
throw new Error('No active user session.');
|
|
72
112
|
}
|
|
73
113
|
|
|
74
|
-
const { data: forms } = await axios.get<{ results: { id: string }[] }>(
|
|
114
|
+
const { data: forms } = await axios.get<{ results: { id: string; status: FormStatus }[] }>(
|
|
75
115
|
`${FORM_SERVICE_URL}/form/v1/forms`,
|
|
76
116
|
{
|
|
77
117
|
headers: {
|
|
@@ -86,9 +126,9 @@ export const initializeForm = createAsyncThunk(
|
|
|
86
126
|
}
|
|
87
127
|
);
|
|
88
128
|
|
|
89
|
-
let
|
|
90
|
-
if (!
|
|
91
|
-
const { data:
|
|
129
|
+
let form = forms.results?.[0];
|
|
130
|
+
if (!form) {
|
|
131
|
+
const { data: newForm } = await axios.post<{ id: string; status: FormStatus }>(
|
|
92
132
|
`${FORM_SERVICE_URL}/form/v1/forms`,
|
|
93
133
|
{
|
|
94
134
|
definitionId: FORM_DEFINITION_ID,
|
|
@@ -109,19 +149,21 @@ export const initializeForm = createAsyncThunk(
|
|
|
109
149
|
},
|
|
110
150
|
}
|
|
111
151
|
);
|
|
112
|
-
|
|
152
|
+
form = newForm;
|
|
113
153
|
}
|
|
114
154
|
|
|
115
155
|
const { data } = await axios.get<{
|
|
116
156
|
id: string;
|
|
117
157
|
data: <%= className %>;
|
|
118
|
-
}>(`${FORM_SERVICE_URL}/form/v1/forms/${
|
|
158
|
+
}>(`${FORM_SERVICE_URL}/form/v1/forms/${form.id}/data`, {
|
|
119
159
|
headers: {
|
|
120
160
|
Authorization: `Bearer ${user.access_token}`,
|
|
121
161
|
},
|
|
122
162
|
});
|
|
123
163
|
|
|
124
|
-
|
|
164
|
+
const complete = areSectionsComplete(data.data);
|
|
165
|
+
|
|
166
|
+
return { ...form, ...data, complete };
|
|
125
167
|
}
|
|
126
168
|
);
|
|
127
169
|
|
|
@@ -172,20 +214,7 @@ export const updateForm = createAsyncThunk(
|
|
|
172
214
|
errors[section] = sectionErrors;
|
|
173
215
|
});
|
|
174
216
|
|
|
175
|
-
const complete
|
|
176
|
-
Object.entries(required).forEach(([section, requiredValues]) => {
|
|
177
|
-
const sectionValue = values[section] || {};
|
|
178
|
-
complete[section] =
|
|
179
|
-
Object.values(errors[section]).filter((error) => error).length < 1 &&
|
|
180
|
-
requiredValues
|
|
181
|
-
.map(
|
|
182
|
-
(required) =>
|
|
183
|
-
sectionValue[required] !== undefined &&
|
|
184
|
-
sectionValue[required] !== null &&
|
|
185
|
-
sectionValue[required] !== ''
|
|
186
|
-
)
|
|
187
|
-
.filter((hasValue) => !hasValue).length < 1;
|
|
188
|
-
});
|
|
217
|
+
const complete = areSectionsComplete(values, errors);
|
|
189
218
|
|
|
190
219
|
if (!hasError) {
|
|
191
220
|
dispatch(queueSaveForm(values));
|
|
@@ -197,11 +226,19 @@ export const updateForm = createAsyncThunk(
|
|
|
197
226
|
|
|
198
227
|
export const submitForm = createAsyncThunk(
|
|
199
228
|
'<%= propertyName %>/submit',
|
|
200
|
-
async (
|
|
229
|
+
async (_, { getState }) => {
|
|
230
|
+
const state = getState();
|
|
231
|
+
const { user }: UserState = state['user'];
|
|
232
|
+
const { formId }: <%= className %>State = state[<%= constantName %>_FEATURE_KEY];
|
|
233
|
+
|
|
201
234
|
const { data } = await axios.post(
|
|
202
235
|
`${FORM_SERVICE_URL}/form/v1/forms/${formId}`,
|
|
203
236
|
{ operation: 'submit' },
|
|
204
|
-
{
|
|
237
|
+
{
|
|
238
|
+
headers: {
|
|
239
|
+
Authorization: `Bearer ${user.access_token}`,
|
|
240
|
+
},
|
|
241
|
+
}
|
|
205
242
|
);
|
|
206
243
|
return data;
|
|
207
244
|
}
|
|
@@ -209,10 +246,11 @@ export const submitForm = createAsyncThunk(
|
|
|
209
246
|
|
|
210
247
|
export const initial<%= className %>State: <%= className %>State = {
|
|
211
248
|
formId: null,
|
|
249
|
+
status: null,
|
|
250
|
+
step: 1,
|
|
212
251
|
values: {} as <%= className %>,
|
|
213
252
|
errors: {},
|
|
214
253
|
complete: {},
|
|
215
|
-
review: false,
|
|
216
254
|
busy: {
|
|
217
255
|
loading: false,
|
|
218
256
|
saving: false,
|
|
@@ -223,7 +261,11 @@ export const initial<%= className %>State: <%= className %>State = {
|
|
|
223
261
|
export const <%= propertyName %>Slice = createSlice({
|
|
224
262
|
name: <%= constantName %>_FEATURE_KEY,
|
|
225
263
|
initialState: initial<%= className %>State,
|
|
226
|
-
reducers: {
|
|
264
|
+
reducers: {
|
|
265
|
+
setStep: (state, action: PayloadAction<number>) => {
|
|
266
|
+
state.step = action.payload;
|
|
267
|
+
},
|
|
268
|
+
},
|
|
227
269
|
extraReducers: (builder) => {
|
|
228
270
|
builder
|
|
229
271
|
.addCase(initializeForm.pending, (state) => {
|
|
@@ -231,7 +273,9 @@ export const <%= propertyName %>Slice = createSlice({
|
|
|
231
273
|
})
|
|
232
274
|
.addCase(initializeForm.fulfilled, (state, action) => {
|
|
233
275
|
state.formId = action.payload.id;
|
|
276
|
+
state.status = action.payload.status;
|
|
234
277
|
state.values = action.payload.data;
|
|
278
|
+
state.complete = action.payload.complete;
|
|
235
279
|
state.busy.loading = false;
|
|
236
280
|
})
|
|
237
281
|
.addCase(initializeForm.rejected, (state) => {
|
|
@@ -250,6 +294,16 @@ export const <%= propertyName %>Slice = createSlice({
|
|
|
250
294
|
})
|
|
251
295
|
.addCase(queueSaveForm.rejected, (state) => {
|
|
252
296
|
state.busy.saving = false;
|
|
297
|
+
})
|
|
298
|
+
.addCase(submitForm.pending, (state) => {
|
|
299
|
+
state.busy.submitting = true;
|
|
300
|
+
})
|
|
301
|
+
.addCase(submitForm.fulfilled, (state) => {
|
|
302
|
+
state.busy.submitting = false;
|
|
303
|
+
state.status = 'submitted'
|
|
304
|
+
})
|
|
305
|
+
.addCase(submitForm.rejected, (state) => {
|
|
306
|
+
state.busy.submitting = false;
|
|
253
307
|
});
|
|
254
308
|
},
|
|
255
309
|
});
|
|
@@ -299,6 +353,16 @@ export const get<%= className %>State = (
|
|
|
299
353
|
rootState: unknown
|
|
300
354
|
): <%= className %>State => rootState[<%= constantName %>_FEATURE_KEY];
|
|
301
355
|
|
|
356
|
+
export const getFormStatus = createSelector(
|
|
357
|
+
get<%= className %>State,
|
|
358
|
+
(state) => state.status
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
export const getFormStep = createSelector(
|
|
362
|
+
get<%= className %>State,
|
|
363
|
+
(state) => state.step
|
|
364
|
+
);
|
|
365
|
+
|
|
302
366
|
export const getFormValues = createSelector(
|
|
303
367
|
get<%= className %>State,
|
|
304
368
|
(state) => state.values
|
|
@@ -318,3 +382,17 @@ export const getFormComplete = createSelector(
|
|
|
318
382
|
get<%= className %>State,
|
|
319
383
|
(state) => state.complete
|
|
320
384
|
);
|
|
385
|
+
|
|
386
|
+
export const getFormInReview = createSelector(
|
|
387
|
+
get<%= className %>State,
|
|
388
|
+
(state) => state.step === <%= sections.length + 1 %>
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
export const getFormCanSubmit = createSelector(
|
|
392
|
+
get<%= className %>State,
|
|
393
|
+
(state) =>
|
|
394
|
+
!state.busy.saving && !state.busy.submitting &&
|
|
395
|
+
state.status === 'draft' &&
|
|
396
|
+
Object.values(state.complete).filter((complete) => complete !== 'complete')
|
|
397
|
+
.length < 1
|
|
398
|
+
);
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GoAButton,
|
|
3
|
+
GoAButtonGroup,
|
|
4
|
+
GoACallout,
|
|
3
5
|
GoACheckbox,
|
|
6
|
+
GoADropdown,
|
|
7
|
+
GoADropdownItem,
|
|
4
8
|
GoAFormItem,
|
|
5
9
|
GoAFormStep,
|
|
6
10
|
GoAFormStepper,
|
|
7
11
|
GoAInput,
|
|
12
|
+
GoAInputDate,
|
|
13
|
+
GoAInputDateTime,
|
|
14
|
+
GoAInputTime,
|
|
15
|
+
GoANotification,
|
|
8
16
|
GoASpinner,
|
|
9
17
|
} from '@abgov/react-components';
|
|
10
18
|
import { FunctionComponent, useEffect } from 'react';
|
|
@@ -12,61 +20,134 @@ import { useDispatch, useSelector } from 'react-redux';
|
|
|
12
20
|
import { AppDispatch } from '../../store';
|
|
13
21
|
import {
|
|
14
22
|
getFormBusy,
|
|
23
|
+
getFormCanSubmit,
|
|
15
24
|
getFormComplete,
|
|
16
25
|
getFormErrors,
|
|
26
|
+
getFormInReview,
|
|
27
|
+
getFormStatus,
|
|
28
|
+
getFormStep,
|
|
17
29
|
getFormValues,
|
|
18
30
|
initializeForm,
|
|
31
|
+
submitForm,
|
|
32
|
+
<%= propertyName %>Actions,
|
|
19
33
|
updateForm,
|
|
20
34
|
} from './<%= fileName %>.slice';
|
|
21
35
|
import styles from './<%= fileName %>.module.css';
|
|
22
36
|
|
|
23
37
|
interface FieldSetProps {
|
|
38
|
+
className?: string;
|
|
39
|
+
inReview: boolean;
|
|
40
|
+
isReadOnly: boolean;
|
|
24
41
|
value: Record<string, unknown>;
|
|
25
42
|
errors: Record<string, boolean>;
|
|
26
43
|
onChange: (value: Record<string, unknown>) => void;
|
|
44
|
+
onEdit: () => void;
|
|
27
45
|
}
|
|
28
46
|
|
|
29
47
|
<% Object.entries(dataSchema.properties).forEach(function([sectionKey, section]) { %>
|
|
30
48
|
const <%= section.className %>FieldSet: FunctionComponent<FieldSetProps> = ({
|
|
49
|
+
className,
|
|
50
|
+
inReview,
|
|
51
|
+
isReadOnly,
|
|
31
52
|
value,
|
|
32
53
|
errors,
|
|
33
54
|
onChange,
|
|
55
|
+
onEdit,
|
|
34
56
|
}) => {
|
|
35
57
|
return (
|
|
36
|
-
<fieldset>
|
|
58
|
+
<fieldset className={className}>
|
|
37
59
|
<legend><%= section.title || sectionKey %></legend>
|
|
60
|
+
<div className={styles.sectionActions}>
|
|
61
|
+
<p><%= section.description %></p>
|
|
62
|
+
{!isReadOnly && inReview && <GoAButton type="tertiary" onClick={onEdit}>Edit</GoAButton>}
|
|
63
|
+
</div>
|
|
38
64
|
<%_ Object.entries(section.properties).forEach(function([key, value]) { _%>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
<%_ if (!value.type) { _%>
|
|
66
|
+
<%_ if (value.constant) { _%>
|
|
67
|
+
<p><%= value.constant %></p>
|
|
68
|
+
<%_ } _%>
|
|
69
|
+
<%_ } else { _%>
|
|
70
|
+
<GoAFormItem label="<%= value.title || key %>" helpText="<%= value.description %>">
|
|
71
|
+
<%_ switch(value.type) {
|
|
72
|
+
case 'string': _%>
|
|
73
|
+
<%_ if (value.enum) { _%>
|
|
74
|
+
<GoADropdown
|
|
75
|
+
name="<%= key %>"
|
|
76
|
+
disabled={isReadOnly || inReview}
|
|
77
|
+
onChange={(name, updated) => onChange({ ...value, [name]: updated })}
|
|
78
|
+
value={`${value.<%= key %> || ''}`}
|
|
79
|
+
>
|
|
80
|
+
<%_ value.enum.forEach((enumValue) => { _%>
|
|
81
|
+
<GoADropdownItem value="<%= enumValue %>" label="<%= enumValue %>" />
|
|
82
|
+
<%_ }); _%>
|
|
83
|
+
</GoADropdown>
|
|
84
|
+
<%_ } else { _%>
|
|
85
|
+
<%_ switch(value.format) {
|
|
86
|
+
case 'date-time': _%>
|
|
87
|
+
<GoAInputDateTime
|
|
88
|
+
name="<%= key %>"
|
|
89
|
+
disabled={isReadOnly || inReview}
|
|
90
|
+
value={value.<%= key %> ? new Date(value.<%= key %> as string) : null}
|
|
91
|
+
onChange={(name, updated) => onChange({ ...value, [name]: (updated as Date)?.toISOString() })}
|
|
92
|
+
/>
|
|
93
|
+
<%_ break;
|
|
94
|
+
case 'date': _%>
|
|
95
|
+
<GoAInputDate
|
|
96
|
+
name="<%= key %>"
|
|
97
|
+
disabled={isReadOnly || inReview}
|
|
98
|
+
value={value.<%= key %> ? new Date(value.<%= key %> as string) : null}
|
|
99
|
+
onChange={(name, updated) => onChange({ ...value, [name]: (updated as Date)?.toISOString() })}
|
|
100
|
+
/>
|
|
101
|
+
<%_ break;
|
|
102
|
+
case 'time': _%>
|
|
103
|
+
<GoAInputTime
|
|
104
|
+
name="<%= key %>"
|
|
105
|
+
step={1}
|
|
106
|
+
disabled={isReadOnly || inReview}
|
|
107
|
+
value={value.<%= key %> ? new Date(value.<%= key %> as string) : null}
|
|
108
|
+
onChange={(name, updated) => onChange({ ...value, [name]: (updated as Date)?.toISOString() })}
|
|
109
|
+
/>
|
|
110
|
+
<%_ break;
|
|
111
|
+
default: _%>
|
|
112
|
+
<GoAInput
|
|
113
|
+
type="text"
|
|
114
|
+
disabled={isReadOnly || inReview}
|
|
115
|
+
error={errors['<%= key %>']}
|
|
116
|
+
placeholder="<%= value?.examples?.join(', ') || '' %>"
|
|
117
|
+
onChange={(name, updated) => onChange({ ...value, [name]: updated })}
|
|
118
|
+
value={`${value.<%= key %> || ''}`}
|
|
119
|
+
name="<%= key %>"
|
|
120
|
+
/>
|
|
121
|
+
<%_ break;
|
|
122
|
+
} _%>
|
|
123
|
+
<%_ } _%>
|
|
124
|
+
<%_ break;
|
|
125
|
+
case 'number': _%>
|
|
126
|
+
<GoAInput
|
|
127
|
+
type="number"
|
|
128
|
+
disabled={isReadOnly || inReview}
|
|
129
|
+
error={errors['<%= key %>']}
|
|
130
|
+
placeholder="<%= value?.examples || []%>"
|
|
131
|
+
onChange={(name, updated) => onChange({ ...value, [name]: updated })}
|
|
132
|
+
value={`${value.<%= key %> || ''}`}
|
|
133
|
+
name="<%= key %>"
|
|
134
|
+
/>
|
|
135
|
+
<%_ break;
|
|
136
|
+
case 'boolean': _%>
|
|
137
|
+
<GoACheckbox
|
|
138
|
+
disabled={isReadOnly || inReview}
|
|
139
|
+
checked={value.<%= key %> as any || false}
|
|
140
|
+
onChange={(name, updated) => onChange({ ...value, [name]: updated })}
|
|
141
|
+
name="<%= key %>"
|
|
142
|
+
/>
|
|
143
|
+
<%_ break;
|
|
144
|
+
case 'array': _%>
|
|
145
|
+
<%_ break;
|
|
146
|
+
default:
|
|
147
|
+
break; _%>
|
|
148
|
+
<%_ } _%>
|
|
149
|
+
</GoAFormItem>
|
|
150
|
+
<%_ } _%>
|
|
70
151
|
<%_ }); _%>
|
|
71
152
|
</fieldset>
|
|
72
153
|
);
|
|
@@ -80,18 +161,36 @@ export const <%= className %>Form: FunctionComponent = () => {
|
|
|
80
161
|
dispatch(initializeForm());
|
|
81
162
|
}, [dispatch, user]);
|
|
82
163
|
|
|
164
|
+
const formStatus = useSelector(getFormStatus);
|
|
165
|
+
const formStep = useSelector(getFormStep);
|
|
83
166
|
const formData = useSelector(getFormValues);
|
|
84
167
|
const formErrors = useSelector(getFormErrors);
|
|
85
168
|
const formBusy = useSelector(getFormBusy);
|
|
86
169
|
const formComplete = useSelector(getFormComplete);
|
|
87
|
-
|
|
170
|
+
const inReview = useSelector(getFormInReview);
|
|
171
|
+
const canSubmit = useSelector(getFormCanSubmit);
|
|
172
|
+
|
|
173
|
+
<% const sections = Object.entries(dataSchema.properties); %>
|
|
88
174
|
return (
|
|
89
|
-
<form>
|
|
90
|
-
<
|
|
91
|
-
|
|
175
|
+
<form className={styles.form}>
|
|
176
|
+
<GoANotification type="important">
|
|
177
|
+
This is a generated rapid prototype. Use it as a starting point to build the right thing for users.
|
|
178
|
+
</GoANotification>
|
|
179
|
+
{
|
|
180
|
+
formStatus === 'submitted' &&
|
|
181
|
+
<GoACallout type="success" heading="<%= name %> form submitted">
|
|
182
|
+
We received your <%= name %> form and it is being processed.
|
|
183
|
+
</GoACallout>
|
|
184
|
+
}
|
|
185
|
+
<GoAFormStepper
|
|
186
|
+
testId="<%= fileName %>"
|
|
187
|
+
step={formStep}
|
|
188
|
+
onChange={(step) => dispatch(<%= propertyName %>Actions.setStep(step))}
|
|
189
|
+
>
|
|
190
|
+
<%_ sections.forEach(function([sectionKey, section]) { _%>
|
|
92
191
|
<GoAFormStep
|
|
93
192
|
text="<%= section.title || sectionKey %>"
|
|
94
|
-
status={formComplete['<%= sectionKey %>']
|
|
193
|
+
status={formComplete['<%= sectionKey %>']} />
|
|
95
194
|
<%_ }); _%>
|
|
96
195
|
<GoAFormStep text="Review" />
|
|
97
196
|
</GoAFormStepper>
|
|
@@ -101,20 +200,48 @@ export const <%= className %>Form: FunctionComponent = () => {
|
|
|
101
200
|
</div>
|
|
102
201
|
) : (
|
|
103
202
|
<>
|
|
104
|
-
<%_
|
|
105
|
-
<<%= section.className %>FieldSet
|
|
203
|
+
<%_ sections.forEach(function([sectionKey, section], idx) { _%>
|
|
204
|
+
<<%= section.className %>FieldSet
|
|
205
|
+
className={formStep === <%= idx + 1 %> || formStep === <%= sections.length + 1 %> ? styles.show : ''}
|
|
206
|
+
inReview={inReview}
|
|
207
|
+
isReadOnly={formStatus !== 'draft'}
|
|
106
208
|
value={formData.<%= sectionKey %> as any || {}}
|
|
107
209
|
errors={formErrors['<%= sectionKey %>'] || {}}
|
|
108
210
|
onChange={(value) =>
|
|
109
211
|
dispatch(updateForm({ ...formData, '<%= sectionKey %>': value as any }))
|
|
110
212
|
}
|
|
213
|
+
onEdit={() => dispatch(<%= propertyName %>Actions.setStep(<%= idx + 1 %>))}
|
|
111
214
|
/>
|
|
112
215
|
<%_ }); _%>
|
|
113
216
|
</>
|
|
114
217
|
)}
|
|
115
218
|
<div className={styles.formActions}>
|
|
116
|
-
<
|
|
117
|
-
|
|
219
|
+
<div className={styles.save} data-show={formBusy.saving}>
|
|
220
|
+
<GoASpinner size="medium" type="infinite" />
|
|
221
|
+
<span>Saving...</span>
|
|
222
|
+
</div>
|
|
223
|
+
<GoAButtonGroup alignment="end">
|
|
224
|
+
{formStep > 1 && (
|
|
225
|
+
<GoAButton
|
|
226
|
+
type="secondary"
|
|
227
|
+
onClick={() => dispatch(<%= propertyName %>Actions.setStep(formStep - 1))}
|
|
228
|
+
>
|
|
229
|
+
Back
|
|
230
|
+
</GoAButton>
|
|
231
|
+
)}
|
|
232
|
+
{formStep < <%= sections.length + 1 %> ? (
|
|
233
|
+
<GoAButton
|
|
234
|
+
type="primary"
|
|
235
|
+
onClick={() => dispatch(<%= propertyName %>Actions.setStep(formStep + 1))}
|
|
236
|
+
>
|
|
237
|
+
Next
|
|
238
|
+
</GoAButton>
|
|
239
|
+
) : (
|
|
240
|
+
<GoAButton disabled={!canSubmit} type="primary" onClick={() => dispatch(submitForm())}>
|
|
241
|
+
Submit
|
|
242
|
+
</GoAButton>
|
|
243
|
+
)}
|
|
244
|
+
</GoAButtonGroup>
|
|
118
245
|
</div>
|
|
119
246
|
</form>
|
|
120
247
|
);
|