@zeniai/client-epic-state 5.2.49-beta0ND → 5.2.49-betaAK0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/entity/notificationRegistry/notificationRegistryReducer.d.ts +1 -2
- package/lib/entity/notificationRegistry/notificationRegistryReducer.js +9 -20
- package/lib/entity/notificationRegistry/notificationRegistrySelector.d.ts +1 -2
- package/lib/entity/notificationRegistry/notificationRegistrySelector.js +1 -7
- package/lib/entity/notificationRegistry/notificationRegistryState.d.ts +0 -5
- package/lib/entity/notificationRegistry/notificationRegistryState.js +1 -7
- package/lib/entity/sectionProjectView/sectionProjectViewReducer.js +1 -3
- package/lib/epic.js +4 -4
- package/lib/esm/entity/notificationRegistry/notificationRegistryReducer.js +9 -20
- package/lib/esm/entity/notificationRegistry/notificationRegistrySelector.js +0 -5
- package/lib/esm/entity/notificationRegistry/notificationRegistryState.js +0 -5
- package/lib/esm/entity/sectionProjectView/sectionProjectViewReducer.js +1 -3
- package/lib/esm/epic.js +4 -4
- package/lib/esm/index.js +4 -4
- package/lib/esm/view/aiCfoView/aiCfoViewReducer.js +2 -4
- package/lib/esm/view/aiCfoView/epics/fetchRoutineRunsEpic.js +1 -3
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +19 -11
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +4 -1
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -2
- package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +18 -9
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +19 -11
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +3 -1
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +1 -1
- package/lib/esm/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +16 -7
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +19 -11
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +4 -2
- package/lib/esm/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +21 -13
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +3 -2
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +18 -9
- package/lib/esm/view/invoicing/invoicingFormDraft.js +56 -0
- package/lib/esm/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +7 -20
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewReducer.js +17 -43
- package/lib/esm/view/notificationPreferencesView/notificationPreferencesViewSelector.js +4 -24
- package/lib/esm/view/settingsView/epic/fetchNotificationSettingsEpic.js +2 -12
- package/lib/index.d.ts +4 -4
- package/lib/index.js +14 -21
- package/lib/view/aiCfoView/aiCfoViewReducer.js +2 -4
- package/lib/view/aiCfoView/aiCfoViewSelector.d.ts +1 -1
- package/lib/view/aiCfoView/epics/fetchRoutineRunsEpic.js +1 -3
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.d.ts +3 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewReducer.js +19 -11
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.d.ts +10 -0
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewSelector.js +4 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/editInvoicingCatalogItemDetailViewState.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js +3 -2
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.d.ts +10 -6
- package/lib/view/invoicing/editInvoicingCatalogItemDetailView/seedInvoicingCatalogItemFormDraftEpic.js +17 -8
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.d.ts +3 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js +19 -11
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.d.ts +13 -0
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewSelector.js +3 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewState.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/saveInvoicingCouponEpic.js +1 -1
- package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.d.ts +10 -6
- package/lib/view/invoicing/editInvoicingCouponDetailView/seedInvoicingCouponFormDraftEpic.js +15 -6
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.d.ts +3 -1
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewReducer.js +19 -11
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.d.ts +9 -0
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewSelector.js +4 -2
- package/lib/view/invoicing/editInvoicingCustomerDetailView/editInvoicingCustomerDetailViewState.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingCustomerDetailView/saveInvoicingCustomerEpic.js +1 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.d.ts +3 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +21 -13
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +9 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +3 -2
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState.d.ts +2 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +4 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +1 -1
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.d.ts +10 -6
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/seedInvoicingSubscriptionFormDraftEpic.js +17 -8
- package/lib/view/invoicing/invoicingFormDraft.d.ts +50 -0
- package/lib/view/invoicing/invoicingFormDraft.js +63 -0
- package/lib/view/notificationPreferencesView/epics/saveNotificationPreferencesEpic.js +6 -19
- package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.d.ts +5 -8
- package/lib/view/notificationPreferencesView/notificationPreferencesViewReducer.js +17 -43
- package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.d.ts +0 -4
- package/lib/view/notificationPreferencesView/notificationPreferencesViewSelector.js +5 -30
- package/lib/view/notificationPreferencesView/notificationPreferencesViewState.d.ts +3 -4
- package/lib/view/settingsView/epic/fetchNotificationSettingsEpic.js +2 -12
- package/lib/view/settingsView/settingsViewPayload.d.ts +0 -2
- package/package.json +1 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { NotificationRegistryPayload } from './notificationRegistryPayload';
|
|
2
|
-
import {
|
|
2
|
+
import { NotificationRegistryState } from './notificationRegistryState';
|
|
3
3
|
export declare const initialState: NotificationRegistryState;
|
|
4
4
|
export interface UpdateNotificationRegistryPayload {
|
|
5
5
|
registry: NotificationRegistryPayload | undefined;
|
|
6
6
|
registryEpochAtDispatch: number;
|
|
7
|
-
registriesByDomain?: Partial<Record<NotificationDomain, NotificationRegistryPayload | undefined>>;
|
|
8
7
|
}
|
|
9
8
|
export declare const updateNotificationRegistry: import("@reduxjs/toolkit").ActionCreatorWithPayload<UpdateNotificationRegistryPayload, "notificationRegistry/updateNotificationRegistry">;
|
|
10
9
|
declare const _default: import("redux").Reducer<NotificationRegistryState>;
|
|
@@ -4,7 +4,6 @@ exports.updateNotificationRegistry = exports.initialState = void 0;
|
|
|
4
4
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
5
5
|
const rootActions_1 = require("../../rootActions");
|
|
6
6
|
const notificationRegistryPayload_1 = require("./notificationRegistryPayload");
|
|
7
|
-
const notificationRegistryState_1 = require("./notificationRegistryState");
|
|
8
7
|
exports.initialState = { registryEpoch: 0 };
|
|
9
8
|
const notificationRegistry = (0, toolkit_1.createSlice)({
|
|
10
9
|
name: 'notificationRegistry',
|
|
@@ -23,24 +22,16 @@ const notificationRegistry = (0, toolkit_1.createSlice)({
|
|
|
23
22
|
// `task_checklist_registry` CLEARS so tenant B doesn't inherit A's
|
|
24
23
|
// Task Checklist section. Guarded by the epoch check above so a stale
|
|
25
24
|
// absent-response can't wipe a fresh registry.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const snapshots = registriesByDomain ?? { task_checklist: action.payload.registry };
|
|
30
|
-
draft.registryByDomain = {};
|
|
31
|
-
for (const domain of notificationRegistryState_1.NOTIFICATION_DOMAINS) {
|
|
32
|
-
const payload = snapshots[domain];
|
|
33
|
-
// Full-replace per domain, and an absent snapshot CLEARS that domain so
|
|
34
|
-
// tenant B never inherits A's section. The gates are independent
|
|
35
|
-
// server-side, so one domain clearing must not touch the other.
|
|
36
|
-
if (payload != null) {
|
|
37
|
-
draft.registryByDomain[domain] =
|
|
38
|
-
(0, notificationRegistryPayload_1.mapNotificationRegistryPayloadToRegistry)(payload);
|
|
39
|
-
}
|
|
25
|
+
if (action.payload.registry == null) {
|
|
26
|
+
draft.registry = undefined;
|
|
27
|
+
return;
|
|
40
28
|
}
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
// Full-replace contract: the registry payload is atomic — every fetch
|
|
30
|
+
// returns the complete group/event tree. If the backend ever ships a
|
|
31
|
+
// partial registry response, this write MUST switch to a merge (mirror
|
|
32
|
+
// the preferences slice's `mergePreferences`). Do not assume the
|
|
33
|
+
// current payload is partial without also updating this reducer.
|
|
34
|
+
draft.registry = (0, notificationRegistryPayload_1.mapNotificationRegistryPayloadToRegistry)(action.payload.registry);
|
|
44
35
|
},
|
|
45
36
|
},
|
|
46
37
|
extraReducers: (builder) => {
|
|
@@ -53,8 +44,6 @@ const notificationRegistry = (0, toolkit_1.createSlice)({
|
|
|
53
44
|
// — Cursor Bugbot 67d6918e. Returning `initialState` verbatim would
|
|
54
45
|
// rewind the counter and let a stale in-flight GET restore A's
|
|
55
46
|
// registry over B's.
|
|
56
|
-
// Drops `registry` and `registryByDomain` together: a tenant switch must
|
|
57
|
-
// not leave one domain's section behind.
|
|
58
47
|
return { registryEpoch: draft.registryEpoch + 1 };
|
|
59
48
|
});
|
|
60
49
|
},
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NotificationRegistry, NotificationRegistryState } from './notificationRegistryState';
|
|
2
2
|
export declare const getNotificationRegistry: (slice: NotificationRegistryState) => NotificationRegistry | undefined;
|
|
3
|
-
export declare const getNotificationRegistryForDomain: (slice: NotificationRegistryState, domain: NotificationDomain) => NotificationRegistry | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getNotificationRegistry = void 0;
|
|
4
4
|
// Current behavior: a null / undefined registry means the whole notification
|
|
5
5
|
// section is hidden by consumers. This conflates two distinct states —
|
|
6
6
|
// "user has no saved preferences yet" vs "the Task Checklist feature gate is
|
|
@@ -8,9 +8,3 @@ exports.getNotificationRegistryForDomain = exports.getNotificationRegistry = voi
|
|
|
8
8
|
// them (e.g. an explicit `gate_off` signal from the server) is a follow-up.
|
|
9
9
|
const getNotificationRegistry = (slice) => slice.registry;
|
|
10
10
|
exports.getNotificationRegistry = getNotificationRegistry;
|
|
11
|
-
// `undefined` conflates gate-off with no-preferences-yet; consumers hide the
|
|
12
|
-
// section either way. Falls back to `registry` for task_checklist so state built
|
|
13
|
-
// in the pre-domain shape (consumer mocks, older callers) still resolves.
|
|
14
|
-
const getNotificationRegistryForDomain = (slice, domain) => slice.registryByDomain?.[domain] ??
|
|
15
|
-
(domain === 'task_checklist' ? slice.registry : undefined);
|
|
16
|
-
exports.getNotificationRegistryForDomain = getNotificationRegistryForDomain;
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
export declare const NOTIFICATION_DOMAINS: readonly ["task_checklist", "commenting"];
|
|
2
|
-
export type NotificationDomain = (typeof NOTIFICATION_DOMAINS)[number];
|
|
3
|
-
export declare const DEFAULT_NOTIFICATION_DOMAIN: NotificationDomain;
|
|
4
|
-
export declare const toNotificationDomain: (raw: string) => NotificationDomain;
|
|
5
1
|
export declare const NOTIFICATION_CHANNELS: readonly ["email", "slack", "in_app"];
|
|
6
2
|
export type NotificationChannel = (typeof NOTIFICATION_CHANNELS)[number];
|
|
7
3
|
export declare const toNotificationChannel: (raw: string) => NotificationChannel;
|
|
@@ -34,5 +30,4 @@ export interface NotificationRegistry {
|
|
|
34
30
|
export interface NotificationRegistryState {
|
|
35
31
|
registryEpoch: number;
|
|
36
32
|
registry?: NotificationRegistry;
|
|
37
|
-
registryByDomain?: Partial<Record<NotificationDomain, NotificationRegistry>>;
|
|
38
33
|
}
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toNotificationFrequency = exports.NOTIFICATION_FREQUENCIES = exports.toNotificationChannel = exports.NOTIFICATION_CHANNELS =
|
|
3
|
+
exports.toNotificationFrequency = exports.NOTIFICATION_FREQUENCIES = exports.toNotificationChannel = exports.NOTIFICATION_CHANNELS = void 0;
|
|
4
4
|
const stringToUnion_1 = require("../../commonStateTypes/stringToUnion");
|
|
5
|
-
// Wire vocabulary owned by the notifications service, not by this slice.
|
|
6
|
-
exports.NOTIFICATION_DOMAINS = ['task_checklist', 'commenting'];
|
|
7
|
-
// Callers that predate the domain split mean Task Checklist.
|
|
8
|
-
exports.DEFAULT_NOTIFICATION_DOMAIN = 'task_checklist';
|
|
9
|
-
const toNotificationDomain = (raw) => (0, stringToUnion_1.stringToUnion)(raw, exports.NOTIFICATION_DOMAINS);
|
|
10
|
-
exports.toNotificationDomain = toNotificationDomain;
|
|
11
5
|
exports.NOTIFICATION_CHANNELS = ['email', 'slack', 'in_app'];
|
|
12
6
|
const toNotificationChannel = (raw) => (0, stringToUnion_1.stringToUnion)(raw, exports.NOTIFICATION_CHANNELS);
|
|
13
7
|
exports.toNotificationChannel = toNotificationChannel;
|
|
@@ -35,9 +35,7 @@ const sectionProjectView = (0, toolkit_1.createSlice)({
|
|
|
35
35
|
Object.keys(sectionPayload.projects).forEach((projectId) => {
|
|
36
36
|
const projectPayload = sectionPayload.projects[projectId];
|
|
37
37
|
// A calculated section is grouped by project with no accounts under it.
|
|
38
|
-
const projectAccounts = 'accounts' in projectPayload
|
|
39
|
-
? projectPayload.accounts
|
|
40
|
-
: undefined;
|
|
38
|
+
const projectAccounts = 'accounts' in projectPayload ? projectPayload.accounts : undefined;
|
|
41
39
|
const nestedAccountIds = projectAccounts != null
|
|
42
40
|
? (0, getNestedAccountIDHierarchyForSection_1.getNestedAccountIDHierarchyForAccounts)(Object.keys(projectAccounts), accountsMetadata)
|
|
43
41
|
: [];
|
package/lib/epic.js
CHANGED
|
@@ -53,21 +53,21 @@ const acceptMasterTOSEpic_1 = require("./view/aiCfoView/epics/acceptMasterTOSEpi
|
|
|
53
53
|
const createSessionAndSubmitEpic_1 = require("./view/aiCfoView/epics/createSessionAndSubmitEpic");
|
|
54
54
|
const createSessionEpic_1 = require("./view/aiCfoView/epics/createSessionEpic");
|
|
55
55
|
const deleteChatSessionEpic_1 = require("./view/aiCfoView/epics/deleteChatSessionEpic");
|
|
56
|
-
const deleteRoutineEpic_1 = require("./view/aiCfoView/epics/deleteRoutineEpic");
|
|
57
56
|
const deleteSkillEpic_1 = require("./view/aiCfoView/epics/deleteSkillEpic");
|
|
58
57
|
const fetchChatHistoryEpic_1 = require("./view/aiCfoView/epics/fetchChatHistoryEpic");
|
|
59
58
|
const fetchChatSessionsForUserEpic_1 = require("./view/aiCfoView/epics/fetchChatSessionsForUserEpic");
|
|
60
|
-
const fetchRoutineRunsEpic_1 = require("./view/aiCfoView/epics/fetchRoutineRunsEpic");
|
|
61
|
-
const fetchRoutinesEpic_1 = require("./view/aiCfoView/epics/fetchRoutinesEpic");
|
|
62
59
|
const fetchSkillDetailEpic_1 = require("./view/aiCfoView/epics/fetchSkillDetailEpic");
|
|
63
60
|
const fetchSkillVersionBodyEpic_1 = require("./view/aiCfoView/epics/fetchSkillVersionBodyEpic");
|
|
64
61
|
const fetchSkillVersionsEpic_1 = require("./view/aiCfoView/epics/fetchSkillVersionsEpic");
|
|
65
62
|
const fetchSkillsEpic_1 = require("./view/aiCfoView/epics/fetchSkillsEpic");
|
|
66
63
|
const fetchSuggestedQuestionsEpic_1 = require("./view/aiCfoView/epics/fetchSuggestedQuestionsEpic");
|
|
64
|
+
const deleteRoutineEpic_1 = require("./view/aiCfoView/epics/deleteRoutineEpic");
|
|
65
|
+
const fetchRoutineRunsEpic_1 = require("./view/aiCfoView/epics/fetchRoutineRunsEpic");
|
|
66
|
+
const fetchRoutinesEpic_1 = require("./view/aiCfoView/epics/fetchRoutinesEpic");
|
|
67
67
|
const runRoutineEpic_1 = require("./view/aiCfoView/epics/runRoutineEpic");
|
|
68
68
|
const saveRoutineEpic_1 = require("./view/aiCfoView/epics/saveRoutineEpic");
|
|
69
|
-
const saveSkillEpic_1 = require("./view/aiCfoView/epics/saveSkillEpic");
|
|
70
69
|
const setRoutineEnabledEpic_1 = require("./view/aiCfoView/epics/setRoutineEnabledEpic");
|
|
70
|
+
const saveSkillEpic_1 = require("./view/aiCfoView/epics/saveSkillEpic");
|
|
71
71
|
const setSkillVisibilityEpic_1 = require("./view/aiCfoView/epics/setSkillVisibilityEpic");
|
|
72
72
|
const stopSubmitEpic_1 = require("./view/aiCfoView/epics/stopSubmitEpic");
|
|
73
73
|
const stopSubmitQuestionEpic_1 = require("./view/aiCfoView/epics/stopSubmitQuestionEpic");
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { clearAll } from '../../rootActions';
|
|
3
3
|
import { mapNotificationRegistryPayloadToRegistry, } from './notificationRegistryPayload';
|
|
4
|
-
import { NOTIFICATION_DOMAINS, } from './notificationRegistryState';
|
|
5
4
|
export const initialState = { registryEpoch: 0 };
|
|
6
5
|
const notificationRegistry = createSlice({
|
|
7
6
|
name: 'notificationRegistry',
|
|
@@ -20,24 +19,16 @@ const notificationRegistry = createSlice({
|
|
|
20
19
|
// `task_checklist_registry` CLEARS so tenant B doesn't inherit A's
|
|
21
20
|
// Task Checklist section. Guarded by the epoch check above so a stale
|
|
22
21
|
// absent-response can't wipe a fresh registry.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const snapshots = registriesByDomain ?? { task_checklist: action.payload.registry };
|
|
27
|
-
draft.registryByDomain = {};
|
|
28
|
-
for (const domain of NOTIFICATION_DOMAINS) {
|
|
29
|
-
const payload = snapshots[domain];
|
|
30
|
-
// Full-replace per domain, and an absent snapshot CLEARS that domain so
|
|
31
|
-
// tenant B never inherits A's section. The gates are independent
|
|
32
|
-
// server-side, so one domain clearing must not touch the other.
|
|
33
|
-
if (payload != null) {
|
|
34
|
-
draft.registryByDomain[domain] =
|
|
35
|
-
mapNotificationRegistryPayloadToRegistry(payload);
|
|
36
|
-
}
|
|
22
|
+
if (action.payload.registry == null) {
|
|
23
|
+
draft.registry = undefined;
|
|
24
|
+
return;
|
|
37
25
|
}
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
// Full-replace contract: the registry payload is atomic — every fetch
|
|
27
|
+
// returns the complete group/event tree. If the backend ever ships a
|
|
28
|
+
// partial registry response, this write MUST switch to a merge (mirror
|
|
29
|
+
// the preferences slice's `mergePreferences`). Do not assume the
|
|
30
|
+
// current payload is partial without also updating this reducer.
|
|
31
|
+
draft.registry = mapNotificationRegistryPayloadToRegistry(action.payload.registry);
|
|
41
32
|
},
|
|
42
33
|
},
|
|
43
34
|
extraReducers: (builder) => {
|
|
@@ -50,8 +41,6 @@ const notificationRegistry = createSlice({
|
|
|
50
41
|
// — Cursor Bugbot 67d6918e. Returning `initialState` verbatim would
|
|
51
42
|
// rewind the counter and let a stale in-flight GET restore A's
|
|
52
43
|
// registry over B's.
|
|
53
|
-
// Drops `registry` and `registryByDomain` together: a tenant switch must
|
|
54
|
-
// not leave one domain's section behind.
|
|
55
44
|
return { registryEpoch: draft.registryEpoch + 1 };
|
|
56
45
|
});
|
|
57
46
|
},
|
|
@@ -4,8 +4,3 @@
|
|
|
4
4
|
// off for this tenant". Both currently surface as `undefined`. Distinguishing
|
|
5
5
|
// them (e.g. an explicit `gate_off` signal from the server) is a follow-up.
|
|
6
6
|
export const getNotificationRegistry = (slice) => slice.registry;
|
|
7
|
-
// `undefined` conflates gate-off with no-preferences-yet; consumers hide the
|
|
8
|
-
// section either way. Falls back to `registry` for task_checklist so state built
|
|
9
|
-
// in the pre-domain shape (consumer mocks, older callers) still resolves.
|
|
10
|
-
export const getNotificationRegistryForDomain = (slice, domain) => slice.registryByDomain?.[domain] ??
|
|
11
|
-
(domain === 'task_checklist' ? slice.registry : undefined);
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { stringToUnion } from '../../commonStateTypes/stringToUnion';
|
|
2
|
-
// Wire vocabulary owned by the notifications service, not by this slice.
|
|
3
|
-
export const NOTIFICATION_DOMAINS = ['task_checklist', 'commenting'];
|
|
4
|
-
// Callers that predate the domain split mean Task Checklist.
|
|
5
|
-
export const DEFAULT_NOTIFICATION_DOMAIN = 'task_checklist';
|
|
6
|
-
export const toNotificationDomain = (raw) => stringToUnion(raw, NOTIFICATION_DOMAINS);
|
|
7
2
|
export const NOTIFICATION_CHANNELS = ['email', 'slack', 'in_app'];
|
|
8
3
|
export const toNotificationChannel = (raw) => stringToUnion(raw, NOTIFICATION_CHANNELS);
|
|
9
4
|
export const NOTIFICATION_FREQUENCIES = ['instant', 'daily'];
|
|
@@ -32,9 +32,7 @@ const sectionProjectView = createSlice({
|
|
|
32
32
|
Object.keys(sectionPayload.projects).forEach((projectId) => {
|
|
33
33
|
const projectPayload = sectionPayload.projects[projectId];
|
|
34
34
|
// A calculated section is grouped by project with no accounts under it.
|
|
35
|
-
const projectAccounts = 'accounts' in projectPayload
|
|
36
|
-
? projectPayload.accounts
|
|
37
|
-
: undefined;
|
|
35
|
+
const projectAccounts = 'accounts' in projectPayload ? projectPayload.accounts : undefined;
|
|
38
36
|
const nestedAccountIds = projectAccounts != null
|
|
39
37
|
? getNestedAccountIDHierarchyForAccounts(Object.keys(projectAccounts), accountsMetadata)
|
|
40
38
|
: [];
|
package/lib/esm/epic.js
CHANGED
|
@@ -50,21 +50,21 @@ import { acceptMasterTOSEpic, } from './view/aiCfoView/epics/acceptMasterTOSEpic
|
|
|
50
50
|
import { createSessionAndSubmitEpic, } from './view/aiCfoView/epics/createSessionAndSubmitEpic';
|
|
51
51
|
import { createSessionEpic, } from './view/aiCfoView/epics/createSessionEpic';
|
|
52
52
|
import { deleteChatSessionEpic, } from './view/aiCfoView/epics/deleteChatSessionEpic';
|
|
53
|
-
import { deleteRoutineEpic } from './view/aiCfoView/epics/deleteRoutineEpic';
|
|
54
53
|
import { deleteSkillEpic } from './view/aiCfoView/epics/deleteSkillEpic';
|
|
55
54
|
import { fetchChatHistoryEpic, } from './view/aiCfoView/epics/fetchChatHistoryEpic';
|
|
56
55
|
import { fetchChatSessionsForUserEpic, } from './view/aiCfoView/epics/fetchChatSessionsForUserEpic';
|
|
57
|
-
import { fetchRoutineRunsEpic } from './view/aiCfoView/epics/fetchRoutineRunsEpic';
|
|
58
|
-
import { fetchRoutinesEpic } from './view/aiCfoView/epics/fetchRoutinesEpic';
|
|
59
56
|
import { fetchSkillDetailEpic } from './view/aiCfoView/epics/fetchSkillDetailEpic';
|
|
60
57
|
import { fetchSkillVersionBodyEpic } from './view/aiCfoView/epics/fetchSkillVersionBodyEpic';
|
|
61
58
|
import { fetchSkillVersionsEpic } from './view/aiCfoView/epics/fetchSkillVersionsEpic';
|
|
62
59
|
import { fetchSkillsEpic, } from './view/aiCfoView/epics/fetchSkillsEpic';
|
|
63
60
|
import { fetchSuggestedQuestionsEpic, } from './view/aiCfoView/epics/fetchSuggestedQuestionsEpic';
|
|
61
|
+
import { deleteRoutineEpic } from './view/aiCfoView/epics/deleteRoutineEpic';
|
|
62
|
+
import { fetchRoutineRunsEpic } from './view/aiCfoView/epics/fetchRoutineRunsEpic';
|
|
63
|
+
import { fetchRoutinesEpic } from './view/aiCfoView/epics/fetchRoutinesEpic';
|
|
64
64
|
import { runRoutineEpic } from './view/aiCfoView/epics/runRoutineEpic';
|
|
65
65
|
import { saveRoutineEpic } from './view/aiCfoView/epics/saveRoutineEpic';
|
|
66
|
-
import { saveSkillEpic } from './view/aiCfoView/epics/saveSkillEpic';
|
|
67
66
|
import { setRoutineEnabledEpic } from './view/aiCfoView/epics/setRoutineEnabledEpic';
|
|
67
|
+
import { saveSkillEpic } from './view/aiCfoView/epics/saveSkillEpic';
|
|
68
68
|
import { setSkillVisibilityEpic } from './view/aiCfoView/epics/setSkillVisibilityEpic';
|
|
69
69
|
import { stopSubmitEpic, } from './view/aiCfoView/epics/stopSubmitEpic';
|
|
70
70
|
import { stopSubmitQuestionEpic, } from './view/aiCfoView/epics/stopSubmitQuestionEpic';
|
package/lib/esm/index.js
CHANGED
|
@@ -64,8 +64,8 @@ import { ALL_JE_SCHEDULE_IGNORE_REASONS, ALL_JE_SCHEDULE_REVERT_REASONS, ALL_ONE
|
|
|
64
64
|
import { ALL_MONTH_END_CLOSE_CHECKS_FREQUENCY, } from './entity/monthEndCloseChecks/monthEndCloseChecksState';
|
|
65
65
|
import { updateCommentsNotifications, updateCommentsNotificationsStatuses, } from './entity/notification/notificationReducer';
|
|
66
66
|
import { toNotificationModeStrict, } from './entity/notification/types/notificationTypes';
|
|
67
|
-
import { getNotificationRegistry
|
|
68
|
-
import {
|
|
67
|
+
import { getNotificationRegistry } from './entity/notificationRegistry/notificationRegistrySelector';
|
|
68
|
+
import { toNotificationChannel, toNotificationFrequency, } from './entity/notificationRegistry/notificationRegistryState';
|
|
69
69
|
import { getPlaidPaymentAccounts } from './entity/paymentAccount/paymentAccountSelector';
|
|
70
70
|
import { VERIFIED_PAYMENT_ACCOUNT_PROVIDER_VERIFICATION_STATUS, } from './entity/paymentAccount/paymentAccountState';
|
|
71
71
|
import { closeSnackbar, openSnackbar } from './entity/snackbar/snackbarReducer';
|
|
@@ -236,7 +236,7 @@ import { toAverageMonthsCount } from './view/netBurnOrIncomeStoryCard/epic/updat
|
|
|
236
236
|
import { fetchNetBurnOrIncomeStoryCard, updateNetBurnOrIncomeStoryCardSettings, } from './view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardReducer';
|
|
237
237
|
import { getNetBurnOrIncomeStoryCardReport } from './view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardSelector';
|
|
238
238
|
import { clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, } from './view/notificationPreferencesView/notificationPreferencesViewReducer';
|
|
239
|
-
import { getEffectiveNotificationPreferences,
|
|
239
|
+
import { getEffectiveNotificationPreferences, getNotificationLocalOverrides, getNotificationPreferencesSaveState, hasUnsavedNotificationPreferences, } from './view/notificationPreferencesView/notificationPreferencesViewSelector';
|
|
240
240
|
import { fetchNotificationUnreadCount, fetchNotificationUnreadCountSuccess, fetchNotificationView, updateNotificationViewAllNotificationsStatus, updateNotificationViewCurrentTabAndSubTab, updateNotificationViewNotificationStatus, updateNotificationViewSubTab, updateNotificationViewTabState, updateNotificationViewUIState, } from './view/notificationView/notificationViewReducer';
|
|
241
241
|
import { getExternalNotificationsForSelectedSubTab, getNotificationView, getNotificationsForSelectedSubTab, } from './view/notificationView/notificationViewSelector';
|
|
242
242
|
import { toNotificationSubTabTypeStrict, toNotificationTabTypeStrict, } from './view/notificationView/notificationViewState';
|
|
@@ -680,7 +680,7 @@ export { toNotificationModeStrict, updateCommentsNotifications, updateCommentsNo
|
|
|
680
680
|
export { toNotificationSubTabTypeStrict, toNotificationTabTypeStrict, fetchNotificationView, fetchNotificationUnreadCount, fetchNotificationUnreadCountSuccess, updateNotificationViewAllNotificationsStatus, updateNotificationViewNotificationStatus, updateNotificationViewTabState, updateNotificationViewCurrentTabAndSubTab, updateNotificationViewSubTab, updateNotificationViewUIState, getNotificationView, getExternalNotificationsForSelectedSubTab, getNotificationsForSelectedSubTab, };
|
|
681
681
|
export { fetchRegisteredInterests, notifyMeForFeature, getFeatureNotificationView, getRegisteredInterests, getRegisteredInterestsByFeature, isFeatureInterestRegistered, };
|
|
682
682
|
export { pushToastNotification, getLastNotificationTime, getNotifications, };
|
|
683
|
-
export { getEffectiveNotificationPreferences,
|
|
683
|
+
export { getEffectiveNotificationPreferences, getNotificationLocalOverrides, getNotificationPreferencesSaveState, getNotificationRegistry, hasUnsavedNotificationPreferences, clearAllNotificationPreferencesView, clearNotificationPreferencesLocalOverrides, saveNotificationPreferences, setGroupChannelMaster, setGroupFrequency, toggleEventChannel, toNotificationChannel, toNotificationFrequency, };
|
|
684
684
|
export { getReferralListView, getInviteFormView, toReferralListViewSortKeyType, StatusTypes, AmountStatusTypes, DEFAULT_REFERRER_AMOUNT, fetchReferrals, sendReferralInvite, clearReferrals, saveReferralFormDataInLocalStore, updateReferralListSortUiState, resendReferralInvite, fetchRewardsPlan, getRewardsPlanCard, updateReferViewed, };
|
|
685
685
|
export { ALL_WEEK_DAYS, SEMI_WEEKLY_REQUIRED_DAYS_COUNT, getMinAllowedEndDate, getRecurringEndDateFromCount, toDayOfWeek, toRecurringFrequency, };
|
|
686
686
|
export { fetchCockpitContext, fetchCompanyTaskManagerView, fetchTaskManagerMetrics, getCompanyTaskManagerView, createTaskFromTaskGroupTemplate, };
|
|
@@ -835,8 +835,7 @@ const aiCfoView = createSlice({
|
|
|
835
835
|
// The server has it. Nothing it sends from here on will contain this row,
|
|
836
836
|
// so the tombstone has done its job and holding it any longer is what
|
|
837
837
|
// makes a routine recreated under the same id invisible forever.
|
|
838
|
-
draft.routines.deletedScheduleIds =
|
|
839
|
-
draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
|
|
838
|
+
draft.routines.deletedScheduleIds = draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
|
|
840
839
|
draft.routineMutation = {
|
|
841
840
|
fetchState: 'Completed',
|
|
842
841
|
error: undefined,
|
|
@@ -848,8 +847,7 @@ const aiCfoView = createSlice({
|
|
|
848
847
|
// is what lets the refetch put the row back — leaving it hidden would
|
|
849
848
|
// tell the user they had stopped something they had not, which is the
|
|
850
849
|
// worst outcome this list can produce.
|
|
851
|
-
draft.routines.deletedScheduleIds =
|
|
852
|
-
draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
|
|
850
|
+
draft.routines.deletedScheduleIds = draft.routines.deletedScheduleIds.filter((id) => id !== action.payload.scheduleId);
|
|
853
851
|
// Its own slice: the refetch that puts the row back would otherwise wipe
|
|
854
852
|
// this error, leaving the row silently reappearing with no explanation.
|
|
855
853
|
draft.routineMutation = {
|
|
@@ -27,9 +27,7 @@ export const fetchRoutineRunsEpic = (actions$, _state$, zeniAPI) => actions$.pip
|
|
|
27
27
|
// Filtered, not trusted: one malformed row must not take the
|
|
28
28
|
// whole history down, and a run without an id is not addressable
|
|
29
29
|
// by anything the UI does with it.
|
|
30
|
-
runs: (data.runs ?? [])
|
|
31
|
-
.filter(isRoutineRunPayload)
|
|
32
|
-
.map(toRoutineRun),
|
|
30
|
+
runs: (data.runs ?? []).filter(isRoutineRunPayload).map(toRoutineRun),
|
|
33
31
|
scheduleId,
|
|
34
32
|
sessionId: data.session_id ?? null,
|
|
35
33
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { clearAll } from '../../../rootActions';
|
|
3
|
+
import { applyInvoicingFormDraftEdit, applyInvoicingFormDraftFailure, applyInvoicingFormDraftSeed, } from '../invoicingFormDraft';
|
|
3
4
|
import { initialInvoicingCatalogItemFormLocalData } from './invoicingCatalogItemFormHelper';
|
|
4
5
|
export const initialState = {
|
|
5
6
|
fetchState: 'Not-Started',
|
|
@@ -21,7 +22,7 @@ const editInvoicingCatalogItemDetailView = createSlice({
|
|
|
21
22
|
updateInvoicingCatalogItemFormDraft(draft, action) {
|
|
22
23
|
const { id, localData } = action.payload;
|
|
23
24
|
if (id != null) {
|
|
24
|
-
draft.editDraftById
|
|
25
|
+
applyInvoicingFormDraftEdit(draft.editDraftById, id, localData);
|
|
25
26
|
}
|
|
26
27
|
else {
|
|
27
28
|
draft.newItemDraft = localData;
|
|
@@ -64,6 +65,7 @@ const editInvoicingCatalogItemDetailView = createSlice({
|
|
|
64
65
|
fetchState: 'Error',
|
|
65
66
|
error: action.payload.error,
|
|
66
67
|
};
|
|
68
|
+
applyInvoicingFormDraftFailure(draft.editDraftById, action.payload.id, initialInvoicingCatalogItemFormLocalData);
|
|
67
69
|
},
|
|
68
70
|
fetchInvoicingCatalogItemDetailPage: {
|
|
69
71
|
reducer() {
|
|
@@ -79,14 +81,10 @@ const editInvoicingCatalogItemDetailView = createSlice({
|
|
|
79
81
|
};
|
|
80
82
|
},
|
|
81
83
|
},
|
|
82
|
-
/**
|
|
83
|
-
* Seed the draft from the loaded record, once. Skipped when a draft already
|
|
84
|
-
* exists so a late-arriving refetch cannot overwrite in-progress edits.
|
|
85
|
-
*/
|
|
84
|
+
/** Fill the draft from the loaded record — see `applyInvoicingFormDraftSeed`. */
|
|
86
85
|
seedInvoicingCatalogItemFormDraft(draft, action) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
86
|
+
const { id, localData } = action.payload;
|
|
87
|
+
applyInvoicingFormDraftSeed(draft.editDraftById, id, localData);
|
|
90
88
|
},
|
|
91
89
|
runInvoicingCatalogPlanAction(draft, action) {
|
|
92
90
|
draft.actionStateById[action.payload.id] = {
|
|
@@ -125,13 +123,23 @@ const editInvoicingCatalogItemDetailView = createSlice({
|
|
|
125
123
|
error: action.payload.error,
|
|
126
124
|
};
|
|
127
125
|
},
|
|
128
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Discard a form's draft — the catalog item's own when `id` is given,
|
|
128
|
+
* the create draft otherwise. Scoped to the one form on purpose: another
|
|
129
|
+
* catalog item's half-finished edit is not this form's to throw away.
|
|
130
|
+
* Clearing every draft at once is what `clearAll` is for.
|
|
131
|
+
*/
|
|
132
|
+
resetEditInvoicingCatalogItemDetailView(draft, action) {
|
|
129
133
|
draft.fetchState = 'Not-Started';
|
|
130
134
|
draft.error = undefined;
|
|
131
135
|
draft.savedItemId = undefined;
|
|
132
136
|
draft.savedKind = undefined;
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
const id = action.payload?.id;
|
|
138
|
+
if (id == null) {
|
|
139
|
+
draft.newItemDraft = { ...initialInvoicingCatalogItemFormLocalData };
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
delete draft.editDraftById[id];
|
|
135
143
|
},
|
|
136
144
|
},
|
|
137
145
|
extraReducers: (builder) => {
|
|
@@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
|
|
|
2
2
|
import { reduceAllFetchState } from '../../../commonStateTypes/reduceFetchState';
|
|
3
3
|
import { getInvoicingPlanById, getInvoicingPlansByIds, } from '../../../entity/invoicing/plan/planSelector';
|
|
4
4
|
import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
5
|
+
import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
|
|
5
6
|
import { getInvoicingQboView, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
|
|
6
7
|
import { INVOICING_CATALOG_APPLICABILITY_VALUES, INVOICING_CATALOG_PRICING_MODEL_VALUES, INVOICING_CATALOG_PRORATION_VALUES, INVOICING_CATALOG_TIER_TYPE_VALUES, initialInvoicingCatalogItemFormLocalData, } from './invoicingCatalogItemFormHelper';
|
|
7
8
|
export const getEditInvoicingCatalogItemDetailViewState = (state) => ({
|
|
@@ -52,7 +53,7 @@ export const getInvoicingCatalogItemFormView = (state, id) => {
|
|
|
52
53
|
// lands. Until then the page is showing its skeleton, so the blank shape here
|
|
53
54
|
// is never rendered.
|
|
54
55
|
const localData = id != null
|
|
55
|
-
? (editDraftById
|
|
56
|
+
? getInvoicingFormDraftLocalData(editDraftById, id, initialInvoicingCatalogItemFormLocalData)
|
|
56
57
|
: newItemDraft;
|
|
57
58
|
const allApplicableItemOptions = getInvoicingApplicableCatalogItemOptions(state);
|
|
58
59
|
const applicableItemOptions = id != null
|
|
@@ -90,6 +91,8 @@ export const getInvoicingCatalogItemFormView = (state, id) => {
|
|
|
90
91
|
catalogProductLifecycleActionOptions,
|
|
91
92
|
recordState,
|
|
92
93
|
fieldHelp: invoicingFieldHelpForGroup(state, 'catalog'),
|
|
94
|
+
isFormLoading: isInvoicingAccountingPickerDataLoading(state) ||
|
|
95
|
+
isInvoicingFormDraftPending(editDraftById, id),
|
|
93
96
|
isQboPickerDataLoading: isInvoicingAccountingPickerDataLoading(state),
|
|
94
97
|
qboAccountHierarchy: qboView?.nestedAccountHierarchy ?? [],
|
|
95
98
|
qboClassHierarchy: qboView?.nestedClassHierarchy ?? [],
|
package/lib/esm/view/invoicing/editInvoicingCatalogItemDetailView/saveInvoicingCatalogItemEpic.js
CHANGED
|
@@ -17,8 +17,9 @@ export const saveInvoicingCatalogItemEpic = (actions$, state$, zeniAPI) => actio
|
|
|
17
17
|
// class become the records the service stores. Price points ride along
|
|
18
18
|
// nested, so the detail page and the create/edit form share one request.
|
|
19
19
|
const viewState = state.editInvoicingCatalogItemDetailViewState;
|
|
20
|
-
const localData = (id != null
|
|
21
|
-
viewState.
|
|
20
|
+
const localData = (id != null
|
|
21
|
+
? viewState.editDraftById[id]?.localData
|
|
22
|
+
: viewState.newItemDraft) ?? viewState.newItemDraft;
|
|
22
23
|
const data = localDataToSaveInvoicingCatalogItemPayload(localData, id == null, accountingLookup);
|
|
23
24
|
const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
|
|
24
25
|
const path = id != null
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import { EMPTY, of } from 'rxjs';
|
|
2
2
|
import { filter, mergeMap, withLatestFrom } from 'rxjs/operators';
|
|
3
3
|
import { getInvoicingPlanById } from '../../../entity/invoicing/plan/planSelector';
|
|
4
|
-
import { seedInvoicingCatalogItemFormDraft, updateInvoicingCatalogItemDetail, } from './editInvoicingCatalogItemDetailViewReducer';
|
|
4
|
+
import { fetchInvoicingCatalogItemDetailPage, seedInvoicingCatalogItemFormDraft, updateInvoicingCatalogItemDetail, } from './editInvoicingCatalogItemDetailViewReducer';
|
|
5
5
|
import { entityToInvoicingCatalogItemFormLocalData } from './invoicingCatalogItemFormHelper';
|
|
6
6
|
/**
|
|
7
|
-
* Seeds the edit draft from the record the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Seeds the edit draft from the record, so the form has its values before it
|
|
8
|
+
* mounts. The reducer ignores a seed once the draft holds the record, which is
|
|
9
|
+
* what stops a background refetch from overwriting edits in progress.
|
|
10
|
+
*
|
|
11
|
+
* Opening the page counts as a trigger, not just the detail response: the page
|
|
12
|
+
* fetch is cache-guarded, so re-opening a form for a record already loaded this
|
|
13
|
+
* session issues no request at all — and a draft cleared in between (by a save
|
|
14
|
+
* or a cancel) would have nothing left to seed it.
|
|
11
15
|
*/
|
|
12
|
-
export const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actions$.pipe(filter(updateInvoicingCatalogItemDetail.match
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
+
export const seedInvoicingCatalogItemFormDraftEpic = (actions$, state$) => actions$.pipe(filter((action) => updateInvoicingCatalogItemDetail.match(action) ||
|
|
17
|
+
fetchInvoicingCatalogItemDetailPage.match(action)), withLatestFrom(state$), mergeMap(([action, state]) => {
|
|
18
|
+
const id = action.payload.id;
|
|
19
|
+
// Nothing to seed from on a cold open; the detail response triggers this
|
|
20
|
+
// again once it lands.
|
|
21
|
+
const plan = id != null
|
|
22
|
+
? getInvoicingPlanById(state.invoicingPlanState, id)
|
|
23
|
+
: undefined;
|
|
24
|
+
if (id == null || plan == null) {
|
|
16
25
|
return EMPTY;
|
|
17
26
|
}
|
|
18
27
|
return of(seedInvoicingCatalogItemFormDraft({
|
package/lib/esm/view/invoicing/editInvoicingCouponDetailView/editInvoicingCouponDetailViewReducer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
2
|
import { clearAll } from '../../../rootActions';
|
|
3
|
+
import { applyInvoicingFormDraftEdit, applyInvoicingFormDraftFailure, applyInvoicingFormDraftSeed, } from '../invoicingFormDraft';
|
|
3
4
|
import { initialInvoicingCouponFormLocalData, } from './editInvoicingCouponDetailViewState';
|
|
4
5
|
export const initialState = {
|
|
5
6
|
fetchState: 'Not-Started',
|
|
@@ -20,7 +21,7 @@ const editInvoicingCouponDetailView = createSlice({
|
|
|
20
21
|
updateInvoicingCouponFormDraft(draft, action) {
|
|
21
22
|
const { id, localData } = action.payload;
|
|
22
23
|
if (id != null) {
|
|
23
|
-
draft.editDraftById
|
|
24
|
+
applyInvoicingFormDraftEdit(draft.editDraftById, id, localData);
|
|
24
25
|
}
|
|
25
26
|
else {
|
|
26
27
|
draft.newCouponDraft = localData;
|
|
@@ -85,15 +86,12 @@ const editInvoicingCouponDetailView = createSlice({
|
|
|
85
86
|
fetchState: 'Error',
|
|
86
87
|
error: action.payload.status,
|
|
87
88
|
};
|
|
89
|
+
applyInvoicingFormDraftFailure(draft.editDraftById, action.payload.id, initialInvoicingCouponFormLocalData);
|
|
88
90
|
},
|
|
89
|
-
/**
|
|
90
|
-
* Seed the draft from the loaded record, once. Skipped when a draft already
|
|
91
|
-
* exists so a late refetch cannot overwrite in-progress edits.
|
|
92
|
-
*/
|
|
91
|
+
/** Fill the draft from the loaded record — see `applyInvoicingFormDraftSeed`. */
|
|
93
92
|
seedInvoicingCouponFormDraft(draft, action) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
93
|
+
const { id, localData } = action.payload;
|
|
94
|
+
applyInvoicingFormDraftSeed(draft.editDraftById, id, localData);
|
|
97
95
|
},
|
|
98
96
|
runInvoicingDiscountAction(draft, action) {
|
|
99
97
|
draft.actionStateById[action.payload.id] = {
|
|
@@ -113,12 +111,22 @@ const editInvoicingCouponDetailView = createSlice({
|
|
|
113
111
|
error: action.payload.error,
|
|
114
112
|
};
|
|
115
113
|
},
|
|
116
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Discard a form's draft — the coupon's own when `id` is given,
|
|
116
|
+
* the create draft otherwise. Scoped to the one form on purpose: another
|
|
117
|
+
* coupon's half-finished edit is not this form's to throw away.
|
|
118
|
+
* Clearing every draft at once is what `clearAll` is for.
|
|
119
|
+
*/
|
|
120
|
+
resetEditInvoicingCouponDetailView(draft, action) {
|
|
117
121
|
draft.fetchState = 'Not-Started';
|
|
118
122
|
draft.error = undefined;
|
|
119
123
|
draft.savedCouponId = undefined;
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
const id = action.payload?.id;
|
|
125
|
+
if (id == null) {
|
|
126
|
+
draft.newCouponDraft = { ...initialInvoicingCouponFormLocalData };
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
delete draft.editDraftById[id];
|
|
122
130
|
},
|
|
123
131
|
},
|
|
124
132
|
extraReducers: (builder) => {
|
|
@@ -2,6 +2,7 @@ import { reduceAllFetchState } from '../../../commonStateTypes/reduceFetchState'
|
|
|
2
2
|
import { getInvoicingCouponById } from '../../../entity/invoicing/coupon/couponSelector';
|
|
3
3
|
import { toInvoicingFormOption, toInvoicingQboAccountMappingSlot, } from '../../../entity/invoicing/invoicingCommonPayload';
|
|
4
4
|
import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
5
|
+
import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
|
|
5
6
|
import { getInvoicingQboView, invoicingAccountsForQboAccountTypes, invoicingQboAccountByQboId, isInvoicingAccountingPickerDataLoading, isInvoicingQboConnected, } from '../invoicingQboView/invoicingQboViewSelector';
|
|
6
7
|
import { getInvoicingSettings, getInvoicingSettingsFetchState, } from '../settingsView/settingsViewSelector';
|
|
7
8
|
import { initialInvoicingCouponFormLocalData, invoicingCouponDiscountsAccountIdForCreate, } from './editInvoicingCouponDetailViewState';
|
|
@@ -41,6 +42,7 @@ export const getEditInvoicingCouponDetailViewState = (state, couponId) => {
|
|
|
41
42
|
{ error: undefined, fetchState: configFetchState },
|
|
42
43
|
]),
|
|
43
44
|
recordState,
|
|
45
|
+
isFormLoading: isInvoicingFormDraftPending(state.editInvoicingCouponDetailViewState.editDraftById, couponId),
|
|
44
46
|
coupon,
|
|
45
47
|
discountsAccountName: invoicingQboAccountByQboId(state, coupon?.discountsAccountId)?.accountName,
|
|
46
48
|
savedCouponId,
|
|
@@ -70,7 +72,7 @@ export const getInvoicingCouponFormView = (state, couponId) => {
|
|
|
70
72
|
// `seedInvoicingCouponFormDraftEpic` when the detail load lands. Until then
|
|
71
73
|
// the page is showing its skeleton.
|
|
72
74
|
const localData = couponId != null
|
|
73
|
-
? (editDraftById
|
|
75
|
+
? getInvoicingFormDraftLocalData(editDraftById, couponId, initialInvoicingCouponFormLocalData)
|
|
74
76
|
: newCouponDraft;
|
|
75
77
|
const { billingPeriodUnitOptions, discountApplyOnOptions, discountDurationTypeOptions, discountTypeOptions, qboAccountMappingKindOptions, } = getInvoicingConfigView(state);
|
|
76
78
|
const isQboConnected = isInvoicingQboConnected(state);
|
|
@@ -20,7 +20,7 @@ export const saveInvoicingCouponEpic = (actions$, state$, zeniAPI) => actions$.p
|
|
|
20
20
|
const state = state$.value;
|
|
21
21
|
const viewState = state.editInvoicingCouponDetailViewState;
|
|
22
22
|
const draft = (id != null
|
|
23
|
-
? viewState.editDraftById[id]
|
|
23
|
+
? viewState.editDraftById[id]?.localData
|
|
24
24
|
: viewState.newCouponDraft) ?? viewState.newCouponDraft;
|
|
25
25
|
// The create default is resolved here as well as in the form view,
|
|
26
26
|
// so a create that never touched the picker still persists the
|