@zeniai/client-epic-state 5.1.45-betaDI3 → 5.1.45
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.
- package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
- package/lib/entity/snackbar/snackbarTypes.js +0 -3
- package/lib/entity/task/taskPayload.d.ts +1 -3
- package/lib/entity/task/taskPayload.js +2 -24
- package/lib/entity/task/taskReducer.d.ts +1 -8
- package/lib/entity/task/taskReducer.js +4 -29
- package/lib/entity/task/taskState.d.ts +0 -2
- package/lib/entity/tenant/epic/saveExternalConnectionEpic.d.ts +1 -2
- package/lib/entity/tenant/epic/saveExternalConnectionEpic.js +0 -17
- package/lib/epic.d.ts +3 -6
- package/lib/epic.js +3 -6
- package/lib/esm/entity/snackbar/snackbarTypes.js +0 -3
- package/lib/esm/entity/task/taskPayload.js +2 -24
- package/lib/esm/entity/task/taskReducer.js +3 -28
- package/lib/esm/entity/tenant/epic/saveExternalConnectionEpic.js +0 -17
- package/lib/esm/epic.js +3 -6
- package/lib/esm/index.js +8 -8
- package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +1 -31
- package/lib/esm/view/aiCfoView/epics/fetchSkillsEpic.js +8 -33
- package/lib/esm/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.js +0 -3
- package/lib/esm/view/companyTaskManagerView/epics/fetchTaskManagerMetricsEpic.js +1 -7
- package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +0 -7
- package/lib/esm/view/taskManager/taskDetailView/epics/initializeTaskToLocalStoreEpic.js +0 -1
- package/lib/esm/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +9 -70
- package/lib/esm/view/taskManager/taskDetailView/taskDetailReducer.js +2 -56
- package/lib/esm/view/taskManager/taskDetailView/taskDetailSelector.js +9 -35
- package/lib/esm/view/taskManager/taskListView/epics/fetchTaskListEpic.js +1 -3
- package/lib/esm/view/taskManager/taskListView/taskList.js +0 -1
- package/lib/esm/view/taskManager/taskListView/taskListReducer.js +18 -310
- package/lib/esm/view/taskManager/taskListView/taskListSelector.js +0 -1
- package/lib/esm/view/taskManager/taskListView/taskListViewHelpers.js +0 -58
- package/lib/esm/view/transactionDetail/epics/saveTransactionHelper.js +0 -22
- package/lib/esm/view/transactionDetail/epics/updateTransactionDetailEpic.js +3 -75
- package/lib/esm/view/transactionDetail/journalEntryLinesViewModel.js +0 -58
- package/lib/esm/view/transactionDetail/transactionDetailLocalDataHelper.js +8 -10
- package/lib/esm/view/transactionDetail/transactionDetailReducer.js +1 -66
- package/lib/index.d.ts +11 -12
- package/lib/index.js +28 -37
- package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +0 -10
- package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +2 -7
- package/lib/view/aiCfoView/aiCfoViewReducer.js +2 -32
- package/lib/view/aiCfoView/aiCfoViewState.d.ts +1 -5
- package/lib/view/aiCfoView/epics/fetchSkillsEpic.js +8 -33
- package/lib/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.js +0 -3
- package/lib/view/companyTaskManagerView/epics/fetchTaskManagerMetricsEpic.js +1 -7
- package/lib/view/companyView/types/cockpitTypes.d.ts +0 -3
- package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +0 -7
- package/lib/view/taskManager/taskDetailView/epics/initializeTaskToLocalStoreEpic.js +0 -1
- package/lib/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.d.ts +2 -2
- package/lib/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +8 -69
- package/lib/view/taskManager/taskDetailView/taskDetail.d.ts +0 -15
- package/lib/view/taskManager/taskDetailView/taskDetailReducer.d.ts +2 -14
- package/lib/view/taskManager/taskDetailView/taskDetailReducer.js +3 -57
- package/lib/view/taskManager/taskDetailView/taskDetailSelector.d.ts +1 -3
- package/lib/view/taskManager/taskDetailView/taskDetailSelector.js +7 -33
- package/lib/view/taskManager/taskListView/epics/fetchTaskListEpic.js +1 -3
- package/lib/view/taskManager/taskListView/taskList.d.ts +1 -1
- package/lib/view/taskManager/taskListView/taskList.js +0 -1
- package/lib/view/taskManager/taskListView/taskListPayload.d.ts +0 -4
- package/lib/view/taskManager/taskListView/taskListReducer.d.ts +2 -6
- package/lib/view/taskManager/taskListView/taskListReducer.js +19 -311
- package/lib/view/taskManager/taskListView/taskListSelector.js +0 -1
- package/lib/view/taskManager/taskListView/taskListViewHelpers.d.ts +0 -11
- package/lib/view/taskManager/taskListView/taskListViewHelpers.js +1 -60
- package/lib/view/transactionDetail/epics/saveTransactionHelper.js +0 -22
- package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.d.ts +1 -5
- package/lib/view/transactionDetail/epics/updateTransactionDetailEpic.js +4 -77
- package/lib/view/transactionDetail/journalEntryLinesViewModel.d.ts +0 -27
- package/lib/view/transactionDetail/journalEntryLinesViewModel.js +1 -61
- package/lib/view/transactionDetail/transactionDetailLocalDataHelper.js +8 -10
- package/lib/view/transactionDetail/transactionDetailReducer.d.ts +2 -11
- package/lib/view/transactionDetail/transactionDetailReducer.js +2 -67
- package/lib/view/transactionDetail/transactionDetailState.d.ts +1 -8
- package/lib/view/transactionDetail/transactionDetailTypes.d.ts +0 -2
- package/package.json +1 -1
- package/lib/esm/view/aiCfoView/epics/submitFeedbackEpic.js +0 -39
- package/lib/esm/view/taskManager/taskDetailView/epics/createSubTaskEpic.js +0 -96
- package/lib/esm/view/taskManager/taskDetailView/epics/fetchSubTasksEpic.js +0 -53
- package/lib/view/aiCfoView/epics/submitFeedbackEpic.d.ts +0 -13
- package/lib/view/aiCfoView/epics/submitFeedbackEpic.js +0 -43
- package/lib/view/taskManager/taskDetailView/epics/createSubTaskEpic.d.ts +0 -9
- package/lib/view/taskManager/taskDetailView/epics/createSubTaskEpic.js +0 -100
- package/lib/view/taskManager/taskDetailView/epics/fetchSubTasksEpic.d.ts +0 -8
- package/lib/view/taskManager/taskDetailView/epics/fetchSubTasksEpic.js +0 -57
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
|
-
import { updateTasks } from '../../../entity/task/taskReducer';
|
|
3
2
|
import { toPriorityCodeType, toTaskStatusCodeType, } from '../../../entity/task/taskState';
|
|
4
3
|
import { date, dateNow } from '../../../zeniDayJS';
|
|
5
4
|
import { toDueDateGroupKeyType, } from './taskList';
|
|
@@ -43,7 +42,6 @@ export const initialState = {
|
|
|
43
42
|
currentTab: 'live',
|
|
44
43
|
byTab: {
|
|
45
44
|
live: { ...initialTabData },
|
|
46
|
-
completed: { ...initialTabData },
|
|
47
45
|
archived: { ...initialTabData },
|
|
48
46
|
deleted: { ...initialTabData },
|
|
49
47
|
snoozed: { ...initialTabData },
|
|
@@ -65,12 +63,14 @@ export const initialState = {
|
|
|
65
63
|
},
|
|
66
64
|
filters: {
|
|
67
65
|
categoryCombinationOperator: 'AND',
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
categories: [
|
|
67
|
+
{
|
|
68
|
+
field: 'status',
|
|
69
|
+
matchingOperator: 'not_equal',
|
|
70
|
+
values: ['resolved'],
|
|
71
|
+
valuesCombinationOperator: 'ANY',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
74
|
},
|
|
75
75
|
hasValidState() {
|
|
76
76
|
return this.fetchState == 'Completed';
|
|
@@ -102,37 +102,21 @@ const taskList = createSlice({
|
|
|
102
102
|
draft.error = undefined;
|
|
103
103
|
},
|
|
104
104
|
updateTaskList(draft, action) {
|
|
105
|
-
const { data, archived,
|
|
105
|
+
const { data, archived, deleted, snoozed } = action.payload;
|
|
106
106
|
draft.fetchState = 'Completed';
|
|
107
107
|
draft.error = undefined;
|
|
108
|
-
// Build the nested-subtask set from the UNION of every bucket in
|
|
109
|
-
// this payload — not per-bucket. A resolved subtask can arrive in
|
|
110
|
-
// `completed` while its parent (which claims it via `subtasks`)
|
|
111
|
-
// is still in `data`; a per-bucket scan would miss the cross-tab
|
|
112
|
-
// claim and surface the subtask as a phantom top-level row in
|
|
113
|
-
// the Completed tab.
|
|
114
|
-
const nestedSubtaskIds = collectNestedSubtaskIds([
|
|
115
|
-
data,
|
|
116
|
-
completed,
|
|
117
|
-
archived,
|
|
118
|
-
deleted,
|
|
119
|
-
snoozed,
|
|
120
|
-
]);
|
|
121
108
|
// Group live tasks
|
|
122
|
-
const liveGrouped = getGroupedTaskIds(data
|
|
109
|
+
const liveGrouped = getGroupedTaskIds(data);
|
|
123
110
|
draft.byTab.live = toTabData(liveGrouped);
|
|
124
111
|
// Group other tabs if provided
|
|
125
|
-
if (completed != null) {
|
|
126
|
-
draft.byTab.completed = toTabData(getGroupedTaskIds(completed, nestedSubtaskIds));
|
|
127
|
-
}
|
|
128
112
|
if (archived != null) {
|
|
129
|
-
draft.byTab.archived = toTabData(getGroupedTaskIds(archived
|
|
113
|
+
draft.byTab.archived = toTabData(getGroupedTaskIds(archived));
|
|
130
114
|
}
|
|
131
115
|
if (deleted != null) {
|
|
132
|
-
draft.byTab.deleted = toTabData(getGroupedTaskIds(deleted
|
|
116
|
+
draft.byTab.deleted = toTabData(getGroupedTaskIds(deleted));
|
|
133
117
|
}
|
|
134
118
|
if (snoozed != null) {
|
|
135
|
-
draft.byTab.snoozed = toTabData(getGroupedTaskIds(snoozed
|
|
119
|
+
draft.byTab.snoozed = toTabData(getGroupedTaskIds(snoozed));
|
|
136
120
|
}
|
|
137
121
|
// Copy active tab data to top-level fields
|
|
138
122
|
const activeTabData = draft.byTab[draft.currentTab];
|
|
@@ -370,26 +354,9 @@ const taskList = createSlice({
|
|
|
370
354
|
draft.taskIdsByAssignees[assigneeKey] = draft.taskIdsByAssignees[assigneeKey].filter((taskId) => taskId !== currentTaskState.id);
|
|
371
355
|
draft.taskIdsByTags[tagsKey] = draft.taskIdsByTags[tagsKey].filter((taskId) => taskId !== currentTaskState.id);
|
|
372
356
|
draft.taskIdsByDueDate[dueDateKey] = draft.taskIdsByDueDate[dueDateKey].filter((taskId) => taskId !== currentTaskState.id);
|
|
373
|
-
|
|
374
|
-
// just the destination bucket. Otherwise a detail-page save that
|
|
375
|
-
// moves a task from group A to group B leaves the row listed
|
|
376
|
-
// under both A and B until refetch.
|
|
377
|
-
const previousGroupIds = currentTaskState.taskGroupIds ?? [];
|
|
378
|
-
const allGroupIdsToClean = new Set([
|
|
379
|
-
...previousGroupIds,
|
|
380
|
-
taskGroupId,
|
|
381
|
-
]);
|
|
382
|
-
allGroupIdsToClean.forEach((gid) => {
|
|
383
|
-
if (draft.taskIdsByGroupsIds[gid] != null) {
|
|
384
|
-
draft.taskIdsByGroupsIds[gid] = draft.taskIdsByGroupsIds[gid].filter((id) => id !== currentTaskState.id);
|
|
385
|
-
}
|
|
386
|
-
});
|
|
357
|
+
draft.taskIdsByGroupsIds[taskGroupId] = draft.taskIdsByGroupsIds[taskGroupId].filter((taskId) => taskId !== currentTaskState.id);
|
|
387
358
|
}
|
|
388
359
|
doUpdateTaskList(draft, task, taskGroupId);
|
|
389
|
-
// Mirror top-level grouping into the active tab's byTab snapshot,
|
|
390
|
-
// otherwise the next updateTaskListTab call copies stale byTab data
|
|
391
|
-
// back over this optimistic update.
|
|
392
|
-
syncTopLevelToActiveTab(draft);
|
|
393
360
|
},
|
|
394
361
|
updateTaskListOnCreateTaskFromTemplateSuccess(draft, action) {
|
|
395
362
|
const { tasks } = action.payload;
|
|
@@ -413,7 +380,6 @@ const taskList = createSlice({
|
|
|
413
380
|
const taskId = task.id;
|
|
414
381
|
draft.updateTasksFetchState.fetchState = 'In-Progress';
|
|
415
382
|
draft.updateTasksFetchState.error = undefined;
|
|
416
|
-
let didMigrateTab = false;
|
|
417
383
|
if (updates.priority != null) {
|
|
418
384
|
const prevPriorityCode = toPriorityCodeType(task.priority.code);
|
|
419
385
|
draft.taskIdsByPriority[prevPriorityCode] = draft.taskIdsByPriority[prevPriorityCode].filter((id) => id != taskId);
|
|
@@ -425,32 +391,6 @@ const taskList = createSlice({
|
|
|
425
391
|
draft.taskIdsByStatus[prevStatusCode] = draft.taskIdsByStatus[prevStatusCode].filter((id) => id != taskId);
|
|
426
392
|
const newStatusCode = toTaskStatusCodeType(updates.status.code);
|
|
427
393
|
draft.taskIdsByStatus[newStatusCode].push(taskId);
|
|
428
|
-
// Optimistically re-bucket between the live and completed tabs
|
|
429
|
-
// when status transitions in/out of "resolved", so the task
|
|
430
|
-
// moves to the Completed tab without waiting for the next
|
|
431
|
-
// fetch. Mirrors backend split in task_handler.py: only
|
|
432
|
-
// `resolved` counts as completed; `done` stays Active.
|
|
433
|
-
//
|
|
434
|
-
// "Top-level" here means: currently rendered as a standalone
|
|
435
|
-
// row in the live or completed tab (i.e. present in
|
|
436
|
-
// `byTab.<tab>.taskIds`). We use presence in byTab — NOT a
|
|
437
|
-
// `parent_task_id == null` check — so orphan subtasks (parent
|
|
438
|
-
// hidden from the customer's payload, so we render them flat)
|
|
439
|
-
// ALSO migrate between tabs on status flip. This matches the
|
|
440
|
-
// criterion `getGroupedTaskIds` uses when it builds byTab from
|
|
441
|
-
// the initial fetch — a task is nested iff a visible parent's
|
|
442
|
-
// `subtasks` array claims it, otherwise it's top-level here.
|
|
443
|
-
const isTopLevelTask = draft.byTab.live.taskIds.includes(taskId) ||
|
|
444
|
-
draft.byTab.completed.taskIds.includes(taskId);
|
|
445
|
-
const wasCompleted = prevStatusCode === 'resolved';
|
|
446
|
-
const isCompleted = newStatusCode === 'resolved';
|
|
447
|
-
if (isTopLevelTask && wasCompleted !== isCompleted) {
|
|
448
|
-
const from = isCompleted ? 'live' : 'completed';
|
|
449
|
-
const to = isCompleted ? 'completed' : 'live';
|
|
450
|
-
const updatedTask = { ...task, ...updates };
|
|
451
|
-
migrateTaskBetweenTabs(draft, taskId, updatedTask, from, to);
|
|
452
|
-
didMigrateTab = true;
|
|
453
|
-
}
|
|
454
394
|
}
|
|
455
395
|
if (updates.assignees != null) {
|
|
456
396
|
const prevGroupId = getAssigneesGroupKey(task.assignees);
|
|
@@ -490,18 +430,7 @@ const taskList = createSlice({
|
|
|
490
430
|
};
|
|
491
431
|
}
|
|
492
432
|
}
|
|
493
|
-
|
|
494
|
-
// The migration moved the task out of the source byTab and into
|
|
495
|
-
// the destination byTab. The active tab's grouping arrays are
|
|
496
|
-
// now authoritative — mirror them back to the top-level so the
|
|
497
|
-
// UI sees the task gone from Live (or freshly arrived in
|
|
498
|
-
// Completed). Inverse of syncTopLevelToActiveTab, used only on
|
|
499
|
-
// this path.
|
|
500
|
-
copyTabDataToTopLevel(draft, draft.byTab[draft.currentTab]);
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
syncTopLevelToActiveTab(draft);
|
|
504
|
-
}
|
|
433
|
+
syncTopLevelToActiveTab(draft);
|
|
505
434
|
},
|
|
506
435
|
updateTasksListOnUpdateTaskSuccess(draft) {
|
|
507
436
|
draft.updateTasksFetchState.fetchState = 'Completed';
|
|
@@ -514,83 +443,12 @@ const taskList = createSlice({
|
|
|
514
443
|
removeTaskFromList(draft, action) {
|
|
515
444
|
removeTaskIds(draft, [action.payload]);
|
|
516
445
|
},
|
|
517
|
-
/**
|
|
518
|
-
* Remove a task id from a single group bucket without touching tab
|
|
519
|
-
* membership or other groupings. Used when the user clears a task's
|
|
520
|
-
* group on the Detail page so the row stops appearing under the old
|
|
521
|
-
* group while saveTaskDetailEpic skips the standard list re-insert.
|
|
522
|
-
*/
|
|
523
|
-
removeTaskFromGroupBucket(draft, action) {
|
|
524
|
-
const { taskId, taskGroupId } = action.payload;
|
|
525
|
-
if (draft.taskIdsByGroupsIds[taskGroupId] != null) {
|
|
526
|
-
draft.taskIdsByGroupsIds[taskGroupId] = draft.taskIdsByGroupsIds[taskGroupId].filter((id) => id !== taskId);
|
|
527
|
-
}
|
|
528
|
-
// Mirror to the active tab's grouping so the UI re-renders.
|
|
529
|
-
const activeTab = draft.byTab[draft.currentTab];
|
|
530
|
-
if (activeTab.taskIdsByGroupsIds[taskGroupId] != null) {
|
|
531
|
-
activeTab.taskIdsByGroupsIds[taskGroupId] =
|
|
532
|
-
activeTab.taskIdsByGroupsIds[taskGroupId].filter((id) => id !== taskId);
|
|
533
|
-
}
|
|
534
|
-
},
|
|
535
446
|
clearTaskList(draft) {
|
|
536
447
|
Object.assign(draft, initialState);
|
|
537
448
|
},
|
|
538
449
|
},
|
|
539
|
-
extraReducers: (builder) => {
|
|
540
|
-
// When the entity store receives updated tasks (e.g. from task detail
|
|
541
|
-
// save, snooze/archive/unarchive, etc.), re-bucket top-level tasks
|
|
542
|
-
// between the live and completed tabs based on the new status. Mirrors
|
|
543
|
-
// the backend split in task_handler.py: only `resolved` counts as
|
|
544
|
-
// completed.
|
|
545
|
-
//
|
|
546
|
-
// "Top-level" here means: currently rendered as a standalone row in
|
|
547
|
-
// the live or completed tab (i.e. present in
|
|
548
|
-
// `byTab.<tab>.taskIds`). Tasks that are nested under a parent's
|
|
549
|
-
// expanded subtask row live outside byTab.taskIds and are silently
|
|
550
|
-
// skipped by the "in-bucket" check below. This uses the same
|
|
551
|
-
// criterion as `getGroupedTaskIds` — a task is nested iff a visible
|
|
552
|
-
// parent's `subtasks` array claims it, otherwise it's top-level —
|
|
553
|
-
// so orphan subtasks (parent hidden from the customer's payload)
|
|
554
|
-
// migrate correctly on status flip via pusher / detail-save.
|
|
555
|
-
builder.addCase(updateTasks, (draft, action) => {
|
|
556
|
-
action.payload.forEach((taskPayload) => {
|
|
557
|
-
const taskId = taskPayload.task_id;
|
|
558
|
-
// `TaskPayload.status` is required per the type — every producer
|
|
559
|
-
// in this repo sends it, and `mapTaskPayloadToTask` dereferences
|
|
560
|
-
// it unguarded. The optional chain + null guard are defensive
|
|
561
|
-
// only, in case a downstream consumer (web-app-ui / cockpit-ui)
|
|
562
|
-
// ever dispatches `updateTasks` with a partial payload; in that
|
|
563
|
-
// case we treat missing status as "no status change" and skip
|
|
564
|
-
// re-bucketing so we don't migrate a Completed task back into
|
|
565
|
-
// the Live tab.
|
|
566
|
-
const statusCode = taskPayload.status?.code;
|
|
567
|
-
if (statusCode == null) {
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
const isCompleted = statusCode === 'resolved';
|
|
571
|
-
const targetTab = isCompleted ? 'completed' : 'live';
|
|
572
|
-
const otherTab = isCompleted ? 'live' : 'completed';
|
|
573
|
-
// Skip if the task isn't currently in either of these buckets
|
|
574
|
-
// (archived/snoozed/deleted are managed by their own actions).
|
|
575
|
-
const inLive = draft.byTab.live.taskIds.includes(taskId);
|
|
576
|
-
const inCompleted = draft.byTab.completed.taskIds.includes(taskId);
|
|
577
|
-
if (!inLive && !inCompleted) {
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
if (draft.byTab[otherTab].taskIds.includes(taskId)) {
|
|
581
|
-
migrateTaskPayloadBetweenTabs(draft, taskId, taskPayload, otherTab, targetTab);
|
|
582
|
-
}
|
|
583
|
-
else if (!draft.byTab[targetTab].taskIds.includes(taskId)) {
|
|
584
|
-
draft.byTab[targetTab].taskIds.push(taskId);
|
|
585
|
-
}
|
|
586
|
-
});
|
|
587
|
-
// Re-sync the top-level taskIds / grouping arrays for whichever
|
|
588
|
-
// tab the user is currently looking at.
|
|
589
|
-
copyTabDataToTopLevel(draft, draft.byTab[draft.currentTab]);
|
|
590
|
-
});
|
|
591
|
-
},
|
|
592
450
|
});
|
|
593
|
-
export const { fetchTaskListPage, fetchTaskList, updateTaskList, updateTaskListTab, updateTaskListFetchStatus, updateTaskListSearchText, updateTaskListUIState, updateTaskFilters, updateTaskListOnNewGroupCreationSuccess, bulkUpdateTaskList, updateTasksListOnBulkUpdateTasksSuccess, updateTasksListOnBulkUpdateTasksFailure, initiateTaskListLocalData, updateTaskListLocalData, updateTaskListOnTaskGroupDeletion, dragNDropTasks, updateTaskListOnNewTaskCreationSuccess, updateTaskListOnTaskGroupNameUpdate, updateTaskFromListView, updateTasksListOnUpdateTaskSuccess, updateTasksListOnUpdateTaskFailure, updateTaskListOnCreateTaskFromTemplateSuccess, removeTaskFromList,
|
|
451
|
+
export const { fetchTaskListPage, fetchTaskList, updateTaskList, updateTaskListTab, updateTaskListFetchStatus, updateTaskListSearchText, updateTaskListUIState, updateTaskFilters, updateTaskListOnNewGroupCreationSuccess, bulkUpdateTaskList, updateTasksListOnBulkUpdateTasksSuccess, updateTasksListOnBulkUpdateTasksFailure, initiateTaskListLocalData, updateTaskListLocalData, updateTaskListOnTaskGroupDeletion, dragNDropTasks, updateTaskListOnNewTaskCreationSuccess, updateTaskListOnTaskGroupNameUpdate, updateTaskFromListView, updateTasksListOnUpdateTaskSuccess, updateTasksListOnUpdateTaskFailure, updateTaskListOnCreateTaskFromTemplateSuccess, removeTaskFromList, clearTaskList, } = taskList.actions;
|
|
594
452
|
export default taskList.reducer;
|
|
595
453
|
/**
|
|
596
454
|
* helpers
|
|
@@ -697,115 +555,6 @@ const copyTabDataToTopLevel = (draft, tabData) => {
|
|
|
697
555
|
draft.taskIdsByDueDate = tabData.taskIdsByDueDate;
|
|
698
556
|
draft.taskIdsByTags = tabData.taskIdsByTags;
|
|
699
557
|
};
|
|
700
|
-
/**
|
|
701
|
-
* Migrate a parent task between two `byTab` buckets (live ↔ completed)
|
|
702
|
-
* when its status flips into / out of `resolved`. The destination tab
|
|
703
|
-
* picks up the task in EVERY grouping dimension (status, priority,
|
|
704
|
-
* group id, assignees, dueDate, tags) using the UPDATED task as the
|
|
705
|
-
* source-of-truth for the new bucket keys. Otherwise the Completed tab
|
|
706
|
-
* shows the task in the right tab-level list but the grouped views
|
|
707
|
-
* (Group by Status, Group by Priority, etc.) miss it until the next
|
|
708
|
-
* full fetch.
|
|
709
|
-
*/
|
|
710
|
-
const migrateTaskBetweenTabs = (draft, taskId, updatedTask, from, to) => {
|
|
711
|
-
const fromTab = draft.byTab[from];
|
|
712
|
-
const toTab = draft.byTab[to];
|
|
713
|
-
// Source tab: drop the task from every grouping dimension.
|
|
714
|
-
removeIdsFromTabData(fromTab, [taskId]);
|
|
715
|
-
// Destination tab: add the task to taskIds (deduped) plus the
|
|
716
|
-
// appropriate bucket inside each grouping dimension based on the
|
|
717
|
-
// updated task.
|
|
718
|
-
if (!toTab.taskIds.includes(taskId)) {
|
|
719
|
-
toTab.taskIds.push(taskId);
|
|
720
|
-
}
|
|
721
|
-
const priorityKey = toPriorityCodeType(updatedTask.priority.code);
|
|
722
|
-
if (!toTab.taskIdsByPriority[priorityKey].includes(taskId)) {
|
|
723
|
-
toTab.taskIdsByPriority[priorityKey].push(taskId);
|
|
724
|
-
}
|
|
725
|
-
const statusKey = toTaskStatusCodeType(updatedTask.status.code);
|
|
726
|
-
if (!toTab.taskIdsByStatus[statusKey].includes(taskId)) {
|
|
727
|
-
toTab.taskIdsByStatus[statusKey].push(taskId);
|
|
728
|
-
}
|
|
729
|
-
const dueDateKey = updatedTask.dueDate != null
|
|
730
|
-
? getDueDateGroupKey(updatedTask.dueDate.toString())
|
|
731
|
-
: 'upcoming';
|
|
732
|
-
if (!toTab.taskIdsByDueDate[dueDateKey].includes(taskId)) {
|
|
733
|
-
toTab.taskIdsByDueDate[dueDateKey].push(taskId);
|
|
734
|
-
}
|
|
735
|
-
const assigneeKey = getAssigneesGroupKey(updatedTask.assignees);
|
|
736
|
-
if (toTab.taskIdsByAssignees[assigneeKey] == null) {
|
|
737
|
-
toTab.taskIdsByAssignees[assigneeKey] = [taskId];
|
|
738
|
-
}
|
|
739
|
-
else if (!toTab.taskIdsByAssignees[assigneeKey].includes(taskId)) {
|
|
740
|
-
toTab.taskIdsByAssignees[assigneeKey].push(taskId);
|
|
741
|
-
}
|
|
742
|
-
const tagIds = [...updatedTask.tagIds].sort();
|
|
743
|
-
const tagsKey = tagIds.join(',');
|
|
744
|
-
if (toTab.taskIdsByTags[tagsKey] == null) {
|
|
745
|
-
toTab.taskIdsByTags[tagsKey] = [taskId];
|
|
746
|
-
}
|
|
747
|
-
else if (!toTab.taskIdsByTags[tagsKey].includes(taskId)) {
|
|
748
|
-
toTab.taskIdsByTags[tagsKey].push(taskId);
|
|
749
|
-
}
|
|
750
|
-
(updatedTask.taskGroupIds ?? []).forEach((groupId) => {
|
|
751
|
-
if (toTab.taskIdsByGroupsIds[groupId] == null) {
|
|
752
|
-
toTab.taskIdsByGroupsIds[groupId] = [taskId];
|
|
753
|
-
}
|
|
754
|
-
else if (!toTab.taskIdsByGroupsIds[groupId].includes(taskId)) {
|
|
755
|
-
toTab.taskIdsByGroupsIds[groupId].push(taskId);
|
|
756
|
-
}
|
|
757
|
-
});
|
|
758
|
-
};
|
|
759
|
-
/**
|
|
760
|
-
* `updateTasks` extraReducer variant — same migration semantics but
|
|
761
|
-
* keyed off the raw `TaskPayload` instead of the rich `Task` entity.
|
|
762
|
-
*/
|
|
763
|
-
const migrateTaskPayloadBetweenTabs = (draft, taskId, payload, from, to) => {
|
|
764
|
-
const fromTab = draft.byTab[from];
|
|
765
|
-
const toTab = draft.byTab[to];
|
|
766
|
-
removeIdsFromTabData(fromTab, [taskId]);
|
|
767
|
-
if (!toTab.taskIds.includes(taskId)) {
|
|
768
|
-
toTab.taskIds.push(taskId);
|
|
769
|
-
}
|
|
770
|
-
if (payload.priority?.code != null) {
|
|
771
|
-
const priorityKey = toPriorityCodeType(payload.priority.code);
|
|
772
|
-
if (!toTab.taskIdsByPriority[priorityKey].includes(taskId)) {
|
|
773
|
-
toTab.taskIdsByPriority[priorityKey].push(taskId);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
if (payload.status?.code != null) {
|
|
777
|
-
const statusKey = toTaskStatusCodeType(payload.status.code);
|
|
778
|
-
if (!toTab.taskIdsByStatus[statusKey].includes(taskId)) {
|
|
779
|
-
toTab.taskIdsByStatus[statusKey].push(taskId);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
const dueDateKey = getDueDateGroupKey(payload.due_date);
|
|
783
|
-
if (!toTab.taskIdsByDueDate[dueDateKey].includes(taskId)) {
|
|
784
|
-
toTab.taskIdsByDueDate[dueDateKey].push(taskId);
|
|
785
|
-
}
|
|
786
|
-
const assigneeKey = getAssigneesGroupKey(payload.assignees ?? []);
|
|
787
|
-
if (toTab.taskIdsByAssignees[assigneeKey] == null) {
|
|
788
|
-
toTab.taskIdsByAssignees[assigneeKey] = [taskId];
|
|
789
|
-
}
|
|
790
|
-
else if (!toTab.taskIdsByAssignees[assigneeKey].includes(taskId)) {
|
|
791
|
-
toTab.taskIdsByAssignees[assigneeKey].push(taskId);
|
|
792
|
-
}
|
|
793
|
-
const tagsKey = getTagsGroupKey(payload.tags ?? []);
|
|
794
|
-
if (toTab.taskIdsByTags[tagsKey] == null) {
|
|
795
|
-
toTab.taskIdsByTags[tagsKey] = [taskId];
|
|
796
|
-
}
|
|
797
|
-
else if (!toTab.taskIdsByTags[tagsKey].includes(taskId)) {
|
|
798
|
-
toTab.taskIdsByTags[tagsKey].push(taskId);
|
|
799
|
-
}
|
|
800
|
-
(payload.task_group_ids ?? []).forEach((groupId) => {
|
|
801
|
-
if (toTab.taskIdsByGroupsIds[groupId] == null) {
|
|
802
|
-
toTab.taskIdsByGroupsIds[groupId] = [taskId];
|
|
803
|
-
}
|
|
804
|
-
else if (!toTab.taskIdsByGroupsIds[groupId].includes(taskId)) {
|
|
805
|
-
toTab.taskIdsByGroupsIds[groupId].push(taskId);
|
|
806
|
-
}
|
|
807
|
-
});
|
|
808
|
-
};
|
|
809
558
|
const removeIdsFromTabData = (tabData, taskIds) => {
|
|
810
559
|
const removedSet = new Set(taskIds);
|
|
811
560
|
const isRemoved = (id) => removedSet.has(id);
|
|
@@ -833,39 +582,7 @@ const removeTaskIds = (draft, taskIds) => {
|
|
|
833
582
|
removeIdsFromTabData(draft, taskIds);
|
|
834
583
|
removeIdsFromTabData(draft.byTab[draft.currentTab], taskIds);
|
|
835
584
|
};
|
|
836
|
-
|
|
837
|
-
// bucket in the payload. See the callsite in `updateTaskList` for why
|
|
838
|
-
// this must be cross-bucket rather than per-bucket.
|
|
839
|
-
const collectNestedSubtaskIds = (buckets) => {
|
|
840
|
-
const nestedSubtaskIds = new Set();
|
|
841
|
-
buckets.forEach((bucket) => {
|
|
842
|
-
if (bucket == null) {
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
bucket.forEach((task) => {
|
|
846
|
-
(task.subtasks ?? []).forEach((subtaskId) => nestedSubtaskIds.add(subtaskId));
|
|
847
|
-
});
|
|
848
|
-
});
|
|
849
|
-
return nestedSubtaskIds;
|
|
850
|
-
};
|
|
851
|
-
// A task is a "nested subtask" (hidden from the flat top-level list +
|
|
852
|
-
// dropped from the tab count) iff a parent in the same payload claims
|
|
853
|
-
// it via its `subtasks: [...]` array. Match this criterion exactly to
|
|
854
|
-
// WC's `allSubtaskIds` union in TaskManagementPage.tsx — otherwise
|
|
855
|
-
// the tab count and the visible top-level row count disagree
|
|
856
|
-
// whenever the BE emits `parent_task_id` on a subtask but omits it
|
|
857
|
-
// from the parent's `subtasks` array (a real inconsistency this UI
|
|
858
|
-
// sees today).
|
|
859
|
-
//
|
|
860
|
-
// Using `parent_task_id` here would sometimes drop a task from the
|
|
861
|
-
// count that WC still renders as a flat top-level row — that's the
|
|
862
|
-
// bug we're fixing.
|
|
863
|
-
//
|
|
864
|
-
// `nestedSubtaskIds` is passed in from the caller and covers subtasks
|
|
865
|
-
// claimed by parents in ANY bucket (live/completed/archived/deleted/
|
|
866
|
-
// snoozed), so a subtask resolved into `completed` while its parent
|
|
867
|
-
// stays in `data` is still correctly recognized as nested.
|
|
868
|
-
const getGroupedTaskIds = (tasks, nestedSubtaskIds) => {
|
|
585
|
+
const getGroupedTaskIds = (tasks) => {
|
|
869
586
|
const taskIds = [];
|
|
870
587
|
const taskIdsByGroupIds = {};
|
|
871
588
|
const taskIdsByAssignees = {};
|
|
@@ -880,21 +597,12 @@ const getGroupedTaskIds = (tasks, nestedSubtaskIds) => {
|
|
|
880
597
|
};
|
|
881
598
|
const taskIdsByTags = {};
|
|
882
599
|
tasks.forEach((task) => {
|
|
883
|
-
|
|
884
|
-
// Subtasks stay in every groupBy array so the UI can render them
|
|
885
|
-
// nested under their parent (WC's TaskManagementPage builds
|
|
886
|
-
// `subtasksByParentId` from `group.tasks` and then filters them
|
|
887
|
-
// out of top-level rows via `filterOutSubtasks`). Only the flat
|
|
888
|
-
// top-level `taskIds` array — which drives the Active/Completed/…
|
|
889
|
-
// tab counts — must exclude nested subtasks.
|
|
600
|
+
taskIds.push(task.task_id);
|
|
890
601
|
const priorityKey = toPriorityCodeType(task.priority.code);
|
|
891
602
|
const statusKey = toTaskStatusCodeType(task.status.code);
|
|
892
603
|
const assigneeKey = getAssigneesGroupKey(task.assignees);
|
|
893
604
|
const dueDateKey = getDueDateGroupKey(task.due_date);
|
|
894
605
|
const tagsKey = getTagsGroupKey(task.tags);
|
|
895
|
-
if (!isNestedSubtask) {
|
|
896
|
-
taskIds.push(task.task_id);
|
|
897
|
-
}
|
|
898
606
|
taskIdsGroupedByPriority[priorityKey] = [
|
|
899
607
|
...taskIdsGroupedByPriority[priorityKey],
|
|
900
608
|
task.task_id,
|
|
@@ -334,7 +334,6 @@ export const getAllTasks = createSelector((state) => state.taskListState, (state
|
|
|
334
334
|
const taskGroupTemplates = getTaskGroupTemplateByIds(taskGroupTemplateState, taskGroupTemplateViewState.taskGroupTemplateIds);
|
|
335
335
|
const tabCounts = {
|
|
336
336
|
live: taskListState.byTab.live.taskIds.length,
|
|
337
|
-
completed: taskListState.byTab.completed.taskIds.length,
|
|
338
337
|
archived: taskListState.byTab.archived.taskIds.length,
|
|
339
338
|
deleted: taskListState.byTab.deleted.taskIds.length,
|
|
340
339
|
snoozed: taskListState.byTab.snoozed.taskIds.length,
|
|
@@ -48,61 +48,3 @@ export const getTaskUpdates = (groupByKey, groupId) => {
|
|
|
48
48
|
taskGroupIds: [groupId],
|
|
49
49
|
};
|
|
50
50
|
};
|
|
51
|
-
/**
|
|
52
|
-
* Sort a subtask list by one of the sortable columns. Pure — no state
|
|
53
|
-
* dependency. `statusOrder` is derived from `allTaskStatus` so any new
|
|
54
|
-
* status added upstream slots in by its enum position instead of
|
|
55
|
-
* falling into a catch-all bucket.
|
|
56
|
-
*/
|
|
57
|
-
export const sortSubtasks = (subtasks, sortKey, sortOrder, userList, tagList) => {
|
|
58
|
-
if (sortKey == null) {
|
|
59
|
-
return subtasks;
|
|
60
|
-
}
|
|
61
|
-
const statusOrder = allTaskStatus.reduce((acc, status, index) => {
|
|
62
|
-
acc[status.code] = index;
|
|
63
|
-
return acc;
|
|
64
|
-
}, {});
|
|
65
|
-
const unknownStatusRank = allTaskStatus.length;
|
|
66
|
-
const cmpStr = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' });
|
|
67
|
-
const keyFn = (task) => {
|
|
68
|
-
if (sortKey === 'name') {
|
|
69
|
-
return task.name ?? '';
|
|
70
|
-
}
|
|
71
|
-
if (sortKey === 'status') {
|
|
72
|
-
return statusOrder[task.status.code] ?? unknownStatusRank;
|
|
73
|
-
}
|
|
74
|
-
if (sortKey === 'assignee') {
|
|
75
|
-
const firstId = task.assignees?.[0];
|
|
76
|
-
if (firstId == null) {
|
|
77
|
-
return '';
|
|
78
|
-
}
|
|
79
|
-
const user = userList.find((candidate) => candidate.userId === firstId);
|
|
80
|
-
return user
|
|
81
|
-
? `${user.firstName ?? ''} ${user.lastName ?? ''}`.trim()
|
|
82
|
-
: '';
|
|
83
|
-
}
|
|
84
|
-
if (sortKey === 'tag') {
|
|
85
|
-
const firstTagId = task.tagIds?.[0];
|
|
86
|
-
if (firstTagId == null) {
|
|
87
|
-
return '';
|
|
88
|
-
}
|
|
89
|
-
const tag = tagList.find((candidate) => candidate.tagId === firstTagId);
|
|
90
|
-
return tag?.name ?? '';
|
|
91
|
-
}
|
|
92
|
-
return '';
|
|
93
|
-
};
|
|
94
|
-
const copy = [...subtasks];
|
|
95
|
-
copy.sort((a, b) => {
|
|
96
|
-
const av = keyFn(a);
|
|
97
|
-
const bv = keyFn(b);
|
|
98
|
-
let cmp;
|
|
99
|
-
if (typeof av === 'number' && typeof bv === 'number') {
|
|
100
|
-
cmp = av - bv;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
cmp = cmpStr(String(av), String(bv));
|
|
104
|
-
}
|
|
105
|
-
return sortOrder === 'asc' ? cmp : -cmp;
|
|
106
|
-
});
|
|
107
|
-
return copy;
|
|
108
|
-
};
|
|
@@ -45,15 +45,7 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
|
|
|
45
45
|
let lastUpdatedCategory;
|
|
46
46
|
let lastUpdatedClass;
|
|
47
47
|
// single recommendation to just pick the last recommendation in the list of lines
|
|
48
|
-
const existingLineIds = new Set(transaction.lines?.map((l) => l.id) ?? []);
|
|
49
|
-
const deletedLineIds = new Set((transactionDetailLocalData.deletedLineIds ?? []).filter((id) => existingLineIds.has(id)));
|
|
50
|
-
// Collect new lines — in lineItemById but not yet in transaction.lines.
|
|
51
|
-
const addedLines = Object.values(transactionDetailLocalData.lineItemById).filter((item) => !existingLineIds.has(item.id));
|
|
52
48
|
transaction.lines?.forEach((line) => {
|
|
53
|
-
// Skip lines the user deleted in edit mode.
|
|
54
|
-
if (deletedLineIds.has(line.id)) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
49
|
const lineItem = transactionDetailLocalData.lineItemById[line.id];
|
|
58
50
|
if (line.type === 'transaction_with_account_and_class_line' ||
|
|
59
51
|
line.type === 'transaction_with_product_or_service_line' ||
|
|
@@ -64,7 +56,6 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
|
|
|
64
56
|
const entityDetails = getEntityDetails(selectedEntity, lineItem, transactionDetailLocalData.vendorUpdates, transactionDetailLocalData.customerUpdates);
|
|
65
57
|
updatesByLineId[line.id] = {
|
|
66
58
|
lineDescription: lineItem.lineDescription,
|
|
67
|
-
amount: lineItem.amount,
|
|
68
59
|
class: lineItem.class,
|
|
69
60
|
account: lineItem.account,
|
|
70
61
|
// Project is at the transaction level for invoice/credit_memo; omit from lines.
|
|
@@ -86,17 +77,6 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
|
|
|
86
77
|
currentSelectedEntityLine.type === 'vendor'
|
|
87
78
|
? lineItem.vendor
|
|
88
79
|
: undefined,
|
|
89
|
-
// Carry the JE debit/credit side so a split edit that flips an
|
|
90
|
-
// existing line's posting side persists through the line patch.
|
|
91
|
-
...(line.type === 'journal_entry_transaction_line'
|
|
92
|
-
? {
|
|
93
|
-
postingType: lineItem.postingType,
|
|
94
|
-
postingTypeName: lineItem.postingType != null
|
|
95
|
-
? lineItem.postingType.charAt(0).toUpperCase() +
|
|
96
|
-
lineItem.postingType.slice(1)
|
|
97
|
-
: lineItem.postingTypeName,
|
|
98
|
-
}
|
|
99
|
-
: {}),
|
|
100
80
|
};
|
|
101
81
|
}
|
|
102
82
|
let hasCategoryChanged = false;
|
|
@@ -183,8 +163,6 @@ export const toTransactionUpdatesFromTransactionDetailLocalData = (transactionDe
|
|
|
183
163
|
and all the line items are NOT uncategorized*/
|
|
184
164
|
markAsMiscatUpdates: isTransactionMisCategorized,
|
|
185
165
|
anyBulkUpdates: isBulkUpdate,
|
|
186
|
-
addedLines: addedLines.length > 0 ? addedLines : undefined,
|
|
187
|
-
deletedLineIds: deletedLineIds.size > 0 ? [...deletedLineIds] : undefined,
|
|
188
166
|
};
|
|
189
167
|
};
|
|
190
168
|
const doUpdateEntityUpdates = (entityUpdates, entityId, entityType, recommendation, isBulkUpdate = false, updatedEntityId, entityName) => {
|