@aehrc/smart-forms-renderer 0.37.2 → 0.38.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 (53) hide show
  1. package/lib/components/FormComponents/QuantityItem/QuantityComparatorField.d.ts +12 -0
  2. package/lib/components/FormComponents/QuantityItem/QuantityComparatorField.js +13 -0
  3. package/lib/components/FormComponents/QuantityItem/QuantityComparatorField.js.map +1 -0
  4. package/lib/components/FormComponents/QuantityItem/QuantityField.d.ts +15 -0
  5. package/lib/components/FormComponents/QuantityItem/QuantityField.js +14 -0
  6. package/lib/components/FormComponents/QuantityItem/QuantityField.js.map +1 -0
  7. package/lib/components/FormComponents/QuantityItem/QuantityItem.d.ts +9 -0
  8. package/lib/components/FormComponents/QuantityItem/QuantityItem.js +144 -0
  9. package/lib/components/FormComponents/QuantityItem/QuantityItem.js.map +1 -0
  10. package/lib/components/FormComponents/QuantityItem/QuantityUnitField.d.ts +12 -0
  11. package/lib/components/FormComponents/QuantityItem/QuantityUnitField.js +10 -0
  12. package/lib/components/FormComponents/QuantityItem/QuantityUnitField.js.map +1 -0
  13. package/lib/components/FormComponents/SingleItem/SingleItemSwitcher.js +2 -1
  14. package/lib/components/FormComponents/SingleItem/SingleItemSwitcher.js.map +1 -1
  15. package/lib/hooks/useDecimalCalculatedExpression.d.ts +2 -2
  16. package/lib/hooks/useQuantityCalculatedExpression.d.ts +14 -0
  17. package/lib/hooks/useQuantityCalculatedExpression.js +105 -0
  18. package/lib/hooks/useQuantityCalculatedExpression.js.map +1 -0
  19. package/lib/hooks/useRenderingExtensions.d.ts +2 -1
  20. package/lib/hooks/useRenderingExtensions.js +3 -2
  21. package/lib/hooks/useRenderingExtensions.js.map +1 -1
  22. package/lib/hooks/useStringInput.js +1 -0
  23. package/lib/hooks/useStringInput.js.map +1 -1
  24. package/lib/interfaces/valueSet.interface.d.ts +15 -0
  25. package/lib/utils/calculatedExpression.js +4 -1
  26. package/lib/utils/calculatedExpression.js.map +1 -1
  27. package/lib/utils/itemControl.d.ts +7 -1
  28. package/lib/utils/itemControl.js +14 -0
  29. package/lib/utils/itemControl.js.map +1 -1
  30. package/lib/utils/quantity.d.ts +4 -0
  31. package/lib/utils/quantity.js +49 -0
  32. package/lib/utils/quantity.js.map +1 -0
  33. package/lib/utils/valueSet.d.ts +2 -1
  34. package/lib/utils/valueSet.js +22 -0
  35. package/lib/utils/valueSet.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/components/FormComponents/QuantityItem/QuantityComparatorField.tsx +40 -0
  38. package/src/components/FormComponents/QuantityItem/QuantityField.tsx +60 -0
  39. package/src/components/FormComponents/QuantityItem/QuantityItem.tsx +286 -0
  40. package/src/components/FormComponents/QuantityItem/QuantityUnitField.tsx +38 -0
  41. package/src/components/FormComponents/SingleItem/SingleItemSwitcher.tsx +2 -1
  42. package/src/hooks/useDecimalCalculatedExpression.ts +2 -2
  43. package/src/hooks/useQuantityCalculatedExpression.ts +177 -0
  44. package/src/hooks/useRenderingExtensions.ts +5 -2
  45. package/src/hooks/useStringInput.ts +1 -0
  46. package/src/interfaces/valueSet.interface.ts +19 -0
  47. package/src/stories/assets/questionnaires/QPrePopTester.ts +30 -0
  48. package/src/stories/assets/questionnaires/QQuantity.ts +283 -1
  49. package/src/stories/itemTypes/Quantity.stories.tsx +33 -1
  50. package/src/utils/calculatedExpression.ts +5 -1
  51. package/src/utils/itemControl.ts +19 -1
  52. package/src/utils/quantity.ts +62 -0
  53. package/src/utils/valueSet.ts +32 -1
