@abgov/nx-adsp 5.9.0-beta.1 → 5.9.0-beta.3

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 (24) hide show
  1. package/generators.json +5 -0
  2. package/package.json +1 -1
  3. package/src/generators/react-form/files/__fileName__/__fileName__.slice.ts__tmpl__ +2 -2
  4. package/src/generators/react-form/files/__fileName__/__fileName__.tsx__tmpl__ +2 -96
  5. package/src/generators/react-form/includes/input-template.ejs +107 -0
  6. package/src/generators/react-form/react-form.js +1 -1
  7. package/src/generators/react-form/react-form.js.map +1 -1
  8. package/src/generators/react-form/react-forms.spec.ts +1 -0
  9. package/src/generators/react-form/schema.d.ts +1 -0
  10. package/src/generators/react-form/schema.json +3 -3
  11. package/src/generators/react-task-list/files/__fileName__/__fileName__.module.css__tmpl__ +80 -0
  12. package/src/generators/react-task-list/files/__fileName__/__fileName__.slice.spec.ts__tmpl__ +26 -0
  13. package/src/generators/react-task-list/files/__fileName__/__fileName__.slice.ts__tmpl__ +723 -0
  14. package/src/generators/react-task-list/files/__fileName__/__fileName__.tsx__tmpl__ +595 -0
  15. package/src/generators/react-task-list/files/__fileName__/task.d.ts__tmpl__ +30 -0
  16. package/src/generators/react-task-list/react-task-list.d.ts +3 -0
  17. package/src/generators/react-task-list/react-task-list.js +124 -0
  18. package/src/generators/react-task-list/react-task-list.js.map +1 -0
  19. package/src/generators/react-task-list/react-task-list.spec.ts +71 -0
  20. package/src/generators/react-task-list/schema.d.ts +14 -0
  21. package/src/generators/react-task-list/schema.json +45 -0
  22. package/src/utils/form.d.ts +1 -0
  23. package/src/utils/form.js.map +1 -1
  24. package/src/utils/task.d.ts +6 -0
