@kenyaemr/esm-patient-registration-app 8.0.1-pre.101 → 8.0.1-pre.105
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 +18 -16
- package/dist/108.js +1 -1
- package/dist/108.js.map +1 -1
- package/dist/574.js +1 -1
- package/dist/623.js +1 -1
- package/dist/735.js +1 -1
- package/dist/745.js +2 -0
- package/dist/{831.js.LICENSE.txt → 745.js.LICENSE.txt} +10 -0
- package/dist/745.js.map +1 -0
- package/dist/76.js +1 -1
- package/dist/830.js +1 -0
- package/dist/830.js.map +1 -0
- package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +46 -46
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +10 -0
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/client-registry/hie-client-registry/hie-client-registry.component.tsx +97 -21
- package/src/client-registry/hie-client-registry/hie-client-registry.scss +8 -1
- package/src/client-registry/hie-client-registry/hie-resource.ts +159 -0
- package/src/client-registry/hie-client-registry/hie-types.ts +29 -0
- package/src/client-registry/hie-client-registry/modal/confirm-hie.modal.tsx +82 -0
- package/src/client-registry/hie-client-registry/modal/confirm-hie.scss +10 -0
- package/src/client-registry/patient-verification/verification-modal/empty-prompt.component.tsx +9 -6
- package/src/config-schema.ts +13 -1
- package/src/index.ts +3 -0
- package/src/routes.json +7 -2
- package/translations/en.json +7 -1
- package/dist/790.js +0 -1
- package/dist/790.js.map +0 -1
- package/dist/831.js +0 -2
- package/dist/831.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":"8.0.1-pre.
|
|
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}],"modals":[{"component":"hieConfirmationModal","name":"hie-confirmation-modal"}],"version":"8.0.1-pre.105"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-patient-registration-app",
|
|
3
|
-
"version": "8.0.1-pre.
|
|
3
|
+
"version": "8.0.1-pre.105",
|
|
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",
|
|
@@ -3,23 +3,76 @@ import styles from './hie-client-registry.scss';
|
|
|
3
3
|
import { type FormikProps } from 'formik';
|
|
4
4
|
import { type FormValues } from '../../patient-registration/patient-registration.types';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import { Tile, ComboBox, Button, TextInput } from '@carbon/react';
|
|
6
|
+
import { Tile, ComboBox, Button, TextInput, InlineLoading } from '@carbon/react';
|
|
7
7
|
import { Search } from '@carbon/react/icons';
|
|
8
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
8
|
+
import { showModal, showSnackbar, useConfig } from '@openmrs/esm-framework';
|
|
9
|
+
import { z } from 'zod';
|
|
9
10
|
import { type RegistrationConfig } from '../../config-schema';
|
|
11
|
+
import { useForm, Controller, type SubmitHandler } from 'react-hook-form';
|
|
12
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
13
|
+
import { fetchPatientFromHIE, mapHIEPatientToFormValues } from './hie-resource';
|
|
14
|
+
import { type HIEPatient } from './hie-types';
|
|
10
15
|
|
|
11
16
|
type HIEClientRegistryProps = {
|
|
12
17
|
props: FormikProps<FormValues>;
|
|
13
18
|
setInitialFormValues: Dispatch<FormValues>;
|
|
14
19
|
};
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
type HIEFormValues = {
|
|
22
|
+
identifierType: string;
|
|
23
|
+
identifierValue: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const HIEFormSchema = z.object({
|
|
27
|
+
identifierType: z.string().min(1, { message: 'Identifier type is required' }),
|
|
28
|
+
identifierValue: z.string().min(1, { message: 'Identifier value is required' }),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const HIEClientRegistry: React.FC<HIEClientRegistryProps> = ({ setInitialFormValues, props }) => {
|
|
17
32
|
const { t } = useTranslation();
|
|
33
|
+
const { control, handleSubmit, watch, formState } = useForm<HIEFormValues>({
|
|
34
|
+
mode: 'all',
|
|
35
|
+
defaultValues: { identifierType: '', identifierValue: '' },
|
|
36
|
+
resolver: zodResolver(HIEFormSchema),
|
|
37
|
+
});
|
|
18
38
|
const {
|
|
19
39
|
hieClientRegistry: { identifierTypes },
|
|
20
40
|
} = useConfig<RegistrationConfig>();
|
|
41
|
+
|
|
42
|
+
const onSubmit: SubmitHandler<HIEFormValues> = async (data: HIEFormValues, event: React.BaseSyntheticEvent) => {
|
|
43
|
+
try {
|
|
44
|
+
const hieClientRegistry = await fetchPatientFromHIE(data.identifierType, data.identifierValue);
|
|
45
|
+
|
|
46
|
+
if (hieClientRegistry && hieClientRegistry.resourceType === 'Patient') {
|
|
47
|
+
const dispose = showModal('hie-confirmation-modal', {
|
|
48
|
+
patient: hieClientRegistry,
|
|
49
|
+
closeModal: () => dispose(),
|
|
50
|
+
onUseValues: () =>
|
|
51
|
+
setInitialFormValues(mapHIEPatientToFormValues(hieClientRegistry as HIEPatient, props.values)),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hieClientRegistry && hieClientRegistry.resourceType === 'OperationOutcome') {
|
|
56
|
+
const issueMessage = hieClientRegistry?.['issue']?.map((issue) => issue.diagnostics).join(', ');
|
|
57
|
+
const dispose = showModal('empty-client-registry-modal', {
|
|
58
|
+
onConfirm: () => dispose(),
|
|
59
|
+
close: () => dispose(),
|
|
60
|
+
title: t('clientRegistryEmpty', 'Create & Post Patient'),
|
|
61
|
+
message: issueMessage,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
showSnackbar({
|
|
66
|
+
title: t('errorFetchingPatient', 'Error fetching patient'),
|
|
67
|
+
subtitle: error.message,
|
|
68
|
+
kind: 'error',
|
|
69
|
+
isLowContrast: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
21
74
|
return (
|
|
22
|
-
<
|
|
75
|
+
<form onSubmit={handleSubmit(onSubmit)} className={styles.hieContainer}>
|
|
23
76
|
<h3 className={styles.productiveHeading02} style={{ color: '#161616' }}>
|
|
24
77
|
{t('patientVerificationFromHIE', 'Patient verification from HIE')}
|
|
25
78
|
</h3>
|
|
@@ -27,31 +80,54 @@ const HIEClientRegistry: React.FC<HIEClientRegistryProps> = () => {
|
|
|
27
80
|
{t('allFieldsRequiredText', 'All fields are required unless marked optional')}
|
|
28
81
|
</span>
|
|
29
82
|
<Tile className={styles.grid}>
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
83
|
+
<Controller
|
|
84
|
+
control={control}
|
|
85
|
+
name="identifierType"
|
|
86
|
+
render={({ field: { onChange, value, onBlur, ref }, fieldState }) => (
|
|
87
|
+
<ComboBox
|
|
88
|
+
light
|
|
89
|
+
style={{ borderBottom: 'none' }}
|
|
90
|
+
onChange={({ selectedItem }) => onChange(selectedItem?.identifierValue)}
|
|
91
|
+
id="identifier-combobox"
|
|
92
|
+
items={identifierTypes}
|
|
93
|
+
placeholder={t('selectIdentifierType', 'Select identifier type')}
|
|
94
|
+
itemToString={(item) => (item ? item.identifierType : '')}
|
|
95
|
+
titleText={t('identifierType', 'Identifier type')}
|
|
96
|
+
invalid={!!fieldState?.error?.message}
|
|
97
|
+
invalidText={fieldState?.error?.message}
|
|
98
|
+
/>
|
|
99
|
+
)}
|
|
38
100
|
/>
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
101
|
+
<Controller
|
|
102
|
+
control={control}
|
|
103
|
+
name="identifierValue"
|
|
104
|
+
render={({ field, fieldState }) => (
|
|
105
|
+
<TextInput
|
|
106
|
+
disabled={watch('identifierType') === ''}
|
|
107
|
+
light
|
|
108
|
+
id="identifier-search"
|
|
109
|
+
placeholder={t('enterIdentifierSearchValue', 'Enter identifier search value')}
|
|
110
|
+
type="text"
|
|
111
|
+
labelText={t('identifierSearch', 'Identifier search')}
|
|
112
|
+
invalid={!!fieldState?.error?.message}
|
|
113
|
+
invalidText={fieldState?.error?.message}
|
|
114
|
+
{...field}
|
|
115
|
+
/>
|
|
116
|
+
)}
|
|
45
117
|
/>
|
|
46
118
|
<Button
|
|
119
|
+
onClick={handleSubmit(onSubmit)}
|
|
47
120
|
renderIcon={(props) => <Search size={24} {...props} />}
|
|
48
121
|
iconDescription={t('searchRegistry', 'Search registry')}
|
|
49
122
|
size="md"
|
|
50
|
-
|
|
51
|
-
|
|
123
|
+
disabled={!watch('identifierType') || !watch('identifierValue') || formState.isSubmitting}
|
|
124
|
+
kind="tertiary">
|
|
125
|
+
{formState.isSubmitting
|
|
126
|
+
? t('searchingRegistry', 'Searching registry...')
|
|
127
|
+
: t('searchRegistry', 'Search registry')}
|
|
52
128
|
</Button>
|
|
53
129
|
</Tile>
|
|
54
|
-
</
|
|
130
|
+
</form>
|
|
55
131
|
);
|
|
56
132
|
};
|
|
57
133
|
|
|
@@ -41,6 +41,13 @@
|
|
|
41
41
|
display: grid;
|
|
42
42
|
grid-template-columns: 1fr 1fr 0.5fr;
|
|
43
43
|
column-gap: layout.$spacing-05;
|
|
44
|
-
align-items: flex-
|
|
44
|
+
align-items: flex-start;
|
|
45
|
+
|
|
46
|
+
& > button {
|
|
47
|
+
align-self: flex-start;
|
|
48
|
+
margin-top: 1.4rem;
|
|
49
|
+
max-width: 14rem;
|
|
50
|
+
min-width: 14rem;
|
|
51
|
+
}
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { add, capitalize } from 'lodash-es';
|
|
2
|
+
import { type PatientIdentifierValue, type FormValues } from '../../patient-registration/patient-registration.types';
|
|
3
|
+
import { type MapperConfig, type HIEPatient, type ErrorResponse } from './hie-types';
|
|
4
|
+
import { getConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import { type RegistrationConfig } from '../../config-schema';
|
|
6
|
+
import { v4 } from 'uuid';
|
|
7
|
+
/**
|
|
8
|
+
* Represents a client for interacting with a Health Information Exchange (HIE) resource.
|
|
9
|
+
* @template T - The type of the resource being fetched.
|
|
10
|
+
*/
|
|
11
|
+
class HealthInformationExchangeClient<T> {
|
|
12
|
+
async fetchResource(resourceType: string, params: Record<string, string>): Promise<T> {
|
|
13
|
+
const {
|
|
14
|
+
hieClientRegistry: { baseUrl, encodedCredentials },
|
|
15
|
+
} = await getConfig<RegistrationConfig>('@kenyaemr/esm-patient-registration-app');
|
|
16
|
+
const urlParams = new URLSearchParams(params);
|
|
17
|
+
const response = await fetch(`${baseUrl}${resourceType}?${urlParams}`, {
|
|
18
|
+
headers: new Headers({
|
|
19
|
+
Authorization: `Basic ${encodedCredentials}`,
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return response.json();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Represents a generic Mapper class.
|
|
29
|
+
* @template T - The source type.
|
|
30
|
+
* @template U - The target type.
|
|
31
|
+
*/
|
|
32
|
+
class Mapper<T, U> {
|
|
33
|
+
protected config: MapperConfig;
|
|
34
|
+
|
|
35
|
+
constructor(config: MapperConfig) {
|
|
36
|
+
this.config = config;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Maps HIEPatient objects to FormValues objects.
|
|
42
|
+
*/
|
|
43
|
+
class PatientMapper extends Mapper<HIEPatient, FormValues> {
|
|
44
|
+
mapHIEPatientToFormValues(hiePatient: HIEPatient, currentFormValues: FormValues): FormValues {
|
|
45
|
+
const name = hiePatient.name?.[0] || {};
|
|
46
|
+
const telecom = hiePatient.telecom || [];
|
|
47
|
+
|
|
48
|
+
const telecomAttributes = this.mapTelecomToAttributes(telecom);
|
|
49
|
+
const updatedIdentifiers = this.mapIdentifiers(hiePatient, currentFormValues);
|
|
50
|
+
const extensionAddressEntries = this.mapExtensionsToAddress(hiePatient.extension);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
isDead: hiePatient.deceasedBoolean || false,
|
|
54
|
+
gender: hiePatient.gender || '',
|
|
55
|
+
birthdate: hiePatient.birthDate || '',
|
|
56
|
+
givenName: name.given?.[0] || '',
|
|
57
|
+
familyName: name.family || '',
|
|
58
|
+
telephoneNumber: telecom.find((t) => t.system === 'phone')?.value || '',
|
|
59
|
+
middleName: name.given?.[1],
|
|
60
|
+
address: extensionAddressEntries,
|
|
61
|
+
identifiers: updatedIdentifiers,
|
|
62
|
+
attributes: telecomAttributes,
|
|
63
|
+
relationships: [],
|
|
64
|
+
patientUuid: v4(),
|
|
65
|
+
} as FormValues;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private mapTelecomToAttributes(telecom: Array<fhir.ContactPoint>): Record<string, string> {
|
|
69
|
+
return telecom.reduce<Record<string, string>>((acc, { system, value }) => {
|
|
70
|
+
if (system && value && this.config.teleComMap[system]) {
|
|
71
|
+
const filteredValue = value.replace(/^254/, '0');
|
|
72
|
+
if (filteredValue) {
|
|
73
|
+
acc[this.config.teleComMap[system]] = filteredValue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return acc;
|
|
77
|
+
}, {});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private mapIdentifiers(
|
|
81
|
+
hiePatient: HIEPatient,
|
|
82
|
+
currentFormValues: FormValues,
|
|
83
|
+
): Record<string, PatientIdentifierValue> {
|
|
84
|
+
const updatedIdentifiers: Record<string, PatientIdentifierValue> = { ...currentFormValues.identifiers };
|
|
85
|
+
|
|
86
|
+
// Map healthId to hiePatient.id
|
|
87
|
+
updatedIdentifiers.healthId = {
|
|
88
|
+
...currentFormValues.identifiers['healthId'],
|
|
89
|
+
identifierValue: hiePatient.id,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Map fhir.Patient.Identifier to identifiers
|
|
93
|
+
hiePatient.identifier?.forEach((identifier) => {
|
|
94
|
+
const system = identifier.system?.split('/').pop();
|
|
95
|
+
if (system && this.config.identifierMap[system]) {
|
|
96
|
+
const key = this.config.identifierMap[system];
|
|
97
|
+
updatedIdentifiers[key] = {
|
|
98
|
+
...currentFormValues.identifiers[key],
|
|
99
|
+
identifierValue: identifier.value || '',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Filter out undefined keys and values
|
|
105
|
+
Object.keys(updatedIdentifiers).forEach((key) => {
|
|
106
|
+
if (updatedIdentifiers[key] === undefined || updatedIdentifiers[key].identifierValue === undefined) {
|
|
107
|
+
delete updatedIdentifiers[key];
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return updatedIdentifiers;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private mapExtensionsToAddress(extensions: HIEPatient['extension']): Record<string, string> {
|
|
115
|
+
return extensions.reduce<Record<string, string>>((acc, ext) => {
|
|
116
|
+
const identifierType = ext.url.split('/').pop();
|
|
117
|
+
const mappedKey = this.config.addressHierarchyMap[identifierType];
|
|
118
|
+
|
|
119
|
+
if (mappedKey && ext.valueString) {
|
|
120
|
+
acc[mappedKey] = capitalize(ext.valueString);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return acc;
|
|
124
|
+
}, {});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Update MapperConfig interface in hie-types.ts
|
|
129
|
+
|
|
130
|
+
const mapperConfig: MapperConfig = {
|
|
131
|
+
teleComMap: {
|
|
132
|
+
email: 'b8d0b331-1d2d-4a9a-b741-1816f498bdb6',
|
|
133
|
+
phone: 'b2c38640-2603-4629-aebd-3b54f33f1e3a',
|
|
134
|
+
},
|
|
135
|
+
addressHierarchyMap: {
|
|
136
|
+
county: 'countyDistrict',
|
|
137
|
+
'sub-county': 'stateProvince',
|
|
138
|
+
ward: 'address4',
|
|
139
|
+
},
|
|
140
|
+
addressMap: {},
|
|
141
|
+
// Map FHIR Patient identifiers to identifiers, at the moment HIE teams returns null for all identifiers type codings
|
|
142
|
+
identifierMap: {},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Create instances
|
|
146
|
+
const hieApiClient = new HealthInformationExchangeClient<HIEPatient | ErrorResponse>();
|
|
147
|
+
const patientMapper = new PatientMapper(mapperConfig);
|
|
148
|
+
|
|
149
|
+
// Exported functions
|
|
150
|
+
export const fetchPatientFromHIE = async (
|
|
151
|
+
identifierType: string,
|
|
152
|
+
identifierValue: string,
|
|
153
|
+
): Promise<HIEPatient | ErrorResponse> => {
|
|
154
|
+
return hieApiClient.fetchResource('Patient', { [identifierType]: identifierValue });
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const mapHIEPatientToFormValues = (hiePatient: HIEPatient, currentFormValues: FormValues): FormValues => {
|
|
158
|
+
return patientMapper.mapHIEPatientToFormValues(hiePatient, currentFormValues);
|
|
159
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type HIEPatient = fhir.Patient & {
|
|
2
|
+
extension: Array<{
|
|
3
|
+
url: string;
|
|
4
|
+
valueString: string;
|
|
5
|
+
}>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type APIClientConfig = {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
credentials: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface MapperConfig {
|
|
14
|
+
teleComMap: Record<string, string>;
|
|
15
|
+
addressHierarchyMap: Record<string, string>;
|
|
16
|
+
identifierMap: Record<string, string>;
|
|
17
|
+
addressMap: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ErrorResponse = {
|
|
21
|
+
resourceType: string;
|
|
22
|
+
issue: Array<Issue>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type Issue = {
|
|
26
|
+
severity: string;
|
|
27
|
+
code: string;
|
|
28
|
+
diagnostics: string;
|
|
29
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, ModalBody, ModalHeader, ModalFooter, Accordion, AccordionItem, CodeSnippet } from '@carbon/react';
|
|
4
|
+
import styles from './confirm-hie.scss';
|
|
5
|
+
import { age, ExtensionSlot, formatDate } from '@openmrs/esm-framework';
|
|
6
|
+
import { type HIEPatient } from '../hie-types';
|
|
7
|
+
import capitalize from 'lodash-es/capitalize';
|
|
8
|
+
|
|
9
|
+
const PatientInfo: React.FC<{ label: string; value: string }> = ({ label, value }) => {
|
|
10
|
+
return (
|
|
11
|
+
<div style={{ display: 'grid', gridTemplateColumns: '0.25fr 0.75fr', margin: '0.25rem' }}>
|
|
12
|
+
<span style={{ minWidth: '5rem', fontWeight: 'bold' }}>{label}</span>
|
|
13
|
+
<span>{value}</span>
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
interface HIEConfirmationModalProps {
|
|
19
|
+
closeModal: () => void;
|
|
20
|
+
patient: HIEPatient;
|
|
21
|
+
onUseValues: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const HIEConfirmationModal: React.FC<HIEConfirmationModalProps> = ({ closeModal, patient, onUseValues }) => {
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
const firstName = patient?.name[0]['given']?.[0];
|
|
27
|
+
const lastName = patient?.name[0]['family'];
|
|
28
|
+
|
|
29
|
+
const handleUseValues = () => {
|
|
30
|
+
onUseValues();
|
|
31
|
+
closeModal();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div>
|
|
36
|
+
<ModalHeader closeModal={closeModal}>
|
|
37
|
+
<span className={styles.header}>{t('hieModal', 'HIE Patient Record Found')}</span>
|
|
38
|
+
</ModalHeader>
|
|
39
|
+
<ModalBody>
|
|
40
|
+
<div style={{ display: 'flex', margin: '1rem' }}>
|
|
41
|
+
<ExtensionSlot
|
|
42
|
+
style={{ display: 'flex', alignItems: 'center' }}
|
|
43
|
+
name="patient-photo-slot"
|
|
44
|
+
state={{ patientName: `${firstName} ${lastName}` }}
|
|
45
|
+
/>
|
|
46
|
+
<div style={{ width: '100%', marginLeft: '0.625rem' }}>
|
|
47
|
+
<PatientInfo label={t('healthID', 'HealthID')} value={patient.id} />
|
|
48
|
+
<PatientInfo label={t('patientName', 'Patient name')} value={`${firstName} ${lastName}`} />
|
|
49
|
+
<PatientInfo label={t('age', 'Age')} value={age(patient?.birthDate)} />
|
|
50
|
+
<PatientInfo label={t('dateOfBirth', 'Date of birth')} value={formatDate(new Date(patient?.birthDate))} />
|
|
51
|
+
<PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.gender)} />
|
|
52
|
+
<PatientInfo
|
|
53
|
+
label={t('maritalStatus', 'Marital status')}
|
|
54
|
+
value={patient.maritalStatus.coding.map((m) => m.code).join('')}
|
|
55
|
+
/>
|
|
56
|
+
<PatientInfo label={t('dependents', 'Dependents')} value="--" />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div>
|
|
60
|
+
<Accordion>
|
|
61
|
+
<AccordionItem title={t('viewFullResponse', 'View full response')}>
|
|
62
|
+
<CodeSnippet type="multi" feedback="Copied to clipboard">
|
|
63
|
+
{JSON.stringify(patient, null, 2)}
|
|
64
|
+
</CodeSnippet>
|
|
65
|
+
</AccordionItem>
|
|
66
|
+
</Accordion>
|
|
67
|
+
</div>
|
|
68
|
+
</ModalBody>
|
|
69
|
+
<ModalFooter>
|
|
70
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
71
|
+
{t('cancel', 'Cancel')}
|
|
72
|
+
</Button>
|
|
73
|
+
|
|
74
|
+
<Button onClick={handleUseValues} kind="primary">
|
|
75
|
+
{t('useValues', 'Use values')}
|
|
76
|
+
</Button>
|
|
77
|
+
</ModalFooter>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default HIEConfirmationModal;
|
package/src/client-registry/patient-verification/verification-modal/empty-prompt.component.tsx
CHANGED
|
@@ -5,21 +5,24 @@ import { Button } from '@carbon/react';
|
|
|
5
5
|
interface EmptyPromptProps {
|
|
6
6
|
onConfirm: void;
|
|
7
7
|
close: void;
|
|
8
|
+
title?: string;
|
|
9
|
+
message?: string;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
const EmptyPrompt: React.FC<EmptyPromptProps> = ({ close, onConfirm }) => {
|
|
12
|
+
const EmptyPrompt: React.FC<EmptyPromptProps> = ({ close, onConfirm, title, message }) => {
|
|
11
13
|
const { t } = useTranslation();
|
|
12
14
|
return (
|
|
13
15
|
<>
|
|
14
16
|
<div className="cds--modal-header">
|
|
15
|
-
<h3 className="cds--modal-header__heading">{t('clientRegistryEmpty', 'Create & Post Patient')}</h3>
|
|
17
|
+
<h3 className="cds--modal-header__heading">{title ?? t('clientRegistryEmpty', 'Create & Post Patient')}</h3>
|
|
16
18
|
</div>
|
|
17
19
|
<div className="cds--modal-content">
|
|
18
20
|
<p>
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
{message ??
|
|
22
|
+
t(
|
|
23
|
+
'patientNotFound',
|
|
24
|
+
'The patient records could not be found in Client registry, do you want to continue to create and post patient to registry',
|
|
25
|
+
)}
|
|
23
26
|
</p>
|
|
24
27
|
</div>
|
|
25
28
|
<div className="cds--modal-footer">
|
package/src/config-schema.ts
CHANGED
|
@@ -85,6 +85,8 @@ export interface RegistrationConfig {
|
|
|
85
85
|
};
|
|
86
86
|
hieClientRegistry: {
|
|
87
87
|
identifierTypes: Array<{ identifierType: string; identifierValue: string }>;
|
|
88
|
+
baseUrl: string;
|
|
89
|
+
encodedCredentials: string;
|
|
88
90
|
};
|
|
89
91
|
}
|
|
90
92
|
|
|
@@ -382,12 +384,22 @@ export const esmPatientRegistrationSchema = {
|
|
|
382
384
|
},
|
|
383
385
|
_default: [
|
|
384
386
|
{ identifierType: 'National ID', identifierValue: 'national-id' },
|
|
385
|
-
{ identifierType: 'Passport Number', identifierValue: 'passport
|
|
387
|
+
{ identifierType: 'Passport Number', identifierValue: 'passport-number' },
|
|
386
388
|
{ identifierType: 'Birth Certificate Number', identifierValue: 'birth-certificate-number' },
|
|
387
389
|
{ identifierType: 'Alien ID Number', identifierValue: 'alien-id-number' },
|
|
388
390
|
{ identifierType: 'Refugee ID Number', identifierValue: 'refugee-number' },
|
|
389
391
|
],
|
|
390
392
|
},
|
|
393
|
+
baseUrl: {
|
|
394
|
+
_type: Type.String,
|
|
395
|
+
_default: 'https://hie.paperless.co.ke/v4/custom/',
|
|
396
|
+
_description: 'The base URL for the HIE API',
|
|
397
|
+
},
|
|
398
|
+
encodedCredentials: {
|
|
399
|
+
_type: Type.String,
|
|
400
|
+
_default: 'a2VueWFfZW1yOkFsZG5iJmtmayZKc2whMjM0',
|
|
401
|
+
_description: 'The base64 encoded credentials for the HIE API',
|
|
402
|
+
},
|
|
391
403
|
},
|
|
392
404
|
_validators: [
|
|
393
405
|
validator(
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import rootComponent from './root.component';
|
|
|
6
6
|
import addPatientLinkComponent from './add-patient-link';
|
|
7
7
|
import editPatientDetailsButtonComponent from './widgets/edit-patient-details-button.component';
|
|
8
8
|
import { PatientPhotoExtension } from './patient-photo.extension';
|
|
9
|
+
import HIEConfirmationModal from './client-registry/hie-client-registry/modal/confirm-hie.modal';
|
|
9
10
|
|
|
10
11
|
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
11
12
|
|
|
@@ -76,3 +77,5 @@ export const emptyClientRegistryModal = getAsyncLifecycle(
|
|
|
76
77
|
() => import('./client-registry/patient-verification/verification-modal/empty-prompt.component'),
|
|
77
78
|
options,
|
|
78
79
|
);
|
|
80
|
+
|
|
81
|
+
export const hieConfirmationModal = getSyncLifecycle(HIEConfirmationModal, options);
|
package/src/routes.json
CHANGED
package/translations/en.json
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"deleteIdentifierTooltip": "Delete",
|
|
34
34
|
"deleteRelationshipTooltipText": "Delete",
|
|
35
35
|
"demographicsSection": "Basic Info",
|
|
36
|
+
"dependants": "Dependants",
|
|
36
37
|
"discard": "Discard",
|
|
37
38
|
"discardModalBody": "The changes you made to this patient's details have not been saved. Discard changes?",
|
|
38
39
|
"discardModalHeader": "Confirm Discard Changes",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"enterIdentifierSearchValue": "Enter identifier search value",
|
|
44
45
|
"error": "Error",
|
|
45
46
|
"errorFetchingOrderedFields": "Error occured fetching ordered fields for address hierarchy",
|
|
47
|
+
"errorFetchingPatient": "Error fetching patient",
|
|
46
48
|
"estimatedAgeInMonthsLabelText": "Estimated age in months",
|
|
47
49
|
"estimatedAgeInYearsLabelText": "Estimated age in years",
|
|
48
50
|
"familyNameLabelText": "Family Name",
|
|
@@ -55,6 +57,8 @@
|
|
|
55
57
|
"genderUnspecified": "Gender is not specified",
|
|
56
58
|
"givenNameLabelText": "First Name",
|
|
57
59
|
"givenNameRequired": "Given name is required",
|
|
60
|
+
"healthID": "HealthID",
|
|
61
|
+
"hieModal": "HIE Patient Record Found",
|
|
58
62
|
"identifierSearch": "Identifier search",
|
|
59
63
|
"identifierType": "Identifier type",
|
|
60
64
|
"identifierValueRequired": "Identifier value is required",
|
|
@@ -66,6 +70,7 @@
|
|
|
66
70
|
"isDeadInputLabel": "Is Dead",
|
|
67
71
|
"jumpTo": "Jump to",
|
|
68
72
|
"male": "Male",
|
|
73
|
+
"maritalStatus": "Marital status",
|
|
69
74
|
"middleNameLabelText": "Middle Name",
|
|
70
75
|
"nationalId": "National ID",
|
|
71
76
|
"negativeMonths": "Negative months",
|
|
@@ -98,10 +103,10 @@
|
|
|
98
103
|
"removeIdentifierButton": "Remove Identifier",
|
|
99
104
|
"resetIdentifierTooltip": "Reset",
|
|
100
105
|
"restoreRelationshipActionButton": "Undo",
|
|
101
|
-
"search": "Search",
|
|
102
106
|
"searchAddress": "Search address",
|
|
103
107
|
"searchClientRegistry": "Search client registry",
|
|
104
108
|
"searchIdentifierPlaceholder": "Search identifier",
|
|
109
|
+
"searchingRegistry": "Searching registry...",
|
|
105
110
|
"searchRegistry": "Search registry",
|
|
106
111
|
"selectAnOption": "Select an option",
|
|
107
112
|
"selectCountry": "Select country",
|
|
@@ -120,6 +125,7 @@
|
|
|
120
125
|
"updatePatientSuccessSnackbarTitle": "Patient Details Updated",
|
|
121
126
|
"useValues": "Use values",
|
|
122
127
|
"validate": "Validate",
|
|
128
|
+
"viewFullResponse": "View full response",
|
|
123
129
|
"yearsEstimateRequired": "Years estimate required",
|
|
124
130
|
"yes": "Yes"
|
|
125
131
|
}
|