@@ -0,0 +1,177 @@
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 { useEffect, useState } from 'react';
19
+ import type { QuestionnaireItem } from 'fhir/r4';
20
+ import { useQuestionnaireStore } from '../stores';
21
+ import { validateCodePromise } from '../utils/valueSet';
22
+ import { TERMINOLOGY_SERVER_URL } from '../globals';
23
+ import type {
24
+ CodeParameter,
25
+ DisplayParameter,
26
+ SystemParameter
27
+ } from '../interfaces/valueSet.interface';
28
+
29
+ interface UseQuantityCalculatedExpression {
30
+ calcExpUpdated: boolean;
31
+ }
32
+
33
+ interface UseQuantityCalculatedExpressionProps {
34
+ qItem: QuestionnaireItem;
35
+ inputValue: string;
36
+ precision: number | null;
37
+ onChangeByCalcExpressionDecimal: (newValue: number) => void;
38
+ onChangeByCalcExpressionQuantity: (
39
+ newValue: number,
40
+ newUnitSystem: string,
41
+ newUnitCode: string,
42
+ newUnitDisplay: string
43
+ ) => void;
44
+ onChangeByCalcExpressionNull: () => void;
45
+ }
46
+
47
+ function useQuantityCalculatedExpression(
48
+ props: UseQuantityCalculatedExpressionProps
49
+ ): UseQuantityCalculatedExpression {
50
+ const {
51
+ qItem,
52
+ inputValue,
53
+ precision,
54
+ onChangeByCalcExpressionDecimal,
55
+ onChangeByCalcExpressionQuantity,
56
+ onChangeByCalcExpressionNull
57
+ } = props;
58
+
59
+ const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
60
+
61
+ const [calcExpUpdated, setCalcExpUpdated] = useState(false);
62
+
63
+ useEffect(
64
+ () => {
65
+ const calcExpression = calculatedExpressions[qItem.linkId]?.find(
66
+ (exp) => exp.from === 'item'
67
+ );
68
+
69
+ if (!calcExpression) {
70
+ return;
71
+ }
72
+
73
+ // only update if calculated value is different from current value
74
+ if (
75
+ calcExpression.value !== inputValue &&
76
+ (typeof calcExpression.value === 'number' ||
77
+ typeof calcExpression.value === 'string' ||
78
+ calcExpression.value === null)
79
+ ) {
80
+ // Null path
81
+ if (calcExpression.value === null) {
82
+ onChangeByCalcExpressionNull();
83
+ return;
84
+ }
85
+
86
+ // Number path
87
+ if (typeof calcExpression.value === 'number') {
88
+ const calcExpressionValue =
89
+ typeof precision === 'number'
90
+ ? parseFloat(calcExpression.value.toFixed(precision))
91
+ : calcExpression.value;
92
+
93
+ // only update if calculated value is different from current value
94
+ if (calcExpressionValue !== parseFloat(inputValue)) {
95
+ // update ui to show calculated value changes
96
+ setCalcExpUpdated(true);
97
+ setTimeout(() => {
98
+ setCalcExpUpdated(false);
99
+ }, 500);
100
+
101
+ // calculatedExpression value is null
102
+ if (calcExpressionValue === null) {
103
+ onChangeByCalcExpressionNull();
104
+ return;
105
+ }
106
+
107
+ // calculatedExpression value is a number
108
+ onChangeByCalcExpressionDecimal(calcExpressionValue);
109
+ }
110
+ }
111
+
112
+ // String path (quantity)
113
+ if (typeof calcExpression.value === 'string') {
114
+ try {
115
+ const [value, unitCode] = calcExpression.value.split(' ');
116
+ const unitCodeFormatted = unitCode.replace(/'/g, '');
117
+
118
+ const ucumValueSet = 'http://hl7.org/fhir/ValueSet/ucum-units';
119
+ const ucumSystem = 'http://unitsofmeasure.org';
120
+
121
+ validateCodePromise(
122
+ ucumValueSet,
123
+ ucumSystem,
124
+ unitCodeFormatted,
125
+ TERMINOLOGY_SERVER_URL
126
+ ).then((validateCodeResponse) => {
127
+ // Return early if validate-code request fails
128
+ if (!validateCodeResponse) {
129
+ onChangeByCalcExpressionNull();
130
+ return;
131
+ }
132
+
133
+ if (validateCodeResponse.parameter) {
134
+ const systemParameter = validateCodeResponse.parameter.find(
135
+ (p) => p.name === 'system'
136
+ ) as SystemParameter;
137
+ const codeParameter = validateCodeResponse.parameter.find(
138
+ (p) => p.name === 'code'
139
+ ) as CodeParameter;
140
+ const displayParameter = validateCodeResponse.parameter.find(
141
+ (p) => p.name === 'display'
142
+ ) as DisplayParameter;
143
+ if (
144
+ systemParameter.valueUri &&
145
+ codeParameter.valueCode &&
146
+ displayParameter.valueString
147
+ ) {
148
+ // update ui to show calculated value changes
149
+ setCalcExpUpdated(true);
150
+ setTimeout(() => {
151
+ setCalcExpUpdated(false);
152
+ }, 500);
153
+ onChangeByCalcExpressionQuantity(
154
+ parseFloat(value),
155
+ systemParameter.valueUri,
156
+ codeParameter.valueCode,
157
+ displayParameter.valueString
158
+ );
159
+ }
160
+ }
161
+ });
162
+ } catch (e) {
163
+ console.error(e);
164
+ onChangeByCalcExpressionNull();
165
+ }
166
+ }
167
+ }
168
+ },
169
+ // Only trigger this effect if calculatedExpression of item changes
170
+ // eslint-disable-next-line react-hooks/exhaustive-deps
171
+ [calculatedExpressions]
172
+ );
173
+
174
+ return { calcExpUpdated: calcExpUpdated };
175
+ }
176
+
177
+ export default useQuantityCalculatedExpression;
@@ -16,12 +16,13 @@
16
16
  */
17
17
 
18
18
  import {
19
+ getQuantityUnit,
19
20
  getTextDisplayFlyover,
20
21
  getTextDisplayInstructions,
21
22
  getTextDisplayPrompt,
22
23
  getTextDisplayUnit
23
24
  } from '../utils/itemControl';
24
- import type { QuestionnaireItem } from 'fhir/r4';
25
+ import type { QuestionnaireItem, QuestionnaireItemAnswerOption } from 'fhir/r4';
25
26
  import { structuredDataCapture } from 'fhir-sdc-helpers';
26
27
  import { useMemo } from 'react';
27
28
 
@@ -33,6 +34,7 @@ interface RenderingExtensions {
33
34
  readOnly: boolean;
34
35
  entryFormat: string;
35
36
  required: boolean;
37
+ quantityUnit: QuestionnaireItemAnswerOption | null;
36
38
  }
37
39
 
38
40
  function useRenderingExtensions(qItem: QuestionnaireItem): RenderingExtensions {
@@ -44,7 +46,8 @@ function useRenderingExtensions(qItem: QuestionnaireItem): RenderingExtensions {
44
46
  displayFlyover: getTextDisplayFlyover(qItem),
45
47
  readOnly: !!qItem.readOnly,
46
48
  entryFormat: structuredDataCapture.getEntryFormat(qItem) ?? '',
47
- required: qItem.required ?? false
49
+ required: qItem.required ?? false,
50
+ quantityUnit: getQuantityUnit(qItem)
48
51
  }),
49
52
  [qItem]
50
53
  );
