@zeniai/client-epic-state 5.3.22 → 5.3.24-betaAK1
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/esm/view/dashboard/dashboardSelector.js +12 -9
- package/lib/esm/view/invoicing/createInvoice/createInvoiceSelector.js +2 -1
- package/lib/esm/view/invoicing/createInvoice/epics/fetchCreateInvoiceFormPageEpic.js +10 -3
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +2 -1
- package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +8 -1
- package/lib/esm/view/invoicing/invoicingCustomerListView/epics/fetchInvoicingCustomerListEpic.js +15 -3
- package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.js +1 -0
- package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.js +2 -0
- package/lib/esm/view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardSelector.js +1 -4
- package/lib/esm/view/opEx/opExSelector.js +4 -4
- package/lib/view/dashboard/dashboardSelector.js +12 -9
- package/lib/view/invoicing/createInvoice/createInvoiceSelector.d.ts +2 -0
- package/lib/view/invoicing/createInvoice/createInvoiceSelector.js +1 -0
- package/lib/view/invoicing/createInvoice/epics/fetchCreateInvoiceFormPageEpic.js +10 -3
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +2 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +1 -0
- package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionFormPageEpic.js +8 -1
- package/lib/view/invoicing/invoicingCustomerListView/epics/fetchInvoicingCustomerListEpic.js +15 -3
- package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.d.ts +3 -0
- package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.js +1 -0
- package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.d.ts +2 -0
- package/lib/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.js +4 -1
- package/lib/view/netBurnOrIncomeStoryCard/netBurnOrIncomeStoryCardSelector.js +1 -4
- package/lib/view/opEx/opExSelector.js +4 -4
- package/package.json +1 -1
|
@@ -22,15 +22,18 @@ import { getReimbursementCardReport } from '../reimbursementCard/reimbursementCa
|
|
|
22
22
|
import { getRevenueReport } from '../revenue/revenueSelector';
|
|
23
23
|
import { getTasksCardReport } from '../tasksCard/tasksCardSelector';
|
|
24
24
|
import { getTop6Expenses } from '../topEx/topExSelector';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
25
|
+
// Every input must return the same reference until the slices it reads change, or getDashboard
|
|
26
|
+
// rebuilds every card's balances on each dispatched action.
|
|
27
|
+
const getNetBurnOrIncomeFilter = createSelector((state) => state.netBurnOrIncomeState.netBurnOrIncome.firstMonthOfFY, (state) => state.netBurnOrIncomeState.uiState.timeframe, (state) => state.netBurnOrIncomeState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => newBalancesFilter(firstMonthOfFY, timeframe, 4, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
28
|
+
const getNetBurnOrIncomeForLast4Periods = createSelector((state) => state.accountState, (state) => state.accountGroupState, (state) => state.sectionAccountsViewState, (state) => state.netBurnOrIncomeState, (state) => state.forecastState, (state) => state.forecastListState, getNetBurnOrIncomeFilter, (accountState, accountGroupState, sectionAccountsViewState, netBurnOrIncomeState, forecastState, forecastListState, filter) => getNetBurnOrIncomeReport(accountState, accountGroupState, sectionAccountsViewState, netBurnOrIncomeState, forecastState, forecastListState, filter));
|
|
29
|
+
const getOperatingExpensesFilter = createSelector((state) => state.opExState.opEx.firstMonthOfFY, (state) => state.opExState.uiState.timeframe, (state) => state.opExState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => newBalancesFilter(firstMonthOfFY, timeframe, 5, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
30
|
+
const getOperatingExpensesForLast5Periods = (state) => getOperatingExpensesReport(state, getOperatingExpensesFilter(state));
|
|
31
|
+
const getRevenueFilter = createSelector((state) => state.revenueState.revenue.firstMonthOfFY, (state) => state.revenueState.uiState.timeframe, (state) => state.revenueState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => newBalancesFilter(firstMonthOfFY, timeframe, 4, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
32
|
+
const getRevenueForLast4Periods = createSelector(getRevenueFilter, (state) => state.accountState, (state) => state.accountGroupState, (state) => state.sectionAccountsViewState, (state) => state.forecastListState, (state) => state.revenueState, (state) => state.forecastState, (filter, accountState, accountGroupState, sectionAccountsViewState, forecastListState, revenueState, forecastState) => getRevenueReport(filter, accountState, accountGroupState, sectionAccountsViewState, forecastListState, revenueState, forecastState));
|
|
33
|
+
const getCashPositionFilter = createSelector((state) => state.cashPositionState.firstMonthOfFY, (state) => state.cashPositionState.uiState.timeframe, (state) => state.cashPositionState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => newBalancesFilter(firstMonthOfFY, timeframe, 5, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
34
|
+
const getCashPositionLast5Periods = createSelector((state) => state.accountState, (state) => state.cashPositionState, getCashPositionFilter, (accountState, cashPositionState, filter) => getCashPosition(accountState, cashPositionState, filter));
|
|
35
|
+
const getCashInCashOutFilter = createSelector((state) => state.cashInCashOutState.firstMonthOfFY, (state) => state.cashInCashOutState.uiState.timeframe, (state) => state.cashInCashOutState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => newBalancesFilter(firstMonthOfFY, timeframe, 4, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
36
|
+
const getCashInCashOutLast4Periods = createSelector((state) => state.accountState, (state) => state.accountGroupState, (state) => state.cashInCashOutState, getCashInCashOutFilter, (accountState, accountGroupState, cashInCashOutState, filter) => getCashInCashOut(accountState, accountGroupState, cashInCashOutState, filter));
|
|
34
37
|
const getNetBurnOrIncomeStoryCard = (state) => getNetBurnOrIncomeStoryCardReport(state);
|
|
35
38
|
const apAgingSelector = (state) => getApAgingReport(state);
|
|
36
39
|
const arAgingSelector = (state) => getArAgingReport(state);
|
|
@@ -3,7 +3,7 @@ import { getAddressByAddressId } from '../../../entity/address/addressSelector';
|
|
|
3
3
|
import { invoicingPricePointDisplayAmount } from '../../../entity/invoicing/invoicingCatalog/invoicingCatalogSelector';
|
|
4
4
|
import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
|
|
5
5
|
import { invoicingFieldHelpForGroup } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
6
|
-
import { getInvoicingAllCustomers } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
|
|
6
|
+
import { getInvoicingAllCustomers, isInvoicingAllCustomersLoading, } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
|
|
7
7
|
import { getInvoicingSettings } from '../settingsView/settingsViewSelector';
|
|
8
8
|
/**
|
|
9
9
|
* Pricing models whose single-unit amount is a true per-unit rate — the only
|
|
@@ -89,6 +89,7 @@ export const getCreateInvoiceFormView = (state) => {
|
|
|
89
89
|
id: customer.id,
|
|
90
90
|
name: customerDisplayName(customer),
|
|
91
91
|
})),
|
|
92
|
+
isCustomerListLoading: isInvoicingAllCustomersLoading(state),
|
|
92
93
|
localData: createInvoiceState.formDraft,
|
|
93
94
|
planNames: getCreateInvoicePlanNames(state),
|
|
94
95
|
products: getInvoicingCatalogPrices(catalogListView.products),
|
|
@@ -24,13 +24,20 @@ const PICKER_PRODUCT_FILTERS = { type: 'addon|charge' };
|
|
|
24
24
|
export const fetchCreateInvoiceFormPageEpic = (actions$, state$) => actions$.pipe(filter(fetchCreateInvoiceFormPage.match), mergeMap((action) => {
|
|
25
25
|
const { cacheOverride } = action.payload;
|
|
26
26
|
const state = state$.value;
|
|
27
|
-
// The customer picker
|
|
28
|
-
const
|
|
27
|
+
// The customer picker filters the unfiltered `all` cache client-side.
|
|
28
|
+
const customerListState = state.invoicingCustomerListViewState;
|
|
29
|
+
const customerTab = getInvoicingListTab(customerListState.byTab, INVOICING_LIST_ALL_TAB);
|
|
29
30
|
const catalogState = state.invoicingCatalogListViewState;
|
|
30
31
|
const settingsState = state.invoicingSettingsViewState;
|
|
31
32
|
const pageActions = [];
|
|
32
|
-
if (
|
|
33
|
+
if (shouldFetchFullInvoicingList({
|
|
34
|
+
fetchState: customerTab.fetchState,
|
|
35
|
+
filters: customerListState.filters,
|
|
36
|
+
nextCursor: customerTab.nextCursor,
|
|
37
|
+
sort: customerListState.sort,
|
|
38
|
+
}, {}, cacheOverride)) {
|
|
33
39
|
pageActions.push(fetchInvoicingCustomerList({
|
|
40
|
+
fetchAll: true,
|
|
34
41
|
filters: {},
|
|
35
42
|
keepExistingListItems: false,
|
|
36
43
|
tabId: INVOICING_LIST_ALL_TAB,
|
|
@@ -5,7 +5,7 @@ import { getFormattedAddress } from '../../addressView/addressViewSelector';
|
|
|
5
5
|
import { getInvoicingCouponListItems } from '../couponView/couponViewSelector';
|
|
6
6
|
import { getInvoicingCatalogListView, getInvoicingCatalogPrices, } from '../invoicingCatalogListView/invoicingCatalogListViewSelector';
|
|
7
7
|
import { getInvoicingConfigView, invoicingFieldHelpForGroup, } from '../invoicingConfigView/invoicingConfigViewSelector';
|
|
8
|
-
import { getInvoicingAllCustomers } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
|
|
8
|
+
import { getInvoicingAllCustomers, isInvoicingAllCustomersLoading, } from '../invoicingCustomerListView/invoicingCustomerListViewSelector';
|
|
9
9
|
import { getInvoicingFormDraftLocalData, isInvoicingFormDraftPending, } from '../invoicingFormDraft';
|
|
10
10
|
import { initialInvoicingSubscriptionFormLocalData, invoicingPriceHasQuantity, } from './invoicingSubscriptionFormConfig';
|
|
11
11
|
import { INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE } from './invoicingSubscriptionFormConfig';
|
|
@@ -283,6 +283,7 @@ export const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID)
|
|
|
283
283
|
charges: catalogItemOptionsOfType('charge'),
|
|
284
284
|
coupons: getInvoicingCouponListItems(state).map(toInvoicingCouponOption),
|
|
285
285
|
customers: getInvoicingAllCustomers(state).map(toInvoicingCustomerOption),
|
|
286
|
+
isCustomerListLoading: isInvoicingAllCustomersLoading(state),
|
|
286
287
|
localData,
|
|
287
288
|
mode,
|
|
288
289
|
plans: planPrices.map(toInvoicingPlanOption),
|
|
@@ -3,6 +3,7 @@ import { filter, mergeMap } from 'rxjs/operators';
|
|
|
3
3
|
import { fetchInvoicingCouponList } from '../couponView/couponViewReducer';
|
|
4
4
|
import { fetchInvoicingCatalogPlanList } from '../invoicingCatalogListView/invoicingCatalogListViewReducer';
|
|
5
5
|
import { fetchInvoicingCustomerList } from '../invoicingCustomerListView/invoicingCustomerListViewReducer';
|
|
6
|
+
import { INVOICING_LIST_ALL_TAB } from '../invoicingListTabCache';
|
|
6
7
|
import { fetchInvoicingSubscriptionDetail } from './editInvoicingSubscriptionDetailViewReducer';
|
|
7
8
|
import { fetchInvoicingSubscriptionFormPage } from './editInvoicingSubscriptionDetailViewReducer';
|
|
8
9
|
/**
|
|
@@ -15,7 +16,13 @@ import { fetchInvoicingSubscriptionFormPage } from './editInvoicingSubscriptionD
|
|
|
15
16
|
export const fetchInvoicingSubscriptionFormPageEpic = (actions$) => actions$.pipe(filter(fetchInvoicingSubscriptionFormPage.match), mergeMap((action) => {
|
|
16
17
|
const { subscriptionId } = action.payload;
|
|
17
18
|
const pageActions = [
|
|
18
|
-
|
|
19
|
+
// The customer picker filters the unfiltered `all` cache client-side.
|
|
20
|
+
fetchInvoicingCustomerList({
|
|
21
|
+
fetchAll: true,
|
|
22
|
+
filters: {},
|
|
23
|
+
keepExistingListItems: false,
|
|
24
|
+
tabId: INVOICING_LIST_ALL_TAB,
|
|
25
|
+
}),
|
|
19
26
|
// Plans only: the form's plan picker is the one consumer, and an
|
|
20
27
|
// unfiltered list would replace the store's plans with every catalog
|
|
21
28
|
// item.
|
package/lib/esm/view/invoicing/invoicingCustomerListView/epics/fetchInvoicingCustomerListEpic.js
CHANGED
|
@@ -30,21 +30,33 @@ export const fetchInvoicingCustomerListEpic = (actions$, state$, zeniAPI) => act
|
|
|
30
30
|
// Per tab: a newer fetch of the same tab replaces its in-flight one, while
|
|
31
31
|
// another tab's request keeps running and lands in its own cache.
|
|
32
32
|
groupBy(({ tabId }) => tabId), mergeMap((tabFetches$) => tabFetches$.pipe(switchMap(({ action, tabId }) => {
|
|
33
|
-
const { filters, cursor, keepExistingListItems, sort } = action.payload;
|
|
33
|
+
const { filters, cursor, fetchAll, keepExistingListItems, sort } = action.payload;
|
|
34
34
|
const apiUrl = withQuery(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/customers`, buildQuery(filters, cursor, sort, invoicingListStatusParam(tabId)));
|
|
35
35
|
return zeniAPI.getJSON(apiUrl).pipe(switchMap((response) => {
|
|
36
36
|
if (isSuccessResponse(response)) {
|
|
37
37
|
const items = response.data?.items ?? [];
|
|
38
|
-
|
|
38
|
+
const nextCursor = response.data?.next_cursor ?? null;
|
|
39
|
+
const pageActions = [
|
|
39
40
|
updateInvoicingCustomers(items, 'merge'),
|
|
40
41
|
updateAddresses(extractInvoicingCustomerAddresses(items)),
|
|
41
42
|
updateInvoicingCustomerList({
|
|
42
43
|
ids: items.map((item) => item.id),
|
|
43
|
-
nextCursor
|
|
44
|
+
nextCursor,
|
|
44
45
|
keepExistingListItems,
|
|
45
46
|
tabId,
|
|
46
47
|
}),
|
|
47
48
|
];
|
|
49
|
+
if (fetchAll && nextCursor != null && nextCursor !== cursor) {
|
|
50
|
+
pageActions.push(fetchInvoicingCustomerList({
|
|
51
|
+
cursor: nextCursor,
|
|
52
|
+
fetchAll,
|
|
53
|
+
filters,
|
|
54
|
+
keepExistingListItems: true,
|
|
55
|
+
sort,
|
|
56
|
+
tabId,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
return pageActions;
|
|
48
60
|
}
|
|
49
61
|
return of(updateInvoicingCustomerListFailure({
|
|
50
62
|
status: response.status,
|
package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewReducer.js
CHANGED
|
@@ -24,6 +24,7 @@ const invoicingCustomerListView = createSlice({
|
|
|
24
24
|
payload: {
|
|
25
25
|
filters: payload.filters ?? {},
|
|
26
26
|
cursor: payload.cursor,
|
|
27
|
+
fetchAll: payload.fetchAll ?? false,
|
|
27
28
|
keepExistingListItems: payload.keepExistingListItems ?? false,
|
|
28
29
|
sort: payload.sort,
|
|
29
30
|
tabId: payload.tabId,
|
package/lib/esm/view/invoicing/invoicingCustomerListView/invoicingCustomerListViewSelector.js
CHANGED
|
@@ -41,3 +41,5 @@ export const getInvoicingCustomerListView = createSelector((state) => state.invo
|
|
|
41
41
|
* view so they never narrow to whichever tab the customers list was left on.
|
|
42
42
|
*/
|
|
43
43
|
export const getInvoicingAllCustomers = (state) => getInvoicingCustomersByIds(state.invoicingCustomerState, getInvoicingListTab(state.invoicingCustomerListViewState.byTab, INVOICING_LIST_ALL_TAB).ids);
|
|
44
|
+
/** True while the pickers' customer list is still loading pages. */
|
|
45
|
+
export const isInvoicingAllCustomersLoading = (state) => getInvoicingListTab(state.invoicingCustomerListViewState.byTab, INVOICING_LIST_ALL_TAB).fetchState === 'In-Progress';
|
|
@@ -5,12 +5,9 @@ import { getCurrentTenant } from '../../entity/tenant/tenantSelector';
|
|
|
5
5
|
import { defaultPeriods } from '../netBurnOrIncome/netBurnOrIncomeReducer';
|
|
6
6
|
import { ALL_ACCOUNTING_METHODS, toAccountingMethod, } from './netBurnOrIncomeStoryCardSelectorTypes';
|
|
7
7
|
import { ALL_TIME_SPAN_IDS_FOR_AVERAGE, } from './netBurnOrIncomeStoryCardState';
|
|
8
|
-
export const getNetBurnOrIncomeStoryCardReport = createSelector((state) => state, (
|
|
9
|
-
const { netBurnOrIncomeStoryCardState, netBurnOrIncomeState } = rootState;
|
|
8
|
+
export const getNetBurnOrIncomeStoryCardReport = createSelector((state) => state.netBurnOrIncomeStoryCardState, (state) => state.netBurnOrIncomeState.netBurnOrIncome.firstMonthOfFY, getCurrentTenant, (netBurnOrIncomeStoryCardState, firstMonthOfFY, currentTenant) => {
|
|
10
9
|
const { opExCash, ebitda, netIncome, runwayCash, runwayEbitda, runwayNetIncome, reportId, reportTitle, version, status, fetchState, error, updateStatus, } = netBurnOrIncomeStoryCardState;
|
|
11
|
-
const { firstMonthOfFY } = netBurnOrIncomeState.netBurnOrIncome;
|
|
12
10
|
const filter = newBalancesFilter(firstMonthOfFY, 'month', defaultPeriods.numberOfPeriods, undefined, 'ascending_date');
|
|
13
|
-
const currentTenant = getCurrentTenant(rootState);
|
|
14
11
|
const selectedAccountingMethod = currentTenant?.netBurnSettings?.accountingMethod;
|
|
15
12
|
const timeSpanIdForAverage = currentTenant?.netBurnSettings?.timeSpanIdForAverage;
|
|
16
13
|
let netBurnOrIncomeBalances;
|
|
@@ -58,7 +58,7 @@ export const getOperatingExpensesUIState = createSelector((state) => state.opExS
|
|
|
58
58
|
reportFormat,
|
|
59
59
|
};
|
|
60
60
|
});
|
|
61
|
-
|
|
61
|
+
function getOpExBalancesInTimeFrame(opExState, sectionState, readCustomRangeSlot) {
|
|
62
62
|
// Custom detail reads its own section-accounts slot so a default fetch can't
|
|
63
63
|
// overwrite the partial-period data; the dashboard reads the default slot.
|
|
64
64
|
const reportId = reportIdUniqueKey(undefined, readCustomRangeSlot && opExState.uiState.customTimeframe != null);
|
|
@@ -73,8 +73,8 @@ const getOpExBalancesInTimeFrame = createSelector((state) => state.opExState, (s
|
|
|
73
73
|
return incomeOrExpenseBalances.balances;
|
|
74
74
|
}
|
|
75
75
|
return [];
|
|
76
|
-
}
|
|
77
|
-
export const getOperatingExpensesReport = createSelector((state) => state
|
|
76
|
+
}
|
|
77
|
+
export const getOperatingExpensesReport = createSelector((state) => state.accountState, (state) => state.accountGroupState, (state) => state.sectionAccountsViewState, (state) => state.opExState, (state) => state.forecastState, (state) => state.forecastListState, (_state, filter) => filter, (_state, _filter, forecastId) => forecastId, (_state, _filter, _forecastId, readCustomRangeSlot) => readCustomRangeSlot === true, (accountState, accountGroupState, sectionsState, opExState, forecastState, forecastListState, filter, forecastId, readCustomRangeSlot) => {
|
|
78
78
|
const opEx = getState(opExState, opExState.uiState.isForecastModeOn, forecastId);
|
|
79
79
|
// The detail screen opts into customFetchState so its Completed check fires
|
|
80
80
|
// off the custom fetch; the dashboard (no flag) reads the shared fetchState,
|
|
@@ -103,7 +103,7 @@ export const getOperatingExpensesReport = createSelector((state) => state, (stat
|
|
|
103
103
|
}, filter);
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
|
-
const balancesInTimeframe = getOpExBalancesInTimeFrame(
|
|
106
|
+
const balancesInTimeframe = getOpExBalancesInTimeFrame(opExState, sectionsState, useCustomRange);
|
|
107
107
|
const allTimeframeTicks = balancesInTimeframe.map((balance) => toTimeframeTick(balance));
|
|
108
108
|
const forecast = opEx.forecastId != null
|
|
109
109
|
? getForecast(forecastState, opEx.forecastId)
|
|
@@ -25,15 +25,18 @@ const reimbursementCardSelector_1 = require("../reimbursementCard/reimbursementC
|
|
|
25
25
|
const revenueSelector_1 = require("../revenue/revenueSelector");
|
|
26
26
|
const tasksCardSelector_1 = require("../tasksCard/tasksCardSelector");
|
|
27
27
|
const topExSelector_1 = require("../topEx/topExSelector");
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
28
|
+
// Every input must return the same reference until the slices it reads change, or getDashboard
|
|
29
|
+
// rebuilds every card's balances on each dispatched action.
|
|
30
|
+
const getNetBurnOrIncomeFilter = (0, toolkit_1.createSelector)((state) => state.netBurnOrIncomeState.netBurnOrIncome.firstMonthOfFY, (state) => state.netBurnOrIncomeState.uiState.timeframe, (state) => state.netBurnOrIncomeState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => (0, coaBalancesFilter_1.newBalancesFilter)(firstMonthOfFY, timeframe, 4, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
31
|
+
const getNetBurnOrIncomeForLast4Periods = (0, toolkit_1.createSelector)((state) => state.accountState, (state) => state.accountGroupState, (state) => state.sectionAccountsViewState, (state) => state.netBurnOrIncomeState, (state) => state.forecastState, (state) => state.forecastListState, getNetBurnOrIncomeFilter, (accountState, accountGroupState, sectionAccountsViewState, netBurnOrIncomeState, forecastState, forecastListState, filter) => (0, netBurnOrIncomeSelector_1.getNetBurnOrIncomeReport)(accountState, accountGroupState, sectionAccountsViewState, netBurnOrIncomeState, forecastState, forecastListState, filter));
|
|
32
|
+
const getOperatingExpensesFilter = (0, toolkit_1.createSelector)((state) => state.opExState.opEx.firstMonthOfFY, (state) => state.opExState.uiState.timeframe, (state) => state.opExState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => (0, coaBalancesFilter_1.newBalancesFilter)(firstMonthOfFY, timeframe, 5, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
33
|
+
const getOperatingExpensesForLast5Periods = (state) => (0, opExSelector_1.getOperatingExpensesReport)(state, getOperatingExpensesFilter(state));
|
|
34
|
+
const getRevenueFilter = (0, toolkit_1.createSelector)((state) => state.revenueState.revenue.firstMonthOfFY, (state) => state.revenueState.uiState.timeframe, (state) => state.revenueState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => (0, coaBalancesFilter_1.newBalancesFilter)(firstMonthOfFY, timeframe, 4, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
35
|
+
const getRevenueForLast4Periods = (0, toolkit_1.createSelector)(getRevenueFilter, (state) => state.accountState, (state) => state.accountGroupState, (state) => state.sectionAccountsViewState, (state) => state.forecastListState, (state) => state.revenueState, (state) => state.forecastState, (filter, accountState, accountGroupState, sectionAccountsViewState, forecastListState, revenueState, forecastState) => (0, revenueSelector_1.getRevenueReport)(filter, accountState, accountGroupState, sectionAccountsViewState, forecastListState, revenueState, forecastState));
|
|
36
|
+
const getCashPositionFilter = (0, toolkit_1.createSelector)((state) => state.cashPositionState.firstMonthOfFY, (state) => state.cashPositionState.uiState.timeframe, (state) => state.cashPositionState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => (0, coaBalancesFilter_1.newBalancesFilter)(firstMonthOfFY, timeframe, 5, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
37
|
+
const getCashPositionLast5Periods = (0, toolkit_1.createSelector)((state) => state.accountState, (state) => state.cashPositionState, getCashPositionFilter, (accountState, cashPositionState, filter) => (0, cashPositionSelector_1.getCashPosition)(accountState, cashPositionState, filter));
|
|
38
|
+
const getCashInCashOutFilter = (0, toolkit_1.createSelector)((state) => state.cashInCashOutState.firstMonthOfFY, (state) => state.cashInCashOutState.uiState.timeframe, (state) => state.cashInCashOutState.uiState.selectedAdditionalBalances, (firstMonthOfFY, timeframe, selectedAdditionalBalances) => (0, coaBalancesFilter_1.newBalancesFilter)(firstMonthOfFY, timeframe, 4, undefined, 'ascending_date', selectedAdditionalBalances));
|
|
39
|
+
const getCashInCashOutLast4Periods = (0, toolkit_1.createSelector)((state) => state.accountState, (state) => state.accountGroupState, (state) => state.cashInCashOutState, getCashInCashOutFilter, (accountState, accountGroupState, cashInCashOutState, filter) => (0, cashInCashOutSelector_1.getCashInCashOut)(accountState, accountGroupState, cashInCashOutState, filter));
|
|
37
40
|
const getNetBurnOrIncomeStoryCard = (state) => (0, netBurnOrIncomeStoryCardSelector_1.getNetBurnOrIncomeStoryCardReport)(state);
|
|
38
41
|
const apAgingSelector = (state) => (0, apAgingSelector_1.getApAgingReport)(state);
|
|
39
42
|
const arAgingSelector = (state) => (0, arAgingSelector_1.getArAgingReport)(state);
|
|
@@ -37,6 +37,8 @@ export interface CreateInvoiceFormView extends SelectorView {
|
|
|
37
37
|
* Folded in here so the screen never reaches for the config view itself.
|
|
38
38
|
*/
|
|
39
39
|
fieldHelp: InvoicingFieldHelpByCode;
|
|
40
|
+
/** More customer pages are still on their way; `customers` is partial. */
|
|
41
|
+
isCustomerListLoading: boolean;
|
|
40
42
|
localData: InvoicingCreateInvoiceFormLocalData;
|
|
41
43
|
planNames: InvoicingPlanNameOption[];
|
|
42
44
|
products: InvoicingCatalogPricePoint[];
|
|
@@ -92,6 +92,7 @@ const getCreateInvoiceFormView = (state) => {
|
|
|
92
92
|
id: customer.id,
|
|
93
93
|
name: customerDisplayName(customer),
|
|
94
94
|
})),
|
|
95
|
+
isCustomerListLoading: (0, invoicingCustomerListViewSelector_1.isInvoicingAllCustomersLoading)(state),
|
|
95
96
|
localData: createInvoiceState.formDraft,
|
|
96
97
|
planNames: getCreateInvoicePlanNames(state),
|
|
97
98
|
products: (0, invoicingCatalogListViewSelector_1.getInvoicingCatalogPrices)(catalogListView.products),
|
|
@@ -27,13 +27,20 @@ const PICKER_PRODUCT_FILTERS = { type: 'addon|charge' };
|
|
|
27
27
|
const fetchCreateInvoiceFormPageEpic = (actions$, state$) => actions$.pipe((0, operators_1.filter)(createInvoiceReducer_1.fetchCreateInvoiceFormPage.match), (0, operators_1.mergeMap)((action) => {
|
|
28
28
|
const { cacheOverride } = action.payload;
|
|
29
29
|
const state = state$.value;
|
|
30
|
-
// The customer picker
|
|
31
|
-
const
|
|
30
|
+
// The customer picker filters the unfiltered `all` cache client-side.
|
|
31
|
+
const customerListState = state.invoicingCustomerListViewState;
|
|
32
|
+
const customerTab = (0, invoicingListTabCache_1.getInvoicingListTab)(customerListState.byTab, invoicingListTabCache_1.INVOICING_LIST_ALL_TAB);
|
|
32
33
|
const catalogState = state.invoicingCatalogListViewState;
|
|
33
34
|
const settingsState = state.invoicingSettingsViewState;
|
|
34
35
|
const pageActions = [];
|
|
35
|
-
if (
|
|
36
|
+
if ((0, invoicingListTabCache_1.shouldFetchFullInvoicingList)({
|
|
37
|
+
fetchState: customerTab.fetchState,
|
|
38
|
+
filters: customerListState.filters,
|
|
39
|
+
nextCursor: customerTab.nextCursor,
|
|
40
|
+
sort: customerListState.sort,
|
|
41
|
+
}, {}, cacheOverride)) {
|
|
36
42
|
pageActions.push((0, invoicingCustomerListViewReducer_1.fetchInvoicingCustomerList)({
|
|
43
|
+
fetchAll: true,
|
|
37
44
|
filters: {},
|
|
38
45
|
keepExistingListItems: false,
|
|
39
46
|
tabId: invoicingListTabCache_1.INVOICING_LIST_ALL_TAB,
|
|
@@ -63,6 +63,8 @@ export interface InvoicingSubscriptionFormView extends SelectorView {
|
|
|
63
63
|
/** The review dialog's cost estimate for saving this draft. */
|
|
64
64
|
estimate: InvoicingSubscriptionEstimateView;
|
|
65
65
|
fieldHelp: InvoicingFieldHelpByCode;
|
|
66
|
+
/** More customer pages are still on their way; `customers` is partial. */
|
|
67
|
+
isCustomerListLoading: boolean;
|
|
66
68
|
/**
|
|
67
69
|
* True while the subscription being edited is still on its way to the form.
|
|
68
70
|
* The form holds a skeleton on this rather than mount: it seeds
|
|
@@ -290,6 +290,7 @@ const getInvoicingSubscriptionFormView = (state, invoicingSubscriptionID) => {
|
|
|
290
290
|
charges: catalogItemOptionsOfType('charge'),
|
|
291
291
|
coupons: (0, couponViewSelector_1.getInvoicingCouponListItems)(state).map(toInvoicingCouponOption),
|
|
292
292
|
customers: (0, invoicingCustomerListViewSelector_1.getInvoicingAllCustomers)(state).map(toInvoicingCustomerOption),
|
|
293
|
+
isCustomerListLoading: (0, invoicingCustomerListViewSelector_1.isInvoicingAllCustomersLoading)(state),
|
|
293
294
|
localData,
|
|
294
295
|
mode,
|
|
295
296
|
plans: planPrices.map(toInvoicingPlanOption),
|
|
@@ -6,6 +6,7 @@ const operators_1 = require("rxjs/operators");
|
|
|
6
6
|
const couponViewReducer_1 = require("../couponView/couponViewReducer");
|
|
7
7
|
const invoicingCatalogListViewReducer_1 = require("../invoicingCatalogListView/invoicingCatalogListViewReducer");
|
|
8
8
|
const invoicingCustomerListViewReducer_1 = require("../invoicingCustomerListView/invoicingCustomerListViewReducer");
|
|
9
|
+
const invoicingListTabCache_1 = require("../invoicingListTabCache");
|
|
9
10
|
const editInvoicingSubscriptionDetailViewReducer_1 = require("./editInvoicingSubscriptionDetailViewReducer");
|
|
10
11
|
const editInvoicingSubscriptionDetailViewReducer_2 = require("./editInvoicingSubscriptionDetailViewReducer");
|
|
11
12
|
/**
|
|
@@ -18,7 +19,13 @@ const editInvoicingSubscriptionDetailViewReducer_2 = require("./editInvoicingSub
|
|
|
18
19
|
const fetchInvoicingSubscriptionFormPageEpic = (actions$) => actions$.pipe((0, operators_1.filter)(editInvoicingSubscriptionDetailViewReducer_2.fetchInvoicingSubscriptionFormPage.match), (0, operators_1.mergeMap)((action) => {
|
|
19
20
|
const { subscriptionId } = action.payload;
|
|
20
21
|
const pageActions = [
|
|
21
|
-
|
|
22
|
+
// The customer picker filters the unfiltered `all` cache client-side.
|
|
23
|
+
(0, invoicingCustomerListViewReducer_1.fetchInvoicingCustomerList)({
|
|
24
|
+
fetchAll: true,
|
|
25
|
+
filters: {},
|
|
26
|
+
keepExistingListItems: false,
|
|
27
|
+
tabId: invoicingListTabCache_1.INVOICING_LIST_ALL_TAB,
|
|
28
|
+
}),
|
|
22
29
|
// Plans only: the form's plan picker is the one consumer, and an
|
|
23
30
|
// unfiltered list would replace the store's plans with every catalog
|
|
24
31
|
// item.
|
package/lib/view/invoicing/invoicingCustomerListView/epics/fetchInvoicingCustomerListEpic.js
CHANGED
|
@@ -33,21 +33,33 @@ const fetchInvoicingCustomerListEpic = (actions$, state$, zeniAPI) => actions$.p
|
|
|
33
33
|
// Per tab: a newer fetch of the same tab replaces its in-flight one, while
|
|
34
34
|
// another tab's request keeps running and lands in its own cache.
|
|
35
35
|
(0, operators_1.groupBy)(({ tabId }) => tabId), (0, operators_1.mergeMap)((tabFetches$) => tabFetches$.pipe((0, operators_1.switchMap)(({ action, tabId }) => {
|
|
36
|
-
const { filters, cursor, keepExistingListItems, sort } = action.payload;
|
|
36
|
+
const { filters, cursor, fetchAll, keepExistingListItems, sort } = action.payload;
|
|
37
37
|
const apiUrl = (0, invoicingApiHelper_1.withQuery)(`${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/customers`, buildQuery(filters, cursor, sort, (0, invoicingListTabCache_1.invoicingListStatusParam)(tabId)));
|
|
38
38
|
return zeniAPI.getJSON(apiUrl).pipe((0, operators_1.switchMap)((response) => {
|
|
39
39
|
if ((0, responsePayload_1.isSuccessResponse)(response)) {
|
|
40
40
|
const items = response.data?.items ?? [];
|
|
41
|
-
|
|
41
|
+
const nextCursor = response.data?.next_cursor ?? null;
|
|
42
|
+
const pageActions = [
|
|
42
43
|
(0, invoicingCustomerReducer_1.updateInvoicingCustomers)(items, 'merge'),
|
|
43
44
|
(0, addressReducer_1.updateAddresses)((0, invoicingCustomerPayload_1.extractInvoicingCustomerAddresses)(items)),
|
|
44
45
|
(0, invoicingCustomerListViewReducer_1.updateInvoicingCustomerList)({
|
|
45
46
|
ids: items.map((item) => item.id),
|
|
46
|
-
nextCursor
|
|
47
|
+
nextCursor,
|
|
47
48
|
keepExistingListItems,
|
|
48
49
|
tabId,
|
|
49
50
|
}),
|
|
50
51
|
];
|
|
52
|
+
if (fetchAll && nextCursor != null && nextCursor !== cursor) {
|
|
53
|
+
pageActions.push((0, invoicingCustomerListViewReducer_1.fetchInvoicingCustomerList)({
|
|
54
|
+
cursor: nextCursor,
|
|
55
|
+
fetchAll,
|
|
56
|
+
filters,
|
|
57
|
+
keepExistingListItems: true,
|
|
58
|
+
sort,
|
|
59
|
+
tabId,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
return pageActions;
|
|
51
63
|
}
|
|
52
64
|
return (0, rxjs_1.of)((0, invoicingCustomerListViewReducer_1.updateInvoicingCustomerListFailure)({
|
|
53
65
|
status: response.status,
|
|
@@ -6,6 +6,8 @@ import { InvoicingCustomerFilters, InvoicingCustomerListViewState } from './invo
|
|
|
6
6
|
export declare const initialState: InvoicingCustomerListViewState;
|
|
7
7
|
export declare const fetchInvoicingCustomerList: import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<[payload: {
|
|
8
8
|
cursor?: string;
|
|
9
|
+
/** Keep requesting the next page until the list is complete. */
|
|
10
|
+
fetchAll?: boolean;
|
|
9
11
|
filters?: InvoicingCustomerFilters;
|
|
10
12
|
keepExistingListItems?: boolean;
|
|
11
13
|
sort?: InvoicingListSort;
|
|
@@ -14,6 +16,7 @@ export declare const fetchInvoicingCustomerList: import("@reduxjs/toolkit").Acti
|
|
|
14
16
|
}], {
|
|
15
17
|
filters: InvoicingCustomerFilters;
|
|
16
18
|
cursor: string | undefined;
|
|
19
|
+
fetchAll: boolean;
|
|
17
20
|
keepExistingListItems: boolean;
|
|
18
21
|
sort: InvoicingListSort | undefined;
|
|
19
22
|
tabId: string | undefined;
|
|
@@ -28,6 +28,7 @@ const invoicingCustomerListView = (0, toolkit_1.createSlice)({
|
|
|
28
28
|
payload: {
|
|
29
29
|
filters: payload.filters ?? {},
|
|
30
30
|
cursor: payload.cursor,
|
|
31
|
+
fetchAll: payload.fetchAll ?? false,
|
|
31
32
|
keepExistingListItems: payload.keepExistingListItems ?? false,
|
|
32
33
|
sort: payload.sort,
|
|
33
34
|
tabId: payload.tabId,
|
|
@@ -30,3 +30,5 @@ export declare const getInvoicingCustomerListView: (state: RootState) => Invoici
|
|
|
30
30
|
* view so they never narrow to whichever tab the customers list was left on.
|
|
31
31
|
*/
|
|
32
32
|
export declare const getInvoicingAllCustomers: (state: RootState) => InvoicingCustomer[];
|
|
33
|
+
/** True while the pickers' customer list is still loading pages. */
|
|
34
|
+
export declare const isInvoicingAllCustomersLoading: (state: RootState) => boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getInvoicingAllCustomers = exports.getInvoicingCustomerListView = void 0;
|
|
3
|
+
exports.isInvoicingAllCustomersLoading = exports.getInvoicingAllCustomers = exports.getInvoicingCustomerListView = void 0;
|
|
4
4
|
const toolkit_1 = require("@reduxjs/toolkit");
|
|
5
5
|
const invoicingCustomerSelector_1 = require("../../../entity/invoicing/invoicingCustomer/invoicingCustomerSelector");
|
|
6
6
|
const invoicingListTabCache_1 = require("../invoicingListTabCache");
|
|
@@ -45,3 +45,6 @@ exports.getInvoicingCustomerListView = (0, toolkit_1.createSelector)((state) =>
|
|
|
45
45
|
*/
|
|
46
46
|
const getInvoicingAllCustomers = (state) => (0, invoicingCustomerSelector_1.getInvoicingCustomersByIds)(state.invoicingCustomerState, (0, invoicingListTabCache_1.getInvoicingListTab)(state.invoicingCustomerListViewState.byTab, invoicingListTabCache_1.INVOICING_LIST_ALL_TAB).ids);
|
|
47
47
|
exports.getInvoicingAllCustomers = getInvoicingAllCustomers;
|
|
48
|
+
/** True while the pickers' customer list is still loading pages. */
|
|
49
|
+
const isInvoicingAllCustomersLoading = (state) => (0, invoicingListTabCache_1.getInvoicingListTab)(state.invoicingCustomerListViewState.byTab, invoicingListTabCache_1.INVOICING_LIST_ALL_TAB).fetchState === 'In-Progress';
|
|
50
|
+
exports.isInvoicingAllCustomersLoading = isInvoicingAllCustomersLoading;
|
|
@@ -8,12 +8,9 @@ const tenantSelector_1 = require("../../entity/tenant/tenantSelector");
|
|
|
8
8
|
const netBurnOrIncomeReducer_1 = require("../netBurnOrIncome/netBurnOrIncomeReducer");
|
|
9
9
|
const netBurnOrIncomeStoryCardSelectorTypes_1 = require("./netBurnOrIncomeStoryCardSelectorTypes");
|
|
10
10
|
const netBurnOrIncomeStoryCardState_1 = require("./netBurnOrIncomeStoryCardState");
|
|
11
|
-
exports.getNetBurnOrIncomeStoryCardReport = (0, toolkit_1.createSelector)((state) => state, (
|
|
12
|
-
const { netBurnOrIncomeStoryCardState, netBurnOrIncomeState } = rootState;
|
|
11
|
+
exports.getNetBurnOrIncomeStoryCardReport = (0, toolkit_1.createSelector)((state) => state.netBurnOrIncomeStoryCardState, (state) => state.netBurnOrIncomeState.netBurnOrIncome.firstMonthOfFY, tenantSelector_1.getCurrentTenant, (netBurnOrIncomeStoryCardState, firstMonthOfFY, currentTenant) => {
|
|
13
12
|
const { opExCash, ebitda, netIncome, runwayCash, runwayEbitda, runwayNetIncome, reportId, reportTitle, version, status, fetchState, error, updateStatus, } = netBurnOrIncomeStoryCardState;
|
|
14
|
-
const { firstMonthOfFY } = netBurnOrIncomeState.netBurnOrIncome;
|
|
15
13
|
const filter = (0, coaBalancesFilter_1.newBalancesFilter)(firstMonthOfFY, 'month', netBurnOrIncomeReducer_1.defaultPeriods.numberOfPeriods, undefined, 'ascending_date');
|
|
16
|
-
const currentTenant = (0, tenantSelector_1.getCurrentTenant)(rootState);
|
|
17
14
|
const selectedAccountingMethod = currentTenant?.netBurnSettings?.accountingMethod;
|
|
18
15
|
const timeSpanIdForAverage = currentTenant?.netBurnSettings?.timeSpanIdForAverage;
|
|
19
16
|
let netBurnOrIncomeBalances;
|
|
@@ -61,7 +61,7 @@ exports.getOperatingExpensesUIState = (0, toolkit_1.createSelector)((state) => s
|
|
|
61
61
|
reportFormat,
|
|
62
62
|
};
|
|
63
63
|
});
|
|
64
|
-
|
|
64
|
+
function getOpExBalancesInTimeFrame(opExState, sectionState, readCustomRangeSlot) {
|
|
65
65
|
// Custom detail reads its own section-accounts slot so a default fetch can't
|
|
66
66
|
// overwrite the partial-period data; the dashboard reads the default slot.
|
|
67
67
|
const reportId = (0, opExForTimeFrameEpic_1.reportIdUniqueKey)(undefined, readCustomRangeSlot && opExState.uiState.customTimeframe != null);
|
|
@@ -76,8 +76,8 @@ const getOpExBalancesInTimeFrame = (0, toolkit_1.createSelector)((state) => stat
|
|
|
76
76
|
return incomeOrExpenseBalances.balances;
|
|
77
77
|
}
|
|
78
78
|
return [];
|
|
79
|
-
}
|
|
80
|
-
exports.getOperatingExpensesReport = (0, toolkit_1.createSelector)((state) => state
|
|
79
|
+
}
|
|
80
|
+
exports.getOperatingExpensesReport = (0, toolkit_1.createSelector)((state) => state.accountState, (state) => state.accountGroupState, (state) => state.sectionAccountsViewState, (state) => state.opExState, (state) => state.forecastState, (state) => state.forecastListState, (_state, filter) => filter, (_state, _filter, forecastId) => forecastId, (_state, _filter, _forecastId, readCustomRangeSlot) => readCustomRangeSlot === true, (accountState, accountGroupState, sectionsState, opExState, forecastState, forecastListState, filter, forecastId, readCustomRangeSlot) => {
|
|
81
81
|
const opEx = (0, opExReducer_1.getState)(opExState, opExState.uiState.isForecastModeOn, forecastId);
|
|
82
82
|
// The detail screen opts into customFetchState so its Completed check fires
|
|
83
83
|
// off the custom fetch; the dashboard (no flag) reads the shared fetchState,
|
|
@@ -106,7 +106,7 @@ exports.getOperatingExpensesReport = (0, toolkit_1.createSelector)((state) => st
|
|
|
106
106
|
}, filter);
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
|
-
const balancesInTimeframe = getOpExBalancesInTimeFrame(
|
|
109
|
+
const balancesInTimeframe = getOpExBalancesInTimeFrame(opExState, sectionsState, useCustomRange);
|
|
110
110
|
const allTimeframeTicks = balancesInTimeframe.map((balance) => (0, timeframeTick_1.toTimeframeTick)(balance));
|
|
111
111
|
const forecast = opEx.forecastId != null
|
|
112
112
|
? (0, forecastSelector_1.getForecast)(forecastState, opEx.forecastId)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeniai/client-epic-state",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.24-betaAK1",
|
|
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",
|