@zeniai/client-epic-state 5.1.71 → 5.1.72-betaRD1
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/entity/jeSchedules/jeSchedulesPayload.d.ts +2 -0
- package/lib/entity/jeSchedules/jeSchedulesPayload.js +2 -0
- package/lib/entity/jeSchedules/jeSchedulesSelector.d.ts +4 -0
- package/lib/entity/jeSchedules/jeSchedulesSelector.js +11 -1
- package/lib/entity/jeSchedules/jeSchedulesState.d.ts +2 -0
- package/lib/entity/monthEndCloseChecks/monthEndCloseChecksPayload.d.ts +1 -0
- package/lib/entity/monthEndCloseChecks/monthEndCloseChecksPayload.js +1 -0
- package/lib/entity/monthEndCloseChecks/monthEndCloseChecksState.d.ts +1 -0
- package/lib/entity/transaction/payloadTypes/scheduleTransactionPayload.d.ts +2 -0
- package/lib/entity/transaction/payloadTypes/scheduleTransactionPayload.js +2 -0
- package/lib/entity/transaction/stateTypes/scheduleTransaction.d.ts +2 -0
- package/lib/epic.d.ts +5 -1
- package/lib/epic.js +5 -1
- package/lib/esm/entity/jeSchedules/jeSchedulesPayload.js +2 -0
- package/lib/esm/entity/jeSchedules/jeSchedulesSelector.js +11 -1
- package/lib/esm/entity/monthEndCloseChecks/monthEndCloseChecksPayload.js +1 -0
- package/lib/esm/entity/transaction/payloadTypes/scheduleTransactionPayload.js +2 -0
- package/lib/esm/epic.js +5 -1
- package/lib/esm/index.js +12 -6
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +23 -3
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.js +21 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.js +110 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.js +73 -0
- package/lib/esm/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +22 -0
- package/lib/esm/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.js +140 -0
- package/lib/esm/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +133 -8
- package/lib/esm/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.js +1 -0
- package/lib/esm/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.js +70 -0
- package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +110 -15
- package/lib/esm/view/expenseAutomationView/types/jeSchedulesViewState.js +3 -6
- package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +13 -16
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewPayload.js +7 -2
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +55 -10
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +23 -13
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewState.js +1 -0
- package/lib/index.d.ts +13 -9
- package/lib/index.js +72 -48
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.d.ts +2 -2
- package/lib/view/expenseAutomationView/epics/jeSchedule/fetchJeSchedulesEpic.js +21 -1
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.d.ts +8 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/initializeNewScheduleEpic.js +25 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.d.ts +45 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/saveNewScheduleEpic.js +114 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.d.ts +8 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/newSchedule/searchTransactionsForNewScheduleEpic.js +77 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.d.ts +6 -0
- package/lib/view/expenseAutomationView/epics/jeSchedule/switchJESchedulesTabEpic.js +26 -0
- package/lib/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.d.ts +36 -0
- package/lib/view/expenseAutomationView/helpers/newScheduleLocalDataHelper.js +145 -0
- package/lib/view/expenseAutomationView/payload/jeSchedulesPayload.d.ts +7 -1
- package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.d.ts +40 -9
- package/lib/view/expenseAutomationView/reducers/jeSchedulesViewReducer.js +134 -8
- package/lib/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.d.ts +25 -0
- package/lib/view/expenseAutomationView/selectorTypes/jeNewScheduleViewSelectorTypes.js +2 -0
- package/lib/view/expenseAutomationView/selectorTypes/jeSchedulesSelectorTypes.d.ts +31 -7
- package/lib/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.d.ts +10 -0
- package/lib/view/expenseAutomationView/selectors/jeNewScheduleViewSelector.js +75 -0
- package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.d.ts +4 -1
- package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +112 -14
- package/lib/view/expenseAutomationView/types/jeSchedulesViewState.d.ts +53 -6
- package/lib/view/expenseAutomationView/types/jeSchedulesViewState.js +5 -8
- package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.d.ts +2 -8
- package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +10 -13
- package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.d.ts +2 -1
- package/lib/view/notificationPreferencesView/notificationPreferencesViewPayload.js +7 -2
- package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +6 -1
- package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +56 -11
- package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +23 -15
- package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +1 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewState.js +1 -0
- package/package.json +1 -1
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isAiAccountantSchedule = void 0;
|
|
7
|
+
exports.isAccruedScheduleRow = isAccruedScheduleRow;
|
|
6
8
|
exports.getExpenseAutomationJESchedulesView = getExpenseAutomationJESchedulesView;
|
|
7
9
|
const omit_1 = __importDefault(require("lodash/omit"));
|
|
8
10
|
const orderBy_1 = __importDefault(require("lodash/orderBy"));
|
|
@@ -10,6 +12,7 @@ const reduceFetchState_1 = require("../../../commonStateTypes/reduceFetchState")
|
|
|
10
12
|
const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
|
|
11
13
|
const accountSelector_1 = require("../../../entity/account/accountSelector");
|
|
12
14
|
const jeSchedulesSelector_1 = require("../../../entity/jeSchedules/jeSchedulesSelector");
|
|
15
|
+
const jeSchedulesTypes_1 = require("../../../entity/jeSchedules/jeSchedulesTypes");
|
|
13
16
|
const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
|
|
14
17
|
const accountListSelector_1 = require("../../accountList/accountListSelector");
|
|
15
18
|
const classListSelector_1 = require("../../classList/classListSelector");
|
|
@@ -18,6 +21,32 @@ const scheduleDetailSelector_1 = require("../../scheduleView/scheduleDetailView/
|
|
|
18
21
|
const scheduleListHelper_1 = require("../../scheduleView/scheduleListView/scheduleListHelper");
|
|
19
22
|
const expenseAutomationViewSelectorTypes_1 = require("../selectorTypes/expenseAutomationViewSelectorTypes");
|
|
20
23
|
const jeAccountSettingsViewSelector_1 = require("./jeAccountSettingsViewSelector");
|
|
24
|
+
const jeNewScheduleViewSelector_1 = require("./jeNewScheduleViewSelector");
|
|
25
|
+
// Matches the AI-automation user addresses webc's schedule list row already
|
|
26
|
+
// checks (JEScheduleReadOnlyListRow/JEScheduleDraftListRow "Method" column,
|
|
27
|
+
// strings.ts aiBotEmail/botEmail) to determine whether a schedule was
|
|
28
|
+
// created by the AI accountant vs. a human.
|
|
29
|
+
const AI_BOT_EMAIL = 'ai@zeni.ai';
|
|
30
|
+
const BOT_EMAIL = 'bot@zeni.ai';
|
|
31
|
+
// Exported (rather than kept as a closure inside the selector) so it can be
|
|
32
|
+
// unit-tested directly — confidenceScore is a separate, more narrowly
|
|
33
|
+
// populated field that can be null on a schedule the bot user created,
|
|
34
|
+
// which previously caused these schedules to display as "AI Accountant" in
|
|
35
|
+
// the list but count under "Manual" in the KPI summary.
|
|
36
|
+
const isAiAccountantSchedule = (schedule) => schedule.createdByUser?.email === AI_BOT_EMAIL ||
|
|
37
|
+
schedule.createdByUser?.email === BOT_EMAIL;
|
|
38
|
+
exports.isAiAccountantSchedule = isAiAccountantSchedule;
|
|
39
|
+
// Accrued schedules are structurally distinct — no baseTransaction, carrying recurringAmount and
|
|
40
|
+
// memo in its place — so anything reading baseTransaction has to branch on this first.
|
|
41
|
+
function isAccruedScheduleRow(schedule) {
|
|
42
|
+
return schedule.jeScheduleType === 'accrued_expenses';
|
|
43
|
+
}
|
|
44
|
+
function getScheduleAmount(schedule) {
|
|
45
|
+
if (isAccruedScheduleRow(schedule)) {
|
|
46
|
+
return schedule.recurringAmount.amount;
|
|
47
|
+
}
|
|
48
|
+
return schedule.baseTransaction.amount.amount;
|
|
49
|
+
}
|
|
21
50
|
function getJEScheduleSortAccessor(sortKey) {
|
|
22
51
|
switch (sortKey) {
|
|
23
52
|
case 'vendor':
|
|
@@ -29,7 +58,9 @@ function getJEScheduleSortAccessor(sortKey) {
|
|
|
29
58
|
case 'depCategory':
|
|
30
59
|
return (s) => s.jeCredit.account?.accountName?.toLowerCase();
|
|
31
60
|
case 'scheduleCategory':
|
|
32
|
-
return (s) => s
|
|
61
|
+
return (s) => isAccruedScheduleRow(s)
|
|
62
|
+
? undefined
|
|
63
|
+
: s.baseTransaction.account?.accountName?.toLowerCase();
|
|
33
64
|
case 'startMonth':
|
|
34
65
|
return (s) => s.startDate?.valueOf();
|
|
35
66
|
case 'type':
|
|
@@ -57,11 +88,13 @@ function getJEScheduleSortAccessor(sortKey) {
|
|
|
57
88
|
return s.balanceAsOfToday?.amount ?? null;
|
|
58
89
|
};
|
|
59
90
|
case 'totalAmount':
|
|
60
|
-
return (s) => s
|
|
91
|
+
return (s) => getScheduleAmount(s);
|
|
61
92
|
case 'transactionDate':
|
|
62
|
-
return (s) => s.
|
|
93
|
+
return (s) => s.createTime?.valueOf();
|
|
63
94
|
case 'memo':
|
|
64
|
-
return (s) => s
|
|
95
|
+
return (s) => isAccruedScheduleRow(s)
|
|
96
|
+
? s.memo.toLowerCase()
|
|
97
|
+
: s.baseTransaction.memo?.toLowerCase();
|
|
65
98
|
default:
|
|
66
99
|
return (s) => s.startDate?.valueOf();
|
|
67
100
|
}
|
|
@@ -73,9 +106,14 @@ function sortJEScheduledTransactions(transactions, sortKey, sortOrder) {
|
|
|
73
106
|
function getExpenseAutomationJESchedulesView(state) {
|
|
74
107
|
const jeScheduledTransaction = [];
|
|
75
108
|
const failedJeScheduledTransaction = [];
|
|
109
|
+
const accruedSchedules = [];
|
|
76
110
|
const { accountState, accountListState, classState, classListState, expenseAutomationJESchedulesViewState, expenseAutomationViewState, monthEndCloseChecksState, monthEndCloseChecksViewState, } = state;
|
|
77
|
-
const { jeScheduleTransactionKeysByPeriod, failedJeScheduleTransactionKeysByPeriod, jeScheduleLocalDataById, postStatusById, ignoreStatusById, uiState, error, fetchState, refreshStatus, } = expenseAutomationJESchedulesViewState;
|
|
111
|
+
const { jeScheduleTransactionKeysByPeriod, jeScheduleAccruedKeysByPeriod, failedJeScheduleTransactionKeysByPeriod, jeScheduleLocalDataById, postStatusById, ignoreStatusById, uiState, error, fetchState, refreshStatus, newSchedule, } = expenseAutomationJESchedulesViewState;
|
|
78
112
|
const accountSettingsView = (0, jeAccountSettingsViewSelector_1.getJEAccountSettingsView)(state);
|
|
113
|
+
const newScheduleView = newSchedule.selectedDetailScheduleType === 'fixed_assets' ||
|
|
114
|
+
newSchedule.selectedDetailScheduleType === 'prepaid_expenses'
|
|
115
|
+
? (0, jeNewScheduleViewSelector_1.getNewScheduleView)(state, newSchedule.selectedDetailScheduleType)
|
|
116
|
+
: undefined;
|
|
79
117
|
const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state);
|
|
80
118
|
const { selectedPeriodByTenantId } = expenseAutomationViewState;
|
|
81
119
|
const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
|
|
@@ -111,19 +149,24 @@ function getExpenseAutomationJESchedulesView(state) {
|
|
|
111
149
|
: null;
|
|
112
150
|
const jeScheduleTransactionKeys = jeScheduleTransactionKeysForSelectedPeriod ?? [];
|
|
113
151
|
const failedJeScheduleTransactionKeys = failedJeScheduleTransactionKeysForSelectedPeriod ?? [];
|
|
152
|
+
const jeScheduleAccruedKeys = (monthYearPeriodId != null
|
|
153
|
+
? jeScheduleAccruedKeysByPeriod[monthYearPeriodId]
|
|
154
|
+
: null) ?? [];
|
|
114
155
|
jeScheduleTransactionKeys.forEach((jeScheduleKey) => {
|
|
115
156
|
const jeScheduleDetails = (0, jeSchedulesSelector_1.getJEScheduledTransactionByJEScheduleTransactionKey)(jeScheduleKey, state);
|
|
116
157
|
jeScheduledTransaction.push(jeScheduleDetails);
|
|
117
158
|
});
|
|
159
|
+
jeScheduleAccruedKeys.forEach((jeAccruedScheduleKey) => {
|
|
160
|
+
const accruedScheduleDetails = (0, jeSchedulesSelector_1.getJEAccruedScheduleByJEScheduleKey)(jeAccruedScheduleKey, state);
|
|
161
|
+
if (accruedScheduleDetails != null) {
|
|
162
|
+
accruedSchedules.push(accruedScheduleDetails);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
118
165
|
failedJeScheduleTransactionKeys.forEach((jeScheduleKey) => {
|
|
119
166
|
const jeScheduleDetails = (0, jeSchedulesSelector_1.getJEScheduledTransactionByJEScheduleTransactionKey)(jeScheduleKey, state);
|
|
120
167
|
failedJeScheduledTransaction.push(jeScheduleDetails);
|
|
121
168
|
});
|
|
122
|
-
const { sortKey, sortOrder } = uiState;
|
|
123
|
-
const draftSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'draft'), sortKey, sortOrder);
|
|
124
|
-
const ongoingSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'ongoing'), sortKey, sortOrder);
|
|
125
|
-
const completedSchedules = sortJEScheduledTransactions(jeScheduledTransaction.filter((schedule) => schedule.status.code === 'completed' ||
|
|
126
|
-
schedule.status.code === 'marked_as_completed'), sortKey, sortOrder);
|
|
169
|
+
const { sortKey, sortOrder, selectedJETypeFilter } = uiState;
|
|
127
170
|
const unsortedResolveSchedules = [];
|
|
128
171
|
failedJeScheduledTransaction.forEach((schedule) => {
|
|
129
172
|
schedule.scheduledJournalEntry.forEach((journalEntry) => {
|
|
@@ -135,10 +178,46 @@ function getExpenseAutomationJESchedulesView(state) {
|
|
|
135
178
|
});
|
|
136
179
|
const resolveSchedules = sortJEScheduledTransactions(unsortedResolveSchedules, sortKey, sortOrder);
|
|
137
180
|
const validResolveForPending = unsortedResolveSchedules.filter((r) => jeScheduleLocalDataById[r.scheduledJournalEntry.scheduledJournalEntryID] != null);
|
|
181
|
+
const matchesTypeFilter = (schedule) => selectedJETypeFilter == null ||
|
|
182
|
+
schedule.jeScheduleType === selectedJETypeFilter;
|
|
183
|
+
const isCreatedStatus = (schedule) => schedule.status.code === 'ongoing' ||
|
|
184
|
+
schedule.status.code === 'completed' ||
|
|
185
|
+
schedule.status.code === 'marked_as_completed';
|
|
186
|
+
const allScheduleRows = [
|
|
187
|
+
...jeScheduledTransaction,
|
|
188
|
+
...accruedSchedules,
|
|
189
|
+
];
|
|
138
190
|
const pendingReviewSchedules = sortJEScheduledTransactions([
|
|
139
|
-
...
|
|
191
|
+
...allScheduleRows.filter((schedule) => schedule.status.code === 'draft' && matchesTypeFilter(schedule)),
|
|
140
192
|
...validResolveForPending,
|
|
141
193
|
], sortKey, sortOrder);
|
|
194
|
+
const createdSchedules = sortJEScheduledTransactions(allScheduleRows.filter((schedule) => isCreatedStatus(schedule) && matchesTypeFilter(schedule)), sortKey, sortOrder);
|
|
195
|
+
// KPI summary — derived from unfiltered schedules so counts are always accurate
|
|
196
|
+
const allSchedules = allScheduleRows;
|
|
197
|
+
const sumAmount = (schedules) => schedules.reduce((total, schedule) => total + (getScheduleAmount(schedule) ?? 0), 0);
|
|
198
|
+
const pendingReviewTotal = sumAmount(pendingReviewSchedules);
|
|
199
|
+
const createdTotal = sumAmount(createdSchedules);
|
|
200
|
+
const aiAccountantSchedules = allSchedules.filter(exports.isAiAccountantSchedule);
|
|
201
|
+
const manualSchedules = allSchedules.filter((schedule) => !(0, exports.isAiAccountantSchedule)(schedule));
|
|
202
|
+
const pendingUnfiltered = allSchedules.filter((schedule) => schedule.status.code === 'draft');
|
|
203
|
+
const kpiSummary = {
|
|
204
|
+
aiAccountant: {
|
|
205
|
+
count: aiAccountantSchedules.length,
|
|
206
|
+
amount: sumAmount(aiAccountantSchedules),
|
|
207
|
+
},
|
|
208
|
+
manual: {
|
|
209
|
+
count: manualSchedules.length,
|
|
210
|
+
amount: sumAmount(manualSchedules),
|
|
211
|
+
},
|
|
212
|
+
needsReview: {
|
|
213
|
+
count: pendingUnfiltered.length,
|
|
214
|
+
amount: sumAmount(pendingUnfiltered),
|
|
215
|
+
},
|
|
216
|
+
total: {
|
|
217
|
+
count: allSchedules.length,
|
|
218
|
+
amount: sumAmount(allSchedules),
|
|
219
|
+
},
|
|
220
|
+
};
|
|
142
221
|
const allSteps = monthYearPeriodId != null
|
|
143
222
|
? (0, expenseAutomationViewSelectorTypes_1.getAllSteps)(monthEndCloseChecksState, monthYearPeriodId, currentTenant.tenantId)
|
|
144
223
|
: [];
|
|
@@ -151,6 +230,22 @@ function getExpenseAutomationJESchedulesView(state) {
|
|
|
151
230
|
{ fetchState, error },
|
|
152
231
|
monthEndFetchState,
|
|
153
232
|
]);
|
|
233
|
+
const countByType = (schedules) => {
|
|
234
|
+
const counts = jeSchedulesTypes_1.ALL_SCHEDULES_TYPES.reduce((acc, type) => ({ ...acc, [type]: 0 }), {});
|
|
235
|
+
schedules.forEach((schedule) => {
|
|
236
|
+
if (schedule.jeScheduleType != null) {
|
|
237
|
+
counts[schedule.jeScheduleType] += 1;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
return counts;
|
|
241
|
+
};
|
|
242
|
+
const jeScheduleTypeCounts = {
|
|
243
|
+
active: countByType(allSchedules.filter((schedule) => schedule.status.code === 'draft' ||
|
|
244
|
+
schedule.status.code === 'ongoing' ||
|
|
245
|
+
schedule.status.code === 'completed' ||
|
|
246
|
+
schedule.status.code === 'marked_as_completed')),
|
|
247
|
+
ongoing: countByType(allSchedules.filter((schedule) => schedule.status.code === 'ongoing')),
|
|
248
|
+
};
|
|
154
249
|
return {
|
|
155
250
|
uncategorizedAccounts,
|
|
156
251
|
allDepreciationAccountList: filteredDepAccounts,
|
|
@@ -159,12 +254,15 @@ function getExpenseAutomationJESchedulesView(state) {
|
|
|
159
254
|
allAccountList: filteredAccounts,
|
|
160
255
|
accountListNestedAccountHierarchy,
|
|
161
256
|
allClassList: allClasses,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
257
|
+
createdSchedules,
|
|
258
|
+
createdTotal,
|
|
259
|
+
jeScheduleTypeCounts,
|
|
260
|
+
kpiSummary,
|
|
165
261
|
pendingReviewSchedules,
|
|
262
|
+
pendingReviewTotal,
|
|
166
263
|
resolveSchedules: resolveSchedules,
|
|
167
264
|
accountSettingsView,
|
|
265
|
+
newScheduleView,
|
|
168
266
|
postStatusById: postStatusById,
|
|
169
267
|
ignoreStatusById: ignoreStatusById,
|
|
170
268
|
fetchState: reducedFetchStatus.fetchState,
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import { NestedAccountHierarchyForReport } from '../../../commonStateTypes/accountView/nestedAccountID';
|
|
2
|
+
import { Amount } from '../../../commonStateTypes/amount';
|
|
2
3
|
import { FetchStateAndError, FetchedState, ID } from '../../../commonStateTypes/common';
|
|
3
4
|
import { Recommendation } from '../../../commonStateTypes/recommendationBase';
|
|
4
5
|
import { SortOrder } from '../../../commonStateTypes/selectorTypes/sortOrderTypes';
|
|
5
|
-
import { MonthYearPeriodId } from '../../../commonStateTypes/timePeriod';
|
|
6
|
+
import { MonthYearPeriodId, ScheduleDaysOfMonth } from '../../../commonStateTypes/timePeriod';
|
|
6
7
|
import { AccountBase } from '../../../entity/account/accountState';
|
|
7
|
-
import { JEScheduleTransactionKey, ScheduleTypes } from '../../../entity/jeSchedules/jeSchedulesTypes';
|
|
8
|
+
import { JEScheduleKey, JEScheduleTransactionKey, ScheduleJournalEntryStatusCodeType, ScheduleTransactionID, ScheduleTypes } from '../../../entity/jeSchedules/jeSchedulesTypes';
|
|
8
9
|
import { VendorBase } from '../../../entity/vendor/vendorState';
|
|
9
|
-
|
|
10
|
+
import { ZeniDate } from '../../../zeniDayJS';
|
|
11
|
+
import { JETransactionLink } from '../../scheduleView/scheduleDetailView/scheduleDetailState';
|
|
12
|
+
export declare const toJEScheduleSortKey: (v: string) => "vendor" | "type" | "category" | "class" | "memo" | "transactionDate" | "totalAmount" | "runningBalance" | "jePostingDate" | "depCategory" | "scheduleCategory" | "startMonth" | "amortizationPeriod" | "remainingMonths";
|
|
10
13
|
export type JEScheduleSortKey = ReturnType<typeof toJEScheduleSortKey>;
|
|
11
|
-
export declare const
|
|
12
|
-
export type
|
|
14
|
+
export declare const toJEPageTab: (v: string) => "schedules" | "existing_schedules";
|
|
15
|
+
export type JEPageTab = ReturnType<typeof toJEPageTab>;
|
|
13
16
|
export interface JESchedulesViewUIState {
|
|
17
|
+
createdGroupCollapsed: boolean;
|
|
18
|
+
pendingGroupCollapsed: boolean;
|
|
14
19
|
scrollPosition: {
|
|
15
20
|
scrollTop: number;
|
|
16
21
|
scrollLeft?: number;
|
|
17
22
|
} | undefined;
|
|
18
23
|
searchString: string;
|
|
19
|
-
|
|
24
|
+
selectedJETypeFilter: ScheduleTypes | null;
|
|
25
|
+
selectedPageTab: JEPageTab;
|
|
20
26
|
sortKey: JEScheduleSortKey;
|
|
21
27
|
sortOrder: SortOrder;
|
|
22
28
|
totalCount: number;
|
|
@@ -35,12 +41,53 @@ export interface JESchedulesViewState extends FetchedState {
|
|
|
35
41
|
accountSettings: AccountSettingsState;
|
|
36
42
|
failedJeScheduleTransactionKeysByPeriod: Record<MonthYearPeriodId, JEScheduleTransactionKey[]>;
|
|
37
43
|
ignoreStatusById: Record<JEScheduleTransactionKey, FetchStateAndError>;
|
|
44
|
+
jeScheduleAccruedKeysByPeriod: Record<MonthYearPeriodId, JEScheduleKey[]>;
|
|
38
45
|
jeScheduleLocalDataById: Record<ID, JEScheduleLocalData>;
|
|
39
46
|
jeScheduleTransactionKeysByPeriod: Record<MonthYearPeriodId, JEScheduleTransactionKey[]>;
|
|
47
|
+
newSchedule: NewJeScheduleState;
|
|
40
48
|
postStatusById: Record<ID, FetchStateAndError>;
|
|
41
49
|
refreshStatus: FetchStateAndError;
|
|
42
50
|
uiState: JESchedulesViewUIState;
|
|
43
51
|
}
|
|
52
|
+
export interface NewJeDetailLocalData {
|
|
53
|
+
amount: Amount;
|
|
54
|
+
jeLink: JETransactionLink;
|
|
55
|
+
memo: string;
|
|
56
|
+
postingDate: ZeniDate;
|
|
57
|
+
postingStatus: ScheduleJournalEntryStatusCodeType;
|
|
58
|
+
runningBalance: Amount;
|
|
59
|
+
}
|
|
60
|
+
export interface NewScheduleLocalData {
|
|
61
|
+
jeScheduleDetails: NewJeDetailLocalData[];
|
|
62
|
+
scheduleType: ScheduleTypes;
|
|
63
|
+
lastDayOfMonth?: boolean;
|
|
64
|
+
selectedAccount?: ID;
|
|
65
|
+
selectedBaseTransactionId?: ScheduleTransactionID;
|
|
66
|
+
selectedClass?: ID;
|
|
67
|
+
selectedDate?: ZeniDate;
|
|
68
|
+
selectedDayOfMonth?: ScheduleDaysOfMonth;
|
|
69
|
+
selectedJEScheduleKey?: JEScheduleTransactionKey;
|
|
70
|
+
selectedMonths?: number;
|
|
71
|
+
selectedVendor?: VendorBase;
|
|
72
|
+
}
|
|
73
|
+
export interface NewScheduleLocalDataFixedAssets extends NewScheduleLocalData {
|
|
74
|
+
assetId?: string;
|
|
75
|
+
selectedAccumulatedDepreciationCategory?: ID;
|
|
76
|
+
}
|
|
77
|
+
export interface SearchTransactionsState extends FetchStateAndError {
|
|
78
|
+
searchResults: JEScheduleTransactionKey[];
|
|
79
|
+
searchString?: string;
|
|
80
|
+
}
|
|
81
|
+
export declare const MIN_NEW_SCHEDULE_SEARCH_LENGTH = 2;
|
|
82
|
+
export interface NewJeScheduleState extends FetchStateAndError {
|
|
83
|
+
newScheduleState: {
|
|
84
|
+
fixed_assets: NewScheduleLocalDataFixedAssets;
|
|
85
|
+
prepaid_expenses: NewScheduleLocalData;
|
|
86
|
+
};
|
|
87
|
+
saveStatus: FetchStateAndError;
|
|
88
|
+
searchTransactions: SearchTransactionsState;
|
|
89
|
+
selectedDetailScheduleType?: ScheduleTypes;
|
|
90
|
+
}
|
|
44
91
|
export interface AccountSettingsLocalData {
|
|
45
92
|
previousAccruedExpensesAccount: AccountBase[];
|
|
46
93
|
previousFixedAssetsAccount: AccountBase[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.MIN_NEW_SCHEDULE_SEARCH_LENGTH = exports.toJEPageTab = exports.toJEScheduleSortKey = void 0;
|
|
4
4
|
const stringToUnion_1 = require("../../../commonStateTypes/stringToUnion");
|
|
5
5
|
const JE_SCHEDULE_SORT_KEYS = [
|
|
6
6
|
'vendor',
|
|
@@ -20,10 +20,7 @@ const JE_SCHEDULE_SORT_KEYS = [
|
|
|
20
20
|
];
|
|
21
21
|
const toJEScheduleSortKey = (v) => (0, stringToUnion_1.stringToUnion)(v, JE_SCHEDULE_SORT_KEYS);
|
|
22
22
|
exports.toJEScheduleSortKey = toJEScheduleSortKey;
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
];
|
|
28
|
-
const toJEScheduleMainTab = (v) => (0, stringToUnion_1.stringToUnion)(v, JE_SCHEDULE_MAIN_TABS);
|
|
29
|
-
exports.toJEScheduleMainTab = toJEScheduleMainTab;
|
|
23
|
+
const JE_PAGE_TABS = ['schedules', 'existing_schedules'];
|
|
24
|
+
const toJEPageTab = (v) => (0, stringToUnion_1.stringToUnion)(v, JE_PAGE_TABS);
|
|
25
|
+
exports.toJEPageTab = toJEPageTab;
|
|
26
|
+
exports.MIN_NEW_SCHEDULE_SEARCH_LENGTH = 2;
|
|
@@ -2,8 +2,8 @@ import { ActionsObservable, StateObservable } from 'redux-observable';
|
|
|
2
2
|
import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
|
|
3
3
|
import { RootState } from '../../../reducer';
|
|
4
4
|
import { ZeniAPI } from '../../../zeniAPI';
|
|
5
|
-
import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess
|
|
6
|
-
export type ActionType = ReturnType<typeof clearAllNotificationPreferencesView> | ReturnType<typeof clearNotificationPreferencesLocalOverrides> | ReturnType<typeof saveNotificationPreferences> | ReturnType<typeof saveNotificationPreferencesFailure> | ReturnType<typeof saveNotificationPreferencesSuccess> | ReturnType<typeof
|
|
5
|
+
import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, saveNotificationPreferencesFailure, saveNotificationPreferencesSuccess } from '../notificationPreferencesViewReducer';
|
|
6
|
+
export type ActionType = ReturnType<typeof clearAllNotificationPreferencesView> | ReturnType<typeof clearNotificationPreferencesLocalOverrides> | ReturnType<typeof saveNotificationPreferences> | ReturnType<typeof saveNotificationPreferencesFailure> | ReturnType<typeof saveNotificationPreferencesSuccess> | ReturnType<typeof openSnackbar>;
|
|
7
7
|
export declare const saveNotificationPreferencesEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => import("rxjs").Observable<{
|
|
8
8
|
payload: undefined;
|
|
9
9
|
type: "notificationPreferencesView/clearAllNotificationPreferencesView";
|
|
@@ -31,10 +31,4 @@ export declare const saveNotificationPreferencesEpic: (actions$: ActionsObservab
|
|
|
31
31
|
} | {
|
|
32
32
|
payload: import("../notificationPreferencesViewReducer").SaveNotificationPreferencesSuccessPayload;
|
|
33
33
|
type: "notificationPreferencesView/saveNotificationPreferencesSuccess";
|
|
34
|
-
} | {
|
|
35
|
-
payload: import("../notificationPreferencesViewReducer").SetFrequencyPayload;
|
|
36
|
-
type: "notificationPreferencesView/setGroupFrequency";
|
|
37
|
-
} | {
|
|
38
|
-
payload: import("../notificationPreferencesViewReducer").TogglePreferencePayload;
|
|
39
|
-
type: "notificationPreferencesView/toggleEventChannel";
|
|
40
34
|
}>;
|
|
@@ -9,14 +9,9 @@ const notificationPreferencesEndpoint_1 = require("../notificationPreferencesEnd
|
|
|
9
9
|
const notificationPreferencesViewPayload_1 = require("../notificationPreferencesViewPayload");
|
|
10
10
|
const notificationPreferencesViewReducer_1 = require("../notificationPreferencesViewReducer");
|
|
11
11
|
const notificationPreferencesViewSelector_1 = require("../notificationPreferencesViewSelector");
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
// defer saves until they pause. Consider a max-wait wrapper if this
|
|
16
|
-
// becomes a UX problem.
|
|
17
|
-
(0, operators_1.debounceTime)(DEBOUNCE_MS), (0, operators_1.withLatestFrom)(state$),
|
|
18
|
-
// `switchMap` unsubscribes from any in-flight PUT observable when a new
|
|
19
|
-
// debounced batch fires. The prior HTTP request has already left the
|
|
12
|
+
const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)((action) => notificationPreferencesViewReducer_1.saveNotificationPreferences.match(action)), (0, operators_1.withLatestFrom)(state$),
|
|
13
|
+
// `switchMap` unsubscribes from any in-flight PUT observable when a newer
|
|
14
|
+
// Save fires. The prior HTTP request has already left the
|
|
20
15
|
// client — the network call is NOT cancelled — but its response is
|
|
21
16
|
// ignored, so stale success/failure actions cannot clobber the newer
|
|
22
17
|
// batch's state. Server-side ordering is arrival-order LWW.
|
|
@@ -51,7 +46,7 @@ const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.
|
|
|
51
46
|
const isCancelledSince = () => cancelEpochAtDispatch <
|
|
52
47
|
state$.value.notificationPreferencesViewState.cancelEpoch;
|
|
53
48
|
// Abort the in-flight HTTP request when `switchMap` disposes this
|
|
54
|
-
// observable (
|
|
49
|
+
// observable (a newer Save fires, tenant switch, unmount).
|
|
55
50
|
// Without an AbortSignal, `switchMap` would drop the response
|
|
56
51
|
// client-side but the older PUT would still land on the server —
|
|
57
52
|
// under patch-semantic LWW, a slower older PUT arriving after a
|
|
@@ -77,7 +72,7 @@ const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.
|
|
|
77
72
|
];
|
|
78
73
|
if (!isCancelledSince()) {
|
|
79
74
|
// Surface a user-visible error — without it the UI reverts to
|
|
80
|
-
// server truth after
|
|
75
|
+
// server truth after the save and the failed toggle looks like
|
|
81
76
|
// a mystery UI bug.
|
|
82
77
|
actions.push(errorSnackbar);
|
|
83
78
|
}
|
|
@@ -113,8 +108,10 @@ const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.
|
|
|
113
108
|
// under patch-semantic LWW. Cursor Bugbot 3637969823 + 3641372002.
|
|
114
109
|
(0, operators_1.takeUntil)(actions$.pipe((0, operators_1.filter)((action) => notificationPreferencesViewReducer_1.clearNotificationPreferencesLocalOverrides.match(action) ||
|
|
115
110
|
notificationPreferencesViewReducer_1.clearAllNotificationPreferencesView.match(action)))));
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
111
|
+
// `saveNotificationPreferences` (the trigger) already flipped
|
|
112
|
+
// savePreferencesState to In-Progress via its reducer case, so the PUT
|
|
113
|
+
// observable is returned directly — re-emitting the trigger here would
|
|
114
|
+
// loop the epic on itself.
|
|
115
|
+
return request$;
|
|
119
116
|
}));
|
|
120
117
|
exports.saveNotificationPreferencesEpic = saveNotificationPreferencesEpic;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { NotificationPreferences } from './notificationPreferencesViewState';
|
|
2
2
|
export interface NotificationPreferencesPayload {
|
|
3
3
|
event_enabled_by_channel?: Record<string, Record<string, boolean>>;
|
|
4
|
+
group_channel_enabled_by_group_id?: Record<string, Record<string, boolean>>;
|
|
4
5
|
group_frequency_by_group_id?: Record<string, string>;
|
|
5
6
|
}
|
|
6
7
|
export declare const mapPayloadToPreferences: (payload?: NotificationPreferencesPayload) => NotificationPreferences;
|
|
7
|
-
export declare const mapPreferencesToPayload: (preferences: NotificationPreferences) =>
|
|
8
|
+
export declare const mapPreferencesToPayload: (preferences: NotificationPreferences) => NotificationPreferencesPayload;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mapPreferencesToPayload = exports.mapPayloadToPreferences = void 0;
|
|
4
4
|
const notificationRegistryState_1 = require("../../entity/notificationRegistry/notificationRegistryState");
|
|
5
|
-
const
|
|
5
|
+
const mapChannelEnabledMap = (raw) => {
|
|
6
6
|
if (raw == null) {
|
|
7
7
|
return {};
|
|
8
8
|
}
|
|
@@ -23,7 +23,8 @@ const mapPayloadToPreferences = (payload) => {
|
|
|
23
23
|
(0, notificationRegistryState_1.toNotificationFrequency)(frequency),
|
|
24
24
|
]));
|
|
25
25
|
return {
|
|
26
|
-
eventEnabledByChannel:
|
|
26
|
+
eventEnabledByChannel: mapChannelEnabledMap(payload?.event_enabled_by_channel),
|
|
27
|
+
groupChannelEnabledByGroupId: mapChannelEnabledMap(payload?.group_channel_enabled_by_group_id),
|
|
27
28
|
groupFrequencyByGroupId,
|
|
28
29
|
};
|
|
29
30
|
};
|
|
@@ -33,6 +34,10 @@ const mapPreferencesToPayload = (preferences) => {
|
|
|
33
34
|
if (Object.keys(preferences.eventEnabledByChannel).length > 0) {
|
|
34
35
|
payload.event_enabled_by_channel = preferences.eventEnabledByChannel;
|
|
35
36
|
}
|
|
37
|
+
if (Object.keys(preferences.groupChannelEnabledByGroupId).length > 0) {
|
|
38
|
+
payload.group_channel_enabled_by_group_id =
|
|
39
|
+
preferences.groupChannelEnabledByGroupId;
|
|
40
|
+
}
|
|
36
41
|
if (Object.keys(preferences.groupFrequencyByGroupId).length > 0) {
|
|
37
42
|
payload.group_frequency_by_group_id = preferences.groupFrequencyByGroupId;
|
|
38
43
|
}
|
|
@@ -12,6 +12,11 @@ export interface TogglePreferencePayload {
|
|
|
12
12
|
enabled: boolean;
|
|
13
13
|
eventId: string;
|
|
14
14
|
}
|
|
15
|
+
export interface SetGroupChannelMasterPayload {
|
|
16
|
+
channel: NotificationChannel;
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
groupId: string;
|
|
19
|
+
}
|
|
15
20
|
export interface FetchNotificationPreferencesSuccessPayload {
|
|
16
21
|
preferences: NotificationPreferencesPayload | undefined;
|
|
17
22
|
saveEpochAtDispatch: number;
|
|
@@ -25,6 +30,6 @@ export interface SaveNotificationPreferencesFailurePayload {
|
|
|
25
30
|
cancelEpochAtDispatch: number;
|
|
26
31
|
error?: ZeniAPIStatus;
|
|
27
32
|
}
|
|
28
|
-
export declare const clearAllNotificationPreferencesView: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationPreferencesView/clearAllNotificationPreferencesView">, clearNotificationPreferencesLocalOverrides: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationPreferencesView/clearNotificationPreferencesLocalOverrides">, fetchNotificationPreferencesSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<FetchNotificationPreferencesSuccessPayload, "notificationPreferencesView/fetchNotificationPreferencesSuccess">, saveNotificationPreferences: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationPreferencesView/saveNotificationPreferences">, saveNotificationPreferencesFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<SaveNotificationPreferencesFailurePayload, "notificationPreferencesView/saveNotificationPreferencesFailure">, saveNotificationPreferencesSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<SaveNotificationPreferencesSuccessPayload, "notificationPreferencesView/saveNotificationPreferencesSuccess">, setGroupFrequency: import("@reduxjs/toolkit").ActionCreatorWithPayload<SetFrequencyPayload, "notificationPreferencesView/setGroupFrequency">, toggleEventChannel: import("@reduxjs/toolkit").ActionCreatorWithPayload<TogglePreferencePayload, "notificationPreferencesView/toggleEventChannel">;
|
|
33
|
+
export declare const clearAllNotificationPreferencesView: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationPreferencesView/clearAllNotificationPreferencesView">, clearNotificationPreferencesLocalOverrides: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationPreferencesView/clearNotificationPreferencesLocalOverrides">, fetchNotificationPreferencesSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<FetchNotificationPreferencesSuccessPayload, "notificationPreferencesView/fetchNotificationPreferencesSuccess">, saveNotificationPreferences: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"notificationPreferencesView/saveNotificationPreferences">, saveNotificationPreferencesFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<SaveNotificationPreferencesFailurePayload, "notificationPreferencesView/saveNotificationPreferencesFailure">, saveNotificationPreferencesSuccess: import("@reduxjs/toolkit").ActionCreatorWithPayload<SaveNotificationPreferencesSuccessPayload, "notificationPreferencesView/saveNotificationPreferencesSuccess">, setGroupChannelMaster: import("@reduxjs/toolkit").ActionCreatorWithPayload<SetGroupChannelMasterPayload, "notificationPreferencesView/setGroupChannelMaster">, setGroupFrequency: import("@reduxjs/toolkit").ActionCreatorWithPayload<SetFrequencyPayload, "notificationPreferencesView/setGroupFrequency">, toggleEventChannel: import("@reduxjs/toolkit").ActionCreatorWithPayload<TogglePreferencePayload, "notificationPreferencesView/toggleEventChannel">;
|
|
29
34
|
declare const _default: import("redux").Reducer<NotificationPreferencesViewState>;
|
|
30
35
|
export default _default;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.toggleEventChannel = exports.setGroupFrequency = exports.saveNotificationPreferencesSuccess = exports.saveNotificationPreferencesFailure = exports.saveNotificationPreferences = exports.fetchNotificationPreferencesSuccess = exports.clearNotificationPreferencesLocalOverrides = exports.clearAllNotificationPreferencesView = exports.initialState = void 0;
|
|
4
|
+
exports.toggleEventChannel = exports.setGroupFrequency = exports.setGroupChannelMaster = exports.saveNotificationPreferencesSuccess = exports.saveNotificationPreferencesFailure = exports.saveNotificationPreferences = exports.fetchNotificationPreferencesSuccess = exports.clearNotificationPreferencesLocalOverrides = exports.clearAllNotificationPreferencesView = exports.initialState = void 0;
|
|
5
5
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
6
6
|
const notificationPreferencesViewPayload_1 = require("./notificationPreferencesViewPayload");
|
|
7
7
|
const notificationPreferencesViewState_1 = require("./notificationPreferencesViewState");
|
|
@@ -22,8 +22,18 @@ const mergePreferences = (base, patch) => {
|
|
|
22
22
|
...channels,
|
|
23
23
|
};
|
|
24
24
|
});
|
|
25
|
+
const groupChannelEnabledByGroupId = {
|
|
26
|
+
...base.groupChannelEnabledByGroupId,
|
|
27
|
+
};
|
|
28
|
+
Object.entries(patch.groupChannelEnabledByGroupId).forEach(([groupId, channels]) => {
|
|
29
|
+
groupChannelEnabledByGroupId[groupId] = {
|
|
30
|
+
...(base.groupChannelEnabledByGroupId[groupId] ?? {}),
|
|
31
|
+
...channels,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
25
34
|
return {
|
|
26
35
|
eventEnabledByChannel,
|
|
36
|
+
groupChannelEnabledByGroupId,
|
|
27
37
|
groupFrequencyByGroupId: {
|
|
28
38
|
...base.groupFrequencyByGroupId,
|
|
29
39
|
...patch.groupFrequencyByGroupId,
|
|
@@ -34,16 +44,16 @@ const mergePreferences = (base, patch) => {
|
|
|
34
44
|
// to the server from `localOverrides` — AND whose current value still equals
|
|
35
45
|
// what was saved. Any new edits the user added while the PUT was in flight
|
|
36
46
|
// (either new keys not present in `saved`, OR same keys the user flipped back
|
|
37
|
-
// to a different value) are preserved so the next
|
|
47
|
+
// to a different value) are preserved so the next Save picks them up.
|
|
38
48
|
//
|
|
39
49
|
// Presence-only subtraction (the prior implementation) silently dropped a
|
|
40
|
-
// same-key re-edit that landed AFTER
|
|
50
|
+
// same-key re-edit that landed AFTER a Save fired but BEFORE the response
|
|
41
51
|
// returned. Concrete case:
|
|
42
52
|
// 1. toggle evt1.email = false → localOverrides = {evt1:{email:false}}
|
|
43
|
-
// 2.
|
|
53
|
+
// 2. Save → PUT #1 fires with savedOverrides = {evt1:{email:false}}
|
|
44
54
|
// 3. user re-toggles evt1.email = true → localOverrides = {evt1:{email:true}}
|
|
45
55
|
// 4. PUT #1 success → subtract dropped `email` on presence, so the true
|
|
46
|
-
// re-edit disappeared and next
|
|
56
|
+
// re-edit disappeared and the next Save found nothing to send.
|
|
47
57
|
const subtractSavedOverrides = (current, saved) => {
|
|
48
58
|
const eventEnabledByChannel = {};
|
|
49
59
|
Object.entries(current.eventEnabledByChannel).forEach(([eventId, channels]) => {
|
|
@@ -65,13 +75,38 @@ const subtractSavedOverrides = (current, saved) => {
|
|
|
65
75
|
eventEnabledByChannel[eventId] = remainingChannels;
|
|
66
76
|
}
|
|
67
77
|
});
|
|
78
|
+
// Same value-equality subtraction as eventEnabledByChannel above — a
|
|
79
|
+
// group-channel master the user re-flipped while a PUT was in flight must
|
|
80
|
+
// survive so the next Save re-sends it.
|
|
81
|
+
const groupChannelEnabledByGroupId = {};
|
|
82
|
+
Object.entries(current.groupChannelEnabledByGroupId).forEach(([groupId, channels]) => {
|
|
83
|
+
const savedChannels = saved.groupChannelEnabledByGroupId[groupId];
|
|
84
|
+
if (savedChannels == null) {
|
|
85
|
+
groupChannelEnabledByGroupId[groupId] = channels;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const remainingChannels = {};
|
|
89
|
+
Object.keys(channels).forEach((channel) => {
|
|
90
|
+
if (!(channel in savedChannels) ||
|
|
91
|
+
channels[channel] !== savedChannels[channel]) {
|
|
92
|
+
remainingChannels[channel] = channels[channel];
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (Object.keys(remainingChannels).length > 0) {
|
|
96
|
+
groupChannelEnabledByGroupId[groupId] = remainingChannels;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
68
99
|
const groupFrequencyByGroupId = {};
|
|
69
100
|
Object.entries(current.groupFrequencyByGroupId).forEach(([groupId, frequency]) => {
|
|
70
101
|
if (saved.groupFrequencyByGroupId[groupId] !== frequency) {
|
|
71
102
|
groupFrequencyByGroupId[groupId] = frequency;
|
|
72
103
|
}
|
|
73
104
|
});
|
|
74
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
eventEnabledByChannel,
|
|
107
|
+
groupChannelEnabledByGroupId,
|
|
108
|
+
groupFrequencyByGroupId,
|
|
109
|
+
};
|
|
75
110
|
};
|
|
76
111
|
const notificationPreferencesView = (0, toolkit_1.createSlice)({
|
|
77
112
|
name: 'notificationPreferencesView',
|
|
@@ -127,8 +162,8 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
|
|
|
127
162
|
// snapshot of the server-known preferences (empty maps included), so
|
|
128
163
|
// REPLACE `preferences` rather than merging — merging would leak keys
|
|
129
164
|
// the server has since removed. DO NOT touch `localOverrides`: a refetch
|
|
130
|
-
// that fires while the user has
|
|
131
|
-
//
|
|
165
|
+
// that fires while the user has unsaved edits must not silently drop
|
|
166
|
+
// them; the save-success path is the only clearer.
|
|
132
167
|
// A `null`/`undefined` payload isn't a snapshot at all (e.g. the
|
|
133
168
|
// envelope key was omitted); no-op instead of wiping preferences.
|
|
134
169
|
if (action.payload.preferences == null) {
|
|
@@ -181,8 +216,8 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
|
|
|
181
216
|
// In-flight-edit invariant: `savedOverrides` is the exact snapshot the
|
|
182
217
|
// epic PUT to the server. Subtracting those keys from `localOverrides`
|
|
183
218
|
// (instead of clearing it wholesale) preserves any edits the user
|
|
184
|
-
// added AFTER the
|
|
185
|
-
// next
|
|
219
|
+
// added AFTER the Save fired but BEFORE the response landed. The
|
|
220
|
+
// next Save will pick them up. Clearing wholesale drops
|
|
186
221
|
// in-flight edits from both UI (once server echo lands) and server.
|
|
187
222
|
// Cancel/in-flight-save race: `cancelEpoch` monotonically increases
|
|
188
223
|
// on every Cancel. If the save captured a lower epoch at dispatch,
|
|
@@ -208,6 +243,16 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
|
|
|
208
243
|
draft.preferences = mergePreferences(draft.preferences, patch);
|
|
209
244
|
draft.localOverrides = subtractSavedOverrides(draft.localOverrides, action.payload.savedOverrides);
|
|
210
245
|
},
|
|
246
|
+
setGroupChannelMaster(draft, action) {
|
|
247
|
+
// Mirror of `toggleEventChannel` but scoped to the group-channel master
|
|
248
|
+
// held under `groupChannelEnabledByGroupId[groupId][channel]`.
|
|
249
|
+
const { channel, enabled, groupId } = action.payload;
|
|
250
|
+
const groupMap = {
|
|
251
|
+
...(draft.localOverrides.groupChannelEnabledByGroupId[groupId] ?? {}),
|
|
252
|
+
};
|
|
253
|
+
groupMap[channel] = enabled;
|
|
254
|
+
draft.localOverrides.groupChannelEnabledByGroupId[groupId] = groupMap;
|
|
255
|
+
},
|
|
211
256
|
setGroupFrequency(draft, action) {
|
|
212
257
|
draft.localOverrides.groupFrequencyByGroupId[action.payload.groupId] =
|
|
213
258
|
action.payload.frequency;
|
|
@@ -222,5 +267,5 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
|
|
|
222
267
|
},
|
|
223
268
|
},
|
|
224
269
|
});
|
|
225
|
-
_a = notificationPreferencesView.actions, exports.clearAllNotificationPreferencesView = _a.clearAllNotificationPreferencesView, exports.clearNotificationPreferencesLocalOverrides = _a.clearNotificationPreferencesLocalOverrides, exports.fetchNotificationPreferencesSuccess = _a.fetchNotificationPreferencesSuccess, exports.saveNotificationPreferences = _a.saveNotificationPreferences, exports.saveNotificationPreferencesFailure = _a.saveNotificationPreferencesFailure, exports.saveNotificationPreferencesSuccess = _a.saveNotificationPreferencesSuccess, exports.setGroupFrequency = _a.setGroupFrequency, exports.toggleEventChannel = _a.toggleEventChannel;
|
|
270
|
+
_a = notificationPreferencesView.actions, exports.clearAllNotificationPreferencesView = _a.clearAllNotificationPreferencesView, exports.clearNotificationPreferencesLocalOverrides = _a.clearNotificationPreferencesLocalOverrides, exports.fetchNotificationPreferencesSuccess = _a.fetchNotificationPreferencesSuccess, exports.saveNotificationPreferences = _a.saveNotificationPreferences, exports.saveNotificationPreferencesFailure = _a.saveNotificationPreferencesFailure, exports.saveNotificationPreferencesSuccess = _a.saveNotificationPreferencesSuccess, exports.setGroupChannelMaster = _a.setGroupChannelMaster, exports.setGroupFrequency = _a.setGroupFrequency, exports.toggleEventChannel = _a.toggleEventChannel;
|
|
226
271
|
exports.default = notificationPreferencesView.reducer;
|