@ukhomeoffice/cop-react-form-renderer 2.0.0 → 2.5.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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/dist/components/CheckYourAnswers/CheckYourAnswers.js +16 -5
  4. package/dist/components/CheckYourAnswers/CheckYourAnswers.stories.mdx +199 -79
  5. package/dist/components/CheckYourAnswers/CheckYourAnswers.test.js +44 -0
  6. package/dist/components/FormComponent/FormComponent.js +6 -1
  7. package/dist/components/FormComponent/FormComponent.test.js +67 -0
  8. package/dist/components/FormRenderer/FormRenderer.js +122 -11
  9. package/dist/components/FormRenderer/FormRenderer.stories.mdx +115 -49
  10. package/dist/components/FormRenderer/FormRenderer.test.js +275 -0
  11. package/dist/components/FormRenderer/helpers/getNextPageId.js +3 -0
  12. package/dist/components/FormRenderer/helpers/getNextPageId.test.js +6 -0
  13. package/dist/components/FormRenderer/helpers/getPage.js +1 -1
  14. package/dist/components/FormRenderer/helpers/getSubmissionStatus.js +18 -1
  15. package/dist/components/FormRenderer/helpers/getSubmissionStatus.test.js +44 -6
  16. package/dist/components/FormRenderer/helpers/getUpdatedSectionStates.js +50 -0
  17. package/dist/components/FormRenderer/helpers/getUpdatedSectionStates.test.js +122 -0
  18. package/dist/components/FormRenderer/helpers/index.js +7 -1
  19. package/dist/components/SummaryList/SummaryList.js +7 -4
  20. package/dist/components/SummaryList/SummaryList.scss +10 -2
  21. package/dist/components/SummaryList/SummaryList.stories.mdx +64 -10
  22. package/dist/components/SummaryList/SummaryList.test.js +54 -0
  23. package/dist/components/TaskList/Task.js +83 -0
  24. package/dist/components/TaskList/Task.test.js +112 -0
  25. package/dist/components/TaskList/TaskList.js +112 -0
  26. package/dist/components/TaskList/TaskList.scss +70 -0
  27. package/dist/components/TaskList/TaskList.stories.mdx +57 -0
  28. package/dist/components/TaskList/TaskList.test.js +223 -0
  29. package/dist/components/TaskList/TaskState.js +42 -0
  30. package/dist/components/TaskList/TaskState.test.js +99 -0
  31. package/dist/components/TaskList/index.js +13 -0
  32. package/dist/hooks/useHooks.js +5 -1
  33. package/dist/index.js +8 -0
  34. package/dist/json/taskList.json +228 -0
  35. package/dist/json/userProfile.data.json +2 -1
  36. package/dist/models/ComponentTypes.js +3 -1
  37. package/dist/models/FormTypes.js +2 -0
  38. package/dist/models/HubFormats.js +3 -1
  39. package/dist/models/PageAction.js +5 -0
  40. package/dist/models/TaskStates.js +41 -0
  41. package/dist/models/index.js +8 -0
  42. package/dist/utils/Component/cleanAttributes.js +1 -1
  43. package/dist/utils/Component/getComponent.js +26 -0
  44. package/dist/utils/Component/isEditable.js +1 -1
  45. package/dist/utils/FormPage/getPageActions.js +6 -0
  46. package/dist/utils/Hub/getFormHub.js +4 -0
  47. package/dist/utils/Hub/getFormHub.test.js +10 -2
  48. package/dist/utils/Validate/additional/index.js +55 -0
  49. package/dist/utils/Validate/additional/index.test.js +69 -0
  50. package/dist/utils/Validate/additional/mustBeAfter.js +37 -0
  51. package/dist/utils/Validate/additional/mustBeAfter.test.js +98 -0
  52. package/dist/utils/Validate/additional/mustBeBefore.js +35 -0
  53. package/dist/utils/Validate/additional/mustBeBefore.test.js +66 -0
  54. package/dist/utils/Validate/additional/mustBeInTheFuture.js +42 -0
  55. package/dist/utils/Validate/additional/mustBeInTheFuture.test.js +34 -0
  56. package/dist/utils/Validate/additional/mustBeInThePast.js +44 -0
  57. package/dist/utils/Validate/additional/mustBeInThePast.test.js +34 -0
  58. package/dist/utils/Validate/additional/utils.js +66 -0
  59. package/dist/utils/Validate/validateComponent.js +38 -1
  60. package/dist/utils/Validate/validateComponent.test.js +48 -2
  61. package/dist/utils/Validate/validateDate.js +144 -0
  62. package/dist/utils/Validate/validateDate.test.js +112 -0
  63. package/dist/utils/Validate/validateRequired.js +11 -0
  64. package/dist/utils/Validate/validateRequired.test.js +12 -0
  65. package/dist/utils/Validate/validateTime.js +76 -0
  66. package/dist/utils/Validate/validateTime.test.js +55 -0
  67. package/package.json +3 -2
