@ampath/esm-dha-workflow-app 4.0.0-next.4 → 4.0.0-next.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/104.js +2 -0
- package/dist/104.js.LICENSE.txt +9 -0
- package/dist/104.js.map +1 -0
- package/dist/15.js +1 -0
- package/dist/15.js.map +1 -0
- package/dist/246.js +2 -0
- package/dist/246.js.LICENSE.txt +54 -0
- package/dist/246.js.map +1 -0
- package/dist/327.js +1 -0
- package/dist/327.js.map +1 -0
- package/dist/339.js +1 -0
- package/dist/339.js.map +1 -0
- package/dist/701.js +1 -0
- package/dist/701.js.map +1 -0
- package/dist/709.js +1 -0
- package/dist/709.js.map +1 -0
- package/dist/710.js +2 -0
- package/dist/710.js.map +1 -0
- package/dist/729.js +1 -0
- package/dist/729.js.map +1 -0
- package/dist/752.js +1 -0
- package/dist/752.js.map +1 -0
- package/dist/833.js +1 -0
- package/dist/833.js.map +1 -0
- package/dist/91.js +1 -1
- package/dist/91.js.map +1 -1
- package/dist/93.js +1 -0
- package/dist/93.js.map +1 -0
- package/dist/esm-dha-workflow-app.js +1 -0
- package/dist/{openmrs-esm-home-app.js.buildmanifest.json → esm-dha-workflow-app.js.buildmanifest.json} +293 -54
- package/dist/{openmrs-esm-home-app.js.map → esm-dha-workflow-app.js.map} +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +3 -3
- package/src/accounting/accounting.component.tsx +13 -0
- package/src/appointments/appointments.component.tsx +13 -0
- package/src/bookings/daily/daily-bookings.component.scss +38 -0
- package/src/bookings/daily/daily-bookings.component.tsx +138 -0
- package/src/bookings/daily/daily-bookings.resource.ts +27 -0
- package/src/bookings/daily/filters/daily-bookings-filter.component.scss +15 -0
- package/src/bookings/daily/filters/daily-bookings-filter.component.tsx +80 -0
- package/src/bookings/daily/patient-list/daily-bookings-patient-list.component.tsx +97 -0
- package/src/bookings/types/index.ts +68 -0
- package/src/config-schema.ts +132 -32
- package/src/dashboard/dashboard.component.scss +7 -0
- package/src/dashboard/dashboard.component.tsx +63 -0
- package/src/dashboard/overview/overview.component.scss +70 -0
- package/src/dashboard/overview/overview.component.tsx +107 -0
- package/src/dashboard/patient-list/patient-list.component.tsx +41 -0
- package/src/hooks/useActions.ts +165 -0
- package/src/index.ts +23 -2
- package/src/laboratory/laboratory.component.tsx +13 -0
- package/src/left-panel/left-panel.component.tsx +20 -0
- package/src/left-panel/left-panel.scss +42 -0
- package/src/mch/queues/consultation/mch-consultation.tsx +18 -0
- package/src/mch/queues/triage/mch-triage.tsx +15 -0
- package/src/modals/sign-off-modal.scss +7 -0
- package/src/modals/sign-off-modal.tsx +52 -0
- package/src/pharmacy/pharmacy.component.tsx +13 -0
- package/src/registry/client-details/client-details.tsx +40 -0
- package/src/registry/modal/client-details-modal/client-details-modal.scss +28 -0
- package/src/registry/modal/client-details-modal/client-details-modal.tsx +81 -0
- package/src/registry/modal/otp-verification-modal/otp-verification-modal.scss +31 -0
- package/src/registry/modal/otp-verification-modal/otp-verification-modal.tsx +186 -0
- package/src/registry/modal/send-to-triage/send-to-triage.modal.scss +34 -0
- package/src/registry/modal/send-to-triage/send-to-triage.modal.tsx +302 -0
- package/src/registry/payment-details/payment-options/payment-options.tsx +21 -0
- package/src/registry/registry.component.scss +83 -0
- package/src/registry/registry.component.tsx +397 -2
- package/src/registry/registry.resource.ts +60 -0
- package/src/registry/types/index.ts +309 -0
- package/src/registry/utils/error-handler.ts +37 -0
- package/src/registry/utils/format-dependant-display-data.ts +8 -0
- package/src/registry/utils/hie-adapter.ts +56 -0
- package/src/registry/utils/hie-client-adapter.ts +309 -0
- package/src/registry/utils/mask-data.ts +21 -0
- package/src/resources/hie-amrs-automatic-registration.service.ts +16 -0
- package/src/resources/identifier-types.ts +27 -0
- package/src/resources/patient-resource.ts +62 -0
- package/src/resources/patient-search.resource.ts +22 -0
- package/src/resources/queue.resource.ts +60 -0
- package/src/resources/visit.resource.ts +38 -0
- package/src/root.component.tsx +40 -30
- package/src/root.scss +5 -9
- package/src/routes.json +43 -4
- package/src/service-queues/action-button.component.tsx +34 -0
- package/src/service-queues/action-overflow-menu-item.component.tsx +34 -0
- package/src/service-queues/consultation/consultation.component.scss +7 -0
- package/src/service-queues/consultation/consultation.component.tsx +15 -0
- package/src/service-queues/metrics/metrics-cards/attended-patients.extension.tsx +38 -0
- package/src/service-queues/metrics/metrics-cards/metrics-card.component.tsx +86 -0
- package/src/service-queues/metrics/metrics-cards/metrics-card.scss +106 -0
- package/src/service-queues/metrics/metrics-cards/waiting-patients.extension.tsx +34 -0
- package/src/service-queues/metrics/metrics-container.component.tsx +23 -0
- package/src/service-queues/metrics/metrics-container.scss +36 -0
- package/src/service-queues/metrics/metrics.resource.ts +65 -0
- package/src/service-queues/modals/move/move-patient.component.scss +35 -0
- package/src/service-queues/modals/move/move-patient.component.tsx +138 -0
- package/src/service-queues/modals/serve/serve-patient.comppnent.scss +0 -0
- package/src/service-queues/modals/serve/serve-patient.comppnent.tsx +80 -0
- package/src/service-queues/modals/sign-off/sign-off.modal.scss +0 -0
- package/src/service-queues/modals/sign-off/sign-off.modal.tsx +79 -0
- package/src/service-queues/modals/transition/transition-patient.component.scss +0 -0
- package/src/service-queues/modals/transition/transition-patient.component.tsx +122 -0
- package/src/service-queues/queue-list/queue-list.component.scss +19 -0
- package/src/service-queues/queue-list/queue-list.component.tsx +169 -0
- package/src/service-queues/queue-room.component.tsx +39 -0
- package/src/service-queues/room/room.component.tsx +58 -0
- package/src/service-queues/service-queue/service-queue.component.scss +14 -0
- package/src/service-queues/service-queue/service-queue.component.tsx +245 -0
- package/src/service-queues/service-queue/stats/stat-card/stat-card.component.scss +10 -0
- package/src/service-queues/service-queue/stats/stat-card/stat-card.component.tsx +23 -0
- package/src/service-queues/service-queue/stats/stat-details/stat-details.component.scss +7 -0
- package/src/service-queues/service-queue/stats/stat-details/stat-details.component.tsx +34 -0
- package/src/service-queues/service-queue.scss +27 -0
- package/src/service-queues/service-queue.tsx +31 -0
- package/src/service-queues/service-queues.resource.ts +177 -0
- package/src/service-queues/service.resource.ts +28 -0
- package/src/shared/constants/civil-status.ts +29 -0
- package/src/shared/constants/concepts.ts +30 -0
- package/src/shared/constants/index.ts +1 -0
- package/src/shared/constants/person-attributes.ts +33 -0
- package/src/shared/services/location.resource.ts +9 -0
- package/src/shared/ui/otp-input/otp-input.component.scss +14 -0
- package/src/shared/ui/otp-input/otp-input.component.tsx +90 -0
- package/src/shared/ui/timer/timer.component.scss +5 -0
- package/src/shared/ui/timer/timer.component.tsx +40 -0
- package/src/shared/utils/get-base-url.ts +17 -0
- package/src/side-nav-menu/nav-link-config.ts +82 -0
- package/src/side-nav-menu/nav-links.tsx +31 -11
- package/src/triage/metrics/attended-patients.extension.tsx +42 -0
- package/src/triage/metrics/metrics.scss +36 -0
- package/src/triage/metrics/triage-metrics.component.tsx +21 -0
- package/src/triage/metrics/waiting-patients.extension.tsx +39 -0
- package/src/triage/room/room.scss +29 -0
- package/src/triage/triage.component.tsx +15 -0
- package/src/triage/triage.resource.ts +19 -0
- package/src/triage/types.ts +16 -0
- package/src/types/types.ts +128 -0
- package/dist/561.js +0 -2
- package/dist/561.js.map +0 -1
- package/dist/70.js +0 -1
- package/dist/70.js.map +0 -1
- package/dist/731.js +0 -2
- package/dist/731.js.LICENSE.txt +0 -39
- package/dist/731.js.map +0 -1
- package/dist/819.js +0 -1
- package/dist/819.js.map +0 -1
- package/dist/openmrs-esm-home-app.js +0 -1
- package/src/boxes/extensions/blue-box.component.tsx +0 -15
- package/src/boxes/extensions/box.scss +0 -23
- package/src/boxes/extensions/brand-box.component.tsx +0 -15
- package/src/boxes/extensions/red-box.component.tsx +0 -15
- package/src/boxes/slot/boxes.component.tsx +0 -25
- package/src/boxes/slot/boxes.scss +0 -29
- package/src/greeter/greeter.component.tsx +0 -42
- package/src/greeter/greeter.scss +0 -20
- package/src/greeter/greeter.test.tsx +0 -28
- package/src/patient-getter/patient-getter.component.tsx +0 -40
- package/src/patient-getter/patient-getter.resource.ts +0 -39
- package/src/patient-getter/patient-getter.scss +0 -16
- package/src/patient-getter/patient-getter.test.tsx +0 -40
- package/src/resources/resources.component.tsx +0 -56
- package/src/resources/resources.scss +0 -68
- /package/dist/{561.js.LICENSE.txt → 710.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const maskValue = (value: string): string => {
|
|
2
|
+
let arrValue = value.split('');
|
|
3
|
+
for (let i = 0; i < value.length; i++) {
|
|
4
|
+
if (i % 2 === 0) {
|
|
5
|
+
arrValue[i] = '*';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
const maskedValue = arrValue.join('');
|
|
9
|
+
return maskedValue;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const maskExceptFirstAndLast = (value: string): string => {
|
|
13
|
+
let arrValue = value.split('');
|
|
14
|
+
for (let i = 0; i < value.length; i++) {
|
|
15
|
+
if (!(i == 0 || i === value.length - 1)) {
|
|
16
|
+
arrValue[i] = '*';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const maskedValue = arrValue.join('');
|
|
20
|
+
return maskedValue;
|
|
21
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Patient } from '@openmrs/esm-framework';
|
|
2
|
+
import { type HieClient } from '../registry/types';
|
|
3
|
+
import { generateAmrsPersonPayload } from '../registry/utils/hie-client-adapter';
|
|
4
|
+
import { createPatient, generatePatientIdentifiers } from './patient-resource';
|
|
5
|
+
|
|
6
|
+
export async function registerHieClientInAmrs(client: HieClient, identifierLocation: string): Promise<Patient> {
|
|
7
|
+
const createPersonPayload = generateAmrsPersonPayload(client);
|
|
8
|
+
const identifiers = await generatePatientIdentifiers(identifierLocation, client);
|
|
9
|
+
const resp = await createPatient({
|
|
10
|
+
person: createPersonPayload,
|
|
11
|
+
identifiers: identifiers,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const res = await resp.json();
|
|
15
|
+
return res;
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const HOUSE_HOLD_NUMBER_UUID = 'bb74b20e-dcee-4f59-bdf1-2dffc3abf106';
|
|
2
|
+
const SHA_UUID = 'cf5362b2-8049-4442-b3c6-36f870e320cb';
|
|
3
|
+
const CLIENT_REGISTRY_NO_UUID = 'e88dc246-3614-4ee3-8141-1f2a83054e72';
|
|
4
|
+
const NATIONAL_ID_UUID = '58a47054-1359-11df-a1f1-0026b9348838';
|
|
5
|
+
const PROVIDER_NATIONAL_ID_UUID = '4550df92-c684-4597-8ab8-d6b10eabdcfb';
|
|
6
|
+
const REFUGEE_ID_UUID = '465e81af-8d69-47e9-9127-53a94adc75fb';
|
|
7
|
+
const MANDATE_NUMBER_UUID = 'aae2d097-20ba-43ca-9b71-fd8296068f39';
|
|
8
|
+
const ALIEN_ID_UUID = '12f5b147-3403-4a73-913d-7ded9ffec094';
|
|
9
|
+
const TEMPORARY_DEPENDANT_ID_UUID = 'a3d34214-93e8-4faf-bf4d-0272eee079eb';
|
|
10
|
+
const AMRS_UNIVERSAL_ID_UUID = '58a4732e-1359-11df-a1f1-0026b9348838';
|
|
11
|
+
const UPI_NUMBER_UUID = 'cba702b9-4664-4b43-83f1-9ab473cbd64d';
|
|
12
|
+
const BIRTH_CERTIFICATE_NUMBER_UUID = '7924e13b-131a-4da8-8efa-e294184a1b0d';
|
|
13
|
+
|
|
14
|
+
export const IdentifierTypesUuids = {
|
|
15
|
+
HOUSE_HOLD_NUMBER_UUID,
|
|
16
|
+
SHA_UUID,
|
|
17
|
+
CLIENT_REGISTRY_NO_UUID,
|
|
18
|
+
NATIONAL_ID_UUID,
|
|
19
|
+
PROVIDER_NATIONAL_ID_UUID,
|
|
20
|
+
REFUGEE_ID_UUID,
|
|
21
|
+
MANDATE_NUMBER_UUID,
|
|
22
|
+
ALIEN_ID_UUID,
|
|
23
|
+
TEMPORARY_DEPENDANT_ID_UUID,
|
|
24
|
+
AMRS_UNIVERSAL_ID_UUID,
|
|
25
|
+
UPI_NUMBER_UUID,
|
|
26
|
+
BIRTH_CERTIFICATE_NUMBER_UUID,
|
|
27
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import { type HieClient, type CreatePatientDto, HieIdentificationType } from '../registry/types';
|
|
3
|
+
import { IdentifierTypesUuids } from './identifier-types';
|
|
4
|
+
import { getAmrsIdentifierTypeUuid } from '../registry/utils/hie-client-adapter';
|
|
5
|
+
import { getSubDomainUrl } from '../shared/utils/get-base-url';
|
|
6
|
+
|
|
7
|
+
export async function createPatient(payload: CreatePatientDto) {
|
|
8
|
+
return await openmrsFetch(`${restBaseUrl}/patient`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
headers: {
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
},
|
|
13
|
+
body: payload,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const generatePatientIdentifiers = async (identifierLocation: string, client: HieClient) => {
|
|
18
|
+
const amrsUniverSalId = await generateAmrsUniversalIdentifier();
|
|
19
|
+
const identifiers = generateAmrsCreatePatientIdentifiersPayload(client, identifierLocation);
|
|
20
|
+
identifiers.push({
|
|
21
|
+
identifierType: IdentifierTypesUuids.AMRS_UNIVERSAL_ID_UUID,
|
|
22
|
+
identifier: amrsUniverSalId,
|
|
23
|
+
location: identifierLocation,
|
|
24
|
+
preferred: true,
|
|
25
|
+
});
|
|
26
|
+
return identifiers;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function generateAmrsCreatePatientIdentifiersPayload(hieClient: HieClient, identifierLocation: string) {
|
|
30
|
+
const identifiers = [];
|
|
31
|
+
// add CR number
|
|
32
|
+
identifiers.push({
|
|
33
|
+
identifierType: getAmrsIdentifierTypeUuid(HieIdentificationType.Cr),
|
|
34
|
+
identifier: hieClient.id,
|
|
35
|
+
location: identifierLocation,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// add main identifier
|
|
39
|
+
identifiers.push({
|
|
40
|
+
identifierType: getAmrsIdentifierTypeUuid(hieClient.identification_type),
|
|
41
|
+
identifier: hieClient.identification_number,
|
|
42
|
+
location: identifierLocation,
|
|
43
|
+
});
|
|
44
|
+
return identifiers;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function generateAmrsUniversalIdentifier() {
|
|
48
|
+
const subDomainUrl = await getSubDomainUrl();
|
|
49
|
+
const abortController = new AbortController();
|
|
50
|
+
const resp = await openmrsFetch(`${subDomainUrl}/amrs-id-generator/generateidentifier`, {
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
},
|
|
54
|
+
method: 'POST',
|
|
55
|
+
body: {
|
|
56
|
+
user: 1,
|
|
57
|
+
},
|
|
58
|
+
signal: abortController.signal,
|
|
59
|
+
});
|
|
60
|
+
const data = await resp.json();
|
|
61
|
+
return data['identifier'] ?? '';
|
|
62
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import { type PatientSearchResponse } from '../registry/types';
|
|
3
|
+
|
|
4
|
+
export async function searchPatientByCrNumber<T>(cr_id: string): Promise<PatientSearchResponse> {
|
|
5
|
+
const url = `${restBaseUrl}/patient`;
|
|
6
|
+
const params = {
|
|
7
|
+
q: cr_id,
|
|
8
|
+
v: 'full',
|
|
9
|
+
includeDead: 'true',
|
|
10
|
+
limit: '10',
|
|
11
|
+
totalCount: 'true',
|
|
12
|
+
};
|
|
13
|
+
const queryString = new URLSearchParams(params).toString();
|
|
14
|
+
const response = await openmrsFetch(`${url}?${queryString}`);
|
|
15
|
+
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
const errorText = await response.text();
|
|
18
|
+
throw new Error(`Request failed with ${response.status}: ${errorText}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return response.json();
|
|
22
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import { type QueueEntryDto, type ServiceQueueApiResponse } from '../registry/types';
|
|
3
|
+
import { getLocationByUuid } from '../shared/services/location.resource';
|
|
4
|
+
|
|
5
|
+
export async function fetchServiceQueuesByLocationUuid<T>(locationUuid: string): Promise<ServiceQueueApiResponse> {
|
|
6
|
+
const url = `${restBaseUrl}/queue`;
|
|
7
|
+
const params = {
|
|
8
|
+
v: 'custom:(uuid,display,name,description,service:(uuid,display),allowedPriorities:(uuid,display),allowedStatuses:(uuid,display),location:(uuid,display))',
|
|
9
|
+
location: locationUuid,
|
|
10
|
+
};
|
|
11
|
+
const queryString = new URLSearchParams(params).toString();
|
|
12
|
+
const response = await openmrsFetch(`${url}?${queryString}`);
|
|
13
|
+
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
const errorText = await response.text();
|
|
16
|
+
throw new Error(`Request failed with ${response.status}: ${errorText}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return response.json();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function createQueueEntry(createQueueEntryDto: QueueEntryDto) {
|
|
23
|
+
const url = `${restBaseUrl}/visit-queue-entry`;
|
|
24
|
+
const response = await openmrsFetch(url, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'content-type': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify(createQueueEntryDto),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const errorText = await response.text();
|
|
34
|
+
throw new Error(`Request failed with ${response.status}: ${errorText}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return response.json();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function getFacilityServiceQueues(locationUuid: string) {
|
|
41
|
+
const currentLocation = await getLocationByUuid(locationUuid);
|
|
42
|
+
if (!currentLocation) return;
|
|
43
|
+
const parentLocation = currentLocation.parentLocation;
|
|
44
|
+
//get child locations
|
|
45
|
+
if (!parentLocation) return;
|
|
46
|
+
const parentLocationDetail = await getLocationByUuid(parentLocation.uuid);
|
|
47
|
+
const childLocations = parentLocationDetail.childLocations ?? [];
|
|
48
|
+
const childLocationUuids =
|
|
49
|
+
childLocations.map((c) => {
|
|
50
|
+
return c.uuid;
|
|
51
|
+
}) ?? [];
|
|
52
|
+
let sqs = [];
|
|
53
|
+
if (childLocationUuids.length > 0) {
|
|
54
|
+
for (let i = 0; i < childLocationUuids.length; i++) {
|
|
55
|
+
const s = await fetchServiceQueuesByLocationUuid(childLocationUuids[i]);
|
|
56
|
+
sqs = [...sqs, ...s.results];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return sqs;
|
|
60
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import { EndVisitDto, type CreateVisitDto } from '../registry/types';
|
|
3
|
+
|
|
4
|
+
export async function createVisit(createVisitDto: CreateVisitDto) {
|
|
5
|
+
const url = `${restBaseUrl}/visit`;
|
|
6
|
+
const response = await openmrsFetch(url, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
headers: {
|
|
9
|
+
'content-type': 'application/json',
|
|
10
|
+
},
|
|
11
|
+
body: JSON.stringify(createVisitDto),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
const errorText = await response.text();
|
|
16
|
+
throw new Error(`Request failed with ${response.status}: ${errorText}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return response.json();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function endVisit(visitUuid: string, endVisitDto: EndVisitDto) {
|
|
23
|
+
const url = `${restBaseUrl}/visit/${visitUuid}`;
|
|
24
|
+
const response = await openmrsFetch(url, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'content-type': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify(endVisitDto),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const errorText = await response.text();
|
|
34
|
+
throw new Error(`Request failed with ${response.status}: ${errorText}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return response.json();
|
|
38
|
+
}
|
package/src/root.component.tsx
CHANGED
|
@@ -1,38 +1,48 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* From here, the application is pretty typical React, but with lots of
|
|
3
|
-
* support from `@openmrs/esm-framework`. Check out `Greeter` to see
|
|
4
|
-
* usage of the configuration system, and check out `PatientGetter` to
|
|
5
|
-
* see data fetching using the OpenMRS FHIR API.
|
|
6
|
-
*
|
|
7
|
-
* Check out the Config docs:
|
|
8
|
-
* https://openmrs.github.io/openmrs-esm-core/#/main/config
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
import React from 'react';
|
|
12
|
-
import { useTranslation } from 'react-i18next';
|
|
13
|
-
import { Boxes } from './boxes/slot/boxes.component';
|
|
14
|
-
import Greeter from './greeter/greeter.component';
|
|
15
|
-
import PatientGetter from './patient-getter/patient-getter.component';
|
|
16
|
-
import Resources from './resources/resources.component';
|
|
17
2
|
import styles from './root.scss';
|
|
3
|
+
import { useLeftNav, WorkspaceContainer } from '@openmrs/esm-framework';
|
|
4
|
+
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
|
5
|
+
import RegistryComponent from './registry/registry.component';
|
|
6
|
+
import LeftPanel from './left-panel/left-panel.component';
|
|
7
|
+
import Triage from './triage/triage.component';
|
|
8
|
+
import LaboratoryComponent from './laboratory/laboratory.component';
|
|
9
|
+
import AppointmentsComponent from './appointments/appointments.component';
|
|
10
|
+
import PharmacyComponent from './pharmacy/pharmacy.component';
|
|
11
|
+
import Consultation from './service-queues/consultation/consultation.component';
|
|
12
|
+
import Dashboard from './dashboard/dashboard.component';
|
|
13
|
+
import AccountingComponent from './accounting/accounting.component';
|
|
14
|
+
import DailyBookings from './bookings/daily/daily-bookings.component';
|
|
15
|
+
import MchTriage from './mch/queues/triage/mch-triage';
|
|
16
|
+
import MchConsultation from './mch/queues/consultation/mch-consultation';
|
|
18
17
|
|
|
19
18
|
const Root: React.FC = () => {
|
|
20
|
-
const
|
|
21
|
-
|
|
19
|
+
const spaBasePath = window.spaBase;
|
|
20
|
+
useLeftNav({
|
|
21
|
+
name: 'dha-workflow-slot',
|
|
22
|
+
basePath: spaBasePath,
|
|
23
|
+
mode: 'normal',
|
|
24
|
+
});
|
|
22
25
|
return (
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
<BrowserRouter basename={`${window.spaBase}/home`}>
|
|
27
|
+
<LeftPanel />
|
|
28
|
+
<main className={styles.container}>
|
|
29
|
+
<Routes>
|
|
30
|
+
<Route path="dashboard" element={<Dashboard />} />
|
|
31
|
+
<Route path="registry" element={<RegistryComponent />} />
|
|
32
|
+
<Route path="consultation" element={<Consultation />} />
|
|
33
|
+
<Route path="triage" element={<Triage />} />
|
|
34
|
+
<Route path="laboratory" element={<LaboratoryComponent />} />
|
|
35
|
+
<Route path="pharmacy" element={<PharmacyComponent />} />
|
|
36
|
+
<Route path="appointments" element={<AppointmentsComponent />} />
|
|
37
|
+
<Route path="accounting" element={<AccountingComponent />} />
|
|
38
|
+
<Route path="bookings/daily" element={<DailyBookings />} />
|
|
39
|
+
<Route path="mch/triage" element={<MchTriage />} />
|
|
40
|
+
<Route path="mch/consultation" element={<MchConsultation />} />
|
|
41
|
+
<Route path="*" element={<RegistryComponent />} />
|
|
42
|
+
</Routes>
|
|
43
|
+
</main>
|
|
44
|
+
<WorkspaceContainer contextKey="home" />
|
|
45
|
+
</BrowserRouter>
|
|
36
46
|
);
|
|
37
47
|
};
|
|
38
48
|
|
package/src/root.scss
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
1
2
|
@use '@carbon/layout';
|
|
2
|
-
@use '@carbon/type';
|
|
3
3
|
|
|
4
4
|
.container {
|
|
5
|
-
|
|
5
|
+
background-color: colors.$white-0;
|
|
6
|
+
height: calc(100vh - layout.$spacing-09);
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
.
|
|
9
|
-
|
|
10
|
-
margin: layout.$spacing-05 0;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.explainer {
|
|
14
|
-
margin-bottom: layout.$spacing-07;
|
|
9
|
+
:global(.omrs-breakpoint-gt-tablet) .container {
|
|
10
|
+
margin-left: var(--omrs-sidenav-width);
|
|
15
11
|
}
|
package/src/routes.json
CHANGED
|
@@ -1,19 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.openmrs.org/routes.schema.json",
|
|
3
3
|
"backendDependencies": {},
|
|
4
|
-
|
|
4
|
+
"pages": [
|
|
5
|
+
{
|
|
6
|
+
"component": "root",
|
|
7
|
+
"route": "home",
|
|
8
|
+
"online": true,
|
|
9
|
+
"offline": true
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"extensions": [
|
|
5
13
|
{
|
|
6
14
|
"component": "workflowRegistryLink",
|
|
7
15
|
"name": "workflow-registry-link",
|
|
8
16
|
"slot": "app-menu-slot",
|
|
9
17
|
"online": true,
|
|
10
18
|
"offline": true
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"component": "navLinks",
|
|
22
|
+
"name": "side-nav-workflow-links",
|
|
23
|
+
"slot": "dha-workflow-slot",
|
|
24
|
+
"online": true,
|
|
25
|
+
"offline": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "metrics-card-patients-in-waiting",
|
|
29
|
+
"component": "waitingPatientsExtension",
|
|
30
|
+
"slot": "clinic-metrics-slot",
|
|
31
|
+
"order": 1
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "metrics-card-patients-attended-to",
|
|
35
|
+
"component": "attendedToPatientsExtension",
|
|
36
|
+
"slot": "clinic-metrics-slot",
|
|
37
|
+
"order": 2
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "metrics-card-patients-in-waiting",
|
|
41
|
+
"component": "triageWaitingPatientsExtension",
|
|
42
|
+
"slot": "triage-metrics-slot",
|
|
43
|
+
"order": 1
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "metrics-card-patients-attended-to",
|
|
47
|
+
"component": "triageAttendedToPatientsExtension",
|
|
48
|
+
"slot": "triage-metrics-slot",
|
|
49
|
+
"order": 2
|
|
11
50
|
}
|
|
12
51
|
],
|
|
13
|
-
"
|
|
52
|
+
"modals": [
|
|
14
53
|
{
|
|
15
|
-
"
|
|
16
|
-
"
|
|
54
|
+
"name": "sign-off-queue-entry-modal",
|
|
55
|
+
"component": "signOffModal"
|
|
17
56
|
}
|
|
18
57
|
]
|
|
19
58
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Button } from "@carbon/react";
|
|
2
|
+
import { useLayoutType, isDesktop } from "@openmrs/esm-framework";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { useActionPropsByKey } from "../hooks/useActions";
|
|
6
|
+
import { type QueueEntryAction } from "../config-schema";
|
|
7
|
+
import { type QueueEntry } from "../types/types";
|
|
8
|
+
|
|
9
|
+
export function ActionButton({ actionKey, queueEntry }: { actionKey: QueueEntryAction; queueEntry: QueueEntry }) {
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
const layout = useLayoutType();
|
|
12
|
+
const actionPropsByKey = useActionPropsByKey();
|
|
13
|
+
|
|
14
|
+
const actionProps = actionPropsByKey[actionKey];
|
|
15
|
+
if (!actionProps) {
|
|
16
|
+
console.error(`Service queue table configuration uses unknown action in 'action.buttons': ${actionKey}`);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (actionProps.showIf && !actionProps.showIf(queueEntry)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Button
|
|
26
|
+
key={actionKey}
|
|
27
|
+
kind="ghost"
|
|
28
|
+
aria-label={t(actionProps.label, actionProps.text)}
|
|
29
|
+
onClick={() => actionProps.onClick(queueEntry)}
|
|
30
|
+
size={isDesktop(layout) ? 'sm' : 'lg'}>
|
|
31
|
+
{t(actionProps.label, actionProps.text)}
|
|
32
|
+
</Button>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { OverflowMenuItem } from "@carbon/react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { type QueueEntryAction } from "../config-schema";
|
|
5
|
+
import { useActionPropsByKey } from "../hooks/useActions";
|
|
6
|
+
import { type QueueEntry } from "../types/types";
|
|
7
|
+
import styles from './service-queue.scss';
|
|
8
|
+
|
|
9
|
+
export function ActionOverflowMenuItem({ actionKey, queueEntry }: { actionKey: QueueEntryAction; queueEntry: QueueEntry }) {
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
const actionPropsByKey = useActionPropsByKey();
|
|
12
|
+
|
|
13
|
+
const actionProps = actionPropsByKey[actionKey];
|
|
14
|
+
if (!actionProps) {
|
|
15
|
+
console.error(`Service queue table configuration uses unknown action in 'action.overflowMenu': ${actionKey}`);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (actionProps.showIf && !actionProps.showIf(queueEntry)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<OverflowMenuItem
|
|
25
|
+
key={actionKey}
|
|
26
|
+
className={styles.menuItem}
|
|
27
|
+
aria-label={t(actionProps.label, actionProps.text)}
|
|
28
|
+
hasDivider
|
|
29
|
+
isDelete={actionProps.isDelete}
|
|
30
|
+
onClick={() => actionProps.onClick(queueEntry)}
|
|
31
|
+
itemText={t(actionProps.label, actionProps.text)}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { QUEUE_SERVICE_UUIDS } from '../../shared/constants/concepts';
|
|
3
|
+
import ServiceQueueComponent from '../service-queue/service-queue.component';
|
|
4
|
+
const Consultation: React.FC = () => {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
<ServiceQueueComponent
|
|
8
|
+
serviceTypeUuid={QUEUE_SERVICE_UUIDS.CLINICAL_CONSULTATION_SERVICE_UUID}
|
|
9
|
+
title="Clinical Consultation"
|
|
10
|
+
/>
|
|
11
|
+
</>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default Consultation;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { MetricsCard, MetricsCardHeader, MetricsCardBody, MetricsCardItem } from './metrics-card.component';
|
|
4
|
+
import { useSession } from '@openmrs/esm-framework';
|
|
5
|
+
import { QUEUE_SERVICE_UUIDS } from '../../../shared/constants/concepts';
|
|
6
|
+
import { getServiceQueueByLocationUuid } from '../../service-queues.resource';
|
|
7
|
+
import { type QueueEntryResult } from '../../../registry/types';
|
|
8
|
+
|
|
9
|
+
export default function AttendedToPatientsExtension() {
|
|
10
|
+
const { t } = useTranslation();
|
|
11
|
+
|
|
12
|
+
const [consultationQueueEntries, setConsultationQueueEntries] = useState<QueueEntryResult[]>([]);
|
|
13
|
+
const session = useSession();
|
|
14
|
+
const locationUuid = session.sessionLocation.uuid;
|
|
15
|
+
const consultationServiceUuid = QUEUE_SERVICE_UUIDS.CLINICAL_CONSULTATION_SERVICE_UUID;
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
getConsultationEntryQueues();
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const getConsultationEntryQueues = async () => {
|
|
21
|
+
const res = await getServiceQueueByLocationUuid(consultationServiceUuid, locationUuid);
|
|
22
|
+
setConsultationQueueEntries(res);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const attendedToPatientsCount = consultationQueueEntries?.filter((p) => p.status === 'COMPLETED').length ?? 0;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<MetricsCard>
|
|
29
|
+
<MetricsCardHeader title={t('patientsAttendedTo', 'Patients attended to')} />
|
|
30
|
+
<MetricsCardBody>
|
|
31
|
+
<MetricsCardItem
|
|
32
|
+
label={t('patients', 'Patients')}
|
|
33
|
+
value={attendedToPatientsCount ? attendedToPatientsCount : '--'}
|
|
34
|
+
/>
|
|
35
|
+
</MetricsCardBody>
|
|
36
|
+
</MetricsCard>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { Layer, Tile } from '@carbon/react';
|
|
4
|
+
import { ArrowRight } from '@carbon/react/icons';
|
|
5
|
+
import { ConfigurableLink } from '@openmrs/esm-framework';
|
|
6
|
+
import styles from './metrics-card.scss';
|
|
7
|
+
|
|
8
|
+
interface MetricsCardProps {
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const MetricsCard: React.FC<MetricsCardProps> = ({ children }) => {
|
|
13
|
+
return (
|
|
14
|
+
<Layer
|
|
15
|
+
className={classNames({
|
|
16
|
+
cardWithChildren: children,
|
|
17
|
+
})}>
|
|
18
|
+
<Tile className={styles.tileContainer}>{children}</Tile>
|
|
19
|
+
</Layer>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
interface MetricsCardHeaderProps {
|
|
24
|
+
title: string;
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
link?: string;
|
|
27
|
+
linkText?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const MetricsCardHeader: React.FC<MetricsCardHeaderProps> = ({ title, children, link, linkText }) => {
|
|
31
|
+
return (
|
|
32
|
+
<div className={styles.tileHeader}>
|
|
33
|
+
<div className={styles.headerLabelContainer}>
|
|
34
|
+
<label className={styles.headerLabel}>{title}</label>
|
|
35
|
+
{children}
|
|
36
|
+
</div>
|
|
37
|
+
{link && (
|
|
38
|
+
<div className={styles.link}>
|
|
39
|
+
<ConfigurableLink className={styles.link} to={link}>
|
|
40
|
+
{linkText}
|
|
41
|
+
</ConfigurableLink>
|
|
42
|
+
<ArrowRight size={16} />
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
interface MetricsCardBodyProps {
|
|
50
|
+
children?: React.ReactNode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const MetricsCardBody: React.FC<MetricsCardBodyProps> = ({ children }) => {
|
|
54
|
+
return <div className={styles.metricsContainer}>{children}</div>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
interface MetricsCardItemProps {
|
|
58
|
+
label: string;
|
|
59
|
+
/** If the value is null, the item will not be rendered. */
|
|
60
|
+
value: number | string | null;
|
|
61
|
+
small?: boolean;
|
|
62
|
+
color?: 'default' | 'red';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const MetricsCardItem: React.FC<MetricsCardItemProps> = ({ label, value, small, color }) => {
|
|
66
|
+
if (value === null) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
className={classNames(styles.metricItem, {
|
|
73
|
+
[styles.smallItem]: small,
|
|
74
|
+
[styles.mainItem]: !small,
|
|
75
|
+
})}>
|
|
76
|
+
<span className={styles.metricLabel}>{label}</span>
|
|
77
|
+
<p
|
|
78
|
+
className={classNames(styles.metricValue, {
|
|
79
|
+
[styles.red]: color === 'red',
|
|
80
|
+
[styles.smallValue]: small,
|
|
81
|
+
})}>
|
|
82
|
+
{value}
|
|
83
|
+
</p>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
};
|