@ampath/esm-dha-workflow-app 4.0.0-next.14 → 4.0.0-next.16

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.
Files changed (69) hide show
  1. package/dist/104.js +2 -0
  2. package/dist/104.js.LICENSE.txt +9 -0
  3. package/dist/104.js.map +1 -0
  4. package/dist/178.js +1 -0
  5. package/dist/178.js.map +1 -0
  6. package/dist/306.js +1 -1
  7. package/dist/306.js.map +1 -1
  8. package/dist/560.js +1 -0
  9. package/dist/560.js.map +1 -0
  10. package/dist/635.js +1 -0
  11. package/dist/635.js.map +1 -0
  12. package/dist/695.js +1 -0
  13. package/dist/695.js.map +1 -0
  14. package/dist/709.js +1 -0
  15. package/dist/709.js.map +1 -0
  16. package/dist/710.js +2 -0
  17. package/dist/{198.js.LICENSE.txt → 710.js.LICENSE.txt} +0 -10
  18. package/dist/710.js.map +1 -0
  19. package/dist/875.js +2 -0
  20. package/dist/875.js.map +1 -0
  21. package/dist/91.js +1 -1
  22. package/dist/91.js.map +1 -1
  23. package/dist/93.js +1 -1
  24. package/dist/978.js +1 -0
  25. package/dist/978.js.map +1 -0
  26. package/dist/esm-dha-workflow-app.js +1 -0
  27. package/dist/{openmrs-esm-home-app.js.buildmanifest.json → esm-dha-workflow-app.js.buildmanifest.json} +242 -73
  28. package/dist/esm-dha-workflow-app.js.map +1 -0
  29. package/dist/main.js +1 -1
  30. package/dist/main.js.map +1 -1
  31. package/dist/routes.json +1 -1
  32. package/package.json +2 -2
  33. package/src/config-schema.ts +115 -33
  34. package/src/consultation/action-button.component.tsx +34 -0
  35. package/src/consultation/action-overflow-menu-item.component.tsx +34 -0
  36. package/src/consultation/consultation-room.component.tsx +60 -0
  37. package/src/consultation/consultation.scss +5 -0
  38. package/src/consultation/consultation.tsx +9 -2
  39. package/src/hooks/useActions.ts +148 -0
  40. package/src/hooks/useQueueEntries.ts +35 -0
  41. package/src/index.ts +11 -1
  42. package/src/metrics/metrics-cards/attended-patients.extension.tsx +18 -0
  43. package/src/metrics/metrics-cards/metrics-card.component.tsx +86 -0
  44. package/src/metrics/metrics-cards/metrics-card.scss +106 -0
  45. package/src/metrics/metrics-cards/waiting-patients.extension.tsx +18 -0
  46. package/src/metrics/metrics-container.component.tsx +16 -0
  47. package/src/metrics/metrics-container.scss +36 -0
  48. package/src/metrics/metrics.resource.ts +101 -0
  49. package/src/modals/sign-off-modal.scss +7 -0
  50. package/src/modals/sign-off-modal.tsx +52 -0
  51. package/src/root.component.tsx +2 -1
  52. package/src/routes.json +18 -0
  53. package/src/service-queues/service-queues.resource.ts +62 -0
  54. package/src/triage/room/room.component.tsx +39 -0
  55. package/src/triage/room/room.scss +29 -0
  56. package/src/triage/triage.component.tsx +36 -0
  57. package/src/triage/triage.module.scss +19 -0
  58. package/src/triage/triage.resource.ts +19 -0
  59. package/src/triage/types.ts +22 -0
  60. package/src/types/types.ts +100 -0
  61. package/dist/198.js +0 -2
  62. package/dist/198.js.map +0 -1
  63. package/dist/200.js +0 -2
  64. package/dist/200.js.map +0 -1
  65. package/dist/860.js +0 -1
  66. package/dist/860.js.map +0 -1
  67. package/dist/openmrs-esm-home-app.js +0 -1
  68. package/dist/openmrs-esm-home-app.js.map +0 -1
  69. /package/dist/{200.js.LICENSE.txt → 875.js.LICENSE.txt} +0 -0
