@aehrc/smart-forms-renderer 0.25.2 → 0.26.1
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/BooleanItem.js +6 -1
- package/lib/components/FormComponents/BooleanItem/BooleanItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js +5 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js +5 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js +5 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.js.map +1 -1
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js +5 -2
- package/lib/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.js.map +1 -1
- package/lib/components/FormComponents/DecimalItem/DecimalItem.js +9 -3
- package/lib/components/FormComponents/DecimalItem/DecimalItem.js.map +1 -1
- package/lib/components/FormComponents/IntegerItem/IntegerItem.js +7 -3
- package/lib/components/FormComponents/IntegerItem/IntegerItem.js.map +1 -1
- package/lib/components/FormComponents/StringItem/StringItem.js +7 -3
- package/lib/components/FormComponents/StringItem/StringItem.js.map +1 -1
- package/lib/components/FormComponents/TextItem/TextItem.js +7 -3
- package/lib/components/FormComponents/TextItem/TextItem.js.map +1 -1
- package/lib/components/Renderer/BaseRenderer.js +3 -3
- package/lib/components/Renderer/BaseRenderer.js.map +1 -1
- package/lib/hooks/useBooleanCalculatedExpression.d.ts +5 -4
- package/lib/hooks/useBooleanCalculatedExpression.js +12 -7
- package/lib/hooks/useBooleanCalculatedExpression.js.map +1 -1
- package/lib/hooks/useCodingCalculatedExpression.d.ts +4 -3
- package/lib/hooks/useCodingCalculatedExpression.js +14 -7
- package/lib/hooks/useCodingCalculatedExpression.js.map +1 -1
- package/lib/hooks/useDecimalCalculatedExpression.d.ts +3 -3
- package/lib/hooks/useDecimalCalculatedExpression.js +11 -7
- package/lib/hooks/useDecimalCalculatedExpression.js.map +1 -1
- package/lib/hooks/useDisplayCalculatedExpression.js +8 -1
- package/lib/hooks/useDisplayCalculatedExpression.js.map +1 -1
- package/lib/hooks/useIntegerCalculatedExpression.d.ts +3 -3
- package/lib/hooks/useIntegerCalculatedExpression.js +11 -7
- package/lib/hooks/useIntegerCalculatedExpression.js.map +1 -1
- package/lib/hooks/useStringCalculatedExpression.d.ts +3 -3
- package/lib/hooks/useStringCalculatedExpression.js +12 -7
- package/lib/hooks/useStringCalculatedExpression.js.map +1 -1
- package/lib/interfaces/calculatedExpression.interface.d.ts +1 -1
- package/lib/stores/questionnaireResponseStore.d.ts +3 -1
- package/lib/stores/questionnaireResponseStore.js +9 -1
- package/lib/stores/questionnaireResponseStore.js.map +1 -1
- package/lib/stores/questionnaireStore.d.ts +6 -2
- package/lib/stores/questionnaireStore.js +13 -2
- package/lib/stores/questionnaireStore.js.map +1 -1
- package/lib/utils/calculatedExpression.d.ts +2 -1
- package/lib/utils/calculatedExpression.js +14 -8
- package/lib/utils/calculatedExpression.js.map +1 -1
- package/lib/utils/enableWhenExpression.d.ts +3 -2
- package/lib/utils/enableWhenExpression.js +15 -5
- package/lib/utils/enableWhenExpression.js.map +1 -1
- package/lib/utils/fhirpath.d.ts +5 -4
- package/lib/utils/fhirpath.js +45 -33
- package/lib/utils/fhirpath.js.map +1 -1
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/index.js.map +1 -1
- package/lib/utils/initialise.js +5 -2
- package/lib/utils/initialise.js.map +1 -1
- package/lib/utils/parseInputs.js +1 -1
- package/lib/utils/parseInputs.js.map +1 -1
- package/package.json +2 -1
- package/src/components/FormComponents/BooleanItem/BooleanItem.tsx +9 -1
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerOptionItem.tsx +5 -2
- package/src/components/FormComponents/ChoiceItems/ChoiceRadioAnswerValueSetItem.tsx +5 -2
- package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionItem.tsx +5 -2
- package/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerValueSetItem.tsx +5 -2
- package/src/components/FormComponents/DecimalItem/DecimalItem.tsx +14 -3
- package/src/components/FormComponents/IntegerItem/IntegerItem.tsx +10 -3
- package/src/components/FormComponents/StringItem/StringItem.tsx +10 -3
- package/src/components/FormComponents/TextItem/TextItem.tsx +10 -3
- package/src/components/Renderer/BaseRenderer.tsx +3 -3
- package/src/hooks/useBooleanCalculatedExpression.ts +19 -12
- package/src/hooks/useCodingCalculatedExpression.ts +18 -8
- package/src/hooks/useDecimalCalculatedExpression.ts +26 -17
- package/src/hooks/useDisplayCalculatedExpression.ts +12 -1
- package/src/hooks/useIntegerCalculatedExpression.ts +18 -13
- package/src/hooks/useStringCalculatedExpression.ts +16 -13
- package/src/interfaces/calculatedExpression.interface.ts +1 -1
- package/src/stores/questionnaireResponseStore.ts +16 -2
- package/src/stores/questionnaireStore.ts +22 -2
- package/src/utils/calculatedExpression.ts +24 -10
- package/src/utils/enableWhenExpression.ts +22 -9
- package/src/utils/fhirpath.ts +62 -41
- package/src/utils/index.ts +1 -0
- package/src/utils/initialise.ts +5 -2
- package/src/utils/parseInputs.ts +1 -1
- package/src/utils/questionnaireResponseStoreUtils/updatableResponseItems.ts +62 -0
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { useEffect, useState } from 'react';
|
|
19
|
-
import {
|
|
20
|
-
import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
|
|
19
|
+
import type { QuestionnaireItem } from 'fhir/r4';
|
|
21
20
|
import { useQuestionnaireStore } from '../stores';
|
|
22
21
|
|
|
23
22
|
interface UseDecimalCalculatedExpression {
|
|
@@ -28,14 +27,20 @@ interface useDecimalCalculatedExpressionProps {
|
|
|
28
27
|
qItem: QuestionnaireItem;
|
|
29
28
|
inputValue: string;
|
|
30
29
|
precision: number | null;
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
onChangeByCalcExpressionDecimal: (calcExpressionValue: number) => void;
|
|
31
|
+
onChangeByCalcExpressionNull: () => void;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
function useDecimalCalculatedExpression(
|
|
36
35
|
props: useDecimalCalculatedExpressionProps
|
|
37
36
|
): UseDecimalCalculatedExpression {
|
|
38
|
-
const {
|
|
37
|
+
const {
|
|
38
|
+
qItem,
|
|
39
|
+
inputValue,
|
|
40
|
+
precision,
|
|
41
|
+
onChangeByCalcExpressionDecimal,
|
|
42
|
+
onChangeByCalcExpressionNull
|
|
43
|
+
} = props;
|
|
39
44
|
|
|
40
45
|
const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
|
|
41
46
|
|
|
@@ -52,10 +57,14 @@ function useDecimalCalculatedExpression(
|
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
// only update if calculated value is different from current value
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
if (
|
|
61
|
+
calcExpression.value !== inputValue &&
|
|
62
|
+
(typeof calcExpression.value === 'number' || calcExpression.value === null)
|
|
63
|
+
) {
|
|
64
|
+
const calcExpressionValue =
|
|
65
|
+
typeof calcExpression.value === 'number' && typeof precision === 'number'
|
|
66
|
+
? parseFloat(calcExpression.value.toFixed(precision))
|
|
67
|
+
: calcExpression.value;
|
|
59
68
|
|
|
60
69
|
// only update if calculated value is different from current value
|
|
61
70
|
if (calcExpressionValue !== parseFloat(inputValue)) {
|
|
@@ -65,14 +74,14 @@ function useDecimalCalculatedExpression(
|
|
|
65
74
|
setCalcExpUpdated(false);
|
|
66
75
|
}, 500);
|
|
67
76
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
// calculatedExpression value is null
|
|
78
|
+
if (calcExpressionValue === null) {
|
|
79
|
+
onChangeByCalcExpressionNull();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// calculatedExpression value is a number
|
|
84
|
+
onChangeByCalcExpressionDecimal(calcExpressionValue);
|
|
76
85
|
}
|
|
77
86
|
}
|
|
78
87
|
},
|
|
@@ -24,11 +24,22 @@ function useDisplayCalculatedExpression(qItem: QuestionnaireItem): string | null
|
|
|
24
24
|
const calcExpression = calculatedExpressions[qItem.linkId]?.find(
|
|
25
25
|
(exp) => exp.from === 'item._text'
|
|
26
26
|
);
|
|
27
|
+
|
|
27
28
|
if (!calcExpression) {
|
|
28
29
|
return null;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
if (
|
|
32
|
+
if (
|
|
33
|
+
typeof calcExpression.value === 'string' ||
|
|
34
|
+
typeof calcExpression.value === 'number' ||
|
|
35
|
+
calcExpression.value === null
|
|
36
|
+
) {
|
|
37
|
+
// calculatedExpression value is null
|
|
38
|
+
if (calcExpression.value === null) {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// calculatedExpression value is string or number
|
|
32
43
|
return typeof calcExpression.value === 'string'
|
|
33
44
|
? calcExpression.value
|
|
34
45
|
: calcExpression.value.toString();
|
|
@@ -16,9 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { useEffect, useState } from 'react';
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
21
|
-
import { useQuestionnaireStore } from '../stores/questionnaireStore';
|
|
19
|
+
import type { QuestionnaireItem } from 'fhir/r4';
|
|
20
|
+
import { useQuestionnaireStore } from '../stores';
|
|
22
21
|
|
|
23
22
|
interface UseIntegerCalculatedExpression {
|
|
24
23
|
calcExpUpdated: boolean;
|
|
@@ -27,14 +26,15 @@ interface UseIntegerCalculatedExpression {
|
|
|
27
26
|
interface useIntegerCalculatedExpressionProps {
|
|
28
27
|
qItem: QuestionnaireItem;
|
|
29
28
|
inputValue: string;
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
onChangeByCalcExpressionInteger: (calcExpressionValue: number) => void;
|
|
30
|
+
onChangeByCalcExpressionNull: () => void;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
function useIntegerCalculatedExpression(
|
|
35
34
|
props: useIntegerCalculatedExpressionProps
|
|
36
35
|
): UseIntegerCalculatedExpression {
|
|
37
|
-
const { qItem, inputValue,
|
|
36
|
+
const { qItem, inputValue, onChangeByCalcExpressionInteger, onChangeByCalcExpressionNull } =
|
|
37
|
+
props;
|
|
38
38
|
|
|
39
39
|
const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
|
|
40
40
|
|
|
@@ -51,7 +51,10 @@ function useIntegerCalculatedExpression(
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// only update if calculated value is different from current value
|
|
54
|
-
if (
|
|
54
|
+
if (
|
|
55
|
+
calcExpression.value !== inputValue &&
|
|
56
|
+
(typeof calcExpression.value === 'number' || calcExpression.value === null)
|
|
57
|
+
) {
|
|
55
58
|
const calcExpressionValue = calcExpression.value;
|
|
56
59
|
|
|
57
60
|
if (calcExpressionValue !== parseInt(inputValue)) {
|
|
@@ -61,12 +64,14 @@ function useIntegerCalculatedExpression(
|
|
|
61
64
|
setCalcExpUpdated(false);
|
|
62
65
|
}, 500);
|
|
63
66
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
// calculatedExpression value is null
|
|
68
|
+
if (calcExpressionValue === null) {
|
|
69
|
+
onChangeByCalcExpressionNull();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// calculatedExpression value is a number
|
|
74
|
+
onChangeByCalcExpressionInteger(calcExpressionValue);
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
},
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { useEffect, useState } from 'react';
|
|
19
|
-
import {
|
|
20
|
-
import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
|
|
19
|
+
import type { QuestionnaireItem } from 'fhir/r4';
|
|
21
20
|
import { useQuestionnaireStore } from '../stores';
|
|
22
21
|
|
|
23
22
|
interface UseStringCalculatedExpression {
|
|
@@ -27,14 +26,14 @@ interface UseStringCalculatedExpression {
|
|
|
27
26
|
interface useStringCalculatedExpressionProps {
|
|
28
27
|
qItem: QuestionnaireItem;
|
|
29
28
|
inputValue: string;
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
onChangeByCalcExpressionString: (newValueString: string) => void;
|
|
30
|
+
onChangeByCalcExpressionNull: () => void;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
function useStringCalculatedExpression(
|
|
35
34
|
props: useStringCalculatedExpressionProps
|
|
36
35
|
): UseStringCalculatedExpression {
|
|
37
|
-
const { qItem, inputValue,
|
|
36
|
+
const { qItem, inputValue, onChangeByCalcExpressionString, onChangeByCalcExpressionNull } = props;
|
|
38
37
|
|
|
39
38
|
const calculatedExpressions = useQuestionnaireStore.use.calculatedExpressions();
|
|
40
39
|
|
|
@@ -53,7 +52,9 @@ function useStringCalculatedExpression(
|
|
|
53
52
|
// only update if calculated value is different from current value
|
|
54
53
|
if (
|
|
55
54
|
calcExpression.value !== inputValue &&
|
|
56
|
-
(typeof calcExpression.value === 'string' ||
|
|
55
|
+
(typeof calcExpression.value === 'string' ||
|
|
56
|
+
typeof calcExpression.value === 'number' ||
|
|
57
|
+
calcExpression.value === null)
|
|
57
58
|
) {
|
|
58
59
|
// update ui to show calculated value changes
|
|
59
60
|
setCalcExpUpdated(true);
|
|
@@ -61,17 +62,19 @@ function useStringCalculatedExpression(
|
|
|
61
62
|
setCalcExpUpdated(false);
|
|
62
63
|
}, 500);
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
// calculatedExpression value is null
|
|
66
|
+
if (calcExpression.value === null) {
|
|
67
|
+
onChangeByCalcExpressionNull();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// calculatedExpression value is a string or number
|
|
72
|
+
const newInputValue =
|
|
65
73
|
typeof calcExpression.value === 'string'
|
|
66
74
|
? calcExpression.value
|
|
67
75
|
: calcExpression.value.toString();
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
setInputValue(updatedInputValue);
|
|
71
|
-
onQrItemChange({
|
|
72
|
-
...createEmptyQrItem(qItem),
|
|
73
|
-
answer: [{ valueString: updatedInputValue }]
|
|
74
|
-
});
|
|
77
|
+
onChangeByCalcExpressionString(newInputValue);
|
|
75
78
|
}
|
|
76
79
|
},
|
|
77
80
|
// Only trigger this effect if calculatedExpression of item changes
|
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { createStore } from 'zustand/vanilla';
|
|
19
|
-
import type {
|
|
19
|
+
import type {
|
|
20
|
+
OperationOutcome,
|
|
21
|
+
Questionnaire,
|
|
22
|
+
QuestionnaireResponse,
|
|
23
|
+
QuestionnaireResponseItem
|
|
24
|
+
} from 'fhir/r4';
|
|
20
25
|
import { emptyResponse } from '../utils/emptyResource';
|
|
21
26
|
import cloneDeep from 'lodash.clonedeep';
|
|
22
27
|
import type { Diff } from 'deep-diff';
|
|
@@ -24,10 +29,12 @@ import { diff } from 'deep-diff';
|
|
|
24
29
|
import { createSelectors } from './selector';
|
|
25
30
|
import { validateQuestionnaire } from '../utils/validateQuestionnaire';
|
|
26
31
|
import { questionnaireStore } from './questionnaireStore';
|
|
32
|
+
import { createQuestionnaireResponseItemMap } from '../utils/questionnaireResponseStoreUtils/updatableResponseItems';
|
|
27
33
|
|
|
28
34
|
interface QuestionnaireResponseStoreType {
|
|
29
35
|
sourceResponse: QuestionnaireResponse;
|
|
30
36
|
updatableResponse: QuestionnaireResponse;
|
|
37
|
+
updatableResponseItems: Record<string, QuestionnaireResponseItem[]>;
|
|
31
38
|
formChangesHistory: (Diff<QuestionnaireResponse, QuestionnaireResponse>[] | null)[];
|
|
32
39
|
invalidItems: Record<string, OperationOutcome>;
|
|
33
40
|
responseIsValid: boolean;
|
|
@@ -47,6 +54,7 @@ export const questionnaireResponseStore = createStore<QuestionnaireResponseStore
|
|
|
47
54
|
(set, get) => ({
|
|
48
55
|
sourceResponse: cloneDeep(emptyResponse),
|
|
49
56
|
updatableResponse: cloneDeep(emptyResponse),
|
|
57
|
+
updatableResponseItems: {},
|
|
50
58
|
formChangesHistory: [],
|
|
51
59
|
invalidItems: {},
|
|
52
60
|
responseIsValid: true,
|
|
@@ -74,13 +82,15 @@ export const questionnaireResponseStore = createStore<QuestionnaireResponseStore
|
|
|
74
82
|
buildSourceResponse: (questionnaireResponse: QuestionnaireResponse) => {
|
|
75
83
|
set(() => ({
|
|
76
84
|
sourceResponse: questionnaireResponse,
|
|
77
|
-
updatableResponse: questionnaireResponse
|
|
85
|
+
updatableResponse: questionnaireResponse,
|
|
86
|
+
updatableResponseItems: createQuestionnaireResponseItemMap(questionnaireResponse)
|
|
78
87
|
}));
|
|
79
88
|
},
|
|
80
89
|
setUpdatableResponseAsPopulated: (populatedResponse: QuestionnaireResponse) => {
|
|
81
90
|
const formChanges = diff(get().updatableResponse, populatedResponse) ?? null;
|
|
82
91
|
set(() => ({
|
|
83
92
|
updatableResponse: populatedResponse,
|
|
93
|
+
updatableResponseItems: createQuestionnaireResponseItemMap(populatedResponse),
|
|
84
94
|
formChangesHistory: [...get().formChangesHistory, formChanges]
|
|
85
95
|
}));
|
|
86
96
|
},
|
|
@@ -88,6 +98,7 @@ export const questionnaireResponseStore = createStore<QuestionnaireResponseStore
|
|
|
88
98
|
const formChanges = diff(get().updatableResponse, updatedResponse) ?? null;
|
|
89
99
|
set(() => ({
|
|
90
100
|
updatableResponse: updatedResponse,
|
|
101
|
+
updatableResponseItems: createQuestionnaireResponseItemMap(updatedResponse),
|
|
91
102
|
formChangesHistory: [...get().formChangesHistory, formChanges]
|
|
92
103
|
}));
|
|
93
104
|
},
|
|
@@ -95,17 +106,20 @@ export const questionnaireResponseStore = createStore<QuestionnaireResponseStore
|
|
|
95
106
|
set(() => ({
|
|
96
107
|
sourceResponse: savedResponse,
|
|
97
108
|
updatableResponse: savedResponse,
|
|
109
|
+
updatableResponseItems: createQuestionnaireResponseItemMap(savedResponse),
|
|
98
110
|
formChangesHistory: []
|
|
99
111
|
})),
|
|
100
112
|
setUpdatableResponseAsEmpty: (clearedResponse: QuestionnaireResponse) =>
|
|
101
113
|
set(() => ({
|
|
102
114
|
updatableResponse: clearedResponse,
|
|
115
|
+
updatableResponseItems: createQuestionnaireResponseItemMap(clearedResponse),
|
|
103
116
|
formChangesHistory: []
|
|
104
117
|
})),
|
|
105
118
|
destroySourceResponse: () =>
|
|
106
119
|
set(() => ({
|
|
107
120
|
sourceResponse: cloneDeep(emptyResponse),
|
|
108
121
|
updatableResponse: cloneDeep(emptyResponse),
|
|
122
|
+
updatableResponseItems: createQuestionnaireResponseItemMap(cloneDeep(emptyResponse)),
|
|
109
123
|
formChangesHistory: []
|
|
110
124
|
}))
|
|
111
125
|
})
|
|
@@ -45,6 +45,7 @@ import { terminologyServerStore } from './terminologyServerStore';
|
|
|
45
45
|
import { createSelectors } from './selector';
|
|
46
46
|
import { mutateRepeatEnableWhenExpressionInstances } from '../utils/enableWhenExpression';
|
|
47
47
|
import { questionnaireResponseStore } from './questionnaireResponseStore';
|
|
48
|
+
import { createQuestionnaireResponseItemMap } from '../utils/questionnaireResponseStoreUtils/updatableResponseItems';
|
|
48
49
|
|
|
49
50
|
interface QuestionnaireStoreType {
|
|
50
51
|
sourceQuestionnaire: Questionnaire;
|
|
@@ -63,6 +64,7 @@ interface QuestionnaireStoreType {
|
|
|
63
64
|
processedValueSetUrls: Record<string, string>;
|
|
64
65
|
cachedValueSetCodings: Record<string, Coding[]>;
|
|
65
66
|
fhirPathContext: Record<string, any>;
|
|
67
|
+
populatedContext: Record<string, any>;
|
|
66
68
|
focusedLinkId: string;
|
|
67
69
|
readOnly: boolean;
|
|
68
70
|
buildSourceQuestionnaire: (
|
|
@@ -90,9 +92,11 @@ interface QuestionnaireStoreType {
|
|
|
90
92
|
addCodingToCache: (valueSetUrl: string, codings: Coding[]) => void;
|
|
91
93
|
updatePopulatedProperties: (
|
|
92
94
|
populatedResponse: QuestionnaireResponse,
|
|
95
|
+
populatedContext?: Record<string, any>,
|
|
93
96
|
persistTabIndex?: boolean
|
|
94
97
|
) => QuestionnaireResponse;
|
|
95
98
|
onFocusLinkId: (linkId: string) => void;
|
|
99
|
+
setPopulatedContext: (newPopulatedContext: Record<string, any>) => void;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, get) => ({
|
|
@@ -112,6 +116,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
|
|
|
112
116
|
processedValueSetUrls: {},
|
|
113
117
|
cachedValueSetCodings: {},
|
|
114
118
|
fhirPathContext: {},
|
|
119
|
+
populatedContext: {},
|
|
115
120
|
focusedLinkId: '',
|
|
116
121
|
readOnly: false,
|
|
117
122
|
buildSourceQuestionnaire: async (
|
|
@@ -232,6 +237,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
|
|
|
232
237
|
|
|
233
238
|
const { updatedEnableWhenExpressions, isUpdated } = mutateRepeatEnableWhenExpressionInstances({
|
|
234
239
|
questionnaireResponse: questionnaireResponseStore.getState().updatableResponse,
|
|
240
|
+
questionnaireResponseItemMap: questionnaireResponseStore.getState().updatableResponseItems,
|
|
235
241
|
variablesFhirPath: get().variables.fhirPathVariables,
|
|
236
242
|
existingFhirPathContext: get().fhirPathContext,
|
|
237
243
|
enableWhenExpressions: enableWhenExpressions,
|
|
@@ -250,6 +256,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
|
|
|
250
256
|
toggleEnableWhenActivation: (isActivated: boolean) =>
|
|
251
257
|
set(() => ({ enableWhenIsActivated: isActivated })),
|
|
252
258
|
updateExpressions: (updatedResponse: QuestionnaireResponse) => {
|
|
259
|
+
const updatedResponseItemMap = createQuestionnaireResponseItemMap(updatedResponse);
|
|
253
260
|
const {
|
|
254
261
|
isUpdated,
|
|
255
262
|
updatedEnableWhenExpressions,
|
|
@@ -257,6 +264,7 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
|
|
|
257
264
|
updatedFhirPathContext
|
|
258
265
|
} = evaluateUpdatedExpressions({
|
|
259
266
|
updatedResponse,
|
|
267
|
+
updatedResponseItemMap,
|
|
260
268
|
enableWhenExpressions: get().enableWhenExpressions,
|
|
261
269
|
calculatedExpressions: get().calculatedExpressions,
|
|
262
270
|
variablesFhirPath: get().variables.fhirPathVariables,
|
|
@@ -283,9 +291,16 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
|
|
|
283
291
|
[valueSetUrl]: codings
|
|
284
292
|
}
|
|
285
293
|
})),
|
|
286
|
-
updatePopulatedProperties: (
|
|
294
|
+
updatePopulatedProperties: (
|
|
295
|
+
populatedResponse: QuestionnaireResponse,
|
|
296
|
+
populatedContext?: Record<string, any>,
|
|
297
|
+
persistTabIndex?: boolean
|
|
298
|
+
) => {
|
|
299
|
+
const initialResponseItemMap = createQuestionnaireResponseItemMap(populatedResponse);
|
|
300
|
+
|
|
287
301
|
const evaluateInitialCalculatedExpressionsResult = evaluateInitialCalculatedExpressions({
|
|
288
302
|
initialResponse: populatedResponse,
|
|
303
|
+
initialResponseItemMap: initialResponseItemMap,
|
|
289
304
|
calculatedExpressions: get().calculatedExpressions,
|
|
290
305
|
variablesFhirPath: get().variables.fhirPathVariables,
|
|
291
306
|
existingFhirPathContext: get().fhirPathContext
|
|
@@ -321,7 +336,8 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
|
|
|
321
336
|
enableWhenExpressions: initialEnableWhenExpressions,
|
|
322
337
|
calculatedExpressions: initialCalculatedExpressions,
|
|
323
338
|
currentTabIndex: persistTabIndex ? get().currentTabIndex : firstVisibleTab,
|
|
324
|
-
fhirPathContext: updatedFhirPathContext
|
|
339
|
+
fhirPathContext: updatedFhirPathContext,
|
|
340
|
+
populatedContext: populatedContext ?? get().populatedContext
|
|
325
341
|
}));
|
|
326
342
|
|
|
327
343
|
return updatedResponse;
|
|
@@ -329,6 +345,10 @@ export const questionnaireStore = createStore<QuestionnaireStoreType>()((set, ge
|
|
|
329
345
|
onFocusLinkId: (linkId: string) =>
|
|
330
346
|
set(() => ({
|
|
331
347
|
focusedLinkId: linkId
|
|
348
|
+
})),
|
|
349
|
+
setPopulatedContext: (newPopulatedContext: Record<string, any>) =>
|
|
350
|
+
set(() => ({
|
|
351
|
+
populatedContext: newPopulatedContext
|
|
332
352
|
}))
|
|
333
353
|
}));
|
|
334
354
|
|
|
@@ -37,6 +37,7 @@ import dayjs from 'dayjs';
|
|
|
37
37
|
|
|
38
38
|
interface EvaluateInitialCalculatedExpressionsParams {
|
|
39
39
|
initialResponse: QuestionnaireResponse;
|
|
40
|
+
initialResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
|
|
40
41
|
calculatedExpressions: Record<string, CalculatedExpression[]>;
|
|
41
42
|
variablesFhirPath: Record<string, Expression[]>;
|
|
42
43
|
existingFhirPathContext: Record<string, any>;
|
|
@@ -48,8 +49,13 @@ export function evaluateInitialCalculatedExpressions(
|
|
|
48
49
|
initialCalculatedExpressions: Record<string, CalculatedExpression[]>;
|
|
49
50
|
updatedFhirPathContext: Record<string, any>;
|
|
50
51
|
} {
|
|
51
|
-
const {
|
|
52
|
-
|
|
52
|
+
const {
|
|
53
|
+
initialResponse,
|
|
54
|
+
initialResponseItemMap,
|
|
55
|
+
calculatedExpressions,
|
|
56
|
+
variablesFhirPath,
|
|
57
|
+
existingFhirPathContext
|
|
58
|
+
} = params;
|
|
53
59
|
|
|
54
60
|
// Return early if initialResponse is empty or there are no calculated expressions to evaluate
|
|
55
61
|
if (
|
|
@@ -65,10 +71,11 @@ export function evaluateInitialCalculatedExpressions(
|
|
|
65
71
|
const initialCalculatedExpressions: Record<string, CalculatedExpression[]> = {
|
|
66
72
|
...calculatedExpressions
|
|
67
73
|
};
|
|
74
|
+
|
|
68
75
|
const updatedFhirPathContext = createFhirPathContext(
|
|
69
76
|
initialResponse,
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
initialResponseItemMap,
|
|
78
|
+
variablesFhirPath
|
|
72
79
|
);
|
|
73
80
|
|
|
74
81
|
for (const linkId in initialCalculatedExpressions) {
|
|
@@ -83,7 +90,8 @@ export function evaluateInitialCalculatedExpressions(
|
|
|
83
90
|
fhirpath_r4_model
|
|
84
91
|
);
|
|
85
92
|
|
|
86
|
-
if
|
|
93
|
+
// Only update calculatedExpressions if length of result array > 0
|
|
94
|
+
if (result.length > 0 && !_isEqual(calcExpression.value, result[0])) {
|
|
87
95
|
calcExpression.value = result[0];
|
|
88
96
|
}
|
|
89
97
|
} catch (e) {
|
|
@@ -124,11 +132,17 @@ export function evaluateCalculatedExpressions(
|
|
|
124
132
|
fhirpath_r4_model
|
|
125
133
|
);
|
|
126
134
|
|
|
127
|
-
if
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
// Update calculatedExpressions if length of result array > 0
|
|
136
|
+
// Only update when current calcExpression value is different from the result, otherwise it will result in an infinite loop as per issue #733
|
|
137
|
+
if (result.length > 0 && !_isEqual(calcExpression.value, result[0])) {
|
|
138
|
+
isUpdated = true;
|
|
139
|
+
calcExpression.value = result[0];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Update calculatedExpression value to null if no result is returned
|
|
143
|
+
if (result.length === 0 && calcExpression.value !== null) {
|
|
144
|
+
isUpdated = true;
|
|
145
|
+
calcExpression.value = null;
|
|
132
146
|
}
|
|
133
147
|
} catch (e) {
|
|
134
148
|
console.warn(e.message, `LinkId: ${linkId}\nExpression: ${calcExpression.expression}`);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { Expression, QuestionnaireResponse } from 'fhir/r4';
|
|
18
|
+
import type { Expression, QuestionnaireResponse, QuestionnaireResponseItem } from 'fhir/r4';
|
|
19
19
|
import { createFhirPathContext } from './fhirpath';
|
|
20
20
|
import fhirpath from 'fhirpath';
|
|
21
21
|
import fhirpath_r4_model from 'fhirpath/fhir-context/r4';
|
|
@@ -27,9 +27,9 @@ import type {
|
|
|
27
27
|
|
|
28
28
|
interface EvaluateInitialEnableWhenExpressionsParams {
|
|
29
29
|
initialResponse: QuestionnaireResponse;
|
|
30
|
+
initialResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
|
|
30
31
|
enableWhenExpressions: EnableWhenExpressions;
|
|
31
32
|
variablesFhirPath: Record<string, Expression[]>;
|
|
32
|
-
existingFhirPathContext: Record<string, any>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export function evaluateInitialEnableWhenExpressions(
|
|
@@ -38,7 +38,7 @@ export function evaluateInitialEnableWhenExpressions(
|
|
|
38
38
|
initialEnableWhenExpressions: EnableWhenExpressions;
|
|
39
39
|
updatedFhirPathContext: Record<string, any>;
|
|
40
40
|
} {
|
|
41
|
-
const { initialResponse, enableWhenExpressions, variablesFhirPath
|
|
41
|
+
const { initialResponse, initialResponseItemMap, enableWhenExpressions, variablesFhirPath } =
|
|
42
42
|
params;
|
|
43
43
|
|
|
44
44
|
const initialEnableWhenExpressions: EnableWhenExpressions = {
|
|
@@ -46,8 +46,8 @@ export function evaluateInitialEnableWhenExpressions(
|
|
|
46
46
|
};
|
|
47
47
|
const updatedFhirPathContext = createFhirPathContext(
|
|
48
48
|
initialResponse,
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
initialResponseItemMap,
|
|
50
|
+
variablesFhirPath
|
|
51
51
|
);
|
|
52
52
|
|
|
53
53
|
const initialEnableWhenSingleExpressions = evaluateEnableWhenSingleExpressions(
|
|
@@ -85,12 +85,18 @@ function evaluateEnableWhenSingleExpressions(
|
|
|
85
85
|
const result = fhirpath.evaluate('', expression, updatedFhirPathContext, fhirpath_r4_model);
|
|
86
86
|
|
|
87
87
|
// Update enableWhenExpressions if length of result array > 0
|
|
88
|
-
// Only update when current isEnabled value is different from the result, otherwise it will result in
|
|
88
|
+
// Only update when current isEnabled value is different from the result, otherwise it will result in an infinite loop as per issue #733
|
|
89
89
|
if (result.length > 0 && initialValue !== result[0] && typeof result[0] === 'boolean') {
|
|
90
90
|
enableWhenSingleExpressions[linkId].isEnabled = result[0];
|
|
91
91
|
isUpdated = true;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Update isEnabled value to false if no result is returned
|
|
95
|
+
if (result.length === 0 && initialValue !== false) {
|
|
96
|
+
enableWhenSingleExpressions[linkId].isEnabled = false;
|
|
97
|
+
isUpdated = true;
|
|
98
|
+
}
|
|
99
|
+
|
|
94
100
|
// handle intersect edge case - evaluate() returns empty array if result is false
|
|
95
101
|
if (
|
|
96
102
|
enableWhenSingleExpressions[linkId].expression.includes('intersect') &&
|
|
@@ -198,6 +204,12 @@ export function evaluateEnableWhenRepeatExpressionInstance(
|
|
|
198
204
|
isUpdated = true;
|
|
199
205
|
}
|
|
200
206
|
|
|
207
|
+
// Update isEnabled value to false if no result is returned
|
|
208
|
+
if (result.length === 0 && initialValue !== false) {
|
|
209
|
+
isEnabled = false;
|
|
210
|
+
isUpdated = true;
|
|
211
|
+
}
|
|
212
|
+
|
|
201
213
|
// handle intersect edge case - evaluate() returns empty array if result is false
|
|
202
214
|
if (expression.includes('intersect') && result.length === 0 && initialValue !== result[0]) {
|
|
203
215
|
isEnabled = false;
|
|
@@ -245,6 +257,7 @@ export function evaluateEnableWhenExpressions(
|
|
|
245
257
|
|
|
246
258
|
interface MutateRepeatEnableWhenExpressionInstancesParams {
|
|
247
259
|
questionnaireResponse: QuestionnaireResponse;
|
|
260
|
+
questionnaireResponseItemMap: Record<string, QuestionnaireResponseItem[]>;
|
|
248
261
|
variablesFhirPath: Record<string, Expression[]>;
|
|
249
262
|
existingFhirPathContext: Record<string, any>;
|
|
250
263
|
enableWhenExpressions: EnableWhenExpressions;
|
|
@@ -258,8 +271,8 @@ export function mutateRepeatEnableWhenExpressionInstances(
|
|
|
258
271
|
): { updatedEnableWhenExpressions: EnableWhenExpressions; isUpdated: boolean } {
|
|
259
272
|
const {
|
|
260
273
|
questionnaireResponse,
|
|
274
|
+
questionnaireResponseItemMap,
|
|
261
275
|
variablesFhirPath,
|
|
262
|
-
existingFhirPathContext,
|
|
263
276
|
enableWhenExpressions,
|
|
264
277
|
parentRepeatGroupLinkId,
|
|
265
278
|
parentRepeatGroupIndex,
|
|
@@ -270,8 +283,8 @@ export function mutateRepeatEnableWhenExpressionInstances(
|
|
|
270
283
|
|
|
271
284
|
const updatedFhirPathContext = createFhirPathContext(
|
|
272
285
|
questionnaireResponse,
|
|
273
|
-
|
|
274
|
-
|
|
286
|
+
questionnaireResponseItemMap,
|
|
287
|
+
variablesFhirPath
|
|
275
288
|
);
|
|
276
289
|
|
|
277
290
|
let isUpdated = false;
|