@@ -23,6 +23,8 @@ var _CheckYourAnswers = _interopRequireDefault(require("../CheckYourAnswers"));
23
23
 
24
24
  var _FormPage = _interopRequireDefault(require("../FormPage"));
25
25
 
26
+ var _TaskList = _interopRequireDefault(require("../TaskList"));
27
+
26
28
  var _handlers = _interopRequireDefault(require("./handlers"));
27
29
 
28
30
  var _helpers = _interopRequireDefault(require("./helpers"));
@@ -69,7 +71,10 @@ var FormRenderer = function FormRenderer(_ref) {
69
71
  _hooks = _ref.hooks,
70
72
  classBlock = _ref.classBlock,
71
73
  classModifiers = _ref.classModifiers,
72
- className = _ref.className;
74
+ className = _ref.className,
75
+ hide_title = _ref.hide_title,
76
+ summaryListClassModifiers = _ref.summaryListClassModifiers,
77
+ noChangeAction = _ref.noChangeAction;
73
78
 
74
79
  // Set up the initial states.
75
80
  var _useState = (0, _react.useState)({}),
@@ -95,7 +100,17 @@ var FormRenderer = function FormRenderer(_ref) {
95
100
  var _useState9 = (0, _react.useState)(_helpers.default.getFormState(pageId, pages, hub)),
96
101
  _useState10 = _slicedToArray(_useState9, 2),
97
102
  formState = _useState10[0],
98
- setFormState = _useState10[1]; // Set up hooks.
103
+ setFormState = _useState10[1];
104
+
105
+ var _useState11 = (0, _react.useState)({}),
106
+ _useState12 = _slicedToArray(_useState11, 2),
107
+ currentTask = _useState12[0],
108
+ setCurrentTask = _useState12[1];
109
+
110
+ var _useState13 = (0, _react.useState)({}),
111
+ _useState14 = _slicedToArray(_useState13, 2),
112
+ hubDetails = _useState14[0],
113
+ setHubDetails = _useState14[1]; // Set up hooks.
99
114
 
100
115
 
101
116
  var _useHooks = (0, _hooks2.useHooks)(),
@@ -143,6 +158,36 @@ var FormRenderer = function FormRenderer(_ref) {
143
158
  (0, _react.useEffect)(function () {
144
159
  hooks.onFormLoad();
145
160
  }, [hooks]);
161
+ (0, _react.useEffect)(function () {
162
+ setHubDetails(_hub);
163
+ }, [_hub]); // Update task list pages with form data and update states
164
+
165
+ (0, _react.useEffect)(function () {
166
+ var pages = currentTask.fullPages;
167
+
168
+ if (pages) {
169
+ pages.forEach(function (page) {
170
+ page.formData = data;
171
+ });
172
+ setCurrentTask(function (prev) {
173
+ return _objectSpread(_objectSpread({}, prev), {}, {
174
+ fullPages: pages
175
+ });
176
+ });
177
+ }
178
+
179
+ if (hubDetails !== null && hubDetails !== void 0 && hubDetails.sections) {
180
+ var _data$formStatus2;
181
+
182
+ var tasks = data !== null && data !== void 0 && (_data$formStatus2 = data.formStatus) !== null && _data$formStatus2 !== void 0 && _data$formStatus2.tasks ? data.formStatus.tasks : {};
183
+
184
+ var updatedSections = _helpers.default.getUpdatedSectionStates(hubDetails.sections, tasks);
185
+
186
+ setHubDetails(function (prev) {
187
+ return _objectSpread(_objectSpread({}, prev), updatedSections);
188
+ });
189
+ }
190
+ }, [currentTask.fullPages, data, hubDetails === null || hubDetails === void 0 ? void 0 : hubDetails.sections]);
146
191
 
147
192
  var onPageChange = function onPageChange(newPageId) {
148
193
  setPageId(newPageId);
@@ -163,10 +208,20 @@ var FormRenderer = function FormRenderer(_ref) {
163
208
  components: components
164
209
  }, _objectSpread(_objectSpread({}, data), patch), _models.EventTypes.SUBMIT);
165
210
 
166
- submissionData.formStatus = _helpers.default.getSubmissionStatus(type, pages, pageId, action, submissionData);
211
+ submissionData.formStatus = _helpers.default.getSubmissionStatus(type, pages, pageId, action, submissionData, currentTask);
167
212
 
168
213
  if (patch) {
169
214
  setData(submissionData);
215
+ }
216
+
217
+ var pageUpdate = function pageUpdate(next) {
218
+ return onPageChange(_helpers.default.getNextPageId(type, pages, pageId, action, next));
219
+ };
220
+
221
+ if (action.type === _models.PageAction.TYPES.SAVE_AND_NAVIGATE) {
222
+ pageUpdate = function pageUpdate() {
223
+ return _handlers.default.navigate(action, pageId, onPageChange);
224
+ };
170
225
  } // Now submit the data to the backend...
171
226
 
172
227
 
@@ -175,7 +230,7 @@ var FormRenderer = function FormRenderer(_ref) {
175
230
  setData(function (prev) {
176
231
  var next = _objectSpread(_objectSpread({}, prev), response);
177
232
 
178
- onPageChange(_helpers.default.getNextPageId(type, pages, pageId, action, next));
233
+ pageUpdate(next);
179
234
  return next;
180
235
  });
181
236
  }, function (errors) {
@@ -188,6 +243,23 @@ var FormRenderer = function FormRenderer(_ref) {
188
243
 
189
244
  var onCYARowAction = function onCYARowAction(page) {
190
245
  _handlers.default.cyaAction(page, pageId, onPageChange);
246
+ }; //Kick off a task, gather required pages and move to the correct point
247
+
248
+
249
+ var onTaskAction = function onTaskAction(currentTask) {
250
+ if (currentTask) {
251
+ currentTask.fullPages = [];
252
+ currentTask.pages.forEach(function (page) {
253
+ currentTask.fullPages.push(_helpers.default.getPage(page, pages));
254
+ });
255
+ setCurrentTask(currentTask);
256
+
257
+ if (currentTask.state === _models.TaskStates.TYPES.COMPLETE) {
258
+ onPageChange(_models.FormPages.CYA);
259
+ } else {
260
+ onPageChange(currentTask.pages[0]);
261
+ }
262
+ }
191
263
  }; // Handle actions from "Check your answers".
192
264
 
193
265
 
@@ -212,18 +284,51 @@ var FormRenderer = function FormRenderer(_ref) {
212
284
  });
213
285
  }
214
286
  }
287
+
288
+ if (action.type === _models.PageAction.TYPES.SAVE_AND_RETURN) {
289
+ if (_helpers.default.canCYASubmit(currentTask.fullPages, onError)) {
290
+ var _submissionData = _utils.default.Format.form({
291
+ pages: pages,
292
+ components: components
293
+ }, _objectSpread({}, data), _models.EventTypes.SUBMIT);
294
+
295
+ _submissionData.formStatus = _helpers.default.getSubmissionStatus(type, pages, pageId, action, _submissionData, currentTask);
296
+ setData(_submissionData);
297
+ hooks.onSubmit(action.type, _submissionData, function () {
298
+ return onPageChange(_models.FormPages.HUB);
299
+ }, function (errors) {
300
+ return _handlers.default.submissionError(errors, onError);
301
+ });
302
+ }
303
+ }
215
304
  };
216
305
 
217
306
  var classes = _utils.default.classBuilder(classBlock, classModifiers, className);
218
307
 
308
+ if (hub === _models.HubFormats.TASK) {
309
+ cya.actions = [{
310
+ type: _models.PageAction.TYPES.SAVE_AND_RETURN,
311
+ label: 'Submit',
312
+ validate: true
313
+ }];
314
+ }
315
+
219
316
  return /*#__PURE__*/_react.default.createElement("div", {
220
317
  className: classes()
221
- }, title && pageId === _models.FormPages.HUB && /*#__PURE__*/_react.default.createElement(_copReactComponents.LargeHeading, null, title), formState.cya && /*#__PURE__*/_react.default.createElement(_CheckYourAnswers.default, _extends({
222
- pages: pages
318
+ }, title && !hide_title && pageId === _models.FormPages.HUB && /*#__PURE__*/_react.default.createElement(_copReactComponents.LargeHeading, null, title), formState.cya && /*#__PURE__*/_react.default.createElement(_CheckYourAnswers.default, _extends({
319
+ pages: currentTask.fullPages ? currentTask.fullPages : pages
223
320
  }, cya, formState.cya, {
224
321
  onAction: onCYAAction,
225
- onRowAction: onCYARowAction
226
- })), formState.page && /*#__PURE__*/_react.default.createElement(_FormPage.default, {
322
+ onRowAction: onCYARowAction,
323
+ summaryListClassModifiers: summaryListClassModifiers,
324
+ hide_title: hide_title,
325
+ noChangeAction: noChangeAction
326
+ })), hub === _models.HubFormats.TASK && formState.pageId === _models.FormPages.HUB && /*#__PURE__*/_react.default.createElement(_TaskList.default, {
327
+ sections: hubDetails.sections,
328
+ refNumber: data['businessKey'],
329
+ refTitle: _hub.refTitle,
330
+ onTaskAction: onTaskAction
331
+ }), formState.page && /*#__PURE__*/_react.default.createElement(_FormPage.default, {
227
332
  page: formState.page,
228
333
  onAction: onPageAction
229
334
  }));
@@ -233,7 +338,7 @@ FormRenderer.propTypes = {
233
338
  title: _propTypes.default.string,
234
339
 
235
340
  /** See <a href="/?path=/docs/f-json--page#formtypes">FormTypes</a>. */
236
- type: _propTypes.default.oneOf([_models.FormTypes.CYA, _models.FormTypes.FORM, _models.FormTypes.HUB, _models.FormTypes.WIZARD]).isRequired,
341
+ type: _propTypes.default.oneOf([_models.FormTypes.CYA, _models.FormTypes.FORM, _models.FormTypes.HUB, _models.FormTypes.TASK, _models.FormTypes.WIZARD]).isRequired,
237
342
  components: _propTypes.default.arrayOf(_propTypes.default.object).isRequired,
238
343
  pages: _propTypes.default.arrayOf(_propTypes.default.object).isRequired,
239
344
  hub: _propTypes.default.object,
@@ -247,14 +352,20 @@ FormRenderer.propTypes = {
247
352
  }),
248
353
  classBlock: _propTypes.default.string,
249
354
  classModifiers: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.arrayOf(_propTypes.default.string)]),
