@aehrc/smart-forms-renderer 0.7.0 → 0.7.2

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 (189) hide show
  1. package/README.md +5 -5
  2. package/lib/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.js +1 -1
  3. package/lib/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.js.map +1 -1
  4. package/lib/components/FormComponents/DecimalItem/DecimalItem.js +3 -2
  5. package/lib/components/FormComponents/DecimalItem/DecimalItem.js.map +1 -1
  6. package/lib/components/FormComponents/GridGroup/GridGroup.js +2 -2
  7. package/lib/components/FormComponents/GridGroup/GridGroup.js.map +1 -1
  8. package/lib/components/FormComponents/GridGroup/GridRow.js +2 -2
  9. package/lib/components/FormComponents/GridGroup/GridRow.js.map +1 -1
  10. package/lib/components/FormComponents/GroupItem/GroupChildItemSwitcher.d.ts +10 -0
  11. package/lib/components/FormComponents/GroupItem/GroupChildItemSwitcher.js +73 -0
  12. package/lib/components/FormComponents/GroupItem/GroupChildItemSwitcher.js.map +1 -0
  13. package/lib/components/FormComponents/GroupItem/GroupItem.js +3 -3
  14. package/lib/components/FormComponents/GroupItem/GroupItem.js.map +1 -1
  15. package/lib/components/FormComponents/GroupItem/ItemSwitcher.d.ts +10 -0
  16. package/lib/components/FormComponents/GroupItem/ItemSwitcher.js +81 -0
  17. package/lib/components/FormComponents/GroupItem/ItemSwitcher.js.map +1 -0
  18. package/lib/components/FormComponents/GroupItem/TopLevelGroupItemSwitcher.d.ts +10 -0
  19. package/lib/components/FormComponents/GroupItem/TopLevelGroupItemSwitcher.js +45 -0
  20. package/lib/components/FormComponents/GroupItem/TopLevelGroupItemSwitcher.js.map +1 -0
  21. package/lib/components/FormComponents/IntegerItem/IntegerItem.js +3 -2
  22. package/lib/components/FormComponents/IntegerItem/IntegerItem.js.map +1 -1
  23. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.js +2 -2
  24. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.js.map +1 -1
  25. package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js +4 -3
  26. package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js.map +1 -1
  27. package/lib/components/FormComponents/RepeatGroup/index.d.ts +1 -0
  28. package/lib/{theme/overrides/Typography.js → components/FormComponents/RepeatGroup/index.js} +2 -15
  29. package/lib/components/FormComponents/RepeatGroup/index.js.map +1 -0
  30. package/lib/components/FormComponents/RepeatItem/RepeatItem.js +1 -1
  31. package/lib/components/FormComponents/RepeatItem/RepeatItem.js.map +1 -1
  32. package/lib/components/FormComponents/SingleItem/index.d.ts +1 -0
  33. package/lib/components/FormComponents/SingleItem/index.js +18 -0
  34. package/lib/components/FormComponents/SingleItem/index.js.map +1 -0
  35. package/lib/components/FormComponents/StringItem/StringItem.js +3 -2
  36. package/lib/components/FormComponents/StringItem/StringItem.js.map +1 -1
  37. package/lib/components/FormComponents/Tables/QItemGroupTable.js +7 -5
  38. package/lib/components/FormComponents/Tables/QItemGroupTable.js.map +1 -1
  39. package/lib/components/FormComponents/Tables/QItemGroupTableRow.js +2 -2
  40. package/lib/components/FormComponents/Tables/QItemGroupTableRow.js.map +1 -1
  41. package/lib/components/FormComponents/Tables/index.d.ts +1 -0
  42. package/lib/components/FormComponents/Tables/index.js +18 -0
  43. package/lib/components/FormComponents/Tables/index.js.map +1 -0
  44. package/lib/components/FormComponents/TextItem/TextItem.js +3 -2
  45. package/lib/components/FormComponents/TextItem/TextItem.js.map +1 -1
  46. package/lib/components/FormComponents/index.d.ts +3 -0
  47. package/lib/components/FormComponents/index.js +20 -0
  48. package/lib/components/FormComponents/index.js.map +1 -0
  49. package/lib/components/Renderer/BaseRenderer.js +26 -14
  50. package/lib/components/Renderer/BaseRenderer.js.map +1 -1
  51. package/lib/components/Renderer/FormBodyCollapsible.js +2 -2
  52. package/lib/components/Renderer/FormBodyCollapsible.js.map +1 -1
  53. package/lib/components/Renderer/FormBodyTabbed.js +2 -2
  54. package/lib/components/Renderer/FormBodyTabbed.js.map +1 -1
  55. package/lib/components/Renderer/FormTopLevelItem.d.ts +4 -4
  56. package/lib/components/Renderer/FormTopLevelItem.js +14 -1
  57. package/lib/components/Renderer/FormTopLevelItem.js.map +1 -1
  58. package/lib/components/index.d.ts +1 -0
  59. package/lib/components/index.js +1 -0
  60. package/lib/components/index.js.map +1 -1
  61. package/lib/hooks/useDecimalUpdateFromProp.d.ts +2 -0
  62. package/lib/{utils/isHidden.js → hooks/useDecimalUpdateFromProp.js} +12 -13
  63. package/lib/hooks/useDecimalUpdateFromProp.js.map +1 -0
  64. package/lib/hooks/useInitialiseRepeatAnswers.d.ts +2 -2
  65. package/lib/hooks/useInitialiseRepeatAnswers.js +21 -15
  66. package/lib/hooks/useInitialiseRepeatAnswers.js.map +1 -1
  67. package/lib/hooks/useInitialiseRepeatGroups.d.ts +2 -1
  68. package/lib/hooks/useInitialiseRepeatGroups.js +21 -15
  69. package/lib/hooks/useInitialiseRepeatGroups.js.map +1 -1
  70. package/lib/hooks/useIntegerUpdateFromProp.d.ts +2 -0
  71. package/lib/hooks/useIntegerUpdateFromProp.js +29 -0
  72. package/lib/hooks/useIntegerUpdateFromProp.js.map +1 -0
  73. package/lib/hooks/useNumberInput.d.ts +3 -0
  74. package/lib/hooks/useNumberInput.js +31 -0
  75. package/lib/hooks/useNumberInput.js.map +1 -0
  76. package/lib/hooks/useNumberUpdateFromProp.d.ts +2 -0
  77. package/lib/hooks/useNumberUpdateFromProp.js +29 -0
  78. package/lib/hooks/useNumberUpdateFromProp.js.map +1 -0
  79. package/lib/hooks/useRepeatItemState.d.ts +5 -0
  80. package/lib/hooks/useRepeatItemState.js +35 -0
  81. package/lib/hooks/useRepeatItemState.js.map +1 -0
  82. package/lib/hooks/useRepeatItemUpdateFromProp.d.ts +2 -0
  83. package/lib/hooks/useRepeatItemUpdateFromProp.js +29 -0
  84. package/lib/hooks/useRepeatItemUpdateFromProp.js.map +1 -0
  85. package/lib/hooks/useStringField.d.ts +2 -0
  86. package/lib/hooks/useStringField.js +29 -0
  87. package/lib/hooks/useStringField.js.map +1 -0
  88. package/lib/hooks/useStringInput.d.ts +3 -0
  89. package/lib/hooks/useStringInput.js +31 -0
  90. package/lib/hooks/useStringInput.js.map +1 -0
  91. package/lib/hooks/useStringUpdateFromProp.d.ts +2 -0
  92. package/lib/hooks/useStringUpdateFromProp.js +29 -0
  93. package/lib/hooks/useStringUpdateFromProp.js.map +1 -0
  94. package/lib/index.d.ts +16 -0
  95. package/lib/index.js +27 -0
  96. package/lib/index.js.map +1 -1
  97. package/lib/interfaces/questionnaireStore.interface.d.ts +1 -0
  98. package/lib/stores/useQuestionnaireResponseStore.d.ts +1 -1
  99. package/lib/stores/useQuestionnaireResponseStore.js +15 -10
  100. package/lib/stores/useQuestionnaireResponseStore.js.map +1 -1
  101. package/lib/stores/useQuestionnaireStore.d.ts +2 -1
  102. package/lib/stores/useQuestionnaireStore.js +5 -2
  103. package/lib/stores/useQuestionnaireStore.js.map +1 -1
  104. package/lib/theme/overrides/Overrides.js +1 -2
  105. package/lib/theme/overrides/Overrides.js.map +1 -1
  106. package/lib/utils/calculatedExpression.js +2 -2
  107. package/lib/utils/calculatedExpression.js.map +1 -1
  108. package/lib/utils/formChanges.d.ts +18 -0
  109. package/lib/utils/formChanges.js +91 -0
  110. package/lib/utils/formChanges.js.map +1 -0
  111. package/lib/utils/formChangesOld.d.ts +18 -0
  112. package/lib/utils/formChangesOld.js +91 -0
  113. package/lib/utils/formChangesOld.js.map +1 -0
  114. package/lib/utils/index.d.ts +1 -0
  115. package/lib/utils/index.js +18 -0
  116. package/lib/utils/index.js.map +1 -0
  117. package/lib/utils/mapItem.d.ts +2 -2
  118. package/lib/utils/mapItem.js +3 -3
  119. package/lib/utils/mapItem.js.map +1 -1
  120. package/lib/utils/qItem.d.ts +2 -0
  121. package/lib/utils/qItem.js +22 -0
  122. package/lib/utils/qItem.js.map +1 -1
  123. package/lib/utils/qrItem.d.ts +1 -1
  124. package/lib/utils/qrItem.js +92 -93
  125. package/lib/utils/qrItem.js.map +1 -1
  126. package/lib/utils/questionnaireStoreUtils/createQuestionaireModel.js +4 -0
  127. package/lib/utils/questionnaireStoreUtils/createQuestionaireModel.js.map +1 -1
  128. package/lib/utils/repopulate.d.ts +9 -0
  129. package/lib/utils/repopulate.js +133 -0
  130. package/lib/utils/repopulate.js.map +1 -0
  131. package/lib/utils/repopulateGenerateItems.d.ts +9 -0
  132. package/lib/utils/repopulateGenerateItems.js +133 -0
  133. package/lib/utils/repopulateGenerateItems.js.map +1 -0
  134. package/lib/utils/repopulateIntoResponse.d.ts +3 -0
  135. package/lib/utils/repopulateIntoResponse.js +92 -0
  136. package/lib/utils/repopulateIntoResponse.js.map +1 -0
  137. package/lib/utils/repopulateItems.d.ts +9 -0
  138. package/lib/utils/repopulateItems.js +131 -0
  139. package/lib/utils/repopulateItems.js.map +1 -0
  140. package/lib/utils/repopulateRepeatGroup.d.ts +4 -0
  141. package/lib/utils/repopulateRepeatGroup.js +54 -0
  142. package/lib/utils/repopulateRepeatGroup.js.map +1 -0
  143. package/package.json +3 -1
  144. package/src/components/FormComponents/ChoiceItems/ChoiceAutocompleteItem.tsx +1 -1
  145. package/src/components/FormComponents/DecimalItem/DecimalItem.tsx +3 -2
  146. package/src/components/FormComponents/GridGroup/GridGroup.tsx +2 -2
  147. package/src/components/FormComponents/GridGroup/GridRow.tsx +2 -2
  148. package/src/components/FormComponents/GroupItem/GroupItem.tsx +3 -3
  149. package/src/components/FormComponents/IntegerItem/IntegerItem.tsx +3 -2
  150. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceAutocompleteItem.tsx +1 -1
  151. package/src/components/FormComponents/RepeatGroup/RepeatGroup.tsx +4 -3
  152. package/src/{theme/overrides/Backdrop.ts → components/FormComponents/RepeatGroup/index.ts} +1 -17
  153. package/src/components/FormComponents/RepeatItem/RepeatItem.tsx +1 -1
  154. package/src/components/FormComponents/SingleItem/index.ts +18 -0
  155. package/src/components/FormComponents/StringItem/StringItem.tsx +3 -2
  156. package/src/components/FormComponents/Tables/QItemGroupTable.tsx +15 -6
  157. package/src/components/FormComponents/Tables/QItemGroupTableRow.tsx +2 -2
  158. package/src/components/FormComponents/Tables/index.ts +18 -0
  159. package/src/components/FormComponents/TextItem/TextItem.tsx +3 -2
  160. package/src/components/FormComponents/index.ts +20 -0
  161. package/src/components/Renderer/BaseRenderer.tsx +39 -18
  162. package/src/components/Renderer/FormBodyCollapsible.tsx +2 -2
  163. package/src/components/Renderer/FormBodyTabbed.tsx +2 -2
  164. package/src/components/Renderer/FormTopLevelItem.tsx +33 -4
  165. package/src/components/index.ts +1 -0
  166. package/src/hooks/index.ts +1 -0
  167. package/src/hooks/useInitialiseRepeatAnswers.ts +28 -17
  168. package/src/hooks/useInitialiseRepeatGroups.ts +28 -17
  169. package/src/hooks/useNumberInput.ts +37 -0
  170. package/src/hooks/useStringInput.ts +37 -0
  171. package/src/index.ts +43 -0
  172. package/src/interfaces/questionnaireStore.interface.ts +1 -0
  173. package/src/stores/useQuestionnaireResponseStore.ts +14 -10
  174. package/src/stores/useQuestionnaireStore.ts +10 -3
  175. package/src/theme/overrides/Overrides.ts +0 -2
  176. package/src/utils/calculatedExpression.ts +2 -2
  177. package/src/utils/formChanges.ts +141 -0
  178. package/src/utils/index.ts +18 -0
  179. package/src/utils/mapItem.ts +6 -4
  180. package/src/utils/qItem.ts +29 -0
  181. package/src/utils/qrItem.ts +104 -94
  182. package/src/utils/questionnaireStoreUtils/createQuestionaireModel.ts +4 -0
  183. package/src/utils/repopulateIntoResponse.ts +153 -0
  184. package/src/utils/repopulateItems.ts +207 -0
  185. package/src/utils/repopulateRepeatGroup.ts +68 -0
  186. package/lib/theme/overrides/Typography.d.ts +0 -13
  187. package/lib/theme/overrides/Typography.js.map +0 -1
  188. package/lib/utils/isHidden.d.ts +0 -3
  189. package/lib/utils/isHidden.js.map +0 -1
