@openmrs/esm-form-builder-app 3.4.2-pre.3379 → 3.4.2-pre.3382
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/1379.js +1 -0
- package/dist/1379.js.map +1 -0
- package/dist/7252.js +1 -1
- package/dist/7252.js.map +1 -1
- package/dist/8091.js +1 -1
- package/dist/8091.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-form-builder-app.js.buildmanifest.json +30 -30
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/components/action-buttons/action-buttons.component.tsx +1 -1
- package/src/components/form-editor/form-editor.component.tsx +1 -1
- package/src/components/interactive-builder/modals/add-form-reference/add-form-reference.modal.tsx +1 -1
- package/src/components/interactive-builder/modals/question/question-form/question/question.component.tsx +1 -1
- package/src/components/interactive-builder/modals/question/question-form/question-types/inputs/index.tsx +1 -0
- package/src/components/interactive-builder/modals/question/question-form/question-types/inputs/person-attribute/person-attribute-type-question.component.tsx +99 -0
- package/src/components/interactive-builder/modals/question/question-form/question-types/inputs/person-attribute/person-attribute-type-question.scss +15 -0
- package/src/components/interactive-builder/modals/question/question-form/question-types/inputs/person-attribute/person-attribute-type-question.test.tsx +131 -0
- package/src/components/interactive-builder/modals/question/question-form/question-types/question-type.component.tsx +3 -0
- package/src/constants.ts +3 -1
- package/src/resources/form-validator.resource.ts +92 -19
- package/dist/7051.js +0 -1
- package/dist/7051.js.map +0 -1
|
@@ -2,6 +2,7 @@ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
|
2
2
|
import type { FormField } from '@openmrs/esm-form-engine-lib';
|
|
3
3
|
import type { Schema } from '@types';
|
|
4
4
|
import type { ConfigObject } from '../config-schema';
|
|
5
|
+
import type { TFunction } from 'i18next';
|
|
5
6
|
|
|
6
7
|
interface ConceptMapping {
|
|
7
8
|
type: string;
|
|
@@ -28,6 +29,7 @@ interface WarningMessageResponse {
|
|
|
28
29
|
export const handleFormValidation = async (
|
|
29
30
|
schema: string | Schema,
|
|
30
31
|
configObject: ConfigObject['dataTypeToRenderingMap'],
|
|
32
|
+
t: TFunction,
|
|
31
33
|
): Promise<[Array<ErrorMessageResponse>, Array<WarningMessageResponse>]> => {
|
|
32
34
|
const errors: Array<ErrorMessageResponse> = [];
|
|
33
35
|
const warnings: Array<WarningMessageResponse> = [];
|
|
@@ -41,15 +43,17 @@ export const handleFormValidation = async (
|
|
|
41
43
|
page.sections?.forEach((section: { questions: Array<FormField> }) =>
|
|
42
44
|
section.questions?.forEach((question) => {
|
|
43
45
|
asyncTasks.push(
|
|
44
|
-
handleQuestionValidation(question, errors, configObject, warnings),
|
|
45
|
-
handleAnswerValidation(question, errors),
|
|
46
|
-
handlePatientIdentifierValidation(question, errors),
|
|
46
|
+
handleQuestionValidation(question, errors, configObject, warnings, t),
|
|
47
|
+
handleAnswerValidation(question, errors, t),
|
|
48
|
+
handlePatientIdentifierValidation(question, errors, t),
|
|
49
|
+
handlePersonAttributeValidation(question, errors, t),
|
|
47
50
|
);
|
|
48
51
|
if (question.type === 'obsGroup') {
|
|
49
52
|
question?.questions?.forEach((obsGrpQuestion) =>
|
|
50
53
|
asyncTasks.push(
|
|
51
|
-
handleQuestionValidation(obsGrpQuestion, errors, configObject, warnings),
|
|
52
|
-
handleAnswerValidation(obsGrpQuestion, errors),
|
|
54
|
+
handleQuestionValidation(obsGrpQuestion, errors, configObject, warnings, t),
|
|
55
|
+
handleAnswerValidation(obsGrpQuestion, errors, t),
|
|
56
|
+
handlePersonAttributeValidation(obsGrpQuestion, errors, t),
|
|
53
57
|
),
|
|
54
58
|
);
|
|
55
59
|
}
|
|
@@ -68,6 +72,7 @@ const handleQuestionValidation = async (
|
|
|
68
72
|
errorsArray: Array<ErrorMessageResponse>,
|
|
69
73
|
configObject: ConfigObject['dataTypeToRenderingMap'],
|
|
70
74
|
warningsArray: Array<WarningMessageResponse>,
|
|
75
|
+
t: TFunction,
|
|
71
76
|
) => {
|
|
72
77
|
const conceptRepresentation =
|
|
73
78
|
'custom:(uuid,display,datatype,answers,conceptMappings:(conceptReferenceTerm:(conceptSource:(name),code)))';
|
|
@@ -96,7 +101,14 @@ const handleQuestionValidation = async (
|
|
|
96
101
|
answer.concept !== '488b58ff-64f5-4f8a-8979-fa79940b1594'
|
|
97
102
|
) {
|
|
98
103
|
errorsArray.push({
|
|
99
|
-
errorMessage:
|
|
104
|
+
errorMessage: t(
|
|
105
|
+
'booleanConceptNonBooleanAnswer',
|
|
106
|
+
'Concept "{{concept}}" of type "boolean" has a non-boolean answer "{{answer}}"',
|
|
107
|
+
{
|
|
108
|
+
concept: conceptObject.questionOptions.concept,
|
|
109
|
+
answer: answer.label,
|
|
110
|
+
},
|
|
111
|
+
),
|
|
100
112
|
field: conceptObject,
|
|
101
113
|
});
|
|
102
114
|
}
|
|
@@ -107,35 +119,51 @@ const handleQuestionValidation = async (
|
|
|
107
119
|
conceptObject.questionOptions.answers?.forEach((answer) => {
|
|
108
120
|
if (!resObject.answers.some((answerObject: { uuid: string }) => answerObject.uuid === answer.concept)) {
|
|
109
121
|
warningsArray.push({
|
|
110
|
-
warningMessage:
|
|
122
|
+
warningMessage: t(
|
|
123
|
+
'answerNotInResponseAnswers',
|
|
124
|
+
'Answer: "{{label}}" - "{{concept}}" does not exist in the response answers but exists in the form',
|
|
125
|
+
{
|
|
126
|
+
label: answer.label,
|
|
127
|
+
concept: answer.concept,
|
|
128
|
+
},
|
|
129
|
+
),
|
|
111
130
|
field: conceptObject,
|
|
112
131
|
});
|
|
113
132
|
}
|
|
114
133
|
});
|
|
115
134
|
}
|
|
116
135
|
|
|
117
|
-
dataTypeChecker(conceptObject, resObject, errorsArray, configObject);
|
|
136
|
+
dataTypeChecker(conceptObject, resObject, errorsArray, configObject, t);
|
|
118
137
|
} else {
|
|
119
138
|
errorsArray.push({
|
|
120
|
-
errorMessage:
|
|
139
|
+
errorMessage: t('conceptNotFound', 'Concept "{{concept}}" not found', {
|
|
140
|
+
concept: conceptObject.questionOptions.concept,
|
|
141
|
+
}),
|
|
121
142
|
field: conceptObject,
|
|
122
143
|
});
|
|
123
144
|
}
|
|
124
145
|
} catch (error) {
|
|
125
146
|
console.error(error);
|
|
126
147
|
}
|
|
127
|
-
} else if (
|
|
148
|
+
} else if (
|
|
149
|
+
conceptObject.questionOptions.rendering !== 'workspace-launcher' &&
|
|
150
|
+
conceptObject.type !== 'personAttribute'
|
|
151
|
+
) {
|
|
128
152
|
errorsArray.push({
|
|
129
|
-
errorMessage:
|
|
153
|
+
errorMessage: t('noUuid', 'No UUID'),
|
|
130
154
|
field: conceptObject,
|
|
131
155
|
});
|
|
132
156
|
}
|
|
133
157
|
};
|
|
134
158
|
|
|
135
|
-
const handlePatientIdentifierValidation = async (
|
|
159
|
+
const handlePatientIdentifierValidation = async (
|
|
160
|
+
question: FormField,
|
|
161
|
+
errors: Array<ErrorMessageResponse>,
|
|
162
|
+
t: TFunction,
|
|
163
|
+
) => {
|
|
136
164
|
if (question.type === 'patientIdentifier' && !question.questionOptions.identifierType) {
|
|
137
165
|
errors.push({
|
|
138
|
-
errorMessage:
|
|
166
|
+
errorMessage: t('patientIdentifierTypeMissing', 'Patient identifier type missing in schema'),
|
|
139
167
|
field: question,
|
|
140
168
|
});
|
|
141
169
|
}
|
|
@@ -148,14 +176,48 @@ const handlePatientIdentifierValidation = async (question: FormField, errors: Ar
|
|
|
148
176
|
);
|
|
149
177
|
if (!data) {
|
|
150
178
|
errors.push({
|
|
151
|
-
errorMessage:
|
|
179
|
+
errorMessage: t('identifierTypeDoesNotExist', 'The identifier type does not exist'),
|
|
152
180
|
field: question,
|
|
153
181
|
});
|
|
154
182
|
}
|
|
155
183
|
} catch (error) {
|
|
156
184
|
console.error('Error fetching patient identifier:', error);
|
|
157
185
|
errors.push({
|
|
158
|
-
errorMessage:
|
|
186
|
+
errorMessage: t('identifierTypeDoesNotExist', 'The identifier type does not exist'),
|
|
187
|
+
field: question,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const handlePersonAttributeValidation = async (
|
|
194
|
+
question: FormField,
|
|
195
|
+
errors: Array<ErrorMessageResponse>,
|
|
196
|
+
t: TFunction,
|
|
197
|
+
) => {
|
|
198
|
+
const personAttribute = (question.questionOptions as { attributeType?: string }).attributeType;
|
|
199
|
+
|
|
200
|
+
if (question.type === 'personAttribute' && !personAttribute) {
|
|
201
|
+
errors.push({
|
|
202
|
+
errorMessage: t('personAttributeTypeMissing', 'Person attribute type missing in schema'),
|
|
203
|
+
field: question,
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (personAttribute) {
|
|
209
|
+
try {
|
|
210
|
+
const { data } = await openmrsFetch(`${restBaseUrl}/personattributetype/${personAttribute}`);
|
|
211
|
+
if (!data) {
|
|
212
|
+
errors.push({
|
|
213
|
+
errorMessage: t('personAttributeTypeDoesNotExist', 'The person attribute type does not exist'),
|
|
214
|
+
field: question,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.error('Error fetching person attribute:', error);
|
|
219
|
+
errors.push({
|
|
220
|
+
errorMessage: t('personAttributeTypeDoesNotExist', 'The person attribute type does not exist'),
|
|
159
221
|
field: question,
|
|
160
222
|
});
|
|
161
223
|
}
|
|
@@ -167,23 +229,34 @@ const dataTypeChecker = (
|
|
|
167
229
|
responseObject: { datatype: { name: string }; answers: Array<{ uuid: string }> },
|
|
168
230
|
array: Array<ErrorMessageResponse>,
|
|
169
231
|
dataTypeToRenderingMap: ConfigObject['dataTypeToRenderingMap'],
|
|
232
|
+
t: TFunction,
|
|
170
233
|
) => {
|
|
171
234
|
if (Object.prototype.hasOwnProperty.call(dataTypeToRenderingMap, responseObject.datatype.name)) {
|
|
172
235
|
if (!dataTypeToRenderingMap[responseObject.datatype.name].includes(conceptObject.questionOptions.rendering)) {
|
|
173
236
|
array.push({
|
|
174
|
-
errorMessage:
|
|
237
|
+
errorMessage: t(
|
|
238
|
+
'datatypeMismatch',
|
|
239
|
+
'{{concept}}: datatype "{{datatype}}" doesn\'t match control type "{{rendering}}"',
|
|
240
|
+
{
|
|
241
|
+
concept: conceptObject.questionOptions.concept,
|
|
242
|
+
datatype: responseObject.datatype.name,
|
|
243
|
+
rendering: conceptObject.questionOptions.rendering,
|
|
244
|
+
},
|
|
245
|
+
),
|
|
175
246
|
field: conceptObject,
|
|
176
247
|
});
|
|
177
248
|
}
|
|
178
249
|
} else {
|
|
179
250
|
array.push({
|
|
180
|
-
errorMessage:
|
|
251
|
+
errorMessage: t('untrackedDatatype', 'Untracked datatype "{{datatype}}"', {
|
|
252
|
+
datatype: responseObject.datatype.name,
|
|
253
|
+
}),
|
|
181
254
|
field: conceptObject,
|
|
182
255
|
});
|
|
183
256
|
}
|
|
184
257
|
};
|
|
185
258
|
|
|
186
|
-
const handleAnswerValidation = async (questionObject: FormField, array: Array<ErrorMessageResponse
|
|
259
|
+
const handleAnswerValidation = async (questionObject: FormField, array: Array<ErrorMessageResponse>, t: TFunction) => {
|
|
187
260
|
const answerArray = questionObject.questionOptions.answers;
|
|
188
261
|
const conceptRepresentation =
|
|
189
262
|
'custom:(uuid,display,datatype,conceptMappings:(conceptReferenceTerm:(conceptSource:(name),code)))';
|
|
@@ -206,7 +279,7 @@ const handleAnswerValidation = async (questionObject: FormField, array: Array<Er
|
|
|
206
279
|
);
|
|
207
280
|
if (!response.data.results.length) {
|
|
208
281
|
array.push({
|
|
209
|
-
errorMessage:
|
|
282
|
+
errorMessage: t('answerConceptNotFound', 'Concept "{{concept}}" not found', { concept: answer.concept }),
|
|
210
283
|
field: answer,
|
|
211
284
|
});
|
|
212
285
|
}
|