@digihmis/esm-outpatient-app 1.0.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.
Files changed (116) hide show
  1. package/README.md +4 -0
  2. package/dist/117.js +1 -0
  3. package/dist/117.js.map +1 -0
  4. package/dist/132.js +1 -0
  5. package/dist/132.js.map +1 -0
  6. package/dist/152.js +1 -0
  7. package/dist/152.js.map +1 -0
  8. package/dist/209.js +1 -0
  9. package/dist/209.js.map +1 -0
  10. package/dist/317.js +1 -0
  11. package/dist/317.js.map +1 -0
  12. package/dist/349.js +1 -0
  13. package/dist/349.js.map +1 -0
  14. package/dist/371.js +1 -0
  15. package/dist/371.js.map +1 -0
  16. package/dist/378.js +1 -0
  17. package/dist/378.js.map +1 -0
  18. package/dist/395.js +12 -0
  19. package/dist/395.js.map +1 -0
  20. package/dist/442.js +1 -0
  21. package/dist/442.js.map +1 -0
  22. package/dist/45.js +1 -0
  23. package/dist/45.js.map +1 -0
  24. package/dist/466.js +1 -0
  25. package/dist/466.js.map +1 -0
  26. package/dist/508.js +1 -0
  27. package/dist/508.js.map +1 -0
  28. package/dist/564.js +1 -0
  29. package/dist/564.js.map +1 -0
  30. package/dist/61.js +1 -0
  31. package/dist/61.js.map +1 -0
  32. package/dist/640.js +1 -0
  33. package/dist/640.js.map +1 -0
  34. package/dist/689.js +1 -0
  35. package/dist/689.js.map +1 -0
  36. package/dist/709.js +1 -0
  37. package/dist/709.js.map +1 -0
  38. package/dist/712.js +1 -0
  39. package/dist/712.js.map +1 -0
  40. package/dist/720.js +1 -0
  41. package/dist/720.js.map +1 -0
  42. package/dist/742.js +1 -0
  43. package/dist/742.js.map +1 -0
  44. package/dist/759.js +15 -0
  45. package/dist/759.js.map +1 -0
  46. package/dist/771.js +1 -0
  47. package/dist/771.js.map +1 -0
  48. package/dist/797.js +1 -0
  49. package/dist/797.js.map +1 -0
  50. package/dist/806.js +1 -0
  51. package/dist/806.js.map +1 -0
  52. package/dist/912.js +1 -0
  53. package/dist/912.js.map +1 -0
  54. package/dist/913.js +1 -0
  55. package/dist/913.js.map +1 -0
  56. package/dist/940.js +1 -0
  57. package/dist/940.js.map +1 -0
  58. package/dist/974.js +1 -0
  59. package/dist/974.js.map +1 -0
  60. package/dist/digihmis-esm-outpatient-app.js +6 -0
  61. package/dist/digihmis-esm-outpatient-app.js.buildmanifest.json +827 -0
  62. package/dist/digihmis-esm-outpatient-app.js.map +1 -0
  63. package/dist/main.js +17 -0
  64. package/dist/main.js.map +1 -0
  65. package/dist/routes.json +1 -0
  66. package/jest.config.js +26 -0
  67. package/package.json +51 -0
  68. package/rspack.config.js +1 -0
  69. package/src/config-schema.ts +347 -0
  70. package/src/dashboard.meta.ts +16 -0
  71. package/src/declarations.d.ts +6 -0
  72. package/src/icon-wrapper/icon-wrapper.extension.tsx +17 -0
  73. package/src/index.ts +27 -0
  74. package/src/left-panel/left-panel.component.tsx +100 -0
  75. package/src/left-panel/left-panel.scss +13 -0
  76. package/src/main-dashboard/outpatient-dashboard.component.tsx +264 -0
  77. package/src/main-dashboard/outpatient-dashboard.resource.ts +71 -0
  78. package/src/main-dashboard/outpatient-dashboard.scss +166 -0
  79. package/src/main-dashboard/type/index.ts +162 -0
  80. package/src/page-header/page-header.component.tsx +35 -0
  81. package/src/page-header/page-header.scss +76 -0
  82. package/src/patient-management/components/auto-suggest/autosuggest.component.tsx +149 -0
  83. package/src/patient-management/components/auto-suggest/autosuggest.scss +61 -0
  84. package/src/patient-management/components/auto-suggest/diagnosis-autosuggest.component.tsx +131 -0
  85. package/src/patient-management/components/auto-suggest/diagnosis-autosuggest.scss +42 -0
  86. package/src/patient-management/patient-banner/patient-banner.component.tsx +60 -0
  87. package/src/patient-management/patient-banner/patient-banner.scss +72 -0
  88. package/src/patient-management/patient-management-workspace/billable-services-selector.component.tsx +150 -0
  89. package/src/patient-management/patient-management-workspace/patient-management-schema/index.ts +261 -0
  90. package/src/patient-management/patient-management-workspace/patient-management-workspace.scss +304 -0
  91. package/src/patient-management/patient-management-workspace/patient-management-workspace.tsx +1581 -0
  92. package/src/patient-management/patient-management-workspace/patient-management.resource.ts +182 -0
  93. package/src/patient-management/patient-management-workspace/type/index.ts +193 -0
  94. package/src/patient-management/patient-management-workspace/utils/index.ts +172 -0
  95. package/src/patient-management/patient-management.component.tsx +97 -0
  96. package/src/patient-management/patient-management.scss +30 -0
  97. package/src/patient-management/utils/index.ts +5 -0
  98. package/src/queue-room-table/queue-room-table.component.tsx +313 -0
  99. package/src/queue-room-table/queue-room-table.resource.ts +73 -0
  100. package/src/queue-room-table/queue-room-table.scss +193 -0
  101. package/src/queue-room-table/type/index.ts +34 -0
  102. package/src/queue-room-table/useQueueActions.ts +24 -0
  103. package/src/routes.json +55 -0
  104. package/src/setup-tests.ts +1 -0
  105. package/translations/am.json +57 -0
  106. package/translations/ar.json +57 -0
  107. package/translations/en.json +57 -0
  108. package/translations/es.json +57 -0
  109. package/translations/fr.json +57 -0
  110. package/translations/he.json +57 -0
  111. package/translations/km.json +57 -0
  112. package/translations/pt.json +57 -0
  113. package/translations/sw.json +57 -0
  114. package/translations/vi.json +57 -0
  115. package/translations/zh.json +57 -0
  116. package/tsconfig.json +5 -0
