@openmrs/esm-form-builder-app 3.5.2-pre.3588 → 3.5.2-pre.3594
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/dist/1480.js +1 -1
- package/dist/1480.js.map +1 -1
- package/dist/1646.js +1 -1
- package/dist/1646.js.map +1 -1
- package/dist/1869.js +1 -1
- package/dist/1869.js.map +1 -1
- package/dist/1877.js +1 -1
- package/dist/1877.js.map +1 -1
- package/dist/2317.js +1 -1
- package/dist/2317.js.map +1 -1
- package/dist/2416.js +1 -1
- package/dist/2416.js.map +1 -1
- package/dist/282.js +1 -1
- package/dist/282.js.map +1 -1
- package/dist/2881.js +1 -1
- package/dist/2881.js.map +1 -1
- package/dist/3963.js +1 -1
- package/dist/3963.js.map +1 -1
- package/dist/4106.js +1 -1
- package/dist/4106.js.map +1 -1
- package/dist/4111.js +1 -1
- package/dist/4111.js.map +1 -1
- package/dist/434.js +1 -1
- package/dist/434.js.map +1 -1
- package/dist/4348.js +1 -1
- package/dist/4348.js.map +1 -1
- package/dist/4658.js +1 -1
- package/dist/4658.js.map +1 -1
- package/dist/4928.js +1 -1
- package/dist/4928.js.map +1 -1
- package/dist/5117.js +1 -1
- package/dist/5117.js.map +1 -1
- package/dist/5132.js +1 -1
- package/dist/5132.js.map +1 -1
- package/dist/5145.js +1 -1
- package/dist/5145.js.map +1 -1
- package/dist/5503.js +1 -1
- package/dist/5503.js.map +1 -1
- package/dist/556.js +1 -1
- package/dist/556.js.map +1 -1
- package/dist/5644.js +1 -1
- package/dist/5644.js.map +1 -1
- package/dist/5940.js +1 -1
- package/dist/5940.js.map +1 -1
- package/dist/6047.js +1 -1
- package/dist/6047.js.map +1 -1
- package/dist/6371.js +1 -1
- package/dist/6371.js.map +1 -1
- package/dist/6377.js +1 -1
- package/dist/6377.js.map +1 -1
- package/dist/6444.js +1 -1
- package/dist/6444.js.map +1 -1
- package/dist/6508.js +1 -1
- package/dist/6508.js.map +1 -1
- package/dist/6724.js +1 -1
- package/dist/6724.js.map +1 -1
- package/dist/6764.js +1 -1
- package/dist/6764.js.map +1 -1
- package/dist/6904.js +1 -1
- package/dist/6904.js.map +1 -1
- package/dist/7045.js +1 -1
- package/dist/7045.js.map +1 -1
- package/dist/7175.js +1 -1
- package/dist/7175.js.map +1 -1
- package/dist/7182.js +1 -1
- package/dist/7182.js.map +1 -1
- package/dist/7742.js +1 -1
- package/dist/7742.js.map +1 -1
- package/dist/7912.js +1 -1
- package/dist/7912.js.map +1 -1
- package/dist/8358.js +1 -1
- package/dist/8358.js.map +1 -1
- package/dist/8359.js +1 -1
- package/dist/8359.js.map +1 -1
- package/dist/8695.js +1 -1
- package/dist/8695.js.map +1 -1
- package/dist/903.js +1 -1
- package/dist/903.js.map +1 -1
- package/dist/9072.js +1 -1
- package/dist/9072.js.map +1 -1
- package/dist/9103.js +1 -1
- package/dist/9103.js.map +1 -1
- package/dist/9806.js +1 -1
- package/dist/9806.js.map +1 -1
- package/dist/997.js +1 -1
- package/dist/997.js.map +1 -1
- package/dist/openmrs-esm-form-builder-app.js.buildmanifest.json +129 -129
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/select/select-answers.component.tsx +60 -13
- package/src/components/interactive-builder/modals/question/question-form/rendering-types/inputs/select/select-answers.test.tsx +43 -13
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":">=2.2.0"},"pages":[{"component":"root","route":"form-builder","online":true,"offline":true,"order":1}],"extensions":[{"name":"system-administration-form-builder-card-link","slot":"system-admin-page-card-link-slot","component":"systemAdministrationFormBuilderCardLink","online":true,"offline":true}],"modals":[{"name":"new-form-modal","component":"newFormModal"},{"name":"new-page-modal","component":"newPageModal"},{"name":"delete-page-modal","component":"deletePageModal"},{"name":"new-section-modal","component":"newSectionModal"},{"name":"delete-section-modal","component":"deleteSectionModal"},{"name":"add-form-reference-modal","component":"addFormReferenceModal"},{"name":"question-modal","component":"questionModal"},{"name":"delete-question-modal","component":"deleteQuestionModal"},{"name":"edit-question-modal","component":"editQuestionModal"},{"name":"restore-draft-schema-modal","component":"restoreDraftSchemaModal"},{"name":"unpublish-form-modal","component":"unpublishFormModal"},{"name":"delete-form-modal","component":"deleteFormModal"},{"name":"edit-translation-modal","component":"editTranslationModal"}],"version":"3.5.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":">=2.2.0"},"pages":[{"component":"root","route":"form-builder","online":true,"offline":true,"order":1}],"extensions":[{"name":"system-administration-form-builder-card-link","slot":"system-admin-page-card-link-slot","component":"systemAdministrationFormBuilderCardLink","online":true,"offline":true}],"modals":[{"name":"new-form-modal","component":"newFormModal"},{"name":"new-page-modal","component":"newPageModal"},{"name":"delete-page-modal","component":"deletePageModal"},{"name":"new-section-modal","component":"newSectionModal"},{"name":"delete-section-modal","component":"deleteSectionModal"},{"name":"add-form-reference-modal","component":"addFormReferenceModal"},{"name":"question-modal","component":"questionModal"},{"name":"delete-question-modal","component":"deleteQuestionModal"},{"name":"edit-question-modal","component":"editQuestionModal"},{"name":"restore-draft-schema-modal","component":"restoreDraftSchemaModal"},{"name":"unpublish-form-modal","component":"unpublishFormModal"},{"name":"delete-form-modal","component":"deleteFormModal"},{"name":"edit-translation-modal","component":"editTranslationModal"}],"version":"3.5.2-pre.3594"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { InlineNotification, MultiSelect, Stack } from '@carbon/react';
|
|
3
3
|
import { DndContext, type DragEndEvent } from '@dnd-kit/core';
|
|
4
4
|
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
@@ -20,13 +20,40 @@ const SelectAnswers: React.FC = () => {
|
|
|
20
20
|
const { formField, concept, setFormField } = useFormField();
|
|
21
21
|
const [addedAnswers, setAddedAnswers] = useState<AnswerItem[]>([]);
|
|
22
22
|
const [invalidAnswerIds, setInvalidAnswerIds] = useState<string[]>([]);
|
|
23
|
+
const initializedConceptUuid = useRef<string | null>(null);
|
|
23
24
|
|
|
25
|
+
// Reset the additionally selected answers when the concept changes
|
|
24
26
|
useEffect(() => {
|
|
25
27
|
if (!concept) {
|
|
26
28
|
setAddedAnswers([]);
|
|
27
29
|
}
|
|
28
30
|
}, [concept]);
|
|
29
31
|
|
|
32
|
+
// Initialize the selected answers to the concept's answers, once per concept.
|
|
33
|
+
// The once-per-concept guard keeps a cleared selection cleared instead of
|
|
34
|
+
// re-selecting every answer whenever the answers list becomes empty.
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!concept || initializedConceptUuid.current === concept.uuid) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
initializedConceptUuid.current = concept.uuid;
|
|
40
|
+
|
|
41
|
+
if (concept.answers?.length && !formField.questionOptions?.answers?.length) {
|
|
42
|
+
const initialAnswers = concept.answers.map((answer) => ({
|
|
43
|
+
concept: answer.uuid,
|
|
44
|
+
label: answer.display,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
setFormField((prevField) => ({
|
|
48
|
+
...prevField,
|
|
49
|
+
questionOptions: {
|
|
50
|
+
...prevField.questionOptions,
|
|
51
|
+
answers: initialAnswers,
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
}, [concept, formField.questionOptions?.answers, setFormField]);
|
|
56
|
+
|
|
30
57
|
const selectedAnswers = useMemo(() => {
|
|
31
58
|
const answers =
|
|
32
59
|
formField.questionOptions?.answers?.map((answer) => ({
|
|
@@ -39,10 +66,12 @@ const SelectAnswers: React.FC = () => {
|
|
|
39
66
|
|
|
40
67
|
const handleSelectAnswers = useCallback(
|
|
41
68
|
({ selectedItems }: { selectedItems: Array<AnswerItem> }) => {
|
|
42
|
-
const mappedAnswers = selectedItems
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
69
|
+
const mappedAnswers = selectedItems
|
|
70
|
+
.filter((answer) => answer.id !== 'select-all')
|
|
71
|
+
.map((answer) => ({
|
|
72
|
+
concept: answer.id,
|
|
73
|
+
label: answer.text,
|
|
74
|
+
}));
|
|
46
75
|
|
|
47
76
|
setFormField((prevField) => {
|
|
48
77
|
const currentAnswers = prevField.questionOptions?.answers || [];
|
|
@@ -113,10 +142,17 @@ const SelectAnswers: React.FC = () => {
|
|
|
113
142
|
|
|
114
143
|
// If no answers from concept but we have form field answers, use those
|
|
115
144
|
if (conceptAnswerItems.length === 0 && formFieldAnswers.length > 0) {
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
145
|
+
return [
|
|
146
|
+
...formFieldAnswers.map((answer) => ({
|
|
147
|
+
id: answer.concept,
|
|
148
|
+
text: answer.label,
|
|
149
|
+
})),
|
|
150
|
+
{
|
|
151
|
+
id: 'select-all',
|
|
152
|
+
text: t('selectAllOptions', 'Select all options'),
|
|
153
|
+
isSelectAll: true,
|
|
154
|
+
},
|
|
155
|
+
];
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
const formFieldAnswerLabelsMap = new Map(formFieldAnswers.map((answer) => [answer.concept, answer.label]));
|
|
@@ -133,8 +169,16 @@ const SelectAnswers: React.FC = () => {
|
|
|
133
169
|
text: answer.label,
|
|
134
170
|
}));
|
|
135
171
|
|
|
136
|
-
return [
|
|
137
|
-
|
|
172
|
+
return [
|
|
173
|
+
...answersFromConceptWithLabelsFromFormField,
|
|
174
|
+
...additionalAnswers,
|
|
175
|
+
{
|
|
176
|
+
id: 'select-all',
|
|
177
|
+
text: t('selectAllOptions', 'Select all options'),
|
|
178
|
+
isSelectAll: true,
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
}, [concept?.answers, formField.questionOptions?.answers, t]);
|
|
138
182
|
|
|
139
183
|
const validateAnswers = useCallback(async () => {
|
|
140
184
|
if (!answerItems.length) {
|
|
@@ -144,7 +188,10 @@ const SelectAnswers: React.FC = () => {
|
|
|
144
188
|
|
|
145
189
|
const originalAnswerIds = new Set((concept?.answers || []).map((ans) => ans.uuid));
|
|
146
190
|
const invalidIds: string[] = [];
|
|
147
|
-
|
|
191
|
+
// Filter out the 'select-all' option before validation
|
|
192
|
+
const uniqueAnswers = Array.from(
|
|
193
|
+
new Map(answerItems.filter((a) => a.id !== 'select-all').map((a) => [a.id, a])).values(),
|
|
194
|
+
);
|
|
148
195
|
|
|
149
196
|
for (const answer of uniqueAnswers) {
|
|
150
197
|
if (!originalAnswerIds.has(answer.id)) {
|
|
@@ -219,7 +266,7 @@ const SelectAnswers: React.FC = () => {
|
|
|
219
266
|
|
|
220
267
|
return (
|
|
221
268
|
<Stack gap={5}>
|
|
222
|
-
{answerItems.length >
|
|
269
|
+
{answerItems.length > 1 && (
|
|
223
270
|
<MultiSelect
|
|
224
271
|
className={styles.multiSelect}
|
|
225
272
|
direction="bottom"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
3
|
-
import { render, screen
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
4
|
import SelectAnswers from './select-answers.component';
|
|
5
5
|
import { useConceptId } from '@hooks/useConceptId';
|
|
6
6
|
import { useConceptLookup } from '@hooks/useConceptLookup';
|
|
@@ -69,7 +69,7 @@ describe('Select answers component', () => {
|
|
|
69
69
|
).toBeInTheDocument();
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
it('
|
|
72
|
+
it('shows answers provided by concept as selected initially', async () => {
|
|
73
73
|
const user = userEvent.setup();
|
|
74
74
|
renderComponent();
|
|
75
75
|
const answersMenu = screen.getByRole('combobox', {
|
|
@@ -78,20 +78,50 @@ describe('Select answers component', () => {
|
|
|
78
78
|
expect(answersMenu).toBeInTheDocument();
|
|
79
79
|
|
|
80
80
|
await user.click(answersMenu);
|
|
81
|
-
const answerOption1 = screen.getByRole('
|
|
82
|
-
expect(answerOption1).toBeInTheDocument();
|
|
83
|
-
expect(screen.getByText(/answer 2/i)).toBeInTheDocument();
|
|
84
|
-
await user.click(answerOption1);
|
|
85
|
-
const option = screen.getByRole('option', {
|
|
81
|
+
const answerOption1 = screen.getByRole('checkbox', {
|
|
86
82
|
name: /answer 1/i,
|
|
87
83
|
});
|
|
88
|
-
|
|
84
|
+
const answerOption2 = screen.getByRole('checkbox', {
|
|
85
|
+
name: /answer 2/i,
|
|
86
|
+
});
|
|
87
|
+
expect(answerOption1).toBeChecked();
|
|
88
|
+
expect(answerOption2).toBeChecked();
|
|
89
|
+
});
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
it('keeps a cleared selection cleared instead of re-selecting every answer', async () => {
|
|
92
|
+
const user = userEvent.setup();
|
|
93
|
+
renderComponent();
|
|
94
|
+
const answersMenu = screen.getByRole('combobox', {
|
|
95
|
+
name: /select answers to display/i,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await user.click(answersMenu);
|
|
99
|
+
await user.click(screen.getByRole('checkbox', { name: /answer 1/i }));
|
|
100
|
+
await user.click(screen.getByRole('checkbox', { name: /answer 2/i }));
|
|
101
|
+
|
|
102
|
+
expect(screen.getByRole('checkbox', { name: /answer 1/i })).not.toBeChecked();
|
|
103
|
+
expect(screen.getByRole('checkbox', { name: /answer 2/i })).not.toBeChecked();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('renders the answers menu when a saved custom answer exists and the concept has no answers', () => {
|
|
107
|
+
render(
|
|
108
|
+
<FormFieldProvider
|
|
109
|
+
initialFormField={{
|
|
110
|
+
...formField,
|
|
111
|
+
questionOptions: {
|
|
112
|
+
rendering: 'select',
|
|
113
|
+
answers: [{ concept: '999', label: 'Custom answer' }],
|
|
114
|
+
},
|
|
115
|
+
}}
|
|
116
|
+
selectedConcept={{ ...concept, answers: [] }}>
|
|
117
|
+
<SelectAnswers />
|
|
118
|
+
</FormFieldProvider>,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const answersMenu = screen.getByRole('combobox', {
|
|
122
|
+
name: /select answers to display/i,
|
|
123
|
+
});
|
|
124
|
+
expect(answersMenu).toBeInTheDocument();
|
|
95
125
|
});
|
|
96
126
|
|
|
97
127
|
it('lets users add additional answers if concept is of datatype coded', async () => {
|