250
- className: _propTypes.default.string
355
+ className: _propTypes.default.string,
356
+ hide_title: _propTypes.default.bool,
357
+ summaryListClassModifiers: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.arrayOf(_propTypes.default.string)]),
358
+ noChangeAction: _propTypes.default.bool
251
359
  };
252
360
  FormRenderer.defaultProps = {
253
361
  type: _models.FormTypes.HUB,
254
362
  components: [],
255
363
  pages: [],
256
364
  classBlock: DEFAULT_CLASS,
257
- classModifiers: []
365
+ classModifiers: [],
366
+ hide_title: false,
367
+ summaryListClassModifiers: [],
368
+ noChangeAction: false
258
369
  };
259
370
  var _default = FormRenderer;
260
371
  exports.default = _default;
@@ -1,14 +1,23 @@
1
1
  <!-- Global imports -->
2
+
2
3
  import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
3
- import { Button, Details, Heading, Link, Panel } from '@ukhomeoffice/cop-react-components';
4
+ import {
5
+ Button,
6
+ Details,
7
+ Heading,
8
+ Link,
9
+ Panel,
10
+ } from '@ukhomeoffice/cop-react-components';
4
11
  import { useState } from 'react';
5
12
  import withMock from 'storybook-addon-mock';
6
13
 