@@ -18,6 +18,7 @@
18
18
  import type { Dispatch, SetStateAction } from 'react';
19
19
  import { useEffect, useState } from 'react';
20
20
 
21
+ // The purpose of this hook to sync the string state from external changes i.e. re-population changes etc.
21
22
  function useStringInput(valueFromProps: string): [string, Dispatch<SetStateAction<string>>] {
22
23
  const [input, setInput] = useState(valueFromProps);
23
24
 
@@ -21,3 +21,22 @@ export interface ValueSetPromise {
21
21
  promise: Promise<ValueSet>;
22
22
  valueSet?: ValueSet;
23
23
  }
24
+
25
+ export interface ValidateCodeResponse extends Parameters<any> {
26
+ parameter: [SystemParameter, CodeParameter, DisplayParameter];
27
+ }
28
+
29
+ export interface SystemParameter {
30
+ name: 'system';
31
+ valueUri: string;
32
+ }
33
+
34
+ export interface CodeParameter {
35
+ name: 'code';
36
+ valueCode: string;
37
+ }
38
+
39
+ export interface DisplayParameter {
40
+ name: 'display';
41
+ valueString: string;
42
+ }
@@ -184,6 +184,14 @@ export const qSelectivePrePopTester: Questionnaire = {
184
184
  }
185
185
  ]
186
186
  },
