@zeniai/client-epic-state 5.1.20-betaDI2 → 5.1.20-betaRD2

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 (31) hide show
  1. package/lib/epic.d.ts +1 -2
  2. package/lib/epic.js +1 -2
  3. package/lib/esm/epic.js +1 -2
  4. package/lib/esm/index.js +1 -2
  5. package/lib/esm/view/companyTaskManagerView/epics/fetchTaskManagerMetricsEpic.js +1 -8
  6. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +49 -11
  7. package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +48 -4
  8. package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +107 -19
  9. package/lib/esm/view/spendManagement/commonSetup/setupViewReducer.js +5 -1
  10. package/lib/esm/view/taskManager/taskDetailView/epics/initializeTaskToLocalStoreEpic.js +0 -1
  11. package/lib/esm/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +1 -9
  12. package/lib/esm/view/taskManager/taskDetailView/taskDetailSelector.js +1 -2
  13. package/lib/index.d.ts +1 -2
  14. package/lib/index.js +8 -10
  15. package/lib/view/companyTaskManagerView/epics/fetchTaskManagerMetricsEpic.js +1 -8
  16. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +47 -9
  17. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.d.ts +2 -2
  18. package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +47 -3
  19. package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +7 -0
  20. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.d.ts +4 -1
  21. package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +108 -20
  22. package/lib/view/expenseAutomationView/types/transactionsViewState.d.ts +16 -0
  23. package/lib/view/spendManagement/commonSetup/setupViewReducer.js +5 -1
  24. package/lib/view/taskManager/taskDetailView/epics/initializeTaskToLocalStoreEpic.js +0 -1
  25. package/lib/view/taskManager/taskDetailView/epics/saveTaskDetailEpic.js +1 -9
  26. package/lib/view/taskManager/taskDetailView/taskDetail.d.ts +0 -1
  27. package/lib/view/taskManager/taskDetailView/taskDetailSelector.js +1 -2
  28. package/package.json +1 -1
  29. package/lib/esm/view/taskManager/taskDetailView/epics/sendCommentMentionNotificationEpic.js +0 -44
  30. package/lib/view/taskManager/taskDetailView/epics/sendCommentMentionNotificationEpic.d.ts +0 -20
  31. package/lib/view/taskManager/taskDetailView/epics/sendCommentMentionNotificationEpic.js +0 -48
@@ -10,6 +10,7 @@ const classListReducer_1 = require("../../../classList/classListReducer");
10
10
  const ownerListReducer_1 = require("../../../ownerList/ownerListReducer");
11
11
  const projectListReducer_1 = require("../../../projectList/projectListReducer");
12
12
  const transactionsViewReducer_1 = require("../../reducers/transactionsViewReducer");
13
+ const transactionsViewState_1 = require("../../types/transactionsViewState");
13
14
  const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)(transactionsViewReducer_1.fetchTransactionCategorizationView.match), (0, operators_1.mergeMap)((action) => {
14
15
  const { selectedTab, cacheOverride, keepExistingListItems, pageToken, period, refreshViewInBackground, searchString, resetListItems, isUncategorizedExpenseCategoryEnabled, } = action.payload;
15
16
  const updateActions = [];
@@ -19,8 +20,14 @@ const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pi
19
20
  month: period.start.month,
20
21
  year: period.start.year,
21
22
  };
22
- const transactionIds = transactionIdsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod)] ??
23
- [];
23
+ const periodId = (0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod);
24
+ const transactionIds = transactionIdsBySelectedPeriod[periodId] ?? [];
25
+ const hasUsableSnapshot = (tabKey) => {
26
+ const snapshot = expenseAutomationTransactionsViewState.transactionCategorizationView[tabKey].fullDatasetSnapshot;
27
+ return (snapshot != null &&
28
+ snapshot.periodId === periodId &&
29
+ snapshot.transactionIds.length > 0);
30
+ };
24
31
  const accountList = state$.value.accountListState.byReportId['accountList'];
25
32
  if (accountList.hasValidState() === false &&
26
33
  accountList.fetchState === 'Not-Started') {
@@ -42,11 +49,48 @@ const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pi
42
49
  vendorOwnerList.fetchState === 'Not-Started') {
43
50
  updateActions.push((0, ownerListReducer_1.fetchOwnerList)());
44
51
  }
45
- if (cacheOverride === true ||
52
+ // When clearing search (searchString === ''), restore from the pre-search
53
+ // snapshot if one exists — avoids a round-trip since all data is already
54
+ // in the entity store. Fall back to a normal fetch if no snapshot (the
55
+ // tab was never loaded before the search was applied).
56
+ if (searchString === '' && hasUsableSnapshot(selectedTab)) {
57
+ updateActions.push((0, transactionsViewReducer_1.restoreFullDatasetSnapshot)({ selectedTab, period }));
58
+ }
59
+ else if (cacheOverride === true ||
46
60
  fetchState === 'Not-Started' ||
47
61
  transactionIds.length === 0) {
48
62
  updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(selectedTab, period, cacheOverride, keepExistingListItems, searchString, pageToken, refreshViewInBackground, resetListItems, isUncategorizedExpenseCategoryEnabled));
49
63
  }
