@zeniai/client-epic-state 5.1.16-betaRD4 → 5.1.16-betaRD6

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.
@@ -18,11 +18,6 @@ groupBy((action) => action.payload.selectedTab), mergeMap((group$) => group$.pip
18
18
  const { pageToken, sortOrder, sortKey } = uiState;
19
19
  const currentTenant = getCurrentTenant(state$.value);
20
20
  const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
21
- console.log('[EA-SEARCH] fetchTransactionCategorizationEpic HTTP call', {
22
- selectedTab,
23
- refreshViewInBackground,
24
- search_text: uiState.searchString,
25
- });
26
21
  const queryParam = {
27
22
  start_date: toString(period.start),
28
23
  end_date: toString(period.end),
@@ -6,25 +6,25 @@ import { fetchAccountList } from '../../../accountList/accountListReducer';
6
6
  import { fetchClassList } from '../../../classList/classListReducer';
7
7
  import { fetchOwnerList } from '../../../ownerList/ownerListReducer';
8
8
  import { fetchProjectList } from '../../../projectList/projectListReducer';
9
- import { fetchTransactionCategorization, fetchTransactionCategorizationView, } from '../../reducers/transactionsViewReducer';
10
- import { TRANSACTIONS_TABS } from '../../types/transactionsViewState';
9
+ import { fetchTransactionCategorization, fetchTransactionCategorizationView, restoreFullDatasetSnapshot, } from '../../reducers/transactionsViewReducer';
10
+ import { TRANSACTIONS_TABS, } from '../../types/transactionsViewState';
11
11
  export const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pipe(filter(fetchTransactionCategorizationView.match), mergeMap((action) => {
12
12
  const { selectedTab, cacheOverride, keepExistingListItems, pageToken, period, refreshViewInBackground, searchString, resetListItems, isUncategorizedExpenseCategoryEnabled, } = action.payload;
13
13
  const updateActions = [];
14
14
  const { expenseAutomationTransactionsViewState } = state$.value;
15
15
  const { fetchState, transactionIdsBySelectedPeriod } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTab];
16
- console.log('[EA-SEARCH] fetchTransactionCategorizationViewEpic received', {
17
- selectedTab,
18
- cacheOverride,
19
- searchString,
20
- fetchState,
21
- });
22
16
  const monthYearPeriod = {
23
17
  month: period.start.month,
24
18
  year: period.start.year,
25
19
  };
26
- const transactionIds = transactionIdsBySelectedPeriod[toMonthYearPeriodId(monthYearPeriod)] ??
27
- [];
20
+ const periodId = toMonthYearPeriodId(monthYearPeriod);
21
+ const transactionIds = transactionIdsBySelectedPeriod[periodId] ?? [];
22
+ const hasUsableSnapshot = (tabKey) => {
23
+ const snapshot = expenseAutomationTransactionsViewState.transactionCategorizationView[tabKey].fullDatasetSnapshot;
24
+ return (snapshot != null &&
25
+ snapshot.periodId === periodId &&
26
+ snapshot.transactionIds.length > 0);
27
+ };
28
28
  const accountList = state$.value.accountListState.byReportId['accountList'];
29
29
  if (accountList.hasValidState() === false &&
30
30
  accountList.fetchState === 'Not-Started') {
@@ -46,37 +46,45 @@ export const fetchTransactionCategorizationViewEpic = (actions$, state$) => acti
46
46
  vendorOwnerList.fetchState === 'Not-Started') {
47
47
  updateActions.push(fetchOwnerList());
48
48
  }
49
- console.log('[EA-SEARCH] view epic cache check', {
50
- selectedTab,
51
- cacheOverride,
52
- fetchState,
53
- transactionIdsBySelectedPeriod
54
- });
55
- if (cacheOverride === true ||
49
+ // When clearing search (searchString === ''), restore from the pre-search
50
+ // snapshot if one exists — avoids a round-trip since all data is already
51
+ // in the entity store. Fall back to a normal fetch if no snapshot (the
52
+ // tab was never loaded before the search was applied).
53
+ if (searchString === '' && hasUsableSnapshot(selectedTab)) {
54
+ updateActions.push(restoreFullDatasetSnapshot({ selectedTab, period }));
55
+ }
56
+ else if (cacheOverride === true ||
56
57
  fetchState === 'Not-Started' ||
57
58
  transactionIds.length === 0) {
58
- console.log('[EA-SEARCH] dispatching fetchTransactionCategorization for active tab', { selectedTab, searchString });
59
59
  updateActions.push(fetchTransactionCategorization(selectedTab, period, cacheOverride, keepExistingListItems, searchString, pageToken, refreshViewInBackground, resetListItems, isUncategorizedExpenseCategoryEnabled));
60
60
  }
61
+ // On the initial load of a tab (searchString undefined = non-search fetch),
62
+ // preload every other tab that hasn't been fetched yet for this period.
63
+ // This makes switching to the other tab instant instead of triggering a
64
+ // fresh fetch on click. refreshViewInBackground=true keeps it silent.
65
+ if (searchString === undefined) {
66
+ TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
67
+ const otherTabFetchState = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].fetchState;
68
+ if (otherTabFetchState === 'Not-Started') {
69
+ updateActions.push(fetchTransactionCategorization(otherTab, period, false, false, undefined, undefined, true, undefined, isUncategorizedExpenseCategoryEnabled));
70
+ }
71
+ });
72
+ }
61
73
  // When search changes (searchString defined, including empty string to
62
- // clear), also fetch every other tab so all tabs always reflect the same
74
+ // clear), also update every other tab so all tabs always reflect the same
63
75
  // search query. Skip if the other tab already has this search string to
64
- // avoid redundant requests on noop updates. cacheOverride=true bypasses
65
- // the cache check; pageToken reset to undefined so the other tab starts
66
- // from page 1; refreshViewInBackground=true because the user isn't
67
- // looking at the other tab, so a silent background fetch is correct.
76
+ // avoid redundant work on noop updates. On clear, restore from snapshot
77
+ // if available; otherwise issue a background fetch.
68
78
  if (searchString !== undefined) {
69
79
  TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
70
80
  const otherTabSearchString = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].uiState.searchString;
71
- console.log('[EA-SEARCH] dual-fetch check for other tab', {
72
- otherTab,
73
- searchString,
74
- otherTabSearchString,
75
- willDispatch: searchString !== otherTabSearchString,
76
- });
77
81
  if (searchString !== otherTabSearchString) {
78
- console.log('[EA-SEARCH] dispatching fetchTransactionCategorization for other tab', { otherTab, searchString });
79
- updateActions.push(fetchTransactionCategorization(otherTab, period, true, keepExistingListItems, searchString, undefined, true, true, isUncategorizedExpenseCategoryEnabled));
82
+ if (searchString === '' && hasUsableSnapshot(otherTab)) {
83
+ updateActions.push(restoreFullDatasetSnapshot({ selectedTab: otherTab, period }));
84
+ }
85
+ else {
86
+ updateActions.push(fetchTransactionCategorization(otherTab, period, true, keepExistingListItems, searchString, undefined, true, true, isUncategorizedExpenseCategoryEnabled));
87
+ }
80
88
  }
81
89
  });