@@ -0,0 +1,723 @@
1
+ import {
2
+ createAsyncThunk,
3
+ createSelector,
4
+ createSlice,
5
+ PayloadAction,
6
+ } from '@reduxjs/toolkit';
7
+ import axios from 'axios';
8
+ import { UserState } from 'redux-oidc';
9
+ import { io, Socket } from 'socket.io-client';
10
+ import { CONFIG_FEATURE_KEY, ConfigState } from '../config.slice';
11
+ import { Person, QueueDefinition, Task } from './task';
12
+
13
+ export const <%= constantName %>_FEATURE_KEY = '<%= fileName %>';
14
+ const TASK_SERVICE_ID = 'urn:ads:platform:task-service';
15
+ const PUSH_SERVICE_ID = 'urn:ads:platform:push-service';
16
+ const UPDATE_STREAM_ID = '<%= updateStreamId %>';
17
+
18
+ /*
19
+ * Update these interfaces according to your requirements.
20
+ */
21
+ enum TaskPriority {
22
+ Urgent = 2,
23
+ High = 1,
24
+ Normal = 0,
25
+ }
26
+
27
+ export type TaskFilter = 'active' | 'pending' | 'assigned';
28
+
29
+ export interface TaskMetric {
30
+ name: string;
31
+ value: number;
32
+ unit?: string;
33
+ }
34
+
35
+ export interface TaskUser extends Person {
36
+ id: string;
37
+ isAssigner: boolean;
38
+ isWorker: boolean;
39
+ }
40
+
41
+ export interface <%= className %>TaskListState {
42
+ user: TaskUser;
43
+ queue: {
44
+ namespace: string;
45
+ name: string;
46
+ };
47
+ live: boolean;
48
+ people: Record<string, Person>;
49
+ assigners: string[];
50
+ workers: string[];
51
+ tasks: Record<string, Task>;
52
+ results: string[];
53
+ filter: TaskFilter;
54
+ next: string;
55
+ selected: string;
56
+ opened: string;
57
+ busy: {
58
+ initializing: boolean;
59
+ loading: boolean;
60
+ executing: boolean;
61
+ };
62
+ modal: {
63
+ taskToAssign: Task;
64
+ taskToPrioritize: Task;
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Export an effect using createAsyncThunk from
70
+ * the Redux Toolkit: https://redux-toolkit.js.org/api/createAsyncThunk
71
+ *
72
+ * e.g.
73
+ * ```
74
+ * import React, { useEffect } from 'react';
75
+ * import { useDispatch } from 'react-redux';
76
+ *
77
+ * // ...
78
+ *
79
+ * const dispatch = useDispatch();
80
+ * useEffect(() => {
81
+ * dispatch(fetchIntake())
82
+ * }, [dispatch]);
83
+ * ```
84
+ */
85
+
86
+ interface TaskResults {
87
+ results: Task[];
88
+ page: {
89
+ after?: string;
90
+ next?: string;
91
+ size: number;
92
+ };
93
+ }
94
+
95
+ export const initializeQueue = createAsyncThunk(
96
+ '<%= propertyName %>/initialize-definition',
97
+ async (
98
+ { namespace, name }: { namespace: string; name: string },
99
+ { dispatch, getState }
100
+ ) => {
101
+ const state = getState();
102
+ const { user }: UserState = state['user'];
103
+ const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
104
+
105
+ const { data } = await axios.get<QueueDefinition>(
106
+ `${directory[TASK_SERVICE_ID]}/task/v1/queues/${namespace}/${name}`,
107
+ {
108
+ headers: { Authorization: `Bearer ${user.access_token}` },
109
+ }
110
+ );
111
+
112
+ dispatch(loadQueueTasks({ namespace: data.namespace, name: data.name }));
113
+ dispatch(loadQueuePeople({ namespace: data.namespace, name: data.name }));
114
+ if (UPDATE_STREAM_ID) {
115
+ dispatch(connectStream());
116
+ }
117
+
118
+ return {
119
+ queue: data,
120
+ };
121
+ }
122
+ );
123
+
124
+ interface TaskEvent {
125
+ timestamp: string;
126
+ payload: {
127
+ task: Task;
128
+ };
129
+ }
130
+
131
+ let socket: Socket;
132
+ export const connectStream = createAsyncThunk(
133
+ '<%= propertyName %>/connectStream',
134
+ async (_, { dispatch, getState }) => {
135
+ const state = getState();
136
+ const { user }: UserState = state['user'];
137
+ const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
138
+
139
+ // Create the connection if no previous connection or it is disconnected.
140
+ if (!socket || socket.disconnected) {
141
+ socket = io(`${directory[PUSH_SERVICE_ID]}/`, {
142
+ query: {
143
+ stream: UPDATE_STREAM_ID,
144
+ },
145
+ withCredentials: true,
146
+ extraHeaders: { Authorization: `Bearer ${user.access_token}` },
147
+ });
148
+
149
+ socket.on('connect', () => {
150
+ dispatch(<%= propertyName %>TaskListActions.streamConnectionChanged(true));
151
+ });
152
+
153
+ socket.on('disconnected', () => {
154
+ dispatch(<%= propertyName %>TaskListActions.streamConnectionChanged(false));
155
+ });
156
+
157
+ socket.on('task-service:task-created', ({ payload }: TaskEvent) => {
158
+ dispatch(<%= propertyName %>TaskListActions.setTask(payload.task));
159
+ });
160
+
161
+ socket.on('task-service:task-assigned', ({ payload }: TaskEvent) => {
162
+ dispatch(<%= propertyName %>TaskListActions.setTask(payload.task));
163
+ });
164
+
165
+ socket.on('task-service:task-priority-set', ({ payload }: TaskEvent) => {
166
+ dispatch(<%= propertyName %>TaskListActions.setTask(payload.task));
167
+ });
168
+
169
+ socket.on('task-service:task-started', ({ payload }: TaskEvent) => {
170
+ dispatch(<%= propertyName %>TaskListActions.setTask(payload.task));
171
+ });
172
+
173
+ socket.on('task-service:task-completed', ({ payload }: TaskEvent) => {
174
+ dispatch(<%= propertyName %>TaskListActions.setTask(payload.task));
175
+ });
176
+
177
+ socket.on('task-service:task-cancelled', ({ payload }: TaskEvent) => {
178
+ dispatch(<%= propertyName %>TaskListActions.setTask(payload.task));
179
+ });
180
+ }
181
+ }
182
+ );
183
+
184
+ export const loadQueueTasks = createAsyncThunk(
185
+ '<%= propertyName %>/load-queue-tasks',
186
+ async (
187
+ {
188
+ namespace,
189
+ name,
190
+ after,
191
+ }: { namespace: string; name: string; after?: string },
192
+ { getState }
193
+ ) => {
194
+ const state = getState();
195
+ const { user }: UserState = state['user'];
196
+ const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
197
+
198
+ const { data } = await axios.get<TaskResults>(
199
+ `${directory[TASK_SERVICE_ID]}/task/v1/queues/${namespace}/${name}/tasks`,
200
+ {
201
+ headers: { Authorization: `Bearer ${user.access_token}` },
202
+ params: {
203
+ top: 100,
204
+ after,
205
+ },
206
+ }
207
+ );
208
+
209
+ return data;
210
+ }
211
+ );
212
+
213
+ export const loadQueuePeople = createAsyncThunk(
214
+ '<%= propertyName %>/load-queue-people',
215
+ async (
216
+ { namespace, name }: { namespace: string; name: string },
217
+ { getState }
218
+ ) => {
219
+ const state = getState();
220
+ const { user }: UserState = state['user'];
221
+ const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
222
+
223
+ let assigners: Person[] = [];
224
+ let workers: Person[] = [];
225
+ try {
226
+ const { data: assignersResult } = await axios.get<Person[]>(
227
+ `${directory[TASK_SERVICE_ID]}/task/v1/queues/${namespace}/${name}/assigners`,
228
+ {
229
+ headers: { Authorization: `Bearer ${user.access_token}` },
230
+ }
231
+ );
232
+ assigners = assignersResult;
233
+
234
+ const { data: workersResult } = await axios.get<Person[]>(
235
+ `${directory[TASK_SERVICE_ID]}/task/v1/queues/${namespace}/${name}/workers`,
236
+ {
237
+ headers: { Authorization: `Bearer ${user.access_token}` },
238
+ }
239
+ );
240
+ workers = workersResult;
241
+ } catch (err) {
242
+ // Failed to load queue people due to permissions.
243
+ if (
244
+ !axios.isAxiosError(err) ||
245
+ (err.response?.status !== 401 && err.response?.status !== 403)
246
+ ) {
247
+ throw err;
248
+ }
249
+ }
250
+
251
+ return {
252
+ assigners,
253
+ workers,
254
+ user: {
255
+ id: user.profile.sub,
256
+ name: user.profile.name,
257
+ email: user.profile.email,
258
+ isAssigner: !!assigners.find((a) => a.id === user.profile.sub),
259
+ isWorker: !!workers.find((w) => w.id === user.profile.sub),
260
+ },
261
+ };
262
+ }
263
+ );
264
+
265
+ export const setTaskPriority = createAsyncThunk(
266
+ '<%= propertyName %>/set-task-priority',
267
+ async (
268
+ { taskId, priority }: { taskId: string; priority: string },
269
+ { dispatch, getState }
270
+ ) => {
271
+ const state = getState();
272
+ const { user }: UserState = state['user'];
273
+ const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
274
+ const { queue }: <%= className %>TaskListState = state[<%= constantName %>_FEATURE_KEY];
275
+
276
+ const { data } = await axios.post<Task>(
277
+ `${directory[TASK_SERVICE_ID]}/task/v1/queues/${queue.namespace}/${queue.name}/tasks/${taskId}`,
278
+ {
279
+ operation: 'set-priority',
280
+ priority,
281
+ },
282
+ {
283
+ headers: { Authorization: `Bearer ${user.access_token}` },
284
+ }
285
+ );
286
+
287
+ dispatch(<%= propertyName %>TaskListActions.setTaskToPrioritize(null));
288
+
289
+ return data;
290
+ }
291
+ );
292
+
293
+ export const assignTask = createAsyncThunk(
294
+ '<%= propertyName %>/assign-task',
295
+ async (
296
+ { taskId, assignTo }: { taskId: string; assignTo?: Person },
297
+ { dispatch, getState }
298
+ ) => {
299
+ const state = getState();
300
+ const { user }: UserState = state['user'];
301
+ const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
302
+ const { queue }: <%= className %>TaskListState = state[<%= constantName %>_FEATURE_KEY];
303
+
304
+ const { data } = await axios.post<Task>(
305
+ `${directory[TASK_SERVICE_ID]}/task/v1/queues/${queue.namespace}/${queue.name}/tasks/${taskId}`,
306
+ {
307
+ operation: 'assign',
308
+ assignTo,
309
+ },
310
+ {
311
+ headers: { Authorization: `Bearer ${user.access_token}` },
312
+ }
313
+ );
314
+
315
+ dispatch(<%= propertyName %>TaskListActions.setTaskToAssign(null));
316
+
317
+ return data;
318
+ }
319
+ );
320
+
321
+ export const startTask = createAsyncThunk(
322
+ '<%= propertyName %>/start-task',
323
+ async ({ taskId }: { taskId: string }, { getState }) => {
324
+ const state = getState();
325
+ const { user }: UserState = state['user'];
326
+ const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
327
+ const { queue }: <%= className %>TaskListState = state[<%= constantName %>_FEATURE_KEY];
328
+
329
+ const { data } = await axios.post<Task>(
330
+ `${directory[TASK_SERVICE_ID]}/task/v1/queues/${queue.namespace}/${queue.name}/tasks/${taskId}`,
331
+ {
332
+ operation: 'start',
333
+ },
334
+ {
335
+ headers: { Authorization: `Bearer ${user.access_token}` },
336
+ }
337
+ );
338
+
339
+ return data;
340
+ }
341
+ );
342
+
343
+ export const completeTask = createAsyncThunk(
344
+ '<%= propertyName %>/complete-task',
345
+ async ({ taskId }: { taskId: string }, { getState }) => {
346
+ const state = getState();
347
+ const { user }: UserState = state['user'];
348
+ const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
349
+ const { queue }: <%= className %>TaskListState = state[<%= constantName %>_FEATURE_KEY];
350
+
351
+ const { data } = await axios.post<Task>(
352
+ `${directory[TASK_SERVICE_ID]}/task/v1/queues/${queue.namespace}/${queue.name}/tasks/${taskId}`,
353
+ {
354
+ operation: 'complete',
355
+ },
356
+ {
357
+ headers: { Authorization: `Bearer ${user.access_token}` },
358
+ }
359
+ );
360
+
361
+ return data;
362
+ }
363
+ );
364
+
365
+ export const cancelTask = createAsyncThunk(
366
+ '<%= propertyName %>/cancel-task',
367
+ async (
368
+ { taskId, reason }: { taskId: string; reason: string },
369
+ { getState }
370
+ ) => {
371
+ const state = getState();
372
+ const { user }: UserState = state['user'];
373
+ const { directory }: ConfigState = state[CONFIG_FEATURE_KEY];
374
+ const { queue }: <%= className %>TaskListState = state[<%= constantName %>_FEATURE_KEY];
375
+
376
+ const { data } = await axios.post<Task>(
377
+ `${directory[TASK_SERVICE_ID]}/task/v1/queues/${queue.namespace}/${queue.name}/tasks/${taskId}`,
378
+ {
379
+ operation: 'cancel',
380
+ reason: reason || undefined,
381
+ },
382
+ {
383
+ headers: { Authorization: `Bearer ${user.access_token}` },
384
+ }
385
+ );
386
+
387
+ return data;
388
+ }
389
+ );
390
+
391
+ export const initial<%= className %>TaskListState: <%= className %>TaskListState = {
392
+ user: {
393
+ id: null,
394
+ name: null,
395
+ email: null,
396
+ isAssigner: false,
397
+ isWorker: false,
398
+ },
399
+ queue: {
400
+ namespace: null,
401
+ name: null,
402
+ },
403
+ live: false,
404
+ people: {},
405
+ assigners: [],
406
+ workers: [],
407
+ tasks: {},
408
+ results: [],
409
+ filter: 'active',
410
+ next: null,
411
+ selected: null,
412
+ opened: null,
413
+ busy: {
414
+ initializing: true,
415
+ loading: false,
416
+ executing: false,
417
+ },
418
+ modal: {
419
+ taskToAssign: null,
420
+ taskToPrioritize: null,
421
+ },
422
+ };
423
+
424
+ export const <%= propertyName %>TaskListSlice = createSlice({
425
+ name: <%= constantName %>_FEATURE_KEY,
426
+ initialState: initial<%= className %>TaskListState,
427
+ reducers: {
428
+ streamConnectionChanged: (state, { payload }: PayloadAction<boolean>) => {
429
+ state.live = payload;
430
+ },
431
+ setTask: (state, { payload }: PayloadAction<Task>) => {
432
+ state.tasks[payload.id] = payload;
433
+ if (!state.results.includes(payload.id)) {
434
+ state.results = [...state.results, payload.id];
435
+ }
436
+ },
437
+ setFilter: (state, { payload }: PayloadAction<TaskFilter>) => {
438
+ state.filter = payload;
439
+ },
440
+ setOpenTask: (state, { payload }: PayloadAction<string>) => {
441
+ state.opened = payload;
442
+ },
443
+ setTaskToAssign: (state, { payload }: PayloadAction<Task>) => {
444
+ state.modal.taskToAssign = payload;
445
+ },
446
+ setTaskToPrioritize: (state, { payload }: PayloadAction<Task>) => {
447
+ state.modal.taskToPrioritize = payload;
448
+ },
449
+ },
450
+ extraReducers: (builder) => {
451
+ builder
452
+ .addCase(initializeQueue.pending, (state) => {
453
+ state.busy.initializing = true;
454
+ })
455
+ .addCase(initializeQueue.fulfilled, (state, { payload }) => {
456
+ state.busy.initializing = false;
457
+ state.queue = {
458
+ namespace: payload.queue.namespace,
459
+ name: payload.queue.name,
460
+ };
461
+ })
462
+ .addCase(initializeQueue.rejected, (state) => {
463
+ state.busy.initializing = false;
464
+ })
465
+ .addCase(loadQueuePeople.pending, (state) => {
466
+ state.busy.initializing = true;
467
+ })
468
+ .addCase(loadQueuePeople.fulfilled, (state, { payload }) => {
469
+ state.busy.initializing = false;
470
+ state.people = [...payload.assigners, ...payload.workers].reduce(
471
+ (people, person) => ({ ...people, [person.id]: person }),
472
+ {}
473
+ );
474
+ state.assigners = payload.assigners.map((p) => p.id);
475
+ state.workers = payload.workers.map((p) => p.id);
476
+ state.user = payload.user;
477
+ })
478
+ .addCase(loadQueuePeople.rejected, (state) => {
479
+ state.busy.initializing = false;
480
+ })
481
+ .addCase(loadQueueTasks.pending, (state) => {
482
+ state.busy.loading = true;
483
+ })
484
+ .addCase(loadQueueTasks.fulfilled, (state, { payload }) => {
485
+ state.busy.loading = false;
486
+ state.tasks = payload.results.reduce(
487
+ (results, task) => ({
488
+ ...results,
489
+ [task.id]: task,
490
+ }),
491
+ state.tasks
492
+ );
493
+ state.results = (payload.page.after ? state.results : []).concat(
494
+ payload.results.map((r) => r.id)
495
+ );
496
+ state.next = payload.page.next;
497
+ })
498
+ .addCase(loadQueueTasks.rejected, (state) => {
499
+ state.busy.initializing = false;
500
+ })
501
+ .addCase(assignTask.pending, (state) => {
502
+ state.busy.executing = true;
503
+ })
504
+ .addCase(assignTask.fulfilled, (state, { payload }) => {
505
+ state.busy.executing = false;
506
+ state.tasks = {
507
+ ...state.tasks,
508
+ [payload.id]: payload,
509
+ };
510
+ })
511
+ .addCase(assignTask.rejected, (state) => {
512
+ state.busy.executing = false;
513
+ })
514
+ .addCase(setTaskPriority.pending, (state) => {
515
+ state.busy.executing = true;
516
+ })
517
+ .addCase(setTaskPriority.fulfilled, (state, { payload }) => {
518
+ state.busy.executing = false;
519
+ state.tasks = {
520
+ ...state.tasks,
521
+ [payload.id]: payload,
522
+ };
523
+ })
524
+ .addCase(setTaskPriority.rejected, (state) => {
525
+ state.busy.executing = false;
526
+ })
527
+ .addCase(startTask.pending, (state) => {
528
+ state.busy.executing = true;
529
+ })
530
+ .addCase(startTask.fulfilled, (state, { payload }) => {
531
+ state.busy.executing = false;
532
+ state.tasks = {
533
+ ...state.tasks,
534
+ [payload.id]: payload,
535
+ };
536
+ })
537
+ .addCase(startTask.rejected, (state) => {
538
+ state.busy.executing = false;
539
+ })
540
+ .addCase(completeTask.pending, (state) => {
541
+ state.busy.executing = true;
542
+ })
543
+ .addCase(completeTask.fulfilled, (state, { payload }) => {
544
+ state.busy.executing = false;
545
+ state.tasks = {
546
+ ...state.tasks,
547
+ [payload.id]: payload,
548
+ };
549
+ })
550
+ .addCase(completeTask.rejected, (state) => {
551
+ state.busy.executing = false;
552
+ })
553
+ .addCase(cancelTask.pending, (state) => {
554
+ state.busy.executing = true;
555
+ })
556
+ .addCase(cancelTask.fulfilled, (state, { payload }) => {
557
+ state.busy.executing = false;
558
+ state.tasks = {
559
+ ...state.tasks,
560
+ [payload.id]: payload,
561
+ };
562
+ })
563
+ .addCase(cancelTask.rejected, (state) => {
564
+ state.busy.executing = false;
565
+ });
566
+ },
567
+ });
568
+
569
+ /*
570
+ * Export reducer for store configuration.
571
+ */
572
+ export const <%= propertyName %>TaskListReducer = <%= propertyName %>TaskListSlice.reducer;
573
+
574
+ /*
575
+ * Export action creators to be dispatched. For use with the `useDispatch` hook.
576
+ *
577
+ * e.g.
578
+ * ```
579
+ * import React, { useEffect } from 'react';
580
+ * import { useDispatch } from 'react-redux';
581
+ *
582
+ * // ...
583
+ *
584
+ * const dispatch = useDispatch();
585
+ * useEffect(() => {
586
+ * dispatch(intakeActions.add({ id: 1 }))
587
+ * }, [dispatch]);
588
+ * ```
589
+ *
590
+ * See: https://react-redux.js.org/next/api/hooks#usedispatch
591
+ */
592
+ export const <%= propertyName %>TaskListActions = <%= propertyName %>TaskListSlice.actions;
593
+
594
+ /*
595
+ * Export selectors to query state. For use with the `useSelector` hook.
596
+ *
597
+ * e.g.
598
+ * ```
599
+ * import { useSelector } from 'react-redux';
600
+ *
601
+ * // ...
602
+ *
603
+ * const entities = useSelector(selectAllIntake);
604
+ * ```
605
+ *
606
+ * See: https://react-redux.js.org/next/api/hooks#useselector
607
+ */
608
+
609
+ export const get<%= className %>TaskListState = (
610
+ rootState: unknown
611
+ ): <%= className %>TaskListState => rootState[<%= constantName %>_FEATURE_KEY];
612
+
613
+ export const getFilter = createSelector(
614
+ get<%= className %>TaskListState,
615
+ (state) => state.filter
616
+ );
617
+
618
+ export const getLive = createSelector(
619
+ get<%= className %>TaskListState,
620
+ (state) => state.live
621
+ );
622
+
623
+ export const getTasks = createSelector(
624
+ (state) => state[<%= constantName %>_FEATURE_KEY].user.id,
625
+ (state) => state[<%= constantName %>_FEATURE_KEY].results,
626
+ (state) => state[<%= constantName %>_FEATURE_KEY].tasks,
627
+ getFilter,
628
+ (
629
+ userId: string,
630
+ results: string[],
631
+ tasks: Record<string, Task>,
632
+ filter: string
633
+ ) =>
634
+ results
635
+ .map((r) => tasks[r])
636
+ .filter((r) => {
637
+ switch (filter) {
638
+ case 'pending':
639
+ return r?.status === 'Pending';
640
+ case 'assigned':
641
+ return !r?.endedOn && r?.assignment?.assignedTo?.id === userId;
642
+ case 'active':
643
+ return !r?.endedOn;
644
+ default:
645
+ return !!r;
646
+ }
647
+ })
648
+ .sort((a, b) => {
649
+ let result = TaskPriority[b.priority] - TaskPriority[a.priority];
650
+ if (!result) {
651
+ result =
652
+ new Date(a.createdOn).getTime() - new Date(b.createdOn).getTime();
653
+ }
654
+ return result;
655
+ })
656
+ );
657
+
658
+ export const getBusy = createSelector(
659
+ get<%= className %>TaskListState,
660
+ (state) => state.busy
661
+ );
662
+
663
+ export const getModal = createSelector(
664
+ get<%= className %>TaskListState,
665
+ (state) => state.modal
666
+ );
667
+
668
+ export const getOpenTask = createSelector(
669
+ get<%= className %>TaskListState,
670
+ (state) => state.opened && state.tasks[state.opened]
671
+ );
672
+
673
+ export const getQueueWorkers = createSelector(get<%= className %>TaskListState, (state) =>
674
+ state.workers.map((worker) => state.people[worker])
675
+ );
676
+
677
+ export const getQueueUser = createSelector(
678
+ get<%= className %>TaskListState,
679
+ (state) => state.user
680
+ );
681
+
682
+ export const getTaskMetrics = createSelector(
683
+ (state) => state[<%= constantName %>_FEATURE_KEY].user.id,
684
+ (state) => state[<%= constantName %>_FEATURE_KEY].results,
685
+ (state) => state[<%= constantName %>_FEATURE_KEY].tasks,
686
+ (userId: string, results: string[], tasks: Record<string, Task>) => {
687
+ const taskResults = results.map((r) => tasks[r]);
688
+ const statusCounts = {
689
+ Pending: 0,
690
+ 'In Progress': 0,
691
+ Stopped: 0,
692
+ Completed: 0,
693
+ Cancelled: 0,
694
+ };
695
+ taskResults.forEach(
696
+ (t) => (statusCounts[t.status] = (statusCounts[t.status] || 0) + 1)
697
+ );
698
+
699
+ const myTasks = taskResults.filter(
700
+ (t) => t.assignment?.assignedTo?.id === userId
701
+ ).length;
702
+
703
+ const metrics: TaskMetric[] = [
704
+ {
705
+ name: 'Pending',
706
+ value: statusCounts.Pending,
707
+ unit: statusCounts.Pending > 1 ? 'tasks' : 'task',
708
+ },
709
+ {
710
+ name: 'In Progress',
711
+ value: statusCounts['In Progress'],
712
+ unit: statusCounts['In Progress'] > 1 ? 'tasks' : 'task',
713
+ },
714
+ {
715
+ name: 'Assigned to me',
716
+ value: myTasks,
717
+ unit: myTasks > 1 ? 'tasks' : 'task',
718
+ },
719
+ ];
720
+
721
+ return metrics;
722
+ }
723
+ );