@aehrc/smart-forms-renderer 0.24.1 → 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 (65) 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/ChoiceRadioAnswerValueSetFields.d.ts +3 -1
  22. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js +11 -5
  23. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js.map +1 -1
  24. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.d.ts +2 -2
  25. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js +16 -9
  26. package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js.map +1 -1
  27. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.d.ts +1 -0
  28. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js +3 -2
  29. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js.map +1 -1
  30. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js +18 -21
  31. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js.map +1 -1
  32. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.d.ts +1 -0
  33. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.js +3 -1
  34. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.js.map +1 -1
  35. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js +17 -3
  36. package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js.map +1 -1
  37. package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.js.map +1 -1
  38. package/lib/interfaces/calculatedExpression.interface.d.ts +1 -1
  39. package/lib/utils/choice.d.ts +8 -2
  40. package/lib/utils/choice.js +20 -14
  41. package/lib/utils/choice.js.map +1 -1
  42. package/lib/utils/initialise.js +6 -0
  43. package/lib/utils/initialise.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/components/FormComponents/BooleanItem/BooleanField.tsx +5 -1
  46. package/src/components/FormComponents/BooleanItem/BooleanItem.tsx +11 -0
  47. package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.tsx +7 -0
  48. package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.tsx +7 -0
  49. package/src/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.tsx +10 -1
  50. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.tsx +25 -11
  51. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx +65 -32
  52. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.tsx +83 -0
  53. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.tsx +41 -19
  54. package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx +24 -10
  55. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.tsx +5 -2
  56. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx +30 -39
  57. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.tsx +83 -0
  58. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.tsx +13 -2
  59. package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.tsx +20 -0
  60. package/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.tsx +1 -0
  61. package/src/hooks/useBooleanCalculatedExpression.ts +75 -0
  62. package/src/hooks/useCodingCalculatedExpression.ts +80 -0
  63. package/src/interfaces/calculatedExpression.interface.ts +1 -1
  64. package/src/utils/choice.ts +23 -13
  65. package/src/utils/initialise.ts +7 -0
@@ -17,74 +17,107 @@
17
17
 
18
18
  import React from 'react';
19
19
  import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
20
- import { findInAnswerOptions, getQrChoiceValue } from '../../../utils/choice';
20
+ import { findInAnswerOptions, getChoiceControlType, getQrChoiceValue } from '../../../utils/choice';
21
21
  import { createEmptyQrItem } from '../../../utils/qrItem';
22
- import { FullWidthFormComponentBox } from '../../Box.styles';
23
22
  import type {
24
23
  PropsWithIsRepeatedAttribute,
24
+ PropsWithIsTabledAttribute,
25
25
  PropsWithParentIsReadOnlyAttribute,
26
26
  PropsWithQrItemChangeHandler
27
27
  } from '../../../interfaces/renderProps.interface';
28
- import ChoiceRadioAnswerOptionFields from './ChoiceRadioAnswerOptionFields';
29
28
  import useReadOnly from '../../../hooks/useReadOnly';
30
- import ItemFieldGrid from '../ItemParts/ItemFieldGrid';
31
29
  import { useQuestionnaireStore } from '../../../stores';
30
+ import { ChoiceItemControl } from '../../../interfaces/choice.enum';
31
+ import Typography from '@mui/material/Typography';
32
+ import useCodingCalculatedExpression from '../../../hooks/useCodingCalculatedExpression';
33
+ import ChoiceRadioAnswerOptionView from './ChoiceRadioAnswerOptionView';
34
+ import ChoiceSelectAnswerOptionView from './ChoiceSelectAnswerOptionView';
32
35
 
33
36
  interface ChoiceRadioAnswerOptionItemProps
34
37
  extends PropsWithQrItemChangeHandler,
35
38
  PropsWithIsRepeatedAttribute,
39
+ PropsWithIsTabledAttribute,
36
40
  PropsWithParentIsReadOnlyAttribute {
37
41
  qItem: QuestionnaireItem;
38
42
  qrItem: QuestionnaireResponseItem | null;
39
43
  }
40
44
 
