@aehrc/smart-forms-renderer 0.23.2 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/lib/components/FormComponents/BooleanItem/BooleanField.d.ts +1 -0
  2. package/lib/components/FormComponents/BooleanItem/BooleanField.js +3 -1
  3. package/lib/components/FormComponents/BooleanItem/BooleanField.js.map +1 -1
  4. package/lib/components/FormComponents/BooleanItem/BooleanItem.js +10 -3
  5. package/lib/components/FormComponents/BooleanItem/BooleanItem.js.map +1 -1
  6. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.d.ts +2 -2
  7. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.js +4 -1
  8. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.js.map +1 -1
  9. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.d.ts +2 -2
  10. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.js +4 -1
  11. package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.js.map +1 -1
  12. package/lib/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.d.ts +1 -1
  13. package/lib/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.js +10 -5
  14. package/lib/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.js.map +1 -1
  15. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.d.ts +3 -1
  16. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js +9 -3
  17. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js.map +1 -1
  18. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.d.ts +2 -2
  19. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js +39 -17
  20. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js.map +1 -1
  21. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.d.ts +13 -0
  22. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.js +31 -0
  23. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.js.map +1 -0
  24. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.d.ts +3 -1
  25. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js +11 -5
  26. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js.map +1 -1
  27. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.d.ts +2 -2
  28. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js +16 -9
  29. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js.map +1 -1
  30. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.d.ts +1 -0
  31. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js +3 -2
  32. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js.map +1 -1
  33. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js +18 -21
  34. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js.map +1 -1
  35. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.d.ts +13 -0
  36. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.js +31 -0
  37. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.js.map +1 -0
  38. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.d.ts +1 -0
  39. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.js +3 -1
  40. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.js.map +1 -1
  41. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js +17 -3
  42. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js.map +1 -1
  43. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.js.map +1 -1
  44. package/lib/hooks/useBooleanCalculatedExpression.d.ts +11 -0
  45. package/lib/hooks/useBooleanCalculatedExpression.js +47 -0
  46. package/lib/hooks/useBooleanCalculatedExpression.js.map +1 -0
  47. package/lib/hooks/useCodingCalculatedExpression.d.ts +11 -0
  48. package/lib/hooks/useCodingCalculatedExpression.js +51 -0
  49. package/lib/hooks/useCodingCalculatedExpression.js.map +1 -0
  50. package/lib/interfaces/calculatedExpression.interface.d.ts +1 -1
  51. package/lib/utils/choice.d.ts +8 -2
  52. package/lib/utils/choice.js +20 -14
  53. package/lib/utils/choice.js.map +1 -1
  54. package/lib/utils/enableWhen.js +11 -10
  55. package/lib/utils/enableWhen.js.map +1 -1
  56. package/lib/utils/initialise.js +6 -0
  57. package/lib/utils/initialise.js.map +1 -1
  58. package/package.json +1 -1
  59. package/src/components/FormComponents/BooleanItem/BooleanField.tsx +5 -1
  60. package/src/components/FormComponents/BooleanItem/BooleanItem.tsx +11 -0
  61. package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.tsx +7 -0
  62. package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.tsx +7 -0
  63. package/src/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.tsx +10 -1
  64. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.tsx +25 -11
  65. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx +65 -32
  66. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.tsx +83 -0
  67. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.tsx +41 -19
  68. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx +24 -10
  69. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.tsx +5 -2
  70. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx +30 -39
  71. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.tsx +83 -0
  72. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.tsx +13 -2
  73. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.tsx +20 -0
  74. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.tsx +1 -0
  75. package/src/hooks/useBooleanCalculatedExpression.ts +75 -0
  76. package/src/hooks/useCodingCalculatedExpression.ts +80 -0
  77. package/src/interfaces/calculatedExpression.interface.ts +1 -1
  78. package/src/utils/choice.ts +23 -13
  79. package/src/utils/enableWhen.ts +32 -10
  80. package/src/utils/initialise.ts +7 -0