@@ -0,0 +1,264 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { PageHeader } from '../page-header/page-header.component';
3
+ import { Button, Tile, InlineLoading } from '@carbon/react';
4
+ import { ConnectionTwoWay, Login, Logout, Alarm } from '@carbon/react/icons';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { CardHeader } from '@openmrs/esm-patient-common-lib';
7
+ import { useSession, showModal, formatDate, showSnackbar, useConfig } from '@openmrs/esm-framework';
8
+ import styles from './outpatient-dashboard.scss';
9
+
10
+ import { type ProviderSession } from './type';
11
+ import { clockOutProvider, useActiveSessions, useQueueMetrics } from './outpatient-dashboard.resource';
12
+ import { ConfigObject } from '../config-schema';
13
+ import { QueueRoomTable } from '../queue-room-table/queue-room-table.component';
14
+
15
+ export const OutpatientQueueDashboard: React.FC = () => {
16
+ const { t } = useTranslation();
17
+ const { currentProvider } = useSession();
18
+ const { outpatientQueueUuid } = useConfig<ConfigObject>();
19
+
20
+ const [isProcessing, setIsProcessing] = useState(false);
21
+ const [activeSession, setActiveSession] = useState<ProviderSession | null>(null);
22
+ const isClockedIn = activeSession !== null;
23
+
24
+ const {
25
+ activeSessions,
26
+ mutate: mutateSessions,
27
+ isLoading: isLoadingSessions,
28
+ } = useActiveSessions(currentProvider?.uuid || '');
29
+
30
+ const {
31
+ metrics,
32
+ mutate: mutateMetrics,
33
+ isLoading: isLoadingMetrics,
34
+ } = useQueueMetrics(outpatientQueueUuid, activeSession?.queueRoom?.uuid, currentProvider?.uuid, isClockedIn);
35
+
36
+ useEffect(() => {
37
+ if (activeSessions && activeSessions.length > 0) {
38
+ const outpatientSession = activeSessions.find(
39
+ (session) => session.queueRoom?.queue?.uuid === outpatientQueueUuid,
40
+ );
41
+ setActiveSession(outpatientSession || null);
42
+ } else {
43
+ setActiveSession(null);
44
+ }
45
+ }, [activeSessions, outpatientQueueUuid]);
46
+
47
+ const handleClockIn = () => {
48
+ const dispose = showModal('generic-clock-in-modal', {
49
+ moduleName: 'Outpatient',
50
+ url: window.location.pathname,
51
+ queueUuid: outpatientQueueUuid,
52
+ closeModal: () => dispose(),
53
+ onClockInSuccess: (sessionData: ProviderSession) => {
54
+ setActiveSession(sessionData);
55
+ mutateSessions();
56
+ mutateMetrics();
57
+ },
58
+ });
59
+ };
60
+
61
+ const handleClockOut = async () => {
62
+ if (!activeSession?.uuid) {
63
+ showSnackbar({
64
+ title: t('error', 'Error'),
65
+ subtitle: t('noActiveSession', 'No active session found'),
66
+ kind: 'error',
67
+ });
68
+ return;
69
+ }
70
+
71
+ setIsProcessing(true);
72
+
73
+ try {
74
+ await clockOutProvider(activeSession.uuid);
75
+
76
+ showSnackbar({
77
+ title: t('clockOutSuccess', 'Clock Out Successful'),
78
+ subtitle: t('clockOutSuccessMessage', 'You have been clocked out successfully'),
79
+ kind: 'success',
80
+ timeoutInMs: 3000,
81
+ });
82
+
83
+ setActiveSession(null);
84
+ mutateSessions();
85
+ mutateMetrics();
86
+ } catch (error) {
87
+ console.error('Clock out error:', error);
88
+
89
+ const errorMessage = error?.responseBody?.error || t('clockOutFailed', 'Failed to clock out');
90
+
91
+ showSnackbar({
92
+ title: t('clockOutError', 'Clock Out Failed'),
93
+ subtitle: errorMessage,
94
+ kind: 'error',
95
+ timeoutInMs: 5000,
96
+ });
97
+ } finally {
98
+ setIsProcessing(false);
99
+ }
100
+ };
101
+
102
+ const handleSwitchServicePoint = async () => {
103
+ if (!activeSession?.uuid) {
104
+ showSnackbar({
105
+ title: t('error', 'Error'),
106
+ subtitle: t('noActiveSession', 'No active session found'),
107
+ kind: 'error',
108
+ });
109
+ return;
110
+ }
111
+
112
+ setIsProcessing(true);
113
+
114
+ try {
115
+ await clockOutProvider(activeSession.uuid);
116
+
117
+ showSnackbar({
118
+ title: t('switchingStation', 'Switching Station'),
119
+ subtitle: t('clockedOutFromCurrent', 'Clocked out from current station'),
120
+ kind: 'info',
121
+ timeoutInMs: 2000,
122
+ });
123
+
124
+ await mutateSessions();
125
+ setActiveSession(null);
126
+
127
+ const dispose = showModal('generic-clock-in-modal', {
128
+ moduleName: 'Outpatient',
129
+ url: window.location.pathname,
130
+ queueUuid: outpatientQueueUuid,
131
+ closeModal: () => dispose(),
132
+ onClockInSuccess: (sessionData: ProviderSession) => {
133
+ setActiveSession(sessionData);
134
+ mutateSessions();
135
+ mutateMetrics();
136
+ },
137
+ });
138
+ } catch (error) {
139
+ const errorMessage = error?.responseBody?.error || t('switchStationFailed', 'Failed to switch station');
140
+
141
+ showSnackbar({
142
+ title: t('switchStationError', 'Switch Station Failed'),
143
+ subtitle: errorMessage,
144
+ kind: 'error',
145
+ timeoutInMs: 5000,
146
+ });
147
+ } finally {
148
+ setIsProcessing(false);
149
+ }
150
+ };
151
+
152
+ const isLoading = isLoadingSessions || isLoadingMetrics;
153
+
154
+ return (
155
+ <div>
156
+ <PageHeader />
157
+ <div className={styles.dashboardContainer}>
158
+ <Tile className={styles.tileContainer}>
159
+ <div className={styles.displayDetails}>
160
+ <div className={styles.countLabel}>{t('myQueueWaiting', 'My Queue - Waiting')}</div>
161
+ <div className={styles.displayData}>
162
+ {isLoading ? <InlineLoading description="" /> : metrics?.myQueueWaiting || 0}
163
+ </div>
164
+ </div>
165
+ <div className={styles.displayDetails}>
166
+ <div className={styles.countLabel}>{t('myQueueInservice', 'My Queue - In Service')}</div>
167
+ <div className={styles.displayData}>
168
+ {isLoading ? <InlineLoading description="" /> : metrics?.inProgress || 0}
169
+ </div>
170
+ </div>
171
+ <div className={styles.displayDetails}>
172
+ <div className={styles.countLabel}>{t('avgWaitTime', 'Avg. Wait Time')}</div>
173
+ <div className={styles.displayData}>
174
+ {isLoading ? (
175
+ <InlineLoading description="" />
176
+ ) : (
177
+ (() => {
178
+ const totalMinutes = metrics?.averageWaitTime || 0;
179
+ const hours = Math.floor(totalMinutes / 60);
180
+ const minutes = totalMinutes % 60;
181
+ return hours > 0 ? `${hours}h ${minutes}min` : `${minutes}min`;
182
+ })()
183
+ )}
184
+ </div>
185
+ </div>
186
+
187
+ <div className={styles.displayDetails}>
188
+ <div className={styles.countLabel}>{t('completedToday', 'Completed Today')}</div>
189
+ <div className={styles.displayData}>
190
+ {isLoading ? <InlineLoading description="" /> : metrics?.completedToday || 0}
191
+ </div>
192
+ </div>
193
+ </Tile>
194
+
195
+ {isClockedIn && activeSession && (
196
+ <Tile className={styles.clockInBanner}>
197
+ <div className={styles.clockInInfo}>
198
+ <Alarm size={20} className={styles.alarmIcon} />
199
+ <div className={styles.clockInText}>
200
+ <strong>{t('clockedInTo', 'Clocked in to')}: </strong>
201
+ {activeSession?.queueRoom?.display}
202
+ <span className={styles.clockInTime}>
203
+ {' '}
204
+ ({t('since', 'since')} {formatDate(new Date(activeSession.clockInTime), { mode: 'wide' })})
205
+ </span>
206
+ </div>
207
+ </div>
208
+ </Tile>
209
+ )}
210
+
211
+ <div className={styles.cardHeaderContainer}>
212
+ <CardHeader title={activeSession?.queueRoom?.display || t('outpatientQueue', 'Outpatient Queue')}>
213
+ <div className={styles.headerActions}>
214
+ <Button
215
+ kind={isClockedIn ? 'danger' : 'primary'}
216
+ size="sm"
217
+ renderIcon={(props) => (isClockedIn ? <Logout size={16} {...props} /> : <Login size={16} {...props} />)}
218
+ onClick={isClockedIn ? handleClockOut : handleClockIn}
219
+ disabled={isProcessing}>
220
+ {isProcessing ? (
221
+ <InlineLoading
222
+ description={isClockedIn ? t('clockingOut', 'Clocking out') : t('clockingIn', 'Clocking in')}
223
+ />
224
+ ) : isClockedIn ? (
225
+ t('clockOut', 'Clock Out')
226
+ ) : (
227
+ t('clockIn', 'Clock In')
228
+ )}
229
+ </Button>
230
+
231
+ {isClockedIn && (
232
+ <Button
233
+ kind="secondary"
234
+ size="sm"
235
+ renderIcon={(props) => <ConnectionTwoWay size={16} {...props} />}
236
+ onClick={handleSwitchServicePoint}
237
+ disabled={isProcessing}>
238
+ {t('changeStation', 'Change Station')}
239
+ </Button>
240
+ )}
241
+ </div>
242
+ </CardHeader>
243
+ </div>
244
+
245
+ {isClockedIn && activeSession ? (
246
+ <div className={styles.queueContainer}>
247
+ <QueueRoomTable queueRoomUuid={activeSession.queueRoom.uuid} queueUuid={outpatientQueueUuid} />
248
+ </div>
249
+ ) : (
250
+ <div className={styles.emptyStateContainer}>
251
+ <Tile className={styles.tile}>
252
+ <div className={styles.tileContent}>
253
+ <p className={styles.content}>{t('pleaseClockIn', 'Please clock in to view the queue')}</p>
254
+ <p className={styles.emptyStateHelperText}>
255
+ {t('clockInPrompt', 'Click the "Clock In" button above to start managing patients')}
256
+ </p>
257
+ </div>
258
+ </Tile>
259
+ </div>
260
+ )}
261
+ </div>
262
+ </div>
263
+ );
264
+ };
@@ -0,0 +1,71 @@
1
+ import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { type ProviderSession, type QueueMetricsResponse } from './type';
4
+
5
+ export const clockOutProvider = async (sessionUuid?: string, providerUuid?: string) => {
6
+ const postUrl = `${restBaseUrl}/provider-clock/clock-out`;
7
+
8
+ return openmrsFetch(postUrl, {
9
+ method: 'POST',
10
+ headers: {
11
+ 'Content-Type': 'application/json',
12
+ },
13
+ body: JSON.stringify({
14
+ ...(sessionUuid && { sessionUuid }),
15
+ ...(providerUuid && { providerUuid }),
16
+ }),
17
+ });
18
+ };
19
+
20
+ export const useActiveSessions = (providerUuid: string, shouldRefresh: boolean = false) => {
21
+ const apiUrl = providerUuid ? `${restBaseUrl}/provider-clock/active-sessions?provider=${providerUuid}` : null;
22
+
23
+ const { data, error, isLoading, mutate } = useSWR<FetchResponse<Array<ProviderSession>>>(apiUrl, openmrsFetch, {
24
+ refreshInterval: shouldRefresh ? 60000 : 0,
25
+ revalidateOnFocus: true,
26
+ dedupingInterval: 30000,
27
+ });
28
+
29
+ return {
30
+ activeSessions: data?.data ?? [],
31
+ error,
32
+ isLoading,
33
+ mutate,
34
+ };
35
+ };
36
+
37
+ export const useQueueMetrics = (
38
+ queueUuid?: string,
39
+ queueRoomUuid?: string,
40
+ providerUuid?: string,
41
+ isActive: boolean = false,
42
+ ) => {
43
+ const params = new URLSearchParams();
44
+ if (queueUuid) {
45
+ params.append('queue', queueUuid);
46
+ }
47
+ if (queueRoomUuid) {
48
+ params.append('queueRoom', queueRoomUuid);
49
+ }
50
+ if (providerUuid) {
51
+ params.append('provider', providerUuid);
52
+ }
53
+
54
+ const apiUrl = isActive
55
+ ? `${restBaseUrl}/provider-clock/metrics${params.toString() ? `?${params.toString()}` : ''}`
56
+ : null;
57
+
58
+ const { data, error, isLoading, mutate } = useSWR<FetchResponse<QueueMetricsResponse>>(apiUrl, openmrsFetch, {
59
+ refreshInterval: isActive ? 60000 : 0,
60
+ revalidateOnFocus: true,
61
+ revalidateOnReconnect: true,
62
+ dedupingInterval: 30000,
63
+ });
64
+
65
+ return {
66
+ metrics: data?.data,
67
+ error,
68
+ isLoading,
69
+ mutate,
70
+ };
71
+ };
@@ -0,0 +1,166 @@
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 colors.$cool-gray-30;
8
+ margin-bottom: layout.$spacing-04;
9
+ display: grid;
10
+ grid-template-columns: 1fr 1fr 1fr 1fr;
11
+ gap: 0;
12
+ height: 80px;
13
+
14
+ :global(.omrs-breakpoint-lt-desktop) & {
15
+ padding: layout.$spacing-04;
16
+ }
17
+
18
+ :global(.omrs-breakpoint-lt-tablet) & {
19
+ padding: layout.$spacing-03;
20
+ grid-template-columns: 1fr;
21
+ grid-template-rows: auto auto auto;
22
+ gap: layout.$spacing-04;
23
+ }
24
+ }
25
+
26
+ .tileHeader {
27
+ color: colors.$gray-70;
28
+ margin-bottom: layout.$spacing-03;
29
+ text-transform: none;
30
+ letter-spacing: 0;
31
+
32
+ :global(.omrs-breakpoint-lt-tablet) & {
33
+ margin-bottom: layout.$spacing-02;
34
+ }
35
+ }
36
+
37
+ .displayDetails {
38
+ display: flex;
39
+ flex-direction: column;
40
+ align-items: flex-start;
41
+
42
+ :global(.omrs-breakpoint-lt-tablet) & {
43
+ padding: layout.$spacing-03 0;
44
+ border-right: none;
45
+ border-bottom: 1px solid colors.$gray-20;
46
+ padding-bottom: layout.$spacing-04;
47
+ margin-bottom: layout.$spacing-03;
48
+ }
49
+ }
50
+
51
+ .countLabel {
52
+ color: colors.$gray-70;
53
+ margin-bottom: layout.$spacing-02;
54
+ @include type.type-style('caption-01');
55
+ font-weight: bold;
56
+
57
+ :global(.omrs-breakpoint-lt-tablet) & {
58
+ @include type.type-style('caption-01');
59
+ font-weight: bold;
60
+ margin-bottom: layout.$spacing-01;
61
+ }
62
+ }
63
+
64
+ .displayData {
65
+ color: colors.$gray-100;
66
+ font-weight: bold;
67
+ @include type.type-style('caption-01');
68
+ font-size: 25px;
69
+
70
+ :global(.omrs-breakpoint-lt-tablet) & {
71
+ font-size: 2.625rem;
72
+ line-height: 2.75rem;
73
+ }
74
+ }
75
+
76
+ .headerActions {
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ padding-right: 1rem;
81
+
82
+ :global(.omrs-breakpoint-lt-tablet) & {
83
+ gap: 0.25rem;
84
+ }
85
+ }
86
+
87
+ .dashboardContainer {
88
+ padding: layout.$spacing-05;
89
+ padding-bottom: 0;
90
+ background-color: var(--cds-field);
91
+
92
+ :global(.omrs-breakpoint-lt-desktop) & {
93
+ padding: layout.$spacing-02;
94
+ padding-bottom: 0;
95
+ }
96
+
97
+ :global(.omrs-breakpoint-lt-tablet) & {
98
+ padding: layout.$spacing-03;
99
+ padding-bottom: 0;
100
+ }
101
+ }
102
+
103
+ .clockInInfo {
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: flex-end;
107
+ width: 100%;
108
+ gap: layout.$spacing-03;
109
+
110
+ :global(.omrs-breakpoint-lt-tablet) & {
111
+ flex-direction: row;
112
+ align-items: center;
113
+ gap: layout.$spacing-02;
114
+ }
115
+ }
116
+
117
+ .clockInText {
118
+ display: flex;
119
+ align-items: center;
120
+ flex-wrap: wrap;
121
+ gap: 0.25rem;
122
+
123
+ :global(.omrs-breakpoint-lt-tablet) & {
124
+ font-size: 0.875rem;
125
+ }
126
+ }
127
+
128
+ .clockInTime {
129
+ color: colors.$gray-70;
130
+ white-space: nowrap;
131
+ }
132
+
133
+ .alarmIcon {
134
+ flex-shrink: 0;
135
+
136
+ :global(.omrs-breakpoint-lt-tablet) & {
137
+ width: 18px;
138
+ height: 18px;
139
+ }
140
+ }
141
+
142
+ .emptyStateContainer {
143
+ border: 1px solid colors.$gray-20;
144
+ padding: 3rem 0;
145
+ }
146
+
147
+ .tile {
148
+ margin: auto;
149
+ width: fit-content;
150
+ background: colors.$gray-20;
151
+ }
152
+
153
+ .tileContent {
154
+ display: flex;
155
+ flex-direction: column;
156
+ align-items: center;
157
+ }
158
+
159
+ .content {
160
+ @include type.type-style('heading-compact-02');
161
+ color: colors.$gray-70;
162
+ margin-bottom: layout.$spacing-03;
163
+ }
164
+ .loader {
165
+ min-height: fit-content;
166
+ }
@@ -0,0 +1,162 @@
1
+ import { type OpenmrsResource } from '@openmrs/esm-framework';
2
+
3
+ export interface Concept extends OpenmrsResource {
4
+ setMembers?: Array<Concept>;
5
+ answers?: Array<ConceptAnswer>;
6
+ }
7
+
8
+ export interface ConceptAnswer {
9
+ uuid: string;
10
+ display: string;
11
+ }
12
+
13
+ export interface ConceptAnswers {
14
+ uuid: string;
15
+ display: string;
16
+ }
17
+
18
+ export interface ConceptResponse {
19
+ uuid: string;
20
+ display: string;
21
+ answers: Array<ConceptAnswers>;
22
+ }
23
+
24
+ export interface QueueEntry {
25
+ uuid: string;
26
+ queue: {
27
+ uuid: string;
28
+ display: string;
29
+ };
30
+ queueRoom: {
31
+ uuid: string;
32
+ display: string;
33
+ };
34
+ status: {
35
+ uuid: string;
36
+ display: string;
37
+ };
38
+ priority: {
39
+ uuid: string;
40
+ display: string;
41
+ };
42
+ patient: {
43
+ uuid: string;
44
+ display: string;
45
+ person?: {
46
+ age: number;
47
+ gender: string;
48
+ };
49
+ };
50
+ startedAt: string;
51
+ endedAt?: string;
52
+ sortWeight?: number;
53
+ queueComingFrom?: {
54
+ uuid: string;
55
+ display: string;
56
+ };
57
+ queueRoomComingFrom?: {
58
+ uuid: string;
59
+ display: string;
60
+ };
61
+ visit?: {
62
+ uuid: string;
63
+ };
64
+ }
65
+
66
+ export interface Queue {
67
+ uuid: string;
68
+ display: string;
69
+ name: string;
70
+ description: string;
71
+ location: {
72
+ uuid: string;
73
+ display: string;
74
+ };
75
+ service: {
76
+ uuid: string;
77
+ display: string;
78
+ };
79
+ allowedPriorities: Array<{
80
+ uuid: string;
81
+ display: string;
82
+ }>;
83
+ allowedStatuses: Array<{
84
+ uuid: string;
85
+ display: string;
86
+ }>;
87
+ queueRooms: Array<QueueRoom>;
88
+ }
89
+
90
+ export interface QueueRoom {
91
+ uuid: string;
92
+ display: string;
93
+ name: string;
94
+ description: string;
95
+ queue: {
96
+ uuid: string;
97
+ display: string;
98
+ };
99
+ }
100
+
101
+ export interface Patient {
102
+ uuid: string;
103
+ display: string;
104
+ person: {
105
+ uuid: string;
106
+ display: string;
107
+ gender: string;
108
+ age: number;
109
+ birthdate: string;
110
+ dead: boolean;
111
+ deathDate?: string;
112
+ causeOfDeath?: {
113
+ uuid: string;
114
+ display: string;
115
+ };
116
+ identifiers: Array<{
117
+ uuid: string;
118
+ identifier: string;
119
+ identifierType: {
120
+ uuid: string;
121
+ display: string;
122
+ };
123
+ preferred: boolean;
124
+ }>;
125
+ };
126
+ }
127
+
128
+ export interface ProviderSession {
129
+ uuid: string;
130
+ provider: {
131
+ uuid: string;
132
+ display: string;
133
+ };
134
+ queueRoom: {
135
+ uuid: string;
136
+ display: string;
137
+ queue: {
138
+ uuid: string;
139
+ display: string;
140
+ };
141
+ };
142
+ clockInTime: string;
143
+ clockOutTime?: string;
144
+ status: 'ACTIVE' | 'CLOCKED_OUT';
145
+ }
146
+
147
+ export interface ProviderSessionResponse {
148
+ results: Array<ProviderSession>;
149
+ }
150
+
151
+ export interface QueueResponse {
152
+ results: Array<Queue>;
153
+ }
154
+
155
+ export interface QueueMetrics {
156
+ myQueueWaiting: number;
157
+ inProgress: number;
158
+ averageWaitTime: number;
159
+ completedToday: number;
160
+ }
161
+
162
+ export interface QueueMetricsResponse extends QueueMetrics {}
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Calendar } from '@carbon/react/icons';
4
+ import { useSession, formatDate, ExtensionSlot, useConfig } from '@openmrs/esm-framework';
5
+ import styles from './page-header.scss';
6
+ import { ConfigObject } from '../config-schema';
7
+ import { IconWrapper } from '../icon-wrapper/icon-wrapper.extension';
8
+
9
+ export const PageHeader: React.FC = () => {
10
+ const { t } = useTranslation();
11
+ const userSession = useSession();
12
+ const { outpatientIcon } = useConfig<ConfigObject>();
13
+ const userLocation = userSession?.sessionLocation?.display;
14
+
15
+ return (
16
+ <div className={styles.header}>
17
+ <div className={styles['left-justified-items']}>
18
+ <IconWrapper src={outpatientIcon.src} alt={outpatientIcon.alt} />
19
+ <div className={styles['page-labels']}>
20
+ <p className={styles['page-name']}>{userLocation}</p>
21
+ <p className={styles['page-subtitle']}>{t('outpatient', 'Outpatient')}</p>
22
+ </div>
23
+ </div>
24
+ <div className={styles['right-justified-items']}>
25
+ <div className={styles.extensionSlot}>
26
+ <ExtensionSlot name={'provider-banner-slot'} />
27
+ <div className={styles['date-and-location']}>
28
+ <Calendar size={16} />
29
+ <span className={styles.value}>{formatDate(new Date(), { mode: 'wide', noToday: true })}</span>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ );
35
+ };