@zeniai/client-epic-state 5.1.15 → 5.1.16-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.
- package/lib/esm/view/dashboard/dashboardSelector.js +4 -17
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +10 -3
- package/lib/esm/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +16 -0
- package/lib/esm/view/expenseAutomationView/reducers/transactionsViewReducer.js +22 -10
- package/lib/view/dashboard/dashboardSelector.d.ts +1 -1
- package/lib/view/dashboard/dashboardSelector.js +4 -17
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationEpic.js +9 -2
- package/lib/view/expenseAutomationView/epics/transactionCategorization/fetchTransactionCategorizationViewEpic.js +16 -0
- package/lib/view/expenseAutomationView/payload/transactionCategorizationPayload.d.ts +7 -0
- package/lib/view/expenseAutomationView/reducers/transactionsViewReducer.js +22 -10
- package/package.json +1 -1
|
@@ -42,7 +42,7 @@ const tenantSelector = createSelector(getTenantsByCheckInDateSelector, getCurren
|
|
|
42
42
|
export const getDashboard = createSelector(tenantSelector, getCardBalance, getCashBalance, getOperatingExpensesForLast5Periods, getRevenueForLast4Periods, getTop6Expenses, getNetBurnOrIncomeForLast4Periods, getNetBurnOrIncomeStoryCard, getCashPositionLast5Periods, getCashInCashOutLast4Periods, getInsights, getCompanyPassportView, getTasksCardReport, getBillPayCardReport, getReimbursementCardReport, apAgingSelector, arAgingSelector, getZeniAccountsPromoCard, getRewardsPlanCard, getDashboardCardsOrdered, (state) => {
|
|
43
43
|
const currentTenant = getCurrentTenant(state);
|
|
44
44
|
return getMonthEndCloseChecksViewByTenantId(state, currentTenant.tenantId);
|
|
45
|
-
}, (state) => state.tenantState, (_state, additionalExcludedReports) => additionalExcludedReports, (_state, _additionalExcludedReports, signedInUser) => signedInUser, (_state, _excluded, _signedInUser, isExpenseAutomationFeatureEnabledV2) => isExpenseAutomationFeatureEnabledV2, (_state, _excluded, _signedInUser, _enabledV2, isExpenseAutomationFeatureEnabled) => isExpenseAutomationFeatureEnabled, (
|
|
45
|
+
}, (state) => state.tenantState, (_state, additionalExcludedReports) => additionalExcludedReports, (_state, _additionalExcludedReports, signedInUser) => signedInUser, (_state, _excluded, _signedInUser, isExpenseAutomationFeatureEnabledV2) => isExpenseAutomationFeatureEnabledV2, (_state, _excluded, _signedInUser, _enabledV2, isExpenseAutomationFeatureEnabled) => isExpenseAutomationFeatureEnabled, (state) => state.dashboardState.isTreasuryVideoClosed, (tenants, cardBalance, cashBalance, opEx, revenue, topEx, netBurnOrIncome, netBurnOrIncomeStoryCard, cashPosition, cashInCashOut, insightsView, companyPassportView, tasksCard, billPayCard, reimbursementCard, apAgingReport, arAgingReport, zeniAccountsPromoCard, rewardsPlanCard, cards, monthEndCloseChecksView, tenantState, additionalExcludedReports, signedInUser, isExpenseAutomationFeatureEnabledV2, isExpenseAutomationFeatureEnabled, isTreasuryVideoClosed) => {
|
|
46
46
|
const isReimbursementFeatureEnabled = tenants.currentTenant.company?.featuresActivationInfo
|
|
47
47
|
.isReimbursementFeatureEnabled;
|
|
48
48
|
const isBillPayFeatureEnabled = tenants.currentTenant.company?.featuresActivationInfo
|
|
@@ -153,23 +153,10 @@ export const getDashboard = createSelector(tenantSelector, getCardBalance, getCa
|
|
|
153
153
|
cardsToRemove.add('bill_pay_card');
|
|
154
154
|
cardsToRemove.add('reimbursement_card');
|
|
155
155
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
cardsToRemove.add('reimbursement_promo_card');
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
cardsToRemove.add('zeni_accounts_promo_card');
|
|
163
|
-
}
|
|
164
|
-
// remove treasury promo card if its already enabled
|
|
165
|
-
const isTreasuryTOSAccepted = tenants.currentTenant.companyTreasuryInfo?.info?.isTreasuryTOSAccepted;
|
|
166
|
-
if (isTreasuryTOSAccepted === true || isTreasuryFeatureEnabled === false) {
|
|
167
|
-
cardsToRemove.add('treasury_promo_card');
|
|
168
|
-
}
|
|
169
|
-
// More than one promo card can't be shown on the dashboard (priority: Treasury > Accounts > Bill > Remi).
|
|
156
|
+
cardsToRemove.add('zeni_accounts_promo_card');
|
|
157
|
+
cardsToRemove.add('treasury_promo_card');
|
|
158
|
+
// More than one promo card can't be shown on the dashboard (priority: Bill > Remi).
|
|
170
159
|
const PROMO_CARD_PRIORITY_ORDER = [
|
|
171
|
-
'treasury_promo_card',
|
|
172
|
-
'zeni_accounts_promo_card',
|
|
173
160
|
'bill_pay_promo_card',
|
|
174
161
|
'reimbursement_promo_card',
|
|
175
162
|
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { from, of } from 'rxjs';
|
|
2
|
-
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
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';
|
|
5
5
|
import { updateTransactions } from '../../../../entity/transaction/transactionReducer';
|
|
@@ -8,7 +8,10 @@ import { getLineItemsByTransactionsIdsFromResponse } from '../../helpers/transac
|
|
|
8
8
|
import { fetchTransactionCategorization, fetchTransactionCategorizationFailure, fetchTransactionCategorizationSuccess, initializeTransactionCategorizationViewLocalData, resetOtherTabsFetchState, updateParentTotalCountForTab, updateTotalCountForTransactionCategorization, updateTransactionCategorizationUIState, } from '../../reducers/transactionsViewReducer';
|
|
9
9
|
import { DEFAULT_COMPLETED_SUB_TAB } from '../../types/completedSubTab';
|
|
10
10
|
import { TRANSACTIONS_TABS, toTransactionsSortKey, } from '../../types/transactionsViewState';
|
|
11
|
-
export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchTransactionCategorization.match),
|
|
11
|
+
export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchTransactionCategorization.match),
|
|
12
|
+
// Per-tab switchMap: cancels in-flight requests for the same tab on a new
|
|
13
|
+
// request, but allows both tabs to fetch concurrently (e.g. dual search).
|
|
14
|
+
groupBy((action) => action.payload.selectedTab), mergeMap((group$) => group$.pipe(switchMap((action) => {
|
|
12
15
|
const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationTransactionsViewState, } = state$.value;
|
|
13
16
|
const { period, keepExistingListItems, selectedTab, refreshViewInBackground, } = action.payload;
|
|
14
17
|
const uiState = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTab].uiState;
|
|
@@ -24,6 +27,10 @@ export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) =>
|
|
|
24
27
|
page_token: pageToken,
|
|
25
28
|
page_size: 25,
|
|
26
29
|
search_text: uiState.searchString,
|
|
30
|
+
// Always scope to the active tab's auto_categorized status;
|
|
31
|
+
// cross-status search (omit or true) is reserved for Receipts
|
|
32
|
+
// manual-match.
|
|
33
|
+
search_all_statuses: false,
|
|
27
34
|
sub_tab: selectedTab === 'autoCategorized'
|
|
28
35
|
? expenseAutomationTransactionsViewState.selectedTransactionCategorizationCompletedSubTab
|
|
29
36
|
: DEFAULT_COMPLETED_SUB_TAB,
|
|
@@ -79,4 +86,4 @@ export const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) =>
|
|
|
79
86
|
status: createZeniAPIStatus('Unexpected Error', 'fetch Transaction Categorization REST API call errored out' +
|
|
80
87
|
JSON.stringify(error)),
|
|
81
88
|
}))));
|
|
82
|
-
}));
|
|
89
|
+
}))));
|
|
@@ -7,6 +7,7 @@ import { fetchClassList } from '../../../classList/classListReducer';
|
|
|
7
7
|
import { fetchOwnerList } from '../../../ownerList/ownerListReducer';
|
|
8
8
|
import { fetchProjectList } from '../../../projectList/projectListReducer';
|
|
9
9
|
import { fetchTransactionCategorization, fetchTransactionCategorizationView, } from '../../reducers/transactionsViewReducer';
|
|
10
|
+
import { TRANSACTIONS_TABS } from '../../types/transactionsViewState';
|
|
10
11
|
export const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pipe(filter(fetchTransactionCategorizationView.match), mergeMap((action) => {
|
|
11
12
|
const { selectedTab, cacheOverride, keepExistingListItems, pageToken, period, refreshViewInBackground, searchString, resetListItems, isUncategorizedExpenseCategoryEnabled, } = action.payload;
|
|
12
13
|
const updateActions = [];
|
|
@@ -44,5 +45,20 @@ export const fetchTransactionCategorizationViewEpic = (actions$, state$) => acti
|
|
|
44
45
|
transactionIds.length === 0) {
|
|
45
46
|
updateActions.push(fetchTransactionCategorization(selectedTab, period, cacheOverride, keepExistingListItems, searchString, pageToken, refreshViewInBackground, resetListItems, isUncategorizedExpenseCategoryEnabled));
|
|
46
47
|
}
|
|
48
|
+
// When search changes (searchString defined, including empty string to
|
|
49
|
+
// clear), also fetch every other tab so all tabs always reflect the same
|
|
50
|
+
// search query. Skip if the other tab already has this search string to
|
|
51
|
+
// avoid redundant requests on noop updates. cacheOverride=true bypasses
|
|
52
|
+
// the cache check; pageToken reset to undefined so the other tab starts
|
|
53
|
+
// from page 1; refreshViewInBackground=true because the user isn't
|
|
54
|
+
// looking at the other tab, so a silent background fetch is correct.
|
|
55
|
+
if (searchString !== undefined) {
|
|
56
|
+
TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
|
|
57
|
+
const otherTabSearchString = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].uiState.searchString;
|
|
58
|
+
if (searchString !== otherTabSearchString) {
|
|
59
|
+
updateActions.push(fetchTransactionCategorization(otherTab, period, true, keepExistingListItems, searchString, undefined, true, true, isUncategorizedExpenseCategoryEnabled));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
47
63
|
return from(updateActions);
|
|
48
64
|
}));
|
|
@@ -156,8 +156,12 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
156
156
|
}
|
|
157
157
|
draft.transactionCategorizationView[selectedTab].uiState.pageToken =
|
|
158
158
|
pageToken ?? null;
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
// Only overwrite when explicitly set; undefined means a non-search fetch
|
|
160
|
+
// (tab switch, period change) should preserve the existing search string.
|
|
161
|
+
if (searchString !== undefined) {
|
|
162
|
+
draft.transactionCategorizationView[selectedTab].uiState.searchString =
|
|
163
|
+
searchString;
|
|
164
|
+
}
|
|
161
165
|
draft.transactionCategorizationView[selectedTab].error = undefined;
|
|
162
166
|
// Reset list when we change the sort order and fetch list again
|
|
163
167
|
if (resetListItems === true) {
|
|
@@ -305,10 +309,12 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
305
309
|
}
|
|
306
310
|
},
|
|
307
311
|
updateTransactionFilters(draft, action) {
|
|
308
|
-
const {
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
+
const { filters } = action.payload;
|
|
313
|
+
// Filters are tab-agnostic (payee/category/class/amount), so apply to
|
|
314
|
+
// all tabs so switching tabs after filtering shows consistent results.
|
|
315
|
+
TRANSACTIONS_TABS.forEach((tab) => {
|
|
316
|
+
draft.transactionCategorizationView[tab].filters = filters;
|
|
317
|
+
});
|
|
312
318
|
},
|
|
313
319
|
saveTransactionCategorization: {
|
|
314
320
|
prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
|
|
@@ -660,18 +666,24 @@ const expenseAutomationTransactionsView = createSlice({
|
|
|
660
666
|
resetOtherTabsFetchState(draft, action) {
|
|
661
667
|
const { tabs, refreshViewInBackground } = action.payload;
|
|
662
668
|
tabs.forEach((tab) => {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
669
|
+
const tabFetchState = draft.transactionCategorizationView[tab].fetchState;
|
|
670
|
+
if (refreshViewInBackground !== true) {
|
|
671
|
+
// Foreground fetch completed: mark other tabs as Completed.
|
|
666
672
|
draft.transactionCategorizationView[tab].fetchState = 'Completed';
|
|
667
673
|
draft.transactionCategorizationView[tab].error = undefined;
|
|
668
674
|
}
|
|
669
|
-
else {
|
|
675
|
+
else if (tabFetchState !== 'In-Progress') {
|
|
676
|
+
// Background fetch completed: reset the other tab's refreshStatus,
|
|
677
|
+
// but only when it is not mid-load. During a dual-search the active
|
|
678
|
+
// tab has its own foreground fetch in flight — clobbering its
|
|
679
|
+
// fetchState here would drop the skeleton and show an empty list.
|
|
670
680
|
draft.transactionCategorizationView[tab].refreshStatus = {
|
|
671
681
|
fetchState: 'Not-Started',
|
|
672
682
|
error: undefined,
|
|
673
683
|
};
|
|
674
684
|
}
|
|
685
|
+
// If tabFetchState === 'In-Progress' and refreshViewInBackground === true,
|
|
686
|
+
// leave the other tab's state untouched — it has its own active fetch.
|
|
675
687
|
});
|
|
676
688
|
},
|
|
677
689
|
fetchTransactionCategorizationFailure(draft, action) {
|
|
@@ -12,4 +12,4 @@ export interface DashboardReport extends SelectorReport {
|
|
|
12
12
|
currentTenant?: Tenant;
|
|
13
13
|
}
|
|
14
14
|
export declare const getDashboard: (state: RootState, additionalExcludedReports: ReportID[], // used when reports are excluded through statsig
|
|
15
|
-
signedInUser: LoggedInUser | undefined, isExpenseAutomationFeatureEnabledV2: boolean, isExpenseAutomationFeatureEnabled: boolean
|
|
15
|
+
signedInUser: LoggedInUser | undefined, isExpenseAutomationFeatureEnabledV2: boolean, isExpenseAutomationFeatureEnabled: boolean) => DashboardReport;
|
|
@@ -45,7 +45,7 @@ const tenantSelector = (0, toolkit_1.createSelector)(tenantSelector_1.getTenants
|
|
|
45
45
|
exports.getDashboard = (0, toolkit_1.createSelector)(tenantSelector, cardBalanceSelector_1.getCardBalance, cashBalanceSelector_1.getCashBalance, getOperatingExpensesForLast5Periods, getRevenueForLast4Periods, topExSelector_1.getTop6Expenses, getNetBurnOrIncomeForLast4Periods, getNetBurnOrIncomeStoryCard, getCashPositionLast5Periods, getCashInCashOutLast4Periods, insightsCardSelector_1.getInsights, companyPassportViewSelector_1.getCompanyPassportView, tasksCardSelector_1.getTasksCardReport, billPayCardSelector_1.getBillPayCardReport, reimbursementCardSelector_1.getReimbursementCardReport, apAgingSelector, arAgingSelector, zeniAccountsPromoCardSelector_1.getZeniAccountsPromoCard, referralSelector_1.getRewardsPlanCard, dashboardLayoutSelector_1.getDashboardCardsOrdered, (state) => {
|
|
46
46
|
const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state);
|
|
47
47
|
return (0, monthEndCloseChecksViewSelector_1.getMonthEndCloseChecksViewByTenantId)(state, currentTenant.tenantId);
|
|
48
|
-
}, (state) => state.tenantState, (_state, additionalExcludedReports) => additionalExcludedReports, (_state, _additionalExcludedReports, signedInUser) => signedInUser, (_state, _excluded, _signedInUser, isExpenseAutomationFeatureEnabledV2) => isExpenseAutomationFeatureEnabledV2, (_state, _excluded, _signedInUser, _enabledV2, isExpenseAutomationFeatureEnabled) => isExpenseAutomationFeatureEnabled, (
|
|
48
|
+
}, (state) => state.tenantState, (_state, additionalExcludedReports) => additionalExcludedReports, (_state, _additionalExcludedReports, signedInUser) => signedInUser, (_state, _excluded, _signedInUser, isExpenseAutomationFeatureEnabledV2) => isExpenseAutomationFeatureEnabledV2, (_state, _excluded, _signedInUser, _enabledV2, isExpenseAutomationFeatureEnabled) => isExpenseAutomationFeatureEnabled, (state) => state.dashboardState.isTreasuryVideoClosed, (tenants, cardBalance, cashBalance, opEx, revenue, topEx, netBurnOrIncome, netBurnOrIncomeStoryCard, cashPosition, cashInCashOut, insightsView, companyPassportView, tasksCard, billPayCard, reimbursementCard, apAgingReport, arAgingReport, zeniAccountsPromoCard, rewardsPlanCard, cards, monthEndCloseChecksView, tenantState, additionalExcludedReports, signedInUser, isExpenseAutomationFeatureEnabledV2, isExpenseAutomationFeatureEnabled, isTreasuryVideoClosed) => {
|
|
49
49
|
const isReimbursementFeatureEnabled = tenants.currentTenant.company?.featuresActivationInfo
|
|
50
50
|
.isReimbursementFeatureEnabled;
|
|
51
51
|
const isBillPayFeatureEnabled = tenants.currentTenant.company?.featuresActivationInfo
|
|
@@ -156,23 +156,10 @@ exports.getDashboard = (0, toolkit_1.createSelector)(tenantSelector, cardBalance
|
|
|
156
156
|
cardsToRemove.add('bill_pay_card');
|
|
157
157
|
cardsToRemove.add('reimbursement_card');
|
|
158
158
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
cardsToRemove.add('reimbursement_promo_card');
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
cardsToRemove.add('zeni_accounts_promo_card');
|
|
166
|
-
}
|
|
167
|
-
// remove treasury promo card if its already enabled
|
|
168
|
-
const isTreasuryTOSAccepted = tenants.currentTenant.companyTreasuryInfo?.info?.isTreasuryTOSAccepted;
|
|
169
|
-
if (isTreasuryTOSAccepted === true || isTreasuryFeatureEnabled === false) {
|
|
170
|
-
cardsToRemove.add('treasury_promo_card');
|
|
171
|
-
}
|
|
172
|
-
// More than one promo card can't be shown on the dashboard (priority: Treasury > Accounts > Bill > Remi).
|
|
159
|
+
cardsToRemove.add('zeni_accounts_promo_card');
|
|
160
|
+
cardsToRemove.add('treasury_promo_card');
|
|
161
|
+
// More than one promo card can't be shown on the dashboard (priority: Bill > Remi).
|
|
173
162
|
const PROMO_CARD_PRIORITY_ORDER = [
|
|
174
|
-
'treasury_promo_card',
|
|
175
|
-
'zeni_accounts_promo_card',
|
|
176
163
|
'bill_pay_promo_card',
|
|
177
164
|
'reimbursement_promo_card',
|
|
178
165
|
];
|
|
@@ -11,7 +11,10 @@ const transactionCategorizationLocalDataHelper_1 = require("../../helpers/transa
|
|
|
11
11
|
const transactionsViewReducer_1 = require("../../reducers/transactionsViewReducer");
|
|
12
12
|
const completedSubTab_1 = require("../../types/completedSubTab");
|
|
13
13
|
const transactionsViewState_1 = require("../../types/transactionsViewState");
|
|
14
|
-
const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(transactionsViewReducer_1.fetchTransactionCategorization.match),
|
|
14
|
+
const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(transactionsViewReducer_1.fetchTransactionCategorization.match),
|
|
15
|
+
// Per-tab switchMap: cancels in-flight requests for the same tab on a new
|
|
16
|
+
// request, but allows both tabs to fetch concurrently (e.g. dual search).
|
|
17
|
+
(0, operators_1.groupBy)((action) => action.payload.selectedTab), (0, operators_1.mergeMap)((group$) => group$.pipe((0, operators_1.switchMap)((action) => {
|
|
15
18
|
const { expenseAutomationViewState: { selectedPeriodByTenantId }, expenseAutomationTransactionsViewState, } = state$.value;
|
|
16
19
|
const { period, keepExistingListItems, selectedTab, refreshViewInBackground, } = action.payload;
|
|
17
20
|
const uiState = expenseAutomationTransactionsViewState.transactionCategorizationView[selectedTab].uiState;
|
|
@@ -27,6 +30,10 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
|
|
|
27
30
|
page_token: pageToken,
|
|
28
31
|
page_size: 25,
|
|
29
32
|
search_text: uiState.searchString,
|
|
33
|
+
// Always scope to the active tab's auto_categorized status;
|
|
34
|
+
// cross-status search (omit or true) is reserved for Receipts
|
|
35
|
+
// manual-match.
|
|
36
|
+
search_all_statuses: false,
|
|
30
37
|
sub_tab: selectedTab === 'autoCategorized'
|
|
31
38
|
? expenseAutomationTransactionsViewState.selectedTransactionCategorizationCompletedSubTab
|
|
32
39
|
: completedSubTab_1.DEFAULT_COMPLETED_SUB_TAB,
|
|
@@ -82,5 +89,5 @@ const fetchTransactionCategorizationEpic = (actions$, state$, zeniAPI) => action
|
|
|
82
89
|
status: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'fetch Transaction Categorization REST API call errored out' +
|
|
83
90
|
JSON.stringify(error)),
|
|
84
91
|
}))));
|
|
85
|
-
}));
|
|
92
|
+
}))));
|
|
86
93
|
exports.fetchTransactionCategorizationEpic = fetchTransactionCategorizationEpic;
|
|
@@ -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 = [];
|
|
@@ -47,6 +48,21 @@ const fetchTransactionCategorizationViewEpic = (actions$, state$) => actions$.pi
|
|
|
47
48
|
transactionIds.length === 0) {
|
|
48
49
|
updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(selectedTab, period, cacheOverride, keepExistingListItems, searchString, pageToken, refreshViewInBackground, resetListItems, isUncategorizedExpenseCategoryEnabled));
|
|
49
50
|
}
|
|
51
|
+
// When search changes (searchString defined, including empty string to
|
|
52
|
+
// clear), also fetch every other tab so all tabs always reflect the same
|
|
53
|
+
// search query. Skip if the other tab already has this search string to
|
|
54
|
+
// avoid redundant requests on noop updates. cacheOverride=true bypasses
|
|
55
|
+
// the cache check; pageToken reset to undefined so the other tab starts
|
|
56
|
+
// from page 1; refreshViewInBackground=true because the user isn't
|
|
57
|
+
// looking at the other tab, so a silent background fetch is correct.
|
|
58
|
+
if (searchString !== undefined) {
|
|
59
|
+
transactionsViewState_1.TRANSACTIONS_TABS.filter((tab) => tab !== selectedTab).forEach((otherTab) => {
|
|
60
|
+
const otherTabSearchString = expenseAutomationTransactionsViewState.transactionCategorizationView[otherTab].uiState.searchString;
|
|
61
|
+
if (searchString !== otherTabSearchString) {
|
|
62
|
+
updateActions.push((0, transactionsViewReducer_1.fetchTransactionCategorization)(otherTab, period, true, keepExistingListItems, searchString, undefined, true, true, isUncategorizedExpenseCategoryEnabled));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
50
66
|
return (0, rxjs_1.from)(updateActions);
|
|
51
67
|
}));
|
|
52
68
|
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;
|
|
@@ -163,8 +163,12 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
163
163
|
}
|
|
164
164
|
draft.transactionCategorizationView[selectedTab].uiState.pageToken =
|
|
165
165
|
pageToken ?? null;
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
// Only overwrite when explicitly set; undefined means a non-search fetch
|
|
167
|
+
// (tab switch, period change) should preserve the existing search string.
|
|
168
|
+
if (searchString !== undefined) {
|
|
169
|
+
draft.transactionCategorizationView[selectedTab].uiState.searchString =
|
|
170
|
+
searchString;
|
|
171
|
+
}
|
|
168
172
|
draft.transactionCategorizationView[selectedTab].error = undefined;
|
|
169
173
|
// Reset list when we change the sort order and fetch list again
|
|
170
174
|
if (resetListItems === true) {
|
|
@@ -312,10 +316,12 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
312
316
|
}
|
|
313
317
|
},
|
|
314
318
|
updateTransactionFilters(draft, action) {
|
|
315
|
-
const {
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
+
const { filters } = action.payload;
|
|
320
|
+
// Filters are tab-agnostic (payee/category/class/amount), so apply to
|
|
321
|
+
// all tabs so switching tabs after filtering shows consistent results.
|
|
322
|
+
transactionsViewState_1.TRANSACTIONS_TABS.forEach((tab) => {
|
|
323
|
+
draft.transactionCategorizationView[tab].filters = filters;
|
|
324
|
+
});
|
|
319
325
|
},
|
|
320
326
|
saveTransactionCategorization: {
|
|
321
327
|
prepare(selectedTab, transactionIds, cotTrackingByTransactionId, isUncategorizedExpenseCategoryEnabled) {
|
|
@@ -667,18 +673,24 @@ const expenseAutomationTransactionsView = (0, toolkit_1.createSlice)({
|
|
|
667
673
|
resetOtherTabsFetchState(draft, action) {
|
|
668
674
|
const { tabs, refreshViewInBackground } = action.payload;
|
|
669
675
|
tabs.forEach((tab) => {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
676
|
+
const tabFetchState = draft.transactionCategorizationView[tab].fetchState;
|
|
677
|
+
if (refreshViewInBackground !== true) {
|
|
678
|
+
// Foreground fetch completed: mark other tabs as Completed.
|
|
673
679
|
draft.transactionCategorizationView[tab].fetchState = 'Completed';
|
|
674
680
|
draft.transactionCategorizationView[tab].error = undefined;
|
|
675
681
|
}
|
|
676
|
-
else {
|
|
682
|
+
else if (tabFetchState !== 'In-Progress') {
|
|
683
|
+
// Background fetch completed: reset the other tab's refreshStatus,
|
|
684
|
+
// but only when it is not mid-load. During a dual-search the active
|
|
685
|
+
// tab has its own foreground fetch in flight — clobbering its
|
|
686
|
+
// fetchState here would drop the skeleton and show an empty list.
|
|
677
687
|
draft.transactionCategorizationView[tab].refreshStatus = {
|
|
678
688
|
fetchState: 'Not-Started',
|
|
679
689
|
error: undefined,
|
|
680
690
|
};
|
|
681
691
|
}
|
|
692
|
+
// If tabFetchState === 'In-Progress' and refreshViewInBackground === true,
|
|
693
|
+
// leave the other tab's state untouched — it has its own active fetch.
|
|
682
694
|
});
|
|
683
695
|
},
|
|
684
696
|
fetchTransactionCategorizationFailure(draft, action) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.16-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",
|