@zeniai/client-epic-state 5.1.68-betaDI2 → 5.1.69-betaAK0

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 (107) hide show
  1. package/lib/esm/index.js +9 -9
  2. package/lib/esm/view/invoicing/couponView/couponViewEpics.js +0 -3
  3. package/lib/esm/view/invoicing/couponView/couponViewReducer.js +7 -1
  4. package/lib/esm/view/invoicing/couponView/couponViewSelector.js +27 -14
  5. package/lib/esm/view/invoicing/invoiceList/fetchInvoiceListEpic.js +7 -5
  6. package/lib/esm/view/invoicing/invoiceList/fetchInvoiceListPageEpic.js +2 -1
  7. package/lib/esm/view/invoicing/invoiceList/invoiceListReducer.js +62 -36
  8. package/lib/esm/view/invoicing/invoiceList/invoiceListSelector.js +19 -15
  9. package/lib/esm/view/invoicing/invoiceList/invoiceListState.js +10 -0
  10. package/lib/esm/view/invoicing/invoicingCreditNoteListView/epics/fetchInvoicingCreditNoteListEpic.js +0 -3
  11. package/lib/esm/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewReducer.js +7 -1
  12. package/lib/esm/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewSelector.js +23 -13
  13. package/lib/esm/view/invoicing/invoicingCustomerDetailView/fetchInvoicingCustomerDetailPageEpic.js +3 -2
  14. package/lib/esm/view/invoicing/invoicingCustomerDetailView/invoicingCustomerDetailViewSelector.js +1 -1
  15. package/lib/esm/view/invoicing/invoicingCustomerListView/epics/fetchInvoicingCustomerListEpic.js +0 -3
  16. package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.js +7 -1
  17. package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.js +25 -12
  18. package/lib/esm/view/invoicing/invoicingDunningCaseListView/epics/fetchInvoicingDunningCaseListEpic.js +0 -3
  19. package/lib/esm/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewReducer.js +7 -1
  20. package/lib/esm/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewSelector.js +23 -13
  21. package/lib/esm/view/invoicing/invoicingSubscriptionListView/epics/fetchInvoicingSubscriptionListEpic.js +0 -3
  22. package/lib/esm/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewReducer.js +7 -1
  23. package/lib/esm/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewSelector.js +22 -12
  24. package/lib/esm/view/invoicing/invoicingTransactionListView/epics/fetchInvoicingTransactionListEpic.js +7 -5
  25. package/lib/esm/view/invoicing/invoicingTransactionListView/epics/fetchInvoicingTransactionListPageEpic.js +2 -1
  26. package/lib/esm/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewReducer.js +56 -25
  27. package/lib/esm/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewSelector.js +15 -11
  28. package/lib/esm/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewState.js +10 -0
  29. package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +12 -9
  30. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewPayload.js +2 -7
  31. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +2 -47
  32. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +0 -9
  33. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewState.js +0 -1
  34. package/lib/esm/view/spendManagement/billPay/billDetailView/billDetailViewSelector.js +8 -2
  35. package/lib/esm/view/spendManagement/helpers.js +12 -0
  36. package/lib/esm/view/spendManagement/reimbursement/editRemiView/epics/fetchEditRemiDetailPageEpic.js +0 -12
  37. package/lib/esm/view/spendManagement/reimbursement/remiDetailView/remiDetailViewSelector.js +4 -2
  38. package/lib/index.d.ts +9 -9
  39. package/lib/index.js +21 -16
  40. package/lib/view/invoicing/couponView/couponViewEpics.js +0 -3
  41. package/lib/view/invoicing/couponView/couponViewReducer.d.ts +1 -1
  42. package/lib/view/invoicing/couponView/couponViewReducer.js +8 -2
  43. package/lib/view/invoicing/couponView/couponViewSelector.d.ts +1 -0
  44. package/lib/view/invoicing/couponView/couponViewSelector.js +27 -14
  45. package/lib/view/invoicing/couponView/couponViewState.d.ts +2 -1
  46. package/lib/view/invoicing/invoiceList/fetchInvoiceListEpic.d.ts +1 -1
  47. package/lib/view/invoicing/invoiceList/fetchInvoiceListEpic.js +7 -5
  48. package/lib/view/invoicing/invoiceList/fetchInvoiceListPageEpic.js +2 -1
  49. package/lib/view/invoicing/invoiceList/invoiceListReducer.d.ts +5 -4
  50. package/lib/view/invoicing/invoiceList/invoiceListReducer.js +63 -37
  51. package/lib/view/invoicing/invoiceList/invoiceListSelector.d.ts +1 -0
  52. package/lib/view/invoicing/invoiceList/invoiceListSelector.js +19 -15
  53. package/lib/view/invoicing/invoiceList/invoiceListState.d.ts +21 -7
  54. package/lib/view/invoicing/invoiceList/invoiceListState.js +13 -1
  55. package/lib/view/invoicing/invoicingCreditNoteListView/epics/fetchInvoicingCreditNoteListEpic.js +0 -3
  56. package/lib/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewReducer.d.ts +1 -1
  57. package/lib/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewReducer.js +8 -2
  58. package/lib/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewSelector.d.ts +1 -0
  59. package/lib/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewSelector.js +23 -13
  60. package/lib/view/invoicing/invoicingCreditNoteListView/invoicingCreditNoteListViewState.d.ts +2 -1
  61. package/lib/view/invoicing/invoicingCustomerDetailView/fetchInvoicingCustomerDetailPageEpic.js +3 -2
  62. package/lib/view/invoicing/invoicingCustomerDetailView/invoicingCustomerDetailViewSelector.js +1 -1
  63. package/lib/view/invoicing/invoicingCustomerListView/epics/fetchInvoicingCustomerListEpic.js +0 -3
  64. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.d.ts +1 -1
  65. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.js +8 -2
  66. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.d.ts +1 -0
  67. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.js +25 -12
  68. package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewState.d.ts +2 -1
  69. package/lib/view/invoicing/invoicingDunningCaseListView/epics/fetchInvoicingDunningCaseListEpic.js +0 -3
  70. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewReducer.d.ts +1 -1
  71. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewReducer.js +8 -2
  72. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewSelector.d.ts +1 -0
  73. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewSelector.js +23 -13
  74. package/lib/view/invoicing/invoicingDunningCaseListView/invoicingDunningCaseListViewState.d.ts +2 -1
  75. package/lib/view/invoicing/invoicingSubscriptionListView/epics/fetchInvoicingSubscriptionListEpic.js +0 -3
  76. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewReducer.d.ts +1 -1
  77. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewReducer.js +8 -2
  78. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewSelector.d.ts +1 -0
  79. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewSelector.js +22 -12
  80. package/lib/view/invoicing/invoicingSubscriptionListView/invoicingSubscriptionListViewState.d.ts +3 -1
  81. package/lib/view/invoicing/invoicingTransactionListView/epics/fetchInvoicingTransactionListEpic.d.ts +1 -1
  82. package/lib/view/invoicing/invoicingTransactionListView/epics/fetchInvoicingTransactionListEpic.js +7 -5
  83. package/lib/view/invoicing/invoicingTransactionListView/epics/fetchInvoicingTransactionListPageEpic.js +2 -1
  84. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewReducer.d.ts +1 -1
  85. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewReducer.js +57 -26
  86. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewSelector.d.ts +1 -0
  87. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewSelector.js +15 -11
  88. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewState.d.ts +18 -4
  89. package/lib/view/invoicing/invoicingTransactionListView/invoicingTransactionListViewState.js +13 -1
  90. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.d.ts +8 -2
  91. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +9 -6
  92. package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.d.ts +0 -1
  93. package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.js +2 -7
  94. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +1 -6
  95. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +3 -48
  96. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +0 -9
  97. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +0 -1
  98. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.js +0 -1
  99. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewSelector.d.ts +1 -0
  100. package/lib/view/spendManagement/billPay/billDetailView/billDetailViewSelector.js +7 -1
  101. package/lib/view/spendManagement/helpers.d.ts +1 -0
  102. package/lib/view/spendManagement/helpers.js +14 -1
  103. package/lib/view/spendManagement/reimbursement/editRemiView/epics/fetchEditRemiDetailPageEpic.d.ts +1 -2
  104. package/lib/view/spendManagement/reimbursement/editRemiView/epics/fetchEditRemiDetailPageEpic.js +0 -12
  105. package/lib/view/spendManagement/reimbursement/remiDetailView/remiDetailViewSelector.d.ts +1 -0
  106. package/lib/view/spendManagement/reimbursement/remiDetailView/remiDetailViewSelector.js +3 -1
  107. package/package.json +1 -1
