@zeniai/client-epic-state 5.3.7-betaRD0 → 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/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/tenant/epic/fetchAllTenantsEpic.js +38 -45
- 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/task/taskPayload.js +1 -0
- package/lib/esm/entity/task/taskReducer.js +18 -1
- package/lib/esm/entity/tenant/epic/fetchAllTenantsEpic.js +39 -46
- 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 +6 -3
- package/lib/esm/init.js +3 -14
- package/lib/esm/reducer.js +6 -0
- 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 +7 -4
- package/lib/index.js +66 -53
- package/lib/init.d.ts +0 -9
- package/lib/init.js +4 -17
- package/lib/reducer.d.ts +2 -0
- package/lib/reducer.js +6 -0
- package/lib/rootStateTypes.d.ts +4 -0
- 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/lib/zeniAPI.d.ts +2 -2
- package/package.json +1 -26
- package/lib/esm/sessionEndpointsLoader.js +0 -7
- package/lib/sessionEndpointsLoader.d.ts +0 -9
- package/lib/sessionEndpointsLoader.js +0 -11
- package/lib/tsconfig.mocks.tsbuildinfo +0 -1
- package/lib/tsconfig.typecheck.tsbuildinfo +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createSlice } from '@reduxjs/toolkit';
|
|
2
|
+
import { convertMinutesToHHMM } from '../../commonStateTypes/fiscalYearHelpers/formatMinutesToFromHHMM';
|
|
2
3
|
import { clearAll } from '../../rootActions';
|
|
3
4
|
import { mapTaskPayloadToTask } from './taskPayload';
|
|
4
5
|
export const initialState = {
|
|
@@ -23,6 +24,22 @@ const task = createSlice({
|
|
|
23
24
|
removeTask(draft, action) {
|
|
24
25
|
delete draft.taskByID[action.payload];
|
|
25
26
|
},
|
|
27
|
+
/**
|
|
28
|
+
* Set one task's rolled-up total from the minutes the time-entry API
|
|
29
|
+
* returns. A whole-task update is not available on that path -- the write
|
|
30
|
+
* response carries the recomputed minutes, not a TaskPayload -- and
|
|
31
|
+
* refetching the task just to learn one number would put a round trip on
|
|
32
|
+
* the keystroke that logged the time.
|
|
33
|
+
*
|
|
34
|
+
* No-op when the task is absent: the ancestors the server recomputed can
|
|
35
|
+
* include a parent that is not on screen and so not in the store.
|
|
36
|
+
*/
|
|
37
|
+
updateTaskTimeSpentMinutes(draft, action) {
|
|
38
|
+
const taskInStore = draft.taskByID[action.payload.taskId];
|
|
39
|
+
if (taskInStore != null) {
|
|
40
|
+
taskInStore.timeSpent = convertMinutesToHHMM(action.payload.minutes);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
26
43
|
appendSubTaskId(draft, action) {
|
|
27
44
|
const parentTask = draft.taskByID[action.payload.parentTaskId];
|
|
28
45
|
if (parentTask != null) {
|
|
@@ -53,5 +70,5 @@ const task = createSlice({
|
|
|
53
70
|
});
|
|
54
71
|
},
|
|
55
72
|
});
|
|
56
|
-
export const { updateTasks, updateTask, removeTask, appendSubTaskId, setSubTaskIds, } = task.actions;
|
|
73
|
+
export const { updateTasks, updateTask, removeTask, updateTaskTimeSpentMinutes, appendSubTaskId, setSubTaskIds, } = task.actions;
|
|
57
74
|
export default task.reducer;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { from, of } from 'rxjs';
|
|
2
2
|
import { catchError, filter, mergeMap, switchMap } from 'rxjs/operators';
|
|
3
3
|
import { toMonth } from '../../../commonStateTypes/timePeriod';
|
|
4
4
|
import { createZeniAPIStatus, isSuccessResponse } from '../../../responsePayload';
|
|
5
|
-
import { getSessionEndpointsLoader } from '../../../sessionEndpointsLoader';
|
|
6
5
|
import { initializeSelectedPeriodForTenants } from '../../../view/expenseAutomationView/expenseAutomationViewReducer';
|
|
7
6
|
import { date, dateNow } from '../../../zeniDayJS';
|
|
8
7
|
import { updateCompanies } from '../../company/companyReducer';
|
|
@@ -11,54 +10,48 @@ export const fetchAllTenantsEpic = (actions$, state, zeniAPI) => {
|
|
|
11
10
|
const queryParam = {
|
|
12
11
|
is_include_companies_in_response: true,
|
|
13
12
|
};
|
|
14
|
-
return actions$.pipe(filter(fetchAllTenants.match), switchMap((action) =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
13
|
+
return actions$.pipe(filter(fetchAllTenants.match), switchMap((action) => zeniAPI
|
|
14
|
+
.getJSON(`${zeniAPI.apiEndPoints.tenantMicroServiceBaseUrl}/1.0/users/${action.payload.userId}/tenants?query=${encodeURIComponent(JSON.stringify(queryParam))}`, Object.assign({ 'zeni-user-id': action.payload.userId }, { 'zeni-tenant-id': '' }, action.payload.zeniSessionId != null
|
|
15
|
+
? { 'zeni-session-id': action.payload.zeniSessionId }
|
|
16
|
+
: {}))
|
|
17
|
+
.pipe(mergeMap((response) => {
|
|
18
|
+
const currentTenantId = state.value.tenantState.currentTenantId;
|
|
19
|
+
if (isSuccessResponse(response) && response.data != null) {
|
|
20
|
+
const defaultSelectedPeriodsForTenants = getDefaultSelectedPeriodsForTenants(response.data);
|
|
21
|
+
let actions = [
|
|
22
|
+
updateTenantsSuccess(response.data),
|
|
23
|
+
initializeSelectedPeriodForTenants({
|
|
24
|
+
selectedPeriodByTenantId: defaultSelectedPeriodsForTenants,
|
|
25
|
+
}),
|
|
26
|
+
];
|
|
27
|
+
if (response.data.companies != null) {
|
|
28
|
+
let companiesPayload = response.data.companies;
|
|
29
|
+
if (currentTenantId != null) {
|
|
30
|
+
// filter out this tenant ID related company detail
|
|
31
|
+
// Find companyID for this tenant
|
|
32
|
+
const currentTenantPayload = response.data.tenants.find((tenant) => tenant.tenant_id === currentTenantId);
|
|
33
|
+
if (currentTenantPayload != null) {
|
|
34
|
+
companiesPayload = response.data.companies.filter((company) => company.company_id !== currentTenantPayload.company_id);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.warn('Selected tenant record not found in response', currentTenantId);
|
|
39
38
|
}
|
|
40
|
-
actions = [
|
|
41
|
-
updateCompanies({
|
|
42
|
-
payload: companiesPayload,
|
|
43
|
-
schema: {},
|
|
44
|
-
}),
|
|
45
|
-
...actions,
|
|
46
|
-
];
|
|
47
39
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
actions = [
|
|
41
|
+
updateCompanies({
|
|
42
|
+
payload: companiesPayload,
|
|
43
|
+
schema: {},
|
|
44
|
+
}),
|
|
45
|
+
...actions,
|
|
46
|
+
];
|
|
52
47
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return fetchTenants$;
|
|
48
|
+
return from(actions);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return of(updateTenantsFailure(response.status));
|
|
58
52
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}));
|
|
53
|
+
}), catchError((error) => of(updateTenantsFailure(createZeniAPIStatus('Unexpected error', 'Fetch All Tenants REST API call errored out' +
|
|
54
|
+
JSON.stringify(error))))))));
|
|
62
55
|
};
|
|
63
56
|
export const defaultMonthYearPeriod = {
|
|
64
57
|
month: toMonth(dateNow().get('month') + 1),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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';
|
|
@@ -90,7 +91,7 @@ import { getUserRoleMap, hasAICFOAccess, hasAdminLevelAccess, hasAnyCompanyRole,
|
|
|
90
91
|
import { updateVendors } from './entity/vendor/vendorReducer';
|
|
91
92
|
import { defaultVendorCurrency, } from './entity/vendor/vendorState';
|
|
92
93
|
import { VendorExpenseTrend } from './entity/vendorExpense/vendorExpenseTrend';
|
|
93
|
-
import initialize, { disconnectPusher, initializePusher, initializeWithNewStore, injectFeatureEpics, injectFeatureModules,
|
|
94
|
+
import initialize, { disconnectPusher, initializePusher, initializeWithNewStore, injectFeatureEpics, injectFeatureModules, updateZeniAPIClientConfig, zeniAPIClientConfig, } from './init';
|
|
94
95
|
import { isAccessDeniedError, isInvalidSessionError, isSuccessResponse, } from './responsePayload';
|
|
95
96
|
import { clearAll } from './rootActions';
|
|
96
97
|
import { fetchAccountList } from './view/accountList/accountListReducer';
|
|
@@ -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';
|
|
@@ -485,7 +488,7 @@ export { updateCreditAgentMacro, updateCreditAgentRow, updateCreditAgentRows, }
|
|
|
485
488
|
export { getCreditAgentRows, getCreditAgentMacroEntity, } from './entity/creditAgent/creditAgentSelector';
|
|
486
489
|
export { saveJeAccountSettings, saveJeAccountSettingsLocalData };
|
|
487
490
|
export default initialize;
|
|
488
|
-
export { initializePusher, disconnectPusher, Dayjs, dateInLocal as zeniDateInLocal, date as zeniDate, dateLocal, dateNow, getBusinessDayOfDate, formatZeniDateFY as formatZeniDate, getFYMonths, getFYQuarterAndYear, getStartOfAndEndOfTimeframeForFY, getActualPeriodOfFYQtr, getActualPeriodOfFY, getLocalTimezone, addPeriod, subtractPeriod, setLocalTimezone, updateZeniDateLocaleID, toZeniUrl, toZeniUrlWithoutBaseURL, updateZeniAPIClientConfig,
|
|
491
|
+
export { initializePusher, disconnectPusher, Dayjs, dateInLocal as zeniDateInLocal, date as zeniDate, dateLocal, dateNow, getBusinessDayOfDate, formatZeniDateFY as formatZeniDate, getFYMonths, getFYQuarterAndYear, getStartOfAndEndOfTimeframeForFY, getActualPeriodOfFYQtr, getActualPeriodOfFY, getLocalTimezone, addPeriod, subtractPeriod, setLocalTimezone, updateZeniDateLocaleID, toZeniUrl, toZeniUrlWithoutBaseURL, updateZeniAPIClientConfig, zeniAPIClientConfig, injectFeatureEpics, injectFeatureModules, initializeWithNewStore, getTimeframeTickTag, isIncompleteMonthPresentInTimeframeTick, getLastMonthOfFYQuarter, getLastMonthOfFYYear, getMonthIndex, dateFromYearMonthDate, getMinDate, };
|
|
489
492
|
export { toAmount };
|
|
490
493
|
export { toReportID, toReportFormatStrict, isCashFlowOrBalanceSheetReport, isBalanceSheetReport, isBalanceSheetClassesViewReport, isBalanceSheetProjectViewReport, isCashFlowClassesViewReport, isCashFlowProjectViewReport, isCashFlowReport, isGroupedCashFlowReport, isGroupedBalanceSheetReport, isDashboardReport, isDashboardClassesViewReport, isDashboardProjectViewReport, isPAndLReport, isPAndLClassesViewReport, isPAndLProjectViewReport, isAgingReport, isOpExByVendorReport, isFluxAnalysisOpExReport, };
|
|
491
494
|
export { toCOABalanceTimeframe, };
|
|
@@ -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/init.js
CHANGED
|
@@ -2,7 +2,6 @@ import configureNewStore from './configureStore';
|
|
|
2
2
|
import { ZeniAPIClient } from './zeniAPI';
|
|
3
3
|
import { setZeniAPIClient, zeniAPI } from './zeniAPIClientState';
|
|
4
4
|
export { zeniAPI } from './zeniAPIClientState';
|
|
5
|
-
export { setSessionEndpointsLoader } from './sessionEndpointsLoader';
|
|
6
5
|
export { disconnectPusher, initializePusher, pusher } from './pusherClient';
|
|
7
6
|
export let store;
|
|
8
7
|
let initialized = false;
|
|
@@ -14,10 +13,11 @@ let initialized = false;
|
|
|
14
13
|
*/
|
|
15
14
|
export default function initialize(restEndPoints, userId, sessionId, tenantId, isSandboxEnv, onSessionInvalidated) {
|
|
16
15
|
if (initialized) {
|
|
17
|
-
console.warn(
|
|
16
|
+
console.warn(`Already initialized with url ${JSON.stringify(zeniAPI.apiEndPoints)}`);
|
|
18
17
|
return store;
|
|
19
18
|
}
|
|
20
19
|
initialized = true;
|
|
20
|
+
console.info(`initializing with ${JSON.stringify(restEndPoints)}`);
|
|
21
21
|
initialize3rdPartyExtensions();
|
|
22
22
|
setZeniAPIClient(new ZeniAPIClient(restEndPoints, userId, sessionId, tenantId, onSessionInvalidated));
|
|
23
23
|
store = configureNewStore(zeniAPI, isSandboxEnv);
|
|
@@ -35,18 +35,6 @@ export { injectFeatureModules, injectFeatureEpics } from './configureStore';
|
|
|
35
35
|
export function updateZeniAPIClientConfig(userId, sessionId, tenantId) {
|
|
36
36
|
zeniAPI.update(userId, sessionId, tenantId);
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Replace the REST endpoints the API client calls. Epics read the endpoints
|
|
40
|
-
* at request time, so every request made after this uses the new ones.
|
|
41
|
-
* Note: This version SHOULD BE used only in the client side.
|
|
42
|
-
* @param restEndPoints
|
|
43
|
-
*/
|
|
44
|
-
export function updateZeniAPIEndpoints(restEndPoints) {
|
|
45
|
-
if (zeniAPI == null) {
|
|
46
|
-
throw new Error('Call initialize() before updateZeniAPIEndpoints()');
|
|
47
|
-
}
|
|
48
|
-
zeniAPI.apiEndPoints = restEndPoints;
|
|
49
|
-
}
|
|
50
38
|
/**
|
|
51
39
|
* Returns current Zeni REST API client config.
|
|
52
40
|
* @returns
|
|
@@ -73,6 +61,7 @@ export function zeniAPIClientConfig() {
|
|
|
73
61
|
* @param tenantId tenant id
|
|
74
62
|
*/
|
|
75
63
|
export async function initializeWithNewStore(restEndPoints, userId, sessionId, tenantId) {
|
|
64
|
+
console.info(`initializeWithNewStore with ${JSON.stringify(restEndPoints)}`);
|
|
76
65
|
initialize3rdPartyExtensions();
|
|
77
66
|
const zeniHttpClient = new ZeniAPIClient(restEndPoints, userId, sessionId, tenantId);
|
|
78
67
|
const store = configureNewStore(zeniHttpClient, false);
|
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,
|
|
@@ -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))))))));
|