@@ -25,40 +25,62 @@ import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
25
25
  import { StyledAlert } from '../../Alert.styles';
26
26
  import type { TerminologyError } from '../../../hooks/useValueSetCodings';
27
27
  import { getChoiceOrientation } from '../../../utils/choice';
28
+ import { TEXT_FIELD_WIDTH } from '../Textfield.styles';
29
+ import FadingCheckIcon from '../ItemParts/FadingCheckIcon';
30
+ import type { PropsWithIsTabledAttribute } from '../../../interfaces/renderProps.interface';
31
+ import Box from '@mui/material/Box';
28
32
 
29
- interface ChoiceRadioAnswerValueSetFieldsProps {
33
+ interface ChoiceRadioAnswerValueSetFieldsProps extends PropsWithIsTabledAttribute {
30
34
  qItem: QuestionnaireItem;
31
35
  codings: Coding[];
32
36
  valueRadio: string | null;
33
37
  readOnly: boolean;
38
+ calcExpUpdated: boolean;
34
39
  terminologyError: TerminologyError;
35
40
  onCheckedChange: (newValue: string) => void;
36
41
  }
37
42
 
38
43
  function ChoiceRadioAnswerValueSetFields(props: ChoiceRadioAnswerValueSetFieldsProps) {
39
- const { qItem, codings, valueRadio, readOnly, terminologyError, onCheckedChange } = props;
44
+ const {
45
+ qItem,
46
+ codings,
47
+ valueRadio,
48
+ readOnly,
49
+ calcExpUpdated,
50
+ terminologyError,
51
+ isTabled,
52
+ onCheckedChange
53
+ } = props;
40
54
 
41
55
  const orientation = getChoiceOrientation(qItem) ?? ChoiceItemOrientation.Vertical;
42
56
 
43
57
  if (codings.length > 0) {
44
58
  return (
45
- <StyledRadioGroup
46
- row={orientation === ChoiceItemOrientation.Horizontal}
47
- name={qItem.text}
48
- id={qItem.id}
49
- onChange={(e) => onCheckedChange(e.target.value)}
50
- value={valueRadio ?? null}>
51
- {codings.map((coding: Coding) => {
52
- return (
53
- <ChoiceRadioSingle
54
- key={coding.code ?? ''}
55
- value={coding.code ?? ''}
56
- label={coding.display ?? `${coding.code}`}
57
- readOnly={readOnly}
58
- />
59
- );
60
- })}
61
- </StyledRadioGroup>
59
+ <Box
60
+ display="flex"
61
+ alignItems="center"
62
+ sx={{ maxWidth: !isTabled ? TEXT_FIELD_WIDTH : 3000, minWidth: 160 }}>
63
+ <StyledRadioGroup
64
+ row={orientation === ChoiceItemOrientation.Horizontal}
65
+ name={qItem.text}
66
+ id={qItem.id}
67
+ onChange={(e) => onCheckedChange(e.target.value)}
68
+ value={valueRadio ?? null}>
69
+ {codings.map((coding: Coding) => {
70
+ return (
71
+ <ChoiceRadioSingle
72
+ key={coding.code ?? ''}
73
+ value={coding.code ?? ''}
74
+ label={coding.display ?? `${coding.code}`}
75
+ readOnly={readOnly}
76
+ />
77
+ );
78
+ })}
79
+ </StyledRadioGroup>
80
+ <Box flexGrow={1} />
81
+
82
+ <FadingCheckIcon fadeIn={calcExpUpdated} />
83
+ </Box>
62
84
  );
63
85
  }
64
86
 
@@ -15,14 +15,15 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import React from 'react';
18
+ import React, { useMemo } from 'react';
19
19
  import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
20
- import { findInAnswerValueSetCodings } from '../../../utils/choice';
20
+ import { convertCodingsToAnswerOptions, findInAnswerOptions } from '../../../utils/choice';
21
21
  import { createEmptyQrItem } from '../../../utils/qrItem';
22
22
  import { FullWidthFormComponentBox } from '../../Box.styles';
23
23
  import useValueSetCodings from '../../../hooks/useValueSetCodings';
24
24
  import type {
25
25
  PropsWithIsRepeatedAttribute,
26
+ PropsWithIsTabledAttribute,
26
27
  PropsWithParentIsReadOnlyAttribute,
27
28
  PropsWithQrItemChangeHandler
28
29
  } from '../../../interfaces/renderProps.interface';
@@ -30,17 +31,19 @@ import ChoiceRadioAnswerValueSetFields from './ChoiceRadioAnswerValueSetFields';
30
31
  import useReadOnly from '../../../hooks/useReadOnly';
31
32
  import ItemFieldGrid from '../ItemParts/ItemFieldGrid';
32
33
  import { useQuestionnaireStore } from '../../../stores';
34
+ import useCodingCalculatedExpression from '../../../hooks/useCodingCalculatedExpression';
33
35
 
34
36
  interface ChoiceRadioAnswerValueSetItemProps
35
37
  extends PropsWithQrItemChangeHandler,
36
38
  PropsWithIsRepeatedAttribute,
39
+ PropsWithIsTabledAttribute,
37
40
  PropsWithParentIsReadOnlyAttribute {
38
41
  qItem: QuestionnaireItem;
39
42
  qrItem: QuestionnaireResponseItem | null;
40
43
  }
41
44
 
42
45
  function ChoiceRadioAnswerValueSetItem(props: ChoiceRadioAnswerValueSetItemProps) {
43
- const { qItem, qrItem, isRepeated, parentIsReadOnly, onQrItemChange } = props;
46
+ const { qItem, qrItem, isRepeated, isTabled, parentIsReadOnly, onQrItemChange } = props;
44
47
 
45
48
  const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId();
46
49
 
@@ -57,15 +60,22 @@ function ChoiceRadioAnswerValueSetItem(props: ChoiceRadioAnswerValueSetItemProps
57
60
  // Get codings/options from valueSet
58
61
  const { codings, terminologyError } = useValueSetCodings(qItem);
59
62
 
63
+ const answerOptions = useMemo(() => convertCodingsToAnswerOptions(codings), [codings]);
64
+
65
+ const { calcExpUpdated } = useCodingCalculatedExpression({
66
+ qItem: qItem,
67
+ valueInString: valueRadio ?? '',
68
+ onChangeByCalcExpression: (newValueInString) => {
69
+ handleChange(newValueInString);
70
+ }
71
+ });
72
+
60
73
  function handleChange(newValue: string) {
61
74
  if (codings.length > 0) {
62
- const qrAnswer = findInAnswerValueSetCodings(codings, newValue);
63
- if (qrAnswer) {
64
- onQrItemChange({
65
- ...createEmptyQrItem(qItem),
66
- answer: [{ valueCoding: qrAnswer }]
67
- });
68
- }
75
+ const qrAnswer = findInAnswerOptions(answerOptions, newValue);
76
+ onQrItemChange(
77
+ qrAnswer ? { ...createEmptyQrItem(qItem), answer: [qrAnswer] } : createEmptyQrItem(qItem)
78
+ );
69
79
  }
70
80
  }
71
81
 
@@ -76,7 +86,9 @@ function ChoiceRadioAnswerValueSetItem(props: ChoiceRadioAnswerValueSetItemProps
76
86
  codings={codings}
77
87
  valueRadio={valueRadio}
78
88
  readOnly={readOnly}
89
+ calcExpUpdated={calcExpUpdated}
79
90
  terminologyError={terminologyError}
91
+ isTabled={isTabled}
80
92
  onCheckedChange={handleChange}
81
93
  />
82
94
  );
@@ -93,7 +105,9 @@ function ChoiceRadioAnswerValueSetItem(props: ChoiceRadioAnswerValueSetItemProps
93
105
  codings={codings}
94
106
  valueRadio={valueRadio}
95
107
  readOnly={readOnly}
108
+ calcExpUpdated={calcExpUpdated}
96
109
  terminologyError={terminologyError}
110
+ isTabled={isTabled}
97
111
  onCheckedChange={handleChange}
98
112
  />
99
113
  </ItemFieldGrid>
@@ -28,14 +28,17 @@ interface ChoiceSelectAnswerOptionFieldsProps extends PropsWithIsTabledAttribute
28
28
  qItem: QuestionnaireItem;
29
29
  valueSelect: string;
30
30
  readOnly: boolean;
31
+ calcExpUpdated: boolean;
31
32
  onSelectChange: (newValue: string) => void;
32
33
  }
33
34
 
34
35
  function ChoiceSelectAnswerOptionFields(props: ChoiceSelectAnswerOptionFieldsProps) {
35
- const { qItem, valueSelect, readOnly, isTabled, onSelectChange } = props;
36
+ const { qItem, valueSelect, readOnly, calcExpUpdated, isTabled, onSelectChange } = props;
36
37
 
37
38
  const { displayUnit, displayPrompt, entryFormat } = useRenderingExtensions(qItem);
38
39
 
40
+ // TODO use calcExpUpdated as updated feedback
41
+
39
42
  return (
40
43
  <Select
41
44
  id={qItem.id}
@@ -45,7 +48,7 @@ function ChoiceSelectAnswerOptionFields(props: ChoiceSelectAnswerOptionFieldsPro
45
48
  fullWidth
46
49
  placeholder={entryFormat}
47
50
  label={displayPrompt}
48
- endAdornment={<InputAdornment position={'end'}>{displayUnit}</InputAdornment>}
51
+ endAdornment={<InputAdornment position="end">{displayUnit}</InputAdornment>}
49
52
  sx={{ maxWidth: !isTabled ? TEXT_FIELD_WIDTH : 3000, minWidth: 160 }}
50
53
  size="small"
51
54
  onChange={(e) => onSelectChange(e.target.value)}>
@@ -20,18 +20,18 @@ import React from 'react';
20
20
  import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
21
21
  import { findInAnswerOptions, getQrChoiceValue } from '../../../utils/choice';
22
22
  import { createEmptyQrItem } from '../../../utils/qrItem';
23
- import { FullWidthFormComponentBox } from '../../Box.styles';
24
23
  import type {
25
24
  PropsWithIsRepeatedAttribute,
26
25
  PropsWithIsTabledAttribute,
27
26
  PropsWithParentIsReadOnlyAttribute,
28
27
  PropsWithQrItemChangeHandler
29
28
  } from '../../../interfaces/renderProps.interface';
30
- import ChoiceSelectAnswerOptionFields from './ChoiceSelectAnswerOptionFields';
31
29
  import useReadOnly from '../../../hooks/useReadOnly';
32
- import ItemFieldGrid from '../ItemParts/ItemFieldGrid';
33
30
  import { useQuestionnaireStore } from '../../../stores';
31
+ import useCodingCalculatedExpression from '../../../hooks/useCodingCalculatedExpression';
32
+ import ChoiceSelectAnswerOptionView from './ChoiceSelectAnswerOptionView';
34
33
 
34
+ // TODO eventually merge this item with ChoiceRadioAnswerOptionItem
35
35
  interface ChoiceSelectAnswerOptionItemProps
36
36
  extends PropsWithQrItemChangeHandler,
37
37
  PropsWithIsRepeatedAttribute,
@@ -49,51 +49,42 @@ function ChoiceSelectAnswerOptionItem(props: ChoiceSelectAnswerOptionItemProps)
49
49
  const readOnly = useReadOnly(qItem, parentIsReadOnly);
50
50
 
51
51
  // Init input value
52
- const qrChoiceSelect = qrItem ?? createEmptyQrItem(qItem);
53
- let valueSelect = getQrChoiceValue(qrChoiceSelect);
54
- if (!valueSelect) {
55
- valueSelect = '';
56
- }
52
+ const qrChoice = qrItem ?? createEmptyQrItem(qItem);
53
+ const valueChoice = getQrChoiceValue(qrChoice);
54
+
55
+ // Process calculated expressions
56
+ const { calcExpUpdated } = useCodingCalculatedExpression({
57
+ qItem: qItem,
58
+ valueInString: valueChoice ?? '',
59
+ onChangeByCalcExpression: (newValueInString) => {
60
+ handleChange(newValueInString);
61
+ }
62
+ });
57
63
 
58
64
  // Event handlers
59
65
  function handleChange(newValue: string) {
60
- if (qItem.answerOption) {
61
- const qrAnswer = findInAnswerOptions(qItem.answerOption, newValue);
62
- if (qrAnswer) {
63
- onQrItemChange({ ...createEmptyQrItem(qItem), answer: [qrAnswer] });
64
- return;
65
- }
66
+ if (!qItem.answerOption) {
67
+ onQrItemChange(createEmptyQrItem(qItem));
68
+ return;
66
69
  }
67
- onQrItemChange(createEmptyQrItem(qItem));
68
- }
69
70
 
70
- if (isRepeated) {
71
- return (
72
- <ChoiceSelectAnswerOptionFields
73
- qItem={qItem}
74
- valueSelect={valueSelect}
75
- readOnly={readOnly}
76
- isTabled={isTabled}
77
- onSelectChange={handleChange}
78
- />
71
+ const qrAnswer = findInAnswerOptions(qItem.answerOption, newValue);
72
+ onQrItemChange(
73
+ qrAnswer ? { ...createEmptyQrItem(qItem), answer: [qrAnswer] } : createEmptyQrItem(qItem)
79
74
  );
80
75
  }
81
76
 
82
77
  return (
83
- <FullWidthFormComponentBox
84
- data-test="q-item-choice-select-answer-option-box"
85
- data-linkid={qItem.linkId}
86
- onClick={() => onFocusLinkId(qItem.linkId)}>
87
- <ItemFieldGrid qItem={qItem} readOnly={readOnly}>
88
- <ChoiceSelectAnswerOptionFields
89
- qItem={qItem}
90
- valueSelect={valueSelect}
91
- readOnly={readOnly}
92
- isTabled={isTabled}
93
- onSelectChange={handleChange}
94
- />
95
- </ItemFieldGrid>
96
- </FullWidthFormComponentBox>
78
+ <ChoiceSelectAnswerOptionView
79
+ qItem={qItem}
80
+ valueChoice={valueChoice}
81
+ readOnly={readOnly}
82
+ calcExpUpdated={calcExpUpdated}
83
+ isRepeated={isRepeated}
84
+ isTabled={isTabled}
85
+ onFocusLinkId={() => onFocusLinkId(qItem.linkId)}
86
+ onSelectChange={handleChange}
87
+ />
97
88
  );
98
89
  }
99
90
 
@@ -0,0 +1,83 @@
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 from 'react';
19
+ import { FullWidthFormComponentBox } from '../../Box.styles';
20
+ import ItemFieldGrid from '../ItemParts/ItemFieldGrid';
21
+ import type {
22
+ PropsWithIsRepeatedAttribute,
23
+ PropsWithIsTabledAttribute
24
+ } from '../../../interfaces/renderProps.interface';
25
+ import type { QuestionnaireItem } from 'fhir/r4';
26
+ import ChoiceSelectAnswerOptionFields from './ChoiceSelectAnswerOptionFields';
27
+
28
+ interface ChoiceSelectAnswerOptionViewProps
29
+ extends PropsWithIsRepeatedAttribute,
30
+ PropsWithIsTabledAttribute {
31
+ qItem: QuestionnaireItem;
32
+ valueChoice: string | null;
33
+ readOnly: boolean;
34
+ calcExpUpdated: boolean;
35
+ onSelectChange: (linkId: string) => void;
36
+ onFocusLinkId: () => void;
37
+ }
38
+
39
+ function ChoiceSelectAnswerOptionView(props: ChoiceSelectAnswerOptionViewProps) {
40
+ const {
41
+ qItem,
42
+ valueChoice,
43
+ isRepeated,
44
+ isTabled,
45
+ readOnly,
46
+ calcExpUpdated,
47
+ onFocusLinkId,
48
+ onSelectChange
49
+ } = props;
50
+
51
+ if (isRepeated) {
52
+ return (
53
+ <ChoiceSelectAnswerOptionFields
54
+ qItem={qItem}
55
+ valueSelect={valueChoice ?? ''}
56
+ readOnly={readOnly}
57
+ calcExpUpdated={calcExpUpdated}
58
+ isTabled={isTabled}
59
+ onSelectChange={onSelectChange}
60
+ />
61
+ );
62
+ }
63
+
64
+ return (
65
+ <FullWidthFormComponentBox
66
+ data-test="q-item-choice-select-answer-option-box"
67
+ data-linkid={qItem.linkId}
68
+ onClick={onFocusLinkId}>
69
+ <ItemFieldGrid qItem={qItem} readOnly={readOnly}>
70
+ <ChoiceSelectAnswerOptionFields
71
+ qItem={qItem}
72
+ valueSelect={valueChoice ?? ''}
73
+ readOnly={readOnly}
74
+ calcExpUpdated={calcExpUpdated}
75
+ isTabled={isTabled}
76
+ onSelectChange={onSelectChange}
77
+ />
78
+ </ItemFieldGrid>
79
+ </FullWidthFormComponentBox>
80
+ );
81
+ }
82
+
83
+ export default ChoiceSelectAnswerOptionView;
@@ -25,6 +25,7 @@ import type { Coding, QuestionnaireItem } from 'fhir/r4';
25
25
  import useRenderingExtensions from '../../../hooks/useRenderingExtensions';
26
26
  import type { PropsWithIsTabledAttribute } from '../../../interfaces/renderProps.interface';
27
27
  import type { TerminologyError } from '../../../hooks/useValueSetCodings';
28
+ import FadingCheckIcon from '../ItemParts/FadingCheckIcon';
28
29
 
29
30
  interface ChoiceSelectAnswerValueSetFieldsProps extends PropsWithIsTabledAttribute {
30
31
  qItem: QuestionnaireItem;
@@ -32,12 +33,21 @@ interface ChoiceSelectAnswerValueSetFieldsProps extends PropsWithIsTabledAttribu
32
33
  valueCoding: Coding | null;
33
34
  terminologyError: TerminologyError;
34
35
  readOnly: boolean;
36
+ calcExpUpdated: boolean;
35
37
  onSelectChange: (newValue: Coding | null) => void;
36
38
  }
37
39
 
38
40
  function ChoiceSelectAnswerValueSetFields(props: ChoiceSelectAnswerValueSetFieldsProps) {
39
- const { qItem, codings, valueCoding, terminologyError, readOnly, isTabled, onSelectChange } =
40
- props;
41
+ const {
42
+ qItem,
43
+ codings,
44
+ valueCoding,
45
+ terminologyError,
46
+ readOnly,
47
+ calcExpUpdated,
48
+ isTabled,
49
+ onSelectChange
50
+ } = props;
41
51
 
42
52
  const { displayUnit, displayPrompt, entryFormat } = useRenderingExtensions(qItem);
43
53
 
@@ -65,6 +75,7 @@ function ChoiceSelectAnswerValueSetFields(props: ChoiceSelectAnswerValueSetField
65
75
  endAdornment: (
66
76
  <>
67
77
  {params.InputProps.endAdornment}
78
+ <FadingCheckIcon fadeIn={calcExpUpdated} />
68
79
  {displayUnit}
69
80
  </>
70
81
  )
@@ -31,6 +31,8 @@ import ChoiceSelectAnswerValueSetFields from './ChoiceSelectAnswerValueSetFields
31
31
  import useReadOnly from '../../../hooks/useReadOnly';
32
32
  import ItemFieldGrid from '../ItemParts/ItemFieldGrid';
33
33
  import { useQuestionnaireStore } from '../../../stores';
34
+ import useCodingCalculatedExpression from '../../../hooks/useCodingCalculatedExpression';
35
+ import { convertCodingsToAnswerOptions, findInAnswerOptions } from '../../../utils/choice';
34
36
 
35
37
  interface ChoiceSelectAnswerValueSetItemProps
36
38
  extends PropsWithQrItemChangeHandler,
@@ -79,6 +81,22 @@ function ChoiceSelectAnswerValueSetItem(props: ChoiceSelectAnswerValueSetItemPro
79
81
  []
80
82
  );
81
83
 
84
+ const answerOptions = useMemo(() => convertCodingsToAnswerOptions(codings), [codings]);
85
+
86
+ // Process calculated expressions
87
+ const { calcExpUpdated } = useCodingCalculatedExpression({
88
+ qItem: qItem,
89
+ valueInString: valueCoding?.code ?? '',
90
+ onChangeByCalcExpression: (newValueInString) => {
91
+ if (codings.length > 0) {
92
+ const qrAnswer = findInAnswerOptions(answerOptions, newValueInString);
93
+ onQrItemChange(
94
+ qrAnswer ? { ...createEmptyQrItem(qItem), answer: [qrAnswer] } : createEmptyQrItem(qItem)
95
+ );
96
+ }
97
+ }
98
+ });
99
+
82
100
  // Event handlers
83
101
  function handleChange(newValue: Coding | null) {
84
102
  if (newValue) {
@@ -99,6 +117,7 @@ function ChoiceSelectAnswerValueSetItem(props: ChoiceSelectAnswerValueSetItemPro
99
117
  valueCoding={valueCoding}
100
118
  terminologyError={terminologyError}
101
119
  readOnly={readOnly}
120
+ calcExpUpdated={calcExpUpdated}
102
121
  isTabled={isTabled}
103
122
  onSelectChange={handleChange}
104
123
  />
@@ -117,6 +136,7 @@ function ChoiceSelectAnswerValueSetItem(props: ChoiceSelectAnswerValueSetItemPro
117
136
  valueCoding={valueCoding}
118
137
  terminologyError={terminologyError}
119
138
  readOnly={readOnly}
139
+ calcExpUpdated={calcExpUpdated}
120
140
  isTabled={isTabled}
121
141
  onSelectChange={handleChange}
122
142
  />
@@ -73,6 +73,7 @@ function OpenChoiceSelectAnswerValueSetItem(props: OpenChoiceSelectAnswerValueSe
73
73
  }
74
74
  return;
75
75
  }
76
+
76
77
  onQrItemChange(createEmptyQrItem(qItem));
77
78
  }
78
79
 
@@ -0,0 +1,75 @@
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 { createEmptyQrItem } from '../utils/qrItem';
20
+ import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
21
+ import { useQuestionnaireStore } from '../stores';
22
+
23
+ interface UseBooleanCalculatedExpression {
24
+ calcExpUpdated: boolean;
25
+ }
26
+
27
+ interface UseBooleanCalculatedExpressionProps {
28
+ qItem: QuestionnaireItem;
29
+ booleanValue: boolean | undefined;
30
+ onQrItemChange: (qrItem: QuestionnaireResponseItem) => void;
31
+ }
32
+
33
+ function UseBooleanCalculatedExpression(
34
+ props: UseBooleanCalculatedExpressionProps
35
+ ): UseBooleanCalculatedExpression {
36
+ const { qItem, booleanValue, onQrItemChange } = props;
37
+
38
+ const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
39
+
40
+ const [calcExpUpdated, setCalcExpUpdated] = useState(false);
41
+
42
+ useEffect(
43
+ () => {
44
+ const calcExpression = calculatedExpressions[qItem.linkId]?.find(
45
+ (exp) => exp.from === 'item'
46
+ );
47
+
48
+ if (!calcExpression) {
49
+ return;
50
+ }
51
+
52
+ // only update if calculated value is different from current value
53
+ if (calcExpression.value !== booleanValue && typeof calcExpression.value === 'boolean') {
54
+ // update ui to show calculated value changes
55
+ setCalcExpUpdated(true);
56
+ setTimeout(() => {
57
+ setCalcExpUpdated(false);
58
+ }, 500);
59
+
60
+ // update questionnaireResponse
61
+ onQrItemChange({
62
+ ...createEmptyQrItem(qItem),
63
+ answer: [{ valueBoolean: calcExpression.value }]
64
+ });
65
+ }
66
+ },
67
+ // Only trigger this effect if calculatedExpression of item changes
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ [calculatedExpressions]
70
+ );
71
+
72
+ return { calcExpUpdated: calcExpUpdated };
73
+ }
74
+
75
+ export default UseBooleanCalculatedExpression;
@@ -0,0 +1,80 @@
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
+
22
+ interface UseCodingCalculatedExpression {
23
+ calcExpUpdated: boolean;
24
+ }
25
+
26
+ interface UseCodingCalculatedExpressionProps {
27
+ qItem: QuestionnaireItem;
28
+ valueInString: string;
29
+ onChangeByCalcExpression: (newValueInString: string) => void;
30
+ }
31
+
32
+ // TODO use this in all choice and open choice items if possible
33
+ function UseCodingCalculatedExpression(
34
+ props: UseCodingCalculatedExpressionProps
35
+ ): UseCodingCalculatedExpression {
36
+ const { qItem, valueInString, onChangeByCalcExpression } = props;
37
+
38
+ const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
39
+
40
+ const [calcExpUpdated, setCalcExpUpdated] = useState(false);
41
+
42
+ useEffect(
43
+ () => {
44
+ const calcExpression = calculatedExpressions[qItem.linkId]?.find(
45
+ (exp) => exp.from === 'item'
46
+ );
47
+
48
+ if (!calcExpression) {
49
+ return;
50
+ }
51
+
52
+ // only update if calculated value is different from current value
53
+ if (
54
+ calcExpression.value !== valueInString &&
55
+ (typeof calcExpression.value === 'string' || typeof calcExpression.value === 'number')
56
+ ) {
57
+ // update ui to show calculated value changes
58
+ setCalcExpUpdated(true);
59
+ setTimeout(() => {
60
+ setCalcExpUpdated(false);
61
+ }, 500);
62
+
63
+ const updatedValueInString =
64
+ typeof calcExpression.value === 'string'
65
+ ? calcExpression.value
66
+ : calcExpression.value.toString();
67
+
68
+ // update questionnaireResponse
69
+ onChangeByCalcExpression(updatedValueInString);
70
+ }
71
+ },
72
+ // Only trigger this effect if calculatedExpression of item changes
73
+ // eslint-disable-next-line react-hooks/exhaustive-deps
74
+ [calculatedExpressions]
75
+ );
76
+
77
+ return { calcExpUpdated: calcExpUpdated };
78
+ }
79
+
80
+ export default UseCodingCalculatedExpression;
@@ -1,5 +1,5 @@
1
1
  export interface CalculatedExpression {
2
2
  expression: string;
3
3
  from: 'item' | 'item._text';
4
- value?: number | string | object;
4
+ value?: number | string | boolean | object;
5
5
  }