@kenyaemr/esm-patient-registration-app 6.0.2 → 7.0.2-pre.65
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/.turbo/turbo-build.log +38 -0
- package/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/152.js +1 -1
- package/dist/152.js.map +1 -1
- package/dist/169.js +1 -0
- package/dist/169.js.map +1 -0
- package/dist/207.js +1 -0
- package/dist/{537.js.map → 207.js.map} +1 -1
- package/dist/255.js +1 -1
- package/dist/255.js.map +1 -1
- package/dist/266.js +2 -0
- package/dist/266.js.LICENSE.txt +14 -0
- package/dist/266.js.map +1 -0
- package/dist/303.js +1 -1
- package/dist/303.js.map +1 -1
- package/dist/330.js +1 -1
- package/dist/501.js +1 -0
- package/dist/501.js.map +1 -0
- package/dist/548.js +1 -0
- package/dist/548.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/59.js +1 -1
- package/dist/59.js.map +1 -1
- package/dist/729.js +1 -1
- package/dist/729.js.map +1 -1
- package/dist/735.js +1 -1
- package/dist/753.js +1 -0
- package/dist/753.js.map +1 -0
- package/dist/879.js +1 -1
- package/dist/879.js.map +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +136 -112
- package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +0 -15
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +5 -4
- package/src/config-schema.ts +6 -0
- package/src/offline.resources.ts +7 -5
- package/src/patient-registration/field/custom-field.component.tsx +6 -0
- package/src/patient-registration/field/dob/dob.component.tsx +17 -14
- package/src/patient-registration/field/dob/dob.test.tsx +1 -0
- package/src/patient-registration/field/field.test.tsx +1 -0
- package/src/patient-registration/field/obs/obs-field.component.tsx +64 -2
- package/src/patient-registration/field/obs/obs-field.test.tsx +44 -2
- package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx +56 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +21 -7
- package/src/patient-registration/form-manager.ts +7 -5
- package/src/patient-registration/patient-registration.component.tsx +5 -4
- package/src/patient-registration/patient-registration.resource.ts +1 -1
- package/src/patient-registration/patient-registration.scss +16 -13
- package/src/patient-registration/patient-registration.test.tsx +6 -6
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +1 -0
- package/translations/en.json +5 -5
- package/dist/481.js +0 -1
- package/dist/481.js.map +0 -1
- package/dist/537.js +0 -1
- package/dist/676.js +0 -1
- package/dist/676.js.map +0 -1
- package/dist/762.js +0 -2
- package/dist/762.js.LICENSE.txt +0 -29
- package/dist/762.js.map +0 -1
- package/dist/816.js +0 -1
- package/dist/816.js.map +0 -1
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true},{"component":"cancelPatientEditModal","name":"cancel-patient-edit-modal","online":true,"offline":true},{"component":"patientPhotoExtension","name":"patient-photo-widget","slot":"patient-photo-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-actions-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-search-actions-slot","online":true,"offline":true},{"component":"deleteIdentifierConfirmationModal","name":"delete-identifier-confirmation-modal","online":true,"offline":true},{"component":"emptyClientRegistryModal","name":"empty-client-registry-modal","online":true,"offline":true},{"component":"confirmClientRegistryModal","name":"confirm-client-registry-modal","online":true,"offline":true}],"version":"
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true},{"component":"cancelPatientEditModal","name":"cancel-patient-edit-modal","online":true,"offline":true},{"component":"patientPhotoExtension","name":"patient-photo-widget","slot":"patient-photo-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-actions-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-search-actions-slot","online":true,"offline":true},{"component":"deleteIdentifierConfirmationModal","name":"delete-identifier-confirmation-modal","online":true,"offline":true},{"component":"emptyClientRegistryModal","name":"empty-client-registry-modal","online":true,"offline":true},{"component":"confirmClientRegistryModal","name":"confirm-client-registry-modal","online":true,"offline":true}],"version":"7.0.2-pre.65"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-patient-registration-app",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.2-pre.65",
|
|
4
4
|
"description": "Patient registration microfrontend for the OpenMRS SPA",
|
|
5
5
|
"browser": "dist/kenyaemr-esm-patient-registration-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
|
|
19
19
|
"coverage": "yarn test --coverage",
|
|
20
20
|
"typescript": "tsc",
|
|
21
|
-
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/index.ts'"
|
|
21
|
+
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
|
|
22
22
|
},
|
|
23
23
|
"browserslist": [
|
|
24
24
|
"extends browserslist-config-openmrs"
|
|
@@ -53,5 +53,6 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"webpack": "^5.74.0"
|
|
56
|
-
}
|
|
57
|
-
|
|
56
|
+
},
|
|
57
|
+
"stableVersion": "7.0.1"
|
|
58
|
+
}
|
package/src/config-schema.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface FieldDefinition {
|
|
|
12
12
|
label?: string;
|
|
13
13
|
uuid: string;
|
|
14
14
|
placeholder?: string;
|
|
15
|
+
dateFormat?: string;
|
|
15
16
|
showHeading: boolean;
|
|
16
17
|
validation?: {
|
|
17
18
|
required: boolean;
|
|
@@ -19,6 +20,11 @@ export interface FieldDefinition {
|
|
|
19
20
|
};
|
|
20
21
|
answerConceptSetUuid?: string;
|
|
21
22
|
customConceptAnswers?: Array<CustomConceptAnswer>;
|
|
23
|
+
showWhenExpression?: {
|
|
24
|
+
field: string;
|
|
25
|
+
value: string;
|
|
26
|
+
};
|
|
27
|
+
renderType?: string;
|
|
22
28
|
}
|
|
23
29
|
export interface CustomConceptAnswer {
|
|
24
30
|
uuid: string;
|
package/src/offline.resources.ts
CHANGED
|
@@ -75,13 +75,15 @@ export async function fetchPatientIdentifierTypesWithSources(): Promise<Array<Pa
|
|
|
75
75
|
// @ts-ignore Reason: The required props of the type are generated below.
|
|
76
76
|
const identifierTypes: Array<PatientIdentifierType> = patientIdentifierTypes.filter(Boolean);
|
|
77
77
|
|
|
78
|
-
const [autoGenOptions,
|
|
78
|
+
const [autoGenOptions, identifierSourcesResponse] = await Promise.all([
|
|
79
79
|
fetchAutoGenerationOptions(),
|
|
80
|
-
|
|
80
|
+
fetchIdentifierSources(),
|
|
81
81
|
]);
|
|
82
82
|
|
|
83
|
+
const allIdentifierSources = identifierSourcesResponse.data.results;
|
|
84
|
+
|
|
83
85
|
for (let i = 0; i < identifierTypes?.length; i++) {
|
|
84
|
-
identifierTypes[i].identifierSources = allIdentifierSources
|
|
86
|
+
identifierTypes[i].identifierSources = allIdentifierSources.map((source) => {
|
|
85
87
|
const option = find(autoGenOptions.data.results, { source: { uuid: source.uuid } });
|
|
86
88
|
source.autoGenerationOption = option;
|
|
87
89
|
return source;
|
|
@@ -118,8 +120,8 @@ async function fetchPatientIdentifierTypes(): Promise<Array<FetchedPatientIdenti
|
|
|
118
120
|
return [];
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
async function fetchIdentifierSources(
|
|
122
|
-
return await cacheAndFetch(`${restBaseUrl}/idgen/identifiersource?v=default
|
|
123
|
+
async function fetchIdentifierSources() {
|
|
124
|
+
return await cacheAndFetch(`${restBaseUrl}/idgen/identifiersource?v=default`);
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
async function fetchAutoGenerationOptions(abortController?: AbortController) {
|
|
@@ -4,6 +4,7 @@ import { type RegistrationConfig } from '../../config-schema';
|
|
|
4
4
|
import { AddressField } from './address/custom-address-field.component';
|
|
5
5
|
import { ObsField } from './obs/obs-field.component';
|
|
6
6
|
import { PersonAttributeField } from './person-attributes/person-attribute-field.component';
|
|
7
|
+
import { useField } from 'formik';
|
|
7
8
|
|
|
8
9
|
export interface CustomFieldProps {
|
|
9
10
|
name: string;
|
|
@@ -13,6 +14,11 @@ export function CustomField({ name }: CustomFieldProps) {
|
|
|
13
14
|
const config = useConfig() as RegistrationConfig;
|
|
14
15
|
const fieldDefinition = config.fieldDefinitions.filter((def) => def.id == name)[0];
|
|
15
16
|
|
|
17
|
+
const [{ value }] = useField(`attributes.${fieldDefinition.showWhenExpression?.field}`);
|
|
18
|
+
if (fieldDefinition.showWhenExpression && value !== fieldDefinition.showWhenExpression.value) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
if (fieldDefinition.type === 'person attribute') {
|
|
17
23
|
return <PersonAttributeField fieldDefinition={fieldDefinition} />;
|
|
18
24
|
} else if (fieldDefinition.type === 'obs') {
|
|
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
4
4
|
import { useField } from 'formik';
|
|
5
5
|
import { generateFormatting } from '../../date-util';
|
|
6
6
|
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
7
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
7
|
+
import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework';
|
|
8
8
|
import { type RegistrationConfig } from '../../../config-schema';
|
|
9
9
|
import styles from '../field.scss';
|
|
10
10
|
|
|
@@ -46,8 +46,8 @@ export const DobField: React.FC = () => {
|
|
|
46
46
|
);
|
|
47
47
|
|
|
48
48
|
const onDateChange = useCallback(
|
|
49
|
-
(birthdate: Date
|
|
50
|
-
setFieldValue('birthdate', birthdate
|
|
49
|
+
(birthdate: Date) => {
|
|
50
|
+
setFieldValue('birthdate', birthdate);
|
|
51
51
|
},
|
|
52
52
|
[setFieldValue],
|
|
53
53
|
);
|
|
@@ -101,17 +101,20 @@ export const DobField: React.FC = () => {
|
|
|
101
101
|
<Layer>
|
|
102
102
|
{!dobUnknown ? (
|
|
103
103
|
<div className={styles.dobField}>
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
<OpenmrsDatePicker
|
|
105
|
+
id="birthdate"
|
|
106
|
+
{...birthdate}
|
|
107
|
+
dateFormat={dateFormat}
|
|
108
|
+
onChange={onDateChange}
|
|
109
|
+
maxDate={today}
|
|
110
|
+
labelText={t('dateOfBirthLabelText', 'Date of Birth')}
|
|
111
|
+
invalid={!!(birthdateMeta.touched && birthdateMeta.error)}
|
|
112
|
+
invalidText={birthdateMeta.error && t(birthdateMeta.error)}
|
|
113
|
+
value={birthdate.value}
|
|
114
|
+
carbonOptions={{
|
|
115
|
+
placeholder: placeHolder,
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
115
118
|
</div>
|
|
116
119
|
) : (
|
|
117
120
|
<div className={styles.grid}>
|
|
@@ -10,6 +10,7 @@ import { PatientRegistrationContext } from '../patient-registration-context';
|
|
|
10
10
|
jest.mock('@openmrs/esm-framework', () => ({
|
|
11
11
|
...jest.requireActual('@openmrs/esm-framework'),
|
|
12
12
|
useConfig: jest.fn(),
|
|
13
|
+
getLocale: jest.fn().mockReturnValue('en'),
|
|
13
14
|
}));
|
|
14
15
|
|
|
15
16
|
const predefinedAddressTemplate = {
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
1
|
+
import React, { useCallback, useContext, useMemo } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { Field } from 'formik';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { InlineNotification, Layer, Select, SelectItem } from '@carbon/react';
|
|
6
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
6
|
+
import { OpenmrsDatePicker, parseDate, useConfig } from '@openmrs/esm-framework';
|
|
7
7
|
import { type ConceptResponse } from '../../patient-registration.types';
|
|
8
8
|
import { type FieldDefinition, type RegistrationConfig } from '../../../config-schema';
|
|
9
9
|
import { Input } from '../../input/basic-input/input/input.component';
|
|
10
10
|
import { useConcept, useConceptAnswers } from '../field.resource';
|
|
11
11
|
import styles from './../field.scss';
|
|
12
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
13
|
+
import { generateFormatting } from '../../date-util';
|
|
12
14
|
|
|
13
15
|
export interface ObsFieldProps {
|
|
14
16
|
fieldDefinition: FieldDefinition;
|
|
@@ -51,6 +53,16 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) {
|
|
|
51
53
|
required={fieldDefinition.validation.required}
|
|
52
54
|
/>
|
|
53
55
|
);
|
|
56
|
+
case 'Date':
|
|
57
|
+
return (
|
|
58
|
+
<DateObsField
|
|
59
|
+
concept={concept}
|
|
60
|
+
label={fieldDefinition.label}
|
|
61
|
+
required={fieldDefinition.validation.required}
|
|
62
|
+
dateFormat={fieldDefinition.dateFormat}
|
|
63
|
+
placeholder={fieldDefinition.placeholder}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
54
66
|
case 'Coded':
|
|
55
67
|
return (
|
|
56
68
|
<CodedObsField
|
|
@@ -145,6 +157,56 @@ function NumericObsField({ concept, label, required }: NumericObsFieldProps) {
|
|
|
145
157
|
);
|
|
146
158
|
}
|
|
147
159
|
|
|
160
|
+
interface DateObsFieldProps {
|
|
161
|
+
concept: ConceptResponse;
|
|
162
|
+
label: string;
|
|
163
|
+
required?: boolean;
|
|
164
|
+
dateFormat?: string;
|
|
165
|
+
placeholder?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function DateObsField({ concept, label, required, placeholder }: DateObsFieldProps) {
|
|
169
|
+
const { t } = useTranslation();
|
|
170
|
+
const fieldName = `obs.${concept.uuid}`;
|
|
171
|
+
const { setFieldValue } = useContext(PatientRegistrationContext);
|
|
172
|
+
const { format, placeHolder, dateFormat } = generateFormatting(['d', 'm', 'Y'], '/');
|
|
173
|
+
|
|
174
|
+
const onDateChange = useCallback(
|
|
175
|
+
(date: Date) => {
|
|
176
|
+
setFieldValue(fieldName, date);
|
|
177
|
+
},
|
|
178
|
+
[setFieldValue],
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<Layer>
|
|
183
|
+
<div className={styles.dobField}>
|
|
184
|
+
<Field name={fieldName}>
|
|
185
|
+
{({ field, form: { touched, errors }, meta }) => {
|
|
186
|
+
const dateValue = field.value ? parseDate(field.value) : field.value;
|
|
187
|
+
return (
|
|
188
|
+
<OpenmrsDatePicker
|
|
189
|
+
id={fieldName}
|
|
190
|
+
{...field}
|
|
191
|
+
required={required}
|
|
192
|
+
dateFormat={dateFormat}
|
|
193
|
+
onChange={onDateChange}
|
|
194
|
+
labelText={label ?? concept.display}
|
|
195
|
+
invalid={errors[fieldName] && touched[fieldName]}
|
|
196
|
+
invalidText={meta.error && t(meta.error)}
|
|
197
|
+
value={dateValue}
|
|
198
|
+
carbonOptions={{
|
|
199
|
+
placeholder: placeholder ?? placeHolder,
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
);
|
|
203
|
+
}}
|
|
204
|
+
</Field>
|
|
205
|
+
</div>
|
|
206
|
+
</Layer>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
148
210
|
interface CodedObsFieldProps {
|
|
149
211
|
concept: ConceptResponse;
|
|
150
212
|
answerConceptSetUuid?: string;
|
|
@@ -5,6 +5,7 @@ import { useConfig } from '@openmrs/esm-framework';
|
|
|
5
5
|
import { type FieldDefinition } from '../../../config-schema';
|
|
6
6
|
import { useConcept, useConceptAnswers } from '../field.resource';
|
|
7
7
|
import { ObsField } from './obs-field.component';
|
|
8
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
8
9
|
|
|
9
10
|
const mockUseConfig = useConfig as jest.Mock;
|
|
10
11
|
|
|
@@ -42,6 +43,14 @@ const useConceptMockImpl = (uuid: string) => {
|
|
|
42
43
|
],
|
|
43
44
|
setMembers: [],
|
|
44
45
|
};
|
|
46
|
+
} else if (uuid == 'vaccination-date-uuid') {
|
|
47
|
+
data = {
|
|
48
|
+
uuid: 'vaccination-date-uuid',
|
|
49
|
+
display: 'Vaccination Date',
|
|
50
|
+
datatype: { display: 'Date', uuid: 'date' },
|
|
51
|
+
answers: [],
|
|
52
|
+
setMembers: [],
|
|
53
|
+
};
|
|
45
54
|
} else {
|
|
46
55
|
throw Error(`Programming error, you probably didn't mean to do this: unknown concept uuid '${uuid}'`);
|
|
47
56
|
}
|
|
@@ -79,12 +88,14 @@ const useConceptAnswersMockImpl = (uuid: string) => {
|
|
|
79
88
|
};
|
|
80
89
|
|
|
81
90
|
type FieldProps = {
|
|
82
|
-
children: ({ field, form: { touched, errors } }) => React.ReactNode;
|
|
91
|
+
children: ({ field, form: { touched, errors }, meta }) => React.ReactNode;
|
|
83
92
|
};
|
|
84
93
|
|
|
85
94
|
jest.mock('formik', () => ({
|
|
86
95
|
...(jest.requireActual('formik') as object),
|
|
87
|
-
Field: jest.fn(({ children }: FieldProps) =>
|
|
96
|
+
Field: jest.fn(({ children }: FieldProps) => (
|
|
97
|
+
<>{children({ field: {}, form: { touched: {}, errors: {} }, meta: { error: undefined } })}</>
|
|
98
|
+
)),
|
|
88
99
|
useField: jest.fn(() => [{ value: null }, {}]),
|
|
89
100
|
}));
|
|
90
101
|
|
|
@@ -118,6 +129,21 @@ const numberFieldDef: FieldDefinition = {
|
|
|
118
129
|
customConceptAnswers: [],
|
|
119
130
|
};
|
|
120
131
|
|
|
132
|
+
const dateFieldDef: FieldDefinition = {
|
|
133
|
+
id: 'vac_date',
|
|
134
|
+
type: 'obs',
|
|
135
|
+
label: '',
|
|
136
|
+
placeholder: '',
|
|
137
|
+
showHeading: false,
|
|
138
|
+
uuid: 'vaccination-date-uuid',
|
|
139
|
+
validation: {
|
|
140
|
+
required: false,
|
|
141
|
+
matches: null,
|
|
142
|
+
},
|
|
143
|
+
answerConceptSetUuid: null,
|
|
144
|
+
customConceptAnswers: [],
|
|
145
|
+
};
|
|
146
|
+
|
|
121
147
|
const codedFieldDef: FieldDefinition = {
|
|
122
148
|
id: 'nationality',
|
|
123
149
|
type: 'obs',
|
|
@@ -163,6 +189,22 @@ describe('ObsField', () => {
|
|
|
163
189
|
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
164
190
|
});
|
|
165
191
|
|
|
192
|
+
it('renders a datepicker for date concept', async () => {
|
|
193
|
+
render(
|
|
194
|
+
<PatientRegistrationContext.Provider value={{ setFieldValue: jest.fn() }}>
|
|
195
|
+
<ObsField fieldDefinition={dateFieldDef} />
|
|
196
|
+
</PatientRegistrationContext.Provider>,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
200
|
+
|
|
201
|
+
const datePickerInput = screen.getByPlaceholderText('dd/mm/yyyy');
|
|
202
|
+
expect(datePickerInput).toBeInTheDocument();
|
|
203
|
+
|
|
204
|
+
await userEvent.type(datePickerInput, '28/05/2024');
|
|
205
|
+
expect(datePickerInput).toHaveValue('28/05/2024');
|
|
206
|
+
});
|
|
207
|
+
|
|
166
208
|
it('renders a select for a coded concept', () => {
|
|
167
209
|
render(<ObsField fieldDefinition={codedFieldDef} />);
|
|
168
210
|
// expect(screen.getByLabelText("Nationality")).toBeInTheDocument();
|
package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Field } from 'formik';
|
|
3
|
+
import { Layer, Select, SelectItem } from '@carbon/react';
|
|
4
|
+
import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import styles from './../field.scss';
|
|
7
|
+
import classNames from 'classnames';
|
|
8
|
+
|
|
9
|
+
type CustomPersonAttributeFieldProps = {
|
|
10
|
+
id: string;
|
|
11
|
+
personAttributeType: PersonAttributeTypeResponse;
|
|
12
|
+
answerConceptSetUuid: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
customConceptAnswers: Array<{ uuid: string; label?: string }>;
|
|
15
|
+
required: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const CustomPersonAttributeField: React.FC<CustomPersonAttributeFieldProps> = ({
|
|
19
|
+
personAttributeType,
|
|
20
|
+
required,
|
|
21
|
+
id,
|
|
22
|
+
label,
|
|
23
|
+
customConceptAnswers,
|
|
24
|
+
}) => {
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
const fieldName = `attributes.${personAttributeType.uuid}`;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
|
|
30
|
+
<Layer>
|
|
31
|
+
<Field name={fieldName}>
|
|
32
|
+
{({ field, form: { touched, errors }, meta }) => {
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
<Select
|
|
36
|
+
id={id}
|
|
37
|
+
name={`person-attribute-${personAttributeType.uuid}`}
|
|
38
|
+
labelText={label ?? personAttributeType?.display}
|
|
39
|
+
invalid={errors[fieldName] && touched[fieldName]}
|
|
40
|
+
required={required}
|
|
41
|
+
{...field}>
|
|
42
|
+
<SelectItem value={''} text={t('selectAnOption', 'Select an option')} />
|
|
43
|
+
{customConceptAnswers.map((answer) => (
|
|
44
|
+
<SelectItem key={answer.uuid} value={answer.uuid} text={answer.uuid} />
|
|
45
|
+
))}
|
|
46
|
+
</Select>
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
}}
|
|
50
|
+
</Field>
|
|
51
|
+
</Layer>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default CustomPersonAttributeField;
|
package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { usePersonAttributeType } from './person-attributes.resource';
|
|
|
6
6
|
import { TextPersonAttributeField } from './text-person-attribute-field.component';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
import styles from '../field.scss';
|
|
9
|
+
import CustomPersonAttributeField from './custom-person-attribute-field.component';
|
|
9
10
|
|
|
10
11
|
export interface PersonAttributeFieldProps {
|
|
11
12
|
fieldDefinition: FieldDefinition;
|
|
@@ -22,13 +23,26 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
|
|
|
22
23
|
switch (personAttributeType.format) {
|
|
23
24
|
case 'java.lang.String':
|
|
24
25
|
return (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
<>
|
|
27
|
+
{fieldDefinition.renderType === 'select' ? (
|
|
28
|
+
<CustomPersonAttributeField
|
|
29
|
+
personAttributeType={personAttributeType}
|
|
30
|
+
answerConceptSetUuid={fieldDefinition.answerConceptSetUuid}
|
|
31
|
+
label={fieldDefinition.label}
|
|
32
|
+
id={fieldDefinition?.id}
|
|
33
|
+
customConceptAnswers={fieldDefinition.customConceptAnswers ?? []}
|
|
34
|
+
required={fieldDefinition.validation?.required ?? false}
|
|
35
|
+
/>
|
|
36
|
+
) : (
|
|
37
|
+
<TextPersonAttributeField
|
|
38
|
+
personAttributeType={personAttributeType}
|
|
39
|
+
validationRegex={fieldDefinition.validation?.matches ?? ''}
|
|
40
|
+
label={fieldDefinition.label}
|
|
41
|
+
required={fieldDefinition.validation?.required ?? false}
|
|
42
|
+
id={fieldDefinition?.id}
|
|
43
|
+
/>
|
|
44
|
+
)}
|
|
45
|
+
</>
|
|
32
46
|
);
|
|
33
47
|
case 'org.openmrs.Concept':
|
|
34
48
|
return (
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type FetchResponse,
|
|
3
|
+
type Session,
|
|
4
|
+
type StyleguideConfigObject,
|
|
5
|
+
getConfig,
|
|
3
6
|
openmrsFetch,
|
|
4
7
|
queueSynchronizationItem,
|
|
5
|
-
type Session,
|
|
6
8
|
restBaseUrl,
|
|
7
|
-
getConfig,
|
|
8
9
|
} from '@openmrs/esm-framework';
|
|
9
10
|
import { patientRegistration } from '../constants';
|
|
10
11
|
import {
|
|
@@ -131,14 +132,15 @@ export class FormManager {
|
|
|
131
132
|
|
|
132
133
|
await this.saveObservations(values.obs, savePatientResponse, currentLocation, currentUser, config);
|
|
133
134
|
|
|
134
|
-
const {
|
|
135
|
-
|
|
135
|
+
const { patientPhotoConceptUuid } = await getConfig<StyleguideConfigObject>('@openmrs/esm-styleguide');
|
|
136
|
+
|
|
137
|
+
if (patientPhotoConceptUuid && capturePhotoProps?.imageData) {
|
|
136
138
|
await savePatientPhoto(
|
|
137
139
|
savePatientResponse.data.uuid,
|
|
138
140
|
capturePhotoProps.imageData,
|
|
139
141
|
`${restBaseUrl}/obs`,
|
|
140
142
|
capturePhotoProps.dateTime || new Date().toISOString(),
|
|
141
|
-
|
|
143
|
+
patientPhotoConceptUuid,
|
|
142
144
|
);
|
|
143
145
|
}
|
|
144
146
|
}
|
|
@@ -195,7 +195,9 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
195
195
|
<div>
|
|
196
196
|
<div className={styles.stickyColumn}>
|
|
197
197
|
<h4>
|
|
198
|
-
{inEditMode
|
|
198
|
+
{inEditMode
|
|
199
|
+
? t('editPatientDetails', 'Edit patient details')
|
|
200
|
+
: t('createNewPatient', 'Create new patient')}
|
|
199
201
|
</h4>
|
|
200
202
|
{showDummyData && <DummyDataInput setValues={props.setValues} />}
|
|
201
203
|
<p className={styles.label01}>{t('jumpTo', 'Jump to')}</p>
|
|
@@ -231,12 +233,11 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
231
233
|
className={styles.spinner}
|
|
232
234
|
description={`${t('submitting', 'Submitting')} ...`}
|
|
233
235
|
iconDescription="submitting"
|
|
234
|
-
status="active"
|
|
235
236
|
/>
|
|
236
237
|
) : inEditMode ? (
|
|
237
|
-
t('updatePatient', 'Update
|
|
238
|
+
t('updatePatient', 'Update patient')
|
|
238
239
|
) : (
|
|
239
|
-
t('registerPatient', 'Register
|
|
240
|
+
t('registerPatient', 'Register patient')
|
|
240
241
|
)}
|
|
241
242
|
</Button>
|
|
242
243
|
<Button className={styles.cancelButton} kind="tertiary" onClick={cancelRegistration}>
|
|
@@ -5,7 +5,7 @@ export const uuidIdentifier = '05a29f94-c0ed-11e2-94be-8c13b969e334';
|
|
|
5
5
|
export const uuidTelephoneNumber = '14d4f066-15f5-102d-96e4-000c29c2a5d7';
|
|
6
6
|
|
|
7
7
|
function dataURItoFile(dataURI: string) {
|
|
8
|
-
const byteString = atob(dataURI.split(',')[1]);
|
|
8
|
+
const byteString = window.atob(dataURI.split(',')[1]);
|
|
9
9
|
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
|
|
10
10
|
// write the bytes of the string to a typed array
|
|
11
11
|
const buffer = new Uint8Array(byteString.length);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
@use '@carbon/
|
|
2
|
-
@use '@carbon/
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@carbon/type';
|
|
3
3
|
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
4
|
|
|
5
5
|
.title {
|
|
@@ -19,23 +19,23 @@
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
.submitButton {
|
|
22
|
-
margin-bottom:
|
|
22
|
+
margin-bottom: layout.$spacing-05;
|
|
23
23
|
width: 11.688rem;
|
|
24
24
|
display: block;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.infoGrid {
|
|
28
28
|
width: 100%;
|
|
29
|
-
padding-left:
|
|
29
|
+
padding-left: layout.$spacing-07;
|
|
30
30
|
margin-bottom: 40vh;
|
|
31
|
-
margin-top:
|
|
31
|
+
margin-top: layout.$spacing-05;
|
|
32
32
|
max-width: 50rem;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.label01 {
|
|
36
36
|
@include type.type-style('label-01');
|
|
37
|
-
margin-top:
|
|
38
|
-
margin-bottom:
|
|
37
|
+
margin-top: layout.$spacing-05;
|
|
38
|
+
margin-bottom: layout.$spacing-05;
|
|
39
39
|
color: $ui-04;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
.space05 {
|
|
49
|
-
margin:
|
|
49
|
+
margin: layout.$spacing-05 0;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
.formContainer {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
|
|
57
57
|
.stickyColumn {
|
|
58
58
|
position: sticky;
|
|
59
|
-
margin-top:
|
|
59
|
+
margin-top: layout.$spacing-05;
|
|
60
60
|
// 3rem for the nav height and 1rem for top margin
|
|
61
61
|
top: 4rem;
|
|
62
62
|
}
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
.linkName {
|
|
69
69
|
color: $color-gray-70;
|
|
70
70
|
line-height: 1.38;
|
|
71
|
-
font-size:
|
|
71
|
+
font-size: layout.$spacing-05;
|
|
72
72
|
font-weight: 600;
|
|
73
73
|
|
|
74
74
|
&:active {
|
|
@@ -94,8 +94,11 @@
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
.spinner {
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
min-height: 1rem;
|
|
98
|
+
width: max-content;
|
|
99
|
+
|
|
100
|
+
:global(.cds--inline-loading__text) {
|
|
101
|
+
@include type.type-style('body-01');
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
@@ -109,6 +112,6 @@ html[dir='rtl'] {
|
|
|
109
112
|
|
|
110
113
|
.infoGrid {
|
|
111
114
|
padding-left: unset;
|
|
112
|
-
padding-right:
|
|
115
|
+
padding-right: layout.$spacing-07;
|
|
113
116
|
}
|
|
114
117
|
}
|
|
@@ -264,7 +264,7 @@ describe('Registering a new patient', () => {
|
|
|
264
264
|
});
|
|
265
265
|
|
|
266
266
|
await fillRequiredFields();
|
|
267
|
-
await user.click(await screen.findByText(
|
|
267
|
+
await user.click(await screen.findByText(/Register Patient/i));
|
|
268
268
|
expect(mockedSavePatient).toHaveBeenCalledWith(
|
|
269
269
|
expect.objectContaining({
|
|
270
270
|
identifiers: [], //TODO when the identifer story is finished: { identifier: '', identifierType: '05a29f94-c0ed-11e2-94be-8c13b969e334', location: '' },
|
|
@@ -293,7 +293,7 @@ describe('Registering a new patient', () => {
|
|
|
293
293
|
const givenNameInput = (await screen.findByLabelText('First Name')) as HTMLInputElement;
|
|
294
294
|
|
|
295
295
|
await user.type(givenNameInput, '5');
|
|
296
|
-
await user.click(screen.getByText(
|
|
296
|
+
await user.click(screen.getByText(/Register Patient/i));
|
|
297
297
|
|
|
298
298
|
expect(mockedSavePatientForm).not.toHaveBeenCalled();
|
|
299
299
|
});
|
|
@@ -317,7 +317,7 @@ describe('Registering a new patient', () => {
|
|
|
317
317
|
const nationality = within(customSection).getByLabelText('Nationality');
|
|
318
318
|
await user.selectOptions(nationality, 'USA');
|
|
319
319
|
|
|
320
|
-
await user.click(screen.getByText(
|
|
320
|
+
await user.click(screen.getByText(/Register Patient/i));
|
|
321
321
|
|
|
322
322
|
expect(mockedSavePatient).toHaveBeenCalled();
|
|
323
323
|
|
|
@@ -350,7 +350,7 @@ describe('Registering a new patient', () => {
|
|
|
350
350
|
|
|
351
351
|
mockedSaveEncounter.mockRejectedValue({ status: 400, responseBody: { error: { message: 'an error message' } } });
|
|
352
352
|
|
|
353
|
-
const registerPatientButton = screen.getByText(
|
|
353
|
+
const registerPatientButton = screen.getByText(/Register Patient/i);
|
|
354
354
|
|
|
355
355
|
await user.click(registerPatientButton);
|
|
356
356
|
|
|
@@ -405,7 +405,7 @@ describe('Updating an existing patient record', () => {
|
|
|
405
405
|
expect(givenNameInput.value).toBe('John');
|
|
406
406
|
expect(familyNameInput.value).toBe('Wilson');
|
|
407
407
|
expect(middleNameInput.value).toBeFalsy();
|
|
408
|
-
expect(dateOfBirthInput.value).toBe('
|
|
408
|
+
expect(dateOfBirthInput.value).toBe('04/04/1972');
|
|
409
409
|
expect(genderInput.value).toBe('male');
|
|
410
410
|
|
|
411
411
|
// do some edits
|
|
@@ -415,7 +415,7 @@ describe('Updating an existing patient record', () => {
|
|
|
415
415
|
await user.type(givenNameInput, 'Eric');
|
|
416
416
|
await user.type(middleNameInput, 'Johnson');
|
|
417
417
|
await user.type(familyNameInput, 'Smith');
|
|
418
|
-
await user.click(screen.getByText(
|
|
418
|
+
await user.click(screen.getByText(/Update patient/i));
|
|
419
419
|
|
|
420
420
|
expect(mockedSavePatient).toHaveBeenCalledWith(
|
|
421
421
|
false,
|