82
90
  }
@@ -38,6 +38,7 @@ export const initialTransactionTabViewState = {
38
38
  hasValidState() {
39
39
  return this.fetchState == 'Completed';
40
40
  },
41
+ fullDatasetSnapshot: undefined,
41
42
  transactionReviewLocalDataById: {},
42
43
  transactionIdsBySelectedPeriod: {},
43
44
  selectedCheckBoxTransactionIds: [],
@@ -169,8 +170,30 @@ const expenseAutomationTransactionsView = createSlice({
169
170
  month: period.start.month,
170
171
  year: period.start.year,
171
172
  };
173
+ const periodId = toMonthYearPeriodId(monthYearPeriod);
172
174
  const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
173
- .transactionIdsBySelectedPeriod[toMonthYearPeriodId(monthYearPeriod)] ?? [];
175
+ .transactionIdsBySelectedPeriod[periodId] ?? [];
176
+ // When starting a non-empty search, snapshot the current full dataset
177
+ // so clearing the search can restore instantly without an API round-trip.
178
+ // Only save the snapshot when there are rows to restore; skip for tabs
179
+ // that have never been loaded (empty list → no useful snapshot).
180
+ if (searchString !== undefined &&
181
+ searchString !== '' &&
182
+ transactionIdsBySelectedPeriod.length > 0) {
183
+ const snapshot = {
184
+ parentTotalCount: draft.parentTotalCountByTab[selectedTab][periodId],
185
+ periodId,
186
+ totalCount: draft.transactionCategorizationView[selectedTab].uiState
187
+ .totalCount[periodId],
188
+ transactionIds: [...transactionIdsBySelectedPeriod],
189
+ transactionReviewLocalDataById: {
190
+ ...draft.transactionCategorizationView[selectedTab]
191
+ .transactionReviewLocalDataById,
192
+ },
193
+ };
194
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
195
+ snapshot;
196
+ }
174
197
  transactionIdsBySelectedPeriod.forEach((transactionId) => {
175
198
  delete draft.transactionCategorizationView[selectedTab]
176
199
  .transactionReviewLocalDataById[transactionId];
@@ -686,6 +709,43 @@ const expenseAutomationTransactionsView = createSlice({
686
709
  // leave the other tab's state untouched — it has its own active fetch.
687
710
  });
688
711
  },
712
+ restoreFullDatasetSnapshot(draft, action) {
713
+ const { selectedTab, period } = action.payload;
714
+ const periodId = toMonthYearPeriodId({
715
+ month: period.start.month,
716
+ year: period.start.year,
717
+ });
718
+ const snapshot = draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot;
719
+ if (snapshot == null || snapshot.periodId !== periodId) {
720
+ return;
721
+ }
722
+ draft.transactionCategorizationView[selectedTab]
723
+ .transactionIdsBySelectedPeriod[periodId] = snapshot.transactionIds;
724
+ // Merge back per-transaction local data; skip entries that were updated
725
+ // during the search (e.g. a save that happened while filtered results
726
+ // were showing) so those saves are not overwritten.
727
+ Object.entries(snapshot.transactionReviewLocalDataById).forEach(([transactionId, localData]) => {
728
+ if (draft.transactionCategorizationView[selectedTab]
729
+ .transactionReviewLocalDataById[transactionId] == null) {
730
+ draft.transactionCategorizationView[selectedTab]
731
+ .transactionReviewLocalDataById[transactionId] = localData;
732
+ }
733
+ });
734
+ if (snapshot.totalCount != null) {
735
+ draft.transactionCategorizationView[selectedTab].uiState.totalCount[periodId] = snapshot.totalCount;
736
+ }
737
+ if (snapshot.parentTotalCount != null) {
738
+ draft.parentTotalCountByTab[selectedTab][periodId] =
739
+ snapshot.parentTotalCount;
740
+ }
741
+ draft.transactionCategorizationView[selectedTab].uiState.searchString =
742
+ '';
743
+ draft.transactionCategorizationView[selectedTab].uiState.pageToken = null;
744
+ draft.transactionCategorizationView[selectedTab].fetchState = 'Completed';
745
+ draft.transactionCategorizationView[selectedTab].error = undefined;
746
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
747
+ undefined;
748
+ },
689
749
  fetchTransactionCategorizationFailure(draft, action) {
690
750
  const { selectedTab, selectedPeriod } = action.payload;
691
751
  draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[toMonthYearPeriodId(selectedPeriod)] =
@@ -1066,5 +1126,5 @@ const expenseAutomationTransactionsView = createSlice({
1066
1126
  },
1067
1127
  },
1068
1128
  });
1069
- export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, updateTransactionFilters, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, markCategoryClassRecommendationsInProgressForCategorization, markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, removeTransactionFromAllTabs, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
1129
+ export const { fetchTransactionCategorization, updateTransactionCategorizationUIState, updateTransactionFilters, initializeTransactionCategorizationViewLocalData, saveTransactionCategorizationLocalData, fetchTransactionCategorizationFailure, saveTransactionCategorization, updateTransactionCategorization, updateCurrentSelectedTransactionCategorizationTab, updateTransactionCategorizationCompletedSubTab, updateTransactionCategorizationSaveStatus, markTransactionAsNotMiscategorized, updateStatusForTransactionNotMiscategorizedUpdateForCategorization, updateSelectedVendorForTransaction, updateSelectedCustomerForTransaction, setAllItemsToCategoryClassInLocalDataForCategorization, updateTotalCountForTransactionCategorization, updateParentTotalCountForTab, fetchTransactionCategorizationSuccess, resetOtherTabsFetchState, restoreFullDatasetSnapshot, fetchTransactionCategorizationView, updateSelectedCheckboxTransactionIds, clearExpenseAutomationTransactionsViewPerTabView, clearExpenseAutomationTransactionsView, setEntityRecommendationForLineIdsForCategorization, markCategoryClassRecommendationsInProgressForCategorization, markCategoryClassRecommendationsCompletedForCategorization, markCategoryClassRecommendationsFailureForCategorization, updateSelectedTransactionId, syncTransactionCategorizationFromDetailSave, backgroundRefetchReviewTab, removeTransactionFromAllTabs, updateTransactionCategorizationUploadReceiptState, uploadTransactionCategorizationReceiptSuccess, } = expenseAutomationTransactionsView.actions;
1070
1130
  export default expenseAutomationTransactionsView.reducer;
@@ -21,11 +21,6 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
21
21
  const { pageToken, sortOrder, sortKey } = uiState;
22
22
  const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state$.value);
23
23
  const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
24
- console.log('[EA-SEARCH] fetchTransactionCategorizationEpic HTTP call', {
25
- selectedTab,
26
- refreshViewInBackground,
27
- search_text: uiState.searchString,
28
- });
29
24
  const queryParam = {
30
25
  start_date: (0, timePeriod_1.toString)(period.start),
31
26
  end_date: (0, timePeriod_1.toString)(period.end),
@@ -5,6 +5,6 @@ import { fetchAccountList } from '../../../accountList/accountListReducer';
5
5
  import { fetchClassList } from '../../../classList/classListReducer';
6
6
  import { fetchOwnerList } from '../../../ownerList/ownerListReducer';
7
7
  import { fetchProjectList } from '../../../projectList/projectListReducer';
8
- import { fetchTransactionCategorization, fetchTransactionCategorizationView } from '../../reducers/transactionsViewReducer';
9
- export type ActionType = ReturnType<typeof fetchTransactionCategorizationView> | ReturnType<typeof fetchTransactionCategorization> | ReturnType<typeof fetchAccountList> | ReturnType<typeof fetchClassList> | ReturnType<typeof fetchOwnerList> | ReturnType<typeof fetchProjectList>;
8
+ import { fetchTransactionCategorization, fetchTransactionCategorizationView, restoreFullDatasetSnapshot } from '../../reducers/transactionsViewReducer';
9
+ export type ActionType = ReturnType<typeof fetchTransactionCategorizationView> | ReturnType<typeof fetchTransactionCategorization> | ReturnType<typeof restoreFullDatasetSnapshot> | ReturnType<typeof fetchAccountList> | ReturnType<typeof fetchClassList> | ReturnType<typeof fetchOwnerList> | ReturnType<typeof fetchProjectList>;
10
10
  export declare const fetchTransactionCategorizationViewEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>) => Observable<ActionType>;
@@ -16,18 +16,18 @@ const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pi
16
16
  const updateActions = [];
17
17
  const { expenseAutomationTransactionsViewState } = state$.value;
18
18
  const { fetchState, transactionIdsBySelectedPeriod } = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTab];
