@ampath/esm-dha-workflow-app 4.0.0-next.340 → 4.0.0-next.344

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 (128) hide show
  1. package/dist/1149.js +1 -1
  2. package/dist/1171.js +1 -0
  3. package/dist/1171.js.map +1 -0
  4. package/dist/1177.js +1 -0
  5. package/dist/1177.js.map +1 -0
  6. package/dist/1239.js +1 -0
  7. package/dist/1239.js.map +1 -0
  8. package/dist/1551.js +1 -1
  9. package/dist/1560.js +1 -0
  10. package/dist/1560.js.map +1 -0
  11. package/dist/1928.js +1 -0
  12. package/dist/1928.js.map +1 -0
  13. package/dist/2093.js +1 -1
  14. package/dist/2230.js +1 -1
  15. package/dist/2493.js +1 -1
  16. package/dist/2914.js +1 -1
  17. package/dist/3078.js +1 -0
  18. package/dist/3078.js.map +1 -0
  19. package/dist/327.js +1 -0
  20. package/dist/327.js.map +1 -0
  21. package/dist/4605.js +1 -1
  22. package/dist/4611.js +1 -1
  23. package/dist/4674.js +1 -1
  24. package/dist/4674.js.map +1 -1
  25. package/dist/4986.js +1 -1
  26. package/dist/5167.js +1 -1
  27. package/dist/5317.js +1 -0
  28. package/dist/5317.js.map +1 -0
  29. package/dist/5344.js +1 -0
  30. package/dist/{2936.js.map → 5344.js.map} +1 -1
  31. package/dist/5765.js +1 -0
  32. package/dist/5765.js.map +1 -0
  33. package/dist/5851.js +1 -1
  34. package/dist/5851.js.map +1 -1
  35. package/dist/5961.js +1 -1
  36. package/dist/6264.js +1 -1
  37. package/dist/6264.js.map +1 -1
  38. package/dist/6720.js +1 -1
  39. package/dist/6942.js +1 -1
  40. package/dist/6942.js.map +1 -1
  41. package/dist/7521.js +1 -1
  42. package/dist/7521.js.map +1 -1
  43. package/dist/7549.js +1 -0
  44. package/dist/7549.js.map +1 -0
  45. package/dist/7717.js +1 -1
  46. package/dist/7857.js +1 -0
  47. package/dist/7857.js.map +1 -0
  48. package/dist/850.js +1 -0
  49. package/dist/850.js.map +1 -0
  50. package/dist/8733.js +1 -1
  51. package/dist/9070.js +1 -1
  52. package/dist/9093.js +1 -1
  53. package/dist/9093.js.map +1 -1
  54. package/dist/9338.js +1 -1
  55. package/dist/9338.js.map +1 -1
  56. package/dist/9349.js +1 -0
  57. package/dist/9349.js.map +1 -0
  58. package/dist/9428.js +1 -1
  59. package/dist/9428.js.map +1 -1
  60. package/dist/9789.js +1 -0
  61. package/dist/9789.js.map +1 -0
  62. package/dist/9871.js +1 -0
  63. package/dist/9871.js.map +1 -0
  64. package/dist/esm-dha-workflow-app.js +1 -1
  65. package/dist/esm-dha-workflow-app.js.buildmanifest.json +362 -239
  66. package/dist/esm-dha-workflow-app.js.map +1 -1
  67. package/dist/main.js +1 -1
  68. package/dist/main.js.map +1 -1
  69. package/dist/routes.json +1 -1
  70. package/package.json +1 -1
  71. package/src/billing/billing-claims.resource.ts +8 -7
  72. package/src/billing/dashboard/v2/clearance/cash.component.tsx +9 -9
  73. package/src/billing/dashboard/v2/clearance/pay-cash.component.tsx +24 -22
  74. package/src/billing/dashboard/v2/facility-bills/facility-bills.component.tsx +5 -4
  75. package/src/billing/dashboard/v2/patient-bill-details/attachments/final-bill.component.tsx +2 -2
  76. package/src/billing/dashboard/v2/patient-bill-details/attachments/generate-attachments.component.tsx +15 -4
  77. package/src/billing/dashboard/v2/patient-bill-details/attachments/invoice.component.tsx +2 -2
  78. package/src/billing/dashboard/v2/types/index.ts +6 -0
  79. package/src/billing/dashboard/v3/claim-visits/claim-intervention-details/claim-intervention-details.component.tsx +6 -1
  80. package/src/billing/dashboard/v3/claim-visits/claim-visit-details/claim-visit-details.component.tsx +5 -8
  81. package/src/billing/dashboard/v3/facility-bills/facility-bills.component.tsx +65 -62
  82. package/src/billing/dashboard/v3/patient-bill-details/attachments/case-summary/case-summary.tsx +30 -16
  83. package/src/billing/dashboard/v3/patient-bill-details/attachments/generate-attachments.component.tsx +3 -1
  84. package/src/billing/dashboard/v3/patient-bill-details/bill-details/bill-details.scss +4 -0
  85. package/src/billing/dashboard/v3/patient-bill-details/bill-details/bill-details.tsx +154 -156
  86. package/src/billing/dashboard/v3/patient-bill-details/claim-details/patient-claim-details.component.tsx +22 -23
  87. package/src/billing/dashboard/v3/patient-bill-details/patient-bill-details.tsx +5 -4
  88. package/src/dashboard/dashboard.component.tsx +3 -2
  89. package/src/dashboard/overview/overview.component.tsx +3 -1
  90. package/src/dashboard-meta/emt-dashboard.meta.ts +6 -0
  91. package/src/emt/cr-lookup.resource.test.ts +88 -0
  92. package/src/emt/cr-lookup.resource.ts +42 -0
  93. package/src/emt/emt-queue.component.test.tsx +324 -0
  94. package/src/emt/emt-queue.component.tsx +449 -0
  95. package/src/emt/emt-queue.scss +83 -0
  96. package/src/emt/emt.resource.test.ts +247 -0
  97. package/src/emt/emt.resource.ts +150 -0
  98. package/src/emt/handover-modal/handover-modal.component.test.tsx +273 -0
  99. package/src/emt/handover-modal/handover-modal.component.tsx +524 -0
  100. package/src/emt/handover-modal/handover-modal.scss +88 -0
  101. package/src/emt/types/emt.types.ts +137 -0
  102. package/src/index.ts +10 -0
  103. package/src/registry/registry.component.tsx +49 -1
  104. package/src/registry/utils/hie-client-adapter.ts +1 -1
  105. package/src/root.component.tsx +2 -0
  106. package/src/routes.json +25 -0
  107. package/src/side-nav-menu/nav-link-config.ts +4 -0
  108. package/dist/1362.js +0 -1
  109. package/dist/1362.js.map +0 -1
  110. package/dist/203.js +0 -1
  111. package/dist/203.js.map +0 -1
  112. package/dist/2200.js +0 -1
  113. package/dist/2200.js.map +0 -1
  114. package/dist/2936.js +0 -1
  115. package/dist/2988.js +0 -1
  116. package/dist/2988.js.map +0 -1
  117. package/dist/307.js +0 -1
  118. package/dist/307.js.map +0 -1
  119. package/dist/4008.js +0 -1
  120. package/dist/4008.js.map +0 -1
  121. package/dist/446.js +0 -1
  122. package/dist/446.js.map +0 -1
  123. package/dist/5097.js +0 -1
  124. package/dist/5097.js.map +0 -1
  125. package/dist/7240.js +0 -1
  126. package/dist/7240.js.map +0 -1
  127. package/dist/874.js +0 -1
  128. package/dist/874.js.map +0 -1