@@ -7,15 +7,28 @@ import { INVOICING_CUSTOMER_COLUMNS, INVOICING_CUSTOMER_TABS, } from './invoicin
7
7
  * slice actually change. Without this, the connected list page re-rendered on
8
8
  * every unrelated redux action because a fresh array was built each call.
9
9
  */
10
- export const getInvoicingCustomerListView = createSelector((state) => state.invoicingCustomerState, (state) => state.invoicingCustomerListViewState, (customerState, listViewState) => ({
11
- columns: INVOICING_CUSTOMER_COLUMNS,
12
- customers: getInvoicingCustomersByIds(customerState, listViewState.ids),
13
- fetchState: listViewState.fetchState,
14
- error: listViewState.error,
15
- nextCursor: listViewState.nextCursor,
16
- filters: listViewState.filters,
17
- count: listViewState.counts,
18
- countsFetchState: listViewState.countsFetchState,
19
- tabIds: INVOICING_CUSTOMER_TABS,
20
- sort: listViewState.sort,
21
- }));
10
+ export const getInvoicingCustomerListView = createSelector((state) => state.invoicingCustomerState, (state) => state.invoicingCustomerListViewState, (customerState, listViewState) => {
11
+ const { activeTab } = listViewState;
12
+ const allCustomers = getInvoicingCustomersByIds(customerState, listViewState.ids);
13
+ // The list API ignores `status`, so it returns every status; filter to the
14
+ // active tab client-side ('all' shows everything, 'at_risk' keys off the
15
+ // `isAtRisk` flag, the rest match the customer `status`).
16
+ const customers = activeTab === 'all'
17
+ ? allCustomers
18
+ : activeTab === 'at_risk'
19
+ ? allCustomers.filter((customer) => customer.isAtRisk === true)
20
+ : allCustomers.filter((customer) => customer.status === activeTab);
21
+ return {
22
+ activeTab,
23
+ columns: INVOICING_CUSTOMER_COLUMNS,
24
+ customers,
25
+ fetchState: listViewState.fetchState,
26
+ error: listViewState.error,
27
+ nextCursor: listViewState.nextCursor,
28
+ filters: listViewState.filters,
29
+ count: listViewState.counts,
30
+ countsFetchState: listViewState.countsFetchState,
31
+ tabIds: INVOICING_CUSTOMER_TABS,
32
+ sort: listViewState.sort,
33
+ };
34
+ });
@@ -6,9 +6,6 @@ import { INVOICING_LIST_PAGE_SIZE, applyListSortParams, withQuery, } from '../..
6
6
  import { fetchInvoicingDunningCaseList, updateInvoicingDunningCaseList, updateInvoicingDunningCaseListFailure, } from '../invoicingDunningCaseListViewReducer';
