@openmrs/esm-form-engine-lib 2.1.0-pre.1561 → 2.1.0-pre.1564
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/package.json +1 -1
- package/src/adapters/obs-adapter.test.ts +1 -278
- package/src/adapters/obs-adapter.ts +30 -66
- package/src/components/group/obs-group.component.tsx +18 -43
- package/src/components/group/obs-group.scss +0 -4
- package/src/components/renderer/field/form-field-renderer.component.tsx +1 -1
- package/src/components/repeat/repeat.component.tsx +10 -2
- package/src/hooks/useFormFields.ts +23 -33
- package/src/processors/encounter/encounter-form-processor.ts +1 -1
- package/src/processors/encounter/encounter-processor-helper.ts +40 -50
- package/src/transformers/default-schema-transformer.ts +3 -8
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { type FormContextProps } from '../provider/form-provider';
|
2
2
|
import { type FormField } from '../types';
|
3
|
-
import {
|
3
|
+
import { hasPreviousObsValueChanged, findObsByFormField, ObsAdapter } from './obs-adapter';
|
4
4
|
|
5
5
|
const formContext = {
|
6
6
|
methods: null,
|
@@ -944,280 +944,3 @@ describe('findObsByFormField', () => {
|
|
944
944
|
expect(matchedObs[0]).toBe(obsList[3]);
|
945
945
|
});
|
946
946
|
});
|
947
|
-
|
948
|
-
describe('ObsAdapter - handling nested obsGroups', () => {
|
949
|
-
const createNestedFields = (): FormField => ({
|
950
|
-
label: 'Parent obsGroup',
|
951
|
-
type: 'obsGroup',
|
952
|
-
required: false,
|
953
|
-
id: 'parentObsgroup',
|
954
|
-
questionOptions: {
|
955
|
-
rendering: 'group',
|
956
|
-
concept: '163770AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
957
|
-
},
|
958
|
-
questions: [
|
959
|
-
{
|
960
|
-
label: 'Health Center',
|
961
|
-
type: 'obs',
|
962
|
-
required: false,
|
963
|
-
id: 'healthCenter',
|
964
|
-
questionOptions: {
|
965
|
-
rendering: 'select',
|
966
|
-
concept: '1745AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
967
|
-
answers: [
|
968
|
-
{
|
969
|
-
concept: '1560AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
970
|
-
label: 'Family member',
|
971
|
-
},
|
972
|
-
{
|
973
|
-
concept: '1588AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
974
|
-
label: 'Health clinic/post',
|
975
|
-
},
|
976
|
-
{
|
977
|
-
concept: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
978
|
-
label: 'Other',
|
979
|
-
},
|
980
|
-
],
|
981
|
-
},
|
982
|
-
},
|
983
|
-
{
|
984
|
-
label: 'Nested obsGroup',
|
985
|
-
type: 'obsGroup',
|
986
|
-
required: false,
|
987
|
-
id: 'nestedObsgroup',
|
988
|
-
questionOptions: {
|
989
|
-
rendering: 'group',
|
990
|
-
concept: '3f824eeb-8452-4df0-b346-6ed056cbc5b9',
|
991
|
-
},
|
992
|
-
questions: [
|
993
|
-
{
|
994
|
-
label: 'Comment',
|
995
|
-
type: 'obs',
|
996
|
-
required: false,
|
997
|
-
id: 'comment',
|
998
|
-
questionOptions: {
|
999
|
-
rendering: 'textarea',
|
1000
|
-
concept: '161011AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1001
|
-
},
|
1002
|
-
},
|
1003
|
-
{
|
1004
|
-
label: 'Other Diagnoses',
|
1005
|
-
type: 'obs',
|
1006
|
-
required: false,
|
1007
|
-
id: 'otherDiagnoses',
|
1008
|
-
questionOptions: {
|
1009
|
-
rendering: 'select',
|
1010
|
-
concept: '159947AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1011
|
-
answers: [
|
1012
|
-
{
|
1013
|
-
concept: '159394AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1014
|
-
label: 'Diagnosis certainty',
|
1015
|
-
},
|
1016
|
-
],
|
1017
|
-
},
|
1018
|
-
},
|
1019
|
-
],
|
1020
|
-
},
|
1021
|
-
],
|
1022
|
-
});
|
1023
|
-
|
1024
|
-
const createNestedObs = () => ({
|
1025
|
-
uuid: 'encounter-uuid',
|
1026
|
-
obs: [
|
1027
|
-
{
|
1028
|
-
uuid: 'parent-group-uuid',
|
1029
|
-
concept: {
|
1030
|
-
uuid: '163770AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1031
|
-
},
|
1032
|
-
groupMembers: [
|
1033
|
-
{
|
1034
|
-
uuid: 'health-center-uuid',
|
1035
|
-
concept: {
|
1036
|
-
uuid: '1745AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1037
|
-
},
|
1038
|
-
value: {
|
1039
|
-
uuid: '1588AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1040
|
-
},
|
1041
|
-
formFieldPath: 'rfe-forms-healthCenter',
|
1042
|
-
},
|
1043
|
-
{
|
1044
|
-
uuid: 'nested-group-uuid',
|
1045
|
-
concept: {
|
1046
|
-
uuid: '3f824eeb-8452-4df0-b346-6ed056cbc5b9',
|
1047
|
-
},
|
1048
|
-
groupMembers: [
|
1049
|
-
{
|
1050
|
-
uuid: 'comment-uuid',
|
1051
|
-
concept: {
|
1052
|
-
uuid: '161011AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1053
|
-
},
|
1054
|
-
value: 'Test comment for nested group',
|
1055
|
-
formFieldPath: 'rfe-forms-comment',
|
1056
|
-
},
|
1057
|
-
{
|
1058
|
-
uuid: 'diagnosis-uuid',
|
1059
|
-
concept: {
|
1060
|
-
uuid: '159947AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1061
|
-
},
|
1062
|
-
value: {
|
1063
|
-
uuid: '159394AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1064
|
-
},
|
1065
|
-
formFieldPath: 'rfe-forms-otherDiagnoses',
|
1066
|
-
},
|
1067
|
-
],
|
1068
|
-
},
|
1069
|
-
],
|
1070
|
-
},
|
1071
|
-
],
|
1072
|
-
});
|
1073
|
-
|
1074
|
-
beforeEach(() => {
|
1075
|
-
formContext.domainObjectValue = createNestedObs();
|
1076
|
-
ObsAdapter.tearDown();
|
1077
|
-
});
|
1078
|
-
|
1079
|
-
it('should get initial values from nested obs groups', async () => {
|
1080
|
-
const fields = createNestedFields();
|
1081
|
-
|
1082
|
-
const healthCenterField = fields.questions[0];
|
1083
|
-
const healthCenterValue = await ObsAdapter.getInitialValue(
|
1084
|
-
healthCenterField,
|
1085
|
-
formContext.domainObjectValue,
|
1086
|
-
formContext,
|
1087
|
-
);
|
1088
|
-
expect(healthCenterValue).toBe('1588AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
|
1089
|
-
|
1090
|
-
const commentField = fields.questions[1].questions[0];
|
1091
|
-
const commentValue = await ObsAdapter.getInitialValue(commentField, formContext.domainObjectValue, formContext);
|
1092
|
-
expect(commentValue).toBe('Test comment for nested group');
|
1093
|
-
|
1094
|
-
const diagnosisField = fields.questions[1].questions[1];
|
1095
|
-
const diagnosisValue = await ObsAdapter.getInitialValue(diagnosisField, formContext.domainObjectValue, formContext);
|
1096
|
-
expect(diagnosisValue).toBe('159394AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
|
1097
|
-
});
|
1098
|
-
|
1099
|
-
it('should transform values in nested groups', () => {
|
1100
|
-
const fields = createNestedFields();
|
1101
|
-
|
1102
|
-
const healthCenterField = fields.questions[0];
|
1103
|
-
const healthCenterObs = ObsAdapter.transformFieldValue(
|
1104
|
-
healthCenterField,
|
1105
|
-
'1560AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1106
|
-
formContext,
|
1107
|
-
);
|
1108
|
-
expect(healthCenterObs).toEqual({
|
1109
|
-
concept: '1745AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1110
|
-
formFieldNamespace: 'rfe-forms',
|
1111
|
-
formFieldPath: 'rfe-forms-healthCenter',
|
1112
|
-
value: '1560AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1113
|
-
});
|
1114
|
-
|
1115
|
-
const commentField = fields.questions[1].questions[0];
|
1116
|
-
const commentObs = ObsAdapter.transformFieldValue(commentField, 'New test comment', formContext);
|
1117
|
-
expect(commentObs).toEqual({
|
1118
|
-
concept: '161011AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1119
|
-
formFieldNamespace: 'rfe-forms',
|
1120
|
-
formFieldPath: 'rfe-forms-comment',
|
1121
|
-
value: 'New test comment',
|
1122
|
-
});
|
1123
|
-
});
|
1124
|
-
|
1125
|
-
it('should edit existing values in nested groups', () => {
|
1126
|
-
formContext.sessionMode = 'edit';
|
1127
|
-
const fields = createNestedFields();
|
1128
|
-
|
1129
|
-
const healthCenterField = fields.questions[0];
|
1130
|
-
healthCenterField.meta = {
|
1131
|
-
previousValue: {
|
1132
|
-
uuid: 'health-center-uuid',
|
1133
|
-
value: {
|
1134
|
-
uuid: '1588AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1135
|
-
},
|
1136
|
-
},
|
1137
|
-
};
|
1138
|
-
|
1139
|
-
const healthCenterObs = ObsAdapter.transformFieldValue(
|
1140
|
-
healthCenterField,
|
1141
|
-
'5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1142
|
-
formContext,
|
1143
|
-
);
|
1144
|
-
|
1145
|
-
expect(healthCenterObs).toEqual({
|
1146
|
-
uuid: 'health-center-uuid',
|
1147
|
-
formFieldNamespace: 'rfe-forms',
|
1148
|
-
formFieldPath: 'rfe-forms-healthCenter',
|
1149
|
-
value: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1150
|
-
});
|
1151
|
-
|
1152
|
-
const commentField = fields.questions[1].questions[0];
|
1153
|
-
commentField.meta = {
|
1154
|
-
previousValue: {
|
1155
|
-
uuid: 'comment-uuid',
|
1156
|
-
value: 'Test comment for nested group',
|
1157
|
-
},
|
1158
|
-
};
|
1159
|
-
|
1160
|
-
const commentObs = ObsAdapter.transformFieldValue(commentField, 'Updated comment text', formContext);
|
1161
|
-
|
1162
|
-
expect(commentObs).toEqual({
|
1163
|
-
uuid: 'comment-uuid',
|
1164
|
-
formFieldNamespace: 'rfe-forms',
|
1165
|
-
formFieldPath: 'rfe-forms-comment',
|
1166
|
-
value: 'Updated comment text',
|
1167
|
-
});
|
1168
|
-
});
|
1169
|
-
|
1170
|
-
it('should void deleted values in nested groups', () => {
|
1171
|
-
formContext.sessionMode = 'edit';
|
1172
|
-
const fields = createNestedFields();
|
1173
|
-
|
1174
|
-
const commentField = fields.questions[1].questions[0];
|
1175
|
-
commentField.meta = {
|
1176
|
-
previousValue: {
|
1177
|
-
uuid: 'comment-uuid',
|
1178
|
-
value: 'Test comment for nested group',
|
1179
|
-
},
|
1180
|
-
};
|
1181
|
-
|
1182
|
-
ObsAdapter.transformFieldValue(commentField, '', formContext);
|
1183
|
-
expect(commentField.meta.submission.voidedValue).toEqual({
|
1184
|
-
uuid: 'comment-uuid',
|
1185
|
-
voided: true,
|
1186
|
-
});
|
1187
|
-
expect(commentField.meta.submission.newValue).toBe(null);
|
1188
|
-
|
1189
|
-
const diagnosisField = fields.questions[1].questions[1];
|
1190
|
-
diagnosisField.meta = {
|
1191
|
-
previousValue: {
|
1192
|
-
uuid: 'diagnosis-uuid',
|
1193
|
-
value: {
|
1194
|
-
uuid: '159394AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
1195
|
-
},
|
1196
|
-
},
|
1197
|
-
};
|
1198
|
-
|
1199
|
-
ObsAdapter.transformFieldValue(diagnosisField, null, formContext);
|
1200
|
-
expect(diagnosisField.meta.submission.voidedValue).toEqual({
|
1201
|
-
uuid: 'diagnosis-uuid',
|
1202
|
-
voided: true,
|
1203
|
-
});
|
1204
|
-
expect(diagnosisField.meta.submission.newValue).toBe(null);
|
1205
|
-
});
|
1206
|
-
|
1207
|
-
it('should handle empty nested groups', async () => {
|
1208
|
-
const emptyEncounter = {
|
1209
|
-
uuid: 'encounter-uuid',
|
1210
|
-
obs: [],
|
1211
|
-
};
|
1212
|
-
|
1213
|
-
const fields = createNestedFields();
|
1214
|
-
|
1215
|
-
const healthCenterField = fields.questions[0];
|
1216
|
-
const healthCenterValue = await ObsAdapter.getInitialValue(healthCenterField, emptyEncounter, formContext);
|
1217
|
-
expect(healthCenterValue).toBe('');
|
1218
|
-
|
1219
|
-
const commentField = fields.questions[1].questions[0];
|
1220
|
-
const commentValue = await ObsAdapter.getInitialValue(commentField, emptyEncounter, formContext);
|
1221
|
-
expect(commentValue).toBe('');
|
1222
|
-
});
|
1223
|
-
});
|
@@ -1,23 +1,23 @@
|
|
1
1
|
import dayjs from 'dayjs';
|
2
|
-
import {
|
2
|
+
import { ConceptTrue, codedTypes } from '../constants';
|
3
3
|
import {
|
4
|
-
type
|
5
|
-
type AttachmentResponse,
|
4
|
+
type OpenmrsObs,
|
6
5
|
type FormField,
|
7
|
-
type FormFieldValueAdapter,
|
8
6
|
type OpenmrsEncounter,
|
9
|
-
type
|
7
|
+
type AttachmentResponse,
|
8
|
+
type Attachment,
|
10
9
|
type ValueAndDisplay,
|
11
10
|
} from '../types';
|
12
11
|
import {
|
12
|
+
hasRendering,
|
13
|
+
gracefullySetSubmission,
|
13
14
|
clearSubmission,
|
14
15
|
flattenObsList,
|
15
|
-
formatDateAsDisplayString,
|
16
|
-
gracefullySetSubmission,
|
17
|
-
hasRendering,
|
18
16
|
parseToLocalDateTime,
|
17
|
+
formatDateAsDisplayString,
|
19
18
|
} from '../utils/common-utils';
|
20
19
|
import { type FormContextProps } from '../provider/form-provider';
|
20
|
+
import { type FormFieldValueAdapter } from '../types';
|
21
21
|
import { isEmpty } from '../validators/form-validator';
|
22
22
|
import { getAttachmentByUuid } from '../api';
|
23
23
|
import { formatDate, restBaseUrl } from '@openmrs/esm-framework';
|
@@ -60,45 +60,40 @@ export const ObsAdapter: FormFieldValueAdapter = {
|
|
60
60
|
if (isEmpty(value)) {
|
61
61
|
return value;
|
62
62
|
}
|
63
|
-
|
64
|
-
|
65
|
-
return value.map((val) => getDisplayValueForSingleObs(field, val)).join(', ');
|
63
|
+
if (value instanceof Date) {
|
64
|
+
return formatDateAsDisplayString(field, value);
|
66
65
|
}
|
67
|
-
|
68
|
-
|
66
|
+
if (rendering == 'checkbox') {
|
67
|
+
return value.map(
|
68
|
+
(selected) => field.questionOptions.answers?.find((option) => option.concept == selected)?.label,
|
69
|
+
);
|
70
|
+
}
|
71
|
+
if (rendering === 'toggle') {
|
72
|
+
return value ? field.questionOptions.toggleOptions.labelTrue : field.questionOptions.toggleOptions.labelFalse;
|
73
|
+
}
|
74
|
+
if (codedTypes.includes(rendering)) {
|
75
|
+
return field.questionOptions.answers?.find((option) => option.concept == value)?.label;
|
76
|
+
}
|
77
|
+
return value;
|
69
78
|
},
|
70
79
|
transformFieldValue: (field: FormField, value: any, context: FormContextProps) => {
|
71
80
|
// clear previous submission
|
72
81
|
clearSubmission(field);
|
73
|
-
|
74
82
|
if (!field.meta.previousValue && isEmpty(value)) {
|
75
83
|
return null;
|
76
84
|
}
|
77
|
-
|
78
|
-
// Handle `obsGroup` recursively
|
79
|
-
if (field.type === 'obsGroup' && Array.isArray(field.questions)) {
|
80
|
-
const groupObs = field.questions.map((nestedField) =>
|
81
|
-
ObsAdapter.transformFieldValue(nestedField, nestedField.value, context),
|
82
|
-
);
|
83
|
-
return gracefullySetSubmission(field, { groupMembers: groupObs }, undefined);
|
84
|
-
}
|
85
|
-
|
86
85
|
if (hasRendering(field, 'checkbox')) {
|
87
86
|
return handleMultiSelect(field, value);
|
88
87
|
}
|
89
|
-
|
90
88
|
if (!isEmpty(value) && hasPreviousObsValueChanged(field, value)) {
|
91
89
|
return gracefullySetSubmission(field, editObs(field, value), undefined);
|
92
90
|
}
|
93
|
-
|
94
91
|
if (field.meta.previousValue && isEmpty(value)) {
|
95
92
|
return gracefullySetSubmission(field, undefined, voidObs(field.meta.previousValue));
|
96
93
|
}
|
97
|
-
|
98
94
|
if (!isEmpty(value)) {
|
99
95
|
return gracefullySetSubmission(field, constructObs(field, value), undefined);
|
100
96
|
}
|
101
|
-
|
102
97
|
return null;
|
103
98
|
},
|
104
99
|
tearDown: function (): void {
|
@@ -150,46 +145,22 @@ function extractFieldValue(field: FormField, obsList: OpenmrsObs[] = [], makeFie
|
|
150
145
|
return '';
|
151
146
|
}
|
152
147
|
|
153
|
-
/**
|
154
|
-
* Extracts field's display value
|
155
|
-
*/
|
156
|
-
|
157
|
-
function getDisplayValueForSingleObs(field: FormField, value: any) {
|
158
|
-
const rendering = field.questionOptions.rendering;
|
159
|
-
|
160
|
-
if (value instanceof Date) {
|
161
|
-
return formatDateAsDisplayString(field, value);
|
162
|
-
}
|
163
|
-
if (rendering == 'checkbox') {
|
164
|
-
return value.map((selected) => field.questionOptions.answers?.find((option) => option.concept == selected)?.label);
|
165
|
-
}
|
166
|
-
if (rendering === 'toggle') {
|
167
|
-
return value ? field.questionOptions.toggleOptions.labelTrue : field.questionOptions.toggleOptions.labelFalse;
|
168
|
-
}
|
169
|
-
if (codedTypes.includes(rendering)) {
|
170
|
-
return field.questionOptions.answers?.find((option) => option.concept == value)?.label;
|
171
|
-
}
|
172
|
-
return value;
|
173
|
-
}
|
174
|
-
|
175
148
|
export function constructObs(field: FormField, value: any): Partial<OpenmrsObs> {
|
176
149
|
if (isEmpty(value) && field.type !== 'obsGroup') {
|
177
150
|
return null;
|
178
151
|
}
|
179
|
-
|
180
|
-
|
152
|
+
const draftObs =
|
153
|
+
field.type === 'obsGroup'
|
154
|
+
? { groupMembers: [] }
|
155
|
+
: {
|
156
|
+
value: field.questionOptions.rendering.startsWith('date') ? formatDateByPickerType(field, value) : value,
|
157
|
+
};
|
158
|
+
return {
|
159
|
+
...draftObs,
|
181
160
|
concept: field.questionOptions.concept,
|
182
161
|
formFieldNamespace: 'rfe-forms',
|
183
162
|
formFieldPath: `rfe-forms-${field.id}`,
|
184
163
|
};
|
185
|
-
|
186
|
-
if (field.type === 'obsGroup') {
|
187
|
-
draftObs.groupMembers = value?.map((v: any) => constructObs(field, v)) || [];
|
188
|
-
} else {
|
189
|
-
draftObs.value = field.questionOptions.rendering.startsWith('date') ? formatDateByPickerType(field, value) : value;
|
190
|
-
}
|
191
|
-
|
192
|
-
return draftObs;
|
193
164
|
}
|
194
165
|
|
195
166
|
export function voidObs(obs: OpenmrsObs) {
|
@@ -251,11 +222,6 @@ function handleMultiSelect(field: FormField, values: Array<string> = []) {
|
|
251
222
|
// 2. a mix of both (previous and current)
|
252
223
|
// 3. we only have a current value
|
253
224
|
|
254
|
-
// For `obsGroup` types, handle nested groups
|
255
|
-
if (field.type === 'obsGroup' && Array.isArray(field.questions)) {
|
256
|
-
return field.questions.map((nestedField) => handleMultiSelect(nestedField, nestedField.value));
|
257
|
-
}
|
258
|
-
|
259
225
|
if (field.meta.previousValue && isEmpty(values)) {
|
260
226
|
// we assume the user cleared the existing value(s)
|
261
227
|
// so we void all previous values
|
@@ -265,7 +231,6 @@ function handleMultiSelect(field: FormField, values: Array<string> = []) {
|
|
265
231
|
field.meta.previousValue.map((previousValue) => voidObs(previousValue)),
|
266
232
|
);
|
267
233
|
}
|
268
|
-
|
269
234
|
if (field.meta.previousValue && !isEmpty(values)) {
|
270
235
|
const toBeVoided = field.meta.previousValue.filter((obs) => !values.includes(obs.value.uuid));
|
271
236
|
const toBeCreated = values.filter((v) => !field.meta.previousValue.some((obs) => obs.value.uuid === v));
|
@@ -275,7 +240,6 @@ function handleMultiSelect(field: FormField, values: Array<string> = []) {
|
|
275
240
|
toBeVoided.map((obs) => voidObs(obs)),
|
276
241
|
);
|
277
242
|
}
|
278
|
-
|
279
243
|
return gracefullySetSubmission(
|
280
244
|
field,
|
281
245
|
values.map((value) => constructObs(field, value)),
|
@@ -1,54 +1,29 @@
|
|
1
|
-
import React
|
1
|
+
import React from 'react';
|
2
2
|
import classNames from 'classnames';
|
3
3
|
import { type FormFieldInputProps } from '../../types';
|
4
4
|
import styles from './obs-group.scss';
|
5
|
-
import { FormFieldRenderer
|
5
|
+
import { FormFieldRenderer } from '../renderer/field/form-field-renderer.component';
|
6
6
|
import { useFormProviderContext } from '../../provider/form-provider';
|
7
|
-
import { FormGroup } from '@carbon/react';
|
8
|
-
import { useTranslation } from 'react-i18next';
|
9
7
|
|
10
|
-
export const ObsGroup: React.FC<FormFieldInputProps> = ({ field
|
11
|
-
const { t } = useTranslation();
|
8
|
+
export const ObsGroup: React.FC<FormFieldInputProps> = ({ field }) => {
|
12
9
|
const { formFieldAdapters } = useFormProviderContext();
|
13
|
-
const showLabel = useMemo(() => field.questions?.length > 1, [field]);
|
14
10
|
|
15
|
-
const
|
16
|
-
() =>
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
11
|
+
const groupContent = field.questions
|
12
|
+
?.filter((child) => !child.isHidden)
|
13
|
+
.map((child, index) => {
|
14
|
+
const keyId = child.id + '_' + index;
|
15
|
+
if (formFieldAdapters[child.type]) {
|
16
|
+
return (
|
17
|
+
<div className={classNames(styles.flexColumn)} key={keyId}>
|
18
|
+
<div className={styles.groupContainer}>
|
19
|
+
<FormFieldRenderer fieldId={child.id} valueAdapter={formFieldAdapters[child.type]} />
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
);
|
23
|
+
}
|
24
|
+
});
|
21
25
|
|
22
|
-
|
23
|
-
return (
|
24
|
-
<div key={keyId} className={styles.nestedGroupContainer}>
|
25
|
-
<ObsGroup field={child} {...restProps} />
|
26
|
-
</div>
|
27
|
-
);
|
28
|
-
} else if (formFieldAdapters[child.type]) {
|
29
|
-
return (
|
30
|
-
<div className={classNames(styles.flexColumn)} key={keyId}>
|
31
|
-
<div className={styles.groupContainer}>
|
32
|
-
<FormFieldRenderer fieldId={child.id} valueAdapter={formFieldAdapters[child.type]} />
|
33
|
-
</div>
|
34
|
-
</div>
|
35
|
-
);
|
36
|
-
}
|
37
|
-
}),
|
38
|
-
[field],
|
39
|
-
);
|
40
|
-
|
41
|
-
return (
|
42
|
-
<div className={styles.groupContainer}>
|
43
|
-
{showLabel ? (
|
44
|
-
<FormGroup legendText={t(field.label)} className={styles.boldLegend}>
|
45
|
-
{content}
|
46
|
-
</FormGroup>
|
47
|
-
) : (
|
48
|
-
content
|
49
|
-
)}
|
50
|
-
</div>
|
51
|
-
);
|
26
|
+
return <div className={styles.flexRow}>{groupContent}</div>;
|
52
27
|
};
|
53
28
|
|
54
29
|
export default ObsGroup;
|
@@ -282,6 +282,6 @@ export function isUnspecifiedSupported(question: FormField) {
|
|
282
282
|
);
|
283
283
|
}
|
284
284
|
|
285
|
-
|
285
|
+
function isGroupField(rendering: RenderType) {
|
286
286
|
return rendering === 'group' || rendering === 'repeating';
|
287
287
|
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
2
|
+
import { FormGroup } from '@carbon/react';
|
2
3
|
import { useTranslation } from 'react-i18next';
|
3
4
|
import type { FormField, FormFieldInputProps, RenderType } from '../../types';
|
4
5
|
import { evaluateAsyncExpression, evaluateExpression } from '../../utils/expression-runner';
|
@@ -77,7 +78,6 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
|
|
77
78
|
});
|
78
79
|
}
|
79
80
|
}
|
80
|
-
|
81
81
|
const clonedField = cloneRepeatField(field, null, counter);
|
82
82
|
// run necessary expressions
|
83
83
|
if (clonedField.type === 'obsGroup') {
|
@@ -168,7 +168,15 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
|
|
168
168
|
|
169
169
|
return (
|
170
170
|
<React.Fragment>
|
171
|
-
|
171
|
+
{isGrouped ? (
|
172
|
+
<div className={styles.container}>
|
173
|
+
<FormGroup legendText={t(field.label)} className={styles.boldLegend}>
|
174
|
+
{nodes}
|
175
|
+
</FormGroup>
|
176
|
+
</div>
|
177
|
+
) : (
|
178
|
+
<div>{nodes}</div>
|
179
|
+
)}
|
172
180
|
</React.Fragment>
|
173
181
|
);
|
174
182
|
};
|
@@ -1,45 +1,35 @@
|
|
1
1
|
import { useMemo } from 'react';
|
2
|
-
import { type
|
2
|
+
import { type FormSchema, type FormField } from '../types';
|
3
3
|
|
4
4
|
export function useFormFields(form: FormSchema): { formFields: FormField[]; conceptReferences: Set<string> } {
|
5
5
|
const [flattenedFields, conceptReferences] = useMemo(() => {
|
6
|
-
const flattenedFieldsTemp
|
6
|
+
const flattenedFieldsTemp = [];
|
7
7
|
const conceptReferencesTemp = new Set<string>();
|
8
|
-
|
9
|
-
const flattenFields = (fields: FormField[]) => {
|
10
|
-
fields.forEach((field) => {
|
11
|
-
flattenedFieldsTemp.push(field);
|
12
|
-
|
13
|
-
// If the field is an obsGroup, we need to flatten its nested questions
|
14
|
-
if (field.type === 'obsGroup' && field.questions) {
|
15
|
-
field.questions.forEach((groupedField) => {
|
16
|
-
groupedField.meta.groupId = field.id;
|
17
|
-
flattenFields([groupedField]);
|
18
|
-
});
|
19
|
-
}
|
20
|
-
|
21
|
-
// Collect concept references
|
22
|
-
if (field.questionOptions?.concept) {
|
23
|
-
conceptReferencesTemp.add(field.questionOptions.concept);
|
24
|
-
}
|
25
|
-
if (field.questionOptions?.answers) {
|
26
|
-
field.questionOptions.answers.forEach((answer) => {
|
27
|
-
if (answer.concept) {
|
28
|
-
conceptReferencesTemp.add(answer.concept);
|
29
|
-
}
|
30
|
-
});
|
31
|
-
}
|
32
|
-
});
|
33
|
-
};
|
34
|
-
|
35
8
|
form.pages?.forEach((page) =>
|
36
9
|
page.sections?.forEach((section) => {
|
37
|
-
|
38
|
-
|
39
|
-
|
10
|
+
section.questions?.forEach((question) => {
|
11
|
+
flattenedFieldsTemp.push(question);
|
12
|
+
if (question.type == 'obsGroup') {
|
13
|
+
question.questions.forEach((groupedField) => {
|
14
|
+
groupedField.meta.groupId = question.id;
|
15
|
+
flattenedFieldsTemp.push(groupedField);
|
16
|
+
});
|
17
|
+
}
|
18
|
+
});
|
40
19
|
}),
|
41
20
|
);
|
42
|
-
|
21
|
+
flattenedFieldsTemp.forEach((field) => {
|
22
|
+
if (field.questionOptions?.concept) {
|
23
|
+
conceptReferencesTemp.add(field.questionOptions.concept);
|
24
|
+
}
|
25
|
+
if (field.questionOptions?.answers) {
|
26
|
+
field.questionOptions.answers.forEach((answer) => {
|
27
|
+
if (answer.concept) {
|
28
|
+
conceptReferencesTemp.add(answer.concept);
|
29
|
+
}
|
30
|
+
});
|
31
|
+
}
|
32
|
+
});
|
43
33
|
return [flattenedFieldsTemp, conceptReferencesTemp];
|
44
34
|
}, [form]);
|
45
35
|
|
@@ -100,7 +100,7 @@ export class EncounterFormProcessor extends FormProcessor {
|
|
100
100
|
field.meta.fixedValue = field.value;
|
101
101
|
delete field.value;
|
102
102
|
}
|
103
|
-
if (field.questionOptions?.rendering == 'group'
|
103
|
+
if (field.questionOptions?.rendering == 'group') {
|
104
104
|
field.questions?.forEach((child) => {
|
105
105
|
child.readonly = child.readonly ?? field.readonly;
|
106
106
|
return prepareFormField(child, section, page, schema);
|
@@ -1,16 +1,16 @@
|
|
1
1
|
import {
|
2
|
+
type PatientProgram,
|
2
3
|
type FormField,
|
3
|
-
type FormProcessorContextProps,
|
4
4
|
type OpenmrsEncounter,
|
5
5
|
type OpenmrsObs,
|
6
6
|
type PatientIdentifier,
|
7
|
-
type PatientProgram,
|
8
7
|
type PatientProgramPayload,
|
8
|
+
type FormProcessorContextProps,
|
9
9
|
} from '../../types';
|
10
10
|
import { saveAttachment, savePatientIdentifier, saveProgramEnrollment } from '../../api';
|
11
11
|
import { hasRendering, hasSubmission } from '../../utils/common-utils';
|
12
12
|
import dayjs from 'dayjs';
|
13
|
-
import {
|
13
|
+
import { voidObs, constructObs, assignedObsIds } from '../../adapters/obs-adapter';
|
14
14
|
import { type FormContextProps } from '../../provider/form-provider';
|
15
15
|
import { ConceptTrue } from '../../constants';
|
16
16
|
import { DefaultValueValidator } from '../../validators/default-value-validator';
|
@@ -185,53 +185,43 @@ export function getMutableSessionProps(context: FormContextProps) {
|
|
185
185
|
// Helpers
|
186
186
|
|
187
187
|
function prepareObs(obsForSubmission: OpenmrsObs[], fields: FormField[]) {
|
188
|
-
fields
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
addObsToList(obsGroup.groupMembers, nestedObsGroup);
|
226
|
-
} else if (hasSubmission(nestedField)) {
|
227
|
-
addObsToList(obsGroup.groupMembers, nestedField.meta.submission.newValue);
|
228
|
-
addObsToList(obsGroup.groupMembers, nestedField.meta.submission.voidedValue);
|
229
|
-
}
|
230
|
-
});
|
231
|
-
|
232
|
-
if (obsGroup.groupMembers?.length || obsGroup.voided) {
|
233
|
-
addObsToList(obsForSubmission, obsGroup);
|
234
|
-
}
|
188
|
+
fields
|
189
|
+
.filter((field) => hasSubmittableObs(field))
|
190
|
+
.forEach((field) => {
|
191
|
+
if ((field.isHidden || field.isParentHidden) && field.meta.previousValue) {
|
192
|
+
const valuesArray = Array.isArray(field.meta.previousValue)
|
193
|
+
? field.meta.previousValue
|
194
|
+
: [field.meta.previousValue];
|
195
|
+
addObsToList(
|
196
|
+
obsForSubmission,
|
197
|
+
valuesArray.map((obs) => voidObs(obs)),
|
198
|
+
);
|
199
|
+
return;
|
200
|
+
}
|
201
|
+
if (field.type == 'obsGroup') {
|
202
|
+
if (field.meta.submission?.voidedValue) {
|
203
|
+
addObsToList(obsForSubmission, field.meta.submission.voidedValue);
|
204
|
+
return;
|
205
|
+
}
|
206
|
+
const obsGroup = constructObs(field, null);
|
207
|
+
if (field.meta.previousValue) {
|
208
|
+
obsGroup.uuid = field.meta.previousValue.uuid;
|
209
|
+
}
|
210
|
+
field.questions.forEach((groupedField) => {
|
211
|
+
if (hasSubmission(groupedField)) {
|
212
|
+
addObsToList(obsGroup.groupMembers, groupedField.meta.submission.newValue);
|
213
|
+
addObsToList(obsGroup.groupMembers, groupedField.meta.submission.voidedValue);
|
214
|
+
}
|
215
|
+
});
|
216
|
+
if (obsGroup.groupMembers.length || obsGroup.voided) {
|
217
|
+
addObsToList(obsForSubmission, obsGroup);
|
218
|
+
}
|
219
|
+
}
|
220
|
+
if (hasSubmission(field)) {
|
221
|
+
addObsToList(obsForSubmission, field.meta.submission.newValue);
|
222
|
+
addObsToList(obsForSubmission, field.meta.submission.voidedValue);
|
223
|
+
}
|
224
|
+
});
|
235
225
|
}
|
236
226
|
|
237
227
|
function prepareOrders(fields: FormField[]) {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { type FormField, type
|
1
|
+
import { type FormField, type FormSchemaTransformer, type FormSchema, type RenderType } from '../types';
|
2
2
|
import { isTrue } from '../utils/boolean-utils';
|
3
3
|
import { hasRendering } from '../utils/common-utils';
|
4
4
|
|
@@ -39,13 +39,8 @@ function handleQuestion(question: FormField, form: FormSchema) {
|
|
39
39
|
setFieldValidators(question);
|
40
40
|
transformByType(question);
|
41
41
|
transformByRendering(question);
|
42
|
-
|
43
|
-
|
44
|
-
if (question.type === 'obsGroup' && question.questions.length) {
|
45
|
-
question.questions.forEach((nestedQuestion) => handleQuestion(nestedQuestion, form));
|
46
|
-
} else {
|
47
|
-
question.questions.forEach((nestedQuestion) => handleQuestion(nestedQuestion, form));
|
48
|
-
}
|
42
|
+
if (question?.questions?.length) {
|
43
|
+
question.questions.forEach((question) => handleQuestion(question, form));
|
49
44
|
}
|
50
45
|
} catch (error) {
|
51
46
|
console.error(error);
|