@@ -0,0 +1,449 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ Button,
4
+ DataTable,
5
+ type DataTableRow,
6
+ DataTableSkeleton,
7
+ InlineLoading,
8
+ Pagination,
9
+ Table,
10
+ TableBody,
11
+ TableCell,
12
+ TableHead,
13
+ TableHeader,
14
+ TableRow,
15
+ Tag,
16
+ } from '@carbon/react';
17
+ import { launchWorkspace, navigate, showSnackbar, useSession } from '@openmrs/esm-framework';
18
+ import { useTranslation } from 'react-i18next';
19
+ import dayjs from 'dayjs';
20
+
21
+ import styles from './emt-queue.scss';
22
+ import EmptyState from '../billing/dashboard/v3/shared/empty-state.component';
23
+ import TableToolbar from '../billing/dashboard/v3/shared/table-toolbar.component';
24
+ import FacilityAndWorkerSlot from '../shared/ui/facility-worker-slot/facility-worker.component-slot.component';
25
+ import { EMT_HANDOVER_WORKSPACE } from './handover-modal/handover-modal.component';
26
+ import {
27
+ usePendingReferrals,
28
+ EMT_PENDING_KEY,
29
+ } from './emt.resource';
30
+ import { fetchClientByCrId, clientDisplayName } from './cr-lookup.resource';
31
+ import { EmtApiError, type EmtReferralRow } from './types/emt.types';
32
+ import { searchPatientByCrNumber } from '../resources/patient-search.resource';
33
+ import { IdentifierTypesUuids } from '../resources/identifier-types';
34
+
35
+ const EmtQueue: React.FC = () => {
36
+ const { t } = useTranslation();
37
+ const session = useSession();
38
+ const locationUuid = session?.sessionLocation?.uuid ?? '';
39
+ const [searchString, setSearchString] = useState('');
40
+ const [pageSize, setPageSize] = useState(10);
41
+ const [pageIndex, setPageIndex] = useState(0); // 0-based page for server offset
42
+ const [rows, setRows] = useState<EmtReferralRow[]>([]);
43
+ const [globalError, setGlobalError] = useState<string>('');
44
+
45
+ const limit = pageSize;
46
+ const offset = pageIndex * pageSize;
47
+
48
+ const {
49
+ referrals,
50
+ count,
51
+ isLoading,
52
+ error,
53
+ mutate,
54
+ } = usePendingReferrals(limit, offset, locationUuid);
55
+
56
+ // Surface upstream errors visibly (network / auth / 5xx) with a retry path.
57
+ useEffect(() => {
58
+ if (!error) {
59
+ setGlobalError('');
60
+ return;
61
+ }
62
+ if (error instanceof EmtApiError) {
63
+ const { status } = error;
64
+ if (status === 401 || status === 403) {
65
+ setGlobalError('Your session may have expired. Please re-authenticate and retry.');
66
+ } else if (status >= 500 || status === 0) {
67
+ setGlobalError('The EMT service is temporarily unavailable. Please retry.');
68
+ } else if (status === 404) {
69
+ setGlobalError('No pending referrals found for this facility.');
70
+ } else {
71
+ setGlobalError(error.message || 'Failed to load referrals.');
72
+ }
73
+ } else {
74
+ setGlobalError('Unable to reach the EMT service. Please check your connection and retry.');
75
+ }
76
+ }, [error]);
77
+
78
+ // Map the fetched page into display rows.
79
+ useEffect(() => {
80
+ const mapped: EmtReferralRow[] = (referrals ?? []).map((r) => ({
81
+ ...r,
82
+ patientName: r.cr_id, // placeholder until CR resolves
83
+ crLoading: true,
84
+ }));
85
+ setRows(mapped);
86
+
87
+ // Lazy CR enrichment: fetch each row's CR record in parallel. A failed
88
+ // lookup degrades the row (keeps cr_id, marks "details unavailable")
89
+ // rather than failing the whole queue.
90
+ let cancelled = false;
91
+ mapped.forEach(async (row, idx) => {
92
+ try {
93
+ const client = await fetchClientByCrId(row.cr_id, locationUuid);
94
+ if (cancelled) return;
95
+ setRows((prev) => {
96
+ const next = [...prev];
97
+ if (!next[idx]) return prev;
98
+ next[idx] = {
99
+ ...next[idx],
100
+ client: client ?? undefined,
101
+ patientName: clientDisplayName(client, row.cr_id),
102
+ crLoading: false,
103
+ crError: client ? undefined : 'Patient details unavailable',
104
+ };
105
+ return next;
106
+ });
107
+ } catch {
108
+ if (cancelled) return;
109
+ setRows((prev) => {
110
+ const next = [...prev];
111
+ if (!next[idx]) return prev;
112
+ next[idx] = {
113
+ ...next[idx],
114
+ crLoading: false,
115
+ crError: 'Patient details unavailable',
116
+ };
117
+ return next;
118
+ });
119
+ }
120
+ });
121
+
122
+ return () => {
123
+ cancelled = true;
124
+ };
125
+ }, [referrals, locationUuid]);
126
+
127
+ const columns = [
128
+ { id: 'patientName', header: 'Patient', key: 'patientName' },
129
+ { id: 'crId', header: 'CR ID', key: 'crId' },
130
+ { id: 'caseNumber', header: 'Case number', key: 'caseNumber' },
131
+ { id: 'ambulance', header: 'Ambulance', key: 'ambulance' },
132
+ { id: 'requestedAt', header: 'Requested', key: 'requestedAt' },
133
+ { id: 'interventions', header: 'Interventions', key: 'interventions' },
134
+ { id: 'notes', header: 'Referral notes', key: 'notes' },
135
+ { id: 'status', header: 'Status', key: 'status' },
136
+ { id: 'action', header: 'Action', key: 'action' },
137
+ { id: 'submissionId', header: '', key: 'submissionId' }, // hidden payload carrier
138
+ ];
139
+
140
+ // Map each referral into a DataTable row whose `id` + keys match the columns.
141
+ const tableRows = useMemo(
142
+ () =>
143
+ rows.map((r) => ({
144
+ id: String(r.submission_id),
145
+ patientName: r.patientName,
146
+ crId: r.cr_id,
147
+ caseNumber: r.case_number,
148
+ ambulance: r.ambulance_fr_code,
149
+ requestedAt: r.requested_at,
150
+ interventions: (r.interventions ?? []).join(', '),
151
+ notes: r.referral_notes,
152
+ status: r.status,
153
+ action: String(r.submission_id),
154
+ submissionId: String(r.submission_id),
155
+ })),
156
+ [rows],
157
+ );
158
+
159
+ // Sort newest first, then apply the client-side search filter.
160
+ const visibleRows = useMemo(() => {
161
+ const sorted = [...tableRows].sort(
162
+ (a, b) => dayjs(b.requestedAt).valueOf() - dayjs(a.requestedAt).valueOf(),
163
+ );
164
+ if (!searchString.trim()) return sorted;
165
+ const q = searchString.toLowerCase();
166
+ return sorted.filter(
167
+ (r) =>
168
+ r.patientName?.toLowerCase().includes(q) ||
169
+ r.crId?.toLowerCase().includes(q) ||
170
+ r.caseNumber?.toLowerCase().includes(q) ||
171
+ r.ambulance?.toLowerCase().includes(q),
172
+ );
173
+ }, [tableRows, searchString]);
174
+
175
+ // Lookup of raw referral rows by submission id, so cell renderers can read
176
+ // enriched fields (interventions, notes, crLoading, crError) directly.
177
+ const rowsById = useMemo(
178
+ () => new Map(rows.map((r) => [String(r.submission_id), r])),
179
+ [rows],
180
+ );
181
+
182
+ const showSkeleton = isLoading && rows.length === 0;
183
+
184
+ const relativeTime = (iso: string) => {
185
+ const mins = dayjs().diff(dayjs(iso), 'minute');
186
+ if (mins < 1) return 'just now';
187
+ if (mins < 60) return `${mins}m ago`;
188
+ const hrs = Math.floor(mins / 60);
189
+ const rem = mins % 60;
190
+ return rem ? `${hrs}h ${rem}m ago` : `${hrs}h ago`;
191
+ };
192
+
193
+ const handleRefresh = async () => {
194
+ try {
195
+ await mutate();
196
+ } catch {
197
+ showSnackbar({ kind: 'error', title: 'Refresh failed', subtitle: '' });
198
+ }
199
+ };
200
+
201
+ const handleHandoverComplete = async (referral: EmtReferralRow) => {
202
+ // Refresh from the source of truth — don't just splice locally.
203
+ mutate();
204
+ showSnackbar({
205
+ kind: 'success',
206
+ title: 'Handover complete',
207
+ subtitle: `Starting visit for ${referral.patientName} (${referral.case_number}).`,
208
+ });
209
+ // EmtQueue is mounted as a bare extension (no React Router ancestor), so
210
+ // cross-app navigation has to go through the shell's navigate() rather
211
+ // than react-router's useNavigate — and there's no router `state` channel
212
+ // either.
213
+ //
214
+ // If the patient already exists in AMRS (registered on an earlier visit),
215
+ // skip straight to their chart. Otherwise fall back to the registry
216
+ // screen so staff complete registration manually, same as today — the CR
217
+ // id travels via a query param and the Registration screen re-resolves
218
+ // the CR record itself.
219
+ let amrsPatientUuid: string | undefined;
220
+ try {
221
+ const resp = await searchPatientByCrNumber(referral.cr_id);
222
+ amrsPatientUuid = (resp.results ?? []).find((p) =>
223
+ p.identifiers.some(
224
+ (id) =>
225
+ id.identifier === referral.cr_id &&
226
+ id.identifierType.uuid === IdentifierTypesUuids.CLIENT_REGISTRY_NO_UUID,
227
+ ),
228
+ )?.uuid;
229
+ } catch {
230
+ // Registration-status check failed — fall back to the manual registry path below.
231
+ }
232
+
233
+ navigate({
234
+ to: amrsPatientUuid
235
+ ? `\${openmrsSpaBase}/patient/${amrsPatientUuid}/chart`
236
+ : `\${openmrsSpaBase}/home/registry?emtCrId=${encodeURIComponent(referral.cr_id)}`,
237
+ });
238
+ };
239
+
240
+ // The backend returned 404 during initiate/verify — another facility/user
241
+ // already handled this referral, or it no longer exists.
242
+ const handleReferralUnavailable = (referral: EmtReferralRow, reason: string) => {
243
+ mutate();
244
+ showSnackbar({
245
+ kind: 'warning',
246
+ title: 'Referral no longer available',
247
+ subtitle:
248
+ reason ||
249
+ `${referral.patientName || referral.cr_id} (${referral.case_number}) could not be found — it may have already been handled.`,
250
+ });
251
+ };
252
+
253
+ const openHandover = (row: DataTableRow<any[]>) => {
254
+ const target = rowsById.get(String(row.id));
255
+ if (!target) return;
256
+ if (!locationUuid) {
257
+ showSnackbar({ kind: 'error', title: 'No default location selected', subtitle: '' });
258
+ return;
259
+ }
260
+ launchWorkspace(EMT_HANDOVER_WORKSPACE, {
261
+ workspaceTitle: 'EMT Handover',
262
+ referral: target,
263
+ locationUuid,
264
+ onHandoverComplete: handleHandoverComplete,
265
+ onReferralUnavailable: handleReferralUnavailable,
266
+ });
267
+ };
268
+
269
+ return (
270
+ <div className={styles.container}>
271
+ <div className={styles.header}>
272
+ <FacilityAndWorkerSlot />
273
+ </div>
274
+
275
+ <div className={styles.toolbarRow}>
276
+ <div>
277
+ <h3 className={styles.title}>EMT / Referral</h3>
278
+ <p className={styles.subtitle}>
279
+ Incoming ambulance and EMT referrals awaiting handover.
280
+ </p>
281
+ </div>
282
+ <Button kind="tertiary" onClick={handleRefresh} disabled={isLoading}>
283
+ {isLoading ? <InlineLoading description="Refreshing…" /> : 'Refresh'}
284
+ </Button>
285
+ </div>
286
+
287
+ {globalError && (
288
+ <div className={styles.errorBanner} role="alert">
289
+ <span>{globalError}</span>
290
+ <Button size="sm" kind="ghost" onClick={handleRefresh}>
291
+ Retry
292
+ </Button>
293
+ </div>
294
+ )}
295
+
296
+ {showSkeleton ? (
297
+ <div className={styles.tableCard}>
298
+ <DataTableSkeleton role="progressbar" showHeader={false} showToolbar={false} />
299
+ </div>
300
+ ) : rows.length === 0 && !globalError ? (
301
+ <div className={styles.tableCard}>
302
+ <EmptyState message="No pending EMT referrals for your facility." />
303
+ </div>
304
+ ) : (
305
+ <>
306
+ <TableToolbar
307
+ id="emt-queue"
308
+ search={searchString}
309
+ onSearch={setSearchString}
310
+ searchPlaceholder={t('searchThisList', 'Search this list')}
311
+ />
312
+ {visibleRows.length === 0 ? (
313
+ <div className={styles.tableCard}>
314
+ <EmptyState message="No referrals match your search." />
315
+ </div>
316
+ ) : (
317
+ <DataTable rows={visibleRows} headers={columns}>
318
+ {({ rows: tableRows, headers, getTableProps, getHeaderProps, getRowProps, getCellProps }) => (
319
+ <div className={styles.tableCard}>
320
+ <Table size="sm" useZebraStyles aria-label="EMT referrals" {...getTableProps()}>
321
+ <TableHead>
322
+ <TableRow>
323
+ {headers
324
+ .filter((h) => h.key !== 'submissionId')
325
+ .map((header) => (
326
+ <TableHeader {...getHeaderProps({ header })}>{header.header}</TableHeader>
327
+ ))}
328
+ </TableRow>
329
+ </TableHead>
330
+ <TableBody>
331
+ {tableRows.map((row) => {
332
+ const dataRow = rowsById.get(String(row.id));
333
+ return (
334
+ <TableRow {...getRowProps({ row })}>
335
+ {row.cells.map((cell) => {
336
+ if (cell.info.header === 'submissionId') return null;
337
+
338
+ if (cell.info.header === 'patientName') {
339
+ return (
340
+ <TableCell key={cell.id}>
341
+ <div className={styles.patientCell}>
342
+ <span>{cell.value}</span>
343
+ {dataRow?.crLoading && (
344
+ <InlineLoading description="…" />
345
+ )}
346
+ {dataRow?.crError && (
347
+ <span className={styles.muted}> ({dataRow.crError})</span>
348
+ )}
349
+ </div>
350
+ </TableCell>
351
+ );
352
+ }
353
+ if (cell.info.header === 'interventions') {
354
+ const list = dataRow?.interventions ?? [];
355
+ return (
356
+ <TableCell key={cell.id}>
357
+ {list.length ? (
358
+ list.map((iv) => (
359
+ <Tag key={iv} size="sm" type="gray">
360
+ {iv}
361
+ </Tag>
362
+ ))
363
+ ) : (
364
+ '—'
365
+ )}
366
+ </TableCell>
367
+ );
368
+ }
369
+ if (cell.info.header === 'notes') {
370
+ const notes = dataRow?.referral_notes ?? '';
371
+ return (
372
+ <TableCell key={cell.id} className={styles.notesCell} title={notes}>
373
+ {notes ? `${notes.slice(0, 60)}${notes.length > 60 ? '…' : ''}` : '—'}
374
+ </TableCell>
375
+ );
376
+ }
377
+ if (cell.info.header === 'requestedAt') {
378
+ return (
379
+ <TableCell key={cell.id}>
380
+ <div className={styles.timeCell}>
381
+ <span>{relativeTime(dataRow?.requested_at)}</span>
382
+ <span className={styles.muted}>
383
+ {dataRow?.requested_at
384
+ ? dayjs(dataRow.requested_at).format('DD MMM, HH:mm')
385
+ : ''}
386
+ </span>
387
+ </div>
388
+ </TableCell>
389
+ );
390
+ }
391
+ if (cell.info.header === 'status') {
392
+ return (
393
+ <TableCell key={cell.id}>
394
+ <Tag size="sm" type="warm-gray">
395
+ {dataRow?.status ?? 'pending'}
396
+ </Tag>
397
+ </TableCell>
398
+ );
399
+ }
400
+ if (cell.info.header === 'action') {
401
+ return (
402
+ <TableCell key={cell.id} className={styles.actionCell}>
403
+ <Button kind="tertiary" size="sm" onClick={() => openHandover(row)}>
404
+ Handover
405
+ </Button>
406
+ </TableCell>
407
+ );
408
+ }
409
+ return (
410
+ <TableCell key={cell.id} {...getCellProps({ cell })}>
411
+ {cell.value}
412
+ </TableCell>
413
+ );
414
+ })}
415
+ </TableRow>
416
+ );
417
+ })}
418
+ </TableBody>
419
+ </Table>
420
+ </div>
421
+ )}
422
+ </DataTable>
423
+ )}
424
+ {visibleRows.length > 0 && (
425
+ <Pagination
426
+ forwardText={t('nextPage', 'Next page')}
427
+ backwardText={t('previousPage', 'Previous page')}
428
+ page={pageIndex + 1}
429
+ pageSize={pageSize}
430
+ pageSizes={[10, 20, 30, 40, 50]}
431
+ totalItems={count}
432
+ onChange={({ pageSize: ps, page }) => {
433
+ if (ps !== pageSize) {
434
+ setPageSize(ps);
435
+ setPageIndex(0);
436
+ } else {
437
+ setPageIndex(page - 1);
438
+ }
439
+ }}
440
+ />
441
+ )}
442
+ </>
443
+ )}
444
+ </div>
445
+ );
446
+ };
447
+
448
+ export default EmtQueue;
449
+ export { EMT_PENDING_KEY };
@@ -0,0 +1,83 @@
1
+ .container {
2
+ padding: 1rem;
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 0.75rem;
6
+ }
7
+
8
+ .header {
9
+ /* Matches FacilityAndWorkerSlot layout in other queue pages. */
10
+ }
11
+
12
+ .toolbarRow {
13
+ display: flex;
14
+ align-items: flex-start;
15
+ justify-content: space-between;
16
+ gap: 1rem;
17
+ flex-wrap: wrap;
18
+ }
19
+
20
+ .title {
21
+ margin: 0;
22
+ font-size: 1rem;
23
+ font-weight: 600;
24
+ color: #161616;
25
+ }
26
+
27
+ .subtitle {
28
+ margin: 0.125rem 0 0;
29
+ font-size: 0.8125rem;
30
+ color: #6f6f6f;
31
+ }
32
+
33
+ .errorBanner {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 0.75rem;
37
+ padding: 0.75rem 1rem;
38
+ background-color: #fff1f1;
39
+ border: 1px solid #e0737a;
40
+ border-radius: 0.25rem;
41
+ color: #da1e28;
42
+ font-size: 0.8125rem;
43
+ }
44
+
45
+ .tableCard {
46
+ overflow-x: auto;
47
+ }
48
+
49
+ .actionCell {
50
+ white-space: nowrap;
51
+ }
52
+
53
+ .actionCell :global(.cds--btn) {
54
+ min-height: 2rem;
55
+ width: max-content;
56
+ white-space: nowrap;
57
+ padding-inline: 0.75rem;
58
+ }
59
+
60
+ .patientCell {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 0.375rem;
64
+ flex-wrap: wrap;
65
+ }
66
+
67
+ .notesCell {
68
+ max-width: 12rem;
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ white-space: nowrap;
72
+ }
73
+
74
+ .timeCell {
75
+ display: flex;
76
+ flex-direction: column;
77
+ line-height: 1.3;
78
+ }
79
+
80
+ .muted {
81
+ color: #a8a8a8;
82
+ font-size: 0.75rem;
83
+ }