64
+ // On the initial load of a tab (searchString undefined = non-search fetch),
65
+ // preload every other tab that hasn't been fetched yet for this period.
66
+ // This makes switching to the other tab instant instead of triggering a
67
+ // fresh fetch on click. refreshViewInBackground=true keeps it silent.
68
+ if (searchString === undefined) {
69
+ transactionsViewState_1.TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
70
+ const otherTabFetchState = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].fetchState;
71
+ if (otherTabFetchState === 'Not-Started') {
72
+ updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(otherTab, period, false, false, undefined, undefined, true, undefined, isUncategorizedExpenseCategoryEnabled));
73
+ }
74
+ });
75
+ }
76
+ // When search changes (searchString defined, including empty string to
77
+ // clear), also update every other tab so all tabs always reflect the same
78
+ // search query. Skip if the other tab already has this search string to
79
+ // avoid redundant work on noop updates. On clear, restore from snapshot
80
+ // if available; otherwise issue a background fetch.
81
+ if (searchString !== undefined) {
82
+ transactionsViewState_1.TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
83
+ const otherTabSearchString = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].uiState.searchString;
84
+ if (searchString !== otherTabSearchString) {
85
+ if (searchString === '' && hasUsableSnapshot(otherTab)) {
86
+ updateActions.push((0, transactionsViewReducer_1.restoreFullDatasetSnapshot)({ selectedTab: otherTab, period }));
87
+ }
88
+ else {
89
+ updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(otherTab, period, true, keepExistingListItems, searchString, undefined, true, true, isUncategorizedExpenseCategoryEnabled));
90
+ }
91
+ }
92
+ });
93
+ }
50
94
  return (0, rxjs_1.from)(updateActions);
51
95
  }));
52
96
  exports.fetchTransactionCategorizationViewEpic = fetchTransactionCategorizationViewEpic;
@@ -19,6 +19,13 @@ export interface TransactionCategorizationQueryPayload {
19
19
  * on the backend.
20
20
  */
21
21
  sub_tab: CompletedSubTab;
22
+ /**
23
+ * When `false`, the backend scopes the search to the active tab's
24
+ * categorization status (respects `auto_categorized`). When `true` or
25
+ * omitted, the backend searches across all statuses — the legacy behaviour
26
+ * that the Receipts manual-match flow relies on.
27
+ */
28
+ search_all_statuses?: boolean;
22
29
  }
23
30
  export interface TransactionCategorizationPayload {
24
31
  next_page_token: string | null;
@@ -137,7 +137,10 @@ export declare const fetchTransactionCategorization: import("@reduxjs/toolkit").
137
137
  }, "expenseAutomationTransactionsView/fetchTransactionCategorizationSuccess">, resetOtherTabsFetchState: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
138
138
  tabs: TransactionsTab[];
139
139
  refreshViewInBackground?: boolean;
