@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.
Files changed (24) hide show
  1. package/dist/1379.js +1 -0
  2. package/dist/1379.js.map +1 -0
  3. package/dist/7252.js +1 -1
  4. package/dist/7252.js.map +1 -1
  5. package/dist/8091.js +1 -1
  6. package/dist/8091.js.map +1 -1
  7. package/dist/main.js +1 -1
  8. package/dist/main.js.map +1 -1
  9. package/dist/openmrs-esm-form-builder-app.js.buildmanifest.json +30 -30
  10. package/dist/routes.json +1 -1
  11. package/package.json +1 -1
  12. package/src/components/action-buttons/action-buttons.component.tsx +1 -1
  13. package/src/components/form-editor/form-editor.component.tsx +1 -1
  14. package/src/components/interactive-builder/modals/add-form-reference/add-form-reference.modal.tsx +1 -1
  15. package/src/components/interactive-builder/modals/question/question-form/question/question.component.tsx +1 -1
  16. package/src/components/interactive-builder/modals/question/question-form/question-types/inputs/index.tsx +1 -0
  17. package/src/components/interactive-builder/modals/question/question-form/question-types/inputs/person-attribute/person-attribute-type-question.component.tsx +99 -0
  18. package/src/components/interactive-builder/modals/question/question-form/question-types/inputs/person-attribute/person-attribute-type-question.scss +15 -0
  19. package/src/components/interactive-builder/modals/question/question-form/question-types/inputs/person-attribute/person-attribute-type-question.test.tsx +131 -0
  20. package/src/components/interactive-builder/modals/question/question-form/question-types/question-type.component.tsx +3 -0
  21. package/src/constants.ts +3 -1
  22. package/src/resources/form-validator.resource.ts +92 -19
  23. package/dist/7051.js +0 -1
  24. 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: `❌ concept "${conceptObject.questionOptions.concept}" of type "boolean" has a non-boolean answer "${answer.label}"`,
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: `⚠️ answer: "${answer.label}" - "${answer.concept}" does not exist in the response answers but exists in the form`,
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: `❓ Concept "${conceptObject.questionOptions.concept}" not found`,
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 (conceptObject.questionOptions.rendering !== 'workspace-launcher') {
148
+ } else if (
149
+ conceptObject.questionOptions.rendering !== 'workspace-launcher' &&
150
+ conceptObject.type !== 'personAttribute'
151
+ ) {
128
152
  errorsArray.push({
129
- errorMessage: `❓ No UUID`,
153
+ errorMessage: t('noUuid', 'No UUID'),
130
154
  field: conceptObject,
131
155
  });
132
156
  }
133
157
  };
134
158
 
135
- const handlePatientIdentifierValidation = async (question: FormField, errors: Array<ErrorMessageResponse>) => {
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: `❓ Patient identifier type missing in schema`,
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: `❓ The identifier type does not exist`,
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: `❓ The identifier type does not exist`,
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: `❓ ${conceptObject.questionOptions.concept}: datatype "${responseObject.datatype.name}" doesn't match control type "${conceptObject.questionOptions.rendering}"`,
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: `❓ Untracked datatype "${responseObject.datatype.name}"`,
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: `❌ concept "${answer.concept}" not found`,
282
+ errorMessage: t('answerConceptNotFound', 'Concept "{{concept}}" not found', { concept: answer.concept }),
210
283
  field: answer,
211
284
  });
212
285
  }