41
45
  function ChoiceRadioAnswerOptionItem(props: ChoiceRadioAnswerOptionItemProps) {
42
- const { qItem, qrItem, isRepeated, parentIsReadOnly, onQrItemChange } = props;
46
+ const { qItem, qrItem, isRepeated, isTabled, parentIsReadOnly, onQrItemChange } = props;
43
47
 
44
48
  const onFocusLinkId = useQuestionnaireStore.use.onFocusLinkId();
45
49
 
46
50
  // Init input value
47
- const qrChoiceRadio = qrItem ?? createEmptyQrItem(qItem);
48
- const valueRadio = getQrChoiceValue(qrChoiceRadio);
51
+ const qrChoice = qrItem ?? createEmptyQrItem(qItem);
52
+ const valueChoice = getQrChoiceValue(qrChoice);
49
53
 
50
54
  const readOnly = useReadOnly(qItem, parentIsReadOnly);
51
55
 
56
+ // Process calculated expressions
57
+ const { calcExpUpdated } = useCodingCalculatedExpression({
58
+ qItem: qItem,
59
+ valueInString: valueChoice ?? '',
60
+ onChangeByCalcExpression: (newValueInString) => {
61
+ handleChange(newValueInString);
62
+ }
63
+ });
64
+
52
65
  // Event handlers
53
66
  function handleChange(newValue: string) {
54
- if (qItem.answerOption) {
55
- const qrAnswer = findInAnswerOptions(qItem.answerOption, newValue);
56
- if (qrAnswer) {
57
- onQrItemChange({ ...createEmptyQrItem(qItem), answer: [qrAnswer] });
58
- }
67
+ if (!qItem.answerOption) {
68
+ onQrItemChange(createEmptyQrItem(qItem));
69
+ return;
59
70
  }
60
- }
61
71
 
62
- if (isRepeated) {
63
- return (
64
- <ChoiceRadioAnswerOptionFields
65
- qItem={qItem}
66
- valueRadio={valueRadio}
67
- readOnly={readOnly}
68
- onCheckedChange={handleChange}
69
- />
72
+ const qrAnswer = findInAnswerOptions(qItem.answerOption, newValue);
73
+ onQrItemChange(
74
+ qrAnswer ? { ...createEmptyQrItem(qItem), answer: [qrAnswer] } : createEmptyQrItem(qItem)
70
75
  );
71
76
  }
72
77
 
73
- return (
74
- <FullWidthFormComponentBox
75
- data-test="q-item-choice-radio-answer-option-box"
76
- data-linkid={qItem.linkId}
77
- onClick={() => onFocusLinkId(qItem.linkId)}>
78
- <ItemFieldGrid qItem={qItem} readOnly={readOnly}>
79
- <ChoiceRadioAnswerOptionFields
78
+ // TODO This is in preparation of refactoring all choice answerOption fields into one component
79
+ const choiceControlType = getChoiceControlType(qItem);
80
+
81
+ switch (choiceControlType) {
82
+ // TODO At the moment only this case will be executed because this switch statment was already in the parent components
83
+ case ChoiceItemControl.Radio: {
84
+ return (
85
+ <ChoiceRadioAnswerOptionView
80
86
  qItem={qItem}
81
- valueRadio={valueRadio}
87
+ valueChoice={valueChoice}
88
+ isRepeated={isRepeated}
89
+ isTabled={isTabled}
82
90
  readOnly={readOnly}
91
+ calcExpUpdated={calcExpUpdated}
92
+ onFocusLinkId={() => onFocusLinkId(qItem.linkId)}
83
93
  onCheckedChange={handleChange}
84
94
  />
85
- </ItemFieldGrid>
86
- </FullWidthFormComponentBox>
87
- );
95
+ );
96
+ }
97
+
98
+ case ChoiceItemControl.Select: {
99
+ return (
100
+ <ChoiceSelectAnswerOptionView
101
+ qItem={qItem}
102
+ valueChoice={valueChoice}
103
+ isRepeated={isRepeated}
104
+ isTabled={isTabled}
105
+ readOnly={readOnly}
106
+ calcExpUpdated={calcExpUpdated}
107
+ onFocusLinkId={() => onFocusLinkId(qItem.linkId)}
108
+ onSelectChange={handleChange}
109
+ />
110
+ );
111
+ }
112
+
113
+ default: {
114
+ return (
115
+ <Typography>
116
+ Something has went wrong when parsing item {qItem.linkId} - {qItem.text}
117
+ </Typography>
118
+ );
119
+ }
120
+ }
88
121
  }
89
122
 
90
123
  export default ChoiceRadioAnswerOptionItem;
@@ -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 ChoiceRadioAnswerOptionFields from './ChoiceRadioAnswerOptionFields';
20
+ import { FullWidthFormComponentBox } from '../../Box.styles';
21
+ import ItemFieldGrid from '../ItemParts/ItemFieldGrid';
22
+ import type {
23
+ PropsWithIsRepeatedAttribute,
24
+ PropsWithIsTabledAttribute
25
+ } from '../../../interfaces/renderProps.interface';
26
+ import type { QuestionnaireItem } from 'fhir/r4';
27
+
28
+ interface ChoiceRadioAnswerOptionViewProps
29
+ extends PropsWithIsRepeatedAttribute,
30
+ PropsWithIsTabledAttribute {
31
+ qItem: QuestionnaireItem;
32
+ valueChoice: string | null;
33
+ readOnly: boolean;
34
+ calcExpUpdated: boolean;
35
+ onCheckedChange: (linkId: string) => void;
36
+ onFocusLinkId: () => void;
37
+ }
38
+
39
+ function ChoiceRadioAnswerOptionView(props: ChoiceRadioAnswerOptionViewProps) {
40
+ const {
41
+ qItem,
42
+ valueChoice,
43
+ isRepeated,
44
+ isTabled,
45
+ readOnly,
46
+ calcExpUpdated,
47
+ onFocusLinkId,
48
+ onCheckedChange
49
+ } = props;
50
+
51
+ if (isRepeated) {
52
+ return (
53
+ <ChoiceRadioAnswerOptionFields
54
+ qItem={qItem}
55
+ valueRadio={valueChoice}
56
+ isTabled={isTabled}
57
+ readOnly={readOnly}
58
+ calcExpUpdated={calcExpUpdated}
59
+ onCheckedChange={onCheckedChange}
60
+ />
61
+ );
62
+ }
63
+
64
+ return (
65
+ <FullWidthFormComponentBox
66
+ data-test="q-item-choice-radio-answer-option-box"
67
+ data-linkid={qItem.linkId}
68
+ onClick={onFocusLinkId}>
69
+ <ItemFieldGrid qItem={qItem} readOnly={readOnly}>
70
+ <ChoiceRadioAnswerOptionFields
71
+ qItem={qItem}
72
+ valueRadio={valueChoice}
73
+ readOnly={readOnly}
74
+ isTabled={isTabled}
75
+ calcExpUpdated={calcExpUpdated}
76
+ onCheckedChange={onCheckedChange}
77
+ />
78
+ </ItemFieldGrid>
79
+ </FullWidthFormComponentBox>
80
+ );
81
+ }
82
+
83
+ export default ChoiceRadioAnswerOptionView;
@@ -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
  )