@@ -0,0 +1,35 @@
1
+ import { openmrsFetch, restBaseUrl } from "@openmrs/esm-framework";
2
+ import useSWR from 'swr';
3
+ import { useSWRConfig } from 'swr/_internal';
4
+ import { type QueueEntryResponse } from "../types/types";
5
+ import { useMemo } from "react";
6
+
7
+ export function useMutateQueueEntries() {
8
+ const { mutate } = useSWRConfig();
9
+
10
+ return {
11
+ mutateQueueEntries: () => {
12
+ return mutate((key) => {
13
+ return (
14
+ typeof key === 'string' &&
15
+ (key.includes(`${restBaseUrl}/queue-entry`) || key.includes(`${restBaseUrl}/visit-queue-entry`))
16
+ );
17
+ }).then(() => {
18
+ window.dispatchEvent(new CustomEvent('queue-entry-updated'));
19
+ });
20
+ },
21
+ };
22
+ }
23
+
24
+ export function useQueueEntries() {
25
+ const queueEntryBaseUrl = `${restBaseUrl}/queue-entry?` +
26
+ `isEnded=false&service=7f7ec7ad-cdd7-4ed9-bc2e-5c5bd9f065b2&location=18c343eb-b353-462a-9139-b16606e6b6c2`;
27
+ const { data, isValidating, isLoading, error: pageError } = useSWR<QueueEntryResponse, Error>(queueEntryBaseUrl, openmrsFetch);
28
+
29
+ const queueEntries = useMemo(() => data, [data]);
30
+
31
+ return {
32
+ queueEntries,
33
+ isLoading
34
+ }
35
+ }
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@
7
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
+ const moduleName = '@ampath/openmrs-esm-home-app.js';
11
11
 