7
14
  <!-- Local imports -->
15
+
8
16
  import { addHook } from '../../hooks/useHooks';
9
17
  import FormRenderer from './FormRenderer';
10
18
 
11
19
  <!-- JSON documents -->
20
+
12
21
  import CIVIL_SERVANT from '../../json/areYouACivilServant.json';
13
22
  import FIRST_FORM from '../../json/firstForm.json';
14
23
  import GRADE from '../../json/grade.json';
@@ -16,52 +25,60 @@ import TEAMS from '../../json/team.json';
16
25
  import USER_PROFILE_DATA from '../../json/userProfile.data.json';
17
26
  import USER_PROFILE from '../../json/userProfile.json';
18
27
 
19
- <Meta title="Components/Form renderer" id="D-FormRenderer" component={ FormRenderer } decorators={[withMock]} />
28
+ <Meta
29
+ title='Components/Form renderer'
30
+ id='D-FormRenderer'
31
+ component={FormRenderer}
32
+ decorators={[withMock]}
33
+ />
20
34
 
21
- <Heading size="xl" caption="Components">Form renderer</Heading>
35
+ <Heading size='xl' caption='Components'>
36
+ Form renderer
37
+ </Heading>
22
38
 
23
39
  Renders a form with <Link href="https://ukhomeoffice.github.io/cop-react-components/?path=/docs/d-alert--default-story">COP React components</Link>,
