@openmrs/esm-task-list-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 (42) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc +57 -0
  4. package/.husky/pre-commit +7 -0
  5. package/.husky/pre-push +6 -0
  6. package/.prettierignore +13 -0
  7. package/.turbo.json +18 -0
  8. package/.yarn/plugins/@yarnpkg/plugin-outdated.cjs +35 -0
  9. package/LICENSE +401 -0
  10. package/README.md +28 -0
  11. package/__mocks__/react-i18next.js +73 -0
  12. package/example.env +6 -0
  13. package/jest.config.js +34 -0
  14. package/package.json +108 -0
  15. package/prettier.config.js +8 -0
  16. package/src/config-schema.ts +13 -0
  17. package/src/declarations.d.ts +5 -0
  18. package/src/index.ts +24 -0
  19. package/src/launch-button/task-list-launch-button.extension.tsx +20 -0
  20. package/src/loader/loader.component.tsx +12 -0
  21. package/src/loader/loader.scss +9 -0
  22. package/src/routes.json +28 -0
  23. package/src/types.d.ts +9 -0
  24. package/src/workspace/add-task-form.component.tsx +551 -0
  25. package/src/workspace/add-task-form.scss +58 -0
  26. package/src/workspace/add-task-form.test.tsx +458 -0
  27. package/src/workspace/delete-task.modal.tsx +71 -0
  28. package/src/workspace/delete-task.scss +7 -0
  29. package/src/workspace/task-details-view.component.tsx +212 -0
  30. package/src/workspace/task-details-view.scss +67 -0
  31. package/src/workspace/task-details-view.test.tsx +411 -0
  32. package/src/workspace/task-list-view.component.tsx +154 -0
  33. package/src/workspace/task-list-view.scss +150 -0
  34. package/src/workspace/task-list.resource.ts +570 -0
  35. package/src/workspace/task-list.scss +37 -0
  36. package/src/workspace/task-list.workspace.tsx +88 -0
  37. package/tools/i18next-parser.config.js +89 -0
  38. package/tools/setup-tests.ts +8 -0
  39. package/tools/update-openmrs-deps.mjs +43 -0
  40. package/translations/en.json +63 -0
  41. package/tsconfig.json +24 -0
  42. package/webpack.config.js +1 -0
