@ampath/esm-dha-workflow-app 4.0.0-next.4 → 4.0.0-next.41
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/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/938.js +1 -0
- package/dist/938.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} +294 -55
- 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/mortuary/mortuary.component.tsx +13 -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 +42 -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
package/src/index.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* connects the app shell to the React application(s) that make up this
|
|
5
5
|
* microfrontend.
|
|
6
6
|
*/
|
|
7
|
-
import { getAsyncLifecycle, defineConfigSchema } from '@openmrs/esm-framework';
|
|
7
|
+
import { getAsyncLifecycle, defineConfigSchema, getSyncLifecycle } from '@openmrs/esm-framework';
|
|
8
8
|
import { configSchema } from './config-schema';
|
|
9
9
|
|
|
10
|
-
const moduleName = '@ampath/esm-dha-workflow-app';
|
|
10
|
+
export const moduleName = '@ampath/esm-dha-workflow-app';
|
|
11
11
|
|
|
12
12
|
const options = {
|
|
13
13
|
featureName: 'Consulation Workflow',
|
|
@@ -31,6 +31,11 @@ export function startupApp() {
|
|
|
31
31
|
defineConfigSchema(moduleName, configSchema);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export const navLinks = getAsyncLifecycle(() => import('./side-nav-menu/nav-links'), {
|
|
35
|
+
featureName: 'side-nav-workflow-link',
|
|
36
|
+
moduleName,
|
|
37
|
+
});
|
|
38
|
+
|
|
34
39
|
/**
|
|
35
40
|
* This named export tells the app shell that the default export of `root.component.tsx`
|
|
36
41
|
* should be rendered when the route matches `root`. The full route
|
|
@@ -39,6 +44,22 @@ export function startupApp() {
|
|
|
39
44
|
*/
|
|
40
45
|
export const root = getAsyncLifecycle(() => import('./root.component'), options);
|
|
41
46
|
export const registry = getAsyncLifecycle(() => import('./registry/registry.component'), options);
|
|
47
|
+
export const waitingPatientsExtension = getAsyncLifecycle(
|
|
48
|
+
() => import('./service-queues/metrics/metrics-cards/waiting-patients.extension'),
|
|
49
|
+
options,
|
|
50
|
+
);
|
|
51
|
+
export const attendedToPatientsExtension = getAsyncLifecycle(
|
|
52
|
+
() => import('./service-queues/metrics/metrics-cards/attended-patients.extension'),
|
|
53
|
+
options,
|
|
54
|
+
);
|
|
55
|
+
export const triageWaitingPatientsExtension = getAsyncLifecycle(
|
|
56
|
+
() => import('./triage/metrics/waiting-patients.extension'),
|
|
57
|
+
options,
|
|
58
|
+
);
|
|
59
|
+
export const triageAttendedToPatientsExtension = getAsyncLifecycle(
|
|
60
|
+
() => import('./triage/metrics/attended-patients.extension'),
|
|
61
|
+
options,
|
|
62
|
+
);
|
|
42
63
|
|
|
43
64
|
export const workflowRegistryLink = getAsyncLifecycle(() => import('./widgets/workflow-registry-link.extension'), {
|
|
44
65
|
featureName: 'workflow-registry-link',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { ExtensionSlot, WorkspaceContainer } from '@openmrs/esm-framework';
|
|
3
|
+
|
|
4
|
+
const LaboratoryComponent: React.FC = () => {
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<ExtensionSlot name="laboratory-dashboard-slot" />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default LaboratoryComponent;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { SideNav } from '@carbon/react';
|
|
4
|
+
import { attach, ExtensionSlot, isDesktop, useLayoutType } from '@openmrs/esm-framework';
|
|
5
|
+
import styles from './left-panel.scss';
|
|
6
|
+
|
|
7
|
+
const LeftPanel: React.FC = () => {
|
|
8
|
+
const { t } = useTranslation();
|
|
9
|
+
const layout = useLayoutType();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
isDesktop(layout) && (
|
|
13
|
+
<SideNav className={styles.leftPanel} expanded>
|
|
14
|
+
<ExtensionSlot name="dha-workflow-slot" />
|
|
15
|
+
</SideNav>
|
|
16
|
+
)
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default LeftPanel;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
|
+
|
|
5
|
+
.leftPanel {
|
|
6
|
+
border-right: 1px solid colors.$gray-20;
|
|
7
|
+
padding-top: layout.$spacing-05;
|
|
8
|
+
|
|
9
|
+
// Left nav menu item
|
|
10
|
+
:global(.cds--side-nav__link) {
|
|
11
|
+
color: colors.$gray-70;
|
|
12
|
+
@include type.type-style('heading-compact-01');
|
|
13
|
+
|
|
14
|
+
&:focus,
|
|
15
|
+
&:hover {
|
|
16
|
+
background-color: colors.$gray-10-hover;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Active menu item
|
|
20
|
+
&:global(.active-left-nav-link) {
|
|
21
|
+
color: colors.$gray-100;
|
|
22
|
+
border-left: layout.$spacing-02 solid var(--brand-01);
|
|
23
|
+
outline: none;
|
|
24
|
+
padding: 0 layout.$spacing-04;
|
|
25
|
+
background-color: colors.$gray-20;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Desktop */
|
|
31
|
+
:global(.omrs-breakpoint-gt-tablet) {
|
|
32
|
+
:global(.cds--side-nav__link) {
|
|
33
|
+
height: layout.$spacing-07;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Tablet */
|
|
38
|
+
:global(.omrs-breakpoint-lt-desktop) {
|
|
39
|
+
:global(.cds--side-nav__link) {
|
|
40
|
+
height: layout.$spacing-09;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ServiceQueueComponent from '../../../service-queues/service-queue/service-queue.component';
|
|
3
|
+
import { QUEUE_SERVICE_UUIDS } from '../../../shared/constants/concepts';
|
|
4
|
+
|
|
5
|
+
interface MchConsultationProps {}
|
|
6
|
+
const MchConsultation: React.FC<MchConsultationProps> = () => {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
<div>
|
|
10
|
+
<ServiceQueueComponent
|
|
11
|
+
serviceTypeUuid={QUEUE_SERVICE_UUIDS.MCH_CLINICAL_CONSULTATION_SERVICE_UUID}
|
|
12
|
+
title="MCH Consultation"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
</>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
export default MchConsultation;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ServiceQueueComponent from '../../../service-queues/service-queue/service-queue.component';
|
|
3
|
+
import { QUEUE_SERVICE_UUIDS } from '../../../shared/constants/concepts';
|
|
4
|
+
|
|
5
|
+
interface TriageProps {}
|
|
6
|
+
const MchTriage: React.FC<TriageProps> = () => {
|
|
7
|
+
return (
|
|
8
|
+
<>
|
|
9
|
+
<div>
|
|
10
|
+
<ServiceQueueComponent serviceTypeUuid={QUEUE_SERVICE_UUIDS.MCH_TRIAGE_SERVICE_UUID} title="MCH Triage" />
|
|
11
|
+
</div>
|
|
12
|
+
</>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
export default MchTriage;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { Button, ButtonSkeleton, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import styles from './sign-off-modal.scss';
|
|
5
|
+
import { type QueueEntry } from '../types/types';
|
|
6
|
+
|
|
7
|
+
interface SignOffModalProps {
|
|
8
|
+
queueEntries: Array<QueueEntry>;
|
|
9
|
+
closeModal: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const SignOffModal: React.FC<SignOffModalProps> = ({ queueEntries, closeModal }) => {
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
15
|
+
|
|
16
|
+
const handleSignOffRequest = useCallback(() => {
|
|
17
|
+
setIsSubmitting(true);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div>
|
|
22
|
+
<ModalHeader
|
|
23
|
+
closeModal={closeModal}
|
|
24
|
+
label={t('signOffMessage', 'Sign off message')}
|
|
25
|
+
title={t('signOffMessage', 'Sign off message')}
|
|
26
|
+
/>
|
|
27
|
+
<ModalBody>
|
|
28
|
+
<p className={styles.subHeading} id="subHeading">
|
|
29
|
+
{t(
|
|
30
|
+
'signOffMessage',
|
|
31
|
+
'Sign off message',
|
|
32
|
+
)}
|
|
33
|
+
.
|
|
34
|
+
</p>
|
|
35
|
+
</ModalBody>
|
|
36
|
+
<ModalFooter>
|
|
37
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
38
|
+
{t('cancel', 'Cancel')}
|
|
39
|
+
</Button>
|
|
40
|
+
{isSubmitting === true ? (
|
|
41
|
+
<ButtonSkeleton />
|
|
42
|
+
) : (
|
|
43
|
+
<Button kind="danger" onClick={handleSignOffRequest}>
|
|
44
|
+
{t('signOff', 'SignOff')}
|
|
45
|
+
</Button>
|
|
46
|
+
)}
|
|
47
|
+
</ModalFooter>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default SignOffModal;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { ExtensionSlot, WorkspaceContainer } from '@openmrs/esm-framework';
|
|
3
|
+
|
|
4
|
+
const Mortuary: React.FC = () => {
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<ExtensionSlot name="mortuary-dashboard-slot" />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default Mortuary;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { ExtensionSlot, WorkspaceContainer } from '@openmrs/esm-framework';
|
|
3
|
+
|
|
4
|
+
const PharmacyComponent: React.FC = () => {
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<ExtensionSlot name="dispensing-dashboard-slot" />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default PharmacyComponent;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { type HieClient } from '../types';
|
|
3
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@carbon/react';
|
|
4
|
+
import { generateHieClientDetails } from '../utils/hie-adapter';
|
|
5
|
+
interface ClientDetailsProps {
|
|
6
|
+
client: HieClient;
|
|
7
|
+
}
|
|
8
|
+
export const ClientDetails: React.FC<ClientDetailsProps> = ({ client }) => {
|
|
9
|
+
const clientDetails = useMemo(() => generateHieClientDetails(client), [client]);
|
|
10
|
+
|
|
11
|
+
if (!client || !clientDetails) {
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<h4>No patient Data to Display</h4>
|
|
15
|
+
</>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<Table>
|
|
21
|
+
<TableHead>
|
|
22
|
+
<TableRow>
|
|
23
|
+
<TableHeader>Field</TableHeader>
|
|
24
|
+
<TableHeader>Value</TableHeader>
|
|
25
|
+
</TableRow>
|
|
26
|
+
</TableHead>
|
|
27
|
+
<TableBody>
|
|
28
|
+
{Object.keys(clientDetails).map((key) => (
|
|
29
|
+
<TableRow>
|
|
30
|
+
<TableCell>{key}</TableCell>
|
|
31
|
+
<TableCell>{clientDetails[key]}</TableCell>
|
|
32
|
+
</TableRow>
|
|
33
|
+
))}
|
|
34
|
+
</TableBody>
|
|
35
|
+
</Table>
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default ClientDetails;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.clientDetailsLayout{
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
width: 100%;
|
|
5
|
+
row-gap: 5px;
|
|
6
|
+
}
|
|
7
|
+
.sectionHeader{
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
row-gap: 5px;
|
|
11
|
+
margin-top: 5px;
|
|
12
|
+
margin-bottom: 10px;
|
|
13
|
+
}
|
|
14
|
+
.sectionContent{
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
row-gap: 5px;
|
|
18
|
+
}
|
|
19
|
+
.actionSection{
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: row;
|
|
22
|
+
width: 100%;
|
|
23
|
+
column-gap: 5px;
|
|
24
|
+
}
|
|
25
|
+
.btnContainer{
|
|
26
|
+
margin-top: 2px;
|
|
27
|
+
margin-bottom: 2px;
|
|
28
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Button, Modal, ModalBody, Tab, TabList, TabPanel, TabPanels, Tabs } from '@carbon/react';
|
|
2
|
+
import { type HieClient } from '../../types';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import styles from './client-details-modal.scss';
|
|
5
|
+
import ClientDetails from '../../client-details/client-details';
|
|
6
|
+
import PaymentOptionsComponent from '../../payment-details/payment-options/payment-options';
|
|
7
|
+
|
|
8
|
+
interface ClientDetailsModalProps {
|
|
9
|
+
client: HieClient;
|
|
10
|
+
open: boolean;
|
|
11
|
+
onModalClose: () => void;
|
|
12
|
+
onSubmit: () => void;
|
|
13
|
+
onSendClientToTriage: (crId: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ClientDetailsModal: React.FC<ClientDetailsModalProps> = ({
|
|
17
|
+
client,
|
|
18
|
+
open,
|
|
19
|
+
onModalClose,
|
|
20
|
+
onSubmit,
|
|
21
|
+
onSendClientToTriage,
|
|
22
|
+
}) => {
|
|
23
|
+
if (!client) {
|
|
24
|
+
return <>No Client data</>;
|
|
25
|
+
}
|
|
26
|
+
const registerOnAfyaYangu = () => {
|
|
27
|
+
window.open('https://afyayangu.go.ke/', '_blank');
|
|
28
|
+
};
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<Modal
|
|
32
|
+
open={open}
|
|
33
|
+
size="md"
|
|
34
|
+
onSecondarySubmit={onModalClose}
|
|
35
|
+
onRequestClose={onModalClose}
|
|
36
|
+
onRequestSubmit={registerOnAfyaYangu}
|
|
37
|
+
primaryButtonText="Register on Afya Yangu"
|
|
38
|
+
secondaryButtonText="Cancel"
|
|
39
|
+
>
|
|
40
|
+
<ModalBody>
|
|
41
|
+
<div className={styles.clientDetailsLayout}>
|
|
42
|
+
<div className={styles.sectionHeader}>
|
|
43
|
+
<h4 className={styles.sectionTitle}>Patient/Payment Details</h4>
|
|
44
|
+
</div>
|
|
45
|
+
<div className={styles.sectionContent}>
|
|
46
|
+
<Tabs>
|
|
47
|
+
<TabList contained>
|
|
48
|
+
<Tab>Patient Details</Tab>
|
|
49
|
+
<Tab>Payment Details</Tab>
|
|
50
|
+
</TabList>
|
|
51
|
+
<TabPanels>
|
|
52
|
+
<TabPanel>
|
|
53
|
+
<ClientDetails client={client} />
|
|
54
|
+
</TabPanel>
|
|
55
|
+
<TabPanel>
|
|
56
|
+
<PaymentOptionsComponent />
|
|
57
|
+
</TabPanel>
|
|
58
|
+
</TabPanels>
|
|
59
|
+
</Tabs>
|
|
60
|
+
</div>
|
|
61
|
+
<div className={styles.actionSection}>
|
|
62
|
+
<div className={styles.btnContainer}>
|
|
63
|
+
<Button kind="primary">Book Appointment</Button>
|
|
64
|
+
</div>
|
|
65
|
+
<div className={styles.btnContainer}>
|
|
66
|
+
<Button kind="secondary">Walk In Orders</Button>
|
|
67
|
+
</div>
|
|
68
|
+
<div className={styles.btnContainer}>
|
|
69
|
+
<Button kind="tertiary" onClick={() => onSendClientToTriage(client.id)}>
|
|
70
|
+
Send To Triage
|
|
71
|
+
</Button>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</ModalBody>
|
|
76
|
+
</Modal>
|
|
77
|
+
</>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default ClientDetailsModal;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.modalVerificationLayout {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
row-gap: 10px;
|
|
5
|
+
width: 100%;
|
|
6
|
+
}
|
|
7
|
+
.sectionHeader{
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
column-gap: 5px;
|
|
11
|
+
margin-top: 5px;
|
|
12
|
+
margin-bottom: 5px;
|
|
13
|
+
}
|
|
14
|
+
.otpSection{
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: row;
|
|
17
|
+
width: 100%;
|
|
18
|
+
column-gap: 15px;
|
|
19
|
+
}
|
|
20
|
+
.otpForm{
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
width: 50%;
|
|
24
|
+
row-gap: 15px;
|
|
25
|
+
}
|
|
26
|
+
.otpTimer{
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
width: 50%;
|
|
30
|
+
padding-top: 6%;
|
|
31
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { OtpStatus, type RequestCustomOtpDto } from '../../types';
|
|
3
|
+
import { Button, FormLabel, InlineLoading, Modal, ModalBody } from '@carbon/react';
|
|
4
|
+
import styles from './otp-verification-modal.scss';
|
|
5
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
|
6
|
+
import { requestCustomOtp, validateCustomOtp } from '../../registry.resource';
|
|
7
|
+
import { maskValue } from '../../utils/mask-data';
|
|
8
|
+
import OTPInput from '../../../shared/ui/otp-input/otp-input.component';
|
|
9
|
+
import Timer from '../../../shared/ui/timer/timer.component';
|
|
10
|
+
|
|
11
|
+
interface OtpVerificationModalpProps {
|
|
12
|
+
requestCustomOtpDto: RequestCustomOtpDto;
|
|
13
|
+
phoneNumber: string;
|
|
14
|
+
open: boolean;
|
|
15
|
+
onModalClose: () => void;
|
|
16
|
+
onOtpSuccessfullVerification: () => void;
|
|
17
|
+
}
|
|
18
|
+
const OtpVerificationModal: React.FC<OtpVerificationModalpProps> = ({
|
|
19
|
+
requestCustomOtpDto,
|
|
20
|
+
phoneNumber,
|
|
21
|
+
open,
|
|
22
|
+
onModalClose,
|
|
23
|
+
onOtpSuccessfullVerification,
|
|
24
|
+
}) => {
|
|
25
|
+
const [otp, setOtp] = useState('');
|
|
26
|
+
const [otpStatus, setOtpStatus] = useState<string>(OtpStatus.Draft);
|
|
27
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
28
|
+
const [sessionId, setSessionId] = useState<string>('');
|
|
29
|
+
|
|
30
|
+
const handleSendOtp = async () => {
|
|
31
|
+
if (!requestCustomOtpDto.identificationNumber) {
|
|
32
|
+
showAlert('error', 'Invalid Identification Value', 'Please enter a valid ID value');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
setLoading(true);
|
|
36
|
+
try {
|
|
37
|
+
const response = await requestCustomOtp(requestCustomOtpDto);
|
|
38
|
+
setSessionId(response.sessionId);
|
|
39
|
+
setOtpStatus(OtpStatus.Sent);
|
|
40
|
+
|
|
41
|
+
showAlert('success', 'OTP sent successfully', `A code was sent to ${response.maskedPhone}`);
|
|
42
|
+
} catch (err: any) {
|
|
43
|
+
const errorMessage = err.message || 'Failed to send OTP';
|
|
44
|
+
showAlert('error', 'Error sending OTP', errorMessage);
|
|
45
|
+
} finally {
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const showAlert = (alertType: 'error' | 'success', title: string, subtitle: string) => {
|
|
50
|
+
showSnackbar({
|
|
51
|
+
kind: alertType,
|
|
52
|
+
title: title,
|
|
53
|
+
subtitle: subtitle,
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleVerifyOtp = async () => {
|
|
58
|
+
if (!otp.trim()) {
|
|
59
|
+
showAlert('error', 'Please enter the OTP code', '');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setLoading(true);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const payload = { sessionId, otp, locationUuid: requestCustomOtpDto.locationUuid };
|
|
67
|
+
await validateCustomOtp(payload);
|
|
68
|
+
|
|
69
|
+
setOtpStatus(OtpStatus.Verified);
|
|
70
|
+
|
|
71
|
+
showSnackbar({
|
|
72
|
+
kind: 'success',
|
|
73
|
+
title: 'OTP Verified',
|
|
74
|
+
subtitle: 'You can now fetch data from Client Registry.',
|
|
75
|
+
});
|
|
76
|
+
} catch (err: any) {
|
|
77
|
+
const errorMessage = err.message || 'OTP verification failed';
|
|
78
|
+
showSnackbar({
|
|
79
|
+
kind: 'error',
|
|
80
|
+
title: 'OTP Verification Failed',
|
|
81
|
+
subtitle: errorMessage,
|
|
82
|
+
});
|
|
83
|
+
} finally {
|
|
84
|
+
setLoading(false);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const registerOnAfyaYangu = () => {
|
|
88
|
+
window.open('https://afyayangu.go.ke/', '_blank');
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const onSubmit = () => {};
|
|
92
|
+
const handleTimeUp = () => {
|
|
93
|
+
if (OtpStatus.Sent) {
|
|
94
|
+
setOtpStatus(OtpStatus.Draft);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return (
|
|
98
|
+
<>
|
|
99
|
+
<Modal
|
|
100
|
+
open={open}
|
|
101
|
+
size="md"
|
|
102
|
+
onSecondarySubmit={onModalClose}
|
|
103
|
+
onRequestClose={onModalClose}
|
|
104
|
+
onRequestSubmit={registerOnAfyaYangu}
|
|
105
|
+
primaryButtonText="Register on Afya Yangu"
|
|
106
|
+
secondaryButtonText="Cancel"
|
|
107
|
+
>
|
|
108
|
+
<ModalBody>
|
|
109
|
+
<div className={styles.modalVerificationLayout}>
|
|
110
|
+
<div className={styles.sectionHeader}>
|
|
111
|
+
<h4 className={styles.sectionTitle}>One Time Password (OTP)</h4>
|
|
112
|
+
<h6>Enter one time password to proceed</h6>
|
|
113
|
+
</div>
|
|
114
|
+
<div className={styles.sectionContent}>
|
|
115
|
+
<div className={styles.contentHeader}>
|
|
116
|
+
{otpStatus === OtpStatus.Draft ? (
|
|
117
|
+
<>
|
|
118
|
+
<h6>Send Code to Phone {maskValue(phoneNumber)}</h6>
|
|
119
|
+
</>
|
|
120
|
+
) : (
|
|
121
|
+
<></>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
{otpStatus === OtpStatus.Verified ? (
|
|
125
|
+
<>
|
|
126
|
+
<h6>OTP Verification Successfull!</h6>
|
|
127
|
+
</>
|
|
128
|
+
) : (
|
|
129
|
+
<></>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
<div className={styles.otpSection}>
|
|
133
|
+
{otpStatus === OtpStatus.Sent ? (
|
|
134
|
+
<>
|
|
135
|
+
<div className={styles.otpForm}>
|
|
136
|
+
<FormLabel>Enter OTP</FormLabel>
|
|
137
|
+
<OTPInput otpLength={5} onChange={(value) => setOtp(value)} />
|
|
138
|
+
</div>
|
|
139
|
+
<div className={styles.otpTimer}>
|
|
140
|
+
<Timer durationInSeconds={60} resetTimer={() => {}} onTimeUp={handleTimeUp} />
|
|
141
|
+
</div>
|
|
142
|
+
</>
|
|
143
|
+
) : (
|
|
144
|
+
<></>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
<div className={styles.sectionAction}>
|
|
149
|
+
{otpStatus === OtpStatus.Draft ? (
|
|
150
|
+
<>
|
|
151
|
+
<Button kind="primary" onClick={handleSendOtp}>
|
|
152
|
+
{loading ? <InlineLoading description="Sending OTP..." /> : 'Send OTP'}
|
|
153
|
+
</Button>
|
|
154
|
+
</>
|
|
155
|
+
) : (
|
|
156
|
+
<></>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{otpStatus === OtpStatus.Sent ? (
|
|
160
|
+
<>
|
|
161
|
+
<Button kind="primary" onClick={handleVerifyOtp}>
|
|
162
|
+
{loading ? <InlineLoading description="Verifying OTP..." /> : 'Verify'}
|
|
163
|
+
</Button>
|
|
164
|
+
</>
|
|
165
|
+
) : (
|
|
166
|
+
<></>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
{otpStatus === OtpStatus.Verified ? (
|
|
170
|
+
<>
|
|
171
|
+
<Button kind="primary" onClick={onOtpSuccessfullVerification}>
|
|
172
|
+
Continue
|
|
173
|
+
</Button>
|
|
174
|
+
</>
|
|
175
|
+
) : (
|
|
176
|
+
<></>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</ModalBody>
|
|
181
|
+
</Modal>
|
|
182
|
+
</>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export default OtpVerificationModal;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.formSection{
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
width: 100%;
|
|
5
|
+
row-gap: 5px;
|
|
6
|
+
margin-top: 15px;
|
|
7
|
+
}
|
|
8
|
+
.formRow{
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: row;
|
|
11
|
+
width: 100%;
|
|
12
|
+
column-gap: 5px;
|
|
13
|
+
}
|
|
14
|
+
.formControl{
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
width: 45%;
|
|
18
|
+
row-gap: 5px;
|
|
19
|
+
}
|
|
20
|
+
.actionSection{
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: row;
|
|
23
|
+
column-gap: 5px;
|
|
24
|
+
margin-top: 15px;
|
|
25
|
+
}
|
|
26
|
+
.patientAction{
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: row;
|
|
29
|
+
column-gap: 5px;
|
|
30
|
+
margin-top: 15px;
|
|
31
|
+
}
|
|
32
|
+
.greenBtn{
|
|
33
|
+
background-color: #28a745;
|
|
34
|
+
}
|