19
- console.log('[EA-SEARCH] fetchTransactionCategorizationViewEpic received', {
20
- selectedTab,
21
- cacheOverride,
22
- searchString,
23
- fetchState,
24
- });
25
19
  const monthYearPeriod = {
26
20
  month: period.start.month,
27
21
  year: period.start.year,
28
22
  };
29
- const transactionIds = transactionIdsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod)] ??
30
- [];
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
+ };
31
31
  const accountList = state$.value.accountListState.byReportId['accountList'];
32
32
  if (accountList.hasValidState() === false &&
33
33
  accountList.fetchState === 'Not-Started') {
@@ -49,37 +49,45 @@ const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pi
49
49
  vendorOwnerList.fetchState === 'Not-Started') {
50
50
  updateActions.push((0, ownerListReducer_1.fetchOwnerList)());
51
51
  }
52
- console.log('[EA-SEARCH] view epic cache check', {
53
- selectedTab,
54
- cacheOverride,
55
- fetchState,
56
- transactionIdsBySelectedPeriod
57
- });
58
- 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 ||
59
60
  fetchState === 'Not-Started' ||
60
61
  transactionIds.length === 0) {
61
- console.log('[EA-SEARCH] dispatching fetchTransactionCategorization for active tab', { selectedTab, searchString });
62
62
  updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(selectedTab, period, cacheOverride, keepExistingListItems, searchString, pageToken, refreshViewInBackground, resetListItems, isUncategorizedExpenseCategoryEnabled));