24
40
  on the basis of a <Link href="/?path=/docs/f-json-form">JSON</Link> that describes which elements are required.
25
41
 
26
42
  <Canvas withToolbar>
27
- <Story name="Default" parameters={{
28
- mockData: [
29
- {
30
- url: `${USER_PROFILE_DATA.urls.refData}/areYouACivilServant`,
31
- method: 'GET',
32
- status: 200,
33
- response: CIVIL_SERVANT
34
- },
35
- {
36
- url: `${USER_PROFILE_DATA.urls.refData}/grade`,
37
- method: 'GET',
38
- status: 200,
39
- response: GRADE
40
- },
41
- {
42
- url: `${USER_PROFILE_DATA.urls.refData}/team`,
43
- method: 'GET',
44
- status: 200,
45
- response: TEAMS
46
- }
47
- ]
48
- }}>
43
+ <Story
44
+ name='Default'
45
+ parameters={{
46
+ mockData: [
47
+ {
48
+ url: `${USER_PROFILE_DATA.urls.refData}/areYouACivilServant`,
49
+ method: 'GET',
50
+ status: 200,
51
+ response: CIVIL_SERVANT,
52
+ },
53
+ {
54
+ url: `${USER_PROFILE_DATA.urls.refData}/grade`,
55
+ method: 'GET',
56
+ status: 200,
57
+ response: GRADE,
58
+ },
59
+ {
60
+ url: `${USER_PROFILE_DATA.urls.refData}/team`,
61
+ method: 'GET',
62
+ status: 200,
63
+ response: TEAMS,
64
+ },
65
+ ],
66
+ }}
67
+ >
49
68
  {() => {
50
- return (
51
- <FormRenderer {...USER_PROFILE} data={USER_PROFILE_DATA} />
52
- );
69
+ return <FormRenderer {...USER_PROFILE} data={USER_PROFILE_DATA} />;
53
70
  }}
