@aehrc/smart-forms-renderer 0.39.0 → 0.40.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/ItemParts/ItemFieldGrid.js +6 -2
- package/lib/components/FormComponents/ItemParts/ItemFieldGrid.js.map +1 -1
- package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js +2 -2
- package/lib/components/FormComponents/RepeatGroup/RepeatGroup.js.map +1 -1
- package/lib/components/FormComponents/RepeatItem/RepeatItem.js +5 -5
- package/lib/components/FormComponents/RepeatItem/RepeatItem.js.map +1 -1
- package/lib/components/FormComponents/Tables/GroupTable.js +3 -3
- package/lib/components/FormComponents/Tables/GroupTable.js.map +1 -1
- package/lib/components/Renderer/FormTopLevelItem.js +12 -1
- package/lib/components/Renderer/FormTopLevelItem.js.map +1 -1
- package/lib/hooks/useDateValidation.js +2 -2
- package/lib/hooks/useDateValidation.js.map +1 -1
- package/lib/hooks/useGroupTableRows.d.ts +1 -1
- package/lib/hooks/useGroupTableRows.js +2 -2
- package/lib/hooks/useGroupTableRows.js.map +1 -1
- package/lib/hooks/useInitialiseGroupTable.d.ts +1 -1
- package/lib/hooks/useInitialiseGroupTable.js +5 -5
- package/lib/hooks/useInitialiseGroupTable.js.map +1 -1
- package/lib/hooks/useInitialiseRepeatAnswers.d.ts +1 -1
- package/lib/hooks/useInitialiseRepeatAnswers.js +9 -12
- package/lib/hooks/useInitialiseRepeatAnswers.js.map +1 -1
- package/lib/hooks/useInitialiseRepeatGroups.js +4 -4
- package/lib/hooks/useInitialiseRepeatGroups.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/tests/test-data/observationSample.d.ts +2 -0
- package/lib/tests/test-data/observationSample.js +523 -0
- package/lib/tests/test-data/observationSample.js.map +1 -1
- package/lib/utils/extractObservation.d.ts +5 -0
- package/lib/utils/extractObservation.js +121 -33
- package/lib/utils/extractObservation.js.map +1 -1
- package/lib/utils/genericRecursive.d.ts +5 -2
- package/lib/utils/genericRecursive.js +10 -2
- package/lib/utils/genericRecursive.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/itemControl.js +1 -1
- package/lib/utils/itemControl.js.map +1 -1
- package/lib/utils/manageForm.js +2 -1
- package/lib/utils/manageForm.js.map +1 -1
- package/lib/utils/qItem.js +5 -0
- package/lib/utils/qItem.js.map +1 -1
- package/lib/utils/repeatId.d.ts +2 -0
- package/lib/utils/repeatId.js +25 -0
- package/lib/utils/repeatId.js.map +1 -0
- package/package.json +1 -1
- package/src/components/FormComponents/ItemParts/ItemFieldGrid.tsx +8 -2
- package/src/components/FormComponents/RepeatGroup/RepeatGroup.tsx +2 -2
- package/src/components/FormComponents/RepeatItem/RepeatItem.tsx +7 -5
- package/src/components/FormComponents/Tables/GroupTable.tsx +6 -3
- package/src/components/Renderer/FormTopLevelItem.tsx +31 -1
- package/src/hooks/useDateValidation.tsx +2 -2
- package/src/hooks/useGroupTableRows.ts +2 -2
- package/src/hooks/useInitialiseGroupTable.ts +8 -5
- package/src/hooks/useInitialiseRepeatAnswers.ts +9 -12
- package/src/hooks/useInitialiseRepeatGroups.ts +4 -4
- package/src/index.ts +2 -1
- package/src/stories/assets/questionnaires/QIdRemoverDebugger.ts +161 -0
- package/src/stories/storybookWrappers/IdRemoverButtonForStorybook.tsx +54 -0
- package/src/stories/storybookWrappers/IdRemoverDebuggerWrapperForStorybook.tsx +81 -0
- package/src/stories/testing/IdRemoverDebuggerWrapper.stories.tsx +39 -0
- package/src/tests/extractObservation.test.ts +129 -0
- package/src/tests/test-data/observationSample.ts +721 -0
- package/src/utils/extractObservation.ts +284 -0
- package/src/utils/genericRecursive.ts +27 -5
- package/src/utils/index.ts +1 -0
- package/src/utils/itemControl.ts +1 -1
- package/src/utils/manageForm.ts +2 -1
- package/src/utils/qItem.ts +6 -0
- package/src/utils/repeatId.ts +27 -0
- package/CHANGELOG.md +0 -34
- package/lib/hooks/useRepeatAnswers.d.ts +0 -4
- package/lib/hooks/useRepeatAnswers.js +0 -34
- package/lib/hooks/useRepeatAnswers.js.map +0 -1
- package/lib/interfaces/repeatItem.interface.d.ts +0 -5
- package/lib/interfaces/repeatItem.interface.js +0 -2
- package/lib/interfaces/repeatItem.interface.js.map +0 -1
- package/lib/utils/answerExpression.d.ts +0 -18
- package/lib/utils/answerExpression.js +0 -133
- package/lib/utils/answerExpression.js.map +0 -1
- package/lib/utils/dynamicValueSet.d.ts +0 -5
- package/lib/utils/dynamicValueSet.js +0 -96
- package/lib/utils/dynamicValueSet.js.map +0 -1
- package/lib/utils/fhirpathAsyncUtils/fhirpath-async.d.ts +0 -14
- package/lib/utils/fhirpathAsyncUtils/fhirpath-async.js +0 -639
- package/lib/utils/fhirpathAsyncUtils/fhirpath-async.js.map +0 -1
- package/lib/utils/fhirpathAsyncUtils/outcome-utils.d.ts +0 -3
- package/lib/utils/fhirpathAsyncUtils/outcome-utils.js +0 -41
- package/lib/utils/fhirpathAsyncUtils/outcome-utils.js.map +0 -1
- package/lib/utils/updateQr.d.ts +0 -9
- package/lib/utils/updateQr.js +0 -55
- package/lib/utils/updateQr.js.map +0 -1
|
@@ -34,8 +34,10 @@ import useHidden from '../../hooks/useHidden';
|
|
|
34
34
|
import GroupItemSwitcher from '../FormComponents/GroupItem/GroupItemSwitcher';
|
|
35
35
|
import useReadOnly from '../../hooks/useReadOnly';
|
|
36
36
|
import Box from '@mui/material/Box';
|
|
37
|
-
import { isSpecificItemControl } from '../../utils';
|
|
37
|
+
import { isRepeatItemAndNotCheckbox, isSpecificItemControl } from '../../utils';
|
|
38
38
|
import GroupTable from '../FormComponents/Tables/GroupTable';
|
|
39
|
+
import RepeatItem from '../FormComponents/RepeatItem/RepeatItem';
|
|
40
|
+
import GridGroup from '../FormComponents/GridGroup/GridGroup';
|
|
39
41
|
|
|
40
42
|
interface FormTopLevelItemProps
|
|
41
43
|
extends PropsWithQrItemChangeHandler,
|
|
@@ -127,6 +129,20 @@ function FormTopLevelItem(props: FormTopLevelItemProps) {
|
|
|
127
129
|
|
|
128
130
|
// If form is untabbed, it is rendered as a regular group
|
|
129
131
|
if (itemIsGroup) {
|
|
132
|
+
// Item is 'grid'
|
|
133
|
+
const itemIsGrid = isSpecificItemControl(topLevelQItem, 'grid');
|
|
134
|
+
if (itemIsGrid) {
|
|
135
|
+
return (
|
|
136
|
+
<GridGroup
|
|
137
|
+
qItem={topLevelQItem}
|
|
138
|
+
qrItem={topLevelQRItem}
|
|
139
|
+
groupCardElevation={1}
|
|
140
|
+
parentIsReadOnly={parentIsReadOnly}
|
|
141
|
+
onQrItemChange={onQrItemChange}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
130
146
|
// GroupTable "gtable" can be rendered with either repeats:true or false
|
|
131
147
|
if (isSpecificItemControl(topLevelQItem, 'gtable')) {
|
|
132
148
|
return (
|
|
@@ -156,6 +172,20 @@ function FormTopLevelItem(props: FormTopLevelItemProps) {
|
|
|
156
172
|
}
|
|
157
173
|
|
|
158
174
|
// Otherwise, it is rendered as a non-group item
|
|
175
|
+
const itemRepeatsAndIsNotCheckbox = isRepeatItemAndNotCheckbox(topLevelQItem);
|
|
176
|
+
if (itemRepeatsAndIsNotCheckbox) {
|
|
177
|
+
return (
|
|
178
|
+
<RepeatItem
|
|
179
|
+
key={topLevelQItem.linkId}
|
|
180
|
+
qItem={topLevelQItem}
|
|
181
|
+
qrItem={topLevelQRItem}
|
|
182
|
+
groupCardElevation={1}
|
|
183
|
+
parentIsReadOnly={readOnly}
|
|
184
|
+
onQrItemChange={onQrItemChange}
|
|
185
|
+
/>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
159
189
|
return (
|
|
160
190
|
<Box mt={1}>
|
|
161
191
|
<SingleItem
|
|
@@ -55,8 +55,8 @@ function useDateValidation(input: string, parseFail: boolean = false): string |
|
|
|
55
55
|
|
|
56
56
|
const matches = input.split('/');
|
|
57
57
|
|
|
58
|
-
if (validateTwoMatches(matches[0], matches[1])) {
|
|
59
|
-
return
|
|
58
|
+
if (!validateTwoMatches(matches[0], matches[1])) {
|
|
59
|
+
return 'Input is an invalid date.';
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
return null;
|
|
@@ -19,8 +19,8 @@ import { useState } from 'react';
|
|
|
19
19
|
import useInitialiseGroupTable from './useInitialiseGroupTable';
|
|
20
20
|
import type { QuestionnaireResponseItem } from 'fhir/r4';
|
|
21
21
|
|
|
22
|
-
function useGroupTableRows(qrItems: QuestionnaireResponseItem[]) {
|
|
23
|
-
const initialisedGroupTableRows = useInitialiseGroupTable(qrItems);
|
|
22
|
+
function useGroupTableRows(linkId: string, qrItems: QuestionnaireResponseItem[]) {
|
|
23
|
+
const initialisedGroupTableRows = useInitialiseGroupTable(linkId, qrItems);
|
|
24
24
|
|
|
25
25
|
const [tableRows, setTableRows] = useState(initialisedGroupTableRows);
|
|
26
26
|
const [selectedIds, setSelectedIds] = useState<string[]>(
|
|
@@ -16,21 +16,24 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { QuestionnaireResponseItem } from 'fhir/r4';
|
|
19
|
-
import { nanoid } from 'nanoid';
|
|
20
19
|
import type { GroupTableRowModel } from '../interfaces/groupTable.interface';
|
|
20
|
+
import { generateExistingRepeatId, generateNewRepeatId } from '../utils/repeatId';
|
|
21
21
|
|
|
22
|
-
function useInitialiseGroupTable(
|
|
22
|
+
function useInitialiseGroupTable(
|
|
23
|
+
linkId: string,
|
|
24
|
+
qrItems: QuestionnaireResponseItem[]
|
|
25
|
+
): GroupTableRowModel[] {
|
|
23
26
|
let initialGroupTableRows: GroupTableRowModel[] = [
|
|
24
27
|
{
|
|
25
|
-
nanoId:
|
|
28
|
+
nanoId: generateNewRepeatId(linkId),
|
|
26
29
|
qrItem: null
|
|
27
30
|
}
|
|
28
31
|
];
|
|
29
32
|
|
|
30
33
|
if (qrItems.length > 0) {
|
|
31
|
-
initialGroupTableRows = qrItems.map((qrItem) => {
|
|
34
|
+
initialGroupTableRows = qrItems.map((qrItem, index) => {
|
|
32
35
|
return {
|
|
33
|
-
nanoId:
|
|
36
|
+
nanoId: generateExistingRepeatId(linkId, index),
|
|
34
37
|
qrItem
|
|
35
38
|
};
|
|
36
39
|
});
|
|
@@ -16,26 +16,23 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { QuestionnaireResponseItem, QuestionnaireResponseItemAnswer } from 'fhir/r4';
|
|
19
|
-
import { nanoid } from 'nanoid';
|
|
20
19
|
import { useMemo } from 'react';
|
|
20
|
+
import { generateExistingRepeatId, generateNewRepeatId } from '../utils/repeatId';
|
|
21
21
|
|
|
22
22
|
function useInitialiseRepeatAnswers(
|
|
23
|
+
linkId: string,
|
|
23
24
|
qrItem: QuestionnaireResponseItem | null
|
|
24
25
|
): (QuestionnaireResponseItemAnswer | null)[] {
|
|
25
26
|
return useMemo(() => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (qrItem?.answer) {
|
|
29
|
-
initialRepeatAnswers = qrItem.answer.map((answer) => {
|
|
30
|
-
if (!answer.id) {
|
|
31
|
-
answer.id = nanoid();
|
|
32
|
-
}
|
|
33
|
-
return answer;
|
|
34
|
-
});
|
|
27
|
+
if (!qrItem?.answer) {
|
|
28
|
+
return [{ id: generateNewRepeatId(linkId) }];
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
return
|
|
38
|
-
|
|
31
|
+
return qrItem.answer.map((answer, index) => ({
|
|
32
|
+
...answer,
|
|
33
|
+
id: answer.id ?? generateExistingRepeatId(linkId, index)
|
|
34
|
+
}));
|
|
35
|
+
}, [linkId, qrItem]);
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
export default useInitialiseRepeatAnswers;
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { QuestionnaireItem, QuestionnaireResponseItem } from 'fhir/r4';
|
|
19
|
-
import { nanoid } from 'nanoid';
|
|
20
19
|
import type { RepeatGroupSingle } from '../interfaces/repeatGroup.interface';
|
|
21
20
|
import { useMemo } from 'react';
|
|
21
|
+
import { generateExistingRepeatId, generateNewRepeatId } from '../utils/repeatId';
|
|
22
22
|
|
|
23
23
|
function useInitialiseRepeatGroups(
|
|
24
24
|
qItem: QuestionnaireItem,
|
|
@@ -28,15 +28,15 @@ function useInitialiseRepeatGroups(
|
|
|
28
28
|
() => {
|
|
29
29
|
let initialRepeatGroupAnswers: RepeatGroupSingle[] = [
|
|
30
30
|
{
|
|
31
|
-
nanoId:
|
|
31
|
+
nanoId: generateNewRepeatId(qItem.linkId),
|
|
32
32
|
qrItem: null
|
|
33
33
|
}
|
|
34
34
|
];
|
|
35
35
|
|
|
36
36
|
if (qrItems.length > 0) {
|
|
37
|
-
initialRepeatGroupAnswers = qrItems.map((qrItem) => {
|
|
37
|
+
initialRepeatGroupAnswers = qrItems.map((qrItem, index) => {
|
|
38
38
|
return {
|
|
39
|
-
nanoId:
|
|
39
|
+
nanoId: generateExistingRepeatId(qItem.linkId, index),
|
|
40
40
|
qrItem
|
|
41
41
|
};
|
|
42
42
|
});
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,161 @@
|
|
|
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 type { Questionnaire } from 'fhir/r4';
|
|
19
|
+
|
|
20
|
+
export const qMyPatient: Questionnaire = {
|
|
21
|
+
resourceType: 'Questionnaire',
|
|
22
|
+
id: 'canshare-myPatient1',
|
|
23
|
+
meta: {
|
|
24
|
+
versionId: '9',
|
|
25
|
+
lastUpdated: '2024-09-18T07:23:35.7317908+00:00'
|
|
26
|
+
},
|
|
27
|
+
extension: [
|
|
28
|
+
{
|
|
29
|
+
extension: [
|
|
30
|
+
{
|
|
31
|
+
url: 'name',
|
|
32
|
+
valueId: 'LaunchPatient'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
url: 'type',
|
|
36
|
+
valueCode: 'Patient'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
url: 'description',
|
|
40
|
+
valueString: 'The patient that is to be used to pre-populate the form'
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
extension: [
|
|
47
|
+
{
|
|
48
|
+
url: 'name',
|
|
49
|
+
valueId: 'LaunchPractitioner'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
url: 'type',
|
|
53
|
+
valueCode: 'Practitioner'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
url: 'description',
|
|
57
|
+
valueString: 'The practitioner that is to be used to pre-populate the form'
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-launchContext'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
url: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemExtractionContext',
|
|
64
|
+
valueCode: 'Patient'
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
url: 'http://canshare.co.nz/questionnaire/myPatient1',
|
|
68
|
+
name: 'myPatient1',
|
|
69
|
+
status: 'active',
|
|
70
|
+
publisher: 'DEMO: David Hay',
|
|
71
|
+
useContext: [
|
|
72
|
+
{
|
|
73
|
+
code: {
|
|
74
|
+
system: 'http://terminology.hl7.org/CodeSystem/usage-context-type',
|
|
75
|
+
code: 'user',
|
|
76
|
+
display: 'User Type'
|
|
77
|
+
},
|
|
78
|
+
valueCodeableConcept: {
|
|
79
|
+
coding: [
|
|
80
|
+
{
|
|
81
|
+
code: 'extract',
|
|
82
|
+
display: 'Demo Extract'
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
item: [
|
|
89
|
+
{
|
|
90
|
+
linkId: 'myPatient1',
|
|
91
|
+
text: 'myPatient1',
|
|
92
|
+
type: 'group',
|
|
93
|
+
item: [
|
|
94
|
+
{
|
|
95
|
+
linkId: 'myPatient1.name',
|
|
96
|
+
definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.name',
|
|
97
|
+
text: 'name *',
|
|
98
|
+
type: 'group',
|
|
99
|
+
repeats: true,
|
|
100
|
+
item: [
|
|
101
|
+
{
|
|
102
|
+
linkId: 'myPatient1.name.first',
|
|
103
|
+
definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.given',
|
|
104
|
+
text: 'firstName *',
|
|
105
|
+
type: 'string',
|
|
106
|
+
repeats: true
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
linkId: 'myPatient1.name.lastName',
|
|
110
|
+
definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.name.family',
|
|
111
|
+
text: 'lastName',
|
|
112
|
+
type: 'string'
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
linkId: 'myPatient1.hair',
|
|
118
|
+
definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension',
|
|
119
|
+
text: 'hair',
|
|
120
|
+
type: 'group',
|
|
121
|
+
item: [
|
|
122
|
+
{
|
|
123
|
+
linkId: 'myPatient1.hair.colour',
|
|
124
|
+
definition:
|
|
125
|
+
'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.valueString',
|
|
126
|
+
text: 'colour',
|
|
127
|
+
type: 'string'
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
linkId: 'myPatient1.hair.url',
|
|
131
|
+
definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.url',
|
|
132
|
+
text: 'url',
|
|
133
|
+
type: 'string'
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
linkId: 'myPatient1.religion',
|
|
139
|
+
definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension',
|
|
140
|
+
text: 'religion',
|
|
141
|
+
type: 'group',
|
|
142
|
+
item: [
|
|
143
|
+
{
|
|
144
|
+
linkId: 'myPatient1.religion.brand',
|
|
145
|
+
definition:
|
|
146
|
+
'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.valueString',
|
|
147
|
+
text: 'brand',
|
|
148
|
+
type: 'string'
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
linkId: 'myPatient1.religion.url1',
|
|
152
|
+
definition: 'http://hl7.org/fhir/StructureDefinition/Patient#Patient.extension.url',
|
|
153
|
+
text: 'url',
|
|
154
|
+
type: 'string'
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
// @ts-ignore
|
|
19
|
+
import React from 'react';
|
|
20
|
+
import type { QuestionnaireResponse } from 'fhir/r4';
|
|
21
|
+
import { Box, IconButton, Tooltip } from '@mui/material';
|
|
22
|
+
import ContentCutIcon from '@mui/icons-material/ContentCut';
|
|
23
|
+
|
|
24
|
+
interface IdRemoverButtonProps {
|
|
25
|
+
questionnaireResponse: QuestionnaireResponse;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function IdRemoverButtonForStorybook(props: IdRemoverButtonProps) {
|
|
29
|
+
const { questionnaireResponse } = props;
|
|
30
|
+
|
|
31
|
+
async function handleRemoveIds() {
|
|
32
|
+
console.log('handle remove IDs');
|
|
33
|
+
|
|
34
|
+
// return buildForm()
|
|
35
|
+
// updateQuestionnaireResponse(
|
|
36
|
+
// questionnaire,
|
|
37
|
+
// populatedResponse,
|
|
38
|
+
// initialiseItemCalculatedExpressionValueRecursive,
|
|
39
|
+
// calculatedExpressionsWithValues
|
|
40
|
+
// );
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Box display="flex" mb={0.5} alignItems="center" columnGap={3}>
|
|
45
|
+
<Tooltip title="Remove IDs from questionnaire response" placement="right">
|
|
46
|
+
<IconButton onClick={handleRemoveIds} size="small" color="primary">
|
|
47
|
+
<ContentCutIcon />
|
|
48
|
+
</IconButton>
|
|
49
|
+
</Tooltip>
|
|
50
|
+
</Box>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default IdRemoverButtonForStorybook;
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
// @ts-ignore
|
|
19
|
+
import React from 'react';
|
|
20
|
+
import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
|
|
21
|
+
import { BaseRenderer } from '../../components';
|
|
22
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
23
|
+
import { RendererThemeProvider } from '../../theme';
|
|
24
|
+
import { useBuildForm, useRendererQueryClient } from '../../hooks';
|
|
25
|
+
import { STORYBOOK_TERMINOLOGY_SERVER_URL } from './globals';
|
|
26
|
+
import IdRemoverButtonForStorybook from './IdRemoverButtonForStorybook';
|
|
27
|
+
import { Grid } from '@mui/material';
|
|
28
|
+
import { useQuestionnaireResponseStore, useQuestionnaireStore } from '../../stores';
|
|
29
|
+
|
|
30
|
+
interface IdRemoverDebuggerWrapperForStorybookProps {
|
|
31
|
+
questionnaire: Questionnaire;
|
|
32
|
+
questionnaireResponse?: QuestionnaireResponse;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* This is a wrapper which for debugging answer/item IDs in repeating items and groups.
|
|
37
|
+
* It features a button to remove answer/item IDs from the QuestionnaireResponse.
|
|
38
|
+
*
|
|
39
|
+
* @author Sean Fong
|
|
40
|
+
*/
|
|
41
|
+
function IdRemoverDebuggerWrapperForStorybook(props: IdRemoverDebuggerWrapperForStorybookProps) {
|
|
42
|
+
const { questionnaire, questionnaireResponse } = props;
|
|
43
|
+
|
|
44
|
+
const queryClient = useRendererQueryClient();
|
|
45
|
+
|
|
46
|
+
const focusedLinkId = useQuestionnaireStore.use.focusedLinkId();
|
|
47
|
+
const updatableResponse = useQuestionnaireResponseStore.use.updatableResponse();
|
|
48
|
+
|
|
49
|
+
const isBuilding = useBuildForm(
|
|
50
|
+
questionnaire,
|
|
51
|
+
undefined,
|
|
52
|
+
undefined,
|
|
53
|
+
STORYBOOK_TERMINOLOGY_SERVER_URL
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (isBuilding) {
|
|
57
|
+
return <div>Loading...</div>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<RendererThemeProvider>
|
|
62
|
+
<QueryClientProvider client={queryClient}>
|
|
63
|
+
<div>
|
|
64
|
+
<Grid container>
|
|
65
|
+
<Grid item xs={6}>
|
|
66
|
+
<IdRemoverButtonForStorybook questionnaireResponse={questionnaireResponse} />
|
|
67
|
+
<BaseRenderer />
|
|
68
|
+
</Grid>
|
|
69
|
+
<Grid item xs={6}>
|
|
70
|
+
<pre>{JSON.stringify(focusedLinkId, null, 2)}</pre>
|
|
71
|
+
----
|
|
72
|
+
<pre style={{ fontSize: 10 }}>{JSON.stringify(updatableResponse, null, 2)}</pre>
|
|
73
|
+
</Grid>
|
|
74
|
+
</Grid>
|
|
75
|
+
</div>
|
|
76
|
+
</QueryClientProvider>
|
|
77
|
+
</RendererThemeProvider>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default IdRemoverDebuggerWrapperForStorybook;
|
|
@@ -0,0 +1,39 @@
|
|
|
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 type { Meta, StoryObj } from '@storybook/react';
|
|
19
|
+
import IdRemoverDebuggerWrapperForStorybook from '../storybookWrappers/IdRemoverDebuggerWrapperForStorybook';
|
|
20
|
+
import { qMyPatient } from '../assets/questionnaires/QIdRemoverDebugger';
|
|
21
|
+
|
|
22
|
+
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
|
23
|
+
const meta = {
|
|
24
|
+
title: 'Component/Testing/ID Remover Debugger',
|
|
25
|
+
component: IdRemoverDebuggerWrapperForStorybook,
|
|
26
|
+
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
|
|
27
|
+
tags: []
|
|
28
|
+
} satisfies Meta<typeof IdRemoverDebuggerWrapperForStorybook>;
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
|
|
33
|
+
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
|
34
|
+
|
|
35
|
+
export const MyPatient: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
questionnaire: qMyPatient
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Questionnaire, QuestionnaireResponse } from 'fhir/r4';
|
|
2
|
+
|
|
3
|
+
import type { Extractable } from '../utils/extractObservation';
|
|
4
|
+
import { extractObservationBased, mapQItemsExtractable } from '../utils/extractObservation';
|
|
5
|
+
import {
|
|
6
|
+
observationResults,
|
|
7
|
+
qExtractSample,
|
|
8
|
+
qObservationSample,
|
|
9
|
+
qObservationSampleWithExtractExtension,
|
|
10
|
+
qrObservationSample
|
|
11
|
+
} from './test-data/observationSample';
|
|
12
|
+
|
|
13
|
+
describe('extractObservationBased', () => {
|
|
14
|
+
it('should correctly extract Observations from a QuestionnaireResponse', () => {
|
|
15
|
+
const observations = extractObservationBased(
|
|
16
|
+
qObservationSampleWithExtractExtension,
|
|
17
|
+
qrObservationSample
|
|
18
|
+
);
|
|
19
|
+
expect(observations).toHaveLength(4);
|
|
20
|
+
expect(observations[0]).toEqual(observationResults[0]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return an Observations array only if there are observation-extract extensions', () => {
|
|
24
|
+
const singleExtractExtension: Questionnaire = JSON.parse(
|
|
25
|
+
JSON.stringify(qObservationSampleWithExtractExtension)
|
|
26
|
+
);
|
|
27
|
+
singleExtractExtension.item!.at(0)!.extension!.at(0)!.valueBoolean = false;
|
|
28
|
+
|
|
29
|
+
const observations = extractObservationBased(singleExtractExtension, qrObservationSample);
|
|
30
|
+
expect(observations).toHaveLength(2);
|
|
31
|
+
expect(observations[0].id).toContain('phq2-4');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return all Observations expect for observation-extract extensions false', () => {
|
|
35
|
+
const topLevelExtract: Questionnaire = JSON.parse(
|
|
36
|
+
JSON.stringify(qObservationSampleWithExtractExtension)
|
|
37
|
+
);
|
|
38
|
+
topLevelExtract.extension!.at(0)!.valueBoolean = true;
|
|
39
|
+
|
|
40
|
+
const observations = extractObservationBased(topLevelExtract, qrObservationSample);
|
|
41
|
+
expect(observations).toHaveLength(6);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return an empty array if there are no observation-extract extensions', () => {
|
|
45
|
+
const observations = extractObservationBased(qObservationSample, qrObservationSample);
|
|
46
|
+
expect(observations).toHaveLength(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return an empty array if there are no items in the Questionnaire or QuestionnaireResponse', () => {
|
|
50
|
+
const emptyQuestionnaire: Questionnaire = {
|
|
51
|
+
resourceType: 'Questionnaire',
|
|
52
|
+
item: [],
|
|
53
|
+
status: 'draft'
|
|
54
|
+
};
|
|
55
|
+
const emptyResponse: QuestionnaireResponse = {
|
|
56
|
+
resourceType: 'QuestionnaireResponse',
|
|
57
|
+
item: [],
|
|
58
|
+
status: 'completed'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const observations = extractObservationBased(emptyQuestionnaire, emptyResponse);
|
|
62
|
+
expect(observations).toHaveLength(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return an empty array if no matching Questionnaire item is found for a QuestionnaireResponse item', () => {
|
|
66
|
+
const responseWithNoMatch: QuestionnaireResponse = {
|
|
67
|
+
resourceType: 'QuestionnaireResponse',
|
|
68
|
+
status: 'completed',
|
|
69
|
+
id: 'qr2',
|
|
70
|
+
item: [
|
|
71
|
+
{
|
|
72
|
+
linkId: '999', // No matching linkId in the questionnaire
|
|
73
|
+
answer: [
|
|
74
|
+
{
|
|
75
|
+
valueQuantity: {
|
|
76
|
+
value: 100,
|
|
77
|
+
unit: 'kg',
|
|
78
|
+
system: 'http://unitsofmeasure.org',
|
|
79
|
+
code: 'kg'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
subject: {
|
|
86
|
+
reference: 'Patient/456'
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const observations = extractObservationBased(qObservationSample, responseWithNoMatch);
|
|
91
|
+
expect(observations).toHaveLength(0);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('mapQItemsExtractable', () => {
|
|
96
|
+
it('should correctly return extractionMap from a Questionnaire', () => {
|
|
97
|
+
const extractionMap = mapQItemsExtractable(qExtractSample);
|
|
98
|
+
expect(extractionMap).toEqual({
|
|
99
|
+
'phq-2-questionnaire': { extractable: false, extractCategories: [] },
|
|
100
|
+
'phq2-1': { extractable: true, extractCategories: [] },
|
|
101
|
+
'phq2-2': { extractable: false, extractCategories: [] },
|
|
102
|
+
'phq2-3': { extractable: false, extractCategories: [] },
|
|
103
|
+
'phq2-4': { extractable: true, extractCategories: [] },
|
|
104
|
+
'phq2-5': { extractable: true, extractCategories: [] },
|
|
105
|
+
'phq2-6': { extractable: false, extractCategories: [] },
|
|
106
|
+
'phq2-7': { extractable: false, extractCategories: [] },
|
|
107
|
+
'phq2-8': { extractable: true, extractCategories: [] }
|
|
108
|
+
} satisfies Record<string, Extractable>);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should correctly return extractionMap even with topLevel observation-extract extensions true', () => {
|
|
112
|
+
const topLevelExtract: Questionnaire = JSON.parse(JSON.stringify(qExtractSample));
|
|
113
|
+
topLevelExtract.extension!.at(0)!.valueBoolean = true;
|
|
114
|
+
|
|
115
|
+
const extractionMap = mapQItemsExtractable(topLevelExtract);
|
|
116
|
+
|
|
117
|
+
expect(extractionMap).toEqual({
|
|
118
|
+
'phq-2-questionnaire': { extractable: true, extractCategories: [] },
|
|
119
|
+
'phq2-1': { extractable: true, extractCategories: [] },
|
|
120
|
+
'phq2-2': { extractable: false, extractCategories: [] },
|
|
121
|
+
'phq2-3': { extractable: true, extractCategories: [] },
|
|
122
|
+
'phq2-4': { extractable: true, extractCategories: [] },
|
|
123
|
+
'phq2-5': { extractable: true, extractCategories: [] },
|
|
124
|
+
'phq2-6': { extractable: false, extractCategories: [] },
|
|
125
|
+
'phq2-7': { extractable: true, extractCategories: [] },
|
|
126
|
+
'phq2-8': { extractable: true, extractCategories: [] }
|
|
127
|
+
} satisfies Record<string, Extractable>);
|
|
128
|
+
});
|
|
129
|
+
});
|