@kenyaemr/esm-patient-registration-app 8.0.1-pre.95 → 8.1.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/.turbo/turbo-build.log +18 -16
- package/dist/108.js +1 -0
- package/dist/108.js.map +1 -0
- 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/{59.js → 76.js} +1 -1
- package/dist/{59.js.map → 76.js.map} +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 +69 -69
- 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 +2 -3
- package/src/client-registry/client-registry.component.tsx +22 -0
- package/src/client-registry/hie-client-registry/hie-client-registry.component.tsx +134 -0
- package/src/client-registry/hie-client-registry/hie-client-registry.scss +53 -0
- package/src/client-registry/hie-client-registry/hie-resource.ts +160 -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/{patient-verification → client-registry/patient-verification}/patient-verification-hook.tsx +2 -2
- package/src/{patient-verification → client-registry/patient-verification}/patient-verification-utils.ts +1 -1
- package/src/{patient-verification → client-registry/patient-verification}/patient-verification.component.tsx +4 -1
- package/src/{patient-verification → client-registry/patient-verification}/patient-verification.scss +17 -1
- package/src/{patient-verification → client-registry/patient-verification}/verification-modal/empty-prompt.component.tsx +9 -6
- package/src/config-schema.ts +37 -0
- package/src/index.ts +5 -2
- package/src/patient-registration/field/obs/obs-field.component.tsx +1 -1
- package/src/patient-registration/patient-registration-hooks.ts +4 -1
- package/src/patient-registration/patient-registration.component.tsx +24 -17
- package/src/routes.json +7 -2
- package/translations/en.json +12 -0
- package/dist/330.js +0 -1
- package/dist/330.js.map +0 -1
- package/dist/564.js +0 -1
- package/dist/564.js.map +0 -1
- package/dist/831.js +0 -2
- package/dist/831.js.map +0 -1
- /package/src/{patient-verification → client-registry/patient-verification}/assets/counties.json +0 -0
- /package/src/{patient-verification → client-registry/patient-verification}/assets/verification-assets.ts +0 -0
- /package/src/{patient-verification → client-registry/patient-verification}/verification-modal/confirm-prompt.component.tsx +0 -0
- /package/src/{patient-verification → client-registry/patient-verification}/verification-types.ts +0 -0
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.
|
|
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.1.0"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-patient-registration-app",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.1.0",
|
|
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",
|
|
@@ -53,6 +53,5 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"webpack": "^5.74.0"
|
|
56
|
-
}
|
|
57
|
-
"stableVersion": "8.0.0"
|
|
56
|
+
}
|
|
58
57
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type FormikProps } from 'formik';
|
|
2
|
+
import React, { type Dispatch } from 'react';
|
|
3
|
+
import { type FormValues } from '../patient-registration/patient-registration.types';
|
|
4
|
+
import { useFeatureFlag } from '@openmrs/esm-framework';
|
|
5
|
+
import PatientVerification from './patient-verification/patient-verification.component';
|
|
6
|
+
import HIEClientRegistry from './hie-client-registry/hie-client-registry.component';
|
|
7
|
+
|
|
8
|
+
type ClientRegistryProps = {
|
|
9
|
+
props: FormikProps<FormValues>;
|
|
10
|
+
setInitialFormValues: Dispatch<FormValues>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const ClientRegistry: React.FC<ClientRegistryProps> = (clientRegistryProps) => {
|
|
14
|
+
const healthInformationExchangeFlag = useFeatureFlag('healthInformationExchange');
|
|
15
|
+
|
|
16
|
+
if (healthInformationExchangeFlag) {
|
|
17
|
+
return <HIEClientRegistry {...clientRegistryProps} />;
|
|
18
|
+
}
|
|
19
|
+
return <PatientVerification {...clientRegistryProps} />;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default ClientRegistry;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import React, { type Dispatch } from 'react';
|
|
2
|
+
import styles from './hie-client-registry.scss';
|
|
3
|
+
import { type FormikProps } from 'formik';
|
|
4
|
+
import { type FormValues } from '../../patient-registration/patient-registration.types';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Tile, ComboBox, Button, TextInput, InlineLoading } from '@carbon/react';
|
|
7
|
+
import { Search } from '@carbon/react/icons';
|
|
8
|
+
import { showModal, showSnackbar, useConfig } from '@openmrs/esm-framework';
|
|
9
|
+
import { z } from 'zod';
|
|
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';
|
|
15
|
+
|
|
16
|
+
type HIEClientRegistryProps = {
|
|
17
|
+
props: FormikProps<FormValues>;
|
|
18
|
+
setInitialFormValues: Dispatch<FormValues>;
|
|
19
|
+
};
|
|
20
|
+
|
|
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 }) => {
|
|
32
|
+
const { t } = useTranslation();
|
|
33
|
+
const { control, handleSubmit, watch, formState } = useForm<HIEFormValues>({
|
|
34
|
+
mode: 'all',
|
|
35
|
+
defaultValues: { identifierType: '', identifierValue: '' },
|
|
36
|
+
resolver: zodResolver(HIEFormSchema),
|
|
37
|
+
});
|
|
38
|
+
const {
|
|
39
|
+
hieClientRegistry: { identifierTypes },
|
|
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
|
+
|
|
74
|
+
return (
|
|
75
|
+
<form onSubmit={handleSubmit(onSubmit)} className={styles.hieContainer}>
|
|
76
|
+
<h3 className={styles.productiveHeading02} style={{ color: '#161616' }}>
|
|
77
|
+
{t('patientVerificationFromHIE', 'Patient verification from HIE')}
|
|
78
|
+
</h3>
|
|
79
|
+
<span className={styles.label01}>
|
|
80
|
+
{t('allFieldsRequiredText', 'All fields are required unless marked optional')}
|
|
81
|
+
</span>
|
|
82
|
+
<Tile className={styles.grid}>
|
|
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
|
+
)}
|
|
100
|
+
/>
|
|
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
|
+
)}
|
|
117
|
+
/>
|
|
118
|
+
<Button
|
|
119
|
+
onClick={handleSubmit(onSubmit)}
|
|
120
|
+
renderIcon={(props) => <Search size={24} {...props} />}
|
|
121
|
+
iconDescription={t('searchRegistry', 'Search registry')}
|
|
122
|
+
size="md"
|
|
123
|
+
disabled={!watch('identifierType') || !watch('identifierValue') || formState.isSubmitting}
|
|
124
|
+
kind="tertiary">
|
|
125
|
+
{formState.isSubmitting
|
|
126
|
+
? t('searchingRegistry', 'Searching registry...')
|
|
127
|
+
: t('searchRegistry', 'Search registry')}
|
|
128
|
+
</Button>
|
|
129
|
+
</Tile>
|
|
130
|
+
</form>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export default HIEClientRegistry;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
@use '@carbon/type';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.hieContainer {
|
|
6
|
+
margin-bottom: layout.$layout-01;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.productiveHeading02 {
|
|
10
|
+
@include type.type-style('heading-compact-02');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.label01 {
|
|
14
|
+
@include type.type-style('label-01');
|
|
15
|
+
margin-top: layout.$spacing-05;
|
|
16
|
+
margin-bottom: layout.$spacing-05;
|
|
17
|
+
color: colors.$gray-50;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Tablet viewport */
|
|
21
|
+
:global(.omrs-breakpoint-lt-desktop) {
|
|
22
|
+
.grid {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
row-gap: layout.$spacing-05;
|
|
26
|
+
margin: layout.$spacing-01 0;
|
|
27
|
+
padding-bottom: layout.$layout-02;
|
|
28
|
+
column-gap: layout.$spacing-05;
|
|
29
|
+
|
|
30
|
+
& > button {
|
|
31
|
+
width: 50%;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Desktop viewport */
|
|
37
|
+
:global(.omrs-breakpoint-gt-tablet) {
|
|
38
|
+
.grid {
|
|
39
|
+
margin: layout.$spacing-01 0;
|
|
40
|
+
padding-bottom: layout.$layout-02;
|
|
41
|
+
display: grid;
|
|
42
|
+
grid-template-columns: 1fr 1fr 0.5fr;
|
|
43
|
+
column-gap: layout.$spacing-05;
|
|
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
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
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 Social Health Authority Unique Identification Number to HIE Patient ID
|
|
87
|
+
// See https://github.com/palladiumkenya/openmrs-module-kenyaemr/blob/1e1d281eaba8041c45318e60ca0730449b8e4197/api/src/main/distro/metadata/identifierTypes.xml#L33
|
|
88
|
+
updatedIdentifiers.socialHealthAuthorityIdentificationNumber = {
|
|
89
|
+
...currentFormValues.identifiers['socialHealthAuthorityIdentificationNumber'],
|
|
90
|
+
identifierValue: hiePatient.id,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Map fhir.Patient.Identifier to identifiers
|
|
94
|
+
hiePatient.identifier?.forEach((identifier) => {
|
|
95
|
+
const system = identifier.system?.split('/').pop();
|
|
96
|
+
if (system && this.config.identifierMap[system]) {
|
|
97
|
+
const key = this.config.identifierMap[system];
|
|
98
|
+
updatedIdentifiers[key] = {
|
|
99
|
+
...currentFormValues.identifiers[key],
|
|
100
|
+
identifierValue: identifier.value || '',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Filter out undefined keys and values
|
|
106
|
+
Object.keys(updatedIdentifiers).forEach((key) => {
|
|
107
|
+
if (updatedIdentifiers[key] === undefined || updatedIdentifiers[key].identifierValue === undefined) {
|
|
108
|
+
delete updatedIdentifiers[key];
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return updatedIdentifiers;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private mapExtensionsToAddress(extensions: HIEPatient['extension']): Record<string, string> {
|
|
116
|
+
return extensions.reduce<Record<string, string>>((acc, ext) => {
|
|
117
|
+
const identifierType = ext.url.split('/').pop();
|
|
118
|
+
const mappedKey = this.config.addressHierarchyMap[identifierType];
|
|
119
|
+
|
|
120
|
+
if (mappedKey && ext.valueString) {
|
|
121
|
+
acc[mappedKey] = capitalize(ext.valueString);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return acc;
|
|
125
|
+
}, {});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Update MapperConfig interface in hie-types.ts
|
|
130
|
+
|
|
131
|
+
const mapperConfig: MapperConfig = {
|
|
132
|
+
teleComMap: {
|
|
133
|
+
email: 'b8d0b331-1d2d-4a9a-b741-1816f498bdb6',
|
|
134
|
+
phone: 'b2c38640-2603-4629-aebd-3b54f33f1e3a',
|
|
135
|
+
},
|
|
136
|
+
addressHierarchyMap: {
|
|
137
|
+
county: 'countyDistrict',
|
|
138
|
+
'sub-county': 'stateProvince',
|
|
139
|
+
ward: 'address4',
|
|
140
|
+
},
|
|
141
|
+
addressMap: {},
|
|
142
|
+
// Map FHIR Patient identifiers to identifiers, at the moment HIE teams returns null for all identifiers type codings
|
|
143
|
+
identifierMap: {},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Create instances
|
|
147
|
+
const hieApiClient = new HealthInformationExchangeClient<HIEPatient | ErrorResponse>();
|
|
148
|
+
const patientMapper = new PatientMapper(mapperConfig);
|
|
149
|
+
|
|
150
|
+
// Exported functions
|
|
151
|
+
export const fetchPatientFromHIE = async (
|
|
152
|
+
identifierType: string,
|
|
153
|
+
identifierValue: string,
|
|
154
|
+
): Promise<HIEPatient | ErrorResponse> => {
|
|
155
|
+
return hieApiClient.fetchResource('Patient', { [identifierType]: identifierValue });
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const mapHIEPatientToFormValues = (hiePatient: HIEPatient, currentFormValues: FormValues): FormValues => {
|
|
159
|
+
return patientMapper.mapHIEPatientToFormValues(hiePatient, currentFormValues);
|
|
160
|
+
};
|
|
@@ -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;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type ConceptAnswers,
|
|
7
7
|
type ConceptResponse,
|
|
8
8
|
type FormValues,
|
|
9
|
-
} from '
|
|
9
|
+
} from '../../patient-registration/patient-registration.types';
|
|
10
10
|
|
|
11
11
|
export function searchClientRegistry(
|
|
12
12
|
identifierType: string,
|
|
@@ -108,7 +108,7 @@ export function useGlobalProperties() {
|
|
|
108
108
|
const { data, isLoading, error } = useSWRImmutable(
|
|
109
109
|
`https://afyakenyaidentityapi.health.go.ke/connect/token`,
|
|
110
110
|
swrFetcher,
|
|
111
|
-
{ refreshInterval: 864000 },
|
|
111
|
+
{ refreshInterval: 864000, errorRetryCount: 0 },
|
|
112
112
|
);
|
|
113
113
|
return { data: data, isLoading, error };
|
|
114
114
|
}
|
|
@@ -2,7 +2,7 @@ import { showModal } from '@openmrs/esm-framework';
|
|
|
2
2
|
import { type FormikProps } from 'formik';
|
|
3
3
|
import { type ClientRegistryPatient, type RegistryPatient } from './verification-types';
|
|
4
4
|
import counties from './assets/counties.json';
|
|
5
|
-
import { type FormValues } from '
|
|
5
|
+
import { type FormValues } from '../../patient-registration/patient-registration.types';
|
|
6
6
|
import { capitalize } from 'lodash-es';
|
|
7
7
|
|
|
8
8
|
export function handleClientRegistryResponse(
|
|
@@ -7,7 +7,7 @@ import { searchClientRegistry, useGlobalProperties } from './patient-verificatio
|
|
|
7
7
|
import { showSnackbar, showToast } from '@openmrs/esm-framework';
|
|
8
8
|
import { handleClientRegistryResponse } from './patient-verification-utils';
|
|
9
9
|
import { type FormikProps } from 'formik';
|
|
10
|
-
import { type FormValues } from '
|
|
10
|
+
import { type FormValues } from '../../patient-registration/patient-registration.types';
|
|
11
11
|
|
|
12
12
|
interface PatientVerificationProps {
|
|
13
13
|
props: FormikProps<FormValues>;
|
|
@@ -58,6 +58,9 @@ const PatientVerification: React.FC<PatientVerificationProps> = ({ props }) => {
|
|
|
58
58
|
<h3 className={styles.productiveHeading02} style={{ color: '#161616' }}>
|
|
59
59
|
{t('clientVerificationWithClientRegistry', 'Client verification with client registry')}
|
|
60
60
|
</h3>
|
|
61
|
+
<span className={styles.label01}>
|
|
62
|
+
{t('allFieldsRequiredText', 'All fields are required unless marked optional')}
|
|
63
|
+
</span>
|
|
61
64
|
<div style={{ margin: '1rem 0 1rem' }}>
|
|
62
65
|
<Layer>
|
|
63
66
|
{isLoading && <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />}
|
package/src/{patient-verification → client-registry/patient-verification}/patient-verification.scss
RENAMED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
@use '@carbon/colors';
|
|
2
|
-
@
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
5
|
+
@use '../../patient-registration/patient-registration.scss' as *;
|
|
3
6
|
|
|
4
7
|
/* Desktop */
|
|
5
8
|
:global(.omrs-breakpoint-gt-tablet) {
|
|
@@ -23,3 +26,16 @@
|
|
|
23
26
|
.errorWrapper {
|
|
24
27
|
margin: 0 0 1rem 0;
|
|
25
28
|
}
|
|
29
|
+
|
|
30
|
+
.label01 {
|
|
31
|
+
@include type.type-style('label-01');
|
|
32
|
+
margin-top: layout.$spacing-05;
|
|
33
|
+
margin-bottom: layout.$spacing-05;
|
|
34
|
+
color: $ui-04;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.productiveHeading02 {
|
|
38
|
+
@include type.type-style('heading-compact-02');
|
|
39
|
+
color: $ui-04;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
}
|
|
@@ -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">
|