@kenyaemr/esm-patient-registration-app 4.4.4 → 4.5.0
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/144.js +1 -1
- package/dist/207.js +1 -1
- package/dist/207.js.map +1 -1
- package/dist/574.js +1 -1
- package/dist/59.js +1 -1
- package/dist/59.js.map +1 -1
- package/dist/68.js +1 -1
- package/dist/68.js.map +1 -1
- package/dist/821.js +1 -1
- package/dist/833.js +1 -0
- package/dist/876.js +1 -0
- package/dist/876.js.map +1 -0
- package/dist/{kenya-esm-patient-registration-app.js → kenyaemr-esm-patient-registration-app.js} +1 -1
- package/dist/{kenya-esm-patient-registration-app.js.buildmanifest.json → kenyaemr-esm-patient-registration-app.js.buildmanifest.json} +57 -35
- package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -0
- package/dist/{kenya-esm-patient-registration-app.old → kenyaemr-esm-patient-registration-app.old} +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/package.json +2 -2
- package/src/config-schema.ts +5 -13
- package/src/patient-registration/field/address/address-field.component.tsx +180 -20
- package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +68 -0
- package/src/patient-registration/field/address/address-hierarchy.resource.tsx +102 -0
- package/src/patient-registration/field/address/custom-address-field.component.tsx +30 -0
- package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +140 -0
- package/src/patient-registration/field/address/{address-hierarchy.test.tsx → tests/mocks.ts} +3 -80
- package/src/patient-registration/field/custom-field.component.tsx +1 -1
- package/src/patient-registration/field/field.component.tsx +2 -2
- package/src/patient-registration/field/name/name-field.component.tsx +1 -1
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +100 -62
- package/src/patient-registration/input/combo-input/selection-tick.component.tsx +20 -0
- package/src/patient-registration/input/input.scss +6 -1
- package/src/patient-registration/patient-registration-context.ts +1 -0
- package/src/patient-registration/patient-registration-hooks.ts +1 -0
- package/src/patient-registration/patient-registration-utils.ts +3 -3
- package/src/patient-registration/patient-registration.component.tsx +1 -0
- package/src/patient-registration/patient-registration.scss +14 -0
- package/src/patient-registration/validation/patient-registration-validation.test.tsx +128 -114
- package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +1 -0
- package/src/root.component.tsx +1 -1
- package/translations/en.json +3 -5
- package/translations/he.json +89 -0
- package/dist/822.js +0 -1
- package/dist/822.js.map +0 -1
- package/dist/kenya-esm-patient-registration-app.js.map +0 -1
- package/src/patient-registration/field/address/address-hierarchy.component.tsx +0 -143
- package/src/patient-registration/input/combo-input/combo-input.test.tsx +0 -43
- /package/src/{declarations.d.tsx → declarations.d.ts} +0 -0
|
@@ -1,76 +1,114 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { performAdressHierarchyWithParentSearch } from '../../../resource';
|
|
1
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { TextInput, Layer } from '@carbon/react';
|
|
3
|
+
import SelectionTick from './selection-tick.component';
|
|
4
|
+
import styles from '../input.scss';
|
|
6
5
|
|
|
7
|
-
interface ComboInputProps
|
|
6
|
+
interface ComboInputProps {
|
|
7
|
+
entries: Array<string>;
|
|
8
8
|
name: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
fieldProps: {
|
|
10
|
+
value: string;
|
|
11
|
+
labelText: string;
|
|
12
|
+
[x: string]: any;
|
|
13
|
+
};
|
|
14
|
+
handleInputChange: (newValue: string) => void;
|
|
15
|
+
handleSelection: (newSelection) => void;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
18
|
+
const ComboInput: React.FC<ComboInputProps> = ({ entries, fieldProps, handleInputChange, handleSelection }) => {
|
|
19
|
+
const [highlightedEntry, setHighlightedEntry] = useState(-1);
|
|
20
|
+
const { value = '' } = fieldProps;
|
|
21
|
+
const [showEntries, setShowEntries] = useState(false);
|
|
22
|
+
const comboInputRef = useRef(null);
|
|
23
|
+
|
|
24
|
+
const handleFocus = useCallback(() => {
|
|
25
|
+
setShowEntries(true);
|
|
26
|
+
setHighlightedEntry(-1);
|
|
27
|
+
}, [setShowEntries, setHighlightedEntry]);
|
|
28
|
+
|
|
29
|
+
const handleBlur = useCallback(
|
|
30
|
+
(e) => {
|
|
31
|
+
// This check is in place to not hide the entries when an entry is clicked
|
|
32
|
+
// Else the onClick of the entry will not be counted.
|
|
33
|
+
if (!comboInputRef?.current?.contains(e.target)) {
|
|
34
|
+
setShowEntries(false);
|
|
35
|
+
}
|
|
36
|
+
setHighlightedEntry(-1);
|
|
37
|
+
},
|
|
38
|
+
[setShowEntries, setHighlightedEntry],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const filteredEntries = useMemo(() => {
|
|
42
|
+
if (!entries) {
|
|
43
|
+
return [];
|
|
42
44
|
}
|
|
43
|
-
|
|
45
|
+
if (!value) {
|
|
46
|
+
return entries;
|
|
47
|
+
}
|
|
48
|
+
return entries.filter((entry) => entry.toLowerCase().includes(value.toLowerCase()));
|
|
49
|
+
}, [entries, value]);
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
const handleOptionClick = useCallback(
|
|
52
|
+
(newSelection: string, e: KeyboardEvent = null) => {
|
|
53
|
+
e?.preventDefault();
|
|
54
|
+
handleSelection(newSelection);
|
|
55
|
+
setShowEntries(false);
|
|
56
|
+
},
|
|
57
|
+
[handleSelection, setShowEntries],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const handleKeyPress = useCallback(
|
|
61
|
+
(e: KeyboardEvent) => {
|
|
62
|
+
const totalResults = filteredEntries.length ?? 0;
|
|
63
|
+
if (e.key === 'ArrowUp') {
|
|
64
|
+
setHighlightedEntry((prev) => Math.max(-1, prev - 1));
|
|
65
|
+
} else if (e.key === 'ArrowDown') {
|
|
66
|
+
setHighlightedEntry((prev) => Math.min(totalResults - 1, prev + 1));
|
|
67
|
+
} else if (e.key === 'Enter' && highlightedEntry > -1) {
|
|
68
|
+
handleOptionClick(filteredEntries[highlightedEntry], e);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
[highlightedEntry, handleOptionClick, filteredEntries, setHighlightedEntry],
|
|
72
|
+
);
|
|
48
73
|
|
|
49
74
|
return (
|
|
50
|
-
<div
|
|
75
|
+
<div className={styles.comboInput} ref={comboInputRef}>
|
|
51
76
|
<Layer>
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
itemToString={(item) => item?.text ?? ''}
|
|
60
|
-
{...field}
|
|
61
|
-
onChange={(e) => {
|
|
62
|
-
if (Boolean(e.selectedItem)) {
|
|
63
|
-
setSelectedValue(e.selectedItem.id);
|
|
64
|
-
setValue(e.selectedItem.text);
|
|
65
|
-
} else {
|
|
66
|
-
setSelectedValue(undefined);
|
|
67
|
-
setValue(undefined);
|
|
68
|
-
}
|
|
69
|
-
}}
|
|
70
|
-
placeholder={placeholder}
|
|
71
|
-
titleText={comboLabelText}
|
|
77
|
+
<TextInput
|
|
78
|
+
{...fieldProps}
|
|
79
|
+
onChange={(e) => handleInputChange(e.target.value)}
|
|
80
|
+
onFocus={handleFocus}
|
|
81
|
+
onBlur={handleBlur}
|
|
82
|
+
autoComplete={'off'}
|
|
83
|
+
onKeyDown={handleKeyPress}
|
|
72
84
|
/>
|
|
73
85
|
</Layer>
|
|
86
|
+
<div className={styles.comboInputEntries}>
|
|
87
|
+
{showEntries && (
|
|
88
|
+
<div id="downshift-1-menu" style={{ height: filteredEntries.length > 5 && "15rem" }} className="cds--list-box__menu" role="listbox">
|
|
89
|
+
{filteredEntries.map((entry, indx) => (
|
|
90
|
+
<div
|
|
91
|
+
key={indx}
|
|
92
|
+
id="downshift-1-item-0"
|
|
93
|
+
role="option"
|
|
94
|
+
className={`cds--list-box__menu-item ${indx === highlightedEntry && 'cds--list-box__menu-item--highlighted'
|
|
95
|
+
}`}
|
|
96
|
+
tabIndex={-1}
|
|
97
|
+
aria-selected="true"
|
|
98
|
+
onClick={() => handleOptionClick(entry)}>
|
|
99
|
+
<div
|
|
100
|
+
className={`cds--list-box__menu-item__option ${entry === value && 'cds--list-box__menu-item--active'
|
|
101
|
+
}`}>
|
|
102
|
+
{entry}
|
|
103
|
+
{entry === value && <SelectionTick />}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
74
110
|
</div>
|
|
75
111
|
);
|
|
76
112
|
};
|
|
113
|
+
|
|
114
|
+
export default ComboInput;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
function SelectionTick() {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
focusable="false"
|
|
7
|
+
preserveAspectRatio="xMidYMid meet"
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
width="16"
|
|
11
|
+
height="16"
|
|
12
|
+
viewBox="0 0 32 32"
|
|
13
|
+
aria-hidden="true"
|
|
14
|
+
className="cds--list-box__menu-item__selected-icon">
|
|
15
|
+
<path d="M13 24L4 15 5.414 13.586 13 21.171 26.586 7.586 28 9 13 24z"></path>
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default SelectionTick;
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
color: $text-02;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
.textID
|
|
21
|
+
.textID,
|
|
22
|
+
.comboInput {
|
|
22
23
|
margin-bottom: spacing.$spacing-05;
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -106,3 +107,7 @@
|
|
|
106
107
|
@include type.type-style('label-01');
|
|
107
108
|
color: $danger;
|
|
108
109
|
}
|
|
110
|
+
|
|
111
|
+
.comboInputEntries {
|
|
112
|
+
position: relative;
|
|
113
|
+
}
|
|
@@ -14,6 +14,7 @@ export interface PatientRegistrationContextProps {
|
|
|
14
14
|
currentPhoto: string;
|
|
15
15
|
isOffline: boolean;
|
|
16
16
|
initialFormValues: FormValues;
|
|
17
|
+
setInitialFormValues?: React.Dispatch<SetStateAction<FormValues>>;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export const PatientRegistrationContext = createContext<PatientRegistrationContextProps | undefined>(undefined);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { navigate } from '@openmrs/esm-framework';
|
|
2
1
|
import * as Yup from 'yup';
|
|
3
2
|
import {
|
|
4
3
|
AddressValidationSchemaType,
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
PatientIdentifierValue,
|
|
9
8
|
Encounter,
|
|
10
9
|
} from './patient-registration-types';
|
|
10
|
+
import { parseDate } from '@openmrs/esm-framework';
|
|
11
11
|
import camelCase from 'lodash-es/camelCase';
|
|
12
12
|
import capitalize from 'lodash-es/capitalize';
|
|
13
13
|
|
|
@@ -103,7 +103,7 @@ export function scrollIntoView(viewId: string) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export function cancelRegistration() {
|
|
106
|
-
|
|
106
|
+
window.history.back();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
export function getFormValuesFromFhirPatient(patient: fhir.Patient) {
|
|
@@ -124,7 +124,7 @@ export function getFormValuesFromFhirPatient(patient: fhir.Patient) {
|
|
|
124
124
|
result.additionalFamilyName = additionalPatientName?.family;
|
|
125
125
|
|
|
126
126
|
result.gender = capitalize(patient.gender);
|
|
127
|
-
result.birthdate = patient.birthDate ? (
|
|
127
|
+
result.birthdate = patient.birthDate ? parseDate(patient.birthDate) : undefined;
|
|
128
128
|
result.telephoneNumber = patient.telecom ? patient.telecom[0].value : '';
|
|
129
129
|
|
|
130
130
|
if (patient.deceasedBoolean || patient.deceasedDateTime) {
|
|
@@ -225,6 +225,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
225
225
|
currentPhoto: photo?.imageSrc,
|
|
226
226
|
isOffline,
|
|
227
227
|
initialFormValues: props.initialValues,
|
|
228
|
+
setInitialFormValues,
|
|
228
229
|
}}>
|
|
229
230
|
<PatientVerification props={props} />
|
|
230
231
|
{sections.map((section, index) => (
|
|
@@ -92,3 +92,17 @@
|
|
|
92
92
|
max-width: unset;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
// Overriding styles for RTL support
|
|
97
|
+
html[dir='rtl'] {
|
|
98
|
+
.linkName {
|
|
99
|
+
& > svg {
|
|
100
|
+
transform: scale(-1, 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.infoGrid {
|
|
105
|
+
padding-left: unset;
|
|
106
|
+
padding-right: spacing.$spacing-07;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -1,129 +1,143 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render, fireEvent, screen } from '@testing-library/react';
|
|
3
|
-
import userEvent from '@testing-library/user-event';
|
|
4
|
-
import { Formik, Form } from 'formik';
|
|
5
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
6
1
|
import { validationSchema } from './patient-registration-validation';
|
|
7
|
-
import { NameField } from '../field/name/name-field.component';
|
|
8
|
-
import { PatientRegistrationContext } from '../patient-registration-context';
|
|
9
|
-
import { initialFormValues } from '../patient-registration.component';
|
|
10
|
-
import { FormValues } from '../patient-registration-types';
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
describe('Patient Registration Validation', () => {
|
|
4
|
+
describe('validationSchema', () => {
|
|
5
|
+
const validFormValues = {
|
|
6
|
+
givenName: 'John',
|
|
7
|
+
familyName: 'Doe',
|
|
8
|
+
additionalGivenName: '',
|
|
9
|
+
additionalFamilyName: '',
|
|
10
|
+
gender: 'Male',
|
|
11
|
+
birthdate: new Date('1990-01-01'),
|
|
12
|
+
birthdateEstimated: false,
|
|
13
|
+
deathDate: null,
|
|
14
|
+
email: 'john.doe@example.com',
|
|
15
|
+
identifiers: {
|
|
16
|
+
nationalId: {
|
|
17
|
+
required: true,
|
|
18
|
+
identifierValue: '123456789',
|
|
19
|
+
},
|
|
20
|
+
passportId: {
|
|
21
|
+
required: false,
|
|
22
|
+
identifierValue: '',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
const validateFormValues = async (formValues) => {
|
|
28
|
+
try {
|
|
29
|
+
await validationSchema.validate(formValues, { abortEarly: false });
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return err;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
});
|
|
35
|
+
it('should allow valid form values', async () => {
|
|
36
|
+
const validationError = await validateFormValues(validFormValues);
|
|
37
|
+
expect(validationError).toBeFalsy();
|
|
38
|
+
});
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
it('should require givenName', async () => {
|
|
41
|
+
const invalidFormValues = {
|
|
42
|
+
...validFormValues,
|
|
43
|
+
givenName: '',
|
|
44
|
+
};
|
|
45
|
+
const validationError = await validateFormValues(invalidFormValues);
|
|
46
|
+
expect(validationError.errors).toContain('givenNameRequired');
|
|
47
|
+
});
|
|
33
48
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
async () => {
|
|
43
|
-
const error = await updateNameAndReturnError(givenNameValue, middleNameValue, familyNameValue);
|
|
44
|
-
Object.values(error).map((currentError) => expect(currentError).toBeNull());
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
};
|
|
49
|
+
it('should require familyName', async () => {
|
|
50
|
+
const invalidFormValues = {
|
|
51
|
+
...validFormValues,
|
|
52
|
+
familyName: '',
|
|
53
|
+
};
|
|
54
|
+
const validationError = await validateFormValues(invalidFormValues);
|
|
55
|
+
expect(validationError.errors).toContain('familyNameRequired');
|
|
56
|
+
});
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
givenNameValue +
|
|
59
|
-
', middleNameValue: ' +
|
|
60
|
-
middleNameValue +
|
|
61
|
-
', familyNameValue: ' +
|
|
62
|
-
familyNameValue,
|
|
63
|
-
async () => {
|
|
64
|
-
const error = (await updateNameAndReturnError(givenNameValue, middleNameValue, familyNameValue))[errorType];
|
|
65
|
-
expect(error.textContent).toEqual(expectedError);
|
|
66
|
-
},
|
|
67
|
-
);
|
|
68
|
-
};
|
|
58
|
+
it('should require additionalGivenName when addNameInLocalLanguage is true', async () => {
|
|
59
|
+
const invalidFormValues = {
|
|
60
|
+
...validFormValues,
|
|
61
|
+
addNameInLocalLanguage: true,
|
|
62
|
+
additionalGivenName: '',
|
|
63
|
+
};
|
|
64
|
+
const validationError = await validateFormValues(invalidFormValues);
|
|
65
|
+
expect(validationError.errors).toContain('givenNameRequired');
|
|
66
|
+
});
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
it('should require additionalFamilyName when addNameInLocalLanguage is true', async () => {
|
|
69
|
+
const invalidFormValues = {
|
|
70
|
+
...validFormValues,
|
|
71
|
+
addNameInLocalLanguage: true,
|
|
72
|
+
additionalFamilyName: '',
|
|
73
|
+
};
|
|
74
|
+
const validationError = await validateFormValues(invalidFormValues);
|
|
75
|
+
expect(validationError.errors).toContain('familyNameRequired');
|
|
76
|
+
});
|
|
72
77
|
|
|
73
|
-
|
|
78
|
+
it('should require gender', async () => {
|
|
79
|
+
const invalidFormValues = {
|
|
80
|
+
...validFormValues,
|
|
81
|
+
gender: '',
|
|
82
|
+
};
|
|
83
|
+
const validationError = await validateFormValues(invalidFormValues);
|
|
84
|
+
expect(validationError.errors).toContain('genderUnspecified');
|
|
85
|
+
});
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
validationSchema={validationSchema}>
|
|
84
|
-
<Form>
|
|
85
|
-
<PatientRegistrationContext.Provider
|
|
86
|
-
value={{
|
|
87
|
-
initialFormValues: null,
|
|
88
|
-
identifierTypes: [],
|
|
89
|
-
validationSchema,
|
|
90
|
-
setValidationSchema: () => {},
|
|
91
|
-
values: formValues,
|
|
92
|
-
inEditMode: false,
|
|
93
|
-
setFieldValue: () => null,
|
|
94
|
-
currentPhoto: 'TEST',
|
|
95
|
-
isOffline: true,
|
|
96
|
-
setCapturePhotoProps: (value) => {},
|
|
97
|
-
}}>
|
|
98
|
-
<NameField />
|
|
99
|
-
</PatientRegistrationContext.Provider>
|
|
100
|
-
</Form>
|
|
101
|
-
</Formik>,
|
|
102
|
-
);
|
|
103
|
-
const givenNameInput = screen.getByLabelText('First Name') as HTMLInputElement;
|
|
104
|
-
const middleNameInput = screen.getByLabelText(/Middle Name/i) as HTMLInputElement;
|
|
105
|
-
const familyNameInput = screen.getByLabelText('Family Name') as HTMLInputElement;
|
|
87
|
+
it('should allow Male as a valid gender', async () => {
|
|
88
|
+
const validFormValuesWithOtherGender = {
|
|
89
|
+
...validFormValues,
|
|
90
|
+
gender: 'Male',
|
|
91
|
+
};
|
|
92
|
+
const validationError = await validateFormValues(validFormValuesWithOtherGender);
|
|
93
|
+
expect(validationError).toBeFalsy();
|
|
94
|
+
});
|
|
106
95
|
|
|
107
|
-
|
|
96
|
+
it('should allow Female as a valid gender', async () => {
|
|
97
|
+
const validFormValuesWithOtherGender = {
|
|
98
|
+
...validFormValues,
|
|
99
|
+
gender: 'Female',
|
|
100
|
+
};
|
|
101
|
+
const validationError = await validateFormValues(validFormValuesWithOtherGender);
|
|
102
|
+
expect(validationError).toBeFalsy();
|
|
103
|
+
});
|
|
108
104
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
it('should throw error when date of birth is a future date', async () => {
|
|
106
|
+
const invalidFormValues = {
|
|
107
|
+
...validFormValues,
|
|
108
|
+
birthdate: new Date('2100-01-01'),
|
|
109
|
+
};
|
|
110
|
+
const validationError = await validateFormValues(invalidFormValues);
|
|
111
|
+
expect(validationError.errors).toContain('birthdayNotInTheFuture');
|
|
112
|
+
});
|
|
115
113
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
114
|
+
it('should require yearsEstimated when birthdateEstimated is true', async () => {
|
|
115
|
+
const invalidFormValues = {
|
|
116
|
+
...validFormValues,
|
|
117
|
+
birthdateEstimated: true,
|
|
118
|
+
};
|
|
119
|
+
const validationError = await validateFormValues(invalidFormValues);
|
|
120
|
+
expect(validationError.errors).toContain('yearsEstimateRequired');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should throw error when monthEstimated is negative', async () => {
|
|
124
|
+
const invalidFormValues = {
|
|
125
|
+
...validFormValues,
|
|
126
|
+
birthdateEstimated: true,
|
|
127
|
+
yearsEstimated: 0,
|
|
128
|
+
monthsEstimated: -1,
|
|
129
|
+
};
|
|
130
|
+
const validationError = await validateFormValues(invalidFormValues);
|
|
131
|
+
expect(validationError.errors).toContain('negativeMonths');
|
|
132
|
+
});
|
|
122
133
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
it('should throw error when deathDate is in future', async () => {
|
|
135
|
+
const invalidFormValues = {
|
|
136
|
+
...validFormValues,
|
|
137
|
+
deathDate: new Date('2100-01-01'),
|
|
138
|
+
};
|
|
139
|
+
const validationError = await validateFormValues(invalidFormValues);
|
|
140
|
+
expect(validationError.errors).toContain('deathdayNotInTheFuture');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
129
143
|
});
|
|
@@ -53,6 +53,7 @@ const ConfirmPrompt: React.FC<ConfirmPromptProps> = ({ close, onConfirm, patient
|
|
|
53
53
|
<PatientInfo label={t('age', 'Age')} value={age(patient?.dateOfBirth)} />
|
|
54
54
|
<PatientInfo label={t('dateOfBirth', 'Date of birth')} value={formatDate(new Date(patient?.dateOfBirth))} />
|
|
55
55
|
<PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.gender)} />
|
|
56
|
+
<PatientInfo label={t('nascopNumber', 'Nascop facility no')} value={capitalize(patient?.nascopCCCNumber)} />
|
|
56
57
|
</div>
|
|
57
58
|
</div>
|
|
58
59
|
</div>
|
package/src/root.component.tsx
CHANGED
|
@@ -34,7 +34,7 @@ export default function Root({ savePatientForm, isOffline }: RootProps) {
|
|
|
34
34
|
<main className={`omrs-main-content ${styles.root}`}>
|
|
35
35
|
<Grid className={styles.grid}>
|
|
36
36
|
<Row>
|
|
37
|
-
<ExtensionSlot
|
|
37
|
+
<ExtensionSlot name="breadcrumbs-slot" />
|
|
38
38
|
</Row>
|
|
39
39
|
<ResourcesContext.Provider
|
|
40
40
|
value={{
|
package/translations/en.json
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"editIdentifierTooltip": "Edit",
|
|
37
37
|
"editPatientDetails": "Edit patient details",
|
|
38
38
|
"error": "Error",
|
|
39
|
+
"errorFetchingOrderedFields": "Error occured fetching ordered fields for address hierarchy",
|
|
39
40
|
"estimatedAgeInMonthsLabelText": "Estimated age in months",
|
|
40
41
|
"estimatedAgeInYearsLabelText": "Estimated age in years",
|
|
41
42
|
"familyNameLabelText": "Family Name",
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
"loadingResults": "Loading results",
|
|
60
61
|
"male": "Male",
|
|
61
62
|
"middleNameLabelText": "Middle Name",
|
|
62
|
-
"
|
|
63
|
+
"nascopNumber": "Nascop facility no",
|
|
63
64
|
"nationalId": "National ID",
|
|
64
65
|
"negativeMonths": "Negative months",
|
|
65
66
|
"negativeYears": "Negative years",
|
|
@@ -73,8 +74,6 @@
|
|
|
73
74
|
"patientName": "Patient name",
|
|
74
75
|
"patientNameKnown": "Patient's Name is Known?",
|
|
75
76
|
"patientNotFound": "The patient records could not be found in Client registry, do you want to continue to create and post patient to registry",
|
|
76
|
-
"phoneEmailLabelText": "Phone, Email, etc.",
|
|
77
|
-
"phoneNumberInputLabelText": "Phone number",
|
|
78
77
|
"postalCode": "Postcode",
|
|
79
78
|
"postToRegistry": "Post to registry",
|
|
80
79
|
"registerPatient": "Register Patient",
|
|
@@ -90,20 +89,19 @@
|
|
|
90
89
|
"resetIdentifierTooltip": "Reset",
|
|
91
90
|
"restoreRelationshipActionButton": "Undo",
|
|
92
91
|
"searchAddress": "Search address",
|
|
92
|
+
"selectAnOption": "Select an option",
|
|
93
93
|
"selectCountry": "Select country",
|
|
94
94
|
"selectIdentifierType": "Select identifier type",
|
|
95
95
|
"sexFieldLabelText": "Sex",
|
|
96
96
|
"stateProvince": "State",
|
|
97
97
|
"stroke": "Stroke",
|
|
98
98
|
"unableToFetch": "Unable to fetch person attribute type {personattributetype}",
|
|
99
|
-
"unidentifiedPatient": "Unidentified Patient",
|
|
100
99
|
"unknown": "Unknown",
|
|
101
100
|
"updatePatient": "Update Patient",
|
|
102
101
|
"updationSuccessToastDescription": "The patient's information has been successfully updated",
|
|
103
102
|
"updationSuccessToastTitle": "Patient Details Updated",
|
|
104
103
|
"useValues": "Use values",
|
|
105
104
|
"validate": "Validate",
|
|
106
|
-
"years": "Years",
|
|
107
105
|
"yearsEstimateRequired": "Years estimate required",
|
|
108
106
|
"yes": "Yes"
|
|
109
107
|
}
|