54
71
  </Story>
55
72
  </Canvas>
56
73
 
57
- <Details summary="Properties" className="no-indent">
58
- <ArgsTable of={ FormRenderer } />
74
+ <Details summary='Properties' className='no-indent'>
75
+ <ArgsTable of={FormRenderer} />
59
76
  </Details>
60
77
 
61
78
  ## CYA type
62
79
 
63
80
  <Canvas>
64
- <Story name="CYA">
81
+ <Story name='CYA'>
65
82
  {() => {
66
83
  const [complete, setComplete] = useState(false);
67
84
  const [returnLater, setReturnLater] = useState(false);
@@ -82,7 +99,12 @@ on the basis of a <Link href="/?path=/docs/f-json-form">JSON</Link> that describ
82
99
  setReturnLater(!!pageId === false);
83
100
  });
84
101
  addHook('onSubmit', (type, payload, onSuccess, onError) => {
85
- console.log('onSubmit called of type', type, 'called, with the payload', payload);
102
+ console.log(
103
+ 'onSubmit called of type',
104
+ type,
105
+ 'called, with the payload',
106
+ payload
107
+ );
86
108
  onSuccess();
87
109
  });
88
110
  const reset = () => {
@@ -91,25 +113,69 @@ on the basis of a <Link href="/?path=/docs/f-json-form">JSON</Link> that describ
91
113
  };
92
114
  return (
93
115
  <>
94
- {!complete && !returnLater && <FormRenderer {...FIRST_FORM} data={{}} />}
95
- {complete &&
96
- <div>
97
- <Panel title="Submission successful">
98
- Your submission was successful.
99
- </Panel>
100
- <Button onClick={() => reset()}>Start again</Button>
101
- </div>
102
- }
103
- {!complete && returnLater &&
104
- <div>
105
- <Panel title="Saved to return later">
106
- You clicked on "Save and return later".
107
- </Panel>
108
- <Button onClick={() => reset()}>Start again</Button>
109
- </div>
110
- }
116
+ {!complete && !returnLater && (
117
+ <FormRenderer {...FIRST_FORM} data={{}} />
118
+ )}
119
+ {complete && (
120
+ <div>
121
+ <Panel title='Submission successful'>
122
+ Your submission was successful.
123
+ </Panel>
124
+ <Button onClick={() => reset()}>Start again</Button>
125
+ </div>
126
+ )}
127
+ {!complete && returnLater && (
128
+ <div>
129
+ <Panel title='Saved to return later'>
130
+ You clicked on "Save and return later".
131
+ </Panel>
132
+ <Button onClick={() => reset()}>Start again</Button>
133
+ </div>
134
+ )}
111
135
  </>
112
136
  );
113
137
  }}
114
138
  </Story>
115
139
  </Canvas>
140
+
141
+ ## Read-only style
142
+
143
+ <Canvas>
144
+ <Story
145
+ name='Read-only style'
146
+ parameters={{
147
+ mockData: [
148
+ {
149
+ url: `${USER_PROFILE_DATA.urls.refData}/areYouACivilServant`,
150
+ method: 'GET',
151
+ status: 200,
152
+ response: CIVIL_SERVANT,
153
+ },
154
+ {
155
+ url: `${USER_PROFILE_DATA.urls.refData}/grade`,
156
+ method: 'GET',
157
+ status: 200,
158
+ response: GRADE,
159
+ },
160
+ {
161
+ url: `${USER_PROFILE_DATA.urls.refData}/team`,
162
+ method: 'GET',
163
+ status: 200,
164
+ response: TEAMS,
165
+ },
166
+ ],
167
+ }}
168
+ >
169
+ {() => {
170
+ return (
171
+ <FormRenderer
172
+ {...USER_PROFILE}
173
+ data={USER_PROFILE_DATA}
174
+ summaryListClassModifiers='no-border'
175
+ hide_title={true}
176
+ noChangeAction={true}
177
+ />
178
+ );
179
+ }}
180
+ </Story>
181
+ </Canvas>