140
- }, "expenseAutomationTransactionsView/resetOtherTabsFetchState">, fetchTransactionCategorizationView: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", period: TimePeriod, cacheOverride?: any, keepExistingListItems?: any, searchString?: string | undefined, pageToken?: PageToken | undefined, refreshViewInBackground?: any, resetListItems?: any, isUncategorizedExpenseCategoryEnabled?: boolean | undefined], {
140
+ }, "expenseAutomationTransactionsView/resetOtherTabsFetchState">, restoreFullDatasetSnapshot: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
141
+ period: TimePeriod;
142
+ selectedTab: TransactionsTab;
143
+ }, "expenseAutomationTransactionsView/restoreFullDatasetSnapshot">, fetchTransactionCategorizationView: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[selectedTab: "review" | "autoCategorized", period: TimePeriod, cacheOverride?: any, keepExistingListItems?: any, searchString?: string | undefined, pageToken?: PageToken | undefined, refreshViewInBackground?: any, resetListItems?: any, isUncategorizedExpenseCategoryEnabled?: boolean | undefined], {
141
144
  selectedTab: "review" | "autoCategorized";
142
145
  period: TimePeriod;
143
146
  searchString: string | undefined;
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  var _a;
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.uploadTransactionCategorizationReceiptSuccess = exports.updateTransactionCategorizationUploadReceiptState = exports.removeTransactionFromAllTabs = exports.backgroundRefetchReviewTab = exports.syncTransactionCategorizationFromDetailSave = exports.updateSelectedTransactionId = exports.markCategoryClassRecommendationsFailureForCategorization = exports.markCategoryClassRecommendationsCompletedForCategorization = exports.markCategoryClassRecommendationsInProgressForCategorization = exports.setEntityRecommendationForLineIdsForCategorization = exports.clearExpenseAutomationTransactionsView = exports.clearExpenseAutomationTransactionsViewPerTabView = exports.updateSelectedCheckboxTransactionIds = exports.fetchTransactionCategorizationView = exports.resetOtherTabsFetchState = exports.fetchTransactionCategorizationSuccess = exports.updateParentTotalCountForTab = exports.updateTotalCountForTransactionCategorization = exports.setAllItemsToCategoryClassInLocalDataForCategorization = exports.updateSelectedCustomerForTransaction = exports.updateSelectedVendorForTransaction = exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = exports.markTransactionAsNotMiscategorized = exports.updateTransactionCategorizationSaveStatus = exports.updateTransactionCategorizationCompletedSubTab = exports.updateCurrentSelectedTransactionCategorizationTab = exports.updateTransactionCategorization = exports.saveTransactionCategorization = exports.fetchTransactionCategorizationFailure = exports.saveTransactionCategorizationLocalData = exports.initializeTransactionCategorizationViewLocalData = exports.updateTransactionFilters = exports.updateTransactionCategorizationUIState = exports.fetchTransactionCategorization = exports.initialState = exports.initialTransactionTabViewState = void 0;
7
+ exports.uploadTransactionCategorizationReceiptSuccess = exports.updateTransactionCategorizationUploadReceiptState = exports.removeTransactionFromAllTabs = exports.backgroundRefetchReviewTab = exports.syncTransactionCategorizationFromDetailSave = exports.updateSelectedTransactionId = exports.markCategoryClassRecommendationsFailureForCategorization = exports.markCategoryClassRecommendationsCompletedForCategorization = exports.markCategoryClassRecommendationsInProgressForCategorization = exports.setEntityRecommendationForLineIdsForCategorization = exports.clearExpenseAutomationTransactionsView = exports.clearExpenseAutomationTransactionsViewPerTabView = exports.updateSelectedCheckboxTransactionIds = exports.fetchTransactionCategorizationView = exports.restoreFullDatasetSnapshot = exports.resetOtherTabsFetchState = exports.fetchTransactionCategorizationSuccess = exports.updateParentTotalCountForTab = exports.updateTotalCountForTransactionCategorization = exports.setAllItemsToCategoryClassInLocalDataForCategorization = exports.updateSelectedCustomerForTransaction = exports.updateSelectedVendorForTransaction = exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = exports.markTransactionAsNotMiscategorized = exports.updateTransactionCategorizationSaveStatus = exports.updateTransactionCategorizationCompletedSubTab = exports.updateCurrentSelectedTransactionCategorizationTab = exports.updateTransactionCategorization = exports.saveTransactionCategorization = exports.fetchTransactionCategorizationFailure = exports.saveTransactionCategorizationLocalData = exports.initializeTransactionCategorizationViewLocalData = exports.updateTransactionFilters = exports.updateTransactionCategorizationUIState = exports.fetchTransactionCategorization = exports.initialState = exports.initialTransactionTabViewState = void 0;
8
8
  const toolkit_1 = require("@reduxjs/toolkit");
9
9
  const get_1 = __importDefault(require("lodash/get"));
10
10
  const uniq_1 = __importDefault(require("lodash/uniq"));
@@ -45,6 +45,7 @@ exports.initialTransactionTabViewState = {
45
45
  hasValidState() {
46
46
  return this.fetchState == 'Completed';
47
47
  },
48
+ fullDatasetSnapshot: undefined,
48
49
  transactionReviewLocalDataById: {},
49
50
  transactionIdsBySelectedPeriod: {},
50
51
  selectedCheckBoxTransactionIds: [],
@@ -160,27 +161,62 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
160
161
  draft.transactionCategorizationView[selectedTab].fetchState =
161
162
  'In-Progress';
162
163
  draft.transactionCategorizationView[selectedTab].error = undefined;
164
+ // A foreground fetch supersedes any in-flight background preload.
165
+ // Reset refreshStatus so a cancelled preload doesn't leave the
166
+ // spinner stuck at In-Progress.
167
+ draft.transactionCategorizationView[selectedTab].refreshStatus = {
168
+ fetchState: 'Not-Started',
169
+ error: undefined,
170
+ };
163
171
  }
164
172
  draft.transactionCategorizationView[selectedTab].uiState.pageToken =
165
173
  pageToken ?? null;
166
- draft.transactionCategorizationView[selectedTab].uiState.searchString =
167
- searchString ?? '';
174
+ // Only overwrite when explicitly set; undefined means a non-search fetch
175
+ // (tab switch, period change) should preserve the existing search string.
176
+ if (searchString !== undefined) {
177
+ draft.transactionCategorizationView[selectedTab].uiState.searchString =
178
+ searchString;
179
+ }
168
180
  draft.transactionCategorizationView[selectedTab].error = undefined;
181
+ const monthYearPeriod = {
182
+ month: period.start.month,
183
+ year: period.start.year,
184
+ };
185
+ const periodId = (0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod);
186
+ const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
187
+ .transactionIdsBySelectedPeriod[periodId] ?? [];
188
+ // Snapshot before any list mutation so clearing the search restores
189
+ // instantly without an API round-trip — for BOTH active and synced tabs.
190
+ // Active-tab search dispatches resetListItems=false, so this must live
191
+ // outside the resetListItems block. fullDatasetSnapshot == null prevents
192
+ // overwriting the original snapshot on search refinement ('ama'→'amazon').
193
+ if (searchString !== undefined &&
194
+ searchString !== '' &&
195
+ transactionIdsBySelectedPeriod.length > 0 &&
196
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot ==
197
+ null) {
198
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
199
+ {
200
+ parentTotalCount: draft.parentTotalCountByTab[selectedTab][periodId],
201
+ periodId,
202
+ totalCount: draft.transactionCategorizationView[selectedTab].uiState
203
+ .totalCount[periodId],
204
+ transactionIds: [...transactionIdsBySelectedPeriod],
205
+ transactionReviewLocalDataById: {
206
+ ...draft.transactionCategorizationView[selectedTab]
207
+ .transactionReviewLocalDataById,
208
+ },
209
+ };
210
+ }
169
211
  // Reset list when we change the sort order and fetch list again
170
212
  if (resetListItems === true) {
171
- const monthYearPeriod = {
172
- month: period.start.month,
173
- year: period.start.year,
174
- };
175
- const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
176
- .transactionIdsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod)];
177
213
  transactionIdsBySelectedPeriod.forEach((transactionId) => {
178
214
  delete draft.transactionCategorizationView[selectedTab]
179
215
  .transactionReviewLocalDataById[transactionId];
180
216
  });
