@zeniai/client-epic-state 5.2.33 → 5.2.34-beta0ND
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/commonPayloadTypes/classesView/reportByClassPayloadV2.d.ts +13 -1
- package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +2 -1
- package/lib/entity/notificationRegistry/notificationRegistryReducer.js +20 -9
- package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +2 -1
- package/lib/entity/notificationRegistry/notificationRegistrySelector.js +7 -1
- package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +5 -0
- package/lib/entity/notificationRegistry/notificationRegistryState.js +7 -1
- package/lib/entity/sectionClassesViewV2/sectionClassesViewEpic.js +10 -7
- package/lib/entity/sectionClassesViewV2/sectionClassesViewReducer.d.ts +3 -3
- package/lib/entity/sectionProjectView/sectionProjectViewEpic.js +6 -2
- package/lib/entity/sectionProjectView/sectionProjectViewReducer.d.ts +3 -3
- package/lib/entity/sectionProjectView/sectionProjectViewReducer.js +6 -3
- package/lib/epic.js +12 -0
- package/lib/esm/entity/class/classReducer.js +1 -1
- package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +20 -9
- package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +5 -0
- package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +5 -0
- package/lib/esm/entity/sectionClassesViewV2/sectionClassesViewEpic.js +10 -7
- package/lib/esm/entity/sectionProjectView/sectionProjectViewEpic.js +6 -2
- package/lib/esm/entity/sectionProjectView/sectionProjectViewReducer.js +7 -4
- package/lib/esm/epic.js +12 -0
- package/lib/esm/index.js +6 -6
- package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +268 -1
- package/lib/esm/view/aiCfoView/aiCfoViewSelector.js +50 -0
- package/lib/esm/view/aiCfoView/epics/deleteRoutineEpic.js +35 -0
- package/lib/esm/view/aiCfoView/epics/fetchRoutineRunsEpic.js +37 -0
- package/lib/esm/view/aiCfoView/epics/fetchRoutinesEpic.js +37 -0
- package/lib/esm/view/aiCfoView/epics/runRoutineEpic.js +34 -0
- package/lib/esm/view/aiCfoView/epics/saveRoutineEpic.js +65 -0
- package/lib/esm/view/aiCfoView/epics/setRoutineEnabledEpic.js +28 -0
- package/lib/esm/view/aiCfoView/epics/toRoutine.js +64 -0
- package/lib/esm/view/aiCfoView/epics/toRoutineRun.js +42 -0
- package/lib/esm/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +12 -10
- package/lib/esm/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -11
- package/lib/esm/view/expenseAutomationView/selectors/missingReceiptsSelector.js +10 -11
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.js +4 -0
- package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +20 -7
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +43 -17
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +24 -4
- package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +12 -2
- package/lib/index.d.ts +8 -8
- package/lib/index.js +63 -41
- package/lib/view/aiCfoView/aiCfoViewPayload.d.ts +90 -0
- package/lib/view/aiCfoView/aiCfoViewReducer.d.ts +164 -123
- package/lib/view/aiCfoView/aiCfoViewReducer.js +270 -3
- package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +13 -1
- package/lib/view/aiCfoView/aiCfoViewSelector.js +55 -0
- package/lib/view/aiCfoView/aiCfoViewState.d.ts +157 -0
- package/lib/view/aiCfoView/epics/deleteRoutineEpic.d.ts +18 -0
- package/lib/view/aiCfoView/epics/deleteRoutineEpic.js +39 -0
- package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.d.ts +13 -0
- package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.js +41 -0
- package/lib/view/aiCfoView/epics/fetchRoutinesEpic.d.ts +7 -0
- package/lib/view/aiCfoView/epics/fetchRoutinesEpic.js +41 -0
- package/lib/view/aiCfoView/epics/runRoutineEpic.d.ts +14 -0
- package/lib/view/aiCfoView/epics/runRoutineEpic.js +38 -0
- package/lib/view/aiCfoView/epics/saveRoutineEpic.d.ts +13 -0
- package/lib/view/aiCfoView/epics/saveRoutineEpic.js +69 -0
- package/lib/view/aiCfoView/epics/setRoutineEnabledEpic.d.ts +15 -0
- package/lib/view/aiCfoView/epics/setRoutineEnabledEpic.js +32 -0
- package/lib/view/aiCfoView/epics/toRoutine.d.ts +17 -0
- package/lib/view/aiCfoView/epics/toRoutine.js +69 -0
- package/lib/view/aiCfoView/epics/toRoutineRun.d.ts +12 -0
- package/lib/view/aiCfoView/epics/toRoutineRun.js +47 -0
- package/lib/view/cashFlowClassesView/cashFlowClassesViewPayload.d.ts +4 -4
- package/lib/view/cashFlowProjectView/cashFlowProjectViewPayload.d.ts +4 -4
- package/lib/view/expenseAutomationView/selectors/fluxAnalysisViewSelector.js +12 -10
- package/lib/view/expenseAutomationView/selectors/jeSchedulesViewSelector.js +3 -11
- package/lib/view/expenseAutomationView/selectors/missingReceiptsSelector.js +10 -11
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.d.ts +1 -11
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.js +4 -0
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +1 -2
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.d.ts +1 -2
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState.d.ts +15 -1
- package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.d.ts +1 -1
- package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewState.d.ts +1 -1
- package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +19 -6
- package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +8 -5
- package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +43 -17
- package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +4 -0
- package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +30 -5
- package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +4 -3
- package/lib/view/profitAndLossProjectView/profitAndLossProjectViewPayload.d.ts +12 -0
- package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +12 -2
- package/lib/view/settingsView/settingsViewPayload.d.ts +2 -0
- package/package.json +1 -1
|
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getExpenseAutomationMissingReceiptsView = getExpenseAutomationMissingReceiptsView;
|
|
7
7
|
const orderBy_1 = __importDefault(require("lodash/orderBy"));
|
|
8
|
-
const reduceFetchState_1 = require("../../../commonStateTypes/reduceFetchState");
|
|
9
8
|
const timePeriod_1 = require("../../../commonStateTypes/timePeriod");
|
|
10
9
|
const tenantSelector_1 = require("../../../entity/tenant/tenantSelector");
|
|
11
10
|
const transactionSelector_1 = require("../../../entity/transaction/transactionSelector");
|
|
@@ -14,7 +13,7 @@ const missingReceiptsViewReducer_1 = require("../reducers/missingReceiptsViewRed
|
|
|
14
13
|
const missingReceiptsViewState_1 = require("../types/missingReceiptsViewState");
|
|
15
14
|
const getAllStepsHelper_1 = require("./getAllStepsHelper");
|
|
16
15
|
function getExpenseAutomationMissingReceiptsView(state) {
|
|
17
|
-
const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState,
|
|
16
|
+
const { expenseAutomationMissingReceiptsViewState, expenseAutomationViewState, transactionState, monthEndCloseChecksState, } = state;
|
|
18
17
|
const currentTenant = (0, tenantSelector_1.getCurrentTenant)(state);
|
|
19
18
|
const { selectedPeriodByTenantId } = expenseAutomationViewState;
|
|
20
19
|
const selectedPeriod = selectedPeriodByTenantId[currentTenant.tenantId];
|
|
@@ -182,16 +181,16 @@ function getExpenseAutomationMissingReceiptsView(state) {
|
|
|
182
181
|
vendorLogo: result.vendorLogo ?? transaction?.logo?.href,
|
|
183
182
|
};
|
|
184
183
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
// `fetchState` is a flat slice field that survives a period switch, so it still reads
|
|
185
|
+
// 'Completed' before the newly selected period's ids land. Reporting 'Not-Started' until they
|
|
186
|
+
// do keeps the skeleton up instead of flashing the "all done" empty state over the previous
|
|
187
|
+
// period's rows.
|
|
188
|
+
const fetchStatus = fetchState === 'Completed' && transactionIdsForSelectedPeriod == null
|
|
189
|
+
? { fetchState: 'Not-Started', error: undefined }
|
|
190
|
+
: { fetchState, error };
|
|
192
191
|
return {
|
|
193
|
-
fetchState:
|
|
194
|
-
error:
|
|
192
|
+
fetchState: fetchStatus.fetchState,
|
|
193
|
+
error: fetchStatus.error,
|
|
195
194
|
uploadReceiptStatusById,
|
|
196
195
|
uiState,
|
|
197
196
|
refreshStatus,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InvoicingCustomerPayload } from '../../../entity/invoicing/invoicingCustomer/invoicingCustomerPayload';
|
|
2
2
|
import { ZeniAPIResponse } from '../../../responsePayload';
|
|
3
3
|
import { AddressUpdatableInfo } from '../../addressView/addressViewState';
|
|
4
|
-
import { InvoicingCustomerFormLocalData } from './editInvoicingCustomerDetailViewState';
|
|
4
|
+
import { InvoicingCustomerFormLocalData, InvoicingPaymentLinkEmailPreviewData } from './editInvoicingCustomerDetailViewState';
|
|
5
5
|
export type SaveInvoicingCustomerResponse = ZeniAPIResponse<InvoicingCustomerPayload>;
|
|
6
6
|
/**
|
|
7
7
|
* Build the create/update customer request body from the form draft. The
|
|
@@ -25,15 +25,5 @@ export type InvoicingPaymentLinkEmailPreviewResponse = ZeniAPIResponse<Invoicing
|
|
|
25
25
|
* The email rendered once with sentinel tokens standing in for the subject and
|
|
26
26
|
* body, so the composer can splice a draft into it without another request.
|
|
27
27
|
*/
|
|
28
|
-
export interface InvoicingPaymentLinkEmailPreviewData {
|
|
29
|
-
/** Default body prefill, plain text. */
|
|
30
|
-
body: string;
|
|
31
|
-
bodyToken: string;
|
|
32
|
-
renderedHtmlTemplate: string;
|
|
33
|
-
/** Default subject prefill. */
|
|
34
|
-
subject: string;
|
|
35
|
-
subjectToken: string;
|
|
36
|
-
to: string;
|
|
37
|
-
}
|
|
38
28
|
export declare const toInvoicingPaymentLinkEmailPreview: (payload: InvoicingPaymentLinkEmailPreviewPayload) => InvoicingPaymentLinkEmailPreviewData;
|
|
39
29
|
export type AddPromotionalCreditsResponse = ZeniAPIResponse<InvoicingCustomerPayload>;
|
package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewPayload.js
CHANGED
|
@@ -34,6 +34,10 @@ const localDataToSaveInvoicingCustomerPayload = (localData, billingAddress) => {
|
|
|
34
34
|
};
|
|
35
35
|
};
|
|
36
36
|
exports.localDataToSaveInvoicingCustomerPayload = localDataToSaveInvoicingCustomerPayload;
|
|
37
|
+
/**
|
|
38
|
+
* The email rendered once with sentinel tokens standing in for the subject and
|
|
39
|
+
* body, so the composer can splice a draft into it without another request.
|
|
40
|
+
*/
|
|
37
41
|
const toInvoicingPaymentLinkEmailPreview = (payload) => ({
|
|
38
42
|
body: payload.body,
|
|
39
43
|
bodyToken: payload.body_token,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ID } from '../../../commonStateTypes/common';
|
|
2
2
|
import { ZeniAPIStatus } from '../../../responsePayload';
|
|
3
|
-
import { InvoicingPaymentLinkEmailPreviewData } from './
|
|
4
|
-
import { EditInvoicingCustomerDetailViewState, InvoicingCustomerFormLocalData } from './editInvoicingCustomerDetailViewState';
|
|
3
|
+
import { EditInvoicingCustomerDetailViewState, InvoicingCustomerFormLocalData, InvoicingPaymentLinkEmailPreviewData } from './editInvoicingCustomerDetailViewState';
|
|
5
4
|
/** Add promotional credits to a customer. */
|
|
6
5
|
export interface SubmitAddPromotionalCreditsPayload {
|
|
7
6
|
amount: number;
|
|
@@ -5,8 +5,7 @@ import { InvoicingFieldHelpByCode, InvoicingNamedOption } from '../../../entity/
|
|
|
5
5
|
import { InvoicingCustomer, InvoicingCustomerPaymentMethod } from '../../../entity/invoicing/invoicingCustomer/invoicingCustomerState';
|
|
6
6
|
import { RootState } from '../../../rootStateTypes';
|
|
7
7
|
import { InvoicingSetupIntent } from '../customerPaymentMethod/customerPaymentMethodState';
|
|
8
|
-
import { InvoicingPaymentLinkEmailPreviewData } from './
|
|
9
|
-
import { InvoicingCustomerFormLocalData } from './editInvoicingCustomerDetailViewState';
|
|
8
|
+
import { InvoicingCustomerFormLocalData, InvoicingPaymentLinkEmailPreviewData } from './editInvoicingCustomerDetailViewState';
|
|
10
9
|
/**
|
|
11
10
|
* Read-only view for the customer detail page: the customer entity + its fetch
|
|
12
11
|
* state, plus everything the page renders in view mode — recent invoices, the
|
package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState.d.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { FetchStateAndError, FetchedState, ID } from '../../../commonStateTypes/common';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The payment-link email the composer opens with: the backend's prefill plus
|
|
4
|
+
* the tokens the frontend splices the user's edits into. State, not wire shape
|
|
5
|
+
* — `toInvoicingPaymentLinkEmailPreview` in the payload file maps onto it.
|
|
6
|
+
*/
|
|
7
|
+
export interface InvoicingPaymentLinkEmailPreviewData {
|
|
8
|
+
/** Default body prefill, plain text. */
|
|
9
|
+
body: string;
|
|
10
|
+
bodyToken: string;
|
|
11
|
+
renderedHtmlTemplate: string;
|
|
12
|
+
/** Default subject prefill. */
|
|
13
|
+
subject: string;
|
|
14
|
+
subjectToken: string;
|
|
15
|
+
to: string;
|
|
16
|
+
}
|
|
3
17
|
/**
|
|
4
18
|
* Draft form state for the create/edit customer form. Held in CES so the form
|
|
5
19
|
* flows its data from here (mirrors the bill-pay / reimbursement pattern). The
|
|
@@ -26,7 +26,7 @@ export declare const fetchInvoicingCustomerList: import("@reduxjs/toolkit").Acti
|
|
|
26
26
|
ids: ID[];
|
|
27
27
|
keepExistingListItems: boolean;
|
|
28
28
|
nextCursor: string | null;
|
|
29
|
-
}, "invoicingCustomerListView/updateInvoicingCustomerList">, updateInvoicingCustomerListFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "invoicingCustomerListView/updateInvoicingCustomerListFailure">, fetchInvoicingCustomerCounts: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"invoicingCustomerListView/fetchInvoicingCustomerCounts">, updateInvoicingCustomerCounts: import("@reduxjs/toolkit").ActionCreatorWithPayload<InvoicingListCounts, "invoicingCustomerListView/updateInvoicingCustomerCounts">, updateInvoicingCustomerCountsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "invoicingCustomerListView/updateInvoicingCustomerCountsFailure">, updateInvoicingCustomerFilters: import("@reduxjs/toolkit").ActionCreatorWithPayload<InvoicingCustomerFilters, "invoicingCustomerListView/updateInvoicingCustomerFilters">, setInvoicingCustomerActiveTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"active" | "all" | "
|
|
29
|
+
}, "invoicingCustomerListView/updateInvoicingCustomerList">, updateInvoicingCustomerListFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "invoicingCustomerListView/updateInvoicingCustomerListFailure">, fetchInvoicingCustomerCounts: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"invoicingCustomerListView/fetchInvoicingCustomerCounts">, updateInvoicingCustomerCounts: import("@reduxjs/toolkit").ActionCreatorWithPayload<InvoicingListCounts, "invoicingCustomerListView/updateInvoicingCustomerCounts">, updateInvoicingCustomerCountsFailure: import("@reduxjs/toolkit").ActionCreatorWithPayload<ZeniAPIStatus<Record<string, unknown>>, "invoicingCustomerListView/updateInvoicingCustomerCountsFailure">, updateInvoicingCustomerFilters: import("@reduxjs/toolkit").ActionCreatorWithPayload<InvoicingCustomerFilters, "invoicingCustomerListView/updateInvoicingCustomerFilters">, setInvoicingCustomerActiveTab: import("@reduxjs/toolkit").ActionCreatorWithPayload<"active" | "all" | "trial" | "at_risk", "invoicingCustomerListView/setInvoicingCustomerActiveTab">, addInvoicingCustomerToList: import("@reduxjs/toolkit").ActionCreatorWithPayload<{
|
|
30
30
|
id: ID;
|
|
31
31
|
status?: string;
|
|
32
32
|
}, "invoicingCustomerListView/addInvoicingCustomerToList">;
|
|
@@ -20,7 +20,7 @@ export interface InvoicingCustomerColumnConfig {
|
|
|
20
20
|
}
|
|
21
21
|
export declare const INVOICING_CUSTOMER_COLUMNS: InvoicingCustomerColumnConfig[];
|
|
22
22
|
export declare const ALL_INVOICING_CUSTOMER_TAB_IDS: readonly ["all", "active", "trial", "at_risk"];
|
|
23
|
-
export declare const toInvoicingCustomerTabId: (v: string) => "active" | "all" | "
|
|
23
|
+
export declare const toInvoicingCustomerTabId: (v: string) => "active" | "all" | "trial" | "at_risk";
|
|
24
24
|
export type InvoicingCustomerTabId = ReturnType<typeof toInvoicingCustomerTabId>;
|
|
25
25
|
export interface InvoicingCustomerListViewState extends FetchedState {
|
|
26
26
|
/** Selected status tab (CES-owned so it persists across page revisits). */
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.saveNotificationPreferencesEpic = void 0;
|
|
4
4
|
const rxjs_1 = require("rxjs");
|
|
5
5
|
const operators_1 = require("rxjs/operators");
|
|
6
|
+
const notificationRegistryState_1 = require("../../../entity/notificationRegistry/notificationRegistryState");
|
|
6
7
|
const snackbarReducer_1 = require("../../../entity/snackbar/snackbarReducer");
|
|
7
8
|
const responsePayload_1 = require("../../../responsePayload");
|
|
8
9
|
const notificationPreferencesEndpoint_1 = require("../notificationPreferencesEndpoint");
|
|
@@ -16,9 +17,19 @@ const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.
|
|
|
16
17
|
// ignored, so stale success/failure actions cannot clobber the newer
|
|
17
18
|
// batch's state. Server-side ordering is arrival-order LWW.
|
|
18
19
|
(0, operators_1.switchMap)(([, state]) => {
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
20
|
+
// A clean domain is omitted entirely: an empty patch is still a write against it.
|
|
21
|
+
const overridesByDomain = {};
|
|
22
|
+
const body = {};
|
|
23
|
+
notificationRegistryState_1.NOTIFICATION_DOMAINS.forEach((domain) => {
|
|
24
|
+
const overrides = (0, notificationPreferencesViewSelector_1.getNotificationLocalOverridesForDomain)(state, domain);
|
|
25
|
+
const preferencesPatch = (0, notificationPreferencesViewPayload_1.mapPreferencesToPayload)(overrides);
|
|
26
|
+
if (Object.keys(preferencesPatch).length === 0) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
overridesByDomain[domain] = overrides;
|
|
30
|
+
body[`${domain}_preferences`] = preferencesPatch;
|
|
31
|
+
});
|
|
32
|
+
if (Object.keys(body).length === 0) {
|
|
22
33
|
// No diff → do not dispatch a synthetic success (that would wipe
|
|
23
34
|
// stored preferences via the reducer). Simply no-op.
|
|
24
35
|
return rxjs_1.EMPTY;
|
|
@@ -31,7 +42,6 @@ const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.
|
|
|
31
42
|
// in-flight save's response (Success/Failure never dispatched, so
|
|
32
43
|
// the counter never drained and future saves got their merge dropped).
|
|
33
44
|
const cancelEpochAtDispatch = state.notificationPreferencesViewState.cancelEpoch;
|
|
34
|
-
const body = { task_checklist_preferences: preferencesPatch };
|
|
35
45
|
// Suppress the error snackbar when the response lands AFTER the user
|
|
36
46
|
// pressed Cancel — the Failure reducer already swallows the state
|
|
37
47
|
// update, so the toast would be a stale error for a save the user
|
|
@@ -60,8 +70,11 @@ const saveNotificationPreferencesEpic = (actions$, state$, zeniAPI) => actions$.
|
|
|
60
70
|
if ((0, responsePayload_1.isSuccessResponse)(response)) {
|
|
61
71
|
return (0, rxjs_1.of)((0, notificationPreferencesViewReducer_1.saveNotificationPreferencesSuccess)({
|
|
62
72
|
cancelEpochAtDispatch,
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
savedOverridesByDomain: overridesByDomain,
|
|
74
|
+
serverEchoByDomain: {
|
|
75
|
+
task_checklist: response.data?.task_checklist_preferences,
|
|
76
|
+
commenting: response.data?.commenting_preferences,
|
|
77
|
+
},
|
|
65
78
|
}));
|
|
66
79
|
}
|
|
67
80
|
const actions = [
|
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
import { NotificationChannel, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
|
|
1
|
+
import { NotificationChannel, NotificationDomain, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
|
|
2
2
|
import { ZeniAPIStatus } from '../../responsePayload';
|
|
3
3
|
import { NotificationPreferencesPayload } from './notificationPreferencesViewPayload';
|
|
4
|
-
import {
|
|
4
|
+
import { NotificationPreferencesByDomain, NotificationPreferencesViewState } from './notificationPreferencesViewState';
|
|
5
5
|
export declare const initialState: NotificationPreferencesViewState;
|
|
6
6
|
export interface SetFrequencyPayload {
|
|
7
7
|
frequency: NotificationFrequency;
|
|
8
8
|
groupId: string;
|
|
9
|
+
domain?: NotificationDomain;
|
|
9
10
|
}
|
|
10
11
|
export interface TogglePreferencePayload {
|
|
11
12
|
channel: NotificationChannel;
|
|
12
13
|
enabled: boolean;
|
|
13
14
|
eventId: string;
|
|
15
|
+
domain?: NotificationDomain;
|
|
14
16
|
}
|
|
15
17
|
export interface SetGroupChannelMasterPayload {
|
|
16
18
|
channel: NotificationChannel;
|
|
17
19
|
enabled: boolean;
|
|
18
20
|
groupId: string;
|
|
21
|
+
domain?: NotificationDomain;
|
|
19
22
|
}
|
|
20
23
|
export interface FetchNotificationPreferencesSuccessPayload {
|
|
21
|
-
|
|
24
|
+
preferencesByDomain: Partial<Record<NotificationDomain, NotificationPreferencesPayload | undefined>>;
|
|
22
25
|
saveEpochAtDispatch: number;
|
|
23
26
|
}
|
|
24
27
|
export interface SaveNotificationPreferencesSuccessPayload {
|
|
25
28
|
cancelEpochAtDispatch: number;
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
savedOverridesByDomain: NotificationPreferencesByDomain;
|
|
30
|
+
serverEchoByDomain: Partial<Record<NotificationDomain, NotificationPreferencesPayload | undefined>>;
|
|
28
31
|
}
|
|
29
32
|
export interface SaveNotificationPreferencesFailurePayload {
|
|
30
33
|
cancelEpochAtDispatch: number;
|
|
@@ -3,16 +3,27 @@ var _a;
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
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
|
+
const notificationRegistryState_1 = require("../../entity/notificationRegistry/notificationRegistryState");
|
|
6
7
|
const rootActions_1 = require("../../rootActions");
|
|
7
8
|
const notificationPreferencesViewPayload_1 = require("./notificationPreferencesViewPayload");
|
|
8
9
|
const notificationPreferencesViewState_1 = require("./notificationPreferencesViewState");
|
|
9
10
|
exports.initialState = {
|
|
10
11
|
cancelEpoch: 0,
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
localOverridesByDomain: {},
|
|
13
|
+
preferencesByDomain: {},
|
|
13
14
|
saveEpoch: 0,
|
|
14
15
|
savePreferencesState: { fetchState: 'Not-Started' },
|
|
15
16
|
};
|
|
17
|
+
// Lazy: a gate-off tenant never receives that domain's key at all.
|
|
18
|
+
const overridesFor = (byDomain, domain) => {
|
|
19
|
+
const existing = byDomain[domain];
|
|
20
|
+
if (existing != null) {
|
|
21
|
+
return existing;
|
|
22
|
+
}
|
|
23
|
+
const created = (0, notificationPreferencesViewState_1.emptyNotificationPreferences)();
|
|
24
|
+
byDomain[domain] = created;
|
|
25
|
+
return created;
|
|
26
|
+
};
|
|
16
27
|
const mergePreferences = (base, patch) => {
|
|
17
28
|
const eventEnabledByChannel = {
|
|
18
29
|
...base.eventEnabledByChannel,
|
|
@@ -150,7 +161,8 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
|
|
|
150
161
|
// Success/Failure never dispatches, subsequent legitimate saves
|
|
151
162
|
// still work because their captured epoch matches the current one.
|
|
152
163
|
draft.cancelEpoch += 1;
|
|
153
|
-
|
|
164
|
+
// One shared Save/Cancel footer, so Cancel discards every domain.
|
|
165
|
+
draft.localOverridesByDomain = {};
|
|
154
166
|
// Also drop `savePreferencesState` back to Not-Started so any
|
|
155
167
|
// "Saving…" affordance bound to `getNotificationPreferencesSaveState`
|
|
156
168
|
// clears on Cancel. Without this, a Cancel-during-save whose stale
|
|
@@ -167,9 +179,6 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
|
|
|
167
179
|
// them; the save-success path is the only clearer.
|
|
168
180
|
// A `null`/`undefined` payload isn't a snapshot at all (e.g. the
|
|
169
181
|
// envelope key was omitted); no-op instead of wiping preferences.
|
|
170
|
-
if (action.payload.preferences == null) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
182
|
// Stale-GET guard (Cursor Bugbot 3632510237): if the fetch epic
|
|
174
183
|
// captured a `saveEpoch` lower than the current one, a save has
|
|
175
184
|
// succeeded since this GET dispatched — the response is carrying
|
|
@@ -179,7 +188,13 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
|
|
|
179
188
|
if (action.payload.saveEpochAtDispatch < draft.saveEpoch) {
|
|
180
189
|
return;
|
|
181
190
|
}
|
|
182
|
-
|
|
191
|
+
Object.entries(action.payload.preferencesByDomain).forEach(([domain, payload]) => {
|
|
192
|
+
if (payload == null) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
draft.preferencesByDomain[domain] =
|
|
196
|
+
(0, notificationPreferencesViewPayload_1.mapPayloadToPreferences)(payload);
|
|
197
|
+
});
|
|
183
198
|
},
|
|
184
199
|
saveNotificationPreferences(draft) {
|
|
185
200
|
draft.savePreferencesState = { fetchState: 'In-Progress' };
|
|
@@ -238,33 +253,44 @@ const notificationPreferencesView = (0, toolkit_1.createSlice)({
|
|
|
238
253
|
// skip the `preferences` replace. Cursor Bugbot 3632510237.
|
|
239
254
|
draft.saveEpoch += 1;
|
|
240
255
|
draft.savePreferencesState = { fetchState: 'Completed' };
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
256
|
+
Object.entries(action.payload.savedOverridesByDomain).forEach(([rawDomain, savedOverrides]) => {
|
|
257
|
+
if (savedOverrides == null) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const domain = rawDomain;
|
|
261
|
+
const serverEcho = action.payload.serverEchoByDomain[domain];
|
|
262
|
+
const patch = serverEcho == null
|
|
263
|
+
? savedOverrides
|
|
264
|
+
: (0, notificationPreferencesViewPayload_1.mapPayloadToPreferences)(serverEcho);
|
|
265
|
+
draft.preferencesByDomain[domain] = mergePreferences(draft.preferencesByDomain[domain] ?? (0, notificationPreferencesViewState_1.emptyNotificationPreferences)(), patch);
|
|
266
|
+
draft.localOverridesByDomain[domain] = subtractSavedOverrides(draft.localOverridesByDomain[domain] ??
|
|
267
|
+
(0, notificationPreferencesViewState_1.emptyNotificationPreferences)(), savedOverrides);
|
|
268
|
+
});
|
|
246
269
|
},
|
|
247
270
|
setGroupChannelMaster(draft, action) {
|
|
248
271
|
// Mirror of `toggleEventChannel` but scoped to the group-channel master
|
|
249
272
|
// held under `groupChannelEnabledByGroupId[groupId][channel]`.
|
|
250
273
|
const { channel, enabled, groupId } = action.payload;
|
|
274
|
+
const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
|
|
251
275
|
const groupMap = {
|
|
252
|
-
...(
|
|
276
|
+
...(overrides.groupChannelEnabledByGroupId[groupId] ?? {}),
|
|
253
277
|
};
|
|
254
278
|
groupMap[channel] = enabled;
|
|
255
|
-
|
|
279
|
+
overrides.groupChannelEnabledByGroupId[groupId] = groupMap;
|
|
256
280
|
},
|
|
257
281
|
setGroupFrequency(draft, action) {
|
|
258
|
-
draft.
|
|
282
|
+
const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
|
|
283
|
+
overrides.groupFrequencyByGroupId[action.payload.groupId] =
|
|
259
284
|
action.payload.frequency;
|
|
260
285
|
},
|
|
261
286
|
toggleEventChannel(draft, action) {
|
|
262
287
|
const { channel, enabled, eventId } = action.payload;
|
|
288
|
+
const overrides = overridesFor(draft.localOverridesByDomain, action.payload.domain ?? notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
|
|
263
289
|
const eventMap = {
|
|
264
|
-
...(
|
|
290
|
+
...(overrides.eventEnabledByChannel[eventId] ?? {}),
|
|
265
291
|
};
|
|
266
292
|
eventMap[channel] = enabled;
|
|
267
|
-
|
|
293
|
+
overrides.eventEnabledByChannel[eventId] = eventMap;
|
|
268
294
|
},
|
|
269
295
|
},
|
|
270
296
|
extraReducers: (builder) => {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { FetchStateAndError } from '../../commonStateTypes/common';
|
|
2
|
+
import { NotificationDomain } from '../../entity/notificationRegistry/notificationRegistryState';
|
|
2
3
|
import { RootState } from '../../rootStateTypes';
|
|
3
4
|
import { NotificationPreferences } from './notificationPreferencesViewState';
|
|
4
5
|
export declare const getNotificationPreferencesSaveState: (state: RootState) => FetchStateAndError;
|
|
6
|
+
export declare const getEffectiveNotificationPreferencesForDomain: (state: RootState, domain: NotificationDomain) => NotificationPreferences;
|
|
5
7
|
export declare const getEffectiveNotificationPreferences: (state: RootState) => NotificationPreferences;
|
|
8
|
+
export declare const getNotificationLocalOverridesForDomain: (state: RootState, domain: NotificationDomain) => NotificationPreferences;
|
|
6
9
|
export declare const getNotificationLocalOverrides: (state: RootState) => NotificationPreferences;
|
|
10
|
+
export declare const hasUnsavedNotificationPreferencesForDomain: (state: RootState, domain: NotificationDomain) => boolean;
|
|
7
11
|
export declare const hasUnsavedNotificationPreferences: (state: RootState) => boolean;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hasUnsavedNotificationPreferences = exports.getNotificationLocalOverrides = exports.getEffectiveNotificationPreferences = exports.getNotificationPreferencesSaveState = void 0;
|
|
3
|
+
exports.hasUnsavedNotificationPreferences = exports.hasUnsavedNotificationPreferencesForDomain = exports.getNotificationLocalOverrides = exports.getNotificationLocalOverridesForDomain = exports.getEffectiveNotificationPreferences = exports.getEffectiveNotificationPreferencesForDomain = exports.getNotificationPreferencesSaveState = void 0;
|
|
4
4
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
5
|
+
const notificationRegistryState_1 = require("../../entity/notificationRegistry/notificationRegistryState");
|
|
6
|
+
const notificationPreferencesViewState_1 = require("./notificationPreferencesViewState");
|
|
5
7
|
const getView = (state) => state.notificationPreferencesViewState;
|
|
6
8
|
const getNotificationPreferencesSaveState = (state) => getView(state).savePreferencesState;
|
|
7
9
|
exports.getNotificationPreferencesSaveState = getNotificationPreferencesSaveState;
|
|
@@ -9,7 +11,12 @@ exports.getNotificationPreferencesSaveState = getNotificationPreferencesSaveStat
|
|
|
9
11
|
// stable reference while neither input changes — a plain function would
|
|
10
12
|
// allocate a fresh object on every call and re-render every `useSelector`
|
|
11
13
|
// consumer on any unrelated dispatch.
|
|
12
|
-
|
|
14
|
+
const EMPTY_PREFERENCES = (0, notificationPreferencesViewState_1.emptyNotificationPreferences)();
|
|
15
|
+
// One per domain: a shared createSelector has a cache of one and would thrash across two.
|
|
16
|
+
const effectivePreferencesSelectorByDomain = new Map();
|
|
17
|
+
const createEffectivePreferencesSelector = (domain) => (0, toolkit_1.createSelector)((state) => getView(state).preferencesByDomain[domain], (state) => getView(state).localOverridesByDomain[domain], (storedPreferences, storedOverrides) => {
|
|
18
|
+
const preferences = storedPreferences ?? EMPTY_PREFERENCES;
|
|
19
|
+
const localOverrides = storedOverrides ?? EMPTY_PREFERENCES;
|
|
13
20
|
const mergedEventEnabledByChannel = { ...preferences.eventEnabledByChannel };
|
|
14
21
|
Object.entries(localOverrides.eventEnabledByChannel).forEach(([eventId, channels]) => {
|
|
15
22
|
mergedEventEnabledByChannel[eventId] = {
|
|
@@ -33,8 +40,26 @@ exports.getEffectiveNotificationPreferences = (0, toolkit_1.createSelector)((sta
|
|
|
33
40
|
},
|
|
34
41
|
};
|
|
35
42
|
});
|
|
36
|
-
const
|
|
43
|
+
const getEffectiveNotificationPreferencesForDomain = (state, domain) => {
|
|
44
|
+
let selector = effectivePreferencesSelectorByDomain.get(domain);
|
|
45
|
+
if (selector == null) {
|
|
46
|
+
selector = createEffectivePreferencesSelector(domain);
|
|
47
|
+
effectivePreferencesSelectorByDomain.set(domain, selector);
|
|
48
|
+
}
|
|
49
|
+
return selector(state);
|
|
50
|
+
};
|
|
51
|
+
exports.getEffectiveNotificationPreferencesForDomain = getEffectiveNotificationPreferencesForDomain;
|
|
52
|
+
const getEffectiveNotificationPreferences = (state) => (0, exports.getEffectiveNotificationPreferencesForDomain)(state, notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
|
|
53
|
+
exports.getEffectiveNotificationPreferences = getEffectiveNotificationPreferences;
|
|
54
|
+
const getNotificationLocalOverridesForDomain = (state, domain) => getView(state).localOverridesByDomain[domain] ?? EMPTY_PREFERENCES;
|
|
55
|
+
exports.getNotificationLocalOverridesForDomain = getNotificationLocalOverridesForDomain;
|
|
56
|
+
const getNotificationLocalOverrides = (state) => (0, exports.getNotificationLocalOverridesForDomain)(state, notificationRegistryState_1.DEFAULT_NOTIFICATION_DOMAIN);
|
|
37
57
|
exports.getNotificationLocalOverrides = getNotificationLocalOverrides;
|
|
38
|
-
|
|
58
|
+
const isDirty = (localOverrides) => Object.keys(localOverrides.eventEnabledByChannel).length > 0 ||
|
|
39
59
|
Object.keys(localOverrides.groupChannelEnabledByGroupId).length > 0 ||
|
|
40
|
-
Object.keys(localOverrides.groupFrequencyByGroupId).length > 0
|
|
60
|
+
Object.keys(localOverrides.groupFrequencyByGroupId).length > 0;
|
|
61
|
+
const hasUnsavedNotificationPreferencesForDomain = (state, domain) => isDirty((0, exports.getNotificationLocalOverridesForDomain)(state, domain));
|
|
62
|
+
exports.hasUnsavedNotificationPreferencesForDomain = hasUnsavedNotificationPreferencesForDomain;
|
|
63
|
+
// Drives the shared page-level Save/Cancel footer.
|
|
64
|
+
const hasUnsavedNotificationPreferences = (state) => Object.values(getView(state).localOverridesByDomain).some((localOverrides) => localOverrides != null && isDirty(localOverrides));
|
|
65
|
+
exports.hasUnsavedNotificationPreferences = hasUnsavedNotificationPreferences;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FetchStateAndError } from '../../commonStateTypes/common';
|
|
2
|
-
import { NotificationChannel, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
|
|
2
|
+
import { NotificationChannel, NotificationDomain, NotificationFrequency } from '../../entity/notificationRegistry/notificationRegistryState';
|
|
3
3
|
export interface NotificationPreferences {
|
|
4
4
|
eventEnabledByChannel: Record<string, Partial<Record<NotificationChannel, boolean>>>;
|
|
5
5
|
groupChannelEnabledByGroupId: Record<string, Partial<Record<NotificationChannel, boolean>>>;
|
|
@@ -7,9 +7,10 @@ export interface NotificationPreferences {
|
|
|
7
7
|
}
|
|
8
8
|
export interface NotificationPreferencesViewState {
|
|
9
9
|
cancelEpoch: number;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
localOverridesByDomain: NotificationPreferencesByDomain;
|
|
11
|
+
preferencesByDomain: NotificationPreferencesByDomain;
|
|
12
12
|
saveEpoch: number;
|
|
13
13
|
savePreferencesState: FetchStateAndError;
|
|
14
14
|
}
|
|
15
|
+
export type NotificationPreferencesByDomain = Partial<Record<NotificationDomain, NotificationPreferences>>;
|
|
15
16
|
export declare const emptyNotificationPreferences: () => NotificationPreferences;
|
|
@@ -17,6 +17,18 @@ interface ProjectCOABalanceGroupedPayloadByProjectIdV2 {
|
|
|
17
17
|
export interface SectionByProjectCOABalanceGroupedPayloadV2 extends COABalanceGroupedPayloadV2 {
|
|
18
18
|
projects: ProjectCOABalanceGroupedPayloadByProjectIdV2;
|
|
19
19
|
}
|
|
20
|
+
interface CalculatedProjectCOABalanceGroupedPayloadByProjectIdV2 {
|
|
21
|
+
[project_Id: string]: COABalanceGroupedPayloadV2;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A section whose per-project entries carry a time series and nothing else: the
|
|
25
|
+
* backend fills the account-wise map only for the account sections, so the
|
|
26
|
+
* calculated sections (net income, the operating total, change in cash) are
|
|
27
|
+
* grouped by project with no accounts underneath.
|
|
28
|
+
*/
|
|
29
|
+
export interface CalculatedSectionByProjectCOABalanceGroupedPayloadV2 extends COABalanceGroupedPayloadV2 {
|
|
30
|
+
projects: CalculatedProjectCOABalanceGroupedPayloadByProjectIdV2;
|
|
31
|
+
}
|
|
20
32
|
export interface ProfitAndLossProjectViewPayload extends ReportByProjectMetadataPayloadV2 {
|
|
21
33
|
cogs: SectionByProjectCOABalanceGroupedPayloadV2;
|
|
22
34
|
earnings_before_interest_and_tax: COABalanceGroupedPayloadV2;
|
|
@@ -41,11 +41,21 @@ const fetchNotificationSettingsEpic = (actions$, state$, zeniAPI) => actions$.pi
|
|
|
41
41
|
// 67d6918e.
|
|
42
42
|
updateActions.push((0, notificationRegistryReducer_1.updateNotificationRegistry)({
|
|
43
43
|
registry: response.data.task_checklist_registry ?? undefined,
|
|
44
|
+
// An absent `<domain>_registry` means that domain's gate is off.
|
|
45
|
+
registriesByDomain: {
|
|
46
|
+
task_checklist: response.data.task_checklist_registry ?? undefined,
|
|
47
|
+
commenting: response.data.commenting_registry ?? undefined,
|
|
48
|
+
},
|
|
44
49
|
registryEpochAtDispatch,
|
|
45
50
|
}));
|
|
46
|
-
|
|
51
|
+
// One action so the domains cannot land on different store epochs.
|
|
52
|
+
const preferencesByDomain = {
|
|
53
|
+
task_checklist: response.data.task_checklist_preferences ?? undefined,
|
|
54
|
+
commenting: response.data.commenting_preferences ?? undefined,
|
|
55
|
+
};
|
|
56
|
+
if (Object.values(preferencesByDomain).some((preferences) => preferences != null)) {
|
|
47
57
|
updateActions.push((0, notificationPreferencesViewReducer_1.fetchNotificationPreferencesSuccess)({
|
|
48
|
-
|
|
58
|
+
preferencesByDomain,
|
|
49
59
|
saveEpochAtDispatch,
|
|
50
60
|
}));
|
|
51
61
|
}
|
|
@@ -37,6 +37,8 @@ export interface NotificationSettingsPayload {
|
|
|
37
37
|
pending_bill_approval_notification: RemindersPayload | null;
|
|
38
38
|
pending_reimbursement_approval_notification: RemindersPayload | null;
|
|
39
39
|
uncategorized_transaction_notification: RemindersPayload | null;
|
|
40
|
+
commenting_preferences?: NotificationPreferencesPayload;
|
|
41
|
+
commenting_registry?: NotificationRegistryPayload;
|
|
40
42
|
month_end_email_recipient_email_ids?: string[];
|
|
41
43
|
task_checklist_preferences?: NotificationPreferencesPayload;
|
|
42
44
|
task_checklist_registry?: NotificationRegistryPayload;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.34-beta0ND",
|
|
4
4
|
"description": "Shared module between Web & Mobile containing required abstractions for state management, async network communication. ",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "lib/esm/index.js",
|