@zeniai/client-epic-state 5.2.74 → 5.2.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/lib/commonStateTypes/common.d.ts +3 -0
  2. package/lib/commonStateTypes/common.js +10 -1
  3. package/lib/entity/invoicing/coupon/couponSelector.d.ts +1 -0
  4. package/lib/entity/invoicing/coupon/couponSelector.js +5 -0
  5. package/lib/epic.d.ts +2 -1
  6. package/lib/epic.js +2 -0
  7. package/lib/esm/commonStateTypes/common.js +8 -0
  8. package/lib/esm/entity/invoicing/coupon/couponSelector.js +4 -0
  9. package/lib/esm/epic.js +2 -0
  10. package/lib/esm/index.js +3 -3
  11. package/lib/esm/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.js +140 -9
  12. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.js +55 -0
  13. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +35 -1
  14. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +53 -1
  15. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimateEpic.js +49 -0
  16. package/lib/esm/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +18 -13
  17. package/lib/esm/view/taskManager/taskListView/taskListSelector.js +70 -19
  18. package/lib/index.d.ts +5 -5
  19. package/lib/index.js +18 -15
  20. package/lib/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.d.ts +1 -1
  21. package/lib/view/companyTaskManagerView/epics/fetchCompanyTaskManagerViewEpic.js +140 -9
  22. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.d.ts +46 -1
  23. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewPayload.js +57 -1
  24. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.d.ts +10 -2
  25. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewReducer.js +36 -2
  26. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.d.ts +20 -1
  27. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewSelector.js +55 -2
  28. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/editInvoicingSubscriptionDetailViewState.d.ts +58 -0
  29. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimateEpic.d.ts +7 -0
  30. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/fetchInvoicingSubscriptionEstimateEpic.js +53 -0
  31. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.d.ts +8 -0
  32. package/lib/view/invoicing/editInvoicingSubscriptionDetailView/saveInvoicingSubscriptionEpic.js +21 -14
  33. package/lib/view/taskManager/taskListView/taskListSelector.d.ts +13 -2
  34. package/lib/view/taskManager/taskListView/taskListSelector.js +70 -19
  35. package/package.json +1 -1
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchInvoicingSubscriptionEstimateEpic = void 0;
4
+ const rxjs_1 = require("rxjs");
5
+ const operators_1 = require("rxjs/operators");
6
+ const couponSelector_1 = require("../../../entity/invoicing/coupon/couponSelector");
7
+ const responsePayload_1 = require("../../../responsePayload");
8
+ const editInvoicingSubscriptionDetailViewPayload_1 = require("./editInvoicingSubscriptionDetailViewPayload");
9
+ const editInvoicingSubscriptionDetailViewReducer_1 = require("./editInvoicingSubscriptionDetailViewReducer");
10
+ const saveInvoicingSubscriptionEpic_1 = require("./saveInvoicingSubscriptionEpic");
11
+ // Posts Save's exact body; a changed coupon rides along as `coupons` (Save syncs it after the PUT).
12
+ const fetchInvoicingSubscriptionEstimateEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(editInvoicingSubscriptionDetailViewReducer_1.fetchInvoicingSubscriptionEstimate.match), (0, operators_1.switchMap)((action) => {
13
+ const { id } = action.payload;
14
+ const { data, localData, previousCouponCode } = (0, saveInvoicingSubscriptionEpic_1.invoicingSubscriptionSaveRequest)(state$.value, id);
15
+ const couponCode = localData.couponId;
16
+ const coupon = (0, couponSelector_1.getInvoicingCouponByCode)(state$.value.invoicingCouponState, couponCode);
17
+ const body = id != null &&
18
+ (0, saveInvoicingSubscriptionEpic_1.isInvoicingSubscriptionCouponChanged)(previousCouponCode, couponCode)
19
+ ? {
20
+ ...data,
21
+ coupons: couponCode !== ''
22
+ ? [
23
+ {
24
+ applied_count: 0,
25
+ apply_till: null,
26
+ coupon_code: couponCode,
27
+ coupon_id: coupon?.id ?? couponCode,
28
+ },
29
+ ]
30
+ : [],
31
+ }
32
+ : data;
33
+ const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0/subscriptions`;
34
+ const url = id != null
35
+ ? `${baseUrl}/${encodeURIComponent(id)}/estimate`
36
+ : `${baseUrl}/estimate`;
37
+ return zeniAPI
38
+ .postAndGetJSON(url, body)
39
+ .pipe((0, operators_1.map)((response) => (0, responsePayload_1.isSuccessResponse)(response) && response.data != null
40
+ ? (0, editInvoicingSubscriptionDetailViewReducer_1.updateInvoicingSubscriptionEstimate)({
41
+ estimate: (0, editInvoicingSubscriptionDetailViewPayload_1.mapInvoicingSubscriptionEstimatePayloadToState)(response.data),
42
+ id,
43
+ })
44
+ : (0, editInvoicingSubscriptionDetailViewReducer_1.updateInvoicingSubscriptionEstimateFailure)({
45
+ id,
46
+ status: response.status,
47
+ })), (0, operators_1.catchError)((error) => (0, rxjs_1.of)((0, editInvoicingSubscriptionDetailViewReducer_1.updateInvoicingSubscriptionEstimateFailure)({
48
+ id,
49
+ status: (0, responsePayload_1.createZeniAPIStatus)('Unexpected Error', 'Subscription estimate REST API call errored out ' +
50
+ JSON.stringify(error)),
51
+ }))));
52
+ }));
53
+ exports.fetchInvoicingSubscriptionEstimateEpic = fetchInvoicingSubscriptionEstimateEpic;
@@ -1,5 +1,6 @@
1
1
  import { ActionsObservable, StateObservable } from 'redux-observable';
2
2
  import { Observable } from 'rxjs';
3
+ import { ID } from '../../../commonStateTypes/common';
3
4
  import { updateAddresses } from '../../../entity/address/addressReducer';
4
5
  import { updateInvoicingSubscriptions } from '../../../entity/invoicing/invoicingSubscription/invoicingSubscriptionReducer';
5
6
  import { openSnackbar } from '../../../entity/snackbar/snackbarReducer';
@@ -10,5 +11,12 @@ import { invalidateInvoicingCustomerDetail } from '../editInvoicingCustomerDetai
10
11
  import { invalidateInvoiceListForCustomer } from '../invoiceList/invoiceListReducer';
11
12
  import { addInvoicingSubscriptionToList } from '../invoicingSubscriptionListView/invoicingSubscriptionListViewReducer';
12
13
  import { submitInvoicingSubscriptionForm, submitInvoicingSubscriptionFormFailure, submitInvoicingSubscriptionFormSuccess, syncInvoicingSubscriptionCoupons } from './editInvoicingSubscriptionDetailViewReducer';
14
+ import { InvoicingSubscriptionFormLocalData } from './editInvoicingSubscriptionDetailViewState';
13
15
  export type ActionType = ReturnType<typeof submitInvoicingSubscriptionForm> | ReturnType<typeof submitInvoicingSubscriptionFormSuccess> | ReturnType<typeof submitInvoicingSubscriptionFormFailure> | ReturnType<typeof syncInvoicingSubscriptionCoupons> | ReturnType<typeof resetNewAddressDataInLocalStore> | ReturnType<typeof updateAddresses> | ReturnType<typeof updateInvoicingSubscriptions> | ReturnType<typeof addInvoicingSubscriptionToList> | ReturnType<typeof invalidateInvoicingCustomerDetail> | ReturnType<typeof invalidateInvoiceListForCustomer> | ReturnType<typeof openSnackbar>;
16
+ export declare const isInvoicingSubscriptionCouponChanged: (previousCouponCode: string, nextCouponCode: string) => boolean;
17
+ export declare const invoicingSubscriptionSaveRequest: (rootState: RootState, id?: ID) => {
18
+ data: Record<string, unknown>;
19
+ localData: InvoicingSubscriptionFormLocalData;
20
+ previousCouponCode: string;
21
+ };
14
22
  export declare const saveInvoicingSubscriptionEpic: (actions$: ActionsObservable<ActionType>, state$: StateObservable<RootState>, zeniAPI: ZeniAPI) => Observable<ActionType>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.saveInvoicingSubscriptionEpic = void 0;
3
+ exports.saveInvoicingSubscriptionEpic = exports.invoicingSubscriptionSaveRequest = exports.isInvoicingSubscriptionCouponChanged = void 0;
4
4
  const rxjs_1 = require("rxjs");
5
5
  const operators_1 = require("rxjs/operators");
6
6
  const addressReducer_1 = require("../../../entity/address/addressReducer");
@@ -17,9 +17,11 @@ const editInvoicingSubscriptionDetailViewPayload_1 = require("./editInvoicingSub
17
17
  const editInvoicingSubscriptionDetailViewReducer_1 = require("./editInvoicingSubscriptionDetailViewReducer");
18
18
  const invoicingSubscriptionFormConfig_1 = require("./invoicingSubscriptionFormConfig");
19
19
  const normalizeCouponCode = (value) => (value ?? '').trim().toUpperCase();
20
- const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(editInvoicingSubscriptionDetailViewReducer_1.submitInvoicingSubscriptionForm.match), (0, operators_1.mergeMap)((action) => {
21
- const { id } = action.payload;
22
- const rootState = state$.value;
20
+ const isInvoicingSubscriptionCouponChanged = (previousCouponCode, nextCouponCode) => normalizeCouponCode(previousCouponCode) !==
21
+ normalizeCouponCode(nextCouponCode);
22
+ exports.isInvoicingSubscriptionCouponChanged = isInvoicingSubscriptionCouponChanged;
23
+ // The draft a save reads and the body it sends; the estimate previews the same body.
24
+ const invoicingSubscriptionSaveRequest = (rootState, id) => {
23
25
  const viewState = rootState.editInvoicingSubscriptionDetailViewState;
24
26
  const localData = (id != null
25
27
  ? viewState.editDraftById[id]?.localData
@@ -28,18 +30,24 @@ const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => actions$.pi
28
30
  // object in the payload (same pattern as the customer save flow).
29
31
  const shippingAddress = rootState.addressViewState.newAddressState?.[invoicingSubscriptionFormConfig_1.INVOICING_SUBSCRIPTION_SHIPPING_ADDRESS_TYPE]?.addressToCreate;
30
32
  const data = (0, editInvoicingSubscriptionDetailViewPayload_1.localDataToSaveInvoicingSubscriptionPayload)(localData, shippingAddress);
31
- // Coupons are synced via apply/remove-coupon after PUT (not via coupon_ids).
32
33
  if (id != null) {
33
34
  delete data.coupon_ids;
34
35
  }
36
+ const savedCoupon = id != null
37
+ ? rootState.invoicingSubscriptionState.invoicingSubscriptionByID[id]
38
+ ?.coupons?.[0]
39
+ : undefined;
40
+ return {
41
+ data,
42
+ localData,
43
+ previousCouponCode: savedCoupon?.couponCode ?? savedCoupon?.couponID ?? '',
44
+ };
45
+ };
46
+ exports.invoicingSubscriptionSaveRequest = invoicingSubscriptionSaveRequest;
47
+ const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => actions$.pipe((0, operators_1.filter)(editInvoicingSubscriptionDetailViewReducer_1.submitInvoicingSubscriptionForm.match), (0, operators_1.mergeMap)((action) => {
48
+ const { id } = action.payload;
49
+ const { data, localData, previousCouponCode } = (0, exports.invoicingSubscriptionSaveRequest)(state$.value, id);
35
50
  const baseUrl = `${zeniAPI.apiEndPoints.invoicingMicroServiceBaseUrl}/1.0`;
36
- const previousCouponCode = id != null
37
- ? (rootState.invoicingSubscriptionState.invoicingSubscriptionByID[id]
38
- ?.coupons?.[0]?.couponCode ??
39
- rootState.invoicingSubscriptionState.invoicingSubscriptionByID[id]
40
- ?.coupons?.[0]?.couponID ??
41
- '')
42
- : '';
43
51
  const request$ = id != null
44
52
  ? zeniAPI.putAndGetJSON(`${baseUrl}/subscriptions/${encodeURIComponent(id)}`, { ...data })
45
53
  : zeniAPI.postAndGetJSON(`${baseUrl}/subscriptions`, { ...data });
@@ -57,8 +65,7 @@ const saveInvoicingSubscriptionEpic = (actions$, state$, zeniAPI) => actions$.pi
57
65
  }
58
66
  const saved = response.data;
59
67
  const couponChanged = id != null &&
60
- normalizeCouponCode(previousCouponCode) !==
61
- normalizeCouponCode(localData.couponId);
68
+ (0, exports.isInvoicingSubscriptionCouponChanged)(previousCouponCode, localData.couponId);
62
69
  // Edit + coupon change: merge the saved subscription, then hand the
63
70
  // coupon attach/detach to syncInvoicingSubscriptionCouponsEpic, which
64
71
  // finishes the flow (form success on both calls ok, form failure
@@ -38,10 +38,20 @@ export interface TaskListSelectorView extends SelectorView {
38
38
  userGroupList: UserGroupListSelectorView;
39
39
  userList: UserListSelectorView;
40
40
  }
41
+ /**
42
+ * Exactly the parts of `TaskListState` the grouping pipeline reads.
43
+ *
44
+ * `TaskListState` also carries `localData` — the current selection and the
45
+ * group name being typed — which changes on every checkbox click and every
46
+ * keystroke. None of the work below looks at it, so naming the real inputs is
47
+ * what lets the pipeline be memoised against them instead of against the whole
48
+ * slice. `TaskListState` still satisfies this, so callers are unaffected.
49
+ */
50
+ type TaskListGroupingInputs = Pick<TaskListState, 'filters' | 'taskIdsByAssignees' | 'taskIdsByDueDate' | 'taskIdsByGroupsIds' | 'taskIdsByPriority' | 'taskIdsByStatus' | 'taskIdsByTags' | 'uiState'>;
41
51
  export declare const getTaskListGroupedByAssignees: ({ taskListState, taskState, userState, userRoleState, addressState, tagState, userGroupsState, }: {
42
52
  addressState: AddressState;
43
53
  tagState: TagState;
44
- taskListState: TaskListState;
54
+ taskListState: TaskListGroupingInputs;
45
55
  taskState: TaskState;
46
56
  userGroupsState: UserGroupsState;
47
57
  userRoleState: UserRoleState;
@@ -55,7 +65,7 @@ export declare const getTaskListGroupedByAssignees: ({ taskListState, taskState,
55
65
  export declare const getTaskListGroupedByPriority: ({ taskListState, taskState, userState, userRoleState, addressState, tagState, userGroupsState, }: {
56
66
  addressState: AddressState;
57
67
  tagState: TagState;
58
- taskListState: TaskListState;
68
+ taskListState: TaskListGroupingInputs;
59
69
  taskState: TaskState;
60
70
  userGroupsState: UserGroupsState;
61
71
  userRoleState: UserRoleState;
@@ -67,3 +77,4 @@ export declare const getTaskListGroupedByPriority: ({ taskListState, taskState,
67
77
  tasks: TaskWithUserDetails[];
68
78
  }[];
69
79
  export declare const getAllTasks: (state: RootState, isTaskTemplatesEnabled: boolean) => TaskListSelectorView;
80
+ export {};
@@ -7,6 +7,7 @@ exports.getAllTasks = exports.getTaskListGroupedByPriority = exports.getTaskList
7
7
  const toolkit_1 = require("@reduxjs/toolkit");
8
8
  const orderBy_1 = __importDefault(require("lodash/orderBy"));
9
9
  const sortOrderPayload_1 = require("../../../commonPayloadTypes/sortOrderPayload");
10
+ const common_1 = require("../../../commonStateTypes/common");
10
11
  const reduceFetchState_1 = require("../../../commonStateTypes/reduceFetchState");
11
12
  const tagSelector_1 = require("../../../entity/tag/tagSelector");
12
13
  const taskSelector_1 = require("../../../entity/task/taskSelector");
@@ -23,14 +24,18 @@ const taskList_1 = require("./taskList");
23
24
  const taskListFilterHelpers_1 = require("./taskListFilterHelpers");
24
25
  const sortTasksList = (tagState, tasksList, sortKey, sortOrder) => {
25
26
  const tasksInOrder = (0, orderBy_1.default)(tasksList, (task) => {
26
- const tagsByIds = (0, tagSelector_1.getTagsByIds)(tagState, task.tagIds);
27
27
  switch (sortKey) {
28
28
  case 'dueDate':
29
29
  return task.dueDate;
30
30
  case 'creationDate':
31
31
  return task.createTime;
32
32
  case 'tag':
33
- return tagsByIds[0]?.name ?? ''; // only one tag is supported
33
+ // Resolved HERE, not above the switch. Hoisted, it allocated a fresh
34
+ // array per task on every sort key, which made `tagState` a real
35
+ // dependency of the whole pipeline: tags arriving after first paint
36
+ // then rebuilt every group and re-rendered every row, moving that
37
+ // work out from behind the skeleton and into the user's hands.
38
+ return (0, tagSelector_1.getTagsByIds)(tagState, task.tagIds)[0]?.name ?? ''; // only one tag is supported
34
39
  case 'status':
35
40
  return task.status.name;
36
41
  case 'priority':
@@ -75,8 +80,7 @@ const getTasksWithUserDetails = ({ taskListState, taskState, userState, userRole
75
80
  const sortedTaskList = sortTasksList(tagState, filteredTaskList, sortKey, sortOrder);
76
81
  return sortedTaskList;
77
82
  };
78
- const getTaskListGroupedByGroupName = ({ taskListState, taskState, taskGroupState, taskGroupViewState, userState, userRoleState, addressState, tagState, userGroupsState, }) => {
79
- const { taskGroupsIds } = taskGroupViewState;
83
+ const getTaskListGroupedByGroupName = ({ taskListState, taskState, taskGroupState, taskGroupsIds, userState, userRoleState, addressState, tagState, userGroupsState, }) => {
80
84
  const taskGroupsWithTasksList = taskGroupsIds
81
85
  .map((taskGroupId) => {
82
86
  const taskGroup = (0, taskGroupSelector_1.getTaskGroupById)(taskGroupState, taskGroupId);
@@ -118,6 +122,7 @@ const getTaskListGroupedByGroupName = ({ taskListState, taskState, taskGroupStat
118
122
  if (defaultTaskGroup != null) {
119
123
  sortedTaskGroupsWithTasksList.push(defaultTaskGroup);
120
124
  }
125
+ // Also hides deleted groups, which are empty as a side effect.
121
126
  return sortedTaskGroupsWithTasksList.filter((group) => group.tasks.length > 0);
122
127
  };
123
128
  const getTaskListGroupedByAssignees = ({ taskListState, taskState, userState, userRoleState, addressState, tagState, userGroupsState, }) => {
@@ -249,7 +254,7 @@ const getTaskListGroupedByDueDate = ({ taskListState, taskState, userState, user
249
254
  });
250
255
  return taskGroupsWithTasksList.filter((group) => group.tasks.length > 0);
251
256
  };
252
- const getTaskGroupsWithTaskList = ({ taskListState, taskState, userState, userRoleState, addressState, taskGroupState, taskGroupViewState, tagState, userGroupsState, }) => {
257
+ const getTaskGroupsWithTaskList = ({ taskListState, taskState, userState, userRoleState, addressState, taskGroupState, taskGroupsIds, tagState, userGroupsState, }) => {
253
258
  const { groupByKey } = taskListState.uiState;
254
259
  if (groupByKey === 'groupName') {
255
260
  return getTaskListGroupedByGroupName({
@@ -259,7 +264,7 @@ const getTaskGroupsWithTaskList = ({ taskListState, taskState, userState, userRo
259
264
  userRoleState,
260
265
  addressState,
261
266
  taskGroupState,
262
- taskGroupViewState,
267
+ taskGroupsIds,
263
268
  tagState,
264
269
  userGroupsState,
265
270
  });
@@ -318,24 +323,70 @@ const getTaskGroupsWithTaskList = ({ taskListState, taskState, userState, userRo
318
323
  userGroupsState,
319
324
  });
320
325
  };
321
- exports.getAllTasks = (0, toolkit_1.createSelector)((state) => state.taskListState, (state) => state.taskState, (state) => state.userState, (state) => state.userRoleState, (state) => state.userListViewState, (state) => state.addressState, (state) => state.taskGroupState, (state) => state.taskGroupViewState, (state) => state.tagViewState, (state) => state.tagState, (state) => state.taskGroupTemplateState, (state) => state.taskGroupTemplateViewState, (state) => state.userGroupsState, (state) => state.userGroupListViewState, (_state, isTaskTemplatesEnabled) => isTaskTemplatesEnabled, (taskListState, taskState, userState, userRoleState, userListViewState, addressState, taskGroupState, taskGroupViewState, tagViewState, tagState, taskGroupTemplateState, taskGroupTemplateViewState, userGroupsState, userGroupListViewState, isTaskTemplatesEnabled) => {
326
+ /**
327
+ * Tags are STRUCTURAL only when they name a group or order the rows. Anywhere
328
+ * else they are decoration on a row that has already rendered.
329
+ */
330
+ const tagsAreStructural = (uiState) => uiState.groupByKey === 'tag' || uiState.sortKey === 'tag';
331
+ /** One frozen instance, so the "tags are not structural" input never changes. */
332
+ const EMPTY_TAG_STATE = { tagByID: {} };
333
+ /** Inputs are listed field by field, not as the slice: immer keeps each one
334
+ * referentially stable when `localData` changes, so the memo holds. */
335
+ const getTaskGroupsWithTaskListMemoized = (0, toolkit_1.createSelector)((state) => state.taskListState.uiState, (state) => state.taskListState.filters, (state) => state.taskListState.taskIdsByGroupsIds, (state) => state.taskListState.taskIdsByAssignees, (state) => state.taskListState.taskIdsByPriority, (state) => state.taskListState.taskIdsByStatus, (state) => state.taskListState.taskIdsByTags, (state) => state.taskListState.taskIdsByDueDate, (state) => state.taskState, (state) => state.userState, (state) => state.userRoleState, (state) => state.addressState, (state) => state.taskGroupState,
336
+ // `taskGroupsIds`, not the whole slice. `TaskGroupViewState` also carries
337
+ // taskGroupCreationStatus / DeleteStatus / UpdateStatus and fetchState, and
338
+ // none of them reach the board — but each transition produces a fresh slice
339
+ // reference, so an inline group rename alone rebuilt the entire grouped list
340
+ // twice (In-Progress, then Completed). That is the same needless rebuild this
341
+ // change removes for the other slices.
342
+ (state) => state.taskGroupViewState.taskGroupsIds,
343
+ // Only when tags are STRUCTURAL — they name a group or order the rows.
344
+ // Otherwise a stable empty slice, so tags landing after first paint cannot
345
+ // invalidate this memo and rebuild a board the user is already using.
346
+ (state) => tagsAreStructural(state.taskListState.uiState)
347
+ ? state.tagState
348
+ : EMPTY_TAG_STATE, (state) => state.userGroupsState, (uiState, filters, taskIdsByGroupsIds, taskIdsByAssignees, taskIdsByPriority, taskIdsByStatus, taskIdsByTags, taskIdsByDueDate, taskState, userState, userRoleState, addressState, taskGroupState, taskGroupsIds, tagState, userGroupsState) => getTaskGroupsWithTaskList({
349
+ taskListState: {
350
+ uiState,
351
+ filters,
352
+ taskIdsByGroupsIds,
353
+ taskIdsByAssignees,
354
+ taskIdsByPriority,
355
+ taskIdsByStatus,
356
+ taskIdsByTags,
357
+ taskIdsByDueDate,
358
+ },
359
+ taskState,
360
+ userState,
361
+ userRoleState,
362
+ addressState,
363
+ taskGroupState,
364
+ taskGroupsIds,
365
+ tagState,
366
+ userGroupsState,
367
+ }));
368
+ exports.getAllTasks = (0, toolkit_1.createSelector)(
369
+ // `taskState`, `taskGroupState`, `addressState` and `tagState` are gone from
370
+ // here: the grouped list was their only consumer and it is memoised
371
+ // separately now, so a tag edit or an address arriving no longer rebuilds
372
+ // this view.
373
+ (state) => state.taskListState, (state) => state.userState, (state) => state.userRoleState, (state) => state.userListViewState, (state) => state.taskGroupViewState, (state) => state.tagViewState,
374
+ // A BOOLEAN, not the tag slice: reselect compares primitives by value, so
375
+ // this invalidates only when the map goes empty <-> non-empty.
376
+ (state) => (0, common_1.hasAnyKey)(state.tagState.tagByID), (state) => state.taskGroupTemplateState, (state) => state.taskGroupTemplateViewState, (state) => state.userGroupsState, (state) => state.userGroupListViewState, getTaskGroupsWithTaskListMemoized, (_state, isTaskTemplatesEnabled) => isTaskTemplatesEnabled, (taskListState, userState, userRoleState, userListViewState, taskGroupViewState, tagViewState, hasAnyTag, taskGroupTemplateState, taskGroupTemplateViewState, userGroupsState, userGroupListViewState, taskGroupsWithTasksList, isTaskTemplatesEnabled) => {
322
377
  const { localData } = taskListState;
323
- const taskGroupsWithTasksList = getTaskGroupsWithTaskList({
324
- taskListState,
325
- taskState,
326
- userState,
327
- userRoleState,
328
- addressState,
329
- taskGroupState,
330
- taskGroupViewState,
331
- tagState,
332
- userGroupsState,
333
- });
378
+ // Only the data the list is BUILT from. Tags decorate a row that can
379
+ // already be drawn.
334
380
  const fetchStateVariables = [
335
381
  taskListState,
336
382
  taskGroupViewState,
337
- tagViewState,
338
383
  ];
384
+ // Structural tags are waited on. A failed fetch is bypassed only when tags
385
+ // are already in the store, because then the headers still render.
386
+ if (tagsAreStructural(taskListState.uiState) &&
387
+ !(hasAnyTag && tagViewState.fetchState === 'Error')) {
388
+ fetchStateVariables.push(tagViewState);
389
+ }
339
390
  if (isTaskTemplatesEnabled) {
340
391
  fetchStateVariables.push(taskGroupTemplateViewState);
341
392
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeniai/client-epic-state",
3
- "version": "5.2.74",
3
+ "version": "5.2.76",
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",