7
7
  const buildQuery = (filters, cursor, sort) => {
8
8
  const params = new URLSearchParams();
9
- if (filters.status != null && filters.status.length > 0) {
10
- params.set('status', filters.status);
11
- }
12
9
  if (filters.search != null && filters.search.length > 0) {
13
10
  params.set('search', filters.search);
14
11
  }
@@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit';
2
2
  export const initialState = {
3
3
  fetchState: 'Not-Started',
4
4
  error: undefined,
5
+ activeTab: 'all',
5
6
  ids: [],
6
7
  nextCursor: null,
7
8
  filters: {},
@@ -80,10 +81,15 @@ const invoicingDunningCaseListView = createSlice({
80
81
  updateInvoicingDunningCaseFilters(draft, action) {
81
82
  draft.filters = action.payload;
82
83
  },
84
+ // Select a status tab. The list API ignores `status` (returns all statuses),
85
+ // so the selector filters rows to this tab client-side — no refetch needed.
86
+ setInvoicingDunningCaseActiveTab(draft, action) {
87
+ draft.activeTab = action.payload;
88
+ },
83
89
  clearInvoicingDunningCaseListView(draft) {
84
90
  Object.assign(draft, initialState);
85
91
  },
86
92
  },
87
93
  });
88
- export const { fetchInvoicingDunningCaseList, fetchInvoicingDunningCaseListPage, updateInvoicingDunningCaseList, updateInvoicingDunningCaseListFailure, fetchInvoicingDunningCaseCounts, updateInvoicingDunningCaseCounts, updateInvoicingDunningCaseCountsFailure, updateInvoicingDunningCaseFilters, clearInvoicingDunningCaseListView, } = invoicingDunningCaseListView.actions;
94
+ export const { fetchInvoicingDunningCaseList, fetchInvoicingDunningCaseListPage, updateInvoicingDunningCaseList, updateInvoicingDunningCaseListFailure, fetchInvoicingDunningCaseCounts, updateInvoicingDunningCaseCounts, updateInvoicingDunningCaseCountsFailure, updateInvoicingDunningCaseFilters, setInvoicingDunningCaseActiveTab, clearInvoicingDunningCaseListView, } = invoicingDunningCaseListView.actions;
89
95
  export default invoicingDunningCaseListView.reducer;
@@ -6,16 +6,26 @@ import { INVOICING_DUNNING_CASE_COLUMNS, INVOICING_DUNNING_CASE_TABS, } from './
6
6
  * object) keep referential identity until the dunning-case entity slice or the
7
7
  * list-view slice actually change.
8
8
  */
9
- export const getInvoicingDunningCaseListView = createSelector((state) => state.invoicingDunningCaseState, (state) => state.invoicingDunningCaseListViewState, (dunningCaseState, listViewState) => ({
10
- columns: INVOICING_DUNNING_CASE_COLUMNS,
11
- dunningCases: getInvoicingDunningCasesByIds(dunningCaseState, listViewState.ids),
12
- count: listViewState.counts,
13
- countsFetchState: listViewState.countsFetchState,
14
- filters: listViewState.filters,
15
- nextCursor: listViewState.nextCursor,
16
- fetchState: listViewState.fetchState,
17
- error: listViewState.error,
18
- tabIds: INVOICING_DUNNING_CASE_TABS,
19
- sort: listViewState.sort,
20
- version: 1.0,
21
- }));
9
+ export const getInvoicingDunningCaseListView = createSelector((state) => state.invoicingDunningCaseState, (state) => state.invoicingDunningCaseListViewState, (dunningCaseState, listViewState) => {
10
+ const { activeTab } = listViewState;
11
+ const allDunningCases = getInvoicingDunningCasesByIds(dunningCaseState, listViewState.ids);
12
+ // The list API ignores `status`, so it returns every status; filter to the
13
+ // active tab client-side ('all' shows everything).
14
+ const dunningCases = activeTab === 'all'
15
+ ? allDunningCases
16
+ : allDunningCases.filter((dunningCase) => dunningCase.status === activeTab);
17
+ return {
18
+ activeTab,
19
+ columns: INVOICING_DUNNING_CASE_COLUMNS,
20
+ dunningCases,
21
+ count: listViewState.counts,
22
+ countsFetchState: listViewState.countsFetchState,
23
+ filters: listViewState.filters,
24
+ nextCursor: listViewState.nextCursor,
25
+ fetchState: listViewState.fetchState,
26
+ error: listViewState.error,
27
+ tabIds: INVOICING_DUNNING_CASE_TABS,
28
+ sort: listViewState.sort,
29
+ version: 1.0,
30
+ };
31
+ });
@@ -8,9 +8,6 @@ import { INVOICING_LIST_PAGE_SIZE, applyListSortParams, withQuery, } from '../..
8
8
  import { fetchInvoicingSubscriptionList, updateInvoicingSubscriptionList, updateInvoicingSubscriptionListFailure, } from '../invoicingSubscriptionListViewReducer';
