@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,313 @@
1
+ import React, { useState, useMemo } from 'react';
2
+ import {
3
+ DataTable,
4
+ Table,
5
+ TableHead,
6
+ TableRow,
7
+ TableHeader,
8
+ TableBody,
9
+ TableCell,
10
+ Button,
11
+ TableContainer,
12
+ Tile,
13
+ Pagination,
14
+ Tag,
15
+ InlineLoading,
16
+ Search,
17
+ } from '@carbon/react';
18
+ import { CheckmarkFilled, Time } from '@carbon/react/icons';
19
+ import { useTranslation } from 'react-i18next';
20
+ import { isDesktop, launchWorkspace, navigate, showSnackbar, useConfig, useLayoutType } from '@openmrs/esm-framework';
21
+ import { useQueueEntries } from './queue-room-table.resource';
22
+ import { useQueueActions } from './useQueueActions';
23
+ import styles from './queue-room-table.scss';
24
+ import { type QueueEntry } from './type';
25
+ import capitalize from 'lodash/capitalize';
26
+
27
+ interface QueueRoomTableProps {
28
+ queueRoomUuid: string;
29
+ queueUuid: string;
30
+ }
31
+
32
+ export const QueueRoomTable: React.FC<QueueRoomTableProps> = ({ queueRoomUuid, queueUuid }) => {
33
+ const { t } = useTranslation();
34
+ const config = useConfig();
35
+ const layout = useLayoutType();
36
+ const { servePatient, completeService } = useQueueActions();
37
+
38
+ const [page, setPage] = useState(1);
39
+ const [pageSize, setPageSize] = useState(10);
40
+ const [searchTerm, setSearchTerm] = useState('');
41
+ const [processingUuid, setProcessingUuid] = useState<string | null>(null);
42
+
43
+ const { queueEntries, isLoading, mutate } = useQueueEntries(queueRoomUuid, queueUuid);
44
+
45
+ const filteredEntries = useMemo(() => {
46
+ if (!queueEntries) {
47
+ return [];
48
+ }
49
+ if (!searchTerm) {
50
+ return queueEntries;
51
+ }
52
+
53
+ const search = searchTerm.toLowerCase();
54
+ return queueEntries?.filter(
55
+ (entry) =>
56
+ entry?.patient?.display.toLowerCase().includes(search) ||
57
+ entry?.display.toLowerCase().includes(search) ||
58
+ entry?.queueRoomComingFrom?.display?.toLowerCase().includes(search),
59
+ );
60
+ }, [queueEntries, searchTerm]);
61
+
62
+ const paginatedEntries = useMemo(() => {
63
+ const start = (page - 1) * pageSize;
64
+ return filteredEntries.slice(start, start + pageSize);
65
+ }, [filteredEntries, page, pageSize]);
66
+
67
+ const calculateWaitTime = (startedAt: string) => {
68
+ const start = new Date(startedAt);
69
+ const now = new Date();
70
+ const diffMs = now.getTime() - start.getTime();
71
+ const diffMins = Math.floor(diffMs / 60000);
72
+ return diffMins;
73
+ };
74
+
75
+ const getPriorityColor = (priority: string) => {
76
+ const lower = priority.toLowerCase();
77
+ if (lower.includes('emergency')) {
78
+ return 'red';
79
+ }
80
+ if (lower.includes('urgent')) {
81
+ return 'magenta';
82
+ }
83
+ if (lower.includes('priority')) {
84
+ return 'purple';
85
+ }
86
+ return 'gray';
87
+ };
88
+
89
+ const handleServePatient = async (entry: QueueEntry) => {
90
+ setProcessingUuid(entry.uuid);
91
+
92
+ try {
93
+ await servePatient(entry.uuid);
94
+ navigate({ to: `${window.spaBase}/home/outpatient/patient/${entry?.patient?.uuid}/file` });
95
+
96
+ showSnackbar({
97
+ title: t('patientServed', 'Patient Served'),
98
+ subtitle: t('patientServedMessage', `${entry?.patient?.person?.display} is now being served`),
99
+ kind: 'success',
100
+ timeoutInMs: 3000,
101
+ });
102
+
103
+ mutate();
104
+ } catch (error) {
105
+ console.error('Error serving patient:', error);
106
+
107
+ showSnackbar({
108
+ title: t('error', 'Error'),
109
+ subtitle: t('servePatientError', 'Failed to serve patient. Please try again.'),
110
+ kind: 'error',
111
+ timeoutInMs: 5000,
112
+ });
113
+ } finally {
114
+ setProcessingUuid(null);
115
+ }
116
+ };
117
+
118
+ const headers = [
119
+ { key: 'position', header: '#' },
120
+ { key: 'patientName', header: t('patient', 'Patient') },
121
+ { key: 'age', header: t('age', 'Age') },
122
+ { key: 'gender', header: t('gender', 'Gender') },
123
+ { key: 'comingFrom', header: t('comingFrom', 'Coming From') },
124
+ { key: 'waitTime', header: t('waitTime', 'Wait Time') },
125
+ { key: 'priority', header: t('priority', 'Priority') },
126
+ { key: 'status', header: t('status', 'Status') },
127
+ { key: 'actions', header: t('actions', 'Actions') },
128
+ { key: 'statusUuid', header: '' },
129
+ { key: 'patientUuid', header: '' },
130
+ { key: 'entryUuid', header: '' },
131
+ { key: 'priorityColor', header: '' },
132
+ { key: 'entry', header: '' },
133
+ ];
134
+
135
+ const rows = paginatedEntries?.map((entry, index) => {
136
+ const patientUuid = entry?.patient?.uuid || '';
137
+ const statusUuid = entry?.status?.uuid || '';
138
+ const entryUuid = entry?.uuid || '';
139
+
140
+ return {
141
+ id: entryUuid,
142
+ position: (page - 1) * pageSize + index + 1,
143
+ patientName: capitalize(entry?.patient?.person?.display || ''),
144
+ patientUuid: patientUuid,
145
+ age: entry?.patient?.person?.age,
146
+ gender: entry?.patient?.person?.gender === 'M' ? t('male', 'Male') : t('female', 'Female'),
147
+ comingFrom: entry?.queueRoomComingFrom?.display || t('direct', 'Direct Entry'),
148
+ priority: entry?.priority?.display || t('normal', 'Normal'),
149
+ priorityColor: getPriorityColor(entry?.priority?.display || ''),
150
+ waitTime: calculateWaitTime(entry?.startedAt),
151
+ status: entry?.status?.display,
152
+ statusUuid: statusUuid,
153
+ entryUuid: entryUuid,
154
+ entry: entry,
155
+ };
156
+ });
157
+
158
+ if (isLoading) {
159
+ return (
160
+ <div className={styles.loadingContainer}>
161
+ <InlineLoading description={t('loadingQueue', 'Loading queue...')} />
162
+ </div>
163
+ );
164
+ }
165
+
166
+ if (!queueEntries || queueEntries?.length === 0) {
167
+ return (
168
+ <div className={styles.emptyStateContainer}>
169
+ <Tile className={styles.tile}>
170
+ <div className={styles.tileContent}>
171
+ <p className={styles.content}>{t('noPatients', 'No patients in queue')}</p>
172
+ <p className={styles.emptyStateHelperText}>
173
+ {t('noPatientsDescription', 'There are currently no patients waiting in this queue room.')}
174
+ </p>
175
+ </div>
176
+ </Tile>
177
+ </div>
178
+ );
179
+ }
180
+
181
+ return (
182
+ <div className={styles.tableContainer}>
183
+ <Search
184
+ labelText={t('filterPatients', 'Filter patients')}
185
+ placeholder={t('filterPatients', 'Filter patients')}
186
+ onChange={(e) => setSearchTerm(e.target.value)}
187
+ size={isDesktop(layout) ? 'sm' : 'lg'}
188
+ />
189
+ <DataTable rows={rows} headers={headers} size={isDesktop(layout) ? 'sm' : 'lg'} useZebraStyles={rows?.length > 1}>
190
+ {({ rows, headers, getHeaderProps, getRowProps, getTableProps }) => (
191
+ <TableContainer className={styles.tableContainer}>
192
+ <Table className={styles.activeVisitsTable} {...getTableProps()}>
193
+ <TableHead>
194
+ <TableRow>
195
+ {headers.map(
196
+ (header) =>
197
+ header.header && (
198
+ <TableHeader key={header.key} {...getHeaderProps({ header })}>
199
+ {header.header}
200
+ </TableHeader>
201
+ ),
202
+ )}
203
+ </TableRow>
204
+ </TableHead>
205
+ <TableBody>
206
+ {rows.map((row) => (
207
+ <TableRow key={row.id} {...getRowProps({ row })}>
208
+ {row.cells.map((cell) => {
209
+ if (
210
+ ['statusUuid', 'patientUuid', 'entryUuid', 'priorityColor', 'entry'].includes(cell.info.header)
211
+ ) {
212
+ return null;
213
+ }
214
+
215
+ if (cell.info.header === 'comingFrom') {
216
+ return (
217
+ <TableCell key={cell.id}>
218
+ <Tag type="cyan" size="md">
219
+ {cell.value}
220
+ </Tag>
221
+ </TableCell>
222
+ );
223
+ }
224
+
225
+ if (cell.info.header === 'priority') {
226
+ return (
227
+ <TableCell key={cell.id}>
228
+ <Tag type={row.cells.find((c) => c.info.header === 'priorityColor')?.value}>
229
+ {cell.value}
230
+ </Tag>
231
+ </TableCell>
232
+ );
233
+ }
234
+
235
+ if (cell.info.header === 'waitTime') {
236
+ const mins = cell.value;
237
+ const hours = Math.floor(mins / 60);
238
+ const minutes = mins % 60;
239
+ const displayTime = hours > 0 ? `${hours}h ${minutes}min` : `${minutes}min`;
240
+
241
+ return (
242
+ <TableCell key={cell.id}>
243
+ <div className={styles.waitTime}>
244
+ <Time size={16} />
245
+ <span className={mins > 60 ? styles.longWait : ''}>{displayTime}</span>
246
+ </div>
247
+ </TableCell>
248
+ );
249
+ }
250
+
251
+ if (cell.info.header === 'status') {
252
+ const statusUuid = row.cells.find((c) => c.info.header === 'statusUuid')?.value;
253
+ const isWaiting = statusUuid === config.concepts.waitingStatusUuid;
254
+ const isInService = statusUuid === config.concepts.inServiceStatusUuid;
255
+
256
+ return (
257
+ <TableCell key={cell.id}>
258
+ <Tag type={isWaiting ? 'blue' : isInService ? 'green' : 'gray'}>{cell.value}</Tag>
259
+ </TableCell>
260
+ );
261
+ }
262
+
263
+ if (cell.info.header === 'actions') {
264
+ const entryUuid = row.cells.find((c) => c.info.header === 'entryUuid')?.value as string;
265
+ const statusUuid = row.cells.find((c) => c.info.header === 'statusUuid')?.value as string;
266
+ const entry = row.cells.find((c) => c.info.header === 'entry')?.value as QueueEntry;
267
+
268
+ const isProcessing = processingUuid === entryUuid;
269
+ const isWaiting = statusUuid === config.concepts.waitingStatusUuid;
270
+ const isInService = statusUuid === config.concepts.inServiceStatusUuid;
271
+ return (
272
+ <TableCell key={cell.id}>
273
+ <div className={styles.actions}>
274
+ {(isWaiting || isInService) && (
275
+ <Button
276
+ kind="primary"
277
+ size="sm"
278
+ renderIcon={CheckmarkFilled}
279
+ onClick={() => handleServePatient(entry)}
280
+ disabled={isProcessing}>
281
+ {isProcessing
282
+ ? t('servePatientLoading', 'Serving Patient...')
283
+ : t('servePatient', 'Serve Patient')}
284
+ </Button>
285
+ )}
286
+ </div>
287
+ </TableCell>
288
+ );
289
+ }
290
+
291
+ return <TableCell key={cell.id}>{cell.value}</TableCell>;
292
+ })}
293
+ </TableRow>
294
+ ))}
295
+ </TableBody>
296
+ </Table>
297
+ </TableContainer>
298
+ )}
299
+ </DataTable>
300
+ <Pagination
301
+ className={styles.pagination}
302
+ page={page}
303
+ pageSize={pageSize}
304
+ pageSizes={[10, 20, 30, 50]}
305
+ totalItems={filteredEntries.length}
306
+ onChange={({ page, pageSize }) => {
307
+ setPage(page);
308
+ setPageSize(pageSize);
309
+ }}
310
+ />
311
+ </div>
312
+ );
313
+ };
@@ -0,0 +1,73 @@
1
+ import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { type QueueEntry } from './type';
4
+
5
+ export const useQueueEntries = (queueRoomUuid: string, queueUuid: string) => {
6
+ const customRepresentation =
7
+ 'custom:(uuid,display,patient:(uuid,display,person:(display,age,gender)),priority:(uuid,display),status:(uuid,display),visit:(uuid),startedAt,sortWeight,queueRoom:(uuid,display),queueRoomComingFrom:(uuid,display))';
8
+
9
+ const params = new URLSearchParams();
10
+ params.append('v', customRepresentation);
11
+ params.append('queue', queueUuid);
12
+ params.append('queueRoom', queueRoomUuid);
13
+ params.append('isEnded', 'false');
14
+
15
+ const apiUrl = `${restBaseUrl}/queue-entry?${params.toString()}`;
16
+
17
+ const { data, error, mutate, isLoading, isValidating } = useSWR<FetchResponse<{ results: Array<QueueEntry> }>>(
18
+ queueRoomUuid ? apiUrl : null,
19
+ openmrsFetch,
20
+ {
21
+ refreshInterval: 30000,
22
+ revalidateOnFocus: true,
23
+ },
24
+ );
25
+
26
+ const sortedEntries = data?.data?.results?.sort((a, b) => {
27
+ const priorityOrder: Record<string, number> = {
28
+ emergency: 0,
29
+ urgent: 1,
30
+ priority: 2,
31
+ normal: 3,
32
+ };
33
+
34
+ const aPriority = a.priority?.display?.toLowerCase() || 'normal';
35
+ const bPriority = b.priority?.display?.toLowerCase() || 'normal';
36
+
37
+ const aOrder = priorityOrder[aPriority] ?? 3;
38
+ const bOrder = priorityOrder[bPriority] ?? 3;
39
+
40
+ if (aOrder !== bOrder) {
41
+ return aOrder - bOrder;
42
+ }
43
+
44
+ return (a.sortWeight || 0) - (b.sortWeight || 0);
45
+ });
46
+
47
+ return {
48
+ queueEntries: sortedEntries || [],
49
+ isLoading,
50
+ error,
51
+ mutate,
52
+ isValidating,
53
+ };
54
+ };
55
+
56
+ export const updateQueueEntryStatus = async (
57
+ queueEntryUuid: string,
58
+ statusUuid: string,
59
+ completedStatusUuid: string,
60
+ ) => {
61
+ const url = `${restBaseUrl}/queue-entry/${queueEntryUuid}`;
62
+
63
+ return openmrsFetch(url, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ body: JSON.stringify({
69
+ status: statusUuid,
70
+ endedAt: statusUuid === completedStatusUuid ? new Date().toISOString() : undefined,
71
+ }),
72
+ });
73
+ };
@@ -0,0 +1,193 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+ @use '@openmrs/esm-styleguide/src/vars' as *;
5
+
6
+ .activeVisitsContainer {
7
+ background-color: $ui-02;
8
+ border: 1px solid $ui-03;
9
+ width: 100%;
10
+ margin: 1rem auto;
11
+ max-width: 95vw;
12
+ padding-bottom: 0;
13
+
14
+ :global(.omrs-breakpoint-lt-tablet) & {
15
+ margin: 0.5rem auto;
16
+ max-width: 100vw;
17
+ border-left: none;
18
+ border-right: none;
19
+ }
20
+ }
21
+
22
+ .tableContainer {
23
+ width: 100%;
24
+
25
+ :global(.omrs-breakpoint-lt-tablet) & {
26
+ overflow-x: auto;
27
+ }
28
+ }
29
+
30
+ .activeVisitsTable {
31
+ width: 100%;
32
+
33
+ th,
34
+ td {
35
+ white-space: nowrap;
36
+ text-align: left;
37
+ }
38
+
39
+ tbody tr[data-parent-row] {
40
+ &:nth-last-of-type(2) > td {
41
+ border-bottom: none;
42
+ }
43
+ }
44
+
45
+ :global(.omrs-breakpoint-lt-desktop) & {
46
+ th,
47
+ td {
48
+ padding: layout.$spacing-03;
49
+ font-size: 0.875rem;
50
+ }
51
+ }
52
+
53
+ :global(.omrs-breakpoint-lt-tablet) & {
54
+ th,
55
+ td {
56
+ padding: layout.$spacing-02;
57
+ font-size: 0.8125rem;
58
+ }
59
+ }
60
+ }
61
+
62
+ .loadingContainer {
63
+ display: flex;
64
+ justify-content: center;
65
+ align-items: center;
66
+ padding: layout.$spacing-07;
67
+
68
+ :global(.omrs-breakpoint-lt-tablet) & {
69
+ padding: layout.$spacing-05;
70
+ }
71
+ }
72
+
73
+ .emptyStateContainer {
74
+ border: 1px solid colors.$gray-20;
75
+ padding: 3rem 0;
76
+
77
+ :global(.omrs-breakpoint-lt-desktop) & {
78
+ padding: 2rem 0;
79
+ }
80
+
81
+ :global(.omrs-breakpoint-lt-tablet) & {
82
+ padding: 1.5rem 0;
83
+ border-left: none;
84
+ border-right: none;
85
+ }
86
+ }
87
+
88
+ .tile {
89
+ margin: auto;
90
+ width: fit-content;
91
+ background: colors.$gray-20;
92
+
93
+ :global(.omrs-breakpoint-lt-tablet) & {
94
+ width: 90%;
95
+ padding: layout.$spacing-04;
96
+ }
97
+ }
98
+
99
+ .tileContent {
100
+ display: flex;
101
+ flex-direction: column;
102
+ align-items: center;
103
+
104
+ :global(.omrs-breakpoint-lt-tablet) & {
105
+ text-align: center;
106
+ }
107
+ }
108
+
109
+ .content {
110
+ @include type.type-style('heading-compact-02');
111
+ color: colors.$gray-70;
112
+ margin-bottom: layout.$spacing-03;
113
+
114
+ :global(.omrs-breakpoint-lt-tablet) & {
115
+ @include type.type-style('heading-compact-01');
116
+ }
117
+ }
118
+
119
+ .emptyStateHelperText {
120
+ :global(.omrs-breakpoint-lt-tablet) & {
121
+ font-size: 0.875rem;
122
+ padding: 0 layout.$spacing-03;
123
+ }
124
+ }
125
+
126
+ .waitTime {
127
+ display: flex;
128
+ align-items: center;
129
+ gap: layout.$spacing-02;
130
+
131
+ :global(.omrs-breakpoint-lt-tablet) & {
132
+ gap: layout.$spacing-01;
133
+ font-size: 0.75rem;
134
+
135
+ svg {
136
+ width: 14px;
137
+ height: 14px;
138
+ }
139
+ }
140
+ }
141
+
142
+ .longWait {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: layout.$spacing-03;
146
+
147
+ :global(.omrs-breakpoint-lt-tablet) & {
148
+ gap: layout.$spacing-02;
149
+ }
150
+ }
151
+
152
+ .actions {
153
+ display: flex;
154
+ flex-direction: row;
155
+ align-items: center;
156
+ // flex-wrap: wrap;
157
+
158
+ :global(.omrs-breakpoint-lt-desktop) & {
159
+ gap: layout.$spacing-02;
160
+ flex-direction: row;
161
+ }
162
+
163
+ :global(.omrs-breakpoint-lt-tablet) & {
164
+ gap: layout.$spacing-02;
165
+ flex-direction: row;
166
+
167
+ button {
168
+ font-size: 0.75rem;
169
+ padding: layout.$spacing-02 layout.$spacing-03;
170
+ }
171
+ }
172
+ }
173
+
174
+ .pagination {
175
+ background-color: colors.$gray-20;
176
+ border: 1px solid colors.$gray-20;
177
+
178
+ :global(.omrs-breakpoint-lt-desktop) & {
179
+ font-size: 0.875rem;
180
+ }
181
+
182
+ :global(.omrs-breakpoint-lt-tablet) & {
183
+ flex-wrap: wrap;
184
+ padding: layout.$spacing-03;
185
+
186
+ :global(.cds--pagination__left),
187
+ :global(.cds--pagination__right) {
188
+ width: 100%;
189
+ justify-content: center;
190
+ margin: layout.$spacing-02 0;
191
+ }
192
+ }
193
+ }
@@ -0,0 +1,34 @@
1
+ export interface QueueEntry {
2
+ uuid: string;
3
+ display: string;
4
+ patient: {
5
+ uuid: string;
6
+ display: string;
7
+ person: {
8
+ display: string;
9
+ age: number;
10
+ gender: string;
11
+ };
12
+ };
13
+ priority: {
14
+ uuid: string;
15
+ display: string;
16
+ };
17
+ status: {
18
+ uuid: string;
19
+ display: string;
20
+ };
21
+ visit: {
22
+ uuid: string;
23
+ };
24
+ queueRoom: {
25
+ uuid: string;
26
+ display: string;
27
+ };
28
+ queueRoomComingFrom?: {
29
+ uuid: string;
30
+ display: string;
31
+ };
32
+ startedAt: string;
33
+ sortWeight: number;
34
+ }
@@ -0,0 +1,24 @@
1
+ import { useConfig } from '@openmrs/esm-framework';
2
+ import { updateQueueEntryStatus } from './queue-room-table.resource';
3
+
4
+ export const useQueueActions = () => {
5
+ const config = useConfig();
6
+
7
+ const servePatient = async (queueEntryUuid: string) => {
8
+ return updateQueueEntryStatus(
9
+ queueEntryUuid,
10
+ config.concepts.inServiceStatusUuid,
11
+ config.concepts.completedStatusUuid,
12
+ );
13
+ };
14
+
15
+ const completeService = async (queueEntryUuid: string) => {
16
+ return updateQueueEntryStatus(
17
+ queueEntryUuid,
18
+ config.concepts.completedStatusUuid,
19
+ config.concepts.completedStatusUuid,
20
+ );
21
+ };
22
+
23
+ return { servePatient, completeService };
24
+ };
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "https://json.openmrs.org/routes.schema.json",
3
+ "backendDependencies": {
4
+ "fhir2": ">=1.2",
5
+ "webservices.rest": "^2.24.0"
6
+ },
7
+ "extensions": [
8
+ {
9
+ "name": "outpatient-dashboard-header",
10
+ "slot": "outpatient-sidebar-slot",
11
+ "component": "outpatientDashboardHeader",
12
+ "order": 0,
13
+ "online": true,
14
+ "offline": true
15
+ },
16
+ {
17
+ "name": "outpatient-db-link",
18
+ "slot": "outpatient-dashboard-slot",
19
+ "component": "leftPanelLink",
20
+ "meta": {
21
+ "name": "outpatient",
22
+ "slot": "outpatient-homepage-dashboard-slot",
23
+ "title": "Outpatient queue"
24
+ },
25
+ "order": 1,
26
+ "online": true,
27
+ "offline": true
28
+ },
29
+ {
30
+ "name": "outpatient-queue-dashboard",
31
+ "slot": "outpatient-homepage-dashboard-slot",
32
+ "component": "outpatientQueueDashboard"
33
+ },
34
+ {
35
+ "name": "care-panel-link",
36
+ "slot": "outpatient-dashboard-slot",
37
+ "component": "carePanelLink",
38
+ "meta": {
39
+ "name": "outpatient/patient/:patientUuid/file",
40
+ "slot": "outpatient-file-dashboard-slot",
41
+ "title": "Care panel",
42
+ "showOnlyOnRoute": "/file"
43
+ },
44
+ "order": 2,
45
+ "online": true,
46
+ "offline": true
47
+ },
48
+ {
49
+ "name": "care-panel-history",
50
+ "slot": "outpatient-file-dashboard-slot",
51
+ "component": "carePanel"
52
+ }
53
+ ],
54
+ "workspaces": []
55
+ }
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/extend-expect';