@@ -17,7 +17,7 @@
17
17
 
18
18
  import React from 'react';
19
19
  import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
20
- import { createQrGroup, updateQrGroup } from '../../../utils/qrItem';
20
+ import { createQrGroup, updateQrItemsInGroup } from '../../../utils/qrItem';
21
21
  import SingleItem from '../SingleItem/SingleItem';
22
22
  import { getQrItemsIndex } from '../../../utils/mapItem';
23
23
  import { StandardTableCell } from './Table.styles';
@@ -42,7 +42,7 @@ function QItemGroupTableRow(props: Props) {
42
42
 
43
43
  function handleQrRowItemChange(newQrRowItem: QuestionnaireResponseItem) {
44
44
  const qrRow: QuestionnaireResponseItem = { ...row };
45
- updateQrGroup(newQrRowItem, null, qrRow, qItemsIndexMap);
45
+ updateQrItemsInGroup(newQrRowItem, null, qrRow, qItemsIndexMap);
46
46
  onQrItemChange(qrRow);
47
47
  }
48
48
 
@@ -0,0 +1,18 @@
1
+ /*
2
+ * Copyright 2023 Commonwealth Scientific and Industrial Research
3
+ * Organisation (CSIRO) ABN 41 687 119 230.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ export { default as GroupTable } from './QItemGroupTable';
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import React, { useCallback, useState } from 'react';
18
+ import React, { useCallback } from 'react';
19
19
  import type {
20
20
  PropsWithIsRepeatedAttribute,
21
21
  PropsWithQrItemChangeHandler
@@ -30,6 +30,7 @@ import { FullWidthFormComponentBox } from '../../Box.styles';
30
30
  import TextField from './TextField';
31
31
  import useStringCalculatedExpression from '../../../hooks/useStringCalculatedExpression';
32
32
  import ItemFieldGrid from '../ItemParts/ItemFieldGrid';
33
+ import useStringInput from '../../../hooks/useStringInput';
33
34
 
34
35
  interface TextItemProps
35
36
  extends PropsWithQrItemChangeHandler<QuestionnaireResponseItem>,
@@ -57,7 +58,7 @@ function TextItem(props: TextItemProps) {
57
58
  if (qrItem?.answer && qrItem?.answer[0].valueString) {
58
59
  valueText = qrItem.answer[0].valueString;
59
60
  }
60
- const [input, setInput] = useState(valueText);
61
+ const [input, setInput] = useStringInput(valueText);
61
62
 
62
63
  // Perform validation checks
63
64
  const feedback = useValidationError(input, regexValidation, maxLength);
@@ -0,0 +1,20 @@
1
+ /*
2
+ * Copyright 2023 Commonwealth Scientific and Industrial Research
3
+ * Organisation (CSIRO) ABN 41 687 119 230.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ export * from './SingleItem';
19
+ export * from './RepeatGroup';
20
+ export * from './Tables';
@@ -15,13 +15,17 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import React from 'react';
18
+ import React, { useMemo } from 'react';
19
19
  import Container from '@mui/material/Container';
20
20
  import Fade from '@mui/material/Fade';
21
21
  import FormTopLevelItem from './FormTopLevelItem';
22
22
  import type { QuestionnaireResponse, QuestionnaireResponseItem } from 'fhir/r4';
23
23
  import useQuestionnaireStore from '../../stores/useQuestionnaireStore';
24
24
  import useQuestionnaireResponseStore from '../../stores/useQuestionnaireResponseStore';
25
+ import cloneDeep from 'lodash.clonedeep';
26
+ import { getQrItemsIndex, mapQItemsIndex } from '../../utils/mapItem';
27
+ import { updateQrItemsInGroup } from '../../utils/qrItem';
28
+ import type { QrRepeatGroup } from '../../interfaces/repeatGroup.interface';
25
29
 
26
30
  function BaseRenderer() {
27
31
  const sourceQuestionnaire = useQuestionnaireStore((state) => state.sourceQuestionnaire);
@@ -29,48 +33,65 @@ function BaseRenderer() {
29
33
  const updatableResponse = useQuestionnaireResponseStore((state) => state.updatableResponse);
30
34
  const updateResponse = useQuestionnaireResponseStore((state) => state.updateResponse);
31
35
 
32
- function handleTopLevelQRItemChange(newTopLevelQItem: QuestionnaireResponseItem, index: number) {
33
- if (!updatableResponse.item || updatableResponse.item.length === 0) {
36
+ const qItemsIndexMap = useMemo(() => mapQItemsIndex(sourceQuestionnaire), [sourceQuestionnaire]);
37
+
38
+ function handleTopLevelQRItemSingleChange(
39
+ newTopLevelQRItem: QuestionnaireResponseItem,
40
+ index: number
41
+ ) {
42
+ const updatedResponse: QuestionnaireResponse = cloneDeep(updatableResponse);
43
+ if (!updatedResponse.item || updatedResponse.item.length === 0) {
34
44
  return;
35
45
  }
36
46
 
37
- const updatedItems = [...updatableResponse.item]; // Copy the original array of items
38
- updatedItems[index] = newTopLevelQItem; // Modify the item at the specified index
47
+ const updatedItems = [...updatedResponse.item]; // Copy the original array of items
48
+ updatedItems[index] = newTopLevelQRItem; // Modify the item at the specified index
39
49
 
40
- const updatedResponse: QuestionnaireResponse = {
41
- ...updatableResponse,
42
- item: updatedItems
43
- };
50
+ updatedResponse.item = updatedItems;
51
+
52
+ updateExpressions(updatedResponse);
53
+ updateResponse(updatedResponse);
54
+ }
55
+
56
+ function handleTopLevelQRItemMultipleChange(newTopLevelQRItems: QrRepeatGroup) {
57
+ const updatedResponse: QuestionnaireResponse = cloneDeep(updatableResponse);
58
+ if (!updatedResponse.item || updatedResponse.item.length === 0) {
59
+ return;
60
+ }
61
+
62
+ updateQrItemsInGroup(null, newTopLevelQRItems, updatedResponse, qItemsIndexMap);
44
63
 
45
64
  updateExpressions(updatedResponse);
46
65
  updateResponse(updatedResponse);
47
66
  }
48
67
 
49
68
  const topLevelQItems = sourceQuestionnaire.item;
50
- const topLevelQRItems = updatableResponse.item;
69
+ const topLevelQRItems = cloneDeep(updatableResponse.item) ?? [];
51
70
 
52
71
  if (!topLevelQItems) {
53
72
  return <>Questionnaire does not have any items</>;
54
73
  }
55
74
 
75
+ // If an item has multiple answers, it is a repeat group
76
+ const topLevelQRItemsByIndex: (QuestionnaireResponseItem | QuestionnaireResponseItem[])[] =
77
+ getQrItemsIndex(topLevelQItems, topLevelQRItems, qItemsIndexMap);
78
+
56
79
  return (
57
80
  <Fade in={true} timeout={500}>
58
81
  <Container maxWidth="xl">
59
82
  {topLevelQItems.map((qItem, index) => {
60
- const qrItem = topLevelQRItems
61
- ? topLevelQRItems[index]
62
- : {
63
- linkId: qItem.linkId,
64
- text: qItem.text
65
- };
83
+ const qrItemOrItems = topLevelQRItemsByIndex[index];
66
84
 
67
85
  return (
68
86
  <FormTopLevelItem
69
87
  key={qItem.linkId}
70
88
  topLevelQItem={qItem}
71
- topLevelQRItem={qrItem}
89
+ topLevelQRItemOrItems={qrItemOrItems}
72
90
  onQrItemChange={(newTopLevelQRItem) =>
73
- handleTopLevelQRItemChange(newTopLevelQRItem, index)
91
+ handleTopLevelQRItemSingleChange(newTopLevelQRItem, index)
92
+ }
93
+ onQrRepeatGroupChange={(newTopLevelQRItems) =>
94
+ handleTopLevelQRItemMultipleChange(newTopLevelQRItems)
74
95
  }
75
96
  />
76
97
  );
@@ -19,7 +19,7 @@ import React, { useMemo } from 'react';
19
19
  import Stack from '@mui/material/Stack';
20
20
  import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
21
21
  import { getQrItemsIndex, mapQItemsIndex } from '../../utils/mapItem';
22
- import { updateQrGroup } from '../../utils/qrItem';
22
+ import { updateQrItemsInGroup } from '../../utils/qrItem';
23
23
  import type { PropsWithQrItemChangeHandler } from '../../interfaces/renderProps.interface';
24
24
  import useQuestionnaireStore from '../../stores/useQuestionnaireStore';
25
25
  import FormBodySingleCollapsibleWrapper from './FormBodySingleCollapsibleWrapper';
@@ -46,7 +46,7 @@ function FormBodyCollapsibleWrapper(props: FormBodyCollapsibleProps) {
46
46
  const qrItems = topLevelQRItem.item;
47
47
 
48
48
  function handleQrGroupChange(qrItem: QuestionnaireResponseItem) {
49
- updateQrGroup(qrItem, null, topLevelQRItem, indexMap);
49
+ updateQrItemsInGroup(qrItem, null, topLevelQRItem, indexMap);
50
50
  onQrItemChange(topLevelQRItem);
51
51
  }
52
52
 
@@ -22,7 +22,7 @@ import TabContext from '@mui/lab/TabContext';
22
22
  import TabPanel from '@mui/lab/TabPanel';
23
23
  import { getQrItemsIndex, mapQItemsIndex } from '../../utils/mapItem';
24
24
  import GroupItem from '../FormComponents/GroupItem/GroupItem';
25
- import { updateQrGroup } from '../../utils/qrItem';
25
+ import { updateQrItemsInGroup } from '../../utils/qrItem';
26
26
  import FormBodyTabListWrapper from '../Tabs/FormBodyTabListWrapper';
27
27
  import type { PropsWithQrItemChangeHandler } from '../../interfaces/renderProps.interface';
28
28
  import useQuestionnaireStore from '../../stores/useQuestionnaireStore';
@@ -47,7 +47,7 @@ function FormBodyTabbed(props: FormBodyTabbedProps) {
47
47
  const qrItems = topLevelQRItem.item;
48
48
 
49
49
  function handleQrGroupChange(qrItem: QuestionnaireResponseItem) {
50
- updateQrGroup(qrItem, null, topLevelQRItem, indexMap);
50
+ updateQrItemsInGroup(qrItem, null, topLevelQRItem, indexMap);
51
51
  onQrItemChange(topLevelQRItem);
52
52
  }
53
53
 
@@ -21,17 +21,24 @@ import FormBodyTabbed from './FormBodyTabbed';
21
21
  import { containsTabs, isTabContainer } from '../../utils/tabs';
22
22
  import GroupItem from '../FormComponents/GroupItem/GroupItem';
23
23
  import SingleItem from '../FormComponents/SingleItem/SingleItem';
24
- import type { PropsWithQrItemChangeHandler } from '../../interfaces/renderProps.interface';
24
+ import type {
25
+ PropsWithQrItemChangeHandler,
26
+ PropsWithQrRepeatGroupChangeHandler
27
+ } from '../../interfaces/renderProps.interface';
25
28
  import FormBodyCollapsible from './FormBodyCollapsible';
26
29
  import useResponsive from '../../hooks/useResponsive';
30
+ import useHidden from '../../hooks/useHidden';
31
+ import GroupItemSwitcher from '../FormComponents/GroupItem/GroupItemSwitcher';
27
32
 
28
- interface FormTopLevelItemProps extends PropsWithQrItemChangeHandler<QuestionnaireResponseItem> {
33
+ interface FormTopLevelItemProps
34
+ extends PropsWithQrItemChangeHandler<QuestionnaireResponseItem>,
35
+ PropsWithQrRepeatGroupChangeHandler {
29
36
  topLevelQItem: QuestionnaireItem;
30
- topLevelQRItem: QuestionnaireResponseItem;
37
+ topLevelQRItemOrItems: QuestionnaireResponseItem | QuestionnaireResponseItem[];
31
38
  }
32
39
 
33
40
  function FormTopLevelItem(props: FormTopLevelItemProps) {
34
- const { topLevelQItem, topLevelQRItem, onQrItemChange } = props;
41
+ const { topLevelQItem, topLevelQRItemOrItems, onQrItemChange, onQrRepeatGroupChange } = props;
35
42
 
36
43
  const itemIsTabContainer = isTabContainer(topLevelQItem);
37
44
  const itemContainsTabs = containsTabs(topLevelQItem);
@@ -40,6 +47,28 @@ function FormTopLevelItem(props: FormTopLevelItemProps) {
40
47
 
41
48
  const itemIsGroup = topLevelQItem.type === 'group';
42
49
 
50
+ const itemIsHidden = useHidden(topLevelQItem);
51
+ if (itemIsHidden) {
52
+ return null;
53
+ }
54
+
55
+ // If item has multiple answers, use a group item switcher to determine how to render it.
56
+ const hasMultipleAnswers = Array.isArray(topLevelQRItemOrItems);
57
+ if (hasMultipleAnswers) {
58
+ return (
59
+ <GroupItemSwitcher
60
+ qItem={topLevelQItem}
61
+ qrItemOrItems={topLevelQRItemOrItems}
62
+ groupCardElevation={1}
63
+ onQrItemChange={onQrItemChange}
64
+ onQrRepeatGroupChange={onQrRepeatGroupChange}
65
+ />
66
+ );
67
+ }
68
+
69
+ // At this point, item only has one answer
70
+ const topLevelQRItem = topLevelQRItemOrItems;
71
+
43
72
  // If form is tabbed, it is rendered as a tabbed form
44
73
  if (itemContainsTabs || itemIsTabContainer) {
45
74
  if (isDesktop) {
@@ -16,3 +16,4 @@
16
16
  */
17
17
 
18
18
  export * from './Renderer';
19
+ export * from './FormComponents';
@@ -0,0 +1 @@
1
+ export { default as useHidden } from './useHidden';
@@ -15,28 +15,39 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import type { QuestionnaireResponseItem } from 'fhir/r4';
18
+ import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
19
19
  import type { RepeatAnswer } from '../interfaces/repeatItem.interface';
20
20
  import { nanoid } from 'nanoid';
21
+ import { useMemo } from 'react';
21
22
 
22
- function useInitialiseRepeatAnswers(qrItem: QuestionnaireResponseItem): RepeatAnswer[] {
23
- let initialRepeatAnswers: RepeatAnswer[] = [
24
- {
25
- nanoId: nanoid(),
26
- answer: null
27
- }
28
- ];
23
+ function useInitialiseRepeatAnswers(
24
+ qItem: QuestionnaireItem,
25
+ qrItem: QuestionnaireResponseItem
26
+ ): RepeatAnswer[] {
27
+ return useMemo(
28
+ () => {
29
+ let initialRepeatAnswers: RepeatAnswer[] = [
30
+ {
31
+ nanoId: nanoid(),
32
+ answer: null
33
+ }
34
+ ];
29
35
 
30
- if (qrItem?.answer) {
31
- initialRepeatAnswers = qrItem.answer.map((answer) => {
32
- return {
33
- nanoId: nanoid(),
34
- answer
35
- };
36
- });
37
- }
36
+ if (qrItem?.answer) {
37
+ initialRepeatAnswers = qrItem.answer.map((answer) => {
38
+ return {
39
+ nanoId: nanoid(),
40
+ answer
41
+ };
42
+ });
43
+ }
38
44
 
39
- return initialRepeatAnswers;
45
+ return initialRepeatAnswers;
46
+ },
47
+ // init initialRepeatAnswers on first render only, leave dependency array empty
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ [qItem]
50
+ );
40
51
  }
41
52
 
42
53
  export default useInitialiseRepeatAnswers;
@@ -16,27 +16,38 @@
16
16
  */
17
17
 
18
18
  import type { QuestionnaireResponseItem } from 'fhir/r4';
19
+ import { QuestionnaireItem } from 'fhir/r4';
19
20
  import { nanoid } from 'nanoid';
20
21
  import type { RepeatGroupSingle } from '../interfaces/repeatGroup.interface';
22
+ import { useMemo } from 'react';
21
23
 
22
- function useInitialiseRepeatGroups(qrItems: QuestionnaireResponseItem[]): RepeatGroupSingle[] {
23
- let initialRepeatGroupAnswers: RepeatGroupSingle[] = [
24
- {
25
- nanoId: nanoid(),
26
- qrItem: null
27
- }
28
- ];
24
+ function useInitialiseRepeatGroups(
25
+ qItem: QuestionnaireItem,
26
+ qrItems: QuestionnaireResponseItem[]
27
+ ): RepeatGroupSingle[] {
28
+ return useMemo(
29
+ () => {
30
+ let initialRepeatGroupAnswers: RepeatGroupSingle[] = [
31
+ {
32
+ nanoId: nanoid(),
33
+ qrItem: null
34
+ }
35
+ ];
29
36
 
30
- if (qrItems.length > 0) {
31
- initialRepeatGroupAnswers = qrItems.map((qrItem) => {
32
- return {
33
- nanoId: nanoid(),
34
- qrItem
35
- };
36
- });
37
- }
38
-
39
- return initialRepeatGroupAnswers;
37
+ if (qrItems.length > 0) {
38
+ initialRepeatGroupAnswers = qrItems.map((qrItem) => {
39
+ return {
40
+ nanoId: nanoid(),
41
+ qrItem
42
+ };
43
+ });
44
+ }
45
+ return initialRepeatGroupAnswers;
46
+ },
47
+ // init initialRepeatAnswers on first render only, leave dependency array empty
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ [qItem]
50
+ );
40
51
  }
41
52
 
42
53
  export default useInitialiseRepeatGroups;
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright 2023 Commonwealth Scientific and Industrial Research
3
+ * Organisation (CSIRO) ABN 41 687 119 230.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import { Dispatch, SetStateAction, useEffect, useState } from 'react';
19
+
20
+ function useNumberInput(valueFromProps: number): [number, Dispatch<SetStateAction<number>>] {
21
+ const [value, setValue] = useState(valueFromProps);
22
+
23
+ useEffect(
24
+ () => {
25
+ if (value !== valueFromProps) {
26
+ setValue(valueFromProps);
27
+ }
28
+ },
29
+ // Only trigger this effect if prop value changes
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ [valueFromProps]
32
+ );
33
+
34
+ return [value, setValue];
35
+ }
36
+
37
+ export default useNumberInput;
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright 2023 Commonwealth Scientific and Industrial Research
3
+ * Organisation (CSIRO) ABN 41 687 119 230.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import { Dispatch, SetStateAction, useEffect, useState } from 'react';
19
+
20
+ function useStringInput(valueFromProps: string): [string, Dispatch<SetStateAction<string>>] {
21
+ const [input, setInput] = useState(valueFromProps);
22
+
23
+ useEffect(
24
+ () => {
25
+ if (input !== valueFromProps) {
26
+ setInput(valueFromProps);
27
+ }
28
+ },
29
+ // Only trigger this effect if prop value changes
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ [valueFromProps]
32
+ );
33
+
34
+ return [input, setInput];
35
+ }
36
+
37
+ export default useStringInput;
package/src/index.ts CHANGED
@@ -3,9 +3,15 @@ import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
3
3
  import { createQuestionnaireResponse } from './utils/qrItem';
4
4
  import useQuestionnaireResponseStore from './stores/useQuestionnaireResponseStore';
5
5
  import { removeHiddenAnswers } from './utils/removeHidden';
6
+ import type { ItemToRepopulate } from './utils/repopulateItems';
7
+ import { getItemsToRepopulate } from './utils/repopulateItems';
8
+ import { repopulateItemsIntoResponse } from './utils/repopulateIntoResponse';
6
9
 
7
10
  export * from './components';
8
11
  export * from './stores';
12
+ export * from './hooks';
13
+ export * from './utils';
14
+ export type { ItemToRepopulate };
9
15
 
10
16
  /**
11
17
  * Build the form with an initial Questionnaire and an optional filled QuestionnaireResponse.
@@ -72,3 +78,40 @@ export function removeHiddenAnswersFromResponse(
72
78
  enableWhenExpressions
73
79
  });
74
80
  }
81
+
82
+ /**
83
+ * Re-populate stuff
84
+ *
85
+ * @author Sean Fong
86
+ */
87
+ export function generateItemsToRepopulate(populatedResponse: QuestionnaireResponse) {
88
+ const sourceQuestionnaire = useQuestionnaireStore.getState().sourceQuestionnaire;
89
+ const itemTypes = useQuestionnaireStore.getState().itemTypes;
90
+ const tabs = useQuestionnaireStore.getState().tabs;
91
+ const updatableResponse = useQuestionnaireResponseStore.getState().updatableResponse;
92
+ console.log(updatableResponse);
93
+
94
+ return getItemsToRepopulate(
95
+ sourceQuestionnaire,
96
+ itemTypes,
97
+ tabs,
98
+ populatedResponse,
99
+ updatableResponse
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Re-populate stuff
105
+ *
106
+ * @author Sean Fong
107
+ */
108
+ export function repopulate(checkedItemsToRepopulate: Record<string, ItemToRepopulate>) {
109
+ const sourceQuestionnaire = useQuestionnaireStore.getState().sourceQuestionnaire;
110
+ const updatableResponse = useQuestionnaireResponseStore.getState().updatableResponse;
111
+
112
+ return repopulateItemsIntoResponse(
113
+ sourceQuestionnaire,
114
+ updatableResponse,
115
+ checkedItemsToRepopulate
116
+ );
117
+ }
@@ -24,6 +24,7 @@ import type { AnswerExpression } from './answerExpression.interface';
24
24
  import type { Coding } from 'fhir/r4';
25
25
 
26
26
  export interface QuestionnaireModel {
27
+ itemTypes: Record<string, string>;
27
28
  tabs: Tabs;
28
29
  variables: Variables;
29
30
  launchContexts: Record<string, LaunchContext>;
@@ -2,11 +2,12 @@ import { create } from 'zustand';
2
2
  import type { QuestionnaireResponse } from 'fhir/r4';
3
3
  import { emptyResponse } from '../utils/emptyResource';
4
4
  import cloneDeep from 'lodash.clonedeep';
5
+ import { diff } from 'json-diff';
5
6
 
6
7
  export interface UseQuestionnaireResponseStoreType {
7
8
  sourceResponse: QuestionnaireResponse;
8
9
  updatableResponse: QuestionnaireResponse;
9
- hasChanges: boolean;
10
+ formChangesHistory: object[];
10
11
  buildSourceResponse: (response: QuestionnaireResponse) => void;
11
12
  setUpdatableResponseAsPopulated: (populatedResponse: QuestionnaireResponse) => void;
12
13
  updateResponse: (updatedResponse: QuestionnaireResponse) => void;
@@ -15,10 +16,10 @@ export interface UseQuestionnaireResponseStoreType {
15
16
  destroySourceResponse: () => void;
16
17
  }
17
18
 
18
- const useQuestionnaireResponseStore = create<UseQuestionnaireResponseStoreType>()((set) => ({
19
+ const useQuestionnaireResponseStore = create<UseQuestionnaireResponseStoreType>()((set, get) => ({
19
20
  sourceResponse: cloneDeep(emptyResponse),
20
21
  updatableResponse: cloneDeep(emptyResponse),
21
- hasChanges: false,
22
+ formChangesHistory: [],
22
23
  buildSourceResponse: (questionnaireResponse: QuestionnaireResponse) => {
23
24
  set(() => ({
24
25
  sourceResponse: questionnaireResponse,
@@ -26,32 +27,35 @@ const useQuestionnaireResponseStore = create<UseQuestionnaireResponseStoreType>(
26
27
  }));
27
28
  },
28
29
  setUpdatableResponseAsPopulated: (populatedResponse: QuestionnaireResponse) => {
30
+ const formChanges = diff(get().updatableResponse, populatedResponse, { full: true });
29
31
  set(() => ({
30
32
  updatableResponse: populatedResponse,
31
- hasChanges: false
33
+ formChangesHistory: [...get().formChangesHistory, formChanges]
32
34
  }));
33
35
  },
34
- updateResponse: (updatedResponse: QuestionnaireResponse) =>
36
+ updateResponse: (updatedResponse: QuestionnaireResponse) => {
37
+ const formChanges = diff(get().updatableResponse, updatedResponse, { full: true });
35
38
  set(() => ({
36
39
  updatableResponse: updatedResponse,
37
- hasChanges: true
38
- })),
40
+ formChangesHistory: [...get().formChangesHistory, formChanges]
41
+ }));
42
+ },
39
43
  setUpdatableResponseAsSaved: (savedResponse: QuestionnaireResponse) =>
40
44
  set(() => ({
41
45
  sourceResponse: savedResponse,
42
46
  updatableResponse: savedResponse,
43
- hasChanges: false
47
+ formChangesHistory: []
44
48
  })),
45
49
  setUpdatableResponseAsEmpty: (clearedResponse: QuestionnaireResponse) =>
46
50
  set(() => ({
47
51
  updatableResponse: clearedResponse,
48
- hasChanges: false
52
+ formChangesHistory: []
49
53
  })),
50
54
  destroySourceResponse: () =>
51
55
  set(() => ({
52
56
  sourceResponse: cloneDeep(emptyResponse),
53
57
  updatableResponse: cloneDeep(emptyResponse),
54
- hasChanges: false
58
+ formChangesHistory: []
55
59
  }))
56
60
  }));
57
61