63
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
+ }
64
76
  // When search changes (searchString defined, including empty string to
65
- // clear), also fetch every other tab so all tabs always reflect the same
77
+ // clear), also update every other tab so all tabs always reflect the same
66
78
  // search query. Skip if the other tab already has this search string to
67
- // avoid redundant requests on noop updates. cacheOverride=true bypasses
68
- // the cache check; pageToken reset to undefined so the other tab starts
69
- // from page 1; refreshViewInBackground=true because the user isn't
70
- // looking at the other tab, so a silent background fetch is correct.
79
+ // avoid redundant work on noop updates. On clear, restore from snapshot
80
+ // if available; otherwise issue a background fetch.
71
81
  if (searchString !== undefined) {
72
82
  transactionsViewState_1.TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
73
83
  const otherTabSearchString = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].uiState.searchString;
74
- console.log('[EA-SEARCH] dual-fetch check for other tab', {
75
- otherTab,
76
- searchString,
77
- otherTabSearchString,
78
- willDispatch: searchString !== otherTabSearchString,
79
- });
80
84
  if (searchString !== otherTabSearchString) {
81
- console.log('[EA-SEARCH] dispatching fetchTransactionCategorization for other tab', { otherTab, searchString });
82
- updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(otherTab, period, true, keepExistingListItems, searchString, undefined, true, true, isUncategorizedExpenseCategoryEnabled));
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
+ }
83
91
  }