187
+ {
188
+ url: 'http://hl7.org/fhir/StructureDefinition/variable',
189
+ valueExpression: {
190
+ name: 'ObsBloodPressure',
191
+ language: 'application/x-fhir-query',
192
+ expression: 'Observation?code=75367002&_count=1&_sort=-date&patient={{%patient.id}}'
193
+ }
194
+ },
187
195
  {
188
196
  url: 'http://hl7.org/fhir/StructureDefinition/variable',
189
197
  valueExpression: {
@@ -424,6 +432,28 @@ export const qSelectivePrePopTester: Questionnaire = {
424
432
  }
425
433
  ]
426
434
  },
435
+ {
436
+ linkId: 'blood-pressure-unit-fixed',
437
+ text: 'Blood Pressure',
438
+ type: 'quantity',
439
+ extension: [
440
+ {
441
+ url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression',
442
+ valueExpression: {
443
+ language: 'text/fhirpath',
444
+ expression: '%ObsBloodPressure.entry[0].resource.component[0].value'
445
+ }
446
+ },
447
+ {
448
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unit',
449
+ valueCoding: {
450
+ system: 'http://unitsofmeasure.org',
451
+ code: 'mm[Hg]',
452
+ display: 'mmHg'
453
+ }
454
+ }
455
+ ]
456
+ },
427
457
  {
428
458
  extension: [
429
459
  {
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import type { Questionnaire } from 'fhir/r4';
18
+ import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
19
19
 
20
20
  export const qQuantityBasic: Questionnaire = {
21
21
  resourceType: 'Questionnaire',
@@ -39,6 +39,288 @@ export const qQuantityBasic: Questionnaire = {
39
39
  type: 'quantity',
40
40
  repeats: false,
41
41
  text: 'Body Weight'
42
+ },
43
+ {
44
+ extension: [
45
+ {
46
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unit',
47
+ valueCoding: { system: 'http://unitsofmeasure.org', code: 'kg', display: 'kg' }
48
+ }
49
+ ],
50
+ linkId: 'body-weight-comparator',
51
+ type: 'quantity',
52
+ repeats: false,
53
+ text: 'Body Weight (with comparator symbol)'
54
+ }
55
+ ]
56
+ };
57
+
58
+ export const qrQuantityBasicResponse: QuestionnaireResponse = {
59
+ resourceType: 'QuestionnaireResponse',
60
+ status: 'in-progress',
61
+ item: [
62
+ {
63
+ linkId: 'body-weight',
64
+ answer: [
65
+ {
66
+ valueQuantity: {
67
+ value: 80,
68
+ unit: 'kg',
69
+ system: 'http://unitsofmeasure.org',
70
+ code: 'kg'
71
+ }
72
+ }
73
+ ],
74
+ text: 'Body Weight'
75
+ },
76
+ {
77
+ linkId: 'body-weight-comparator',
78
+ answer: [
79
+ {
80
+ valueQuantity: {
81
+ value: 90,
82
+ comparator: '<',
83
+ unit: 'kg',
84
+ system: 'http://unitsofmeasure.org',
85
+ code: 'kg'
86
+ }
87
+ }
88
+ ],
89
+ text: 'Body Weight (with comparator symbol)'
90
+ }
91
+ ],
92
+ questionnaire: 'https://smartforms.csiro.au/docs/components/quantity/basic'
93
+ };
94
+
95
+ export const qQuantityUnitOption: Questionnaire = {
96
+ resourceType: 'Questionnaire',
97
+ id: 'QuantityUnitOption',
98
+ name: 'QuantityUnitOption',
99
+ title: 'Quantity UnitOption',
100
+ version: '0.1.0',
101
+ status: 'draft',
102
+ publisher: 'AEHRC CSIRO',
103
+ date: '2024-07-27',
104
+ url: 'https://smartforms.csiro.au/docs/components/quantity/unit-option',
105
+ item: [
106
+ {
107
+ linkId: 'duration',
108
+ text: 'Duration',
109
+ type: 'quantity',
110
+ extension: [
111
+ {
112
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
113
+ valueCoding: {
114
+ system: 'http://unitsofmeasure.org',
115
+ code: 'd',
116
+ display: 'Day(s)'
117
+ }
118
+ },
119
+ {
120
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
121
+ valueCoding: {
122
+ system: 'http://unitsofmeasure.org',
123
+ code: 'wk',
124
+ display: 'Week(s)'
125
+ }
126
+ },
127
+ {
128
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
129
+ valueCoding: {
130
+ system: 'http://unitsofmeasure.org',
131
+ code: 'mo',
132
+ display: 'Month(s)'
133
+ }
134
+ },
135
+ {
136
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
137
+ valueCoding: {
138
+ system: 'http://unitsofmeasure.org',
139
+ code: 'a',
140
+ display: 'Year(s)'
141
+ }
142
+ },
143
+ {
144
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
145
+ valueCoding: {
146
+ system: 'http://unitsofmeasure.org',
147
+ code: 's',
148
+ display: 'Second(s)'
149
+ }
150
+ },
151
+ {
152
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
153
+ valueCoding: {
154
+ system: 'http://unitsofmeasure.org',
155
+ code: 'min',
156
+ display: 'Minute(s)'
157
+ }
158
+ },
159
+ {
160
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
161
+ valueCoding: {
162
+ system: 'http://unitsofmeasure.org',
163
+ code: 'hour',
164
+ display: 'Hour(s)'
165
+ }
166
+ }
167
+ ]
168
+ },
169
+ {
170
+ linkId: 'duration-comparator',
171
+ text: 'Duration (with comparator symbol)',
172
+ type: 'quantity',
173
+ extension: [
174
+ {
175
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
176
+ valueCoding: {
177
+ system: 'http://unitsofmeasure.org',
178
+ code: 'd',
179
+ display: 'Day(s)'
180
+ }
181
+ },
182
+ {
183
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
184
+ valueCoding: {
185
+ system: 'http://unitsofmeasure.org',
186
+ code: 'wk',
187
+ display: 'Week(s)'
188
+ }
189
+ },
190
+ {
191
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
192
+ valueCoding: {
193
+ system: 'http://unitsofmeasure.org',
194
+ code: 'mo',
195
+ display: 'Month(s)'
196
+ }
197
+ },
198
+ {
199
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
200
+ valueCoding: {
201
+ system: 'http://unitsofmeasure.org',
202
+ code: 'a',
203
+ display: 'Year(s)'
204
+ }
205
+ },
206
+ {
207
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
208
+ valueCoding: {
209
+ system: 'http://unitsofmeasure.org',
210
+ code: 's',
211
+ display: 'Second(s)'
212
+ }
213
+ },
214
+ {
215
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
216
+ valueCoding: {
217
+ system: 'http://unitsofmeasure.org',
218
+ code: 'min',
219
+ display: 'Minute(s)'
220
+ }
221
+ },
222
+ {
223
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption',
224
+ valueCoding: {
225
+ system: 'http://unitsofmeasure.org',
226
+ code: 'hour',
227
+ display: 'Hour(s)'
228
+ }
229
+ }
230
+ ]
231
+ }
232
+ ]
233
+ };
234
+
235
+ export const qrQuantityUnitOptionResponse: QuestionnaireResponse = {
236
+ resourceType: 'QuestionnaireResponse',
237
+ status: 'in-progress',
238
+ item: [
239
+ {
240
+ linkId: 'duration',
241
+ answer: [
242
+ {
243
+ valueQuantity: {
244
+ value: 48,
245
+ unit: 'Hour(s)',
246
+ system: 'http://unitsofmeasure.org',
247
+ code: 'hour'
248
+ }
249
+ }
250
+ ],
251
+ text: 'Duration'
252
+ },
253
+ {
254
+ linkId: 'duration-comparator',
255
+ answer: [
256
+ {
257
+ valueQuantity: {
258
+ value: 48,
259
+ comparator: '>=',
260
+ unit: 'Hour(s)',
261
+ system: 'http://unitsofmeasure.org',
262
+ code: 'hour'
263
+ }
264
+ }
265
+ ],
266
+ text: 'Duration'
267
+ }
268
+ ],
269
+ questionnaire: 'https://smartforms.csiro.au/docs/components/quantity/unit-option'
270
+ };
271
+
272
+ export const qQuantityCalculation: Questionnaire = {
273
+ resourceType: 'Questionnaire',
274
+ id: 'QuantityCalculation',
275
+ name: 'QuantityCalculation',
276
+ title: 'Quantity Calculation',
277
+ version: '0.1.0',
278
+ status: 'draft',
279
+ publisher: 'AEHRC CSIRO',
280
+ date: '2024-05-01',
281
+ url: 'https://smartforms.csiro.au/docs/components/quantity/calculation',
282
+ extension: [
283
+ {
284
+ url: 'http://hl7.org/fhir/StructureDefinition/variable',
285
+ valueExpression: {
286
+ name: 'durationInDays',
287
+ language: 'text/fhirpath',
288
+ expression: "item.where(linkId='duration-in-days').answer.value"
289
+ }
290
+ }
291
+ ],
292
+ item: [
293
+ {
294
+ extension: [
295
+ {
296
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unit',
297
+ valueCoding: { system: 'http://unitsofmeasure.org', code: 'd', display: 'days' }
298
+ }
299
+ ],
300
+ linkId: 'duration-in-days',
301
+ type: 'quantity',
302
+ repeats: false,
303
+ text: 'Duration in Days'
304
+ },
305
+ {
306
+ extension: [
307
+ {
308
+ url: 'http://hl7.org/fhir/StructureDefinition/questionnaire-unit',
309
+ valueCoding: { system: 'http://unitsofmeasure.org', code: 'h', display: 'hours' }
310
+ },
311
+ {
312
+ url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression',
313
+ valueExpression: {
314
+ description: 'Duration In Hours',
315
+ language: 'text/fhirpath',
316
+ expression: '%durationInDays.value * 24'
317
+ }
318
+ }
319
+ ],
320
+ linkId: 'duration-in-hours',
321
+ type: 'quantity',
322
+ repeats: false,
323
+ text: 'Duration in Hours'
42
324
  }
43
325
  ]
44
326
  };
@@ -17,7 +17,13 @@
17
17
 
18
18
  import type { Meta, StoryObj } from '@storybook/react';
19
19
  import BuildFormWrapperForStorybook from '../storybookWrappers/BuildFormWrapperForStorybook';
20
- import { qQuantityBasic } from '../assets/questionnaires';
20
+ import {
21
+ qQuantityBasic,
22
+ qQuantityCalculation,
23
+ qQuantityUnitOption,
24
+ qrQuantityBasicResponse,
25
+ qrQuantityUnitOptionResponse
26
+ } from '../assets/questionnaires';
21
27
 
22
28
  // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
23
29
  const meta = {
@@ -37,3 +43,29 @@ export const QuantityBasic: Story = {
37
43
  questionnaire: qQuantityBasic
38
44
  }
39
45
  };
46
+
47
+ export const QuantityBasicResponse: Story = {
48
+ args: {
49
+ questionnaire: qQuantityBasic,
50
+ questionnaireResponse: qrQuantityBasicResponse
51
+ }
52
+ };
53
+
54
+ export const QuantityUnitOption: Story = {
55
+ args: {
56
+ questionnaire: qQuantityUnitOption
57
+ }
58
+ };
59
+
60
+ export const QuantityUnitOptionResponse: Story = {
61
+ args: {
62
+ questionnaire: qQuantityUnitOption,
63
+ questionnaireResponse: qrQuantityUnitOptionResponse
64
+ }
65
+ };
66
+
67
+ export const QuantityCalculation: Story = {
68
+ args: {
69
+ questionnaire: qQuantityCalculation
70
+ }
71
+ };
@@ -323,7 +323,11 @@ function parseValueToAnswer(qItem: QuestionnaireItem, value: any): Questionnaire
323
323
  }
324
324
  }