181
217
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds =
182
218
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds.filter((selectedCheckBoxTransactionId) => transactionIdsBySelectedPeriod.includes(selectedCheckBoxTransactionId));
183
- draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod)] = [];
219
+ draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[periodId] = [];
184
220
  draft.transactionCategorizationView[selectedTab].refreshStatus = {
185
221
  fetchState: 'Not-Started',
186
222
  error: undefined,
@@ -312,10 +348,12 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
312
348
  }
313
349
  },
314
350
  updateTransactionFilters(draft, action) {
315
- const { selectedTab, filters } = action.payload;
316
- // `filters` is non-nullable per the payload type, so no runtime
317
- // null-check is needed dropping it removes a dead defensive branch.
318
- draft.transactionCategorizationView[selectedTab].filters = filters;
351
+ const { filters } = action.payload;
352
+ // Filters are tab-agnostic (payee/category/class/amount), so apply to
353
+ // all tabs so switching tabs after filtering shows consistent results.
354
+ transactionsViewState_1.TRANSACTIONS_TABS.forEach((tab) => {
355
+ draft.transactionCategorizationView[tab].filters = filters;
356
+ });
319
357
  },
320
358
  saveTransactionCategorization: {
321
359
  prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
@@ -667,19 +705,66 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
667
705
  resetOtherTabsFetchState(draft, action) {
668
706
  const { tabs, refreshViewInBackground } = action.payload;
669
707
  tabs.forEach((tab) => {
670
- if (draft.transactionCategorizationView[tab].fetchState ===
671
- 'In-Progress' ||
672
- refreshViewInBackground !== true) {
708
+ const tabFetchState = draft.transactionCategorizationView[tab].fetchState;
709
+ if (refreshViewInBackground !== true) {
710
+ // Foreground fetch completed: mark other tabs as Completed.
673
711
  draft.transactionCategorizationView[tab].fetchState = 'Completed';
674
712
  draft.transactionCategorizationView[tab].error = undefined;
675
713
  }
676
- else {
714
+ else if (tabFetchState !== 'In-Progress') {
715
+ // Background fetch completed: reset the other tab's refreshStatus,
716
+ // but only when it is not mid-load. During a dual-search the active
717
+ // tab has its own foreground fetch in flight — clobbering its
718
+ // fetchState here would drop the skeleton and show an empty list.
677
719
  draft.transactionCategorizationView[tab].refreshStatus = {
678
720
  fetchState: 'Not-Started',
679
721
  error: undefined,
680
722
  };
681
723
  }
724
+ // If tabFetchState === 'In-Progress' and refreshViewInBackground === true,
725
+ // leave the other tab's state untouched — it has its own active fetch.
726
+ });
727
+ },
728
+ restoreFullDatasetSnapshot(draft, action) {
729
+ const { selectedTab, period } = action.payload;
730
+ const periodId = (0, timePeriod_1.toMonthYearPeriodId)({
731
+ month: period.start.month,
732
+ year: period.start.year,
733
+ });
734
+ const snapshot = draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot;
735
+ if (snapshot == null || snapshot.periodId !== periodId) {
736
+ return;
737
+ }
738
+ draft.transactionCategorizationView[selectedTab]
739
+ .transactionIdsBySelectedPeriod[periodId] = snapshot.transactionIds;
740
+ // Merge back per-transaction local data; skip entries that were updated
741
+ // during the search (e.g. a save that happened while filtered results
742
+ // were showing) so those saves are not overwritten.
743
+ Object.entries(snapshot.transactionReviewLocalDataById).forEach(([transactionId, localData]) => {
744
+ if (draft.transactionCategorizationView[selectedTab]
745
+ .transactionReviewLocalDataById[transactionId] == null) {
746
+ draft.transactionCategorizationView[selectedTab]
747
+ .transactionReviewLocalDataById[transactionId] = localData;
748
+ }
682
749
  });
750
+ if (snapshot.totalCount != null) {
751
+ draft.transactionCategorizationView[selectedTab].uiState.totalCount[periodId] = snapshot.totalCount;
752
+ }
753
+ if (snapshot.parentTotalCount != null) {
754
+ draft.parentTotalCountByTab[selectedTab][periodId] =
755
+ snapshot.parentTotalCount;
756
+ }
757
+ draft.transactionCategorizationView[selectedTab].uiState.searchString =
758
+ '';
759
+ draft.transactionCategorizationView[selectedTab].uiState.pageToken = null;
760
+ draft.transactionCategorizationView[selectedTab].fetchState = 'Completed';
761
+ draft.transactionCategorizationView[selectedTab].refreshStatus = {
762
+ fetchState: 'Not-Started',
763
+ error: undefined,
764
+ };
765
+ draft.transactionCategorizationView[selectedTab].error = undefined;
766
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
767
+ undefined;
683
768
  },
684
769
  fetchTransactionCategorizationFailure(draft, action) {
685
770
  const { selectedTab, selectedPeriod } = action.payload;
@@ -1009,6 +1094,7 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
1009
1094
  if (allReviewed) {
1010
1095
  if (existsInReview) {
1011
1096
  (0, transactionCategorizationLocalDataHelper_1.removeTransactionFromTabView)(reviewTabView, txnId);
1097
+ reviewTabView.fullDatasetSnapshot = undefined;
1012
1098
  }
1013
1099
  return;
1014
1100
  }
@@ -1055,11 +1141,13 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
1055
1141
  reducer(draft, action) {
1056
1142
  const { transactionId } = action.payload;
1057
1143
  for (const tab of transactionsViewState_1.TRANSACTIONS_TABS) {
1058
- (0, transactionCategorizationLocalDataHelper_1.removeTransactionFromTabView)(draft.transactionCategorizationView[tab], transactionId);
1144
+ const tabView = draft.transactionCategorizationView[tab];
1145
+ (0, transactionCategorizationLocalDataHelper_1.removeTransactionFromTabView)(tabView, transactionId);
1146
+ tabView.fullDatasetSnapshot = undefined;
1059
1147
  }
1060
1148
  },
1061
1149
  },
1062
1150
  },
1063
1151
  });