84
92
  });
85
93
  }
@@ -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: [],
@@ -176,8 +177,30 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
176
177
  month: period.start.month,
177
178
  year: period.start.year,
178
179
  };
180
+ const periodId = (0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod);
179
181
  const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
180
- .transactionIdsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod)] ?? [];
182
+ .transactionIdsBySelectedPeriod[periodId] ?? [];
183
+ // When starting a non-empty search, snapshot the current full dataset
184
+ // so clearing the search can restore instantly without an API round-trip.
185
+ // Only save the snapshot when there are rows to restore; skip for tabs
186
+ // that have never been loaded (empty list → no useful snapshot).
187
+ if (searchString !== undefined &&
188
+ searchString !== '' &&
189
+ transactionIdsBySelectedPeriod.length > 0) {
190
+ const snapshot = {
191
+ parentTotalCount: draft.parentTotalCountByTab[selectedTab][periodId],
192
+ periodId,
193
+ totalCount: draft.transactionCategorizationView[selectedTab].uiState
194
+ .totalCount[periodId],
195
+ transactionIds: [...transactionIdsBySelectedPeriod],
196
+ transactionReviewLocalDataById: {
197
+ ...draft.transactionCategorizationView[selectedTab]
198
+ .transactionReviewLocalDataById,
199
+ },
200
+ };
201
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
202
+ snapshot;
203
+ }
181
204
  transactionIdsBySelectedPeriod.forEach((transactionId) => {
182
205
  delete draft.transactionCategorizationView[selectedTab]
183
206
  .transactionReviewLocalDataById[transactionId];
@@ -693,6 +716,43 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
693
716
  // leave the other tab's state untouched — it has its own active fetch.
694
717
  });
695
718
  },
719
+ restoreFullDatasetSnapshot(draft, action) {
720
+ const { selectedTab, period } = action.payload;
721
+ const periodId = (0, timePeriod_1.toMonthYearPeriodId)({
722
+ month: period.start.month,
723
+ year: period.start.year,
724
+ });
725
+ const snapshot = draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot;
726
+ if (snapshot == null || snapshot.periodId !== periodId) {
727
+ return;
728
+ }
729
+ draft.transactionCategorizationView[selectedTab]
730
+ .transactionIdsBySelectedPeriod[periodId] = snapshot.transactionIds;
731
+ // Merge back per-transaction local data; skip entries that were updated
732
+ // during the search (e.g. a save that happened while filtered results
733
+ // were showing) so those saves are not overwritten.
734
+ Object.entries(snapshot.transactionReviewLocalDataById).forEach(([transactionId, localData]) => {
735
+ if (draft.transactionCategorizationView[selectedTab]
736
+ .transactionReviewLocalDataById[transactionId] == null) {
737
+ draft.transactionCategorizationView[selectedTab]
738
+ .transactionReviewLocalDataById[transactionId] = localData;
739
+ }
740
+ });
741
+ if (snapshot.totalCount != null) {
742
+ draft.transactionCategorizationView[selectedTab].uiState.totalCount[periodId] = snapshot.totalCount;
743
+ }
744
+ if (snapshot.parentTotalCount != null) {
745
+ draft.parentTotalCountByTab[selectedTab][periodId] =
746
+ snapshot.parentTotalCount;
747
+ }
748
+ draft.transactionCategorizationView[selectedTab].uiState.searchString =
749
+ '';
750
+ draft.transactionCategorizationView[selectedTab].uiState.pageToken = null;
751
+ draft.transactionCategorizationView[selectedTab].fetchState = 'Completed';
752
+ draft.transactionCategorizationView[selectedTab].error = undefined;
753
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
754
+ undefined;
755
+ },
696
756
  fetchTransactionCategorizationFailure(draft, action) {
697
757
  const { selectedTab, selectedPeriod } = action.payload;
698
758
  draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(selectedPeriod)] =
@@ -1073,5 +1133,5 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
1073
1133
  },
1074
1134
  },
1075
1135
  });
1076
- _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;
1136
+ _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;
1077
1137
  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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.1.16-betaRD4",
3
+ "version": "5.1.16-betaRD6",
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",