325
325
 
326
- if (typeof value === 'object') {
326
+ if (typeof value === 'object' && value.unit) {
327
+ return { valueQuantity: value };
328
+ }
329
+
330
+ if (typeof value === 'object' && value.system && value.code) {
327
331
  return { valueCoding: value };
328
332
  }
329
333
 
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import type { Coding, Extension, QuestionnaireItem } from 'fhir/r4';
18
+ import type { Coding, Extension, QuestionnaireItem, QuestionnaireItemAnswerOption } from 'fhir/r4';
19
19
  import type { RegexValidation } from '../interfaces/regex.interface';
20
20
  import { structuredDataCapture } from 'fhir-sdc-helpers';
21
21
 
@@ -234,6 +234,24 @@ export function getTextDisplayPrompt(qItem: QuestionnaireItem): string {
234
234
  return '';
235
235
  }
236
236
 
237
+ /**
238
+ * Get Quantity unit for items with itemControlCode unit and has a unit childItem
239
+ *
240
+ * @author Sean Fong
241
+ */
242
+ export function getQuantityUnit(qItem: QuestionnaireItem): QuestionnaireItemAnswerOption | null {
243
+ // Otherwise, check if the item has a unit extension
244
+ const itemControl = qItem.extension?.find(
245
+ (extension: Extension) =>
246
+ extension.url === 'http://hl7.org/fhir/StructureDefinition/questionnaire-unit'
247
+ );
248
+ if (itemControl && itemControl.valueCoding) {
249
+ return itemControl;
250
+ }
251
+
252
+ return null;
253
+ }
254
+
237
255
  /**
238
256
  * Get decimal text display unit for items with itemControlCode unit and has a unit childItem
239
257
  *