1064
- _a = expenseAutomationTransactionsView.actions, exports.fetchTransactionCategorization = _a.fetchTransactionCategorization, exports.updateTransactionCategorizationUIState = _a.updateTransactionCategorizationUIState, exports.updateTransactionFilters = _a.updateTransactionFilters, exports.initializeTransactionCategorizationViewLocalData = _a.initializeTransactionCategorizationViewLocalData, exports.saveTransactionCategorizationLocalData = _a.saveTransactionCategorizationLocalData, exports.fetchTransactionCategorizationFailure = _a.fetchTransactionCategorizationFailure, exports.saveTransactionCategorization = _a.saveTransactionCategorization, exports.updateTransactionCategorization = _a.updateTransactionCategorization, exports.updateCurrentSelectedTransactionCategorizationTab = _a.updateCurrentSelectedTransactionCategorizationTab, exports.updateTransactionCategorizationCompletedSubTab = _a.updateTransactionCategorizationCompletedSubTab, exports.updateTransactionCategorizationSaveStatus = _a.updateTransactionCategorizationSaveStatus, exports.markTransactionAsNotMiscategorized = _a.markTransactionAsNotMiscategorized, exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = _a.updateStatusForTransactionNotMiscategorizedUpdateForCategorization, exports.updateSelectedVendorForTransaction = _a.updateSelectedVendorForTransaction, exports.updateSelectedCustomerForTransaction = _a.updateSelectedCustomerForTransaction, exports.setAllItemsToCategoryClassInLocalDataForCategorization = _a.setAllItemsToCategoryClassInLocalDataForCategorization, exports.updateTotalCountForTransactionCategorization = _a.updateTotalCountForTransactionCategorization, exports.updateParentTotalCountForTab = _a.updateParentTotalCountForTab, exports.fetchTransactionCategorizationSuccess = _a.fetchTransactionCategorizationSuccess, exports.resetOtherTabsFetchState = _a.resetOtherTabsFetchState, exports.fetchTransactionCategorizationView = _a.fetchTransactionCategorizationView, exports.updateSelectedCheckboxTransactionIds = _a.updateSelectedCheckboxTransactionIds, exports.clearExpenseAutomationTransactionsViewPerTabView = _a.clearExpenseAutomationTransactionsViewPerTabView, exports.clearExpenseAutomationTransactionsView = _a.clearExpenseAutomationTransactionsView, exports.setEntityRecommendationForLineIdsForCategorization = _a.setEntityRecommendationForLineIdsForCategorization, exports.markCategoryClassRecommendationsInProgressForCategorization = _a.markCategoryClassRecommendationsInProgressForCategorization, exports.markCategoryClassRecommendationsCompletedForCategorization = _a.markCategoryClassRecommendationsCompletedForCategorization, exports.markCategoryClassRecommendationsFailureForCategorization = _a.markCategoryClassRecommendationsFailureForCategorization, exports.updateSelectedTransactionId = _a.updateSelectedTransactionId, exports.syncTransactionCategorizationFromDetailSave = _a.syncTransactionCategorizationFromDetailSave, exports.backgroundRefetchReviewTab = _a.backgroundRefetchReviewTab, exports.removeTransactionFromAllTabs = _a.removeTransactionFromAllTabs, exports.updateTransactionCategorizationUploadReceiptState = _a.updateTransactionCategorizationUploadReceiptState, exports.uploadTransactionCategorizationReceiptSuccess = _a.uploadTransactionCategorizationReceiptSuccess;
1152
+ _a = expenseAutomationTransactionsView.actions, exports.fetchTransactionCategorization = _a.fetchTransactionCategorization, exports.updateTransactionCategorizationUIState = _a.updateTransactionCategorizationUIState, exports.updateTransactionFilters = _a.updateTransactionFilters, exports.initializeTransactionCategorizationViewLocalData = _a.initializeTransactionCategorizationViewLocalData, exports.saveTransactionCategorizationLocalData = _a.saveTransactionCategorizationLocalData, exports.fetchTransactionCategorizationFailure = _a.fetchTransactionCategorizationFailure, exports.saveTransactionCategorization = _a.saveTransactionCategorization, exports.updateTransactionCategorization = _a.updateTransactionCategorization, exports.updateCurrentSelectedTransactionCategorizationTab = _a.updateCurrentSelectedTransactionCategorizationTab, exports.updateTransactionCategorizationCompletedSubTab = _a.updateTransactionCategorizationCompletedSubTab, exports.updateTransactionCategorizationSaveStatus = _a.updateTransactionCategorizationSaveStatus, exports.markTransactionAsNotMiscategorized = _a.markTransactionAsNotMiscategorized, exports.updateStatusForTransactionNotMiscategorizedUpdateForCategorization = _a.updateStatusForTransactionNotMiscategorizedUpdateForCategorization, exports.updateSelectedVendorForTransaction = _a.updateSelectedVendorForTransaction, exports.updateSelectedCustomerForTransaction = _a.updateSelectedCustomerForTransaction, exports.setAllItemsToCategoryClassInLocalDataForCategorization = _a.setAllItemsToCategoryClassInLocalDataForCategorization, exports.updateTotalCountForTransactionCategorization = _a.updateTotalCountForTransactionCategorization, exports.updateParentTotalCountForTab = _a.updateParentTotalCountForTab, exports.fetchTransactionCategorizationSuccess = _a.fetchTransactionCategorizationSuccess, exports.resetOtherTabsFetchState = _a.resetOtherTabsFetchState, exports.restoreFullDatasetSnapshot = _a.restoreFullDatasetSnapshot, exports.fetchTransactionCategorizationView = _a.fetchTransactionCategorizationView, exports.updateSelectedCheckboxTransactionIds = _a.updateSelectedCheckboxTransactionIds, exports.clearExpenseAutomationTransactionsViewPerTabView = _a.clearExpenseAutomationTransactionsViewPerTabView, exports.clearExpenseAutomationTransactionsView = _a.clearExpenseAutomationTransactionsView, exports.setEntityRecommendationForLineIdsForCategorization = _a.setEntityRecommendationForLineIdsForCategorization, exports.markCategoryClassRecommendationsInProgressForCategorization = _a.markCategoryClassRecommendationsInProgressForCategorization, exports.markCategoryClassRecommendationsCompletedForCategorization = _a.markCategoryClassRecommendationsCompletedForCategorization, exports.markCategoryClassRecommendationsFailureForCategorization = _a.markCategoryClassRecommendationsFailureForCategorization, exports.updateSelectedTransactionId = _a.updateSelectedTransactionId, exports.syncTransactionCategorizationFromDetailSave = _a.syncTransactionCategorizationFromDetailSave, exports.backgroundRefetchReviewTab = _a.backgroundRefetchReviewTab, exports.removeTransactionFromAllTabs = _a.removeTransactionFromAllTabs, exports.updateTransactionCategorizationUploadReceiptState = _a.updateTransactionCategorizationUploadReceiptState, exports.uploadTransactionCategorizationReceiptSuccess = _a.uploadTransactionCategorizationReceiptSuccess;
1065
1153
  exports.default = expenseAutomationTransactionsView.reducer;
