@kenyaemr/esm-patient-registration-app 4.3.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/README.md +7 -0
- package/__mocks__/autogenerationoptions.mock.ts +34 -0
- package/__mocks__/react-i18next.js +49 -0
- package/dist/144.js +2 -0
- package/dist/144.js.LICENSE.txt +27 -0
- package/dist/144.js.map +1 -0
- package/dist/207.js +1 -0
- package/dist/207.js.map +1 -0
- package/dist/317.js +2 -0
- package/dist/317.js.LICENSE.txt +6 -0
- package/dist/317.js.map +1 -0
- package/dist/330.js +1 -0
- package/dist/330.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/59.js +1 -0
- package/dist/59.js.map +1 -0
- package/dist/591.js +2 -0
- package/dist/591.js.LICENSE.txt +32 -0
- package/dist/591.js.map +1 -0
- package/dist/62.js +1 -0
- package/dist/62.js.map +1 -0
- package/dist/635.js +1 -0
- package/dist/635.js.map +1 -0
- package/dist/68.js +1 -0
- package/dist/68.js.map +1 -0
- package/dist/735.js +1 -0
- package/dist/735.js.map +1 -0
- package/dist/757.js +1 -0
- package/dist/784.js +2 -0
- package/dist/784.js.LICENSE.txt +9 -0
- package/dist/784.js.map +1 -0
- package/dist/805.js +1 -0
- package/dist/805.js.map +1 -0
- package/dist/807.js +1 -0
- package/dist/821.js +1 -0
- package/dist/821.js.map +1 -0
- package/dist/822.js +1 -0
- package/dist/822.js.map +1 -0
- package/dist/858.js +2 -0
- package/dist/858.js.LICENSE.txt +3 -0
- package/dist/858.js.map +1 -0
- package/dist/887.js +1 -0
- package/dist/887.js.map +1 -0
- package/dist/9.js +2 -0
- package/dist/9.js.LICENSE.txt +9 -0
- package/dist/9.js.map +1 -0
- package/dist/975.js +1 -0
- package/dist/975.js.map +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +9 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-patient-registration-app.js +1 -0
- package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +623 -0
- package/dist/openmrs-esm-patient-registration-app.js.map +1 -0
- package/dist/openmrs-esm-patient-registration-app.old +1 -0
- package/docs/images/patient-registration-hierarchy.png +0 -0
- package/package.json +55 -0
- package/src/add-patient-link.scss +3 -0
- package/src/add-patient-link.tsx +21 -0
- package/src/config-schema.ts +405 -0
- package/src/constants.ts +14 -0
- package/src/declarations.d.tsx +4 -0
- package/src/index.ts +131 -0
- package/src/nav-link.tsx +10 -0
- package/src/offline.resources.ts +109 -0
- package/src/offline.ts +90 -0
- package/src/patient-registration/before-save-prompt.tsx +72 -0
- package/src/patient-registration/date-util.ts +52 -0
- package/src/patient-registration/field/__mocks__/field.resource.ts +60 -0
- package/src/patient-registration/field/address/address-field.component.tsx +31 -0
- package/src/patient-registration/field/address/address-hierarchy.component.tsx +143 -0
- package/src/patient-registration/field/address/address-hierarchy.test.tsx +181 -0
- package/src/patient-registration/field/address/address-search.component.tsx +98 -0
- package/src/patient-registration/field/address/address-search.scss +53 -0
- package/src/patient-registration/field/custom-field.component.tsx +25 -0
- package/src/patient-registration/field/dob/dob.component.tsx +143 -0
- package/src/patient-registration/field/dob/dob.test.tsx +73 -0
- package/src/patient-registration/field/field.component.tsx +44 -0
- package/src/patient-registration/field/field.resource.ts +35 -0
- package/src/patient-registration/field/field.scss +127 -0
- package/src/patient-registration/field/gender/gender-field.component.tsx +49 -0
- package/src/patient-registration/field/gender/gender-field.test.tsx +66 -0
- package/src/patient-registration/field/id/id-field.component.tsx +142 -0
- package/src/patient-registration/field/id/identifier-selection-overlay.tsx +194 -0
- package/src/patient-registration/field/id/identifier-selection.scss +37 -0
- package/src/patient-registration/field/name/name-field.component.tsx +109 -0
- package/src/patient-registration/field/obs/obs-field.component.tsx +185 -0
- package/src/patient-registration/field/obs/obs-field.test.tsx +127 -0
- package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +59 -0
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +68 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +81 -0
- package/src/patient-registration/field/person-attributes/person-attributes.resource.tsx +20 -0
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +57 -0
- package/src/patient-registration/form-manager.test.ts +68 -0
- package/src/patient-registration/form-manager.ts +413 -0
- package/src/patient-registration/input/basic-input/input/input.component.tsx +59 -0
- package/src/patient-registration/input/basic-input/input/input.test.tsx +170 -0
- package/src/patient-registration/input/basic-input/select/select-input.component.tsx +32 -0
- package/src/patient-registration/input/basic-input/select/select-input.test.tsx +32 -0
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +76 -0
- package/src/patient-registration/input/combo-input/combo-input.test.tsx +43 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +84 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.scss +53 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +109 -0
- package/src/patient-registration/input/custom-input/estimated-age/estimated-age-input.component.tsx +32 -0
- package/src/patient-registration/input/custom-input/estimated-age/estimated-age-input.test.tsx +36 -0
- package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +156 -0
- package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +110 -0
- package/src/patient-registration/input/custom-input/identifier/utils.ts +19 -0
- package/src/patient-registration/input/custom-input/unidentified-patient/unidentified-patient-input.component.tsx +24 -0
- package/src/patient-registration/input/custom-input/unidentified-patient/unidentified-patient-input.test.tsx +39 -0
- package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +53 -0
- package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +43 -0
- package/src/patient-registration/input/input.scss +108 -0
- package/src/patient-registration/patient-registration-context.ts +24 -0
- package/src/patient-registration/patient-registration-hooks.ts +320 -0
- package/src/patient-registration/patient-registration-types.tsx +271 -0
- package/src/patient-registration/patient-registration-utils.ts +219 -0
- package/src/patient-registration/patient-registration.component.tsx +250 -0
- package/src/patient-registration/patient-registration.resource.test.tsx +26 -0
- package/src/patient-registration/patient-registration.resource.tsx +296 -0
- package/src/patient-registration/patient-registration.scss +94 -0
- package/src/patient-registration/patient-registration.test.tsx +436 -0
- package/src/patient-registration/section/death-info/death-info-section.component.tsx +30 -0
- package/src/patient-registration/section/death-info/death-info-section.test.tsx +73 -0
- package/src/patient-registration/section/demographics/demographics-section.component.tsx +30 -0
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +84 -0
- package/src/patient-registration/section/generic-section.component.tsx +17 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +226 -0
- package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +78 -0
- package/src/patient-registration/section/patient-relationships/relationships.scss +35 -0
- package/src/patient-registration/section/section-wrapper.component.tsx +40 -0
- package/src/patient-registration/section/section.component.tsx +23 -0
- package/src/patient-registration/section/section.scss +1 -0
- package/src/patient-registration/ui-components/overlay/index.tsx +51 -0
- package/src/patient-registration/ui-components/overlay/overlay.scss +63 -0
- package/src/patient-registration/validation/patient-registration-validation.test.tsx +129 -0
- package/src/patient-registration/validation/patient-registration-validation.tsx +46 -0
- package/src/patient-verification/assets/counties.json +236 -0
- package/src/patient-verification/assets/verification-assets.ts +11 -0
- package/src/patient-verification/patient-verification-hook.tsx +156 -0
- package/src/patient-verification/patient-verification-utils.ts +173 -0
- package/src/patient-verification/patient-verification.component.tsx +118 -0
- package/src/patient-verification/patient-verification.scss +30 -0
- package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +69 -0
- package/src/patient-verification/verification-modal/empty-prompt.component.tsx +35 -0
- package/src/patient-verification/verification-types.ts +50 -0
- package/src/resource.ts +12 -0
- package/src/root.component.tsx +66 -0
- package/src/root.scss +7 -0
- package/src/root.test.tsx +32 -0
- package/src/widgets/cancel-patient-edit.component.tsx +37 -0
- package/src/widgets/delete-identifier-confirmation-modal.tsx +41 -0
- package/src/widgets/delete-identifier-modal.scss +34 -0
- package/src/widgets/display-photo.component.tsx +30 -0
- package/src/widgets/edit-patient-details-button.component.tsx +34 -0
- package/src/widgets/edit-patient-details-button.scss +3 -0
- package/translations/en.json +108 -0
- package/translations/fr.json +89 -0
- package/translations/km.json +89 -0
- package/tsconfig.json +5 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Tile, ComboBox, Layer, Button, Search, InlineLoading, InlineNotification } from '@carbon/react';
|
|
4
|
+
import styles from './patient-verification.scss';
|
|
5
|
+
import { countries, verificationIdentifierTypes } from './assets/verification-assets';
|
|
6
|
+
import { searchClientRegistry, useGlobalProperties } from './patient-verification-hook';
|
|
7
|
+
import { showToast } from '@openmrs/esm-framework';
|
|
8
|
+
import { handleClientRegistryResponse } from './patient-verification-utils';
|
|
9
|
+
import { FormValues } from '../patient-registration/patient-registration-types';
|
|
10
|
+
import { FormikProps } from 'formik';
|
|
11
|
+
|
|
12
|
+
interface PatientVerificationProps {
|
|
13
|
+
props: FormikProps<FormValues>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const PatientVerification: React.FC<PatientVerificationProps> = ({ props }) => {
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
const { data, isLoading, error } = useGlobalProperties();
|
|
19
|
+
const [verificationCriteria, setVerificationCriteria] = useState({
|
|
20
|
+
searchTerm: '',
|
|
21
|
+
identifierType: '',
|
|
22
|
+
});
|
|
23
|
+
const [isLoadingSearch, setIsLoadingSearch] = useState(false);
|
|
24
|
+
|
|
25
|
+
const handleSearch = async () => {
|
|
26
|
+
setIsLoadingSearch(true);
|
|
27
|
+
try {
|
|
28
|
+
const clientRegistryResponse = await searchClientRegistry(
|
|
29
|
+
verificationCriteria.identifierType,
|
|
30
|
+
verificationCriteria.searchTerm,
|
|
31
|
+
props.values.token,
|
|
32
|
+
);
|
|
33
|
+
setIsLoadingSearch(false);
|
|
34
|
+
handleClientRegistryResponse(clientRegistryResponse, props, verificationCriteria.searchTerm);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
showToast({
|
|
37
|
+
title: 'Client registry error',
|
|
38
|
+
description: `Please reload the registration page and re-try again, if the issue persist contact system administrator`,
|
|
39
|
+
millis: 10000,
|
|
40
|
+
kind: 'error',
|
|
41
|
+
critical: true,
|
|
42
|
+
});
|
|
43
|
+
setIsLoadingSearch(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (isLoading) {
|
|
48
|
+
<InlineLoading status="active" iconDescription="Loading" description="Loading data..." />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (error) {
|
|
52
|
+
return (
|
|
53
|
+
<InlineNotification
|
|
54
|
+
lowContrast
|
|
55
|
+
hideCloseButton
|
|
56
|
+
title="Client registry network error"
|
|
57
|
+
subtitle="Could not connect to client registry, please proceed with registration and try again later"
|
|
58
|
+
style={{ marginBottom: '0.5rem' }}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return (
|
|
63
|
+
<div id={'patientVerification'}>
|
|
64
|
+
<h3 className={styles.productiveHeading02} style={{ color: '#161616' }}>
|
|
65
|
+
{t('clientVerificationWithClientRegistry', 'Client verification with client registry')}
|
|
66
|
+
</h3>
|
|
67
|
+
<div style={{ margin: '1rem 0 1rem' }}>
|
|
68
|
+
<Layer>
|
|
69
|
+
{isLoading && <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />}
|
|
70
|
+
</Layer>
|
|
71
|
+
<Tile className={styles.verificationWrapper}>
|
|
72
|
+
<Layer>
|
|
73
|
+
<ComboBox
|
|
74
|
+
ariaLabel={t('selectCountry', 'Select country')}
|
|
75
|
+
id="selectCountry"
|
|
76
|
+
items={countries}
|
|
77
|
+
itemToString={(item) => item?.name ?? ''}
|
|
78
|
+
label="Combo box menu options"
|
|
79
|
+
titleText={t('selectCountry', 'Select country')}
|
|
80
|
+
initialSelectedItem={countries[0]}
|
|
81
|
+
/>
|
|
82
|
+
</Layer>
|
|
83
|
+
<Layer>
|
|
84
|
+
<ComboBox
|
|
85
|
+
ariaLabel={t('selectIdentifierType', 'Select identifier type')}
|
|
86
|
+
id="selectIdentifierType"
|
|
87
|
+
items={verificationIdentifierTypes}
|
|
88
|
+
itemToString={(item) => item?.name ?? ''}
|
|
89
|
+
label="Combo box menu options"
|
|
90
|
+
titleText={t('selectIdentifierType', 'Select identifier type')}
|
|
91
|
+
onChange={({ selectedItem }) =>
|
|
92
|
+
setVerificationCriteria({ ...verificationCriteria, identifierType: selectedItem.value })
|
|
93
|
+
}
|
|
94
|
+
/>
|
|
95
|
+
</Layer>
|
|
96
|
+
<Layer>
|
|
97
|
+
<Search
|
|
98
|
+
id="search-1"
|
|
99
|
+
autoFocus
|
|
100
|
+
placeHolderText="Search"
|
|
101
|
+
disabled={!verificationCriteria.identifierType}
|
|
102
|
+
onChange={(event) => setVerificationCriteria({ ...verificationCriteria, searchTerm: event.target.value })}
|
|
103
|
+
/>
|
|
104
|
+
</Layer>
|
|
105
|
+
{!isLoadingSearch ? (
|
|
106
|
+
<Button disabled={!verificationCriteria.identifierType && !isLoading} size="md" onClick={handleSearch}>
|
|
107
|
+
{t('validate', 'Validate')}
|
|
108
|
+
</Button>
|
|
109
|
+
) : (
|
|
110
|
+
<InlineLoading status="active" iconDescription="Loading" description="Searching client registry" />
|
|
111
|
+
)}
|
|
112
|
+
</Tile>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export default PatientVerification;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@import '../patient-registration/patient-registration.scss';
|
|
3
|
+
|
|
4
|
+
/* Desktop */
|
|
5
|
+
:global(.omrs-breakpoint-gt-tablet) {
|
|
6
|
+
.verificationWrapper {
|
|
7
|
+
display: grid;
|
|
8
|
+
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
9
|
+
column-gap: 0.325rem;
|
|
10
|
+
align-items: flex-end;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Tablet */
|
|
15
|
+
:global(.omrs-breakpoint-lt-desktop) {
|
|
16
|
+
.verificationWrapper {
|
|
17
|
+
row-gap: 0.5rem;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.errorWrapper {
|
|
25
|
+
color: colors.$red-50;
|
|
26
|
+
margin: 0 0 1rem 0;
|
|
27
|
+
display: flex;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
align-items: center;
|
|
30
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button } from '@carbon/react';
|
|
4
|
+
import { age, ExtensionSlot, formatDate } from '@openmrs/esm-framework';
|
|
5
|
+
import capitalize from 'lodash-es/capitalize';
|
|
6
|
+
|
|
7
|
+
const PatientInfo: React.FC<{ label: string; value: string }> = ({ label, value }) => {
|
|
8
|
+
return (
|
|
9
|
+
<div style={{ display: 'grid', gridTemplateColumns: '0.25fr 0.75fr', margin: '0.25rem' }}>
|
|
10
|
+
<span style={{ minWidth: '5rem', fontWeight: 'bold' }}>{label}</span>
|
|
11
|
+
<span>{value}</span>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
interface ConfirmPromptProps {
|
|
17
|
+
onConfirm: void;
|
|
18
|
+
close: void;
|
|
19
|
+
patient: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ConfirmPrompt: React.FC<ConfirmPromptProps> = ({ close, onConfirm, patient }) => {
|
|
23
|
+
const { t } = useTranslation();
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<div className="cds--modal-header">
|
|
27
|
+
<h3 className="cds--modal-header__heading">
|
|
28
|
+
{t('clientRegistryEmpty', `Patient ${patient?.firstName} ${patient?.lastName} found`)}
|
|
29
|
+
</h3>
|
|
30
|
+
</div>
|
|
31
|
+
<div className="cds--modal-content">
|
|
32
|
+
<p>
|
|
33
|
+
{t(
|
|
34
|
+
'patientDetailsFound',
|
|
35
|
+
'Patient information found in the registry, do you want to use the information to continue with registration?',
|
|
36
|
+
)}
|
|
37
|
+
</p>
|
|
38
|
+
<div style={{ display: 'flex', margin: '1rem' }}>
|
|
39
|
+
<ExtensionSlot
|
|
40
|
+
style={{ display: 'flex', alignItems: 'center' }}
|
|
41
|
+
extensionSlotName="patient-photo-slot"
|
|
42
|
+
state={{ patientName: `${patient?.firstName} ${patient?.lastName}` }}
|
|
43
|
+
/>
|
|
44
|
+
<div style={{ width: '100%', marginLeft: '0.625rem' }}>
|
|
45
|
+
<PatientInfo
|
|
46
|
+
label={t('patientName', 'Patient name')}
|
|
47
|
+
value={`${patient?.firstName} ${patient?.lastName}`}
|
|
48
|
+
/>
|
|
49
|
+
<PatientInfo
|
|
50
|
+
label={t('nationalId', 'National ID')}
|
|
51
|
+
value={patient?.identifications[0]?.identificationNumber}
|
|
52
|
+
/>
|
|
53
|
+
<PatientInfo label={t('age', 'Age')} value={age(patient?.dateOfBirth)} />
|
|
54
|
+
<PatientInfo label={t('dateOfBirth', 'Date of birth')} value={formatDate(new Date(patient?.dateOfBirth))} />
|
|
55
|
+
<PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.gender)} />
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div className="cds--modal-footer">
|
|
60
|
+
<Button kind="secondary" onClick={close}>
|
|
61
|
+
{t('cancel', 'Cancel')}
|
|
62
|
+
</Button>
|
|
63
|
+
<Button onClick={onConfirm}>{t('useValues', 'Use values')}</Button>
|
|
64
|
+
</div>
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default ConfirmPrompt;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button } from '@carbon/react';
|
|
4
|
+
|
|
5
|
+
interface EmptyPromptProps {
|
|
6
|
+
onConfirm: void;
|
|
7
|
+
close: void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const EmptyPrompt: React.FC<EmptyPromptProps> = ({ close, onConfirm }) => {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<div className="cds--modal-header">
|
|
15
|
+
<h3 className="cds--modal-header__heading">{t('clientRegistryEmpty', 'Create & Post Patient')}</h3>
|
|
16
|
+
</div>
|
|
17
|
+
<div className="cds--modal-content">
|
|
18
|
+
<p>
|
|
19
|
+
{t(
|
|
20
|
+
'patientNotFound',
|
|
21
|
+
'The patient records could not be found in Client registry, do you want to continue to create and post patient to registry',
|
|
22
|
+
)}
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
<div className="cds--modal-footer">
|
|
26
|
+
<Button kind="secondary" onClick={close}>
|
|
27
|
+
{t('cancel', 'Cancel')}
|
|
28
|
+
</Button>
|
|
29
|
+
<Button onClick={onConfirm}>{t('continue', 'Continue to registration')}</Button>
|
|
30
|
+
</div>
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default EmptyPrompt;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface ClientIdentification {
|
|
2
|
+
identificationType: string;
|
|
3
|
+
identificationNumber: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface ClientContact {
|
|
7
|
+
primaryPhone: string;
|
|
8
|
+
secondaryPhone?: string;
|
|
9
|
+
emailAddress?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ClientRegistryPatient {
|
|
13
|
+
clientExists: boolean;
|
|
14
|
+
client?: RegistryPatient;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RegistryPatient {
|
|
18
|
+
clientNumber?: string;
|
|
19
|
+
firstName: string;
|
|
20
|
+
middleName: string;
|
|
21
|
+
lastName: string;
|
|
22
|
+
dateOfBirth: string;
|
|
23
|
+
maritalStatus?: string;
|
|
24
|
+
gender: string;
|
|
25
|
+
occupation?: string;
|
|
26
|
+
religion?: string;
|
|
27
|
+
educationLevel?: string;
|
|
28
|
+
country: string;
|
|
29
|
+
countyOfBirth?: string;
|
|
30
|
+
isAlive: boolean;
|
|
31
|
+
originFacilityKmflCode?: string;
|
|
32
|
+
isOnART?: string;
|
|
33
|
+
nascopCCCNumber?: string;
|
|
34
|
+
residence: {
|
|
35
|
+
county: string;
|
|
36
|
+
subCounty: string;
|
|
37
|
+
ward: string;
|
|
38
|
+
village: string;
|
|
39
|
+
landmark: string;
|
|
40
|
+
address: string;
|
|
41
|
+
};
|
|
42
|
+
identifications: Array<ClientIdentification>;
|
|
43
|
+
contact: ClientContact;
|
|
44
|
+
nextOfKins: Array<{
|
|
45
|
+
name: string;
|
|
46
|
+
relationship: string;
|
|
47
|
+
residence: string;
|
|
48
|
+
contact: ClientContact;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
package/src/resource.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { openmrsFetch } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
const AddressHierarchyBaseURL = '/module/addresshierarchy/ajax/getPossibleAddressHierarchyEntriesWithParents.form';
|
|
4
|
+
|
|
5
|
+
export function performAdressHierarchyWithParentSearch(addressField, parentid, query) {
|
|
6
|
+
return openmrsFetch(
|
|
7
|
+
`${AddressHierarchyBaseURL}?addressField=${addressField}&limit=20&searchString=${query}&parentUuid=${parentid}`,
|
|
8
|
+
{
|
|
9
|
+
method: 'GET',
|
|
10
|
+
},
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
|
3
|
+
import { Grid, Row } from '@carbon/react';
|
|
4
|
+
import { ExtensionSlot, navigate, useSession } from '@openmrs/esm-framework';
|
|
5
|
+
import {
|
|
6
|
+
Resources,
|
|
7
|
+
ResourcesContext,
|
|
8
|
+
fetchAddressTemplate,
|
|
9
|
+
fetchAllRelationshipTypes,
|
|
10
|
+
fetchPatientIdentifierTypesWithSources,
|
|
11
|
+
} from './offline.resources';
|
|
12
|
+
import { SavePatientForm } from './patient-registration/form-manager';
|
|
13
|
+
import { PatientRegistration, PatientRegistrationProps } from './patient-registration/patient-registration.component';
|
|
14
|
+
import useSWRImmutable from 'swr/immutable';
|
|
15
|
+
import styles from './root.scss';
|
|
16
|
+
export interface RootProps extends PatientRegistrationProps, Resources {
|
|
17
|
+
savePatientForm: SavePatientForm;
|
|
18
|
+
isOffline: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function Root({ savePatientForm, isOffline }: RootProps) {
|
|
22
|
+
const currentSession = useSession();
|
|
23
|
+
const { data: addressTemplate } = useSWRImmutable('patientRegistrationAddressTemplate', fetchAddressTemplate);
|
|
24
|
+
const { data: relationshipTypes } = useSWRImmutable(
|
|
25
|
+
'patientRegistrationRelationshipTypes',
|
|
26
|
+
fetchAllRelationshipTypes,
|
|
27
|
+
);
|
|
28
|
+
const { data: identifierTypes } = useSWRImmutable(
|
|
29
|
+
'patientRegistrationPatientIdentifiers',
|
|
30
|
+
fetchPatientIdentifierTypesWithSources,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (currentSession.sessionLocation === null) {
|
|
34
|
+
navigate({ to: `\${openmrsSpaBase}/login/location?returnToUrl=\${openmrsSpaBase}/patient-registration` });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<main className={`omrs-main-content ${styles.root}`}>
|
|
39
|
+
<Grid className={styles.grid}>
|
|
40
|
+
<Row>
|
|
41
|
+
<ExtensionSlot extensionSlotName="breadcrumbs-slot" />
|
|
42
|
+
</Row>
|
|
43
|
+
<ResourcesContext.Provider
|
|
44
|
+
value={{
|
|
45
|
+
addressTemplate,
|
|
46
|
+
relationshipTypes,
|
|
47
|
+
identifierTypes,
|
|
48
|
+
currentSession,
|
|
49
|
+
}}>
|
|
50
|
+
<BrowserRouter basename={`${window['getOpenmrsSpaBase']()}`}>
|
|
51
|
+
<Routes>
|
|
52
|
+
<Route
|
|
53
|
+
path="patient-registration"
|
|
54
|
+
element={<PatientRegistration savePatientForm={savePatientForm} isOffline={isOffline} />}
|
|
55
|
+
/>
|
|
56
|
+
<Route
|
|
57
|
+
path="patient/:patientUuid/edit"
|
|
58
|
+
element={<PatientRegistration savePatientForm={savePatientForm} isOffline={isOffline} />}
|
|
59
|
+
/>
|
|
60
|
+
</Routes>
|
|
61
|
+
</BrowserRouter>
|
|
62
|
+
</ResourcesContext.Provider>
|
|
63
|
+
</Grid>
|
|
64
|
+
</main>
|
|
65
|
+
);
|
|
66
|
+
}
|
package/src/root.scss
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import Root from './root.component';
|
|
4
|
+
|
|
5
|
+
window['getOpenmrsSpaBase'] = jest.fn().mockImplementation(() => '/');
|
|
6
|
+
|
|
7
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
8
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
...originalModule,
|
|
12
|
+
validator: jest.fn(),
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('root component', () => {
|
|
17
|
+
it('renders without crashing', () => {
|
|
18
|
+
const div = document.createElement('div');
|
|
19
|
+
const root = createRoot(div);
|
|
20
|
+
|
|
21
|
+
root.render(
|
|
22
|
+
<Root
|
|
23
|
+
savePatientForm={jest.fn()}
|
|
24
|
+
addressTemplate={{ results: [] }}
|
|
25
|
+
currentSession={{} as any}
|
|
26
|
+
relationshipTypes={{ results: [] }}
|
|
27
|
+
identifierTypes={[]}
|
|
28
|
+
isOffline={false}
|
|
29
|
+
/>,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button } from '@carbon/react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
interface CancelPatientEditProps {
|
|
6
|
+
close(): void;
|
|
7
|
+
onConfirm(): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const CancelPatientEdit: React.FC<CancelPatientEditProps> = ({ close, onConfirm }) => {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<div className="cds--modal-header">
|
|
15
|
+
<h3 className="cds--modal-header__heading">{t('discardModalHeader', 'Confirm Discard Changes')}</h3>
|
|
16
|
+
</div>
|
|
17
|
+
<div className="cds--modal-content">
|
|
18
|
+
<p>
|
|
19
|
+
{t(
|
|
20
|
+
'discardModalBody',
|
|
21
|
+
"The changes you made to this patient's details have not been saved. Discard changes?",
|
|
22
|
+
)}
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
<div className="cds--modal-footer">
|
|
26
|
+
<Button kind="secondary" onClick={close}>
|
|
27
|
+
{t('cancel', 'Cancel')}
|
|
28
|
+
</Button>
|
|
29
|
+
<Button kind="danger" onClick={onConfirm}>
|
|
30
|
+
{t('discard', 'Discard')}
|
|
31
|
+
</Button>
|
|
32
|
+
</div>
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default CancelPatientEdit;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import styles from './delete-identifier-modal.scss';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { Button } from '@carbon/react';
|
|
5
|
+
|
|
6
|
+
interface DeleteIdentifierConfirmationModalProps {
|
|
7
|
+
deleteIdentifier: (x: boolean) => void;
|
|
8
|
+
identifierName: string;
|
|
9
|
+
identifierValue: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const DeleteIdentifierConfirmationModal: React.FC<DeleteIdentifierConfirmationModalProps> = ({
|
|
13
|
+
deleteIdentifier,
|
|
14
|
+
identifierName,
|
|
15
|
+
identifierValue,
|
|
16
|
+
}) => {
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className={styles.modalContent}>
|
|
21
|
+
<h1 className={styles.productiveHeading}>{t('deleteIdentifierModalHeading', 'Remove identifier?')}</h1>
|
|
22
|
+
<h3 className={styles.modalSubtitle}>
|
|
23
|
+
{identifierName}
|
|
24
|
+
{t('deleteIdentifierModalText', ' has a value of ')} {identifierValue}
|
|
25
|
+
</h3>
|
|
26
|
+
<p className={styles.modalBody}>
|
|
27
|
+
{t('confirmIdentifierDeletionText', 'Are you sure you want to remove this identifier?')}
|
|
28
|
+
</p>
|
|
29
|
+
<div className={styles.buttonSet}>
|
|
30
|
+
<Button kind="secondary" size="lg" onClick={() => deleteIdentifier(false)}>
|
|
31
|
+
{t('cancel', 'Cancel')}
|
|
32
|
+
</Button>
|
|
33
|
+
<Button kind="danger" size="lg" onClick={() => deleteIdentifier(true)}>
|
|
34
|
+
{t('removeIdentifierButton', 'Remove Identifier')}
|
|
35
|
+
</Button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default DeleteIdentifierConfirmationModal;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/spacing';
|
|
2
|
+
@use '@carbon/styles/scss/type';
|
|
3
|
+
@import '../patient-registration/patient-registration.scss';
|
|
4
|
+
|
|
5
|
+
.productiveHeading {
|
|
6
|
+
@include type.type-style('heading-compact-02');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.modalContent {
|
|
10
|
+
width: 100%;
|
|
11
|
+
background-color: $ui-01;
|
|
12
|
+
padding: spacing.$spacing-05;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.modalSubtitle {
|
|
16
|
+
@include type.type-style('body-compact-01');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.modalBody {
|
|
20
|
+
@include type.type-style('body-compact-01');
|
|
21
|
+
margin: spacing.$spacing-05 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.buttonSet {
|
|
25
|
+
display: grid;
|
|
26
|
+
grid-template-columns: 1fr 1fr;
|
|
27
|
+
margin-left: -(spacing.$spacing-05);
|
|
28
|
+
margin-right: -(spacing.$spacing-05);
|
|
29
|
+
margin-bottom: -(spacing.$spacing-05);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.buttonSet > button {
|
|
33
|
+
max-width: unset !important;
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Avatar from 'react-avatar';
|
|
3
|
+
import GeoPattern from 'geopattern';
|
|
4
|
+
import { usePatientPhoto } from '../patient-registration/patient-registration.resource';
|
|
5
|
+
|
|
6
|
+
interface DisplayPatientPhotoProps {
|
|
7
|
+
patientName: string;
|
|
8
|
+
patientUuid: string;
|
|
9
|
+
size?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function DisplayPatientPhoto({ patientUuid, patientName, size }: DisplayPatientPhotoProps) {
|
|
13
|
+
const { data: photo } = usePatientPhoto(patientUuid);
|
|
14
|
+
const patternUrl: string = GeoPattern.generate(patientUuid).toDataUri();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Avatar
|
|
18
|
+
alt={`${patientName ? `${patientName}'s avatar` : 'Patient avatar'}`}
|
|
19
|
+
color="rgba(0,0,0,0)"
|
|
20
|
+
name={patientName}
|
|
21
|
+
src={photo?.imageSrc}
|
|
22
|
+
size={size === 'small' ? '48' : '80'}
|
|
23
|
+
textSizeRatio={size === 'small' ? 1 : 2}
|
|
24
|
+
style={{
|
|
25
|
+
backgroundImage: `url(${patternUrl})`,
|
|
26
|
+
backgroundRepeat: 'round',
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { navigate } from '@openmrs/esm-framework';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import styles from './edit-patient-details-button.scss';
|
|
5
|
+
|
|
6
|
+
interface EditPatientDetailsButtonProps {
|
|
7
|
+
onTransition?: () => void;
|
|
8
|
+
patientUuid: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const EditPatientDetailsButton: React.FC<EditPatientDetailsButtonProps> = ({ patientUuid, onTransition }) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const handleClick = React.useCallback(() => {
|
|
14
|
+
navigate({ to: `\${openmrsSpaBase}/patient/${patientUuid}/edit` });
|
|
15
|
+
onTransition && onTransition();
|
|
16
|
+
}, [onTransition, patientUuid]);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<li className="cds--overflow-menu-options__option">
|
|
20
|
+
<button
|
|
21
|
+
className="cds--overflow-menu-options__btn"
|
|
22
|
+
role="menuitem"
|
|
23
|
+
title={t('editPatientDetails', 'Edit patient details')}
|
|
24
|
+
data-floating-menu-primary-focus
|
|
25
|
+
onClick={handleClick}>
|
|
26
|
+
<span className="cds--overflow-menu-options__option-content">
|
|
27
|
+
{t('editPatientDetails', 'Edit patient details')}
|
|
28
|
+
</span>
|
|
29
|
+
</button>
|
|
30
|
+
</li>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default EditPatientDetailsButton;
|