12
12
  const options = {
13
13
  featureName: 'Consulation Workflow',
@@ -44,8 +44,18 @@ export const navLinks = getAsyncLifecycle(() => import('./side-nav-menu/nav-link
44
44
  */
45
45
  export const root = getAsyncLifecycle(() => import('./root.component'), options);
46
46
  export const registry = getAsyncLifecycle(() => import('./registry/registry.component'), options);
47
+ export const waitingPatientsExtension = getAsyncLifecycle(
48
+ () => import('./metrics/metrics-cards/waiting-patients.extension'),
49
+ options,
50
+ );
51
+ export const attendedToPatientsExtension = getAsyncLifecycle(
52
+ () => import('./metrics/metrics-cards/attended-patients.extension'),
53
+ options,
54
+ );
47
55
 
48
56
  export const workflowRegistryLink = getAsyncLifecycle(() => import('./widgets/workflow-registry-link.extension'), {
49
57
  featureName: 'workflow-registry-link',
50
58
  moduleName,
51
59
  });
60
+
61
+ export const triage = getAsyncLifecycle(() => import('./triage/triage.component'), options);
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { MetricsCard, MetricsCardHeader, MetricsCardBody, MetricsCardItem } from './metrics-card.component';
4
+ import { useServiceMetricsCount } from '../metrics.resource';
5
+
6
+ export default function AttendedToPatientsExtension() {
7
+ const { t } = useTranslation();
8
+ const { serviceCount, isLoading } = useServiceMetricsCount("COMPLETED");
9
+
10
+ return (
11
+ <MetricsCard>
12
+ <MetricsCardHeader title={t('patientsAttendedTo', 'Patients attended to')} />
13
+ <MetricsCardBody>
14
+ <MetricsCardItem label={t('patients', 'Patients')} value={isLoading ? '--' : serviceCount ?? 0} />
15
+ </MetricsCardBody>
16
+ </MetricsCard>
17
+ );
18
+ }
@@ -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
+ };
@@ -0,0 +1,106 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+ @use '@openmrs/esm-styleguide/src/vars' as *;
5
+
6
+ .tileContainer {
7
+ border: 1px solid $ui-03;
8
+ min-height: 7.875rem;
9
+ height: 100%;
10
+ padding: layout.$spacing-05;
11
+ }
12
+
13
+ .tileHeader {
14
+ display: flex;
15
+ justify-content: space-between;
16
+ align-items: baseline;
17
+ margin-bottom: layout.$spacing-03;
18
+ }
19
+
20
+ .headerLabelContainer {
21
+ display: flex;
22
+ height: layout.$spacing-07;
23
+ align-items: center;
24
+
25
+ :global(.cds--dropdown__wrapper--inline) {
26
+ gap: 0;
27
+
28
+ label {
29
+ @include type.type-style('heading-compact-01');
30
+ color: $text-02;
31
+ margin-right: layout.$spacing-03;
32
+ }
33
+ }
34
+
35
+ :global(.cds--list-box__menu-icon) {
36
+ height: layout.$spacing-05;
37
+ }
38
+ }
39
+
40
+ .link {
41
+ text-decoration: none;
42
+ display: flex;
43
+ align-items: center;
44
+ color: $interactive-01;
45
+
46
+ svg {
47
+ margin-left: layout.$spacing-03;
48
+ }
49
+ }
50
+
51
+ .headerLabel {
52
+ @include type.type-style('heading-compact-01');
53
+ color: $text-02;
54
+ }
55
+
56
+ .metricsContainer {
57
+ display: flex;
58
+ gap: layout.$spacing-07;
59
+ align-items: stretch;
60
+ flex-wrap: wrap;
61
+ }
62
+
63
+ .metricItem {
64
+ display: inline-block;
65
+ flex-direction: column;
66
+ }
67
+
68
+ .metricLabel {
69
+ @include type.type-style('label-01');
70
+ color: $text-02;
71
+ margin-bottom: layout.$spacing-02;
72
+ }
73
+
74
+ .metricValue {
75
+ @include type.type-style('heading-04');
76
+ color: $ui-05;
77
+ margin: 0;
78
+ }
79
+
80
+ .mainItem {
81
+ margin-right: layout.$spacing-07;
82
+ }
83
+
84
+ .smallItem {
85
+ flex-direction: row;
86
+ }
87
+
88
+ .smallValue {
89
+ font-size: 1rem !important;
90
+ margin-top: layout.$spacing-02;
91
+ }
92
+
93
+ .red {
94
+ color: colors.$red-50;
95
+ }
96
+
97
+ // Overriding styles for RTL support
98
+ html[dir='rtl'] {
99
+ .link {
100
+ svg {
101
+ margin-right: layout.$spacing-03;
102
+ margin-left: unset;
103
+ transform: scale(-1, 1);
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { MetricsCard, MetricsCardHeader, MetricsCardBody, MetricsCardItem } from './metrics-card.component';
4
+ import { useServiceMetricsCount } from '../metrics.resource';
5
+
6
+ export default function WaitingPatientsExtension() {
7
+ const { t } = useTranslation();
8
+ const { serviceCount, isLoading } = useServiceMetricsCount("WAITING");
9
+
10
+ return (
11
+ <MetricsCard>
12
+ <MetricsCardHeader title={t('patientsInWaiting', 'Patients in waiting')} />
13
+ <MetricsCardBody>
14
+ <MetricsCardItem label={t('patients', 'Patients')} value={isLoading ? '--' : serviceCount ?? 0} />
15
+ </MetricsCardBody>
16
+ </MetricsCard>
17
+ );
18
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { ExtensionSlot } from '@openmrs/esm-framework';
3
+ import styles from './metrics-container.scss';
4
+
5
+ export interface Service {
6
+ display: string;
7
+ uuid?: string;
8
+ }
9
+
10
+ function MetricsContainer() {
11
+ return (
12
+ <ExtensionSlot name="clinic-metrics-slot" className={styles.cardContainer} data-testid="clinic-metrics" />
13
+ );
14
+ }
15
+
16
+ export default MetricsContainer;
@@ -0,0 +1,36 @@
1
+ @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .cardContainer {
5
+ background-color: $ui-02;
6
+ display: flex;
7
+ padding: layout.$spacing-05;
8
+ flex-flow: row wrap;
9
+ gap: layout.$spacing-05;
10
+ align-items: stretch;
11
+ }
12
+
13
+ .cardContainer > * {
14
+ flex: 1 0 0%;
15
+ min-width: 16rem;
16
+ }
17
+
18
+ // If we're on tablet and the screen is too small for 3 cards across
19
+ // @TODO: This will do nonsense things if there are not exactly 3 cards
20
+ @media (max-width: calc(layout.$spacing-05 * 4 + 18.75rem * 3)) {
21
+ :global(.omrs-breakpoint-lt-desktop) {
22
+ .cardContainer > *:has(:global(.cardWithChildren)) {
23
+ order: 999;
24
+ }
25
+ }
26
+ }
27
+
28
+ // If we're on desktop and the screen is too small for the left nav bar
29
+ // plus 3 cards across
30
+ @media (max-width: calc(16rem + layout.$spacing-05 * 4 + 18.75rem * 3)) {
31
+ :global(.omrs-breakpoint-gt-tablet) {
32
+ .cardContainer > *:has(:global(.cardWithChildren)) {
33
+ order: 999;
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,101 @@
1
+ import { useSession, type Visit, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import dayjs from 'dayjs';
3
+ import useSWR from 'swr';
4
+
5
+ // export function useActiveVisits() {
6
+ // const currentUserSession = useSession();
7
+ // const startDate = dayjs().format('YYYY-MM-DD');
8
+ // const sessionLocation = currentUserSession?.sessionLocation?.uuid;
9
+
10
+ // const customRepresentation =
11
+ // 'custom:(uuid,patient:(uuid,identifiers:(identifier,uuid),person:(age,display,gender,uuid)),' +
12
+ // 'visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,' +
13
+ // 'stopDatetime)&fromStartDate=' +
14
+ // startDate +
15
+ // '&location=' +
16
+ // sessionLocation;
17
+ // const url = `${restBaseUrl}/visit?includeInactive=false&v=${customRepresentation}`;
18
+ // const { data, error, isLoading, isValidating } = useSWR<{ data: { results: Array<Visit> } }, Error>(
19
+ // sessionLocation ? url : null,
20
+ // openmrsFetch,
21
+ // );
22
+
23
+ // // Create a Set to store unique patient UUIDs
24
+ // const uniquePatientUUIDs = new Set();
25
+
26
+ // data?.data?.results.forEach((visit) => {
27
+ // const patientUUID = visit.patient?.uuid;
28
+ // const isToday = dayjs(visit.startDatetime).isToday();
29
+ // if (patientUUID && isToday) {
30
+ // uniquePatientUUIDs.add(patientUUID);
31
+ // }
32
+ // });
33
+
34
+ // return {
35
+ // activeVisitsCount: uniquePatientUUIDs.size,
36
+ // isLoading,
37
+ // error,
38
+ // isValidating,
39
+ // };
40
+ // }
41
+
42
+ // Statuses: Waiting, Finished Service, In Service
43
+ export function useServiceMetricsCount(status: string = 'Waiting', service: string = "7f7ec7ad-cdd7-4ed9-bc2e-5c5bd9f065b2") {
44
+ const currentUserSession = useSession();
45
+ const location = currentUserSession?.sessionLocation?.uuid;
46
+
47
+ const apiUrl =
48
+ `${restBaseUrl}/queue-entry-metrics?status=${status}&isEnded=false` +
49
+ (service ? `&service=${service}` : '') +
50
+ (location ? `&location=${location}` : '');
51
+
52
+ const { data, isLoading } = useSWR<
53
+ {
54
+ data: {
55
+ count: number;
56
+ };
57
+ },
58
+ Error
59
+ >(service ? apiUrl : null, openmrsFetch);
60
+
61
+ return {
62
+ serviceCount: data ? data?.data?.count : 0,
63
+ isLoading
64
+ };
65
+ }
66
+
67
+ export function useConsultationQueues(service: string = "7f7ec7ad-cdd7-4ed9-bc2e-5c5bd9f065b2", status: string = 'WAITING') {
68
+ const currentUserSession = useSession();
69
+ const location = currentUserSession?.sessionLocation?.uuid;
70
+
71
+ const customRepresentation =
72
+ 'custom:(uuid,display,queue,status,patient:(uuid,display,person,identifiers:(uuid,display,identifier,identifierType)),' +
73
+ 'visit:(uuid,display,startDatetime))'
74
+
75
+ const apiUrl =
76
+ `${restBaseUrl}/queue-entry?status=${status}&isEnded=false` +
77
+ (service ? `&service=${service}` : '') +
78
+ (location ? `&location=${location}` : '') +
79
+ `&v=${customRepresentation}`;
80
+
81
+ const { data, isLoading } = useSWR<
82
+ {
83
+ data: {
84
+ results: Array<{
85
+ patient: {
86
+ uuid: string;
87
+ display: string;
88
+ },
89
+ visit: {
90
+ uuid: string;
91
+ display: string;
92
+ startDatetime: string;
93
+ }
94
+ }>;
95
+ };
96
+ },
97
+ Error
98
+ >(service ? apiUrl : null, openmrsFetch);
99
+
100
+ return { data, isLoading };
101
+ }
@@ -0,0 +1,7 @@
1
+ @use '@carbon/type';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .subHeading {
5
+ @include type.type-style('heading-compact-01');
6
+ color: $ui-05;
7
+ }
@@ -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;
@@ -5,6 +5,7 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
5
5
  import RegistryComponent from './registry/registry.component';
6
6
  import LeftPanel from './left-panel/left-panel.component';
7
7
  import Consultation from './consultation/consultation';
8
+ import Triage from './triage/triage.component';
8
9
 
9
10
  const Root: React.FC = () => {
10
11
  return (
@@ -14,9 +15,9 @@ const Root: React.FC = () => {
14
15
  <Routes>
15
16
  <Route path="" element={<RegistryComponent />} />
16
17
  <Route path="registry" element={<RegistryComponent />} />
17
- <Route path="triage" element={<Consultation />} />
18
18
  <Route path="consultation" element={<Consultation />} />
19
19
  <Route path="*" element={<RegistryComponent />} />
20
+ <Route path="triage" element={<Triage />} />
20
21
  </Routes>
21
22
  </main>
22
23
  <WorkspaceContainer contextKey="home" />
package/src/routes.json CHANGED
@@ -23,6 +23,24 @@
23
23
  "slot": "dha-workflow-slot",
24
24
  "online": true,
25
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
+ "modals": [
41
+ {
42
+ "name": "sign-off-queue-entry-modal",
43
+ "component": "signOffModal"
26
44
  }
27
45
  ]
28
46
  }
@@ -0,0 +1,62 @@
1
+ import { type Encounter, formatDate, openmrsFetch, parseDate, restBaseUrl } from "@openmrs/esm-framework";
2
+ import { type Identifer, type MappedEncounter, type MappedVisitQueueEntry, type QueueEntry } from "../types/types";
3
+ import dayjs from "dayjs";
4
+
5
+ export function serveQueueEntry(servicePointName: string, ticketNumber: string, status: string) {
6
+ const abortController = new AbortController();
7
+
8
+ return openmrsFetch(`${restBaseUrl}/queueutil/assignticket`, {
9
+ method: 'POST',
10
+ headers: {
11
+ 'Content-Type': 'application/json',
12
+ },
13
+ signal: abortController.signal,
14
+ body: {
15
+ servicePointName,
16
+ ticketNumber,
17
+ status,
18
+ },
19
+ });
20
+ }
21
+
22
+ const mapEncounterProperties = (encounter: Encounter): MappedEncounter => ({
23
+ diagnoses: encounter.diagnoses,
24
+ encounterDatetime: encounter.encounterDatetime,
25
+ encounterType: encounter.encounterType.display,
26
+ obs: encounter.obs,
27
+ provider: encounter.encounterProviders[0]?.provider?.person?.display,
28
+ uuid: encounter.uuid,
29
+ voided: encounter.voided,
30
+ });
31
+
32
+ export const mapVisitQueueEntryProperties = (
33
+ queueEntry: QueueEntry,
34
+ visitQueueNumberAttributeUuid: string,
35
+ ): MappedVisitQueueEntry => ({
36
+ id: queueEntry.uuid,
37
+ encounters: queueEntry.visit?.encounters?.map(mapEncounterProperties),
38
+ name: queueEntry.display,
39
+ patientUuid: queueEntry.patient.uuid,
40
+ patientAge: queueEntry.patient.person?.age + '',
41
+ patientDob: queueEntry?.patient?.person?.birthdate
42
+ ? formatDate(parseDate(queueEntry.patient.person.birthdate), { time: false })
43
+ : '--',
44
+ patientGender: queueEntry.patient.person.gender,
45
+ queue: queueEntry.queue,
46
+ priority: queueEntry.priority,
47
+ priorityComment: queueEntry.priorityComment,
48
+ status: queueEntry.status,
49
+ startedAt: dayjs(queueEntry.startedAt).toDate(),
50
+ endedAt: queueEntry.endedAt ? dayjs(queueEntry.endedAt).toDate() : null,
51
+ visitType: queueEntry.visit?.visitType?.display,
52
+ queueLocation: (queueEntry?.queue as any)?.location?.uuid,
53
+ visitTypeUuid: queueEntry.visit?.visitType?.uuid,
54
+ visitUuid: queueEntry.visit?.uuid,
55
+ queueUuid: queueEntry.queue.uuid,
56
+ queueEntryUuid: queueEntry.uuid,
57
+ sortWeight: queueEntry.sortWeight,
58
+ visitQueueNumber: queueEntry.visit?.attributes?.find((e) => e?.attributeType?.uuid === visitQueueNumberAttributeUuid)
59
+ ?.value,
60
+ identifiers: queueEntry.patient?.identifiers as Identifer[],
61
+ queueComingFrom: queueEntry?.queueComingFrom?.name,
62
+ });
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { navigate } from '@openmrs/esm-framework';
3
+ import { Button, Tile } from '@carbon/react';
4
+ import { type Patient } from '../types';
5
+ import styles from './room.scss';
6
+
7
+ interface RoomProps {
8
+ locationUuid?: string;
9
+ name: string;
10
+ patients: Array<Patient>;
11
+ }
12
+
13
+ const Room: React.FC<RoomProps> = ({ name, locationUuid, patients }) => {
14
+ function openPatientChart(patientUuid: string) {
15
+ navigate({
16
+ to: `patient/${patientUuid}/chart`,
17
+ });
18
+ }
19
+ return (
20
+ <div className={styles.room}>
21
+ <h4 className={styles.roomName}>{name}</h4>
22
+
23
+ <div className={styles.container}>
24
+ {patients.map((patient) => (
25
+ <Tile key={patient.uuid}>
26
+ <h5>{patient.display}</h5>
27
+ <p className={styles.field}>Age: {patient.patient.person.age}</p>
28
+ <p className={styles.field}>Status: {patient.status.display}</p>
29
+ <Button onClick={() => openPatientChart(patient.patient.uuid)} size="sm" className={styles.button}>
30
+ Start
31
+ </Button>
32
+ </Tile>
33
+ ))}
34
+ </div>
35
+ </div>
36
+ );
37
+ };
38
+
39
+ export default Room;
@@ -0,0 +1,29 @@
1
+ .container {
2
+ border: 1px solid #9e9e9e;
3
+ padding: 1rem;
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 0.5rem;
7
+ width: fit-content;
8
+ }
9
+
10
+ .room {
11
+ display: flex;
12
+ flex-direction: column;
13
+ align-items: flex-start;
14
+ margin: 1rem;
15
+ }
16
+
17
+ .roomName {
18
+ margin-bottom: 0.5rem;
19
+ font-size: large;
20
+ font-weight: bold;
21
+ }
22
+
23
+ .field {
24
+ margin: 0.25rem 0 0;
25
+ }
26
+
27
+ .button {
28
+ margin-top: 0.5rem;
29
+ }