@aehrc/smart-forms-renderer 0.27.2 → 0.27.4

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 (179) hide show
  1. package/.storybook/main.ts +2 -1
  2. package/.storybook/preview.ts +6 -1
  3. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionFields.d.ts +3 -2
  4. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionFields.js +9 -17
  5. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionFields.js.map +1 -1
  6. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.js +12 -9
  7. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.js.map +1 -1
  8. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetFields.d.ts +2 -2
  9. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetFields.js +7 -9
  10. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetFields.js.map +1 -1
  11. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.js +12 -8
  12. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.js.map +1 -1
  13. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.d.ts +2 -1
  14. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js +3 -3
  15. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js.map +1 -1
  16. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js +6 -4
  17. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js.map +1 -1
  18. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.d.ts +2 -1
  19. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.js +3 -3
  20. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.js.map +1 -1
  21. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.d.ts +2 -2
  22. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js +5 -7
  23. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js.map +1 -1
  24. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js +4 -4
  25. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js.map +1 -1
  26. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.d.ts +2 -1
  27. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js +2 -3
  28. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js.map +1 -1
  29. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js +5 -3
  30. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js.map +1 -1
  31. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.d.ts +2 -1
  32. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.js +3 -3
  33. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.js.map +1 -1
  34. package/lib/components/FormComponents/DecimalItem/DecimalItem.js +1 -1
  35. package/lib/components/FormComponents/GroupItem/TabButtonsWrapper.js +20 -20
  36. package/lib/components/FormComponents/GroupItem/TabButtonsWrapper.js.map +1 -1
  37. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionFields.d.ts +3 -2
  38. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionFields.js +7 -19
  39. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionFields.js.map +1 -1
  40. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.js +40 -40
  41. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.js.map +1 -1
  42. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceItemSwitcher.js +15 -3
  43. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceItemSwitcher.js.map +1 -1
  44. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.d.ts +2 -1
  45. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.js +3 -3
  46. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.js.map +1 -1
  47. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.js +27 -26
  48. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.js.map +1 -1
  49. package/lib/components/FormComponents/StringItem/StringField.js +1 -1
  50. package/lib/components/FormComponents/StringItem/StringField.js.map +1 -1
  51. package/lib/hooks/useInitialiseRenderer.js +1 -1
  52. package/lib/hooks/useInitialiseRenderer.js.map +1 -1
  53. package/lib/hooks/useNextAndPreviousVisibleTabs.d.ts +7 -0
  54. package/lib/hooks/useNextAndPreviousVisibleTabs.js +63 -0
  55. package/lib/hooks/useNextAndPreviousVisibleTabs.js.map +1 -0
  56. package/lib/hooks/useNextPreviousVisibleTabs.d.ts +6 -0
  57. package/lib/hooks/useNextPreviousVisibleTabs.js +63 -0
  58. package/lib/hooks/useNextPreviousVisibleTabs.js.map +1 -0
  59. package/lib/index.d.ts +0 -7
  60. package/lib/index.js +0 -24
  61. package/lib/index.js.map +1 -1
  62. package/lib/utils/choice.d.ts +1 -7
  63. package/lib/utils/choice.js +10 -20
  64. package/lib/utils/choice.js.map +1 -1
  65. package/lib/utils/enableWhen.js +5 -7
  66. package/lib/utils/enableWhen.js.map +1 -1
  67. package/lib/utils/index.d.ts +1 -0
  68. package/lib/utils/index.js +1 -0
  69. package/lib/utils/index.js.map +1 -1
  70. package/lib/utils/openChoice.d.ts +9 -4
  71. package/lib/utils/openChoice.js +47 -78
  72. package/lib/utils/openChoice.js.map +1 -1
  73. package/lib/utils/tabs.d.ts +0 -21
  74. package/lib/utils/tabs.js +0 -51
  75. package/lib/utils/tabs.js.map +1 -1
  76. package/package.json +4 -4
  77. package/src/components/FormComponents/ChoiceItems/CheckboxOptionList.tsx +82 -0
  78. package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionFields.tsx +23 -52
  79. package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.tsx +15 -9
  80. package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetFields.tsx +17 -19
  81. package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.tsx +13 -8
  82. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.tsx +5 -4
  83. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx +6 -2
  84. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.tsx +5 -1
  85. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.tsx +9 -16
  86. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx +4 -4
  87. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.tsx +4 -3
  88. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx +5 -2
  89. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.tsx +5 -1
  90. package/src/components/FormComponents/DecimalItem/DecimalItem.tsx +1 -1
  91. package/src/components/FormComponents/GroupItem/TabButtonsWrapper.tsx +28 -25
  92. package/src/components/FormComponents/ItemParts/{RadioAnswerOptionButtons.tsx → RadioOptionList.tsx} +7 -7
  93. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionFields.tsx +18 -50
  94. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerOptionItem.tsx +70 -68
  95. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerValueSetFields.tsx +110 -0
  96. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceCheckboxAnswerValueSetItem.tsx +188 -0
  97. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceItemSwitcher.tsx +46 -19
  98. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionFields.tsx +5 -3
  99. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerOptionItem.tsx +37 -29
  100. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerValueSetFields.tsx +104 -0
  101. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceRadioAnswerValueSetItem.tsx +156 -0
  102. package/src/components/FormComponents/StringItem/StringField.tsx +1 -1
  103. package/src/hooks/useInitialiseRenderer.ts +1 -1
  104. package/src/hooks/useNextAndPreviousVisibleTabs.ts +86 -0
  105. package/src/hooks/useOpenLabel.ts +43 -0
  106. package/src/index.ts +0 -21
  107. package/src/stories/BuildFormWrapper.tsx +57 -0
  108. package/src/stories/assets/questionnaires/QAdvancedAdditionalDisplayContent.ts +83 -0
  109. package/src/stories/assets/questionnaires/QAdvancedControlAppearance.ts +294 -0
  110. package/src/stories/assets/questionnaires/QAdvancedOther.ts +495 -0
  111. package/src/stories/assets/questionnaires/QAdvancedTextApperance.ts +188 -0
  112. package/src/stories/assets/questionnaires/QAttachment.ts +38 -0
  113. package/src/stories/assets/questionnaires/QBehaviorCalculations.ts +645 -0
  114. package/src/stories/assets/questionnaires/QBehaviorChoiceRestriction.ts +281 -0
  115. package/src/stories/assets/questionnaires/QBehaviorOther.ts +1149 -0
  116. package/src/stories/assets/questionnaires/QBehaviorValueConstraints.ts +508 -0
  117. package/src/stories/assets/questionnaires/QBoolean.ts +130 -0
  118. package/src/stories/assets/questionnaires/QChoice.ts +137 -0
  119. package/src/stories/assets/questionnaires/QDate.ts +56 -0
  120. package/src/stories/assets/questionnaires/QDateTime.ts +56 -0
  121. package/src/stories/assets/questionnaires/QDecimal.ts +56 -0
  122. package/src/stories/assets/questionnaires/QDisplay.ts +38 -0
  123. package/src/stories/assets/questionnaires/QGroup.ts +52 -0
  124. package/src/stories/assets/questionnaires/QInteger.ts +119 -0
  125. package/src/stories/assets/questionnaires/QItemControlDisplay.ts +114 -0
  126. package/src/stories/assets/questionnaires/QItemControlGroup.ts +419 -0
  127. package/src/stories/assets/questionnaires/QItemControlQuestion.ts +1271 -0
  128. package/src/stories/assets/questionnaires/QOpenChoice.ts +151 -0
  129. package/src/stories/assets/questionnaires/QQuantity.ts +38 -0
  130. package/src/stories/assets/questionnaires/QReference.ts +38 -0
  131. package/src/stories/assets/questionnaires/QSingleItems.ts +251 -0
  132. package/src/stories/assets/questionnaires/QString.ts +131 -0
  133. package/src/stories/assets/questionnaires/QText.ts +169 -0
  134. package/src/stories/assets/questionnaires/QTime.ts +38 -0
  135. package/src/stories/assets/questionnaires/QUrl.ts +38 -0
  136. package/src/stories/assets/questionnaires/index.ts +44 -0
  137. package/src/stories/itemTypes/Attachment.stories.tsx +39 -0
  138. package/src/stories/itemTypes/Boolean.stories.tsx +72 -0
  139. package/src/stories/{MedicalHistoryTable.stories.tsx → itemTypes/Choice.stories.tsx} +32 -26
  140. package/src/stories/itemTypes/Date.stories.tsx +46 -0
  141. package/src/stories/itemTypes/DateTime.stories.tsx +45 -0
  142. package/src/stories/itemTypes/Decimal.stories.tsx +56 -0
  143. package/src/stories/itemTypes/Display.stories.tsx +39 -0
  144. package/src/stories/itemTypes/Group.stories.tsx +39 -0
  145. package/src/stories/itemTypes/Integer.stories.tsx +55 -0
  146. package/src/stories/itemTypes/OpenChoice.stories.tsx +64 -0
  147. package/src/stories/itemTypes/Quantity.stories.tsx +39 -0
  148. package/src/stories/itemTypes/Reference.stories.tsx +39 -0
  149. package/src/stories/itemTypes/String.stories.tsx +51 -0
  150. package/src/stories/itemTypes/Text.stories.tsx +51 -0
  151. package/src/stories/itemTypes/Time.stories.tsx +39 -0
  152. package/src/stories/itemTypes/Url.stories.tsx +39 -0
  153. package/src/stories/sdc/AdvancedAdditionalDisplayContent.stories.tsx +45 -0
  154. package/src/stories/sdc/AdvancedControlAppearance.stories.tsx +51 -0
  155. package/src/stories/sdc/AdvancedOther.stories.tsx +76 -0
  156. package/src/stories/sdc/AdvancedTextAppearance.stories.tsx +69 -0
  157. package/src/stories/sdc/BehaviorCalculations.stories.tsx +69 -0
  158. package/src/stories/sdc/BehaviorChoiceRestriction.stories.tsx +76 -0
  159. package/src/stories/sdc/BehaviorOther.stories.tsx +90 -0
  160. package/src/stories/sdc/BehaviorValueConstraints.stories.tsx +88 -0
  161. package/src/stories/sdc/ItemControlDisplay.stories.tsx +39 -0
  162. package/src/stories/sdc/ItemControlGroup.stories.tsx +55 -0
  163. package/src/stories/sdc/ItemControlQuestion.stories.tsx +118 -0
  164. package/src/utils/buildForm.ts +23 -0
  165. package/src/utils/choice.ts +16 -23
  166. package/src/utils/enableWhen.ts +5 -7
  167. package/src/utils/index.ts +1 -0
  168. package/src/utils/openChoice.ts +83 -98
  169. package/src/utils/tabs.ts +0 -75
  170. package/vite.config.ts +23 -0
  171. package/doctor-storybook.log +0 -18
  172. package/src/stories/SmartFormsRenderer.stories.ts +0 -139
  173. package/src/stories/assets/QItems-and-QRItems/QR_GTableMedicalHistory.json +0 -80
  174. package/src/stories/assets/QItems-and-QRItems/Q_GTableMedicalHistory.json +0 -109
  175. package/src/stories/assets/Qs-and-QRs/Q715.json +0 -15086
  176. package/src/stories/assets/Qs-and-QRs/QDev715.json +0 -16081
  177. package/src/stories/assets/Qs-and-QRs/QTestGrid.json +0 -411
  178. package/src/stories/assets/Qs-and-QRs/R715.json +0 -311
  179. package/src/stories/assets/Qs-and-QRs/RTestGrid.json +0 -34