@@ -0,0 +1,58 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/colors';
3
+ @use '@carbon/type';
4
+ @use '@openmrs/esm-styleguide/src/vars' as *;
5
+
6
+ .formContainer {
7
+ padding: 0 layout.$spacing-05;
8
+ height: 100%;
9
+ }
10
+
11
+ .formSection {
12
+ margin-bottom: layout.$spacing-07;
13
+ }
14
+
15
+ .formSectionHeader {
16
+ margin-bottom: layout.$spacing-05;
17
+ }
18
+
19
+ .field {
20
+ margin-bottom: layout.$spacing-05;
21
+ }
22
+
23
+ .bottomButtons {
24
+ :global(.cds--btn) {
25
+ width: 50%;
26
+ max-width: unset;
27
+ }
28
+ }
29
+
30
+ .datePickerWrapper {
31
+ margin-top: layout.$spacing-03;
32
+ }
33
+
34
+ // Tablet
35
+ :global(.omrs-breakpoint-lt-desktop) {
36
+ .buttonSet {
37
+ padding: layout.$spacing-06 layout.$spacing-05;
38
+ background-color: colors.$white-0;
39
+ }
40
+ }
41
+
42
+ // System task selection styling
43
+ .systemTaskSelected {
44
+ :global(.cds--combo-box) {
45
+ :global(.cds--text-input) {
46
+ background-color: colors.$blue-10;
47
+ border-color: colors.$blue-60;
48
+ }
49
+ }
50
+ }
51
+
52
+ .systemTaskNotification {
53
+ margin-bottom: layout.$spacing-04;
54
+ }
55
+
56
+ .customTaskNotification {
57
+ margin-top: layout.$spacing-03;
58
+ }
@@ -0,0 +1,458 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import AddTaskForm from './add-task-form.component';
5
+ import {
6
+ useTask,
7
+ saveTask,
8
+ updateTask,
9
+ useFetchProviders,
10
+ useProviderRoles,
11
+ useReferenceVisit,
12
+ type Task,
13
+ } from './task-list.resource';
14
+ import { getDefaultsFromConfigSchema, showSnackbar, useVisit, useConfig, useLayoutType } from '@openmrs/esm-framework';
15
+ import { configSchema, type Config } from '../config-schema';
16
+
17
+ const emptySystemTasks: never[] = [];
18
+ jest.mock('./task-list.resource', () => ({
19
+ useTask: jest.fn(),
20
+ saveTask: jest.fn(),
21
+ updateTask: jest.fn(),
22
+ taskListSWRKey: jest.fn((patientUuid) => `tasks-${patientUuid}`),
23
+ useFetchProviders: jest.fn(),
24
+ useProviderRoles: jest.fn(),
25
+ useReferenceVisit: jest.fn(),
26
+ getPriorityLabel: jest.fn((priority) => priority),
27
+ useSystemTasks: jest.fn(() => ({ systemTasks: emptySystemTasks, isLoading: false })),
28
+ }));
29
+
30
+ const mockUseTask = jest.mocked(useTask);
31
+ const mockSaveTask = jest.mocked(saveTask);
32
+ const mockUpdateTask = jest.mocked(updateTask);
33
+ const mockUseFetchProviders = jest.mocked(useFetchProviders);
34
+ const mockUseProviderRoles = jest.mocked(useProviderRoles);
35
+ const mockUseReferenceVisit = jest.mocked(useReferenceVisit);
36
+ const mockShowSnackbar = jest.mocked(showSnackbar);
37
+ const mockUseConfig = jest.mocked(useConfig<Config>);
38
+
39
+ describe('AddTaskForm', () => {
40
+ const patientUuid = 'patient-uuid-123';
41
+ const mockOnBack = jest.fn();
42
+
43
+ const baseTask: Task = {
44
+ uuid: 'task-uuid-456',
45
+ name: 'Existing Task',
46
+ status: 'not-started',
47
+ createdDate: new Date('2024-01-15T10:00:00Z'),
48
+ completed: false,
49
+ createdBy: 'Test User',
50
+ rationale: 'Test rationale',
51
+ priority: 'high',
52
+ dueDate: {
53
+ type: 'DATE',
54
+ date: new Date('2024-01-20T10:00:00Z'),
55
+ },
56
+ assignee: {
57
+ uuid: 'provider-uuid-789',
58
+ display: 'Dr. Test Provider',
59
+ type: 'person',
60
+ },
61
+ };
62
+
63
+ beforeEach(() => {
64
+ mockUseConfig.mockReturnValue({
65
+ ...getDefaultsFromConfigSchema(configSchema),
66
+ });
67
+
68
+ // Default mocks
69
+ mockUseTask.mockReturnValue({
70
+ task: null,
71
+ isLoading: false,
72
+ error: null,
73
+ mutate: jest.fn(),
74
+ });
75
+
76
+ mockUseFetchProviders.mockReturnValue({
77
+ providers: [],
78
+ setProviderQuery: jest.fn(),
79
+ isLoading: false,
80
+ error: null,
81
+ });
82
+
83
+ mockUseProviderRoles.mockReturnValue([]);
84
+
85
+ mockUseReferenceVisit.mockReturnValue({
86
+ data: { results: [{ uuid: 'reference-visit-uuid' }] },
87
+ isLoading: false,
88
+ error: null,
89
+ });
90
+
91
+ mockSaveTask.mockResolvedValue({} as any);
92
+ mockUpdateTask.mockResolvedValue({} as any);
93
+ });
94
+
95
+ describe('Create mode (no editTaskUuid)', () => {
96
+ it('should render the form with empty fields', () => {
97
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} />);
98
+
99
+ expect(screen.getByLabelText(/task name/i)).toHaveValue('');
100
+ expect(screen.getByText(/add task/i)).toBeInTheDocument();
101
+ expect(screen.getByText(/discard/i)).toBeInTheDocument();
102
+ });
103
+
104
+ it('should call saveTask when submitting in create mode', async () => {
105
+ const user = userEvent.setup();
106
+
107
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} />);
108
+
109
+ const taskNameInput = screen.getByLabelText(/task name/i);
110
+ await user.type(taskNameInput, 'New Task');
111
+
112
+ const addButton = screen.getByRole('button', { name: /add task/i });
113
+ await user.click(addButton);
114
+
115
+ await waitFor(() => {
116
+ expect(mockSaveTask).toHaveBeenCalledWith(
117
+ patientUuid,
118
+ expect.objectContaining({
119
+ name: 'New Task',
120
+ }),
121
+ );
122
+ });
123
+
124
+ expect(mockUpdateTask).not.toHaveBeenCalled();
125
+ expect(mockShowSnackbar).toHaveBeenCalledWith(
126
+ expect.objectContaining({
127
+ title: 'Task added',
128
+ kind: 'success',
129
+ }),
130
+ );
131
+ expect(mockOnBack).toHaveBeenCalled();
132
+ });
133
+
134
+ it('should show error snackbar when saveTask fails', async () => {
135
+ const user = userEvent.setup();
136
+ mockSaveTask.mockRejectedValue(new Error('Network error'));
137
+
138
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} />);
139
+
140
+ const taskNameInput = screen.getByLabelText(/task name/i);
141
+ await user.type(taskNameInput, 'New Task');
142
+
143
+ const addButton = screen.getByRole('button', { name: /add task/i });
144
+ await user.click(addButton);
145
+
146
+ await waitFor(() => {
147
+ expect(mockShowSnackbar).toHaveBeenCalledWith(
148
+ expect.objectContaining({
149
+ title: 'Task add failed',
150
+ kind: 'error',
151
+ }),
152
+ );
153
+ });
154
+
155
+ expect(mockOnBack).not.toHaveBeenCalled();
156
+ });
157
+ });
158
+
159
+ describe('Edit mode (with editTaskUuid)', () => {
160
+ const editTaskUuid = 'task-uuid-456';
161
+
162
+ beforeEach(() => {
163
+ mockUseTask.mockReturnValue({
164
+ task: baseTask,
165
+ isLoading: false,
166
+ error: null,
167
+ mutate: jest.fn(),
168
+ });
169
+ });
170
+
171
+ it('should render the form with "Save task" and "Cancel" buttons', async () => {
172
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} editTaskUuid={editTaskUuid} />);
173
+
174
+ await waitFor(() => {
175
+ expect(screen.getByRole('button', { name: /save task/i })).toBeInTheDocument();
176
+ });
177
+ expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
178
+ expect(screen.queryByText(/add task/i)).not.toBeInTheDocument();
179
+ expect(screen.queryByText(/discard/i)).not.toBeInTheDocument();
180
+ });
181
+
182
+ it('should pre-populate form fields with existing task data', async () => {
183
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} editTaskUuid={editTaskUuid} />);
184
+
185
+ await waitFor(() => {
186
+ expect(screen.getByLabelText(/task name/i)).toHaveValue('Existing Task');
187
+ });
188
+
189
+ // Check rationale is populated
190
+ const rationaleTextarea = screen.getByPlaceholderText(/add a note here/i);
191
+ expect(rationaleTextarea).toHaveValue('Test rationale');
192
+
193
+ // Check due date picker is shown (DATE type shows the date input)
194
+ // Wait for the date picker to render (react-hook-form reset + watch timing)
195
+ await waitFor(() => {
196
+ expect(screen.getByLabelText(/due date/i)).toBeInTheDocument();
197
+ });
198
+
199
+ // Check priority is shown in the combobox input
200
+ const priorityInput = screen.getByRole('combobox', { name: /priority/i });
201
+ expect(priorityInput).toHaveValue('high');
202
+
203
+ // Check assignee is shown in the combobox input
204
+ const assigneeInput = screen.getByRole('combobox', { name: /^assign to provider$/i });
205
+ expect(assigneeInput).toHaveValue('Dr. Test Provider');
206
+ });
207
+
208
+ it('should call updateTask when submitting in edit mode', async () => {
209
+ const user = userEvent.setup();
210
+
211
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} editTaskUuid={editTaskUuid} />);
212
+
213
+ // Wait for form to be populated
214
+ await waitFor(() => {
215
+ expect(screen.getByLabelText(/task name/i)).toHaveValue('Existing Task');
216
+ });
217
+
218
+ // Modify the task name
219
+ const taskNameInput = screen.getByLabelText(/task name/i);
220
+ await user.clear(taskNameInput);
221
+ await user.type(taskNameInput, 'Updated Task Name');
222
+
223
+ const saveButton = screen.getByRole('button', { name: /save task/i });
224
+ await user.click(saveButton);
225
+
226
+ await waitFor(() => {
227
+ expect(mockUpdateTask).toHaveBeenCalledWith(
228
+ patientUuid,
229
+ expect.objectContaining({
230
+ uuid: editTaskUuid,
231
+ name: 'Updated Task Name',
232
+ }),
233
+ );
234
+ });
235
+
236
+ expect(mockSaveTask).not.toHaveBeenCalled();
237
+ expect(mockShowSnackbar).toHaveBeenCalledWith(
238
+ expect.objectContaining({
239
+ title: 'Task updated',
240
+ kind: 'success',
241
+ }),
242
+ );
243
+ expect(mockOnBack).toHaveBeenCalled();
244
+ });
245
+
246
+ it('should show error snackbar when updateTask fails', async () => {
247
+ const user = userEvent.setup();
248
+ mockUpdateTask.mockRejectedValue(new Error('Network error'));
249
+
250
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} editTaskUuid={editTaskUuid} />);
251
+
252
+ // Wait for form to be populated
253
+ await waitFor(() => {
254
+ expect(screen.getByLabelText(/task name/i)).toHaveValue('Existing Task');
255
+ });
256
+
257
+ const saveButton = screen.getByRole('button', { name: /save task/i });
258
+ await user.click(saveButton);
259
+
260
+ await waitFor(() => {
261
+ expect(mockShowSnackbar).toHaveBeenCalledWith(
262
+ expect.objectContaining({
263
+ title: 'Unable to update task',
264
+ kind: 'error',
265
+ }),
266
+ );
267
+ });
268
+
269
+ expect(mockOnBack).not.toHaveBeenCalled();
270
+ });
271
+
272
+ it('should preserve existing task properties when updating', async () => {
273
+ const user = userEvent.setup();
274
+
275
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} editTaskUuid={editTaskUuid} />);
276
+
277
+ // Wait for form to be populated
278
+ await waitFor(() => {
279
+ expect(screen.getByLabelText(/task name/i)).toHaveValue('Existing Task');
280
+ });
281
+
282
+ const saveButton = screen.getByRole('button', { name: /save task/i });
283
+ await user.click(saveButton);
284
+
285
+ await waitFor(() => {
286
+ expect(mockUpdateTask).toHaveBeenCalledWith(
287
+ patientUuid,
288
+ expect.objectContaining({
289
+ uuid: baseTask.uuid,
290
+ status: baseTask.status,
291
+ createdDate: baseTask.createdDate,
292
+ completed: baseTask.completed,
293
+ createdBy: baseTask.createdBy,
294
+ priority: baseTask.priority,
295
+ assignee: expect.objectContaining({
296
+ uuid: baseTask.assignee.uuid,
297
+ type: 'person',
298
+ }),
299
+ dueDate: expect.objectContaining({
300
+ type: 'DATE',
301
+ }),
302
+ }),
303
+ );
304
+ });
305
+ });
306
+ });
307
+
308
+ describe('Edit mode with different due date types', () => {
309
+ it('should select DATE tab and show date picker when editing task with DATE due date', async () => {
310
+ mockUseTask.mockReturnValue({
311
+ task: baseTask, // baseTask has dueDate.type = 'DATE'
312
+ isLoading: false,
313
+ error: null,
314
+ mutate: jest.fn(),
315
+ });
316
+
317
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} editTaskUuid="task-uuid-456" />);
318
+
319
+ await waitFor(() => {
320
+ expect(screen.getByLabelText(/task name/i)).toHaveValue('Existing Task');
321
+ });
322
+
323
+ // Verify DATE tab is selected
324
+ const dateTab = screen.getByRole('tab', { name: /^date$/i });
325
+ expect(dateTab).toHaveAttribute('aria-selected', 'true');
326
+
327
+ // Verify other tabs are not selected
328
+ const thisVisitTab = screen.getByRole('tab', { name: /this visit/i });
329
+ const nextVisitTab = screen.getByRole('tab', { name: /next visit/i });
330
+ expect(thisVisitTab).toHaveAttribute('aria-selected', 'false');
331
+ expect(nextVisitTab).toHaveAttribute('aria-selected', 'false');
332
+
333
+ // Verify date input is shown
334
+ // Wait for the date picker to render (react-hook-form reset + watch timing)
335
+ await waitFor(() => {
336
+ expect(screen.getByLabelText(/due date/i)).toBeInTheDocument();
337
+ });
338
+ });
339
+
340
+ it('should select THIS_VISIT tab when editing task with THIS_VISIT due date', async () => {
341
+ const taskWithThisVisit: Task = {
342
+ ...baseTask,
343
+ dueDate: {
344
+ type: 'THIS_VISIT',
345
+ referenceVisitUuid: 'visit-uuid-123',
346
+ },
347
+ };
348
+
349
+ mockUseTask.mockReturnValue({
350
+ task: taskWithThisVisit,
351
+ isLoading: false,
352
+ error: null,
353
+ mutate: jest.fn(),
354
+ });
355
+
356
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} editTaskUuid="task-uuid-456" />);
357
+
358
+ await waitFor(() => {
359
+ expect(screen.getByLabelText(/task name/i)).toHaveValue('Existing Task');
360
+ });
361
+
362
+ // Verify THIS_VISIT tab is selected
363
+ const thisVisitTab = screen.getByRole('tab', { name: /this visit/i });
364
+ expect(thisVisitTab).toHaveAttribute('aria-selected', 'true');
365
+
366
+ // Verify other tabs are not selected
367
+ const dateTab = screen.getByRole('tab', { name: /^date$/i });
368
+ const nextVisitTab = screen.getByRole('tab', { name: /next visit/i });
369
+ expect(dateTab).toHaveAttribute('aria-selected', 'false');
370
+ expect(nextVisitTab).toHaveAttribute('aria-selected', 'false');
371
+
372
+ // Verify date input is NOT shown (visit-based due dates don't show date picker)
373
+ expect(screen.queryByDisplayValue('20/01/2024')).not.toBeInTheDocument();
374
+ });
375
+
376
+ it('should select NEXT_VISIT tab when editing task with NEXT_VISIT due date', async () => {
377
+ const taskWithNextVisit: Task = {
378
+ ...baseTask,
379
+ dueDate: {
380
+ type: 'NEXT_VISIT',
381
+ referenceVisitUuid: 'visit-uuid-456',
382
+ },
383
+ };
384
+
385
+ mockUseTask.mockReturnValue({
386
+ task: taskWithNextVisit,
387
+ isLoading: false,
388
+ error: null,
389
+ mutate: jest.fn(),
390
+ });
391
+
392
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} editTaskUuid="task-uuid-456" />);
393
+
394
+ await waitFor(() => {
395
+ expect(screen.getByLabelText(/task name/i)).toHaveValue('Existing Task');
396
+ });
397
+
398
+ // Verify NEXT_VISIT tab is selected
399
+ const nextVisitTab = screen.getByRole('tab', { name: /next visit/i });
400
+ expect(nextVisitTab).toHaveAttribute('aria-selected', 'true');
401
+
402
+ // Verify other tabs are not selected
403
+ const dateTab = screen.getByRole('tab', { name: /^date$/i });
404
+ const thisVisitTab = screen.getByRole('tab', { name: /this visit/i });
405
+ expect(dateTab).toHaveAttribute('aria-selected', 'false');
406
+ expect(thisVisitTab).toHaveAttribute('aria-selected', 'false');
407
+ });
408
+
409
+ it('should send provider role assignee when updating task with role assignment', async () => {
410
+ const user = userEvent.setup();
411
+ const taskWithRoleAssignee: Task = {
412
+ ...baseTask,
413
+ assignee: {
414
+ uuid: 'role-uuid-123',
415
+ display: 'Nurse Role',
416
+ type: 'role',
417
+ },
418
+ };
419
+
420
+ mockUseTask.mockReturnValue({
421
+ task: taskWithRoleAssignee,
422
+ isLoading: false,
423
+ error: null,
424
+ mutate: jest.fn(),
425
+ });
426
+
427
+ mockUseConfig.mockReturnValue({
428
+ ...getDefaultsFromConfigSchema(configSchema),
429
+ allowAssigningProviderRole: true,
430
+ });
431
+
432
+ render(<AddTaskForm patientUuid={patientUuid} onBack={mockOnBack} editTaskUuid="task-uuid-456" />);
433
+
434
+ await waitFor(() => {
435
+ expect(screen.getByLabelText(/task name/i)).toHaveValue('Existing Task');
436
+ });
437
+
438
+ // Verify the role is displayed in the provider role combobox
439
+ const roleInput = screen.getByRole('combobox', { name: /assign to provider role/i });
440
+ expect(roleInput).toHaveValue('Nurse Role');
441
+
442
+ const saveButton = screen.getByRole('button', { name: /save task/i });
443
+ await user.click(saveButton);
444
+
445
+ await waitFor(() => {
446
+ expect(mockUpdateTask).toHaveBeenCalledWith(
447
+ patientUuid,
448
+ expect.objectContaining({
449
+ assignee: expect.objectContaining({
450
+ uuid: 'role-uuid-123',
451
+ type: 'role',
452
+ }),
453
+ }),
454
+ );
455
+ });
456
+ });
457
+ });
458
+ });
@@ -0,0 +1,71 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, InlineLoading, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
4
+ import { getCoreTranslation, showSnackbar } from '@openmrs/esm-framework';
5
+ import { useSWRConfig } from 'swr';
6
+ import { deleteTask, taskListSWRKey, type Task } from './task-list.resource';
7
+ import styles from './delete-task.scss';
8
+
9
+ interface DeleteTaskModalProps {
10
+ closeModal: () => void;
11
+ task: Task;
12
+ patientUuid: string;
13
+ onDeleted?: () => void;
14
+ }
15
+
16
+ const DeleteTaskModal: React.FC<DeleteTaskModalProps> = ({ closeModal, task, patientUuid, onDeleted }) => {
17
+ const { t } = useTranslation();
18
+ const { mutate } = useSWRConfig();
19
+ const [isDeleting, setIsDeleting] = useState(false);
20
+
21
+ const handleDelete = useCallback(async () => {
22
+ setIsDeleting(true);
23
+
24
+ try {
25
+ await deleteTask(patientUuid, task);
26
+ await mutate(taskListSWRKey(patientUuid));
27
+
28
+ closeModal();
29
+ showSnackbar({
30
+ isLowContrast: true,
31
+ kind: 'success',
32
+ title: t('taskDeleted', 'Task deleted'),
33
+ });
34
+ onDeleted?.();
35
+ } catch (error) {
36
+ console.error('Error deleting task: ', error);
37
+
38
+ showSnackbar({
39
+ isLowContrast: false,
40
+ kind: 'error',
41
+ title: t('errorDeletingTask', 'Error deleting task'),
42
+ subtitle: error?.message,
43
+ });
44
+ } finally {
45
+ setIsDeleting(false);
46
+ }
47
+ }, [closeModal, task, patientUuid, mutate, onDeleted, t]);
48
+
49
+ return (
50
+ <div>
51
+ <ModalHeader closeModal={closeModal} title={t('deleteTask', 'Delete task')} />
52
+ <ModalBody>
53
+ <p>{t('deleteTaskConfirmationText', 'Are you sure you want to delete this task?')}</p>
54
+ </ModalBody>
55
+ <ModalFooter>
56
+ <Button kind="secondary" onClick={closeModal}>
57
+ {getCoreTranslation('cancel')}
58
+ </Button>
59
+ <Button className={styles.deleteButton} kind="danger" onClick={handleDelete} disabled={isDeleting}>
60
+ {isDeleting ? (
61
+ <InlineLoading description={t('deleting', 'Deleting') + '...'} />
62
+ ) : (
63
+ <span>{getCoreTranslation('delete')}</span>
64
+ )}
65
+ </Button>
66
+ </ModalFooter>
67
+ </div>
68
+ );
69
+ };
70
+
71
+ export default DeleteTaskModal;
@@ -0,0 +1,7 @@
1
+ @use '@carbon/layout';
2
+
3
+ .deleteButton {
4
+ :global(.cds--inline-loading) {
5
+ min-height: layout.$spacing-05;
6
+ }
7
+ }