@zeniai/client-epic-state 5.1.61 → 5.1.62-betaRD2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/lib/coreEpics.js +3 -2
  2. package/lib/entity/chargeCard/chargeCard.d.ts +1 -1
  3. package/lib/entity/jeSchedules/jeSchedulesPayload.d.ts +2 -0
  4. package/lib/entity/jeSchedules/jeSchedulesPayload.js +2 -0
  5. package/lib/entity/jeSchedules/jeSchedulesSelector.d.ts +4 -0
  6. package/lib/entity/jeSchedules/jeSchedulesSelector.js +11 -1
  7. package/lib/entity/jeSchedules/jeSchedulesState.d.ts +2 -0
  8. package/lib/entity/notificationRegistry/notificationRegistryPayload.d.ts +18 -0
  9. package/lib/entity/notificationRegistry/notificationRegistryPayload.js +28 -0
  10. package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +10 -0
  11. package/lib/entity/notificationRegistry/notificationRegistryReducer.js +50 -0
  12. package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +2 -0
  13. package/lib/entity/notificationRegistry/notificationRegistrySelector.js +10 -0
  14. package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +32 -0
  15. package/lib/entity/notificationRegistry/notificationRegistryState.js +10 -0
  16. package/lib/entity/snackbar/snackbarTypes.d.ts +1 -1
  17. package/lib/entity/snackbar/snackbarTypes.js +3 -0
  18. package/lib/entity/tenant/clearAllEpic.d.ts +3 -1
  19. package/lib/entity/tenant/clearAllEpic.js +4 -0
  20. package/lib/entity/tenant/tenantReducer.d.ts +4 -1
  21. package/lib/entity/tenant/tenantReducer.js +8 -2
  22. package/lib/entity/tenant/tenantSelector.d.ts +1 -0
  23. package/lib/entity/tenant/tenantSelector.js +4 -0
  24. package/lib/entity/transaction/scheduleTransactionHelper.d.ts +15 -0
  25. package/lib/entity/transaction/scheduleTransactionHelper.js +43 -0
  26. package/lib/epic.d.ts +9 -1
  27. package/lib/epic.js +9 -1
  28. package/lib/esm/coreEpics.js +3 -2
  29. package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +2 -0
  30. package/lib/esm/entity/jeSchedules/jeSchedulesSelector.js +11 -1
  31. package/lib/esm/entity/notificationRegistry/notificationRegistryPayload.js +24 -0
  32. package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +46 -0
  33. package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +6 -0
  34. package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +5 -0
  35. package/lib/esm/entity/snackbar/snackbarTypes.js +3 -0
  36. package/lib/esm/entity/tenant/clearAllEpic.js +4 -0
  37. package/lib/esm/entity/tenant/tenantReducer.js +7 -1
  38. package/lib/esm/entity/tenant/tenantSelector.js +3 -0
  39. package/lib/esm/entity/transaction/scheduleTransactionHelper.js +39 -0
  40. package/lib/esm/epic.js +9 -1
  41. package/lib/esm/index.js +14 -6
  42. package/lib/esm/reducer.js +6 -0
  43. package/lib/esm/view/companyView/companyViewReducer.js +68 -1
  44. package/lib/esm/view/companyView/epic/companyPassport/dismissCapitalizationOnboardingEpic.js +2 -1
  45. package/lib/esm/view/companyView/epic/companyPassport/projects/disableAccountingProjectsEpic.js +56 -0
  46. package/lib/esm/view/companyView/epic/companyPassport/projects/enableAccountingProjectsEpic.js +37 -0
  47. package/lib/esm/view/companyView/epic/companyPassport/projects/resumeEnableAccountingProjectsEpic.js +73 -0
  48. package/lib/esm/view/companyView/epic/companyPassport/updateAccountingClassesEnabledEpic.js +2 -1
  49. package/lib/esm/view/companyView/epic/companyPassport/updateCapitalizationAccountThresholdEpic.js +2 -1
  50. package/lib/esm/view/companyView/types/companyPassport/companyPassportViewState.js +5 -0
  51. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +15 -2
  52. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.js +21 -0
  53. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.js +110 -0
  54. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.js +44 -0
  55. package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +22 -0
  56. package/lib/esm/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.js +152 -0
  57. package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +126 -6
  58. package/lib/esm/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.js +1 -0
  59. package/lib/esm/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.js +71 -0
  60. package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +78 -11
  61. package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +3 -6
  62. package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +116 -0
  63. package/lib/esm/view/notificationPreferencesView/notificationPreferencesEndpoint.js +5 -0
  64. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewPayload.js +36 -0
  65. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +222 -0
  66. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +25 -0
  67. package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewState.js +4 -0
  68. package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +34 -2
  69. package/lib/esm/view/settingsView/epic/saveNotificationSettingsEpic.js +2 -1
  70. package/lib/esm/view/settingsView/settingsViewHelpers.js +3 -2
  71. package/lib/index.d.ts +17 -8
  72. package/lib/index.js +85 -47
  73. package/lib/reducer.d.ts +6 -0
  74. package/lib/reducer.js +6 -0
  75. package/lib/view/common/recurringViewHelper.d.ts +2 -2
  76. package/lib/view/companyView/companyViewReducer.d.ts +9 -1
  77. package/lib/view/companyView/companyViewReducer.js +70 -3
  78. package/lib/view/companyView/epic/companyPassport/dismissCapitalizationOnboardingEpic.js +2 -1
  79. package/lib/view/companyView/epic/companyPassport/projects/disableAccountingProjectsEpic.d.ts +10 -0
  80. package/lib/view/companyView/epic/companyPassport/projects/disableAccountingProjectsEpic.js +60 -0
  81. package/lib/view/companyView/epic/companyPassport/projects/enableAccountingProjectsEpic.d.ts +11 -0
  82. package/lib/view/companyView/epic/companyPassport/projects/enableAccountingProjectsEpic.js +41 -0
  83. package/lib/view/companyView/epic/companyPassport/projects/resumeEnableAccountingProjectsEpic.d.ts +13 -0
  84. package/lib/view/companyView/epic/companyPassport/projects/resumeEnableAccountingProjectsEpic.js +77 -0
  85. package/lib/view/companyView/epic/companyPassport/updateAccountingClassesEnabledEpic.js +2 -1
  86. package/lib/view/companyView/epic/companyPassport/updateCapitalizationAccountThresholdEpic.js +2 -1
  87. package/lib/view/companyView/types/companyPassport/companyPassportViewState.d.ts +2 -0
  88. package/lib/view/companyView/types/companyPassport/companyPassportViewState.js +5 -0
  89. package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +14 -1
  90. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.d.ts +8 -0
  91. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.js +25 -0
  92. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.d.ts +45 -0
  93. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.js +114 -0
  94. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.d.ts +8 -0
  95. package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.js +48 -0
  96. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.d.ts +6 -0
  97. package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +26 -0
  98. package/lib/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.d.ts +43 -0
  99. package/lib/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.js +158 -0
  100. package/lib/view/expenseAutomationView/helpers/transactionCategorizationLocalDataHelper.d.ts +1 -1
  101. package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.d.ts +1 -0
  102. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +36 -8
  103. package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +128 -7
  104. package/lib/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.d.ts +25 -0
  105. package/lib/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.js +2 -0
  106. package/lib/view/expenseAutomationView/selectorTypes/jeSchedulesSelectorTypes.d.ts +29 -4
  107. package/lib/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.d.ts +10 -0
  108. package/lib/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.js +76 -0
  109. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.d.ts +1 -0
  110. package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +80 -11
  111. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +53 -6
  112. package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +5 -8
  113. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.d.ts +40 -0
  114. package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +120 -0
  115. package/lib/view/notificationPreferencesView/notificationPreferencesEndpoint.d.ts +2 -0
  116. package/lib/view/notificationPreferencesView/notificationPreferencesEndpoint.js +9 -0
  117. package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.d.ts +7 -0
  118. package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.js +41 -0
  119. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +30 -0
  120. package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +226 -0
  121. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +7 -0
  122. package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +32 -0
  123. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +14 -0
  124. package/lib/view/notificationPreferencesView/notificationPreferencesViewState.js +8 -0
  125. package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.d.ts +9 -1
  126. package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +34 -2
  127. package/lib/view/settingsView/epic/saveNotificationSettingsEpic.js +2 -1
  128. package/lib/view/settingsView/settingsViewHelpers.d.ts +1 -1
  129. package/lib/view/settingsView/settingsViewHelpers.js +3 -2
  130. package/lib/view/settingsView/settingsViewPayload.d.ts +4 -0
  131. package/lib/view/settingsView/settingsViewState.d.ts +1 -1
  132. package/package.json +1 -1
