@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.
- package/lib/components/FormComponents/BooleanItem/BooleanField.d.ts +1 -0
- package/lib/components/FormComponents/BooleanItem/BooleanField.js +3 -1
- package/lib/components/FormComponents/BooleanItem/BooleanField.js.map +1 -1
- package/lib/components/FormComponents/BooleanItem/BooleanItem.js +10 -3
- package/lib/components/FormComponents/BooleanItem/BooleanItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.d.ts +2 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.js +4 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.d.ts +2 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.js +4 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.d.ts +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.js +10 -5
- package/lib/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.d.ts +3 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js +9 -3
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.d.ts +2 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js +39 -17
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.d.ts +13 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.js +31 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.js.map +1 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.d.ts +3 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js +11 -5
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.d.ts +2 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js +16 -9
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.d.ts +1 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js +3 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js +18 -21
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.d.ts +13 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.js +31 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.js.map +1 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.d.ts +1 -0
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.js +3 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js +17 -3
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js.map +1 -1
- package/lib/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.js.map +1 -1
- package/lib/hooks/useBooleanCalculatedExpression.d.ts +11 -0
- package/lib/hooks/useBooleanCalculatedExpression.js +47 -0
- package/lib/hooks/useBooleanCalculatedExpression.js.map +1 -0
- package/lib/hooks/useCodingCalculatedExpression.d.ts +11 -0
- package/lib/hooks/useCodingCalculatedExpression.js +51 -0
- package/lib/hooks/useCodingCalculatedExpression.js.map +1 -0
- package/lib/interfaces/calculatedExpression.interface.d.ts +1 -1
- package/lib/utils/choice.d.ts +8 -2
- package/lib/utils/choice.js +20 -14
- package/lib/utils/choice.js.map +1 -1
- package/lib/utils/enableWhen.js +11 -10
- package/lib/utils/enableWhen.js.map +1 -1
- package/lib/utils/initialise.js +6 -0
- package/lib/utils/initialise.js.map +1 -1
- package/package.json +1 -1
- package/src/components/FormComponents/BooleanItem/BooleanField.tsx +5 -1
- package/src/components/FormComponents/BooleanItem/BooleanItem.tsx +11 -0
- package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerOptionItem.tsx +7 -0
- package/src/components/FormComponents/ChoiceItems/ChoiceCheckboxAnswerValueSetItem.tsx +7 -0
- package/src/components/FormComponents/ChoiceItems/ChoiceItemSwitcher.tsx +10 -1
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionFields.tsx +25 -11
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx +65 -32
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionView.tsx +83 -0
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetFields.tsx +41 -19
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx +24 -10
- package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.tsx +5 -2
- package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx +30 -39
- package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionView.tsx +83 -0
- package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetFields.tsx +13 -2
- package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.tsx +20 -0
- package/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerValueSetItem.tsx +1 -0
- package/src/hooks/useBooleanCalculatedExpression.ts +75 -0
- package/src/hooks/useCodingCalculatedExpression.ts +80 -0
- package/src/interfaces/calculatedExpression.interface.ts +1 -1
- package/src/utils/choice.ts +23 -13
- package/src/utils/enableWhen.ts +32 -10
- 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 {
|
|
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
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 {
|
|
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 =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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=
|
|
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
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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 {
|
|
40
|
-
|
|
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
|
/>
|
|
@@ -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;
|