@ampath/esm-patient-registration-app 6.0.1-pre.225 → 6.0.1-pre.229
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 +8 -8
- package/dist/831.js +1 -1
- package/dist/831.js.map +1 -1
- package/dist/ampath-esm-patient-registration-app.js +1 -1
- package/dist/ampath-esm-patient-registration-app.js.buildmanifest.json +10 -10
- package/dist/main.js +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/patient-verification/patient-verification-hook.tsx +1 -1
- package/src/patient-verification-HIE/assets/counties.json +236 -0
- package/src/patient-verification-HIE/assets/verification-assets.ts +11 -0
- package/src/patient-verification-HIE/patient-verification-hook.tsx +181 -0
- package/src/patient-verification-HIE/patient-verification-utils.ts +179 -0
- package/src/patient-verification-HIE/patient-verification.component.tsx +124 -0
- package/src/patient-verification-HIE/patient-verification.scss +25 -0
- package/src/patient-verification-HIE/verification-modal/confirm-prompt.component.tsx +72 -0
- package/src/patient-verification-HIE/verification-modal/empty-prompt.component.tsx +35 -0
- package/src/patient-verification-HIE/verification-types.ts +50 -0
|
@@ -0,0 +1,124 @@
|
|
|
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 { showSnackbar, showToast } from '@openmrs/esm-framework';
|
|
8
|
+
import { handleClientRegistryResponse } from './patient-verification-utils';
|
|
9
|
+
import { type FormikProps } from 'formik';
|
|
10
|
+
import { type FormValues } from '../patient-registration/patient-registration.types';
|
|
11
|
+
|
|
12
|
+
interface PatientVerificationProps {
|
|
13
|
+
props: FormikProps<FormValues>;
|
|
14
|
+
setInitialFormValues: React.Dispatch<FormValues>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const PatientVerification: React.FC<PatientVerificationProps> = ({ props }) => {
|
|
18
|
+
const { t } = useTranslation();
|
|
19
|
+
const { data, isLoading, error } = useGlobalProperties();
|
|
20
|
+
const [verificationCriteria, setVerificationCriteria] = useState({
|
|
21
|
+
searchTerm: '',
|
|
22
|
+
identifierType: '',
|
|
23
|
+
countryCode: 'KE',
|
|
24
|
+
});
|
|
25
|
+
const [isLoadingSearch, setIsLoadingSearch] = useState(false);
|
|
26
|
+
|
|
27
|
+
const handleSearch = async () => {
|
|
28
|
+
setIsLoadingSearch(true);
|
|
29
|
+
try {
|
|
30
|
+
const clientRegistryResponse = await searchClientRegistry(
|
|
31
|
+
verificationCriteria.identifierType,
|
|
32
|
+
verificationCriteria.searchTerm,
|
|
33
|
+
props.values.token,
|
|
34
|
+
verificationCriteria.countryCode,
|
|
35
|
+
);
|
|
36
|
+
setIsLoadingSearch(false);
|
|
37
|
+
|
|
38
|
+
handleClientRegistryResponse(clientRegistryResponse, props, verificationCriteria.searchTerm);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
showSnackbar({
|
|
41
|
+
title: 'Client registry error',
|
|
42
|
+
subtitle: `Please reload the registration page and re-try again, if the issue persist contact system administrator`,
|
|
43
|
+
timeoutInMs: 10000,
|
|
44
|
+
kind: 'error',
|
|
45
|
+
isLowContrast: true,
|
|
46
|
+
});
|
|
47
|
+
setIsLoadingSearch(false);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div id={'patientVerification'}>
|
|
53
|
+
<h3 className={styles.productiveHeading02} style={{ color: '#161616' }}>
|
|
54
|
+
{t('clientVerificationWithClientRegistry', 'Client verification with client registry')}
|
|
55
|
+
</h3>
|
|
56
|
+
<div style={{ margin: '1rem 0 1rem' }}>
|
|
57
|
+
<Layer>
|
|
58
|
+
{isLoading && <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />}
|
|
59
|
+
</Layer>
|
|
60
|
+
{error && (
|
|
61
|
+
<InlineNotification
|
|
62
|
+
className={styles.errorWrapper}
|
|
63
|
+
aria-label="closes notification"
|
|
64
|
+
kind="error"
|
|
65
|
+
lowContrast
|
|
66
|
+
statusIconDescription="notification"
|
|
67
|
+
subtitle={t(
|
|
68
|
+
'clientRegistryErrorSubtitle',
|
|
69
|
+
'Please proceed with registration contact system admin and try again later',
|
|
70
|
+
)}
|
|
71
|
+
title={t('clientRegistryError', 'Error occurred while reaching the client registry')}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
<Tile className={styles.verificationWrapper}>
|
|
75
|
+
<Layer>
|
|
76
|
+
<ComboBox
|
|
77
|
+
ariaLabel={t('selectCountry', 'Select country')}
|
|
78
|
+
id="selectCountry"
|
|
79
|
+
items={countries}
|
|
80
|
+
itemToString={(item) => item?.name ?? ''}
|
|
81
|
+
label="Select country"
|
|
82
|
+
titleText={t('selectCountry', 'Select country')}
|
|
83
|
+
initialSelectedItem={countries[0]}
|
|
84
|
+
onChange={({ selectedItem }) =>
|
|
85
|
+
setVerificationCriteria({ ...verificationCriteria, countryCode: selectedItem?.initials })
|
|
86
|
+
}
|
|
87
|
+
/>
|
|
88
|
+
</Layer>
|
|
89
|
+
<Layer>
|
|
90
|
+
<ComboBox
|
|
91
|
+
ariaLabel={t('selectIdentifierType', 'Select identifier type')}
|
|
92
|
+
id="selectIdentifierType"
|
|
93
|
+
items={verificationIdentifierTypes}
|
|
94
|
+
itemToString={(item) => item?.name ?? ''}
|
|
95
|
+
label="Select identifier type"
|
|
96
|
+
titleText={t('selectIdentifierType', 'Select identifier type')}
|
|
97
|
+
onChange={({ selectedItem }) =>
|
|
98
|
+
setVerificationCriteria({ ...verificationCriteria, identifierType: selectedItem?.value })
|
|
99
|
+
}
|
|
100
|
+
/>
|
|
101
|
+
</Layer>
|
|
102
|
+
<Layer>
|
|
103
|
+
<Search
|
|
104
|
+
id="clientRegistrySearch"
|
|
105
|
+
autoFocus
|
|
106
|
+
placeHolderText={t('searchClientRegistry', 'Search client registry')}
|
|
107
|
+
disabled={!verificationCriteria.identifierType}
|
|
108
|
+
onChange={(event) => setVerificationCriteria({ ...verificationCriteria, searchTerm: event.target.value })}
|
|
109
|
+
/>
|
|
110
|
+
</Layer>
|
|
111
|
+
{!isLoadingSearch ? (
|
|
112
|
+
<Button disabled={!verificationCriteria.identifierType && !isLoading} size="md" onClick={handleSearch}>
|
|
113
|
+
{t('validate', 'Validate')}
|
|
114
|
+
</Button>
|
|
115
|
+
) : (
|
|
116
|
+
<InlineLoading status="active" iconDescription="Loading" description="Searching client registry" />
|
|
117
|
+
)}
|
|
118
|
+
</Tile>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export default PatientVerification;
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
.errorWrapper {
|
|
24
|
+
margin: 0 0 1rem 0;
|
|
25
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
import { useFacilityName } from '../patient-verification-hook';
|
|
7
|
+
|
|
8
|
+
const PatientInfo: React.FC<{ label: string; value: string }> = ({ label, value }) => {
|
|
9
|
+
return (
|
|
10
|
+
<div style={{ display: 'grid', gridTemplateColumns: '0.25fr 0.75fr', margin: '0.25rem' }}>
|
|
11
|
+
<span style={{ minWidth: '5rem', fontWeight: 'bold' }}>{label}</span>
|
|
12
|
+
<span>{value}</span>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
interface ConfirmPromptProps {
|
|
18
|
+
onConfirm: void;
|
|
19
|
+
close: void;
|
|
20
|
+
patient: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ConfirmPrompt: React.FC<ConfirmPromptProps> = ({ close, onConfirm, patient }) => {
|
|
24
|
+
const { t } = useTranslation();
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<div className="cds--modal-header">
|
|
28
|
+
<h3 className="cds--modal-header__heading">
|
|
29
|
+
{t('clientRegistryEmpty', `Patient ${patient?.firstName} ${patient?.lastName} found`)}
|
|
30
|
+
</h3>
|
|
31
|
+
</div>
|
|
32
|
+
<div className="cds--modal-content">
|
|
33
|
+
<p>
|
|
34
|
+
{t(
|
|
35
|
+
'patientDetailsFound',
|
|
36
|
+
'Patient information found in the registry, do you want to use the information to continue with registration?',
|
|
37
|
+
)}
|
|
38
|
+
</p>
|
|
39
|
+
<div style={{ display: 'flex', margin: '1rem' }}>
|
|
40
|
+
<ExtensionSlot
|
|
41
|
+
style={{ display: 'flex', alignItems: 'center' }}
|
|
42
|
+
name="patient-photo-slot"
|
|
43
|
+
state={{ patientName: `${patient?.firstName} ${patient?.lastName}` }}
|
|
44
|
+
/>
|
|
45
|
+
<div style={{ width: '100%', marginLeft: '0.625rem' }}>
|
|
46
|
+
<PatientInfo
|
|
47
|
+
label={t('patientName', 'Patient name')}
|
|
48
|
+
value={`${patient?.firstName} ${patient?.lastName}`}
|
|
49
|
+
/>
|
|
50
|
+
<PatientInfo
|
|
51
|
+
label={t('nationalId', 'National ID')}
|
|
52
|
+
value={patient?.identifications[0]?.identificationNumber}
|
|
53
|
+
/>
|
|
54
|
+
<PatientInfo label={t('age', 'Age')} value={age(patient?.dateOfBirth)} />
|
|
55
|
+
<PatientInfo label={t('dateOfBirth', 'Date of birth')} value={formatDate(new Date(patient?.dateOfBirth))} />
|
|
56
|
+
<PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.gender)} />
|
|
57
|
+
<PatientInfo label={t('nupi', 'NUPI')} value={patient?.clientNumber} />
|
|
58
|
+
<PatientInfo label={t('shaNumber', 'SHA Number')} value={'--'} />
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="cds--modal-footer">
|
|
63
|
+
<Button kind="secondary" onClick={close}>
|
|
64
|
+
{t('cancel', 'Cancel')}
|
|
65
|
+
</Button>
|
|
66
|
+
<Button onClick={onConfirm}>{t('useValues', 'Use values')}</Button>
|
|
67
|
+
</div>
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
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
|
+
}
|