@@ -94,8 +94,24 @@ export interface SupportedTransactionCategorization {
94
94
  transactionId?: ID;
95
95
  }
96
96
  export declare const initialSupportedTransactionCategorization: SupportedTransactionCategorization;
97
+ /**
98
+ * Snapshot of a tab's full (pre-search) dataset taken the moment the user
99
+ * first applies a non-empty search string. When the search is cleared the
100
+ * view epic restores from here instead of issuing a new API request.
101
+ *
102
+ * Only saved when there are actual rows to restore (`transactionIds` is
103
+ * non-empty); cleared once consumed or replaced by a fresh search.
104
+ */
105
+ export interface FullDatasetSnapshot {
106
+ parentTotalCount: number | undefined;
107
+ periodId: MonthYearPeriodId;
108
+ totalCount: number | undefined;
109
+ transactionIds: ID[];
110
+ transactionReviewLocalDataById: Record<ID | TransactionDetailKey, SupportedTransactionCategorization>;
111
+ }
97
112
  export interface TransactionsTabViewState extends FetchedState {
98
113
  filters: TransactionFilters;
114
+ fullDatasetSnapshot: FullDatasetSnapshot | undefined;
99
115
  markAsNotMiscategorizedStatus: FetchStateAndError;
100
116
  refreshStatus: FetchStateAndError;
101
117
  saveStatus: FetchStateAndError;
@@ -115,7 +115,11 @@ const setupView = (0, toolkit_1.createSlice)({
115
115
  saveSetupViewDataInLocalStore(draft, action) {
116
116
  const setupViewLocalData = action.payload;
117
117
  if (setupViewLocalData.companyDetails != null) {
118
- draft.companyDetails.localData = setupViewLocalData.companyDetails;
118
+ const syncToken = setupViewLocalData.companyDetails.syncToken ?? draft.companyDetails.localData?.syncToken;
119
+ draft.companyDetails.localData = {
120
+ ...setupViewLocalData.companyDetails,
121
+ ...(syncToken != null ? { syncToken } : {}),
122
+ };
119
123
  }
120
124
  if (setupViewLocalData.primaryContactDetails != null) {
121
125
  draft.primaryContactDetails.localData =
@@ -34,7 +34,6 @@ const initializeTaskToLocalStoreEpic = (actions$, state$) => actions$.pipe((0, o
34
34
  recurringStartDate: task.recurringStartDate,
35
35
  timeSpent: task.timeSpent,
36
36
  isPrivate: task.isPrivate,
37
- taskGroupId: task.taskGroupIds[0],
38
37
  };
39
38
  const actions = [
40
39
  (0, taskDetailReducer_1.saveTaskUpdatesToLocalStore)({ taskDetailLocalData, taskId }),
@@ -15,17 +15,9 @@ const saveTaskDetailEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, oper
15
15
  const { taskId, taskGroupId } = action.payload;
16
16
  const state = state$.value;
17
17
  const isTaskListFetched = state.taskListState.fetchState === 'Completed';
18
- // For an edit, prefer the locally-edited group (Bug 12). If absent,
19
- // fall back to the entity's current group. For a create, use the
20
- // taskGroupId from the action payload.
21
- const localTaskGroupId = taskId != null
22
- ? state.taskDetailState.editTaskStateById[taskId]?.taskDetailLocalData
23
- .taskGroupId
24
- : undefined;
25
18
  const updatedTaskGroupId = taskId == null
26
19
  ? taskGroupId
27
- : (localTaskGroupId ??
28
- (0, taskSelector_1.getTaskById)(state.taskState, taskId)?.taskGroupIds[0]);
20
+ : (0, taskSelector_1.getTaskById)(state.taskState, taskId)?.taskGroupIds[0];
29
21
  const payload = prepareTaskPayload(state, taskId, updatedTaskGroupId);
30
22
  const saveTaskApi = taskId != null
31
23
  ? zeniAPI.putAndGetJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/tasks/${taskId}`, payload)
@@ -40,7 +40,6 @@ export interface EditTaskLocalData {
40
40
  recurringStartDate?: ZeniDate;
41
41
  savedRecurringEndDate?: ZeniDate;
42
42
  savedRecurringTasksCount?: number;
43
- taskGroupId?: ID;
44
43
  }
45
44
  export declare const initialTaskDetailLocalData: EditTaskLocalData;
46
45
  export declare const initialTaskDetail: EditTaskDetail;
@@ -158,8 +158,7 @@ const showFormFooter = (taskDetailLocalData, taskDetailInStore) => {
158
158
  !(0, isEqual_1.default)(taskDetailLocalData.recurringFrequency, taskDetailInStore.recurringFrequency) ||
159
159
  !(0, isEqual_1.default)(taskDetailLocalData.recurringDaysOfWeek, taskDetailInStore.recurringDaysOfWeek) ||
160
160
  !(0, isEqual_1.default)(taskDetailLocalData.recurringStartDate, taskDetailInStore.recurringStartDate) ||
161
- !(0, isEqual_1.default)(taskDetailLocalData.timeSpent, taskDetailInStore.timeSpent) ||
162
- !(0, isEqual_1.default)(taskDetailLocalData.taskGroupId, taskDetailInStore.taskGroupIds[0])) {
161
+ !(0, isEqual_1.default)(taskDetailLocalData.timeSpent, taskDetailInStore.timeSpent)) {
163
162
  return true;
164
163
  }
165
164
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.1.20-betaDI2",
3
+ "version": "5.1.20-betaRD2",
4
4
  "description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib/esm/index.js",
@@ -1,44 +0,0 @@
1
- import { createAction } from '@reduxjs/toolkit';
2
- import { EMPTY, of } from 'rxjs';
3
- import { catchError, filter, mergeMap } from 'rxjs/operators';
4
- import { isSuccessResponse } from '../../../../responsePayload';
5
- /**
6
- * Fire-and-forget action — dispatched by the task-comments UI when a comment
7
- * is created with @-mentions. The epic POSTs to task-manager's
8
- * `/comment-mention-notification` endpoint which fans out Slack DMs to each
9
- * mentioned internal user.
10
- *
11
- * Result is intentionally not captured in redux state: comment creation
12
- * itself is owned by Liveblocks, and a network failure on the notification
13
- * should not surface in the UI (the comment is still persisted in Liveblocks).
14
- */
15
- export const sendCommentMentionNotification = createAction('taskDetailView/sendCommentMentionNotification', (taskId, mentionedUserIds, performedById) => ({
16
- payload: { taskId, mentionedUserIds, performedById },
17
- }));
18
- export const sendCommentMentionNotificationEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(sendCommentMentionNotification.match), mergeMap((action) => handleSendCommentMentionNotification(action.payload, zeniAPI)));
19
- const handleSendCommentMentionNotification = ({ taskId, mentionedUserIds, performedById, }, zeniAPI) => {
20
- // Short-circuit when there's nothing to send. Avoids a useless round-trip
21
- // (server would also short-circuit with a 200/no-op, but we may as well
22
- // not enqueue the request at all).
23
- if (mentionedUserIds.length === 0) {
24
- return EMPTY;
25
- }
26
- const url = `${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/tasks/${taskId}/comment-mention-notification`;
27
- const request$ = zeniAPI.postAndGetJSON(url, {
28
- mentioned_user_ids: mentionedUserIds,
29
- performed_by_id: performedById,
30
- });
31
- return request$.pipe(mergeMap((response) => {
32
- if (!isSuccessResponse(response)) {
33
- // Non-success response — log to console but do not propagate to state.
34
- // Liveblocks still has the comment; Slack fan-out is best-effort.
35
- // eslint-disable-next-line no-console
36
- console.warn('sendCommentMentionNotification non-success response', response);
37
- }
38
- return EMPTY;
39
- }), catchError((error) => {
40
- // eslint-disable-next-line no-console
41
- console.warn('sendCommentMentionNotification error', error);
42
- return of();
43
- }));
44
- };
@@ -1,20 +0,0 @@
1
- import { ActionsObservable, StateObservable } from 'redux-observable';
2
- import { RootState } from '../../../../reducer';
3
- import { ZeniAPI } from '../../../../zeniAPI';
4
- /**
5
- * Fire-and-forget action — dispatched by the task-comments UI when a comment
6
- * is created with @-mentions. The epic POSTs to task-manager's
7
- * `/comment-mention-notification` endpoint which fans out Slack DMs to each
8
- * mentioned internal user.
9
- *
10
- * Result is intentionally not captured in redux state: comment creation
11
- * itself is owned by Liveblocks, and a network failure on the notification
12
- * should not surface in the UI (the comment is still persisted in Liveblocks).
13
- */
14
- export declare const sendCommentMentionNotification: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[taskId: string, mentionedUserIds: string[], performedById: string], {
15
- taskId: string;
16
- mentionedUserIds: string[];
17
- performedById: string;
18
- }, "taskDetailView/sendCommentMentionNotification", never, never>;
19
- export type ActionType = ReturnType<typeof sendCommentMentionNotification>;
20
- export declare const sendCommentMentionNotificationEpic: (actions$: ActionsObservable<ActionType>, _state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => import("rxjs").Observable<unknown>;
@@ -1,48 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sendCommentMentionNotificationEpic = exports.sendCommentMentionNotification = void 0;
4
- const toolkit_1 = require("@reduxjs/toolkit");
5
- const rxjs_1 = require("rxjs");
6
- const operators_1 = require("rxjs/operators");
7
- const responsePayload_1 = require("../../../../responsePayload");
8
- /**
9
- * Fire-and-forget action — dispatched by the task-comments UI when a comment
10
- * is created with @-mentions. The epic POSTs to task-manager's
11
- * `/comment-mention-notification` endpoint which fans out Slack DMs to each
12
- * mentioned internal user.
13
- *
14
- * Result is intentionally not captured in redux state: comment creation
15
- * itself is owned by Liveblocks, and a network failure on the notification
16
- * should not surface in the UI (the comment is still persisted in Liveblocks).
17
- */
18
- exports.sendCommentMentionNotification = (0, toolkit_1.createAction)('taskDetailView/sendCommentMentionNotification', (taskId, mentionedUserIds, performedById) => ({
19
- payload: { taskId, mentionedUserIds, performedById },
20
- }));
21
- const sendCommentMentionNotificationEpic = (actions$, _state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(exports.sendCommentMentionNotification.match), (0, operators_1.mergeMap)((action) => handleSendCommentMentionNotification(action.payload, zeniAPI)));
22
- exports.sendCommentMentionNotificationEpic = sendCommentMentionNotificationEpic;
23
- const handleSendCommentMentionNotification = ({ taskId, mentionedUserIds, performedById, }, zeniAPI) => {
24
- // Short-circuit when there's nothing to send. Avoids a useless round-trip
25
- // (server would also short-circuit with a 200/no-op, but we may as well
26
- // not enqueue the request at all).
27
- if (mentionedUserIds.length === 0) {
28
- return rxjs_1.EMPTY;
29
- }
30
- const url = `${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/tasks/${taskId}/comment-mention-notification`;
31
- const request$ = zeniAPI.postAndGetJSON(url, {
32
- mentioned_user_ids: mentionedUserIds,
33
- performed_by_id: performedById,
34
- });
35
- return request$.pipe((0, operators_1.mergeMap)((response) => {
36
- if (!(0, responsePayload_1.isSuccessResponse)(response)) {
37
- // Non-success response — log to console but do not propagate to state.
38
- // Liveblocks still has the comment; Slack fan-out is best-effort.
39
- // eslint-disable-next-line no-console
40
- console.warn('sendCommentMentionNotification non-success response', response);
41
- }
42
- return rxjs_1.EMPTY;
43
- }), (0, operators_1.catchError)((error) => {
44
- // eslint-disable-next-line no-console
45
- console.warn('sendCommentMentionNotification error', error);
46
- return (0, rxjs_1.of)();
47
- }));
48
- };