@@ -45,20 +45,21 @@ function OpenChoiceRadioAnswerOptionItem(props: OpenChoiceRadioAnswerOptionItemP
45
45
 
46
46
  const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId();
47
47
 
48
- const readOnly = useReadOnly(qItem, parentIsReadOnly);
49
- const openLabelText = getOpenLabelText(qItem);
50
-
51
48
  // Init answers
52
49
  const qrOpenChoiceRadio = qrItem ?? createEmptyQrItem(qItem);
53
50
  let valueRadio: string | null = getQrChoiceValue(qrOpenChoiceRadio, true);
54
51
  const answers = qrOpenChoiceRadio.answer ?? [];
55
52
 
53
+ const readOnly = useReadOnly(qItem, parentIsReadOnly);
54
+ const openLabelText = getOpenLabelText(qItem);
55
+
56
+ const options = qItem.answerOption ?? [];
57
+
56
58
  // Init empty open label
57
- const answerOptions = qItem.answerOption;
58
59
  let initialOpenLabelValue = '';
59
60
  let initialOpenLabelSelected = false;
60
- if (answerOptions) {
61
- const oldLabelAnswer = getOldOpenLabelAnswer(answers, answerOptions);
61
+ if (options) {
62
+ const oldLabelAnswer = getOldOpenLabelAnswer(answers, options);
62
63
  if (oldLabelAnswer && oldLabelAnswer.valueString) {
63
64
  initialOpenLabelValue = oldLabelAnswer.valueString;
64
65
  initialOpenLabelSelected = true;
@@ -79,40 +80,46 @@ function OpenChoiceRadioAnswerOptionItem(props: OpenChoiceRadioAnswerOptionItemP
79
80
  changedOptionValue: string | null,
80
81
  changedOpenLabelValue: string | null
81
82
  ) {
82
- if (!answerOptions) return null;
83
+ if (options.length === 0) {
84
+ onQrItemChange(createEmptyQrItem(qItem));
85
+ return;
86
+ }
83
87
 
84
88
  if (changedOptionValue !== null) {
85
- if (qItem.answerOption) {
86
- const qrAnswer = findInAnswerOptions(qItem.answerOption, changedOptionValue);
87
-
88
- // If selected answer can be found in options, it is a non-open label selection
89
- if (qrAnswer) {
90
- onQrItemChange({ ...createEmptyQrItem(qItem), answer: [qrAnswer] });
91
- setOpenLabelSelected(false);
92
- } else {
93
- // Otherwise, it is an open-label selection
94
- onQrItemChange({
95
- ...createEmptyQrItem(qItem),
96
- answer: [{ valueString: changedOptionValue }]
97
- });
98
- setOpenLabelValue(changedOptionValue);
99
- setOpenLabelSelected(true);
100
- }
89
+ const qrAnswer = findInAnswerOptions(options, changedOptionValue);
90
+
91
+ // If selected answer can be found in options, it is a non-open label selection
92
+ if (qrAnswer) {
93
+ onQrItemChange({ ...createEmptyQrItem(qItem), answer: [qrAnswer] });
94
+ setOpenLabelSelected(false);
95
+ return;
101
96
  }
97
+
98
+ // Otherwise, it is an open-label selection
99
+ onQrItemChange({
100
+ ...createEmptyQrItem(qItem),
101
+ answer: [{ valueString: changedOptionValue }]
102
+ });
103
+ setOpenLabelValue(changedOptionValue);
104
+ setOpenLabelSelected(true);
105
+ return;
102
106
  }
103
107
 
104
108
  if (changedOpenLabelValue !== null) {
105
109
  setOpenLabelValue(changedOpenLabelValue);
106
110
 
111
+ // If open label is unchecked, remove it from answers
107
112
  if (changedOpenLabelValue === '') {
108
113
  onQrItemChange(createEmptyQrItem(qItem));
109
- } else {
110
- setOpenLabelValue(changedOpenLabelValue);
111
- onQrItemChange({
112
- ...createEmptyQrItem(qItem),
113
- answer: [{ valueString: changedOpenLabelValue }]
114
- });
114
+ return;
115
115
  }
116
+
117
+ // Otherwise, add open label to answers
118
+ setOpenLabelValue(changedOpenLabelValue);
119
+ onQrItemChange({
120
+ ...createEmptyQrItem(qItem),
121
+ answer: [{ valueString: changedOpenLabelValue }]
122
+ });
116
123
  }
117
124
  }
118
125
 
@@ -124,6 +131,7 @@ function OpenChoiceRadioAnswerOptionItem(props: OpenChoiceRadioAnswerOptionItemP
124
131
  <ItemFieldGrid qItem={qItem} readOnly={readOnly}>
125
132
  <OpenChoiceRadioAnswerOptionFields
126
133
  qItem={qItem}
134
+ options={options}
127
135
  valueRadio={valueRadio}
128
136
  openLabelText={openLabelText}
129
137
  openLabelValue={openLabelValue}
@@ -0,0 +1,104 @@
1
+ /*
2
+ * Copyright 2024 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 type { ChangeEvent } from 'react';
19
+ import React from 'react';
20
+ import { ChoiceItemOrientation } from '../../../interfaces/choice.enum';
21
+ import type { QuestionnaireItem, QuestionnaireItemAnswerOption } from 'fhir/r4';
22
+ import { StyledRadioGroup } from '../Item.styles';
23
+ import RadioButtonWithOpenLabel from '../ItemParts/RadioButtonWithOpenLabel';
24
+ import RadioOptionList from '../ItemParts/RadioOptionList';
25
+ import { getChoiceOrientation } from '../../../utils/choice';
26
+ import type { TerminologyError } from '../../../hooks/useValueSetCodings';
27
+ import { StyledAlert } from '../../Alert.styles';
28
+ import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
29
+ import Typography from '@mui/material/Typography';
30
+
31
+ interface OpenChoiceRadioAnswerValueSetFieldsProps {
32
+ qItem: QuestionnaireItem;
33
+ options: QuestionnaireItemAnswerOption[];
34
+ valueRadio: string | null;
35
+ openLabelText: string | null;
36
+ openLabelValue: string | null;
37
+ openLabelSelected: boolean;
38
+ readOnly: boolean;
39
+ terminologyError: TerminologyError;
40
+ onValueChange: (changedOptionValue: string | null, changedOpenLabelValue: string | null) => void;
41
+ }
42
+
43
+ function OpenChoiceRadioAnswerValueSetFields(props: OpenChoiceRadioAnswerValueSetFieldsProps) {
44
+ const {
45
+ qItem,
46
+ options,
47
+ valueRadio,
48
+ openLabelText,
49
+ openLabelValue,
50
+ openLabelSelected,
51
+ readOnly,
52
+ terminologyError,
53
+ onValueChange
54
+ } = props;
55
+
56
+ const orientation = getChoiceOrientation(qItem) ?? ChoiceItemOrientation.Vertical;
57
+
58
+ if (options.length > 0) {
59
+ return (
60
+ <StyledRadioGroup
61
+ row={orientation === ChoiceItemOrientation.Horizontal}
62
+ name={qItem.text}
63
+ id={qItem.id}
64
+ onChange={(e: ChangeEvent<HTMLInputElement>) => onValueChange(e.target.value, null)}
65
+ value={valueRadio}
66
+ data-test="q-item-radio-group">
67
+ <RadioOptionList options={options} readOnly={readOnly} />
68
+
69
+ {openLabelText ? (
70
+ <RadioButtonWithOpenLabel
71
+ value={openLabelValue}
72
+ label={openLabelText}
73
+ readOnly={readOnly}
74
+ isSelected={openLabelSelected}
75
+ onInputChange={(input) => onValueChange(null, input)}
76
+ />
77
+ ) : null}
78
+ </StyledRadioGroup>
79
+ );
80
+ }
81
+
82
+ if (terminologyError.error) {
83
+ return (
84
+ <StyledAlert color="error">
85
+ <ErrorOutlineIcon color="error" sx={{ pr: 0.75 }} />
86
+ <Typography variant="subtitle2">
87
+ There was an error fetching options from the terminology server for{' '}
88
+ {terminologyError.answerValueSet}
89
+ </Typography>
90
+ </StyledAlert>
91
+ );
92
+ }
93
+
94
+ return (
95
+ <StyledAlert color="error">
96
+ <ErrorOutlineIcon color="error" sx={{ pr: 0.75 }} />
97
+ <Typography variant="subtitle2">
98
+ Unable to fetch options from the questionnaire or launch context
99
+ </Typography>
100
+ </StyledAlert>
101
+ );
102
+ }
103
+
104
+ export default OpenChoiceRadioAnswerValueSetFields;
@@ -0,0 +1,156 @@
1
+ /*
2
+ * Copyright 2024 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 React, { useMemo, useState } from 'react';
19
+ import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
20
+ import { createEmptyQrItem } from '../../../utils/qrItem';
21
+ import { getOpenLabelText } from '../../../utils/itemControl';
22
+ import { getOldOpenLabelAnswer } from '../../../utils/openChoice';
23
+ import { FullWidthFormComponentBox } from '../../Box.styles';
24
+ import {
25
+ convertCodingsToAnswerOptions,
26
+ findInAnswerOptions,
27
+ getQrChoiceValue
28
+ } from '../../../utils/choice';
29
+ import type {
30
+ PropsWithIsRepeatedAttribute,
31
+ PropsWithParentIsReadOnlyAttribute,
32
+ PropsWithQrItemChangeHandler
33
+ } from '../../../interfaces/renderProps.interface';
34
+ import OpenChoiceRadioAnswerValueSetFields from './OpenChoiceRadioAnswerValueSetFields';
35
+ import useReadOnly from '../../../hooks/useReadOnly';
36
+ import ItemFieldGrid from '../ItemParts/ItemFieldGrid';
37
+ import { useQuestionnaireStore } from '../../../stores';
38
+ import useValueSetCodings from '../../../hooks/useValueSetCodings';
39
+
40
+ interface OpenChoiceRadioAnswerValueSetItemProps
41
+ extends PropsWithQrItemChangeHandler,
42
+ PropsWithIsRepeatedAttribute,
43
+ PropsWithParentIsReadOnlyAttribute {
44
+ qItem: QuestionnaireItem;
45
+ qrItem: QuestionnaireResponseItem | null;
46
+ }
47
+
48
+ function OpenChoiceRadioAnswerValueSetItem(props: OpenChoiceRadioAnswerValueSetItemProps) {
49
+ const { qItem, qrItem, parentIsReadOnly, onQrItemChange } = props;
50
+
51
+ const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId();
52
+
53
+ // Init answers
54
+ const qrOpenChoiceRadio = qrItem ?? createEmptyQrItem(qItem);
55
+ let valueRadio: string | null = getQrChoiceValue(qrOpenChoiceRadio, true);
56
+ const answers = qrOpenChoiceRadio.answer ?? [];
57
+
58
+ const readOnly = useReadOnly(qItem, parentIsReadOnly);
59
+ const openLabelText = getOpenLabelText(qItem);
60
+
61
+ // Get codings/options from valueSet
62
+ const { codings, terminologyError } = useValueSetCodings(qItem);
63
+
64
+ const options = useMemo(() => convertCodingsToAnswerOptions(codings), [codings]);
65
+
66
+ // Init empty open label
67
+ let initialOpenLabelValue = '';
68
+ let initialOpenLabelSelected = false;
69
+ if (options) {
70
+ const oldLabelAnswer = getOldOpenLabelAnswer(answers, options);
71
+ if (oldLabelAnswer && oldLabelAnswer.valueString) {
72
+ initialOpenLabelValue = oldLabelAnswer.valueString;
73
+ initialOpenLabelSelected = true;
74
+ valueRadio = initialOpenLabelValue;
75
+ }
76
+ }
77
+
78
+ const [openLabelValue, setOpenLabelValue] = useState<string | null>(initialOpenLabelValue);
79
+ const [openLabelSelected, setOpenLabelSelected] = useState(initialOpenLabelSelected);
80
+
81
+ // Allow open label to remain selected even if its input was cleared
82
+ if (openLabelSelected && valueRadio === null) {
83
+ valueRadio = '';
84
+ }
85
+
86
+ // Event handlers
87
+ function handleValueChange(
88
+ changedOptionValue: string | null,
89
+ changedOpenLabelValue: string | null
90
+ ) {
91
+ if (options.length === 0) {
92
+ onQrItemChange(createEmptyQrItem(qItem));
93
+ return;
94
+ }
95
+
96
+ if (changedOptionValue !== null) {
97
+ const qrAnswer = findInAnswerOptions(options, changedOptionValue);
98
+
99
+ // If selected answer can be found in options, it is a non-open label selection
100
+ if (qrAnswer) {
101
+ onQrItemChange({ ...createEmptyQrItem(qItem), answer: [qrAnswer] });
102
+ setOpenLabelSelected(false);
103
+ return;
104
+ }
105
+
106
+ // Otherwise, it is an open-label selection
107
+ onQrItemChange({
108
+ ...createEmptyQrItem(qItem),
109
+ answer: [{ valueString: changedOptionValue }]
110
+ });
111
+ setOpenLabelValue(changedOptionValue);
112
+ setOpenLabelSelected(true);
113
+ return;
114
+ }
115
+
116
+ if (changedOpenLabelValue !== null) {
117
+ setOpenLabelValue(changedOpenLabelValue);
118
+
119
+ // If open label is unchecked, remove it from answers
120
+ if (changedOpenLabelValue === '') {
121
+ onQrItemChange(createEmptyQrItem(qItem));
122
+ return;
123
+ }
124
+
125
+ // Otherwise, add open label to answers
126
+ setOpenLabelValue(changedOpenLabelValue);
127
+ onQrItemChange({
128
+ ...createEmptyQrItem(qItem),
129
+ answer: [{ valueString: changedOpenLabelValue }]
130
+ });
131
+ }
132
+ }
133
+
134
+ return (
135
+ <FullWidthFormComponentBox
136
+ data-test="q-item-open-choice-radio-answer-option-box"
137
+ data-linkid={qItem.linkId}
138
+ onClick={() => onFocusLinkId(qItem.linkId)}>
139
+ <ItemFieldGrid qItem={qItem} readOnly={readOnly}>
140
+ <OpenChoiceRadioAnswerValueSetFields
141
+ qItem={qItem}
142
+ options={options}
143
+ valueRadio={valueRadio}
144
+ openLabelText={openLabelText}
145
+ openLabelValue={openLabelValue}
146
+ openLabelSelected={openLabelSelected}
147
+ readOnly={readOnly}
148
+ terminologyError={terminologyError}
149
+ onValueChange={handleValueChange}
150
+ />
151
+ </ItemFieldGrid>
152
+ </FullWidthFormComponentBox>
153
+ );
154
+ }
155
+
156
+ export default OpenChoiceRadioAnswerValueSetItem;
@@ -61,7 +61,7 @@ function StringField(props: StringFieldProps) {
61
61
  size="small"
62
62
  InputProps={{
63
63
  endAdornment: (
64
- <InputAdornment position={'end'}>
64
+ <InputAdornment position="end">
65
65
  <FadingCheckIcon fadeIn={calcExpUpdated} disabled={readOnly} />
66
66
  {displayUnit}
67
67
  </InputAdornment>
@@ -17,7 +17,7 @@
17
17
 
18
18
  import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
19
19
  import { useLayoutEffect, useState } from 'react';
20
- import { initialiseQuestionnaireResponse } from '../utils/initialise';
20
+ import { initialiseQuestionnaireResponse } from '../utils';
21
21
  import type Client from 'fhirclient/lib/Client';
22
22
  import { readEncounter, readPatient, readUser } from '../api/smartClient';
23
23
  import {
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Copyright 2024 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 { useQuestionnaireStore } from '../stores';
19
+ import type { Tabs } from '../interfaces/tab.interface';
20
+ import { constructTabsWithVisibility } from '../utils/tabs';
21
+
22
+ function useNextAndPreviousVisibleTabs(
23
+ currentTabIndex?: number,
24
+ tabs?: Tabs
25
+ ): { previousTabIndex: number | null; nextTabIndex: number | null; numOfVisibleTabs: number } {
26
+ const enableWhenIsActivated = useQuestionnaireStore.use.enableWhenIsActivated();
27
+ const enableWhenItems = useQuestionnaireStore.use.enableWhenItems();
28
+ const enableWhenExpressions = useQuestionnaireStore.use.enableWhenExpressions();
29
+
30
+ const tabsNotDefined = currentTabIndex === undefined || tabs === undefined;
31
+
32
+ if (tabsNotDefined) {
33
+ return { previousTabIndex: null, nextTabIndex: null, numOfVisibleTabs: 0 };
34
+ }
35
+
36
+ const tabsWithVisibility = constructTabsWithVisibility({
37
+ tabs,
38
+ enableWhenIsActivated,
39
+ enableWhenItems,
40
+ enableWhenExpressions
41
+ });
42
+
43
+ return {
44
+ previousTabIndex: getPreviousTabIndex(currentTabIndex, tabsWithVisibility),
45
+ nextTabIndex: getNextTabIndex(currentTabIndex, tabsWithVisibility),
46
+ numOfVisibleTabs: tabsWithVisibility.filter((tab) => tab.isVisible).length
47
+ };
48
+ }
49
+
50
+ function getPreviousTabIndex(
51
+ currentTabIndex: number,
52
+ tabsWithVisibility: { linkId: string; isVisible: boolean }[]
53
+ ): number | null {
54
+ const previousTabs = tabsWithVisibility.slice(0, currentTabIndex);
55
+ const foundIndex = previousTabs.reverse().findIndex((tab) => tab.isVisible);
56
+
57
+ // Previous visible tab not found
58
+ if (foundIndex === -1) {
59
+ return null;
60
+ }
61
+
62
+ // Previous visible tab less than 0
63
+ const previousTabIndex = currentTabIndex - foundIndex - 1;
64
+ if (previousTabIndex < 0) {
65
+ return null;
66
+ }
67
+
68
+ return previousTabIndex;
69
+ }
70
+
71
+ function getNextTabIndex(
72
+ currentTabIndex: number,
73
+ tabsWithVisibility: { linkId: string; isVisible: boolean }[]
74
+ ): number | null {
75
+ const subsequentTabs = tabsWithVisibility.slice(currentTabIndex + 1);
76
+ const foundIndex = subsequentTabs.findIndex((tab) => tab.isVisible);
77
+
78
+ // Next visible tab not found, something is wrong
79
+ if (foundIndex === -1) {
80
+ return null;
81
+ }
82
+
83
+ return currentTabIndex + foundIndex + 1;
84
+ }
85
+
86
+ export default useNextAndPreviousVisibleTabs;
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Copyright 2024 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 { getOldOpenLabelAnswer } from '../utils/openChoice';
19
+ import { useState } from 'react';
20
+ import type { QuestionnaireItemAnswerOption, QuestionnaireResponseItemAnswer } from 'fhir/r4';
21
+
22
+ function useOpenLabel(
23
+ options: QuestionnaireItemAnswerOption[],
24
+ answers: QuestionnaireResponseItemAnswer[]
25
+ ) {
26
+ let initialOpenLabelValue = '';
27
+ let initialOpenLabelChecked = false;
28
+
29
+ if (options.length > 0) {
30
+ const oldLabelAnswer = getOldOpenLabelAnswer(answers, options);
31
+ if (oldLabelAnswer && oldLabelAnswer.valueString) {
32
+ initialOpenLabelValue = oldLabelAnswer.valueString;
33
+ initialOpenLabelChecked = true;
34
+ }
35
+ }
36
+
37
+ const [openLabelValue, setOpenLabelValue] = useState(initialOpenLabelValue);
38
+ const [openLabelChecked, setOpenLabelChecked] = useState(initialOpenLabelChecked);
39
+
40
+ return { openLabelValue, setOpenLabelValue, openLabelChecked, setOpenLabelChecked };
41
+ }
42
+
43
+ export default useOpenLabel;
package/src/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { questionnaireResponseStore, questionnaireStore } from './stores';
2
2
  import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
3
- import { initialiseQuestionnaireResponse } from './utils/initialise';
4
3
  import { removeEmptyAnswers } from './utils/removeEmptyAnswers';
5
4
  import type { ItemToRepopulate } from './utils/repopulateItems';
6
5
  import { getItemsToRepopulate } from './utils/repopulateItems';
@@ -13,26 +12,6 @@ export * from './utils';
13
12
  export * from './interfaces';
14
13
  export type { ItemToRepopulate };
15
14
 
16
- /**
17
- * Build the form with an initial Questionnaire and an optional filled QuestionnaireResponse.
18
- * If a QuestionnaireResponse is not provided, an empty QuestionnaireResponse is set as the initial QuestionnaireResponse.
19
- *
20
- * @author Sean Fong
21
- */
22
- export async function buildForm(
23
- questionnaire: Questionnaire,
24
- questionnaireResponse?: QuestionnaireResponse
25
- ): Promise<void> {
26
- await questionnaireStore.getState().buildSourceQuestionnaire(questionnaire);
27
-
28
- const initialisedQuestionnaireResponse = initialiseQuestionnaireResponse(
29
- questionnaire,
30
- questionnaireResponse
31
- );
32
- questionnaireResponseStore.getState().buildSourceResponse(initialisedQuestionnaireResponse);
33
- questionnaireStore.getState().updatePopulatedProperties(initialisedQuestionnaireResponse);
34
- }
35
-
36
15
  /**
37
16
  * Destroy the form to clean up the questionnaire and questionnaireResponse stores.
38
17
  *
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Copyright 2024 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 React, { useLayoutEffect, useState } from 'react';
19
+ import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
20
+ import { BaseRenderer } from '../components';
21
+ import { QueryClientProvider } from '@tanstack/react-query';
22
+ import ThemeProvider from '../theme/Theme';
23
+ import useQueryClient from '../hooks/useQueryClient';
24
+ import { buildForm } from '../utils';
25
+
26
+ interface BuildFormWrapperProps {
27
+ questionnaire: Questionnaire;
28
+ questionnaireResponse?: QuestionnaireResponse;
29
+ }
30
+
31
+ function BuildFormWrapper(props: BuildFormWrapperProps) {
32
+ const { questionnaire, questionnaireResponse } = props;
33
+
34
+ const [isLoading, setIsLoading] = useState(true);
35
+
36
+ useLayoutEffect(() => {
37
+ buildForm(questionnaire, questionnaireResponse).then(() => {
38
+ setIsLoading(false);
39
+ });
40
+ }, [questionnaire, questionnaireResponse]);
41
+
42
+ const queryClient = useQueryClient();
43
+
44
+ if (isLoading) {
45
+ return <div>Loading...</div>;
46
+ }
47
+
48
+ return (
49
+ <ThemeProvider>
50
+ <QueryClientProvider client={queryClient}>
51
+ <BaseRenderer />
52
+ </QueryClientProvider>
53
+ </ThemeProvider>
54
+ );
55
+ }
56
+
57
+ export default BuildFormWrapper;
@@ -0,0 +1,83 @@
1
+ import type { Questionnaire } from 'fhir/r4';
2
+
3
+ export const qEntryFormat: Questionnaire = {
4
+ resourceType: 'Questionnaire',
5
+ id: 'EntryFormat',
6
+ name: 'EntryFormat',
7
+ title: 'Entry Format',
8
+ version: '0.1.0',
9
+ status: 'draft',
10
+ publisher: 'AEHRC CSIRO',
11
+ date: '2024-05-01',
12
+ url: 'https://smartforms.csiro.au/docs/advanced/additional-display/entry-format',
13
+ item: [
14
+ {
15
+ extension: [
16
+ {
17
+ url: 'http://hl7.org/fhir/StructureDefinition/entryFormat',
18
+ valueString: '####'
19
+ }
20
+ ],
21
+ linkId: 'postcode',
22
+ definition: 'http://hl7.org.au/fhir/StructureDefinition/au-address#Address.postalCode',
23
+ text: 'Postcode',
24
+ type: 'string',
25
+ repeats: false
26
+ }
27
+ ]
28
+ };
29
+
30
+ export const qShortText: Questionnaire = {
31
+ resourceType: 'Questionnaire',
32
+ id: 'ShortText',
33
+ name: 'ShortText',
34
+ title: 'Short Text',
35
+ version: '0.1.0',
36
+ status: 'draft',
37
+ publisher: 'AEHRC CSIRO',
38
+ date: '2024-05-01',
39
+ url: 'https://smartforms.csiro.au/docs/advanced/additional-display/short-text',
40
+ item: [
41
+ {
42
+ extension: [
43
+ {
44
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl',
45
+ valueCodeableConcept: {
46
+ coding: [
47
+ {
48
+ system: 'http://hl7.org/fhir/questionnaire-item-control',
49
+ version: '1.0.0',
50
+ code: 'tab-container'
51
+ }
52
+ ]
53
+ }
54
+ }
55
+ ],
56
+ linkId: 'tab-container',
57
+ type: 'group',
58
+ repeats: false,
59
+ item: [
60
+ {
61
+ extension: [
62
+ {
63
+ url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-shortText',
64
+ valueString: 'Regular medications'
65
+ }
66
+ ],
67
+ linkId: 'tab-regular-medications',
68
+ text: 'Regular medications: check if still required, appropriate dose, understanding of medication and adherence',
69
+ type: 'group',
70
+ repeats: false,
71
+ item: [
72
+ {
73
+ linkId: 'highlight-short-text',
74
+ text: 'Notice the short text is used in the tab. The group title still displays the full text.',
75
+ type: 'display',
76
+ repeats: false
77
+ }
78
+ ]
79
+ }
80
+ ]
81
+ }
82
+ ]
83
+ };