9
9
  const buildQuery = (filters, cursor, sort) => {
10
10
  const params = new URLSearchParams();
11
- if (filters.status != null && filters.status.length > 0) {
12
- params.set('status', filters.status);
13
- }
14
11
  if (filters.customerId != null) {
15
12
  params.set('customer_id', filters.customerId);
16
13
  }
@@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit';
2
2
  export const initialState = {
3
3
  fetchState: 'Not-Started',
4
4
  error: undefined,
5
+ activeTab: 'all',
5
6
  ids: [],
6
7
  nextCursor: null,
7
8
  filters: {},
@@ -80,6 +81,11 @@ const invoicingSubscriptionListView = createSlice({
80
81
  updateInvoicingSubscriptionFilters(draft, action) {
81
82
  draft.filters = action.payload;
82
83
  },
84
+ // Select a status tab. The list API ignores `status` (returns all statuses),
85
+ // so the selector filters rows to this tab client-side — no refetch needed.
86
+ setInvoicingSubscriptionActiveTab(draft, action) {
87
+ draft.activeTab = action.payload;
88
+ },
83
89
  // Reflect a newly created subscription in the cached list + tab counts so the
84
90
  // subscription page shows it without refetching from the server. The status
85
91
  // doubles as the per-tab count key (tab ids mirror InvoicingSubscriptionStatus).
@@ -99,5 +105,5 @@ const invoicingSubscriptionListView = createSlice({
99
105
  },
100
106
  },
101
107
  });
102
- export const { fetchInvoicingSubscriptionList, fetchInvoicingSubscriptionListPage, updateInvoicingSubscriptionList, updateInvoicingSubscriptionListFailure, fetchInvoicingSubscriptionCounts, updateInvoicingSubscriptionCounts, updateInvoicingSubscriptionCountsFailure, updateInvoicingSubscriptionFilters, addInvoicingSubscriptionToList, clearInvoicingSubscriptionListView, } = invoicingSubscriptionListView.actions;
108
+ export const { fetchInvoicingSubscriptionList, fetchInvoicingSubscriptionListPage, updateInvoicingSubscriptionList, updateInvoicingSubscriptionListFailure, fetchInvoicingSubscriptionCounts, updateInvoicingSubscriptionCounts, updateInvoicingSubscriptionCountsFailure, updateInvoicingSubscriptionFilters, setInvoicingSubscriptionActiveTab, addInvoicingSubscriptionToList, clearInvoicingSubscriptionListView, } = invoicingSubscriptionListView.actions;
103
109
  export default invoicingSubscriptionListView.reducer;
@@ -8,15 +8,25 @@ import { INVOICING_SUBSCRIPTION_COLUMNS, INVOICING_SUBSCRIPTION_TABS, } from './
8
8
  * re-rendered on every unrelated redux action because a fresh array was built
9
9
  * each call.
10
10
  */
11
- export const getInvoicingSubscriptionListView = createSelector((state) => state.invoicingSubscriptionState, (state) => state.invoicingSubscriptionListViewState, (subscriptionState, listViewState) => ({
12
- columns: INVOICING_SUBSCRIPTION_COLUMNS,
13
- subscriptions: getInvoicingSubscriptionsByIds(subscriptionState, listViewState.ids),
14
- fetchState: listViewState.fetchState,
15
- error: listViewState.error,
16
- nextCursor: listViewState.nextCursor,
17
- filters: listViewState.filters,
18
- count: listViewState.counts,
19
- countsFetchState: listViewState.countsFetchState,
20
- tabIds: INVOICING_SUBSCRIPTION_TABS,
21
- sort: listViewState.sort,
22
- }));
11
+ export const getInvoicingSubscriptionListView = createSelector((state) => state.invoicingSubscriptionState, (state) => state.invoicingSubscriptionListViewState, (subscriptionState, listViewState) => {
12
+ const { activeTab } = listViewState;
13
+ const allSubscriptions = getInvoicingSubscriptionsByIds(subscriptionState, listViewState.ids);
14
+ // The list API ignores `status`, so it returns every status; filter to the
15
+ // active tab client-side ('all' shows everything).
16
+ const subscriptions = activeTab === 'all'
17
+ ? allSubscriptions
18
+ : allSubscriptions.filter((subscription) => subscription.status === activeTab);
19
+ return {
20
+ activeTab,
21
+ columns: INVOICING_SUBSCRIPTION_COLUMNS,
22
+ subscriptions,
23
+ fetchState: listViewState.fetchState,
24
+ error: listViewState.error,
25
+ nextCursor: listViewState.nextCursor,
26
+ filters: listViewState.filters,
27
+ count: listViewState.counts,
28
+ countsFetchState: listViewState.countsFetchState,
29
+ tabIds: INVOICING_SUBSCRIPTION_TABS,
30
+ sort: listViewState.sort,
31
+ };
32
+ });
@@ -4,13 +4,13 @@ import { updateInvoicingTransactions } from '../../../../entity/invoicing/invoic
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
5
5
  import { INVOICING_LIST_PAGE_SIZE, applyListSortParams, withQuery, } from '../../invoicingApiHelper';
6
6
  import { fetchInvoicingTransactionList, updateInvoicingTransactionList, updateInvoicingTransactionListFailure, } from '../invoicingTransactionListViewReducer';
7
- const buildQuery = (filters, cursor, sort) => {
7
+ const buildQuery = (filters, type, cursor, sort) => {
8
8
  const params = new URLSearchParams();
9
9
  if (filters.status != null && filters.status.length > 0) {
10
10
  params.set('status', filters.status);
11
11
  }
12
- if (filters.type != null && filters.type.length > 0) {
13
- params.set('type', filters.type);
12
+ if (type != null && type.length > 0) {
13
+ params.set('type', type);
14
14
  }
15
15
  if (filters.customerId != null) {
16
16
  params.set('customer_id', filters.customerId);
@@ -25,9 +25,11 @@ const buildQuery = (filters, cursor, sort) => {
25
25
  applyListSortParams(params, sort);
26
26
  return params;
27
27
  };
28
- export const fetchInvoicingTransactionListEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingTransactionList.match), switchMap((action) => {
28
+ export const fetchInvoicingTransactionListEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchInvoicingTransactionList.match), switchMap((action) => {
29
29
  const { filters, cursor, keepExistingListItems, sort } = action.payload;
30
- const apiUrl = withQuery(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/payments`, buildQuery(filters, cursor, sort));
30
+ const { activeTab } = state$.value.invoicingTransactionListViewState;
31
+ const type = activeTab !== 'all' ? activeTab : undefined;
32
+ const apiUrl = withQuery(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/payments`, buildQuery(filters, type, cursor, sort));
31
33
  return zeniAPI.getJSON(apiUrl).pipe(switchMap((response) => {
32
34
  if (isSuccessResponse(response)) {
33
35
  const items = response.data?.items ?? [];
@@ -16,8 +16,9 @@ const shouldFetch = (fetchStateAndError, cacheOverride) => cacheOverride ||
16
16
  export const fetchInvoicingTransactionListPageEpic = (actions$, state$) => actions$.pipe(filter(fetchInvoicingTransactionListPage.match), mergeMap((action) => {
17
17
  const { cacheOverride, filters, sort } = action.payload;
18
18
  const state = state$.value.invoicingTransactionListViewState;
19
+ const activeTabState = state.byTab[state.activeTab];
19
20
  const pageActions = [];
20
- if (shouldFetch({ fetchState: state.fetchState, error: state.error }, cacheOverride)) {
21
+ if (shouldFetch({ fetchState: activeTabState.fetchState, error: activeTabState.error }, cacheOverride)) {
21
22
  pageActions.push(fetchInvoicingTransactionList({ filters, sort }));
22
23
  }
23
24
  return concat(from(pageActions));
@@ -1,13 +1,19 @@
1
1
  import { createSlice } from '@reduxjs/toolkit';
2
+ import { makeInvoicingTransactionIdsByTab, } from './invoicingTransactionListViewState';
3
+ /**
4
+ * Whether two transaction-list queries share the same base (tab-independent)
5
+ * query. When the base query changes (search / customer / status / sort) every
6
+ * per-tab cache is stale, so the reducer resets them all.
7
+ */
8
+ const sameTransactionBaseQuery = (a, b) => a.filters.customerId === b.filters.customerId &&
9
+ a.filters.status === b.filters.status &&
10
+ a.filters.search === b.filters.search &&
11
+ a.sort?.field === b.sort?.field &&
12
+ a.sort?.dir === b.sort?.dir;
2
13
  export const initialState = {
3
- fetchState: 'Not-Started',
4
- error: undefined,
5
- ids: [],
6
- nextCursor: null,
14
+ activeTab: 'all',
15
+ byTab: makeInvoicingTransactionIdsByTab(),
7
16
  filters: {},
8
- hasValidState() {
9
- return this.fetchState === 'Completed';
10
- },
11
17
  };
12
18
  const invoicingTransactionListView = createSlice({
13
19
  name: 'invoicingTransactionListView',
@@ -15,14 +21,15 @@ const invoicingTransactionListView = createSlice({
15
21
  reducers: {
16
22
  fetchInvoicingTransactionList: {
17
23
  reducer(draft, action) {
24
+ const tab = draft.byTab[draft.activeTab];
18
25
  if (!action.payload.keepExistingListItems) {
19
- draft.ids = [];
20
- draft.nextCursor = null;
26
+ tab.ids = [];
27
+ tab.nextCursor = null;
21
28
  }
22
29
  draft.filters = action.payload.filters;
23
30
  draft.sort = action.payload.sort;
24
- draft.fetchState = 'In-Progress';
25
- draft.error = undefined;
31
+ tab.fetchState = 'In-Progress';
32
+ tab.error = undefined;
26
33
  },
27
34
  prepare(payload) {
28
35
  return {
@@ -37,6 +44,12 @@ const invoicingTransactionListView = createSlice({
37
44
  },
38
45
  fetchInvoicingTransactionListPage: {
39
46
  reducer(draft, action) {
47
+ // A different base query invalidates every tab's cache — reset so the
48
+ // page epic's shouldFetch refetches the active tab instead of showing
49
+ // rows fetched under a stale search/customer/status/sort.
50
+ if (!sameTransactionBaseQuery(draft, action.payload)) {
51
+ draft.byTab = makeInvoicingTransactionIdsByTab();
52
+ }
40
53
  draft.filters = action.payload.filters;
41
54
  draft.sort = action.payload.sort;
42
55
  },
@@ -51,34 +64,52 @@ const invoicingTransactionListView = createSlice({
51
64
  },
52
65
  },
53
66
  updateInvoicingTransactionList(draft, action) {
54
- draft.ids = action.payload.keepExistingListItems
55
- ? [...new Set([...draft.ids, ...action.payload.ids])]
67
+ const tab = draft.byTab[draft.activeTab];
68
+ tab.ids = action.payload.keepExistingListItems
69
+ ? [...new Set([...tab.ids, ...action.payload.ids])]
56
70
  : action.payload.ids;
57
- draft.nextCursor = action.payload.nextCursor;
58
- draft.fetchState = 'Completed';
59
- draft.error = undefined;
71
+ tab.nextCursor = action.payload.nextCursor;
72
+ tab.fetchState = 'Completed';
73
+ tab.error = undefined;
60
74
  },
61
75
  updateInvoicingTransactionListFailure(draft, action) {
62
- draft.fetchState = 'Error';
63
- draft.error = action.payload;
76
+ const tab = draft.byTab[draft.activeTab];
77
+ tab.fetchState = 'Error';
78
+ tab.error = action.payload;
64
79
  },
65
80
  updateInvoicingTransactionFilters(draft, action) {
66
81
  draft.filters = action.payload;
67
82
  },
83
+ // Select a type tab. The view dispatches the follow-up
84
+ // fetchInvoicingTransactionListPage so the page epic fetches the tab when
85
+ // its cache is empty and reuses it when already loaded.
86
+ setInvoicingTransactionActiveTab(draft, action) {
87
+ draft.activeTab = action.payload;
88
+ },
68
89
  // Reflect a newly recorded payment's transaction in the cached list so the
69
- // payments page shows it without refetching. The list view has no counts
70
- // slice, so we only prepend the id (dedup/no-op if already present).
90
+ // payments page shows it without refetching. The payload carries no type,
91
+ // so prepend to the 'all' tab and the tab currently in view (dedup/no-op if
92
+ // already present); other tabs refetch fresh on their next visit.
71
93
  addInvoicingTransactionToList(draft, action) {
72
94
  const id = action.payload;
73
- if (draft.ids.includes(id)) {
74
- return;
95
+ const prependTo = (tabId) => {
96
+ const tab = draft.byTab[tabId];
97
+ if (!tab.ids.includes(id)) {
98
+ tab.ids = [id, ...tab.ids];
99
+ }
100
+ };
101
+ prependTo('all');
102
+ if (draft.activeTab !== 'all') {
103
+ prependTo(draft.activeTab);
75
104
  }
76
- draft.ids = [id, ...draft.ids];
77
105
  },
78
106
  clearInvoicingTransactionListView(draft) {
79
- Object.assign(draft, initialState);
107
+ draft.activeTab = 'all';
108
+ draft.byTab = makeInvoicingTransactionIdsByTab();
109
+ draft.filters = {};
110
+ draft.sort = undefined;
80
111
  },
81
112
  },
82
113
  });
83
- export const { fetchInvoicingTransactionList, fetchInvoicingTransactionListPage, updateInvoicingTransactionList, updateInvoicingTransactionListFailure, updateInvoicingTransactionFilters, addInvoicingTransactionToList, clearInvoicingTransactionListView, } = invoicingTransactionListView.actions;
114
+ export const { fetchInvoicingTransactionList, fetchInvoicingTransactionListPage, updateInvoicingTransactionList, updateInvoicingTransactionListFailure, updateInvoicingTransactionFilters, setInvoicingTransactionActiveTab, addInvoicingTransactionToList, clearInvoicingTransactionListView, } = invoicingTransactionListView.actions;
84
115
  export default invoicingTransactionListView.reducer;
@@ -6,14 +6,18 @@ import { INVOICING_TRANSACTION_COLUMNS, INVOICING_TRANSACTION_TABS, } from './in
6
6
  * object) keep referential identity until the transaction entity slice or the
7
7
  * list-view slice actually change.
8
8
  */
9
- export const getInvoicingTransactionListView = createSelector((state) => state.invoicingTransactionState, (state) => state.invoicingTransactionListViewState, (transactionState, listViewState) => ({
10
- columns: INVOICING_TRANSACTION_COLUMNS,
11
- transactions: getInvoicingTransactionsByIds(transactionState, listViewState.ids),
12
- filters: listViewState.filters,
13
- nextCursor: listViewState.nextCursor,
14
- fetchState: listViewState.fetchState,
15
- error: listViewState.error,
16
- tabIds: INVOICING_TRANSACTION_TABS,
17
- sort: listViewState.sort,
18
- version: 1.0,
19
- }));
9
+ export const getInvoicingTransactionListView = createSelector((state) => state.invoicingTransactionState, (state) => state.invoicingTransactionListViewState, (transactionState, listViewState) => {
10
+ const activeTabState = listViewState.byTab[listViewState.activeTab];
11
+ return {
12
+ activeTab: listViewState.activeTab,
13
+ columns: INVOICING_TRANSACTION_COLUMNS,
14
+ transactions: getInvoicingTransactionsByIds(transactionState, activeTabState.ids),
15
+ filters: listViewState.filters,
16
+ nextCursor: activeTabState.nextCursor,
17
+ fetchState: activeTabState.fetchState,
18
+ error: activeTabState.error,
19
+ tabIds: INVOICING_TRANSACTION_TABS,
20
+ sort: listViewState.sort,
21
+ version: 1.0,
22
+ };
23
+ });
@@ -37,3 +37,13 @@ export const toInvoicingTransactionTabId = (v) => stringToUnion(v, ALL_INVOICING
37
37
  export const INVOICING_TRANSACTION_TABS = [
38
38
  ...ALL_INVOICING_TRANSACTION_TAB_IDS,
39
39
  ];
40
+ export const makeInvoicingTransactionTabState = () => ({
41
+ ids: [],
42
+ nextCursor: null,
43
+ fetchState: 'Not-Started',
44
+ error: undefined,
45
+ });
46
+ export const makeInvoicingTransactionIdsByTab = () => ALL_INVOICING_TRANSACTION_TAB_IDS.reduce((acc, tabId) => {
47
+ acc[tabId] = makeInvoicingTransactionTabState();
48
+ return acc;
49
+ }, {});
@@ -1,12 +1,17 @@
1
- import { EMPTY, of } from 'rxjs';
2
- import { catchError, filter, finalize, mergeMap, switchMap, takeUntil, withLatestFrom, } from 'rxjs/operators';
1
+ import { EMPTY, concat, of } from 'rxjs';
2
+ import { catchError, debounceTime, filter, finalize, mergeMap, switchMap, takeUntil, withLatestFrom, } from 'rxjs/operators';
3
3
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
4
4
  import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
5
5
  import { notificationPreferencesUrl } from '../notificationPreferencesEndpoint';
6
6
  import { mapPreferencesToPayload, } from '../notificationPreferencesViewPayload';
7
- import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess, } from '../notificationPreferencesViewReducer';
7
+ import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess, setGroupFrequency, toggleEventChannel, } from '../notificationPreferencesViewReducer';
8
8
  import { getNotificationLocalOverrides } from '../notificationPreferencesViewSelector';
9
- export const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter((action) => saveNotificationPreferences.match(action)), withLatestFrom(state$),
9
+ const DEBOUNCE_MS = 300;
10
+ export const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter((action) => toggleEventChannel.match(action) || setGroupFrequency.match(action)),
11
+ // Coalesce bursts of toggles into a single PUT. Trade-off: rapid clickers
12
+ // defer saves until they pause. Consider a max-wait wrapper if this
13
+ // becomes a UX problem.
14
+ debounceTime(DEBOUNCE_MS), withLatestFrom(state$),
10
15
  // `switchMap` unsubscribes from any in-flight PUT observable when a new
11
16
  // debounced batch fires. The prior HTTP request has already left the
12
17
  // client — the network call is NOT cancelled — but its response is
@@ -105,9 +110,7 @@ switchMap(([, state]) => {
105
110
  // under patch-semantic LWW. Cursor Bugbot 3637969823 + 3641372002.
106
111
  takeUntil(actions$.pipe(filter((action) => clearNotificationPreferencesLocalOverrides.match(action) ||
107
112
  clearAllNotificationPreferencesView.match(action)))));
108
- // `saveNotificationPreferences` (the trigger) already flipped
109
- // savePreferencesState to In-Progress via its reducer case, so the PUT
110
- // observable is returned directly — re-emitting the trigger here would
111
- // loop the epic on itself.
112
- return request$;
113
+ // Flip savePreferencesState to In-Progress BEFORE the network call so
114
+ // any "saving…" UI can render.
115
+ return concat(of(saveNotificationPreferences()), request$);
113
116
  }));
@@ -1,5 +1,5 @@
1
1
  import { toNotificationChannel, toNotificationFrequency, } from '../../entity/notificationRegistry/notificationRegistryState';
2
- const mapChannelEnabledMap = (raw) => {
2
+ const mapEventEnabledByChannel = (raw) => {
3
3
  if (raw == null) {
4
4
  return {};
5
5
  }
@@ -20,8 +20,7 @@ export const mapPayloadToPreferences = (payload) => {
20
20
  toNotificationFrequency(frequency),
21
21
  ]));
22
22
  return {
23
- eventEnabledByChannel: mapChannelEnabledMap(payload?.event_enabled_by_channel),
24
- groupChannelEnabledByGroupId: mapChannelEnabledMap(payload?.group_channel_enabled_by_group_id),
23
+ eventEnabledByChannel: mapEventEnabledByChannel(payload?.event_enabled_by_channel),
25
24
  groupFrequencyByGroupId,
26
25
  };
27
26
  };
@@ -30,10 +29,6 @@ export const mapPreferencesToPayload = (preferences) => {
30
29
  if (Object.keys(preferences.eventEnabledByChannel).length > 0) {
31
30
  payload.event_enabled_by_channel = preferences.eventEnabledByChannel;
32
31
  }
33
- if (Object.keys(preferences.groupChannelEnabledByGroupId).length > 0) {
34
- payload.group_channel_enabled_by_group_id =
35
- preferences.groupChannelEnabledByGroupId;
36
- }
37
32
  if (Object.keys(preferences.groupFrequencyByGroupId).length > 0) {
38
33
  payload.group_frequency_by_group_id = preferences.groupFrequencyByGroupId;
39
34
  }
@@ -18,18 +18,8 @@ const mergePreferences = (base, patch) => {
18
18
  ...channels,
19
19
  };
20
20
  });
21
- const groupChannelEnabledByGroupId = {
22
- ...base.groupChannelEnabledByGroupId,
23
- };
24
- Object.entries(patch.groupChannelEnabledByGroupId).forEach(([groupId, channels]) => {
25
- groupChannelEnabledByGroupId[groupId] = {
26
- ...(base.groupChannelEnabledByGroupId[groupId] ?? {}),
27
- ...channels,
28
- };
29
- });
30
21
  return {
31
22
  eventEnabledByChannel,
32
- groupChannelEnabledByGroupId,
33
23
  groupFrequencyByGroupId: {
34
24
  ...base.groupFrequencyByGroupId,
35
25
  ...patch.groupFrequencyByGroupId,
@@ -71,38 +61,13 @@ const subtractSavedOverrides = (current, saved) => {
71
61
  eventEnabledByChannel[eventId] = remainingChannels;
72
62
  }
73
63
  });
74
- // Same value-equality subtraction as eventEnabledByChannel above — a
75
- // group-channel master the user re-flipped while a PUT was in flight must
76
- // survive so the next debounce cycle re-sends it.
77
- const groupChannelEnabledByGroupId = {};
78
- Object.entries(current.groupChannelEnabledByGroupId).forEach(([groupId, channels]) => {
79
- const savedChannels = saved.groupChannelEnabledByGroupId[groupId];
80
- if (savedChannels == null) {
81
- groupChannelEnabledByGroupId[groupId] = channels;
82
- return;
83
- }
84
- const remainingChannels = {};
85
- Object.keys(channels).forEach((channel) => {
86
- if (!(channel in savedChannels) ||
87
- channels[channel] !== savedChannels[channel]) {
88
- remainingChannels[channel] = channels[channel];
89
- }
90
- });
91
- if (Object.keys(remainingChannels).length > 0) {
92
- groupChannelEnabledByGroupId[groupId] = remainingChannels;
93
- }
94
- });
95
64
  const groupFrequencyByGroupId = {};
96
65
  Object.entries(current.groupFrequencyByGroupId).forEach(([groupId, frequency]) => {
97
66
  if (saved.groupFrequencyByGroupId[groupId] !== frequency) {
98
67
  groupFrequencyByGroupId[groupId] = frequency;
99
68
  }
100
69
  });
101
- return {
102
- eventEnabledByChannel,
103
- groupChannelEnabledByGroupId,
104
- groupFrequencyByGroupId,
105
- };
70
+ return { eventEnabledByChannel, groupFrequencyByGroupId };
106
71
  };
107
72
  const notificationPreferencesView = createSlice({
108
73
  name: 'notificationPreferencesView',
@@ -239,16 +204,6 @@ const notificationPreferencesView = createSlice({
239
204
  draft.preferences = mergePreferences(draft.preferences, patch);
240
205
  draft.localOverrides = subtractSavedOverrides(draft.localOverrides, action.payload.savedOverrides);
241
206
  },
242
- setGroupChannelMaster(draft, action) {
243
- // Mirror of `toggleEventChannel` but scoped to the group-channel master
244
- // held under `groupChannelEnabledByGroupId[groupId][channel]`.
245
- const { channel, enabled, groupId } = action.payload;
246
- const groupMap = {
247
- ...(draft.localOverrides.groupChannelEnabledByGroupId[groupId] ?? {}),
248
- };
249
- groupMap[channel] = enabled;
250
- draft.localOverrides.groupChannelEnabledByGroupId[groupId] = groupMap;
251
- },
252
207
  setGroupFrequency(draft, action) {
253
208
  draft.localOverrides.groupFrequencyByGroupId[action.payload.groupId] =
254
209
  action.payload.frequency;
@@ -263,5 +218,5 @@ const notificationPreferencesView = createSlice({
263
218
  },
264
219
  },
265
220
  });
266
- export const { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, fetchNotificationPreferencesSuccess, saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, } = notificationPreferencesView.actions;
221
+ export const { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, fetchNotificationPreferencesSuccess, saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess, setGroupFrequency, toggleEventChannel, } = notificationPreferencesView.actions;
267
222
  export default notificationPreferencesView.reducer;
@@ -9,16 +9,8 @@ export const getEffectiveNotificationPreferences = (state) => {
9
9
  ...channels,
10
10
  };
11
11
  });
12
- const mergedGroupChannelEnabledByGroupId = { ...view.preferences.groupChannelEnabledByGroupId };
13
- Object.entries(view.localOverrides.groupChannelEnabledByGroupId).forEach(([groupId, channels]) => {
14
- mergedGroupChannelEnabledByGroupId[groupId] = {
15
- ...(view.preferences.groupChannelEnabledByGroupId[groupId] ?? {}),
16
- ...channels,
17
- };
18
- });
19
12
  return {
20
13
  eventEnabledByChannel: mergedEventEnabledByChannel,
21
- groupChannelEnabledByGroupId: mergedGroupChannelEnabledByGroupId,
22
14
  groupFrequencyByGroupId: {
23
15
  ...view.preferences.groupFrequencyByGroupId,
24
16
  ...view.localOverrides.groupFrequencyByGroupId,
@@ -29,6 +21,5 @@ export const getNotificationLocalOverrides = (state) => getView(state).localOver
29
21
  export const hasUnsavedNotificationPreferences = (state) => {
30
22
  const overrides = getView(state).localOverrides;
31
23
  return (Object.keys(overrides.eventEnabledByChannel).length > 0 ||
32
- Object.keys(overrides.groupChannelEnabledByGroupId).length > 0 ||
33
24
  Object.keys(overrides.groupFrequencyByGroupId).length > 0);
34
25
  };
@@ -1,5 +1,4 @@
1
1
  export const emptyNotificationPreferences = () => ({
2
2
  eventEnabledByChannel: {},
3
- groupChannelEnabledByGroupId: {},
4
3
  groupFrequencyByGroupId: {},
5
4
  });
@@ -13,7 +13,7 @@ import { getUserByUserId } from '../../../../entity/user/userSelector';
13
13
  import { getUserRoleByUserId } from '../../../../entity/userRole/userRoleSelector';
14
14
  import { getActivityHistory, } from '../../commonHistoryView/commonHistorySelector';
15
15
  import { getAllFundingAccounts, } from '../../commonSetup/setupViewSelector';
16
- import { getLatestApprovalDate, isAwaitingMarkAsPaid, isSodAdminFallbackEligibleForStep, isSodExcludedViewerBlocked, } from '../../helpers';
16
+ import { getLatestApprovalDate, isAwaitingMarkAsPaid, isSodAdminFallbackEligibleForStep, isSodExcludedViewerBlocked, shouldShowSodAdminLabel, } from '../../helpers';
17
17
  import { getActivityRealTimeApproval, } from '../../realTimeApprovalView/realTimeApprovalSelector';
18
18
  import { getEntityRealTimeApprovalDetailKey } from '../../realTimeApprovalView/realTimeApprovalState';
19
19
  import { getBillPaymentSubConfigCodeKey, getInternationalSubConfigCodeKey, getInternationalWireMethodCodeKey, } from '../billPayConfig/billPayConfigPayload';
@@ -22,7 +22,12 @@ import { getBillPayConfigMethodKey, toBillPaymentSubConfigCodeKeyType, } from '.
22
22
  import { getPreviousBills, } from '../previousBills/previousBillsSelector';
23
23
  import { getBillTransactionDetailKey, initialBillDetailView, } from './billDetailViewState';
24
24
  export const getBillActivities = (state, billId, billStageCode) => {
25
- const { billDetailViewState, approvalRuleState, entityApprovalStatusState, userState, } = state;
25
+ const { billDetailViewState, approvalRuleState, entityApprovalStatusState, userState, billTransactionState, } = state;
26
+ // Note: createdBy is undefined until billTransactionState is populated.
27
+ // getBillActivities is called from getBillDetailView which runs after the
28
+ // bill entity epic completes — this assumption must hold for showSodAdminLabel
29
+ // to render on first paint.
30
+ const createdBy = billTransactionState.transactionById[billId]?.createdBy;
26
31
  const key = getBillTransactionDetailKey(billId);
27
32
  const billDetail = recordGet(billDetailViewState.billDetailById, key, undefined);
28
33
  let billActivities = [];
@@ -85,6 +90,7 @@ export const getBillActivities = (state, billId, billStageCode) => {
85
90
  actorsActivity,
86
91
  isOriginalApprovalStep: step.isOriginalApprovalStep,
87
92
  displayPendingApproverAsAdmin: isSodAdminFallbackEligibleForStep(sodExcludedApproverUserId, isAdminFallbackApprover, actorsActivity),
93
+ showSodAdminLabel: shouldShowSodAdminLabel(sodExcludedApproverUserId, actorsActivity, createdBy),
88
94
  };
89
95
  }),
90
96
  };