@zeniai/client-epic-state 5.1.18-betaRD1 → 5.1.18-betaRD3

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.
@@ -1,4 +1,4 @@
1
- import { from, of } from 'rxjs';
1
+ import { EMPTY, from, of } from 'rxjs';
2
2
  import { catchError, filter, groupBy, mergeMap, switchMap } from 'rxjs/operators';
3
3
  import { toString } from '../../../../commonStateTypes/timePeriod';
4
4
  import { getCurrentTenant } from '../../../../entity/tenant/tenantSelector';
@@ -16,6 +16,7 @@ groupBy((action) => action.payload.selectedTab), mergeMap((group$) => group$.pip
16
16
  const { period, keepExistingListItems, selectedTab, refreshViewInBackground, } = action.payload;
17
17
  const uiState = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTab].uiState;
18
18
  const { pageToken, sortOrder, sortKey } = uiState;
19
+ const requestSearchString = uiState.searchString;
19
20
  const currentTenant = getCurrentTenant(state$.value);
20
21
  const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
21
22
  const queryParam = {
@@ -39,6 +40,17 @@ groupBy((action) => action.payload.selectedTab), mergeMap((group$) => group$.pip
39
40
  .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
40
41
  .pipe(mergeMap((response) => {
41
42
  if (isSuccessResponse(response) && response.data != null) {
43
+ // Stale-response guard: if search was cleared while this
44
+ // request was in-flight (restoreFullDatasetSnapshot ran),
45
+ // discard results to avoid overwriting the restored IDs and
46
+ // counts. switchMap only cancels on a new fetchTransactionCategorization
47
+ // for the same tab; restore doesn't dispatch one, so we guard here.
48
+ const currentSearchString = state$.value.expenseAutomationTransactionsViewState
49
+ .transactionCategorizationView[selectedTab].uiState
50
+ .searchString;
51
+ if (currentSearchString !== requestSearchString) {
52
+ return EMPTY;
53
+ }
42
54
  const updateActions = [];
43
55
  updateActions.push(updateTransactions(response.data.transactions, (value) => value.transaction_id, 'merge'));
44
56
  const transactionIds = response.data.transactions.map((transaction) => transaction.transaction_id);
@@ -164,23 +164,25 @@ const expenseAutomationTransactionsView = createSlice({
164
164
  searchString;
165
165
  }
166
166
  draft.transactionCategorizationView[selectedTab].error = undefined;
167
- // Reset list when we change the sort order and fetch list again
168
- if (resetListItems === true) {
169
- const monthYearPeriod = {
170
- month: period.start.month,
171
- year: period.start.year,
172
- };
173
- const periodId = toMonthYearPeriodId(monthYearPeriod);
174
- const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
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 = {
167
+ const monthYearPeriod = {
168
+ month: period.start.month,
169
+ year: period.start.year,
170
+ };
171
+ const periodId = toMonthYearPeriodId(monthYearPeriod);
172
+ const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
173
+ .transactionIdsBySelectedPeriod[periodId] ?? [];
174
+ // Snapshot before any list mutation so clearing the search restores
175
+ // instantly without an API round-trip — for BOTH active and synced tabs.
176
+ // Active-tab search dispatches resetListItems=false, so this must live
177
+ // outside the resetListItems block. fullDatasetSnapshot == null prevents
178
+ // overwriting the original snapshot on search refinement ('ama'→'amazon').
179
+ if (searchString !== undefined &&
180
+ searchString !== '' &&
181
+ transactionIdsBySelectedPeriod.length > 0 &&
182
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot ==
183
+ null) {
184
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
185
+ {
184
186
  parentTotalCount: draft.parentTotalCountByTab[selectedTab][periodId],
185
187
  periodId,
186
188
  totalCount: draft.transactionCategorizationView[selectedTab].uiState
@@ -191,16 +193,16 @@ const expenseAutomationTransactionsView = createSlice({
191
193
  .transactionReviewLocalDataById,
192
194
  },
193
195
  };
194
- draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
195
- snapshot;
196
- }
196
+ }
197
+ // Reset list when we change the sort order and fetch list again
198
+ if (resetListItems === true) {
197
199
  transactionIdsBySelectedPeriod.forEach((transactionId) => {
198
200
  delete draft.transactionCategorizationView[selectedTab]
199
201
  .transactionReviewLocalDataById[transactionId];
200
202
  });
201
203
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds =
202
204
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds.filter((selectedCheckBoxTransactionId) => transactionIdsBySelectedPeriod.includes(selectedCheckBoxTransactionId));
203
- draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[toMonthYearPeriodId(monthYearPeriod)] = [];
205
+ draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[periodId] = [];
204
206
  draft.transactionCategorizationView[selectedTab].refreshStatus = {
205
207
  fetchState: 'Not-Started',
206
208
  error: undefined,
@@ -742,6 +744,10 @@ const expenseAutomationTransactionsView = createSlice({
742
744
  '';
743
745
  draft.transactionCategorizationView[selectedTab].uiState.pageToken = null;
744
746
  draft.transactionCategorizationView[selectedTab].fetchState = 'Completed';
747
+ draft.transactionCategorizationView[selectedTab].refreshStatus = {
748
+ fetchState: 'Not-Started',
749
+ error: undefined,
750
+ };
745
751
  draft.transactionCategorizationView[selectedTab].error = undefined;
746
752
  draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
747
753
  undefined;
@@ -19,6 +19,7 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
19
19
  const { period, keepExistingListItems, selectedTab, refreshViewInBackground, } = action.payload;
20
20
  const uiState = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTab].uiState;
21
21
  const { pageToken, sortOrder, sortKey } = uiState;
22
+ const requestSearchString = uiState.searchString;
22
23
  const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state$.value);
23
24
  const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
24
25
  const queryParam = {
@@ -42,6 +43,17 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
42
43
  .getJSON(`${zeniAPI.apiEndPoints.accountMicroServiceBaseUrl}/1.0/expense-automation/transactions?query=${encodeURIComponent(JSON.stringify(queryParam))}`)
43
44
  .pipe((0, operators_1.mergeMap)((response) => {
44
45
  if ((0, responsePayload_1.isSuccessResponse)(response) && response.data != null) {
46
+ // Stale-response guard: if search was cleared while this
47
+ // request was in-flight (restoreFullDatasetSnapshot ran),
48
+ // discard results to avoid overwriting the restored IDs and
49
+ // counts. switchMap only cancels on a new fetchTransactionCategorization
50
+ // for the same tab; restore doesn't dispatch one, so we guard here.
51
+ const currentSearchString = state$.value.expenseAutomationTransactionsViewState
52
+ .transactionCategorizationView[selectedTab].uiState
53
+ .searchString;
54
+ if (currentSearchString !== requestSearchString) {
55
+ return rxjs_1.EMPTY;
56
+ }
45
57
  const updateActions = [];
46
58
  updateActions.push((0, transactionReducer_1.updateTransactions)(response.data.transactions, (value) => value.transaction_id, 'merge'));
47
59
  const transactionIds = response.data.transactions.map((transaction) => transaction.transaction_id);
@@ -171,23 +171,25 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
171
171
  searchString;
172
172
  }
173
173
  draft.transactionCategorizationView[selectedTab].error = undefined;
174
- // Reset list when we change the sort order and fetch list again
175
- if (resetListItems === true) {
176
- const monthYearPeriod = {
177
- month: period.start.month,
178
- year: period.start.year,
179
- };
180
- const periodId = (0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod);
181
- const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
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 = {
174
+ const monthYearPeriod = {
175
+ month: period.start.month,
176
+ year: period.start.year,
177
+ };
178
+ const periodId = (0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod);
179
+ const transactionIdsBySelectedPeriod = draft.transactionCategorizationView[selectedTab]
180
+ .transactionIdsBySelectedPeriod[periodId] ?? [];
181
+ // Snapshot before any list mutation so clearing the search restores
182
+ // instantly without an API round-trip — for BOTH active and synced tabs.
183
+ // Active-tab search dispatches resetListItems=false, so this must live
184
+ // outside the resetListItems block. fullDatasetSnapshot == null prevents
185
+ // overwriting the original snapshot on search refinement ('ama'→'amazon').
186
+ if (searchString !== undefined &&
187
+ searchString !== '' &&
188
+ transactionIdsBySelectedPeriod.length > 0 &&
189
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot ==
190
+ null) {
191
+ draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
192
+ {
191
193
  parentTotalCount: draft.parentTotalCountByTab[selectedTab][periodId],
192
194
  periodId,
193
195
  totalCount: draft.transactionCategorizationView[selectedTab].uiState
@@ -198,16 +200,16 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
198
200
  .transactionReviewLocalDataById,
199
201
  },
200
202
  };
201
- draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
202
- snapshot;
203
- }
203
+ }
204
+ // Reset list when we change the sort order and fetch list again
205
+ if (resetListItems === true) {
204
206
  transactionIdsBySelectedPeriod.forEach((transactionId) => {
205
207
  delete draft.transactionCategorizationView[selectedTab]
206
208
  .transactionReviewLocalDataById[transactionId];
207
209
  });
208
210
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds =
209
211
  draft.transactionCategorizationView[selectedTab].selectedCheckBoxTransactionIds.filter((selectedCheckBoxTransactionId) => transactionIdsBySelectedPeriod.includes(selectedCheckBoxTransactionId));
210
- draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[(0, timePeriod_1.toMonthYearPeriodId)(monthYearPeriod)] = [];
212
+ draft.transactionCategorizationView[selectedTab].transactionIdsBySelectedPeriod[periodId] = [];
211
213
  draft.transactionCategorizationView[selectedTab].refreshStatus = {
212
214
  fetchState: 'Not-Started',
213
215
  error: undefined,
@@ -749,6 +751,10 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
749
751
  '';
750
752
  draft.transactionCategorizationView[selectedTab].uiState.pageToken = null;
751
753
  draft.transactionCategorizationView[selectedTab].fetchState = 'Completed';
754
+ draft.transactionCategorizationView[selectedTab].refreshStatus = {
755
+ fetchState: 'Not-Started',
756
+ error: undefined,
757
+ };
752
758
  draft.transactionCategorizationView[selectedTab].error = undefined;
753
759
  draft.transactionCategorizationView[selectedTab].fullDatasetSnapshot =
754
760
  undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.1.18-betaRD1",
3
+ "version": "5.1.18-betaRD3",
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",