@zeniai/client-epic-state 5.3.7 → 5.3.8-beta0DI
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/invoicing/invoice/invoicePayload.d.ts +1 -0
- package/lib/entity/invoicing/invoice/invoicePayload.js +3 -0
- package/lib/entity/invoicing/invoice/invoiceState.d.ts +2 -0
- package/lib/entity/task/taskPayload.d.ts +1 -0
- package/lib/entity/task/taskPayload.js +1 -0
- package/lib/entity/task/taskReducer.d.ts +4 -1
- package/lib/entity/task/taskReducer.js +19 -2
- package/lib/entity/task/taskState.d.ts +3 -0
- package/lib/entity/timeEntry/timeEntryPayload.d.ts +26 -0
- package/lib/entity/timeEntry/timeEntryPayload.js +2 -0
- package/lib/entity/timeEntry/timeEntryReducer.d.ts +8 -0
- package/lib/entity/timeEntry/timeEntryReducer.js +76 -0
- package/lib/entity/timeEntry/timeEntrySelector.d.ts +10 -0
- package/lib/entity/timeEntry/timeEntrySelector.js +32 -0
- package/lib/entity/timeEntry/timeEntryState.d.ts +43 -0
- package/lib/entity/timeEntry/timeEntryState.js +13 -0
- package/lib/epic.d.ts +4 -1
- package/lib/epic.js +9 -0
- package/lib/esm/entity/invoicing/invoice/invoicePayload.js +3 -0
- package/lib/esm/entity/task/taskPayload.js +1 -0
- package/lib/esm/entity/task/taskReducer.js +18 -1
- package/lib/esm/entity/timeEntry/timeEntryPayload.js +1 -0
- package/lib/esm/entity/timeEntry/timeEntryReducer.js +70 -0
- package/lib/esm/entity/timeEntry/timeEntrySelector.js +23 -0
- package/lib/esm/entity/timeEntry/timeEntryState.js +8 -0
- package/lib/esm/epic.js +9 -0
- package/lib/esm/index.js +4 -1
- package/lib/esm/reducer.js +6 -0
- package/lib/esm/view/expenseAutomationView/epics/accountRecon/fetchReconciliationViewEpic.js +2 -1
- package/lib/esm/view/taskManager/taskDetailView/taskDetailSelector.js +3 -0
- package/lib/esm/view/taskManager/taskListView/taskList.js +5 -0
- package/lib/esm/view/taskManager/taskListView/taskListFilterHelpers.js +2 -0
- package/lib/esm/view/taskManager/taskListView/taskListViewHelpers.js +37 -0
- package/lib/esm/view/taskManager/timeEntryView/epic/fetchTaskCategoriesEpic.js +24 -0
- package/lib/esm/view/taskManager/timeEntryView/epic/fetchTimeEntriesEpic.js +73 -0
- package/lib/esm/view/taskManager/timeEntryView/epic/writeTimeEntryEpic.js +77 -0
- package/lib/esm/view/taskManager/timeEntryView/timeEntryViewPayload.js +1 -0
- package/lib/esm/view/taskManager/timeEntryView/timeEntryViewReducer.js +160 -0
- package/lib/esm/view/taskManager/timeEntryView/timeEntryViewSelector.js +41 -0
- package/lib/esm/view/taskManager/timeEntryView/timeEntryViewState.js +1 -0
- package/lib/index.d.ts +5 -1
- package/lib/index.js +31 -16
- package/lib/reducer.d.ts +2 -0
- package/lib/reducer.js +6 -0
- package/lib/rootStateTypes.d.ts +4 -0
- package/lib/view/expenseAutomationView/epics/accountRecon/fetchReconciliationViewEpic.js +2 -1
- package/lib/view/taskManager/taskDetailView/taskDetailSelector.d.ts +4 -0
- package/lib/view/taskManager/taskDetailView/taskDetailSelector.js +3 -0
- package/lib/view/taskManager/taskListView/taskList.d.ts +1 -1
- package/lib/view/taskManager/taskListView/taskList.js +5 -0
- package/lib/view/taskManager/taskListView/taskListFilterHelpers.js +2 -0
- package/lib/view/taskManager/taskListView/taskListViewHelpers.d.ts +1 -1
- package/lib/view/taskManager/taskListView/taskListViewHelpers.js +37 -0
- package/lib/view/taskManager/timeEntryView/epic/fetchTaskCategoriesEpic.d.ts +18 -0
- package/lib/view/taskManager/timeEntryView/epic/fetchTaskCategoriesEpic.js +28 -0
- package/lib/view/taskManager/timeEntryView/epic/fetchTimeEntriesEpic.d.ts +23 -0
- package/lib/view/taskManager/timeEntryView/epic/fetchTimeEntriesEpic.js +78 -0
- package/lib/view/taskManager/timeEntryView/epic/writeTimeEntryEpic.d.ts +23 -0
- package/lib/view/taskManager/timeEntryView/epic/writeTimeEntryEpic.js +83 -0
- package/lib/view/taskManager/timeEntryView/timeEntryViewPayload.d.ts +27 -0
- package/lib/view/taskManager/timeEntryView/timeEntryViewPayload.js +2 -0
- package/lib/view/taskManager/timeEntryView/timeEntryViewReducer.d.ts +48 -0
- package/lib/view/taskManager/timeEntryView/timeEntryViewReducer.js +164 -0
- package/lib/view/taskManager/timeEntryView/timeEntryViewSelector.d.ts +24 -0
- package/lib/view/taskManager/timeEntryView/timeEntryViewSelector.js +46 -0
- package/lib/view/taskManager/timeEntryView/timeEntryViewState.d.ts +21 -0
- package/lib/view/taskManager/timeEntryView/timeEntryViewState.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createSlice } from '@reduxjs/toolkit';
|
|
2
|
+
import { clearAll } from '../../rootActions';
|
|
3
|
+
import { date } from '../../zeniDayJS';
|
|
4
|
+
import { toBillingType, toTimeEntrySource, } from './timeEntryState';
|
|
5
|
+
export const initialState = {
|
|
6
|
+
timeEntriesById: {},
|
|
7
|
+
taskCategoriesById: {},
|
|
8
|
+
};
|
|
9
|
+
const timeEntry = createSlice({
|
|
10
|
+
name: 'timeEntry',
|
|
11
|
+
initialState,
|
|
12
|
+
reducers: {
|
|
13
|
+
updateTimeEntries(draft, action) {
|
|
14
|
+
action.payload.forEach((payload) => {
|
|
15
|
+
const latest = mapTimeEntryPayloadToTimeEntry(payload);
|
|
16
|
+
const inStore = draft.timeEntriesById[latest.timeEntryId];
|
|
17
|
+
// Last-write-wins by updateTime, same as `address`. A weekly fetch and
|
|
18
|
+
// a single-entry edit can land in either order.
|
|
19
|
+
if (inStore == null || latest.updateTime.isAfter(inStore.updateTime)) {
|
|
20
|
+
draft.timeEntriesById[latest.timeEntryId] = latest;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
/** A soft delete comes back as a normal entry; the server has already
|
|
25
|
+
* dropped it from the totals, so the client drops it from the store
|
|
26
|
+
* rather than keeping a row it must then remember to filter. */
|
|
27
|
+
removeTimeEntry(draft, action) {
|
|
28
|
+
delete draft.timeEntriesById[action.payload];
|
|
29
|
+
},
|
|
30
|
+
updateTaskCategories(draft, action) {
|
|
31
|
+
action.payload.forEach((payload) => {
|
|
32
|
+
const category = mapTaskCategoryPayloadToTaskCategory(payload);
|
|
33
|
+
draft.taskCategoriesById[category.taskCategoryId] = category;
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
extraReducers: (builder) => {
|
|
38
|
+
builder.addCase(clearAll, (draft) => {
|
|
39
|
+
Object.assign(draft, initialState);
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
export const { updateTimeEntries, removeTimeEntry, updateTaskCategories } = timeEntry.actions;
|
|
44
|
+
export default timeEntry.reducer;
|
|
45
|
+
export function mapTimeEntryPayloadToTimeEntry(payload) {
|
|
46
|
+
return {
|
|
47
|
+
timeEntryId: payload.time_entry_id,
|
|
48
|
+
taskId: payload.task_id,
|
|
49
|
+
userId: payload.user_id,
|
|
50
|
+
minutes: payload.minutes,
|
|
51
|
+
entryDate: date(payload.entry_date),
|
|
52
|
+
source: toTimeEntrySource(payload.source),
|
|
53
|
+
createTime: date(payload.create_time),
|
|
54
|
+
updateTime: date(payload.update_time),
|
|
55
|
+
billingType: toBillingType(payload.billing_type),
|
|
56
|
+
createdBy: payload.created_by ?? undefined,
|
|
57
|
+
department: payload.department ?? undefined,
|
|
58
|
+
taskCategoryId: payload.task_category_id ?? undefined,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function mapTaskCategoryPayloadToTaskCategory(payload) {
|
|
62
|
+
return {
|
|
63
|
+
taskCategoryId: payload.task_category_id,
|
|
64
|
+
name: payload.name,
|
|
65
|
+
isActive: payload.is_active,
|
|
66
|
+
billingType: toBillingType(payload.billing_type),
|
|
67
|
+
service: payload.service ?? undefined,
|
|
68
|
+
vertical: payload.vertical ?? undefined,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import recordGet from 'lodash/get';
|
|
2
|
+
export function getTimeEntryById(timeEntryState, timeEntryId) {
|
|
3
|
+
return recordGet(timeEntryState.timeEntriesById, timeEntryId, undefined);
|
|
4
|
+
}
|
|
5
|
+
export function getTimeEntriesByIds(timeEntryState, timeEntryIds) {
|
|
6
|
+
return timeEntryIds
|
|
7
|
+
.map((timeEntryId) => getTimeEntryById(timeEntryState, timeEntryId))
|
|
8
|
+
.filter((entry) => entry != null);
|
|
9
|
+
}
|
|
10
|
+
export function getTaskCategoryById(timeEntryState, taskCategoryId) {
|
|
11
|
+
return recordGet(timeEntryState.taskCategoriesById, taskCategoryId, undefined);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The catalog, ordered service → vertical → name, matching the order the
|
|
15
|
+
* server sends so the dropdown reads the same on both sides.
|
|
16
|
+
*/
|
|
17
|
+
export function getTaskCategories(timeEntryState, includeInactive = false) {
|
|
18
|
+
return Object.values(timeEntryState.taskCategoriesById)
|
|
19
|
+
.filter((category) => includeInactive || category.isActive)
|
|
20
|
+
.sort((left, right) => (left.service ?? '').localeCompare(right.service ?? '') ||
|
|
21
|
+
(left.vertical ?? '').localeCompare(right.vertical ?? '') ||
|
|
22
|
+
left.name.localeCompare(right.name));
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { stringToUnionStrict } from '../../commonStateTypes/stringToUnion';
|
|
2
|
+
export const ALL_TIME_ENTRY_SOURCES = ['timer', 'manual', 'migration'];
|
|
3
|
+
export const ALL_BILLING_TYPES = ['billable', 'non_billable'];
|
|
4
|
+
/** Unknown values become `undefined` rather than being cast through.
|
|
5
|
+
* A server that gains a source we do not know about must not land an
|
|
6
|
+
* unmatched string in a union the UI switches on. */
|
|
7
|
+
export const toTimeEntrySource = (raw) => raw == null ? undefined : stringToUnionStrict(raw, ALL_TIME_ENTRY_SOURCES);
|
|
8
|
+
export const toBillingType = (raw) => raw == null ? undefined : stringToUnionStrict(raw, ALL_BILLING_TYPES);
|
package/lib/esm/epic.js
CHANGED
|
@@ -729,6 +729,9 @@ import { dragNDropTasksEpic, } from './view/taskManager/taskListView/epics/dragN
|
|
|
729
729
|
import { fetchTaskListEpic, } from './view/taskManager/taskListView/epics/fetchTaskListEpic';
|
|
730
730
|
import { fetchTaskListPageEpic, } from './view/taskManager/taskListView/epics/fetchTaskListPageEpic';
|
|
731
731
|
import { updateTaskFromListViewEpic, } from './view/taskManager/taskListView/epics/updateTaskFromListViewEpic';
|
|
732
|
+
import { fetchTaskCategoriesEpic, } from './view/taskManager/timeEntryView/epic/fetchTaskCategoriesEpic';
|
|
733
|
+
import { fetchTimeEntriesForRangeEpic, fetchTimeEntriesForTaskEpic, } from './view/taskManager/timeEntryView/epic/fetchTimeEntriesEpic';
|
|
734
|
+
import { deleteTimeEntryEpic, logTimeEpic, updateTimeEntryEpic, } from './view/taskManager/timeEntryView/epic/writeTimeEntryEpic';
|
|
732
735
|
import { fetchTasksCardEpic, } from './view/tasksCard/fetchTasksCardEpic';
|
|
733
736
|
import { fetchTopExEpic, } from './view/topEx/topExEpic';
|
|
734
737
|
import { fetchTransactionActivityLogEpic, } from './view/transactionActivityLogView/fetchTransactionActivityLogEpic';
|
|
@@ -880,6 +883,7 @@ const ALL_EPICS = [
|
|
|
880
883
|
deleteTagEpic,
|
|
881
884
|
deleteTaskEpic,
|
|
882
885
|
deleteTaskGroupEpic,
|
|
886
|
+
deleteTimeEntryEpic,
|
|
883
887
|
deleteTransactionAttachmentEpic,
|
|
884
888
|
deleteUserBankAccountEpic,
|
|
885
889
|
disableAccountingProjectsEpic,
|
|
@@ -1230,6 +1234,7 @@ const ALL_EPICS = [
|
|
|
1230
1234
|
fetchSubscriptionViewEpic,
|
|
1231
1235
|
fetchSubTasksEpic,
|
|
1232
1236
|
fetchSuggestedQuestionsEpic,
|
|
1237
|
+
fetchTaskCategoriesEpic,
|
|
1233
1238
|
fetchTaskDetailEpic,
|
|
1234
1239
|
fetchTaskDetailPageEpic,
|
|
1235
1240
|
fetchTaskGroupTemplatesEpic,
|
|
@@ -1238,6 +1243,8 @@ const ALL_EPICS = [
|
|
|
1238
1243
|
fetchTaskListPageEpic,
|
|
1239
1244
|
fetchTaskManagerMetricsEpic,
|
|
1240
1245
|
fetchTasksCardEpic,
|
|
1246
|
+
fetchTimeEntriesForRangeEpic,
|
|
1247
|
+
fetchTimeEntriesForTaskEpic,
|
|
1241
1248
|
fetchTopExEpic,
|
|
1242
1249
|
fetchTransactionActivityLogEpic,
|
|
1243
1250
|
fetchTransactionDetailEpic,
|
|
@@ -1330,6 +1337,7 @@ const ALL_EPICS = [
|
|
|
1330
1337
|
issueCreditNoteEpic,
|
|
1331
1338
|
lockChargeCardEpic,
|
|
1332
1339
|
lockChargeCardsEpic,
|
|
1340
|
+
logTimeEpic,
|
|
1333
1341
|
markAsCompleteScheduleDetailEpic,
|
|
1334
1342
|
markTransactionAsNotMiscategorizedEpic,
|
|
1335
1343
|
matchAndReverseAccrualsEpic,
|
|
@@ -1554,6 +1562,7 @@ const ALL_EPICS = [
|
|
|
1554
1562
|
updateStatementInfoEpic,
|
|
1555
1563
|
updateTaskFromListViewEpic,
|
|
1556
1564
|
updateTaskGroupNameEpic,
|
|
1565
|
+
updateTimeEntryEpic,
|
|
1557
1566
|
updateTransactionDetailEpic,
|
|
1558
1567
|
updateVendorContactEpic,
|
|
1559
1568
|
uploadAccountStatementEpic,
|
package/lib/esm/index.js
CHANGED
|
@@ -75,6 +75,7 @@ import { getTaskGroupById } from './entity/taskGroup/taskGroupSelector';
|
|
|
75
75
|
import { deleteConnection, doMagicLinkSignIn, doSignIn, doSignOut, fetchActiveTenant, fetchAllTenants, fetchExcludedResources as fetchExcludedResourcesForTenant, fetchExternalConnections as fetchExternalConnectionsForTenant, fetchSubscriptionSummaryForTenant, initEmailConnectOAuth, resendVerifyDeviceOTP, resetSignInState, saveAPIKeyConnection, saveConnectorCredentials, saveExternalConnection as saveExternalConnectionForTenant, saveOAuthConnection, sendEmailMagicLinkToUser, sendSessionHeartbeat, toExternalIntegrationType, updateCurrentTenant, updateSignInState, verifyDeviceWithTwoFA, } from './entity/tenant/tenantReducer';
|
|
76
76
|
import { DEFAULT_RECEIPTS_EMAIL, getCurrentTenant, getIsAccountingClassesEnabled, getIsAccountingProjectsEnabled, getIsAutoReviewIncomeWithoutCustomerEnabled, getReceiptsEmail, getTenantBaseById, getTenantBaseViewForTenantId, getTenantBaseViewForTenantView, getTenantsBaseByCheckInDateSelector, getTenantsByCheckInDateSelector, getTenantsCoreDetailsByCheckInDateSelector, isOtherProductsEnabledForTenant, isTenantBankingOnly, isTenantBookkeepingEnabled, isTenantCardsOnly, isTenantUsingZeniCOA, isZeniDomainTenant, toTenantCoreDetailsView, } from './entity/tenant/tenantSelector';
|
|
77
77
|
import { toExternalSupportedTool } from './entity/tenant/tenantTypes';
|
|
78
|
+
import { getTaskCategories, getTaskCategoryById, } from './entity/timeEntry/timeEntrySelector';
|
|
78
79
|
import { pushToastNotification } from './entity/toastNotification/toastNotificationReducer';
|
|
79
80
|
import { getLastNotificationTime, getNotifications, } from './entity/toastNotification/toastNotificationSelector';
|
|
80
81
|
import { mapFileEntityToAttachment, toContentType, toContentTypeStrict, } from './entity/transaction/stateTypes/attachment';
|
|
@@ -421,6 +422,8 @@ import { ALL_TASK_LIST_TABS, TASK_LIST_FILTER_CATEGORIES, TASK_LIST_GROUP_BY_CAT
|
|
|
421
422
|
import { bulkUpdateTaskList, dragNDropTasks, fetchTaskList, fetchTaskListPage, initiateTaskListLocalData, removeTaskFromList, updateTaskFilters, updateTaskFromListView, updateTaskListLocalData, updateTaskListSearchText, updateTaskListTab, updateTaskListUIState, } from './view/taskManager/taskListView/taskListReducer';
|
|
422
423
|
import { getAllTasks, } from './view/taskManager/taskListView/taskListSelector';
|
|
423
424
|
import { getDueDateValueFromDueDateGroupId, getTaskUpdates, sortSubtasks, } from './view/taskManager/taskListView/taskListViewHelpers';
|
|
425
|
+
import { deleteTimeEntry, fetchTaskCategories, fetchTimeEntriesForRange, fetchTimeEntriesForTask, logTime, updateTimeEntryMinutes, } from './view/taskManager/timeEntryView/timeEntryViewReducer';
|
|
426
|
+
import { getTimeEntriesForRange, getTimeEntriesForTask, getTimeEntrySaveState, } from './view/taskManager/timeEntryView/timeEntryViewSelector';
|
|
424
427
|
import { fetchTasksCard } from './view/tasksCard/tasksCardReducer';
|
|
425
428
|
import { fetchTopEx, updateTopExDownloadState, updateTopExTimeFrame, } from './view/topEx/topExReducer';
|
|
426
429
|
import { getAllTopExpenses, getTop6Expenses, getTopExpenses, } from './view/topEx/topExSelector';
|
|
@@ -678,7 +681,7 @@ export { fetchGlobalMerchantRecommendation, createGlobalMerchant, updateCreateGl
|
|
|
678
681
|
export { approveVendorGlobalReview, rejectVendorGlobalReview, fetchVendorGlobalReviewView, updateSelectedGlobalMerchant, updateVendorGlobalReviewViewUIState, getVendorGlobalReviewView, getTenantMerchantByMerchantId, toVendorGlobalReviewColumnSortKeyType, updateVendorGlobalReviewViewLocalData, };
|
|
679
682
|
export { fetchArAging, updateArAgingUIState, fetchArAgingDetail, updateArAgingDetailUIState, getArAgingReport, getArAgingDetailForCustomer, updateArAgingNodeCollapseState, };
|
|
680
683
|
export { toRecurringBillFrequency, toRecurringBillFrequencyStrict, };
|
|
681
|
-
export { getTaskGroupById, getAllTasks, fetchTaskList, fetchTaskListPage, getTaskDetail, fetchTaskDetailPage, saveTaskUpdatesToLocalStore, saveTaskDetail, createSubTask, resetSubTaskCreateStatus, fetchSubTasks, archiveTask, fetchCannedResponses, saveCannedResponse, deleteCannedResponse, getCannedResponsesView, updateTaskListSearchText, updateTaskListUIState, allTaskStatus, allTaskPriority, updateTaskFilters, TASK_LIST_FILTER_CATEGORIES, TASK_LIST_GROUP_BY_CATEGORIES, deleteTask, discardTaskUpdatesInLocalStore, bulkUpdateTaskList, createNewTaskGroup, initiateTaskListLocalData, deleteTaskGroup, updateTaskListLocalData, dragNDropTasks, updateTaskGroupName, toTaskListGroupByKeyType, toTaskListGroupByKeyTypeStrict, getTaskUpdates, sortSubtasks, fetchAllTaskGroups, initialTaskDetail, getDueDateValueFromDueDateGroupId, toPriorityCodeType, toTaskStatusCodeType, toDueDateGroupKeyType, updateTaskFromListView, updateTaskListTab, removeTaskFromList, snoozeTask, unsnoozeTask, convertHHMMStrToMinutes, initialTaskDetailLocalData, ALL_TASK_LIST_TABS, };
|
|
684
|
+
export { getTaskGroupById, getAllTasks, getTaskCategories, getTaskCategoryById, getTimeEntriesForRange, getTimeEntriesForTask, getTimeEntrySaveState, fetchTimeEntriesForTask, fetchTimeEntriesForRange, fetchTaskCategories, logTime, updateTimeEntryMinutes, deleteTimeEntry, fetchTaskList, fetchTaskListPage, getTaskDetail, fetchTaskDetailPage, saveTaskUpdatesToLocalStore, saveTaskDetail, createSubTask, resetSubTaskCreateStatus, fetchSubTasks, archiveTask, fetchCannedResponses, saveCannedResponse, deleteCannedResponse, getCannedResponsesView, updateTaskListSearchText, updateTaskListUIState, allTaskStatus, allTaskPriority, updateTaskFilters, TASK_LIST_FILTER_CATEGORIES, TASK_LIST_GROUP_BY_CATEGORIES, deleteTask, discardTaskUpdatesInLocalStore, bulkUpdateTaskList, createNewTaskGroup, initiateTaskListLocalData, deleteTaskGroup, updateTaskListLocalData, dragNDropTasks, updateTaskGroupName, toTaskListGroupByKeyType, toTaskListGroupByKeyTypeStrict, getTaskUpdates, sortSubtasks, fetchAllTaskGroups, initialTaskDetail, getDueDateValueFromDueDateGroupId, toPriorityCodeType, toTaskStatusCodeType, toDueDateGroupKeyType, updateTaskFromListView, updateTaskListTab, removeTaskFromList, snoozeTask, unsnoozeTask, convertHHMMStrToMinutes, initialTaskDetailLocalData, ALL_TASK_LIST_TABS, };
|
|
682
685
|
export { getAllTags, fetchTagList, createTag, deleteTag, };
|
|
683
686
|
export { getAllCardsAndBankPaymentMethods, createCardSetupIntent, confirmCardSetupIntent, addCardPaymentSource, fetchPaymentSources, resetCardPaymentErrorStatuses, clearCardPaymentView, };
|
|
684
687
|
export { getAuditReportGroupViewSelectorView, getAuditRuleGroupViewSelectorView, getUserFromAllUsers, fetchAuditRuleGroupView, fetchAuditReportGroupView, saveReasonForAuditRule, clearAuditReportGroupViewByCompanyId, };
|
package/lib/esm/reducer.js
CHANGED
|
@@ -70,6 +70,7 @@ import taskGroup, { initialState as initialTaskGroupState, } from './entity/task
|
|
|
70
70
|
import taskGroupTemplate, { initialState as initialTaskGroupTemplateState, } from './entity/taskGroupTemplate/taskGroupTemplateReducer';
|
|
71
71
|
import taskSummary, { initialState as initailTaskSummaryState, } from './entity/taskSummary/taskSummaryReducer';
|
|
72
72
|
import tenant, { initialState as initialTenantState, } from './entity/tenant/tenantReducer';
|
|
73
|
+
import timeEntry, { initialState as initialTimeEntryState, } from './entity/timeEntry/timeEntryReducer';
|
|
73
74
|
import toastNotification, { initialState as initialToastNotificationState, } from './entity/toastNotification/toastNotificationReducer';
|
|
74
75
|
import transaction, { initialState as initialTransactionState, } from './entity/transaction/transactionReducer';
|
|
75
76
|
import transactionActivityLog, { initialState as initialTransactionActivityLogState, } from './entity/transactionActivityLog/transactionActivityLogReducer';
|
|
@@ -255,6 +256,7 @@ import taskDetailView, { initialState as initialTaskDetailState, } from './view/
|
|
|
255
256
|
import taskGroupTemplateView, { initialState as initialTaskGroupTemplateViewState, } from './view/taskManager/taskGroupTemplateView/taskGroupTemplateViewReducer';
|
|
256
257
|
import taskGroupView, { initialState as initialTaskGroupViewState, } from './view/taskManager/taskGroupView/taskGroupViewReducer';
|
|
257
258
|
import taskList, { initialState as initialTaskListState, } from './view/taskManager/taskListView/taskListReducer';
|
|
259
|
+
import timeEntryView, { initialState as initialTimeEntryViewState, } from './view/taskManager/timeEntryView/timeEntryViewReducer';
|
|
258
260
|
import tasksCard, { initialState as initialTasksCardState, } from './view/tasksCard/tasksCardReducer';
|
|
259
261
|
import topEx, { initialState as initialTopExState, } from './view/topEx/topExReducer';
|
|
260
262
|
import transactionActivityLogView, { initialState as initialTransactionActivityLogViewState, } from './view/transactionActivityLogView/transactionActivityLogViewReducer';
|
|
@@ -343,6 +345,7 @@ const initialEntitiesState = {
|
|
|
343
345
|
subscriptionState: initialSubscriptionState,
|
|
344
346
|
subscriptionSummaryState: initialSubscriptionSummaryState,
|
|
345
347
|
tagState: initialTagState,
|
|
348
|
+
timeEntryState: initialTimeEntryState,
|
|
346
349
|
taskGroupState: initialTaskGroupState,
|
|
347
350
|
taskGroupTemplateState: initialTaskGroupTemplateState,
|
|
348
351
|
taskState: initialTaskState,
|
|
@@ -524,6 +527,7 @@ const initialViewsState = {
|
|
|
524
527
|
taskGroupTemplateViewState: initialTaskGroupTemplateViewState,
|
|
525
528
|
taskGroupViewState: initialTaskGroupViewState,
|
|
526
529
|
taskListState: initialTaskListState,
|
|
530
|
+
timeEntryViewState: initialTimeEntryViewState,
|
|
527
531
|
tasksCardState: initialTasksCardState,
|
|
528
532
|
topExState: initialTopExState,
|
|
529
533
|
transactionActivityLogViewState: initialTransactionActivityLogViewState,
|
|
@@ -632,6 +636,7 @@ const entityReducers = {
|
|
|
632
636
|
subscriptionState: subscription,
|
|
633
637
|
subscriptionSummaryState: subscriptionSummary,
|
|
634
638
|
tagState: tag,
|
|
639
|
+
timeEntryState: timeEntry,
|
|
635
640
|
taskGroupState: taskGroup,
|
|
636
641
|
taskGroupTemplateState: taskGroupTemplate,
|
|
637
642
|
taskState: task,
|
|
@@ -812,6 +817,7 @@ const viewReducers = {
|
|
|
812
817
|
taskGroupTemplateViewState: taskGroupTemplateView,
|
|
813
818
|
taskGroupViewState: taskGroupView,
|
|
814
819
|
taskListState: taskList,
|
|
820
|
+
timeEntryViewState: timeEntryView,
|
|
815
821
|
tagViewState: tagView,
|
|
816
822
|
tasksCardState: tasksCard,
|
|
817
823
|
topExState: topEx,
|
package/lib/esm/view/expenseAutomationView/epics/accountRecon/fetchReconciliationViewEpic.js
CHANGED
|
@@ -26,9 +26,10 @@ export const fetchReconciliationViewEpic = (actions$, state$, zeniAPI) => action
|
|
|
26
26
|
}
|
|
27
27
|
let fetchState;
|
|
28
28
|
if (accountId != null) {
|
|
29
|
+
// Period bucket can be unhydrated during app-shell load.
|
|
29
30
|
fetchState =
|
|
30
31
|
accountReconciliationRecords[toMonthYearPeriodId(selectedPeriod)]
|
|
31
|
-
|
|
32
|
+
?.reconciliationByAccountID[accountId]?.transactionFetchState
|
|
32
33
|
.fetchState;
|
|
33
34
|
}
|
|
34
35
|
else {
|
|
@@ -26,6 +26,7 @@ export const getTaskDetail = (state, taskId) => {
|
|
|
26
26
|
let isDeleted = undefined;
|
|
27
27
|
let depth = undefined;
|
|
28
28
|
let recurringSourceTaskId = undefined;
|
|
29
|
+
let taskCategoryId = undefined;
|
|
29
30
|
let snoozedUntil = undefined;
|
|
30
31
|
let taskGroupId = undefined;
|
|
31
32
|
let subtasks = [];
|
|
@@ -39,6 +40,7 @@ export const getTaskDetail = (state, taskId) => {
|
|
|
39
40
|
if (taskEntity != null) {
|
|
40
41
|
createdByUser = getUserAndUserRole(userState, userRoleState, addressState, taskEntity.createdBy);
|
|
41
42
|
depth = taskEntity.depth;
|
|
43
|
+
taskCategoryId = taskEntity.taskCategoryId;
|
|
42
44
|
isArchived = taskEntity.isArchived;
|
|
43
45
|
isDeleted = taskEntity.isDeleted;
|
|
44
46
|
recurringSourceTaskId = taskEntity.recurringSourceTaskId;
|
|
@@ -103,6 +105,7 @@ export const getTaskDetail = (state, taskId) => {
|
|
|
103
105
|
version: 0,
|
|
104
106
|
createdByUser,
|
|
105
107
|
depth,
|
|
108
|
+
taskCategoryId,
|
|
106
109
|
isArchived,
|
|
107
110
|
isDeleted,
|
|
108
111
|
recurringSourceTaskId,
|
|
@@ -47,6 +47,11 @@ export const TASK_LIST_FILTER_CATEGORIES = [
|
|
|
47
47
|
type: 'user',
|
|
48
48
|
multiple: true,
|
|
49
49
|
},
|
|
50
|
+
{
|
|
51
|
+
value: 'createdBy',
|
|
52
|
+
type: 'user',
|
|
53
|
+
multiple: true,
|
|
54
|
+
},
|
|
50
55
|
{ value: 'creationDate', type: 'dateRange', multiple: true },
|
|
51
56
|
];
|
|
52
57
|
export const TASK_LIST_GROUP_BY_CATEGORIES = [
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { convertHHMMStrToMinutes } from '../../../commonStateTypes/fiscalYearHelpers/formatMinutesToFromHHMM';
|
|
1
2
|
import { dateNow } from '../../../zeniDayJS';
|
|
2
3
|
import { allTaskPriority, allTaskStatus, } from '../taskDetailView/taskDetailSelector';
|
|
3
4
|
import { toDueDateGroupKeyType } from './taskList';
|
|
@@ -63,6 +64,11 @@ export const sortSubtasks = (subtasks, sortKey, sortOrder, userList, tagList) =>
|
|
|
63
64
|
return acc;
|
|
64
65
|
}, {});
|
|
65
66
|
const unknownStatusRank = allTaskStatus.length;
|
|
67
|
+
const priorityOrder = allTaskPriority.reduce((acc, priority, index) => {
|
|
68
|
+
acc[priority.code] = index;
|
|
69
|
+
return acc;
|
|
70
|
+
}, {});
|
|
71
|
+
const unknownPriorityRank = allTaskPriority.length;
|
|
66
72
|
const cmpStr = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' });
|
|
67
73
|
const keyFn = (task) => {
|
|
68
74
|
if (sortKey === 'name') {
|
|
@@ -89,6 +95,37 @@ export const sortSubtasks = (subtasks, sortKey, sortOrder, userList, tagList) =>
|
|
|
89
95
|
const tag = tagList.find((candidate) => candidate.tagId === firstTagId);
|
|
90
96
|
return tag?.name ?? '';
|
|
91
97
|
}
|
|
98
|
+
if (sortKey === 'priority') {
|
|
99
|
+
// Rank by the canonical order, not the code string: alphabetical would
|
|
100
|
+
// sort "high" before "urgent" and read as wrong to anyone scanning it.
|
|
101
|
+
return priorityOrder[task.priority?.code] ?? unknownPriorityRank;
|
|
102
|
+
}
|
|
103
|
+
if (sortKey === 'dueDate') {
|
|
104
|
+
// A task with no date is not "earliest", so undated rows belong at the
|
|
105
|
+
// bottom in BOTH directions. The comparator negates for 'desc', so one
|
|
106
|
+
// fixed sentinel would flip them to the top — the sentinel has to flip
|
|
107
|
+
// with the order to survive that negation.
|
|
108
|
+
// `dueDate` is already a ZeniDate (dayjs), so valueOf() is the epoch.
|
|
109
|
+
// An unparseable date yields NaN, and a NaN comparator leaves the array
|
|
110
|
+
// untouched -- the same silent failure the timeSpent branch guards.
|
|
111
|
+
// Treat it as undated rather than as a date.
|
|
112
|
+
const due = task.dueDate;
|
|
113
|
+
const epoch = due?.valueOf();
|
|
114
|
+
if (epoch == null || Number.isNaN(epoch)) {
|
|
115
|
+
return sortOrder === 'asc'
|
|
116
|
+
? Number.MAX_SAFE_INTEGER
|
|
117
|
+
: Number.MIN_SAFE_INTEGER;
|
|
118
|
+
}
|
|
119
|
+
return epoch;
|
|
120
|
+
}
|
|
121
|
+
if (sortKey === 'timeSpent') {
|
|
122
|
+
// `timeSpent` is an "HHMM" STRING (no colon). Comparing it as text puts
|
|
123
|
+
// "1000" before "0900" only by luck of padding, and any malformed value
|
|
124
|
+
// makes the converter return NaN -- which a comparator turns into "no
|
|
125
|
+
// sort at all", silently. Coerce that to 0.
|
|
126
|
+
const minutes = convertHHMMStrToMinutes(task.timeSpent ?? '');
|
|
127
|
+
return Number.isFinite(minutes) ? minutes : 0;
|
|
128
|
+
}
|
|
92
129
|
return '';
|
|
93
130
|
};
|
|
94
131
|
const copy = [...subtasks];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, switchMap } from 'rxjs/operators';
|
|
3
|
+
import { mergeMap } from 'rxjs/operators';
|
|
4
|
+
import { updateTaskCategories } from '../../../../entity/timeEntry/timeEntryReducer';
|
|
5
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
6
|
+
import { fetchTaskCategories, fetchTaskCategoriesFailure, fetchTaskCategoriesSuccess, } from '../timeEntryViewReducer';
|
|
7
|
+
/**
|
|
8
|
+
* The 156-category catalog that fills the Category dropdown.
|
|
9
|
+
*
|
|
10
|
+
* `switchMap` here, unlike the entry epics: there is one catalog, so a second
|
|
11
|
+
* request supersedes the first rather than racing it.
|
|
12
|
+
*
|
|
13
|
+
* Zeni-only on the server (the billing marker is commercially sensitive), so
|
|
14
|
+
* a customer user gets a 401 — surfaced as Error rather than an empty list,
|
|
15
|
+
* because an empty catalog looks like "no categories exist".
|
|
16
|
+
*/
|
|
17
|
+
export const fetchTaskCategoriesEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchTaskCategories.match), switchMap(() => zeniAPI
|
|
18
|
+
.getJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/task-categories`)
|
|
19
|
+
.pipe(mergeMap((response) => {
|
|
20
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
21
|
+
return of(updateTaskCategories(response.data.task_categories ?? []), fetchTaskCategoriesSuccess());
|
|
22
|
+
}
|
|
23
|
+
return of(fetchTaskCategoriesFailure(response.status));
|
|
24
|
+
}), catchError((error) => of(fetchTaskCategoriesFailure(createZeniAPIStatus('Unexpected Error', 'Fetch task categories errored out' + JSON.stringify(error))))))));
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
|
+
import { updateTimeEntries } from '../../../../entity/timeEntry/timeEntryReducer';
|
|
4
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
5
|
+
import { fetchTimeEntriesForRange, fetchTimeEntriesForRangeFailure, fetchTimeEntriesForRangeSuccess, fetchTimeEntriesForTask, fetchTimeEntriesForTaskFailure, fetchTimeEntriesForTaskSuccess, } from '../timeEntryViewReducer';
|
|
6
|
+
import { toTimeEntryRangeKey } from '../timeEntryViewState';
|
|
7
|
+
/**
|
|
8
|
+
* Every entry on one task, all users — the task detail breakdown.
|
|
9
|
+
*
|
|
10
|
+
* `mergeMap`, not `switchMap`: a detail page and an expanded board row can
|
|
11
|
+
* ask for two different tasks at once, and cancelling the first would leave
|
|
12
|
+
* its breakdown stuck In-Progress forever.
|
|
13
|
+
*/
|
|
14
|
+
export const fetchTimeEntriesForTaskEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(fetchTimeEntriesForTask.match), mergeMap((action) => {
|
|
15
|
+
const { taskId, cacheOverride } = action.payload;
|
|
16
|
+
const cached = state$.value.timeEntryViewState.timeEntryIdsByTaskId[taskId];
|
|
17
|
+
const status = state$.value.timeEntryViewState.fetchStateByTaskId[taskId];
|
|
18
|
+
// The reducer has already flipped this request to In-Progress, so the
|
|
19
|
+
// guard reads the COMPLETED cache, never the fetch state -- a fetch
|
|
20
|
+
// state is not an in-flight signal.
|
|
21
|
+
if (cacheOverride !== true &&
|
|
22
|
+
cached != null &&
|
|
23
|
+
status?.fetchState === 'In-Progress') {
|
|
24
|
+
return of(fetchTimeEntriesForTaskSuccess({ taskId, timeEntryIds: cached }));
|
|
25
|
+
}
|
|
26
|
+
return zeniAPI
|
|
27
|
+
.getJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/tasks/${taskId}/time-entries`)
|
|
28
|
+
.pipe(mergeMap((response) => {
|
|
29
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
30
|
+
const entries = response.data.time_entries ?? [];
|
|
31
|
+
return of(updateTimeEntries(entries), fetchTimeEntriesForTaskSuccess({
|
|
32
|
+
taskId,
|
|
33
|
+
timeEntryIds: entries.map((entry) => entry.time_entry_id),
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
return of(fetchTimeEntriesForTaskFailure({ taskId, status: response.status }));
|
|
37
|
+
}), catchError((error) => of(fetchTimeEntriesForTaskFailure({
|
|
38
|
+
taskId,
|
|
39
|
+
status: createZeniAPIStatus('Unexpected Error', 'Fetch time entries for task errored out' +
|
|
40
|
+
JSON.stringify(error)),
|
|
41
|
+
}))));
|
|
42
|
+
}));
|
|
43
|
+
/**
|
|
44
|
+
* One user's entries in an inclusive date range — the weekly timesheet.
|
|
45
|
+
*
|
|
46
|
+
* Needs the `is_deleted + user_id + entry_date` composite index (api #593).
|
|
47
|
+
* Without it the server returns 500, not an empty list, so the failure is
|
|
48
|
+
* surfaced rather than rendered as a blank week.
|
|
49
|
+
*/
|
|
50
|
+
export const fetchTimeEntriesForRangeEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(fetchTimeEntriesForRange.match), mergeMap((action) => {
|
|
51
|
+
const { userId, startDate, endDate } = action.payload;
|
|
52
|
+
const rangeKey = toTimeEntryRangeKey(userId, startDate, endDate);
|
|
53
|
+
const query = `user_id=${encodeURIComponent(userId)}&start_date=${encodeURIComponent(startDate)}&end_date=${encodeURIComponent(endDate)}`;
|
|
54
|
+
return zeniAPI
|
|
55
|
+
.getJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/time-entries?${query}`)
|
|
56
|
+
.pipe(mergeMap((response) => {
|
|
57
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
58
|
+
const entries = response.data.time_entries ?? [];
|
|
59
|
+
return of(updateTimeEntries(entries), fetchTimeEntriesForRangeSuccess({
|
|
60
|
+
rangeKey,
|
|
61
|
+
timeEntryIds: entries.map((entry) => entry.time_entry_id),
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
return of(fetchTimeEntriesForRangeFailure({
|
|
65
|
+
rangeKey,
|
|
66
|
+
status: response.status,
|
|
67
|
+
}));
|
|
68
|
+
}), catchError((error) => of(fetchTimeEntriesForRangeFailure({
|
|
69
|
+
rangeKey,
|
|
70
|
+
status: createZeniAPIStatus('Unexpected Error', 'Fetch time entries for range errored out' +
|
|
71
|
+
JSON.stringify(error)),
|
|
72
|
+
}))));
|
|
73
|
+
}));
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
import { catchError, filter, mergeMap } from 'rxjs/operators';
|
|
3
|
+
import { updateTaskTimeSpentMinutes } from '../../../../entity/task/taskReducer';
|
|
4
|
+
import { removeTimeEntry, updateTimeEntries, } from '../../../../entity/timeEntry/timeEntryReducer';
|
|
5
|
+
import { getTimeEntryById } from '../../../../entity/timeEntry/timeEntrySelector';
|
|
6
|
+
import { createZeniAPIStatus, isSuccessResponse, } from '../../../../responsePayload';
|
|
7
|
+
import { deleteTimeEntry, logTime, logTimeFailure, logTimeSuccess, timeEntryWriteFailure, timeEntryWriteSuccess, updateTimeEntryMinutes, } from '../timeEntryViewReducer';
|
|
8
|
+
const unexpected = (what, error) => createZeniAPIStatus('Unexpected Error', `${what} errored out${JSON.stringify(error)}`);
|
|
9
|
+
/**
|
|
10
|
+
* Log time against a task.
|
|
11
|
+
*
|
|
12
|
+
* `mergeMap`, not `switchMap`: two rows on the board can be edited in quick
|
|
13
|
+
* succession and cancelling the first would silently drop that entry.
|
|
14
|
+
*/
|
|
15
|
+
export const logTimeEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(logTime.match), mergeMap((action) => {
|
|
16
|
+
const { taskId, minutes, entryDate, source, identifier } = action.payload;
|
|
17
|
+
return zeniAPI
|
|
18
|
+
.postAndGetJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/tasks/${taskId}/time-entries`, {
|
|
19
|
+
minutes,
|
|
20
|
+
entry_date: entryDate,
|
|
21
|
+
source,
|
|
22
|
+
// Sent only when the caller has one. The server treats it as the
|
|
23
|
+
// entity key, which is what makes a retried timer stop idempotent.
|
|
24
|
+
...(identifier != null ? { identifier } : {}),
|
|
25
|
+
})
|
|
26
|
+
.pipe(mergeMap((response) => {
|
|
27
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
28
|
+
const { time_entry, task_time_spent_minutes } = response.data;
|
|
29
|
+
return of(updateTimeEntries([time_entry]),
|
|
30
|
+
// The board reads `Task.time_spent` per row, so it has to be
|
|
31
|
+
// told the new total; otherwise the row keeps the old number
|
|
32
|
+
// until something refetches the whole list.
|
|
33
|
+
updateTaskTimeSpentMinutes({
|
|
34
|
+
taskId,
|
|
35
|
+
minutes: task_time_spent_minutes ?? 0,
|
|
36
|
+
}), logTimeSuccess({ taskId, timeEntryId: time_entry.time_entry_id }));
|
|
37
|
+
}
|
|
38
|
+
return of(logTimeFailure({ taskId, status: response.status }));
|
|
39
|
+
}), catchError((error) => of(logTimeFailure({ taskId, status: unexpected('Log time', error) }))));
|
|
40
|
+
}));
|
|
41
|
+
/** Correct an entry's minutes. Owner, or a Zeni user as the admin path. */
|
|
42
|
+
export const updateTimeEntryEpic = (actions$, _state$, zeniAPI) => actions$.pipe(filter(updateTimeEntryMinutes.match), mergeMap((action) => {
|
|
43
|
+
const { timeEntryId, minutes } = action.payload;
|
|
44
|
+
return zeniAPI
|
|
45
|
+
.putAndGetJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/time-entries/${timeEntryId}`, { minutes })
|
|
46
|
+
.pipe(mergeMap((response) => {
|
|
47
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
48
|
+
return of(updateTimeEntries([response.data.time_entry]), timeEntryWriteSuccess({ timeEntryId }));
|
|
49
|
+
}
|
|
50
|
+
return of(timeEntryWriteFailure({ timeEntryId, status: response.status }));
|
|
51
|
+
}), catchError((error) => of(timeEntryWriteFailure({
|
|
52
|
+
timeEntryId,
|
|
53
|
+
status: unexpected('Update time entry', error),
|
|
54
|
+
}))));
|
|
55
|
+
}));
|
|
56
|
+
/**
|
|
57
|
+
* Soft delete. The row stays in Datastore for the audit trail but leaves the
|
|
58
|
+
* totals, so the client drops it from the store rather than keeping something
|
|
59
|
+
* every reader would then have to remember to filter.
|
|
60
|
+
*/
|
|
61
|
+
export const deleteTimeEntryEpic = (actions$, state$, zeniAPI) => actions$.pipe(filter(deleteTimeEntry.match), mergeMap((action) => {
|
|
62
|
+
const timeEntryId = action.payload;
|
|
63
|
+
// Read the task id BEFORE the delete: the entry is about to leave the
|
|
64
|
+
// store, and the view slice needs to know which task's id list to prune.
|
|
65
|
+
const taskId = getTimeEntryById(state$.value.timeEntryState, timeEntryId)?.taskId;
|
|
66
|
+
return zeniAPI
|
|
67
|
+
.deleteAndGetJSON(`${zeniAPI.apiEndPoints.taskMicroServiceBaseUrl}/1.0/task-manager/time-entries/${timeEntryId}`)
|
|
68
|
+
.pipe(mergeMap((response) => {
|
|
69
|
+
if (isSuccessResponse(response)) {
|
|
70
|
+
return of(removeTimeEntry(timeEntryId), timeEntryWriteSuccess({ timeEntryId, removedFromTaskId: taskId }));
|
|
71
|
+
}
|
|
72
|
+
return of(timeEntryWriteFailure({ timeEntryId, status: response.status }));
|
|
73
|
+
}), catchError((error) => of(timeEntryWriteFailure({
|
|
74
|
+
timeEntryId,
|
|
75
|
+
status: unexpected('Delete time entry', error),
|
|
76
|
+
}))));
|
|
77
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|