@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,324 @@
1
+ /**
2
+ * Tests for the EMT / Referral queue list: loading/empty/error states, row
3
+ * rendering (merged referral + CR data), search filtering, pagination wiring,
4
+ * and that the Handover action launches the handover workspace for the right
5
+ * referral.
6
+ *
7
+ * The handover flow itself (doctor resolution, initiate/verify, OTP, the
8
+ * "already handled elsewhere" race) is covered in
9
+ * `handover-modal/handover-modal.component.test.tsx` — here `launchWorkspace`
10
+ * is mocked so this file stays focused on the queue/list behavior and how it
11
+ * wires the workspace launch.
12
+ */
13
+ import React from 'react';
14
+ import { render, screen, waitFor, within } from '@testing-library/react';
15
+ import userEvent from '@testing-library/user-event';
16
+
17
+ const mockNavigate = jest.fn();
18
+ const mockShowSnackbar = jest.fn();
19
+ const mockLaunchWorkspace = jest.fn();
20
+ const mockUseSession = jest.fn(() => ({ sessionLocation: { uuid: 'location-uuid-1' } }));
21
+ jest.mock('@openmrs/esm-framework', () => ({
22
+ navigate: (...args: unknown[]) => mockNavigate(...args),
23
+ showSnackbar: (...args: unknown[]) => mockShowSnackbar(...args),
24
+ launchWorkspace: (...args: unknown[]) => mockLaunchWorkspace(...args),
25
+ useSession: () => mockUseSession(),
26
+ }));
27
+
28
+ jest.mock('../shared/ui/facility-worker-slot/facility-worker.component-slot.component', () => ({
29
+ __esModule: true,
30
+ default: () => null,
31
+ }));
32
+
33
+ const mockUsePendingReferrals = jest.fn();
34
+ const mockMutate = jest.fn();
35
+ jest.mock('./emt.resource', () => ({
36
+ usePendingReferrals: (...args: unknown[]) => mockUsePendingReferrals(...args),
37
+ EMT_PENDING_KEY: 'emt-pending-referrals',
38
+ }));
39
+
40
+ const mockFetchClientByCrId = jest.fn();
41
+ jest.mock('./cr-lookup.resource', () => ({
42
+ fetchClientByCrId: (...args: unknown[]) => mockFetchClientByCrId(...args),
43
+ clientDisplayName: (client: any, crId: string) =>
44
+ client ? [client.first_name, client.last_name].filter(Boolean).join(' ') : crId,
45
+ }));
46
+
47
+ jest.mock('./handover-modal/handover-modal.component', () => ({
48
+ __esModule: true,
49
+ EMT_HANDOVER_WORKSPACE: 'emt-handover-workspace',
50
+ }));
51
+
52
+ const mockSearchPatientByCrNumber = jest.fn();
53
+ jest.mock('../resources/patient-search.resource', () => ({
54
+ searchPatientByCrNumber: (...args: unknown[]) => mockSearchPatientByCrNumber(...args),
55
+ }));
56
+
57
+ import { IdentifierTypesUuids } from '../resources/identifier-types';
58
+ import { EmtApiError } from './types/emt.types';
59
+ import EmtQueue from './emt-queue.component';
60
+
61
+ const referralFixture = {
62
+ submission_id: 3,
63
+ cr_id: 'CR5617849204955-8',
64
+ status: 'pending_acceptance',
65
+ incident_id: 'INC-20260803100645-254727092999-ffjotq',
66
+ dispatch_id: 'd22419d8-6d36-4b2f-a33c-3e008bd85f77',
67
+ case_number: 'AMB-d22419d8-FAC',
68
+ ambulance_fr_code: 'FID-AMB-916293-3',
69
+ ambulance_registration_number: 'KDN 085T',
70
+ facility_fr_code: 'FID-47-108521-3',
71
+ evacuation_scene: '',
72
+ priority: 'p1 life threatening (als) with altered consciousness',
73
+ referral_reason: '',
74
+ referral_category: '',
75
+ transport_modality: '',
76
+ referral_notes: 'Chief complaint: Test.',
77
+ bundle_id: 'd22419d8-6d36-4b2f-a33c-3e008bd85f77',
78
+ interventions: ['SHA-01-001'],
79
+ requested_at: '2026-08-04T09:37:39.438967Z',
80
+ updated_at: '2026-08-04T09:37:40.428903Z',
81
+ };
82
+
83
+ const olderReferralFixture = {
84
+ ...referralFixture,
85
+ submission_id: 1,
86
+ cr_id: 'CR0000000000000-1',
87
+ case_number: 'AMB-older-FAC',
88
+ requested_at: '2026-08-01T09:37:39.438967Z',
89
+ };
90
+
91
+ function mockPendingReferrals(overrides: Partial<ReturnType<typeof mockUsePendingReferrals>> = {}) {
92
+ mockUsePendingReferrals.mockReturnValue({
93
+ referrals: [],
94
+ count: 0,
95
+ limit: 10,
96
+ offset: 0,
97
+ isLoading: false,
98
+ isValidating: false,
99
+ error: undefined,
100
+ mutate: mockMutate,
101
+ ...overrides,
102
+ });
103
+ }
104
+
105
+ beforeEach(() => {
106
+ jest.clearAllMocks();
107
+ mockUseSession.mockReturnValue({ sessionLocation: { uuid: 'location-uuid-1' } });
108
+ mockFetchClientByCrId.mockResolvedValue(null);
109
+ mockSearchPatientByCrNumber.mockResolvedValue({ results: [], totalCount: 0 });
110
+ mockPendingReferrals();
111
+ });
112
+
113
+ describe('EmtQueue list rendering', () => {
114
+ it('shows a loading skeleton while the first page is loading', () => {
115
+ mockPendingReferrals({ isLoading: true });
116
+ render(<EmtQueue />);
117
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
118
+ });
119
+
120
+ it('shows an empty state when there are no pending referrals', async () => {
121
+ render(<EmtQueue />);
122
+ expect(await screen.findByText(/no pending emt referrals/i)).toBeInTheDocument();
123
+ });
124
+
125
+ it('renders merged referral + CR data once the CR lookup resolves', async () => {
126
+ mockFetchClientByCrId.mockResolvedValueOnce({
127
+ id: 'CR5617849204955-8',
128
+ first_name: 'Jane',
129
+ middle_name: '',
130
+ last_name: 'Doe',
131
+ });
132
+ mockPendingReferrals({ referrals: [referralFixture], count: 1 });
133
+
134
+ render(<EmtQueue />);
135
+
136
+ expect(await screen.findByText('Jane Doe')).toBeInTheDocument();
137
+ expect(screen.getByText('CR5617849204955-8')).toBeInTheDocument();
138
+ expect(screen.getByText('AMB-d22419d8-FAC')).toBeInTheDocument();
139
+ expect(screen.getByText('SHA-01-001')).toBeInTheDocument();
140
+ expect(screen.getByText('pending_acceptance')).toBeInTheDocument();
141
+ });
142
+
143
+ it('degrades a single row gracefully when its CR lookup fails, without failing the queue', async () => {
144
+ mockFetchClientByCrId.mockResolvedValueOnce(null);
145
+ mockPendingReferrals({ referrals: [referralFixture], count: 1 });
146
+
147
+ render(<EmtQueue />);
148
+
149
+ expect(await screen.findByText(/patient details unavailable/i)).toBeInTheDocument();
150
+ // The row still renders its other referral fields even though CR failed.
151
+ expect(screen.getByText('AMB-d22419d8-FAC')).toBeInTheDocument();
152
+ });
153
+
154
+ it('sorts referrals by requested_at descending', async () => {
155
+ mockPendingReferrals({ referrals: [olderReferralFixture, referralFixture], count: 2 });
156
+
157
+ render(<EmtQueue />);
158
+
159
+ const caseCells = await screen.findAllByText(/^AMB-/);
160
+ expect(caseCells[0]).toHaveTextContent('AMB-d22419d8-FAC');
161
+ expect(caseCells[1]).toHaveTextContent('AMB-older-FAC');
162
+ });
163
+
164
+ it('filters rows by the search box', async () => {
165
+ mockPendingReferrals({ referrals: [olderReferralFixture, referralFixture], count: 2 });
166
+ render(<EmtQueue />);
167
+
168
+ await screen.findByText('AMB-d22419d8-FAC');
169
+ const user = userEvent.setup();
170
+ await user.type(screen.getByPlaceholderText(/search this list/i), 'older');
171
+
172
+ expect(screen.queryByText('AMB-d22419d8-FAC')).not.toBeInTheDocument();
173
+ expect(screen.getByText('AMB-older-FAC')).toBeInTheDocument();
174
+ });
175
+
176
+ it('shows a distinct, retryable error banner for an auth failure and lets the user retry', async () => {
177
+ mockPendingReferrals({ error: new EmtApiError(401, 'Request failed with 401') });
178
+ render(<EmtQueue />);
179
+
180
+ expect(await screen.findByText(/session may have expired/i)).toBeInTheDocument();
181
+ const user = userEvent.setup();
182
+ await user.click(screen.getByRole('button', { name: /retry/i }));
183
+ expect(mockMutate).toHaveBeenCalled();
184
+ });
185
+
186
+ it('shows a distinct error banner for a 5xx failure', async () => {
187
+ mockPendingReferrals({ error: new EmtApiError(503, 'Request failed with 503') });
188
+ render(<EmtQueue />);
189
+ expect(await screen.findByText(/temporarily unavailable/i)).toBeInTheDocument();
190
+ });
191
+
192
+ it('passes the server-reported count and current page to Pagination', async () => {
193
+ mockPendingReferrals({ referrals: [referralFixture], count: 47 });
194
+ render(<EmtQueue />);
195
+
196
+ await screen.findByText('AMB-d22419d8-FAC');
197
+ // Carbon's Pagination renders the total item count in its "of N items" text.
198
+ expect(screen.getByText(/47/)).toBeInTheDocument();
199
+ });
200
+
201
+ it('refetches with the next offset when paging forward', async () => {
202
+ mockPendingReferrals({ referrals: [referralFixture], count: 47 });
203
+ render(<EmtQueue />);
204
+ await screen.findByText('AMB-d22419d8-FAC');
205
+
206
+ expect(mockUsePendingReferrals).toHaveBeenLastCalledWith(10, 0, 'location-uuid-1');
207
+
208
+ const user = userEvent.setup();
209
+ await user.click(screen.getByRole('button', { name: /next page/i }));
210
+
211
+ await waitFor(() => expect(mockUsePendingReferrals).toHaveBeenLastCalledWith(10, 10, 'location-uuid-1'));
212
+ });
213
+ });
214
+
215
+ describe('EmtQueue handover wiring', () => {
216
+ it('launches the handover workspace for the clicked row (not some other row)', async () => {
217
+ mockPendingReferrals({ referrals: [olderReferralFixture, referralFixture], count: 2 });
218
+ render(<EmtQueue />);
219
+ await screen.findByText('AMB-d22419d8-FAC');
220
+
221
+ const user = userEvent.setup();
222
+ const rows = screen.getAllByRole('row').filter((r) => within(r).queryByRole('button', { name: /handover/i }));
223
+ const targetRow = rows.find((r) => within(r).queryByText('AMB-older-FAC'));
224
+ await user.click(within(targetRow!).getByRole('button', { name: /handover/i }));
225
+
226
+ expect(mockLaunchWorkspace).toHaveBeenCalledWith(
227
+ 'emt-handover-workspace',
228
+ expect.objectContaining({ referral: expect.objectContaining({ case_number: 'AMB-older-FAC' }) }),
229
+ );
230
+ });
231
+
232
+ it('refuses to open the handover workspace when there is no default location, instead of sending an empty locationUuid', async () => {
233
+ mockUseSession.mockReturnValue({ sessionLocation: undefined });
234
+ mockPendingReferrals({ referrals: [referralFixture], count: 1 });
235
+ render(<EmtQueue />);
236
+ await screen.findByText('AMB-d22419d8-FAC');
237
+
238
+ const user = userEvent.setup();
239
+ await user.click(screen.getByRole('button', { name: /handover/i }));
240
+
241
+ expect(mockLaunchWorkspace).not.toHaveBeenCalled();
242
+ expect(mockShowSnackbar).toHaveBeenCalledWith(
243
+ expect.objectContaining({ kind: 'error', title: expect.stringMatching(/no default location/i) }),
244
+ );
245
+ });
246
+
247
+ it('on handover completion for a not-yet-registered patient: refreshes the queue, shows a success toast, and sends staff to the registry screen', async () => {
248
+ mockPendingReferrals({ referrals: [referralFixture], count: 1 });
249
+ mockSearchPatientByCrNumber.mockResolvedValueOnce({ results: [], totalCount: 0 });
250
+ render(<EmtQueue />);
251
+ await screen.findByText('AMB-d22419d8-FAC');
252
+
253
+ const user = userEvent.setup();
254
+ await user.click(screen.getByRole('button', { name: /handover/i }));
255
+ const { onHandoverComplete } = mockLaunchWorkspace.mock.calls[0][1];
256
+ onHandoverComplete(referralFixture);
257
+
258
+ expect(mockMutate).toHaveBeenCalled();
259
+ expect(mockShowSnackbar).toHaveBeenCalledWith(
260
+ expect.objectContaining({ kind: 'success', subtitle: expect.stringContaining('AMB-d22419d8-FAC') }),
261
+ );
262
+ await waitFor(() =>
263
+ expect(mockSearchPatientByCrNumber).toHaveBeenCalledWith('CR5617849204955-8'),
264
+ );
265
+ await waitFor(() =>
266
+ expect(mockNavigate).toHaveBeenCalledWith(
267
+ expect.objectContaining({
268
+ to: expect.stringMatching(/\/home\/registry\?emtCrId=/),
269
+ }),
270
+ ),
271
+ );
272
+ expect(mockNavigate).toHaveBeenCalledWith(
273
+ expect.objectContaining({ to: expect.stringContaining(encodeURIComponent('CR5617849204955-8')) }),
274
+ );
275
+ });
276
+
277
+ it('on handover completion for an already-registered patient: skips the registry screen and goes straight to their chart', async () => {
278
+ mockPendingReferrals({ referrals: [referralFixture], count: 1 });
279
+ mockSearchPatientByCrNumber.mockResolvedValueOnce({
280
+ totalCount: 1,
281
+ results: [
282
+ {
283
+ uuid: 'amrs-patient-uuid-1',
284
+ identifiers: [
285
+ { identifier: 'CR5617849204955-8', identifierType: { uuid: IdentifierTypesUuids.CLIENT_REGISTRY_NO_UUID } },
286
+ ],
287
+ },
288
+ ],
289
+ });
290
+ render(<EmtQueue />);
291
+ await screen.findByText('AMB-d22419d8-FAC');
292
+
293
+ const user = userEvent.setup();
294
+ await user.click(screen.getByRole('button', { name: /handover/i }));
295
+ const { onHandoverComplete } = mockLaunchWorkspace.mock.calls[0][1];
296
+ onHandoverComplete(referralFixture);
297
+
298
+ await waitFor(() =>
299
+ expect(mockNavigate).toHaveBeenCalledWith(
300
+ expect.objectContaining({ to: expect.stringContaining('/patient/amrs-patient-uuid-1/chart') }),
301
+ ),
302
+ );
303
+ expect(mockNavigate).not.toHaveBeenCalledWith(
304
+ expect.objectContaining({ to: expect.stringContaining('/home/registry') }),
305
+ );
306
+ });
307
+
308
+ it('on "already handled elsewhere": drops the row, refreshes the queue, and warns instead of celebrating', async () => {
309
+ mockPendingReferrals({ referrals: [referralFixture], count: 1 });
310
+ render(<EmtQueue />);
311
+ await screen.findByText('AMB-d22419d8-FAC');
312
+
313
+ const user = userEvent.setup();
314
+ await user.click(screen.getByRole('button', { name: /handover/i }));
315
+ const { onReferralUnavailable } = mockLaunchWorkspace.mock.calls[0][1];
316
+ onReferralUnavailable(referralFixture, 'Already handled elsewhere');
317
+
318
+ expect(mockMutate).toHaveBeenCalled();
319
+ expect(mockShowSnackbar).toHaveBeenCalledWith(
320
+ expect.objectContaining({ kind: 'warning', subtitle: 'Already handled elsewhere' }),
321
+ );
322
+ expect(mockNavigate).not.toHaveBeenCalled();
323
+ });
324
+ });