@@ -4,6 +4,7 @@ import { toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
4
4
  import { mapAccountBasePayloadToAccountBase, mapAccountMetadataPayloadToAccountMetadata, } from '../../../entity/account/accountPayload';
5
5
  import { getJEScheduleTransactionDetailKeyPayload, } from '../../../entity/jeSchedules/jeSchedulesPayload';
6
6
  import { mapAccountTypeRecommendationPayloadToAccountRecommendationByType, } from '../../accountMappingView/accountTypeRecommendation/accountTypeRecommendationPayload';
7
+ import { MIN_NEW_SCHEDULE_SEARCH_LENGTH, } from '../types/jeSchedulesViewState';
7
8
  export const accountSettingsInitialState = {
8
9
  localData: {
9
10
  selectedPrepaidExpensesAccountIds: [],
@@ -35,6 +36,19 @@ export const accountSettingsInitialState = {
35
36
  accruedExpensesNestedAccountListHierarchy: [],
36
37
  accruedExpensesRecommendations: [],
37
38
  };
39
+ export const getBaseNewScheduleLocalData = (scheduleType) => ({
40
+ scheduleType,
41
+ jeScheduleDetails: [],
42
+ });
43
+ export const newScheduleInitialState = {
44
+ fixed_assets: getBaseNewScheduleLocalData('fixed_assets'),
45
+ prepaid_expenses: getBaseNewScheduleLocalData('prepaid_expenses'),
46
+ };
47
+ export const searchTransactionsInitialState = {
48
+ searchResults: [],
49
+ fetchState: 'Not-Started',
50
+ error: undefined,
51
+ };
38
52
  export const initialState = {
39
53
  jeScheduleTransactionKeysByPeriod: {},
40
54
  failedJeScheduleTransactionKeysByPeriod: {},
@@ -42,9 +56,12 @@ export const initialState = {
42
56
  ignoreStatusById: {},
43
57
  jeScheduleLocalDataById: {},
44
58
  uiState: {
45
- searchString: '',
59
+ createdGroupCollapsed: false,
60
+ pendingGroupCollapsed: false,
46
61
  scrollPosition: { scrollTop: 0, scrollLeft: undefined },
47
- selectedMainTab: 'pending_review',
62
+ searchString: '',
63
+ selectedJETypeFilter: null,
64
+ selectedPageTab: 'schedules',
48
65
  sortKey: 'transactionDate',
49
66
  sortOrder: 'descending',
50
67
  totalCount: 0,
@@ -55,6 +72,13 @@ export const initialState = {
55
72
  error: undefined,
56
73
  },
57
74
  accountSettings: accountSettingsInitialState,
75
+ newSchedule: {
76
+ newScheduleState: newScheduleInitialState,
77
+ saveStatus: { fetchState: 'Not-Started', error: undefined },
78
+ searchTransactions: searchTransactionsInitialState,
79
+ fetchState: 'Not-Started',
80
+ error: undefined,
81
+ },
58
82
  fetchState: 'Not-Started',
59
83
  error: undefined,
60
84
  hasValidState() {
@@ -121,8 +145,8 @@ const expenseAutomationJESchedulesView = createSlice({
121
145
  else {
122
146
  draft.fetchState = 'Error';
123
147
  draft.error = status;
148
+ draft.jeScheduleTransactionKeysByPeriod[toMonthYearPeriodId(selectedPeriod)] = [];
124
149
  }
125
- draft.jeScheduleTransactionKeysByPeriod[toMonthYearPeriodId(selectedPeriod)] = [];
126
150
  },
127
151
  retryJeSchedule(draft, action) {
128
152
  const { scheduledJournalEntryID } = action.payload;
@@ -368,6 +392,91 @@ const expenseAutomationJESchedulesView = createSlice({
368
392
  clearJeAccountSettingsLocalData(draft) {
369
393
  draft.accountSettings.localData = accountSettingsInitialState.localData;
370
394
  },
395
+ initializeNewSchedule: {
396
+ prepare({ scheduleType, }) {
397
+ return { payload: { scheduleType } };
398
+ },
399
+ reducer() {
400
+ // Purely an epic-trigger action — the epic fans out the account/class
401
+ // list fetches plus resetNewScheduleLocalData.
402
+ },
403
+ },
404
+ resetNewScheduleLocalData: {
405
+ prepare({ scheduleType, }) {
406
+ return { payload: { scheduleType } };
407
+ },
408
+ reducer(draft, action) {
409
+ const { scheduleType } = action.payload;
410
+ draft.newSchedule.newScheduleState[scheduleType] =
411
+ getBaseNewScheduleLocalData(scheduleType);
412
+ },
413
+ },
414
+ updateNewScheduleLocalData(draft, action) {
415
+ const { scheduleType, localData } = action.payload;
416
+ draft.newSchedule.newScheduleState[scheduleType] = {
417
+ ...draft.newSchedule.newScheduleState[scheduleType],
418
+ ...localData,
419
+ };
420
+ },
421
+ searchTransactionsForNewSchedule: {
422
+ prepare({ query }) {
423
+ return { payload: { query } };
424
+ },
425
+ reducer(draft, action) {
426
+ const trimmed = action.payload.query.trim();
427
+ // Cleared or too-short query: no API (see epic) — reset immediately.
428
+ if (trimmed.length < MIN_NEW_SCHEDULE_SEARCH_LENGTH) {
429
+ draft.newSchedule.searchTransactions = searchTransactionsInitialState;
430
+ return;
431
+ }
432
+ draft.newSchedule.searchTransactions = {
433
+ ...draft.newSchedule.searchTransactions,
434
+ searchString: action.payload.query,
435
+ fetchState: 'In-Progress',
436
+ };
437
+ },
438
+ },
439
+ searchTransactionsForNewScheduleSuccess(draft, action) {
440
+ draft.newSchedule.searchTransactions = {
441
+ ...draft.newSchedule.searchTransactions,
442
+ searchResults: action.payload.searchResults,
443
+ fetchState: 'Completed',
444
+ error: undefined,
445
+ };
446
+ },
447
+ searchTransactionsForNewScheduleFailure(draft, action) {
448
+ draft.newSchedule.searchTransactions = {
449
+ ...draft.newSchedule.searchTransactions,
450
+ fetchState: 'Error',
451
+ error: action.payload.status,
452
+ };
453
+ },
454
+ clearSearchTransactionsForNewSchedule(draft) {
455
+ draft.newSchedule.searchTransactions = searchTransactionsInitialState;
456
+ },
457
+ saveNewSchedule: {
458
+ prepare({ scheduleType, }) {
459
+ return { payload: { scheduleType } };
460
+ },
461
+ reducer(draft) {
462
+ draft.newSchedule.saveStatus = {
463
+ fetchState: 'In-Progress',
464
+ error: undefined,
465
+ };
466
+ },
467
+ },
468
+ saveNewScheduleSuccess(draft) {
469
+ draft.newSchedule.saveStatus = {
470
+ fetchState: 'Completed',
471
+ error: undefined,
472
+ };
473
+ },
474
+ saveNewScheduleFailure(draft, action) {
475
+ draft.newSchedule.saveStatus = {
476
+ fetchState: 'Error',
477
+ error: action.payload.status,
478
+ };
479
+ },
371
480
  updateJESchedulesUIState(draft, action) {
372
481
  if (action.payload.sortKey != null) {
373
482
  draft.uiState.sortKey = action.payload.sortKey;
@@ -381,16 +490,27 @@ const expenseAutomationJESchedulesView = createSlice({
381
490
  if (action.payload.scrollPosition != null) {
382
491
  draft.uiState.scrollPosition = action.payload.scrollPosition;
383
492
  }
384
- if (action.payload.selectedMainTab != null) {
385
- draft.uiState.selectedMainTab = action.payload.selectedMainTab;
493
+ if (action.payload.selectedPageTab != null) {
494
+ draft.uiState.selectedPageTab = action.payload.selectedPageTab;
386
495
  }
387
496
  },
497
+ updateJEScheduleTypeFilter(draft, action) {
498
+ draft.uiState.selectedJETypeFilter = action.payload;
499
+ },
500
+ toggleJEPendingGroupCollapsed(draft) {
501
+ draft.uiState.pendingGroupCollapsed =
502
+ !draft.uiState.pendingGroupCollapsed;
503
+ },
504
+ toggleJECreatedGroupCollapsed(draft) {
505
+ draft.uiState.createdGroupCollapsed =
506
+ !draft.uiState.createdGroupCollapsed;
507
+ },
388
508
  clearExpenseAutomationJESchedulesView(draft) {
389
509
  Object.assign(draft, initialState);
390
510
  },
391
511
  },
392
512
  });
393
- export const { clearExpenseAutomationJESchedulesView, clearJeAccountSettingsLocalData, clearJeScheduleLocalData, fetchAccountSettingsListForAccountTypes, fetchJeSchedules, fetchJeSchedulesFailure, fetchJeSchedulesPage, fetchJeSchedulesSuccess, fetchRecommendationForAccountSettings, ignoreRecommendedJeSchedule, ignoreRecommendedJeScheduleFailure, ignoreRecommendedJeScheduleSuccess, initializeAccountSettingsView, initializeJeScheduleLocalData, removeFailedJeScheduleTransactionKey, removeJeScheduleLocalDataById, removeJeScheduleTransactionKey, retryJeSchedule, retryJeScheduleFailure, retryJeScheduleSuccess, saveAccountSettings, saveAccountSettingsFailure, saveAccountSettingsLocalData, saveAccountSettingsSuccess, updateAccountSettingsListForAccountTypes, updateAccountSettingsListForAccountTypesFailure, updateJESchedulesUIState, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, updateRecommendationForAccountSettings, updateRecommendationForAccountSettingsFailure, } = expenseAutomationJESchedulesView.actions;
513
+ export const { clearExpenseAutomationJESchedulesView, clearJeAccountSettingsLocalData, clearJeScheduleLocalData, clearSearchTransactionsForNewSchedule, fetchAccountSettingsListForAccountTypes, fetchJeSchedules, fetchJeSchedulesFailure, fetchJeSchedulesPage, fetchJeSchedulesSuccess, fetchRecommendationForAccountSettings, ignoreRecommendedJeSchedule, ignoreRecommendedJeScheduleFailure, ignoreRecommendedJeScheduleSuccess, initializeAccountSettingsView, initializeJeScheduleLocalData, initializeNewSchedule, removeFailedJeScheduleTransactionKey, removeJeScheduleLocalDataById, removeJeScheduleTransactionKey, resetNewScheduleLocalData, retryJeSchedule, retryJeScheduleFailure, retryJeScheduleSuccess, saveAccountSettings, saveAccountSettingsFailure, saveAccountSettingsLocalData, saveAccountSettingsSuccess, saveNewSchedule, saveNewScheduleFailure, saveNewScheduleSuccess, searchTransactionsForNewSchedule, searchTransactionsForNewScheduleFailure, searchTransactionsForNewScheduleSuccess, toggleJECreatedGroupCollapsed, toggleJEPendingGroupCollapsed, updateAccountSettingsListForAccountTypes, updateAccountSettingsListForAccountTypesFailure, updateJESchedulesUIState, updateJEScheduleTypeFilter, updateJeScheduleLocalDataById, updateJeScheduleTransactionKeys, updateNewScheduleLocalData, updateRecommendationForAccountSettings, updateRecommendationForAccountSettingsFailure, } = expenseAutomationJESchedulesView.actions;
394
514
  export default expenseAutomationJESchedulesView.reducer;
395
515
  const doUpdateRecommendationForAccountSettings = (draft, payload) => {
396
516
  const { prepaidExpensesRecommendations, fixedAssetsRecommendations, accruedExpensesRecommendations, } = mapAccountTypeRecommendationPayloadToAccountRecommendationByType(payload);
@@ -0,0 +1,71 @@
1
+ import { getAccountIdsForTypes } from '../../../entity/account/accountSelector';
2
+ import { getSupportedTransactionsByIds } from '../../../entity/transaction/transactionSelector';
3
+ import { getAccountIdsForLabel, getAccountList, getNestedAccountListHierarchy, } from '../../accountList/accountListSelector';
4
+ import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
5
+ import { getAccountsTypesForScheduleDetails } from '../../scheduleView/scheduleDetailView/scheduleDetailLocalDataHelper';
6
+ import { filterNestedAccountHierarchyById } from '../../scheduleView/scheduleDetailView/scheduleDetailSelector';
7
+ import { getAccountLabelForScheduleType, getAccountsTypesForScheduleEpics, } from '../../scheduleView/scheduleListView/scheduleListHelper';
8
+ import { isSupportedTransactionSelectableForNewSchedule } from '../helpers/newScheduleLocalDataHelper';
9
+ /**
10
+ * The Base Transaction search is shared across schedule types (one search
11
+ * box, not one per prepaid_expenses/fixed_assets) — split out from
12
+ * getNewScheduleView so a consumer can read it without supplying an
13
+ * otherwise-irrelevant scheduleType.
14
+ */
15
+ export const getNewScheduleSearchTransactionsView = (state) => {
16
+ const { searchTransactions } = state.expenseAutomationJESchedulesViewState.newSchedule;
17
+ const searchTransactionsResults = getSupportedTransactionsByIds(state.transactionState, searchTransactions.searchResults);
18
+ return {
19
+ searchTransactionsResults,
20
+ selectableBaseTransactionIds: searchTransactionsResults
21
+ .filter(isSupportedTransactionSelectableForNewSchedule)
22
+ .map((transaction) => transaction.id),
23
+ searchTransactionsFetchState: {
24
+ fetchState: searchTransactions.fetchState,
25
+ error: searchTransactions.error,
26
+ },
27
+ searchTransactionsString: searchTransactions.searchString,
28
+ };
29
+ };
30
+ export const getNewScheduleView = (state, scheduleType) => {
31
+ const { accountState, accountListState, classState, classListState } = state;
32
+ const { newSchedule } = state.expenseAutomationJESchedulesViewState;
33
+ const searchTransactionsView = getNewScheduleSearchTransactionsView(state);
34
+ const accountType = getAccountsTypesForScheduleEpics();
35
+ const newScheduleAccountTypes = getAccountsTypesForScheduleDetails(scheduleType);
36
+ const newScheduleAccountIds = getAccountIdsForTypes(accountState, newScheduleAccountTypes);
37
+ const allAccountList = getAccountList(accountState, accountListState, accountType.accountListKey);
38
+ const filteredAccounts = allAccountList.accounts.filter((account) => newScheduleAccountIds.includes(account.accountId));
39
+ const accountListNestedAccountHierarchy = filterNestedAccountHierarchyById(getNestedAccountListHierarchy(accountListState, accountType.accountListKey), filteredAccounts.map((account) => account.accountId));
40
+ const allClassList = getClassList(classState, classListState);
41
+ const classListNestedAccountHierarchy = getNestedClassListHierarchy(classListState);
42
+ let allDepreciationAccountList;
43
+ let allDepreciationAccountListNestedAccountHierarchy;
44
+ if (scheduleType === 'fixed_assets') {
45
+ const depreciationCategoryIds = getAccountIdsForTypes(accountState, [
46
+ 'fixed_assets',
47
+ 'other_assets',
48
+ ]);
49
+ const accountLabel = getAccountLabelForScheduleType('fixed_assets');
50
+ const fixedAssetLabelAccountIds = accountLabel != null
51
+ ? getAccountIdsForLabel(accountListState, accountType.accountListKey, accountLabel)
52
+ : [];
53
+ allDepreciationAccountList = allAccountList.accounts.filter((account) => depreciationCategoryIds.includes(account.accountId) &&
54
+ !fixedAssetLabelAccountIds.includes(account.accountId));
55
+ allDepreciationAccountListNestedAccountHierarchy =
56
+ filterNestedAccountHierarchyById(getNestedAccountListHierarchy(accountListState, accountType.accountListKey), allDepreciationAccountList.map((account) => account.accountId));
57
+ }
58
+ return {
59
+ scheduleType,
60
+ accountListNestedAccountHierarchy,
61
+ allAccountList: filteredAccounts,
62
+ allClassList: allClassList.classes,
63
+ allDepreciationAccountList,
64
+ allDepreciationAccountListNestedAccountHierarchy,
65
+ classListNestedAccountHierarchy,
66
+ newScheduleLocalData: newSchedule.newScheduleState[scheduleType],
67
+ ...searchTransactionsView,
68
+ fetchState: newSchedule.fetchState,
69
+ error: newSchedule.error,
70
+ };
71
+ };
@@ -4,6 +4,7 @@ import { reduceAnyFetchState } from '../../../commonStateTypes/reduceFetchState'
4
4
  import { LAST_SCHEDULE_DAY_OF_MONTH, toMonthYearPeriodId, } from '../../../commonStateTypes/timePeriod';
5
5
  import { getAccountIdsForTypes } from '../../../entity/account/accountSelector';
6
6
  import { getJEScheduledTransactionByJEScheduleTransactionKey, } from '../../../entity/jeSchedules/jeSchedulesSelector';
7
+ import { ALL_SCHEDULES_TYPES, } from '../../../entity/jeSchedules/jeSchedulesTypes';
7
8
  import { getCurrentTenant } from '../../../entity/tenant/tenantSelector';
8
9
  import { getAccountIdsForLabel, getAccountList, getNestedAccountListHierarchy, getUncategorizedAccounts, } from '../../accountList/accountListSelector';
9
10
  import { getClassList, getNestedClassListHierarchy, } from '../../classList/classListSelector';
@@ -12,6 +13,20 @@ import { filterNestedAccountHierarchyById } from '../../scheduleView/scheduleDet
12
13
  import { getAccountLabelForScheduleType, getAccountsTypesForScheduleList, } from '../../scheduleView/scheduleListView/scheduleListHelper';
13
14
  import { getAllSteps } from '../selectorTypes/expenseAutomationViewSelectorTypes';
14
15
  import { getJEAccountSettingsView } from './jeAccountSettingsViewSelector';
16
+ import { getNewScheduleView } from './jeNewScheduleViewSelector';
17
+ // Matches the AI-automation user addresses webc's schedule list row already
18
+ // checks (JEScheduleReadOnlyListRow/JEScheduleDraftListRow "Method" column,
19
+ // strings.ts aiBotEmail/botEmail) to determine whether a schedule was
20
+ // created by the AI accountant vs. a human.
21
+ const AI_BOT_EMAIL = 'ai@zeni.ai';
22
+ const BOT_EMAIL = 'bot@zeni.ai';
23
+ // Exported (rather than kept as a closure inside the selector) so it can be
24
+ // unit-tested directly — confidenceScore is a separate, more narrowly
25
+ // populated field that can be null on a schedule the bot user created,
26
+ // which previously caused these schedules to display as "AI Accountant" in
27
+ // the list but count under "Manual" in the KPI summary.
28
+ export const isAiAccountantSchedule = (schedule) => schedule.createdByUser?.email === AI_BOT_EMAIL ||
29
+ schedule.createdByUser?.email === BOT_EMAIL;
15
30
  function getJEScheduleSortAccessor(sortKey) {
16
31
  switch (sortKey) {
17
32
  case 'vendor':
@@ -53,7 +68,7 @@ function getJEScheduleSortAccessor(sortKey) {
53
68
  case 'totalAmount':
54
69
  return (s) => s.baseTransaction.amount.amount;
55
70
  case 'transactionDate':
56
- return (s) => s.baseTransaction.date.valueOf();
71
+ return (s) => s.createTime?.valueOf();
57
72
  case 'memo':
58
73
  return (s) => s.baseTransaction.memo?.toLowerCase();
59
74
  default:
@@ -68,8 +83,12 @@ export function getExpenseAutomationJESchedulesView(state) {
68
83
  const jeScheduledTransaction = [];
69
84
  const failedJeScheduledTransaction = [];
70
85
  const { accountState, accountListState, classState, classListState, expenseAutomationJESchedulesViewState, expenseAutomationViewState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
71
- const { jeScheduleTransactionKeysByPeriod, failedJeScheduleTransactionKeysByPeriod, jeScheduleLocalDataById, postStatusById, ignoreStatusById, uiState, error, fetchState, refreshStatus, } = expenseAutomationJESchedulesViewState;
86
+ const { jeScheduleTransactionKeysByPeriod, failedJeScheduleTransactionKeysByPeriod, jeScheduleLocalDataById, postStatusById, ignoreStatusById, uiState, error, fetchState, refreshStatus, newSchedule, } = expenseAutomationJESchedulesViewState;
72
87
  const accountSettingsView = getJEAccountSettingsView(state);
88
+ const newScheduleView = newSchedule.selectedDetailScheduleType === 'fixed_assets' ||
89
+ newSchedule.selectedDetailScheduleType === 'prepaid_expenses'
90
+ ? getNewScheduleView(state, newSchedule.selectedDetailScheduleType)
91
+ : undefined;
73
92
  const currentTenant = getCurrentTenant(state);
74
93
  const { selectedPeriodByTenantId } = expenseAutomationViewState;
75
94
  const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
@@ -113,11 +132,7 @@ export function getExpenseAutomationJESchedulesView(state) {
113
132
  const jeScheduleDetails = getJEScheduledTransactionByJEScheduleTransactionKey(jeScheduleKey, state);
114
133
  failedJeScheduledTransaction.push(jeScheduleDetails);
115
134
  });
116
- const { sortKey, sortOrder } = uiState;
117
- const draftSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft'), sortKey, sortOrder);
118
- const ongoingSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'ongoing'), sortKey, sortOrder);
119
- const completedSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'completed' ||
120
- schedule.status.code === 'marked_as_completed'), sortKey, sortOrder);
135
+ const { sortKey, sortOrder, selectedJETypeFilter } = uiState;
121
136
  const unsortedResolveSchedules = [];
122
137
  failedJeScheduledTransaction.forEach((schedule) => {
123
138
  schedule.scheduledJournalEntry.forEach((journalEntry) => {
@@ -130,9 +145,42 @@ export function getExpenseAutomationJESchedulesView(state) {
130
145
  const resolveSchedules = sortJEScheduledTransactions(unsortedResolveSchedules, sortKey, sortOrder);
131
146
  const validResolveForPending = unsortedResolveSchedules.filter((r) => jeScheduleLocalDataById[r.scheduledJournalEntry.scheduledJournalEntryID] != null);
132
147
  const pendingReviewSchedules = sortJEScheduledTransactions([
133
- ...jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft'),
148
+ ...jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft' &&
149
+ (selectedJETypeFilter == null ||
150
+ schedule.jeScheduleType === selectedJETypeFilter)),
134
151
  ...validResolveForPending,
135
152
  ], sortKey, sortOrder);
153
+ const createdSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => (schedule.status.code === 'ongoing' ||
154
+ schedule.status.code === 'completed' ||
155
+ schedule.status.code === 'marked_as_completed') &&
156
+ (selectedJETypeFilter == null ||
157
+ schedule.jeScheduleType === selectedJETypeFilter)), sortKey, sortOrder);
158
+ // KPI summary — derived from unfiltered schedules so counts are always accurate
159
+ const allSchedules = jeScheduledTransaction;
160
+ const sumAmount = (schedules) => schedules.reduce((total, schedule) => total + (schedule.baseTransaction.amount.amount ?? 0), 0);
161
+ const pendingReviewTotal = sumAmount(pendingReviewSchedules);
162
+ const createdTotal = sumAmount(createdSchedules);
163
+ const aiAccountantSchedules = allSchedules.filter(isAiAccountantSchedule);
164
+ const manualSchedules = allSchedules.filter((schedule) => !isAiAccountantSchedule(schedule));
165
+ const pendingUnfiltered = allSchedules.filter((schedule) => schedule.status.code === 'draft');
166
+ const kpiSummary = {
167
+ aiAccountant: {
168
+ count: aiAccountantSchedules.length,
169
+ amount: sumAmount(aiAccountantSchedules),
170
+ },
171
+ manual: {
172
+ count: manualSchedules.length,
173
+ amount: sumAmount(manualSchedules),
174
+ },
175
+ needsReview: {
176
+ count: pendingUnfiltered.length,
177
+ amount: sumAmount(pendingUnfiltered),
178
+ },
179
+ total: {
180
+ count: allSchedules.length,
181
+ amount: sumAmount(allSchedules),
182
+ },
183
+ };
136
184
  const allSteps = monthYearPeriodId != null
137
185
  ? getAllSteps(monthEndCloseChecksState, monthYearPeriodId, currentTenant.tenantId)
138
186
  : [];
@@ -145,6 +193,22 @@ export function getExpenseAutomationJESchedulesView(state) {
145
193
  { fetchState, error },
146
194
  monthEndFetchState,
147
195
  ]);
196
+ const countByType = (schedules) => {
197
+ const counts = ALL_SCHEDULES_TYPES.reduce((acc, type) => ({ ...acc, [type]: 0 }), {});
198
+ schedules.forEach((schedule) => {
199
+ if (schedule.jeScheduleType != null) {
200
+ counts[schedule.jeScheduleType] += 1;
201
+ }
202
+ });
203
+ return counts;
204
+ };
205
+ const jeScheduleTypeCounts = {
206
+ active: countByType(allSchedules.filter((schedule) => schedule.status.code === 'draft' ||
207
+ schedule.status.code === 'ongoing' ||
208
+ schedule.status.code === 'completed' ||
209
+ schedule.status.code === 'marked_as_completed')),
210
+ ongoing: countByType(allSchedules.filter((schedule) => schedule.status.code === 'ongoing')),
211
+ };
148
212
  return {
149
213
  uncategorizedAccounts,
150
214
  allDepreciationAccountList: filteredDepAccounts,
@@ -153,12 +217,15 @@ export function getExpenseAutomationJESchedulesView(state) {
153
217
  allAccountList: filteredAccounts,
154
218
  accountListNestedAccountHierarchy,
155
219
  allClassList: allClasses,
156
- completedSchedules,
157
- draftSchedules,
158
- ongoingSchedules,
220
+ createdSchedules,
221
+ createdTotal,
222
+ jeScheduleTypeCounts,
223
+ kpiSummary,
159
224
  pendingReviewSchedules,
225
+ pendingReviewTotal,
160
226
  resolveSchedules: resolveSchedules,
161
227
  accountSettingsView,
228
+ newScheduleView,
162
229
  postStatusById: postStatusById,
163
230
  ignoreStatusById: ignoreStatusById,
164
231
  fetchState: reducedFetchStatus.fetchState,
@@ -16,9 +16,6 @@ const JE_SCHEDULE_SORT_KEYS = [
16
16
  'memo',
17
17
  ];
18
18
  export const toJEScheduleSortKey = (v) => stringToUnion(v, JE_SCHEDULE_SORT_KEYS);
19
- const JE_SCHEDULE_MAIN_TABS = [
20
- 'pending_review',
21
- 'ongoing',
22
- 'completed',
23
- ];
24
- export const toJEScheduleMainTab = (v) => stringToUnion(v, JE_SCHEDULE_MAIN_TABS);
19
+ const JE_PAGE_TABS = ['schedules', 'existing_schedules'];
20
+ export const toJEPageTab = (v) => stringToUnion(v, JE_PAGE_TABS);
21
+ export const MIN_NEW_SCHEDULE_SEARCH_LENGTH = 2;
@@ -0,0 +1,116 @@
1
+ import { EMPTY, concat, of } from 'rxjs';
2
+ import { catchError, debounceTime, filter, finalize, mergeMap, switchMap, takeUntil, withLatestFrom, } from 'rxjs/operators';
3
+ import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
4
+ import { createZeniAPIStatus, isSuccessResponse, } from '../../../responsePayload';
5
+ import { notificationPreferencesUrl } from '../notificationPreferencesEndpoint';
6
+ import { mapPreferencesToPayload, } from '../notificationPreferencesViewPayload';
7
+ import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess, setGroupFrequency, toggleEventChannel, } from '../notificationPreferencesViewReducer';
8
+ import { getNotificationLocalOverrides } from '../notificationPreferencesViewSelector';
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$),
15
+ // `switchMap` unsubscribes from any in-flight PUT observable when a new
16
+ // debounced batch fires. The prior HTTP request has already left the
17
+ // client — the network call is NOT cancelled — but its response is
18
+ // ignored, so stale success/failure actions cannot clobber the newer
19
+ // batch's state. Server-side ordering is arrival-order LWW.
20
+ switchMap(([, state]) => {
21
+ const overrides = getNotificationLocalOverrides(state);
22
+ const preferencesPatch = mapPreferencesToPayload(overrides);
23
+ if (Object.keys(preferencesPatch).length === 0) {
24
+ // No diff → do not dispatch a synthetic success (that would wipe
25
+ // stored preferences via the reducer). Simply no-op.
26
+ return EMPTY;
27
+ }
28
+ // Snapshot the Cancel epoch at PUT-dispatch time. Every Cancel bumps
29
+ // `cancelEpoch`; the reducer compares the captured value on Success/
30
+ // Failure and skips the merge when the value has advanced (user
31
+ // pressed Cancel since this save started). See Cursor Bugbot 4aa021f7
32
+ // — the earlier counter model failed when `switchMap` abandoned an
33
+ // in-flight save's response (Success/Failure never dispatched, so
34
+ // the counter never drained and future saves got their merge dropped).
35
+ const cancelEpochAtDispatch = state.notificationPreferencesViewState.cancelEpoch;
36
+ const body = { task_checklist_preferences: preferencesPatch };
37
+ // Suppress the error snackbar when the response lands AFTER the user
38
+ // pressed Cancel — the Failure reducer already swallows the state
39
+ // update, so the toast would be a stale error for a save the user
40
+ // abandoned. `StateObservable#value` reads the current cancelEpoch at
41
+ // response time (not at dispatch time — that's what
42
+ // `cancelEpochAtDispatch` captures). Cursor Bugbot da2f4fe2 + Nikunj.
43
+ const errorSnackbar = openSnackbar({
44
+ messageSection: 'notification_settings_saved',
45
+ messageText: 'failed',
46
+ type: 'error',
47
+ });
48
+ const isCancelledSince = () => cancelEpochAtDispatch <
49
+ state$.value.notificationPreferencesViewState.cancelEpoch;
50
+ // Abort the in-flight HTTP request when `switchMap` disposes this
51
+ // observable (new debounced batch fires, tenant switch, unmount).
52
+ // Without an AbortSignal, `switchMap` would drop the response
53
+ // client-side but the older PUT would still land on the server —
54
+ // under patch-semantic LWW, a slower older PUT arriving after a
55
+ // newer one silently reverts the user's latest choice on the
56
+ // server. Cursor Bugbot e3861a76. `AbortController.abort()` is
57
+ // idempotent so it's safe if the request already completed.
58
+ const controller = new AbortController();
59
+ const request$ = zeniAPI
60
+ .putAndGetJSON(notificationPreferencesUrl(zeniAPI), body, undefined, controller.signal)
61
+ .pipe(mergeMap((response) => {
62
+ if (isSuccessResponse(response)) {
63
+ return of(saveNotificationPreferencesSuccess({
64
+ cancelEpochAtDispatch,
65
+ savedOverrides: overrides,
66
+ serverEcho: response.data?.task_checklist_preferences,
67
+ }));
68
+ }
69
+ const actions = [
70
+ saveNotificationPreferencesFailure({
71
+ cancelEpochAtDispatch,
72
+ error: response.status,
73
+ }),
74
+ ];
75
+ if (!isCancelledSince()) {
76
+ // Surface a user-visible error — without it the UI reverts to
77
+ // server truth after debounce and the failed toggle looks like
78
+ // a mystery UI bug.
79
+ actions.push(errorSnackbar);
80
+ }
81
+ return of(...actions);
82
+ }), catchError((error) => {
83
+ const actions = [
84
+ saveNotificationPreferencesFailure({
85
+ cancelEpochAtDispatch,
86
+ error: createZeniAPIStatus('Unexpected Error', 'Save notification preferences failed: ' +
87
+ JSON.stringify(error)),
88
+ }),
89
+ ];
90
+ if (!isCancelledSince()) {
91
+ actions.push(errorSnackbar);
92
+ }
93
+ return of(...actions);
94
+ }), finalize(() => {
95
+ // switchMap disposal (new batch fires, unmount) OR the takeUntil
96
+ // below (Cancel fires) → abort the still-in-flight HTTP request.
97
+ // No-op if the request has already completed successfully or
98
+ // errored. Without this, the pre-Cancel PUT would still land on
99
+ // the server; under patch-semantic LWW, a slower older PUT
100
+ // arriving after Cancel silently reverts the user's just-cleared
101
+ // overrides on the server.
102
+ controller.abort();
103
+ }),
104
+ // Cancel and tenant-switch/logout do not flow through the outer
105
+ // `filter`, so switchMap would never dispose this observable on
106
+ // either. `takeUntil` completes request$ on Cancel OR clear-all,
107
+ // which triggers finalize above (aborts the in-flight HTTP
108
+ // request). Without matching clear-all, a tenant switch during
109
+ // an in-flight PUT would let the older PUT land on the server
110
+ // under patch-semantic LWW. Cursor Bugbot 3637969823 + 3641372002.
111
+ takeUntil(actions$.pipe(filter((action) => clearNotificationPreferencesLocalOverrides.match(action) ||
112
+ clearAllNotificationPreferencesView.match(action)))));
113
+ // Flip savePreferencesState to In-Progress BEFORE the network call so
114
+ // any "saving…" UI can render.
115
+ return concat(of(saveNotificationPreferences()), request$);
116
+ }));
@@ -0,0 +1,5 @@
1
+ // Shared endpoint used by both the GET (envelope refresh in
2
+ // `fetchNotificationSettingsEpic`) and the PUT (patch in
3
+ // `saveNotificationPreferencesEpic`). Kept as a helper so an endpoint rename
4
+ // can't drift between the two callers.
5
+ export const notificationPreferencesUrl = (zeniAPI) => `${zeniAPI.apiEndPoints.notificationMicroServiceBaseUrl}/1.0/notification-preferences`;
@@ -0,0 +1,36 @@
1
+ import { toNotificationChannel, toNotificationFrequency, } from '../../entity/notificationRegistry/notificationRegistryState';
2
+ const mapEventEnabledByChannel = (raw) => {
3
+ if (raw == null) {
4
+ return {};
5
+ }
6
+ const out = {};
7
+ Object.entries(raw).forEach(([eventId, channelMap]) => {
8
+ const mapped = {};
9
+ Object.entries(channelMap).forEach(([channel, enabled]) => {
10
+ mapped[toNotificationChannel(channel)] = enabled;
11
+ });
12
+ out[eventId] = mapped;
13
+ });
14
+ return out;
15
+ };
16
+ export const mapPayloadToPreferences = (payload) => {
17
+ const groupFrequencyRaw = payload?.group_frequency_by_group_id ?? {};
18
+ const groupFrequencyByGroupId = Object.fromEntries(Object.entries(groupFrequencyRaw).map(([groupId, frequency]) => [
19
+ groupId,
20
+ toNotificationFrequency(frequency),
21
+ ]));
22
+ return {
23
+ eventEnabledByChannel: mapEventEnabledByChannel(payload?.event_enabled_by_channel),
24
+ groupFrequencyByGroupId,
25
+ };
26
+ };
27
+ export const mapPreferencesToPayload = (preferences) => {
28
+ const payload = {};
29
+ if (Object.keys(preferences.eventEnabledByChannel).length > 0) {
30
+ payload.event_enabled_by_channel = preferences.eventEnabledByChannel;
31
+ }
32
+ if (Object.keys(preferences.groupFrequencyByGroupId).length > 0) {
33
+ payload.group_frequency_by_group_id = preferences.groupFrequencyByGroupId;
34
+ }
35
+ return payload;
36
+ };