Created
February 10, 2020 19:49
-
-
Save dereknelson/222dbd9150d120cf19866901da45ab09 to your computer and use it in GitHub Desktop.
redux normalization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as constants from '../Constants' | |
export const fetchProjectTaskGroups = payload => ({ | |
type: constants.FETCH_PROJECT_TASK_GROUPS.TRIGGER, | |
payload, | |
}) | |
export const addTaskGroupToFetchQueue = payload => ({ | |
type: constants.ADD_TASK_GROUP_TO_FETCH_QUEUE, | |
payload, | |
}) | |
export const removeTaskGroupFromFetchQueue = payload => ({ | |
type: constants.REMOVE_TASK_GROUP_FROM_FETCH_QUEUE, | |
payload, | |
}) | |
export const createProjectTaskGroup = payload => ({ | |
type: constants.CREATE_PROJECT_TASK_GROUP.TRIGGER, | |
payload, | |
}) | |
export const updateProjectTaskGroup = payload => ({ | |
type: constants.UPDATE_PROJECT_TASK_GROUP.TRIGGER, | |
payload, | |
}) | |
export const deleteProjectTaskGroup = payload => ({ | |
type: constants.DELETE_PROJECT_TASK_GROUP.TRIGGER, | |
payload, | |
}) | |
export const toggleTaskGroupCollapse = payload => ({ | |
type: constants.TOGGLE_TASK_GROUP, | |
payload, | |
}) | |
export const toggleTaskGroupCollapseAll = payload => ({ | |
type: constants.TOGGLE_TASK_GROUP_ALL, | |
payload, | |
}) | |
export const fetchProjectTaskGroupTasks = payload => ({ | |
type: constants.FETCH_PROJECT_TASK_GROUP_TASKS.TRIGGER, | |
payload, | |
}) | |
export const fetchAllProjectTaskGroups = () => ({ | |
type: constants.FETCH_ALL_PROJECT_TASK_GROUPS.TRIGGER, | |
}) | |
export const clearTaskGroupFetchQueue = () => ({ | |
type: constants.CLEAR_TASK_GROUP_QUEUE, | |
}) | |
export const moveTaskToGroup = ({ sourceTaskGroupId, destinationTaskGroupId, taskId, index }) => ({ | |
type: constants.MOVE_TASK_TO_GROUP, | |
payload: { | |
sourceTaskGroupId, | |
destinationTaskGroupId, | |
taskId, | |
index, | |
}, | |
}) | |
export const removeTaskFromGroup = ({ taskGroupId, taskId }) => ({ | |
type: constants.REMOVE_TASK_FROM_GROUP, | |
payload: { | |
taskGroupId, | |
taskId, | |
}, | |
}) | |
export const addTaskToGroup = ({ taskGroupId, taskId, index }) => ({ | |
type: constants.ADD_TASK_TO_GROUP, | |
payload: { | |
taskGroupId, | |
taskId, | |
index, | |
}, | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as constants from '../Constants' | |
import keyBy from 'lodash/keyBy' | |
import produce from 'immer' | |
import { filterStates } from '../../Constants' | |
const byId = item => item.id | |
const initialState = { | |
taskGroupOrders: {}, | |
taskGroups: {}, | |
newGroupId: null, | |
queuedFetches: {}, | |
} | |
// these functions only work without return values if we're using immer because it modifies the original state object | |
const filterOutTaskIds = (taskIds, taskGroups) => { | |
const keyedTaskIds = keyBy(taskIds) | |
Object.values(taskGroups).forEach(taskGroup => { | |
taskGroup.task_order = taskGroup.task_order.filter( | |
taskId => !keyedTaskIds[taskId] && taskId !== 'completed' && taskId !== 'incomplete', | |
) | |
}) | |
} | |
const markTasksAs = (taskIds, taskGroups, updateType, shouldRemove) => { | |
Object.values(taskGroups).forEach(taskGroup => { | |
taskIds.map(taskId => { | |
const index = taskGroup.task_order.findIndex(id => id === taskId) | |
if (index !== -1) { | |
if (updateType === 'complete') { | |
if (shouldRemove) { | |
taskGroup.task_order[index] = 'completed' | |
} else { | |
taskGroup.task_order.splice(index, 1) | |
taskGroup.current_page >= taskGroup.total_pages && taskGroup.task_order.push(taskId) | |
} | |
} | |
if (updateType === 'incomplete') { | |
if (shouldRemove) { | |
taskGroup.task_order[index] = 'incomplete' | |
} else { | |
taskGroup.task_order.splice(index, 1) | |
taskGroup.task_order.unshift(taskId) | |
} | |
} | |
} | |
}) | |
}) | |
} | |
const moveTaskToTop = (taskId, taskGroups) => { | |
Object.values(taskGroups).forEach(taskGroup => { | |
if (taskGroup.task_order.includes(taskId)) { | |
taskGroup.task_order = taskGroup.task_order.filter(id => id !== taskId) | |
taskGroup.task_order.unshift(taskId) | |
} | |
}) | |
} | |
export default (state = initialState, action) => { | |
return produce(state, draft => { | |
switch (action.type) { | |
case constants.FETCH_PROJECT_TASK_GROUPS.SUCCESS: { | |
const { | |
response: { task_groups = [], task_group_order }, | |
initialActionPayload: { preventOverwrite, projectId }, | |
} = action.payload | |
draft.taskGroupOrders[projectId] = task_group_order | |
const theGroups = task_groups.map(group => { | |
return { | |
...group, | |
current_page: 0, | |
task_order: | |
preventOverwrite && | |
draft.taskGroups[group.id] && | |
draft.taskGroups[group.id].task_order | |
? draft.taskGroups[group.id].task_order | |
: [], | |
collapsed: | |
preventOverwrite && | |
draft.taskGroups[group.id] && | |
draft.taskGroups[group.id].collapsed, | |
total_unfiltered: group.task_order.length, | |
} | |
}) | |
draft.taskGroups = { ...draft.taskGroups, ...keyBy(theGroups, byId) } | |
break | |
} | |
case constants.CREATE_PROJECT_TASK_GROUP.SUCCESS: { | |
const { | |
response, | |
requestPayload: { token, projectId, name, index = 0 }, | |
} = action.payload | |
draft.newGroupId = response.id | |
if (index === 0 && draft.taskGroupOrders[projectId][0] === response.id) { | |
draft.taskGroups[response.id].is_default = false | |
break // backend actually updated existing default group, do not insert id | |
} | |
draft.taskGroups[response.id] = { | |
...response, | |
current_page: 0, | |
task_order: [], | |
} | |
draft.taskGroupOrders[projectId].splice(index, 0, response.id) | |
break | |
} | |
case constants.DELETE_PROJECT_TASK_GROUP.TRIGGER: { | |
draft.newGroupId = null | |
const { projectId, id } = action.payload | |
draft.taskGroupOrders[projectId] = draft.taskGroupOrders[projectId].filter( | |
groupId => groupId !== id, | |
) | |
delete draft.taskGroups[id] | |
break | |
} | |
case constants.TOGGLE_TASK_GROUP: { | |
const { groupId, collapsed } = action.payload | |
draft.taskGroups[groupId].collapsed = collapsed | |
draft.taskGroups[groupId].isFetching = false | |
break | |
} | |
case constants.TOGGLE_TASK_GROUP_ALL: { | |
const { collapsed } = action.payload | |
Object.values(draft.taskGroups).forEach(taskGroup => { | |
taskGroup.collapsed = collapsed | |
}) | |
break | |
} | |
case constants.UPDATE_PROJECT_TASK_GROUP.TRIGGER: { | |
draft.newGroupId = null | |
const { id } = action.payload | |
draft.taskGroups[id] = { | |
...draft.taskGroups[id], | |
...action.payload, | |
} | |
break | |
} | |
case constants.FETCH_PROJECT_TASK_GROUP_TASKS.REQUEST: { | |
const { taskGroups } = draft | |
const { request } = action.payload | |
const [groupId] = request.task_group_ids | |
const group = taskGroups[groupId] | |
if ( | |
group && | |
group.task_order && | |
(group.task_order.length === 0 || | |
group.task_order.length <= group.project_tasks_count - 1) && | |
!group.collapsed | |
) { | |
draft.queuedFetches[groupId] = true | |
group.isFetching = true | |
break | |
} | |
break | |
} | |
case constants.ADD_TASK_GROUP_TO_FETCH_QUEUE: | |
{ | |
const { groupId } = action.payload | |
draft.queuedFetches[groupId] = true | |
} | |
break | |
case constants.REMOVE_TASK_GROUP_FROM_FETCH_QUEUE: { | |
const { groupId } = action.payload | |
delete draft.queuedFetches[groupId] | |
break | |
} | |
case constants.FETCH_PROJECT_TASK_GROUP_TASKS.SUCCESS: { | |
const { | |
meta: { | |
response: { tasks, current_page, total_pages }, | |
action: { | |
payload: { groupIds }, | |
}, | |
}, | |
} = action.payload | |
/* | |
* if there are no tasks in the response (because none meet the filter/sort criteria), the logic `tasks.map` of clearing "isFetching" etc. will never be called, | |
* leading to what appears to be infinite loading state. if there is more than one group in the groupIds array, the current/total page count will be | |
* for that request & therefore be inaccurate, preventing us from relying on those counts to determine when we need to stop fetching the current | |
* group and begin fetching the next one | |
*/ | |
if (groupIds && groupIds[0] && groupIds.length === 1) { | |
draft.taskGroups[groupIds[0]].isFetching = false | |
draft.taskGroups[groupIds[0]].total_pages = total_pages | |
draft.taskGroups[groupIds[0]].current_page = current_page | |
} | |
tasks.map(task => { | |
const currentGroup = draft.taskGroups[task.task_group_id] | |
if (currentGroup) { | |
currentGroup.current_page = current_page | |
currentGroup.total_pages = total_pages | |
currentGroup.task_order.push(task.id) | |
currentGroup.isFetching = false | |
if (currentGroup.current_page >= currentGroup.total_pages) { | |
delete draft.queuedFetches[currentGroup.id] | |
} | |
} | |
}) | |
Object.values(draft.taskGroups).forEach(taskGroup => { | |
taskGroup.task_order = [...new Set(taskGroup.task_order)] | |
}) | |
break | |
} | |
case constants.CREATE_TASK.TRIGGER: { | |
const { payload } = action | |
if (payload.task_group_id) { | |
const group = draft.taskGroups[payload.task_group_id] || {} | |
group.task_order = [payload.temp_id, ...group.task_order] | |
} | |
break | |
} | |
case constants.CREATE_TASK.SUCCESS: { | |
const { | |
payload: { | |
response: { task }, | |
}, | |
} = action | |
if (task.task_group_id) { | |
const group = draft.taskGroups[task.task_group_id] | |
if (!group) return | |
const index = group.task_order.findIndex(id => id === task.temp_id) | |
group.task_order[index] = task.id | |
} | |
break | |
} | |
case constants.BATCH_DELETE_TASKS.TRIGGER: { | |
const { taskIds } = action.payload | |
filterOutTaskIds(taskIds, draft.taskGroups, draft.taskGroupOrders) | |
break | |
} | |
case constants.TASKS_MOVE_TO_PROJECT.TRIGGER: { | |
const { payload } = action | |
const { task_ids = [], task_group_id } = payload || {} | |
if (!task_group_id) break | |
const taskIds = keyBy(task_ids) | |
Object.values(draft.taskGroups).map(group => { | |
group.task_order = group.task_order.filter(id => id !== taskIds[id]) | |
}) | |
const taskGroup = draft.taskGroups[task_group_id] | |
if (taskGroup) taskGroup.task_order = task_ids.concat(taskGroup.task_order) | |
break | |
} | |
case constants.REMOVE_TASK_FROM_GROUP: { | |
const { taskId, taskGroupId } = action.payload | |
const { taskGroups } = draft | |
const taskGroup = taskGroups[taskGroupId] | |
taskGroup.task_order = taskGroup.task_order.filter(id => id != taskId) | |
break | |
} | |
case constants.ADD_TASK_TO_GROUP: { | |
const { taskId, taskGroupId, index } = action.payload | |
const { taskGroups } = draft | |
const taskGroup = taskGroups[taskGroupId] | |
taskGroup.task_order.splice(index, 0, taskId) | |
break | |
} | |
case constants.HANDLE_TASK_GROUPS_TASK_COMPLETION_TOGGLE: { | |
const { taskIds, isCompleting, currentFilter } = action.payload | |
const { state: filterState } = currentFilter | |
const updateType = isCompleting ? 'complete' : 'incomplete' | |
const shouldRemove = | |
(filterState === 'completed' && !isCompleting) || | |
(filterState === 'incomplete' && isCompleting) | |
markTasksAs(taskIds, draft.taskGroups, updateType, shouldRemove) | |
break | |
} | |
case constants.CLEAR_REMOVED_TASKS: { | |
filterOutTaskIds([], draft.taskGroups, draft.taskGroupOrders) | |
break | |
} | |
case constants.CLEAR_TASK_GROUP_QUEUE: { | |
draft.queuedFetches = {} | |
break | |
} | |
case constants.UPDATE_TASKS_ATTRIBUTES.TRIGGER: { | |
const { | |
body: { task_group_id, task_ids, project_id }, | |
} = action.payload | |
if (!task_group_id) break | |
const taskGroups = draft.taskGroupOrders[project_id] | |
if (taskGroups) { | |
const taskIds = keyBy(task_ids) | |
taskGroups.map(groupId => { | |
const taskGroup = draft.taskGroups[groupId] | |
taskGroup.task_order = taskGroup.task_order.filter(id => !taskIds[id]) | |
}) | |
if (draft.taskGroups[task_group_id]) { | |
draft.taskGroups[task_group_id].task_order = task_ids.concat( | |
draft.taskGroups[task_group_id].task_order, | |
) | |
} | |
} | |
break | |
} | |
case constants.TASK_MOVE_TO_TOP.TRIGGER: { | |
const { taskIds } = action.payload | |
const ids = keyBy(taskIds) | |
Object.values(draft.taskGroups).map(group => { | |
const idsToMove = [] | |
group.task_order.map(id => { | |
if (ids[id]) idsToMove.push(id) | |
}) | |
group.task_order = [...idsToMove, ...group.task_order.filter(id => !ids[id])] | |
}) | |
break | |
} | |
case constants.FLUSH_TASK_LIST_STORE: { | |
Object.values(draft.taskGroups).forEach(taskGroup => { | |
taskGroup.task_order = [] | |
taskGroup.current_page = 0 | |
taskGroup.total_pages = undefined | |
taskGroup.isFetching = false | |
}) | |
break | |
} | |
case constants.REMOVE_UNDO_TASK: { | |
const { task = { updatedTask: {} }, filter = { state: filterStates.INCOMPLETE } } = action | |
const { id, completed_at, task_group_id } = task.updatedTask | |
const group = draft.taskGroups[task_group_id] | |
if (completed_at === null && filter.state === filterStates.COMPLETED) | |
filterOutTaskIds([id], draft.taskGroups) | |
if (completed_at && filter.state === filterStates.INCOMPLETE && group) { | |
group.task_order = [...group.task_order.filter(taskId => taskId !== id), id] | |
} | |
} | |
} | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as api from '../Services/Api' | |
import * as apiv2 from '../Services/Api.v2' | |
import produce from 'immer' | |
import * as entityActions from '../Redux/Actions' | |
const { | |
fetchProjectTaskGroups: fetchProjectTaskGroupsEntityAction, | |
createProjectTaskGroup: createProjectTaskGroupEntityAction, | |
updateProjectTaskGroup: updateProjectTaskGroupEntityAction, | |
deleteProjectTaskGroup: deleteProjectTaskGroupEntityAction, | |
fetchAllProjectTaskGroups: fetchAllProjectTaskGroupsEntityAction, | |
taskGroupPreferencesFetch: taskGroupPreferencesFetchEntityAction, | |
taskGroupPreferenceCreate: taskGroupPreferenceCreateEntityAction, | |
taskGroupPreferenceUpdate: taskGroupPreferenceUpdateEntityAction, | |
taskGroupPreferenceDelete: taskGroupPreferenceDeleteEntityAction, | |
updateTasksAttributesEntityActions, | |
} = entityActions | |
import { | |
updateTaskGroupPreference as updateTaskGroupPreferenceActionCreator, | |
createTaskGroupPreference as createTaskGroupPreferenceActionCreator, | |
} from '../Redux/Actions/userThemeActions' | |
import { fetchEntity, changeEntity, apiRequestSaga } from './helpers' | |
import { select, put, call } from 'redux-saga/effects' | |
import { | |
fetchProjectTaskGroups as fetchProjectTaskGroupsActionCreator, | |
addTaskGroupToFetchQueue, | |
removeTaskGroupFromFetchQueue, | |
fetchProjectTaskGroupTasks as fetchProjectTaskGroupTasksActionCreator, | |
} from '../Redux/Actions/projectTaskGroupActions' | |
import * as actionCreators from '../Redux/Actions/projectTaskGroupActions' | |
import { getProjectTaskGroupsState, getAuthToken, getTaskFetchConfig } from '../Redux/Selectors' | |
export function* fetchProjectTaskGroups(action) { | |
const { projectId } = action.payload | |
const { error } = yield fetchEntity( | |
fetchProjectTaskGroupsEntityAction, | |
api.fetchProjectTaskGroups, | |
[projectId], | |
action, | |
) | |
if (!error) { | |
const { taskGroupOrders, taskGroups } = yield select(getProjectTaskGroupsState) | |
const taskGroupOrder = taskGroupOrders[projectId] | |
/* | |
* this finds the first task group that has tasks because if the viewableItems in the list don't change after the componentDidMount fetch | |
* we won't start fetching the next group because there is nothing to trigger the call | |
*/ | |
let groupId = taskGroupOrders[projectId][0] | |
taskGroupOrder.find(id => { | |
if (taskGroups[id].total_unfiltered != 0) { | |
groupId = id | |
return true | |
} | |
}) | |
yield put(fetchProjectTaskGroupTasksActionCreator({ groupId, projectId })) | |
} | |
} | |
export function* fetchProjectTaskGroupTasksQueue(action) { | |
const { projectId, groupId } = action.payload | |
const { taskGroupOrders, queuedFetches } = yield select(getProjectTaskGroupsState) | |
const taskGroupOrder = taskGroupOrders[projectId] | |
const nextActiveFetch = taskGroupOrder.find(id => queuedFetches[id]) | |
if (!nextActiveFetch || nextActiveFetch === groupId) { | |
yield call(fetchProjectTaskGroupTasks, action) | |
} else { | |
yield put(addTaskGroupToFetchQueue({ groupId })) | |
} | |
} | |
function* callTaskGroupFetchQueueNext(action) { | |
const { taskGroupOrders, queuedFetches } = yield select(getProjectTaskGroupsState) | |
const { groupId, projectId } = action.payload | |
const nextGroupInQueueId = taskGroupOrders[projectId].find( | |
id => queuedFetches[id] && id !== groupId, | |
) | |
if (nextGroupInQueueId) { | |
yield put( | |
fetchProjectTaskGroupTasksActionCreator({ | |
projectId, | |
groupId: nextGroupInQueueId, | |
}), | |
) | |
} | |
} | |
export function* fetchProjectTaskGroupTasksSuccess(action) { | |
const { meta } = action.payload | |
const { current_page, total_pages } = meta.response | |
const initialPayload = meta.action.payload | |
const { groupId, projectId } = initialPayload | |
if (current_page >= total_pages) { | |
/* always try to grab next fetch from queue if current group is done | |
if none exists, no fetch will occur and action terminates gracefully. | |
waypoints already on page will exist in queue and fetch as necessary to fill page | |
waypoints not on page will trigger fetch on entering page | |
*/ | |
yield put(removeTaskGroupFromFetchQueue({ groupId })) | |
yield call(callTaskGroupFetchQueueNext, { | |
payload: { groupId, projectId }, | |
}) | |
} | |
} | |
function* fetchProjectTaskGroupTasks(action) { | |
const { taskGroups } = yield select(getProjectTaskGroupsState) | |
const { sort, filter, selectedAccountIds, ...restConfigProps } = yield select(getTaskFetchConfig) | |
const { projectId } = action.payload | |
const page = | |
taskGroups[action.payload.groupId] && taskGroups[action.payload.groupId].current_page + 1 | |
const newAction = produce(action, theAction => { | |
theAction.payload.groupIds = [action.payload.groupId] | |
}) | |
const body = { | |
project_id: projectId, | |
task_group_ids: [action.payload.groupId], | |
...restConfigProps, | |
state: 'incomplete', | |
limit: 25, | |
page, // important that page comes after restConfigProps because taskFetchConfig has a page key that is different than the page logic here | |
...filter, | |
// sort | |
} | |
const options = { | |
method: 'POST', | |
body, | |
} | |
const endpoint = `tasks` | |
yield call(apiRequestSaga, apiv2.fetchTasks, body, newAction) | |
} | |
export function* createProjectTaskGroup(action) { | |
const { projectId, name, index, color } = action.payload | |
const token = yield select(getAuthToken) | |
const { error, response } = yield changeEntity( | |
createProjectTaskGroupEntityAction, | |
api.createProjectTaskGroup, | |
[token, projectId, name, index], | |
action, | |
) | |
if (!error && color) { | |
const newAction = produce(action.payload, draft => { | |
draft.id = response.id | |
}) | |
yield put(createTaskGroupPreferenceActionCreator(newAction)) | |
} | |
} | |
export function* updateProjectTaskGroup(action) { | |
const { projectId, name, id } = action.payload | |
const token = yield select(getAuthToken) | |
const { error } = yield changeEntity( | |
updateProjectTaskGroupEntityAction, | |
api.updateProjectTaskGroup, | |
[token, id, name, projectId], | |
action, | |
) | |
} | |
export function* deleteProjectTaskGroup(action) { | |
const { id, projectId } = action.payload | |
const token = yield select(getAuthToken) | |
const { error } = yield changeEntity( | |
deleteProjectTaskGroupEntityAction, | |
api.deleteProjectTaskGroup, | |
[token, id], | |
action, | |
) | |
const { taskGroupOrders } = yield select(getProjectTaskGroupsState) | |
if (taskGroupOrders[projectId].length === 0) { | |
yield put(fetchProjectTaskGroupsActionCreator({ projectId })) | |
} | |
} | |
export function* moveTaskToGroupWorker(action) { | |
const { sourceTaskGroupId, destinationTaskGroupId, taskId, index } = action.payload | |
yield put( | |
actionCreators.removeTaskFromGroup({ | |
taskGroupId: sourceTaskGroupId, | |
taskId, | |
}), | |
) | |
yield put( | |
actionCreators.addTaskToGroup({ | |
taskGroupId: destinationTaskGroupId, | |
taskId, | |
index, | |
}), | |
) | |
} | |
export function* addTaskToGroupWorker(action) { | |
const { taskGroupId, taskId, index } = action.payload | |
const token = yield select(getAuthToken) | |
const body = { | |
task_ids: [taskId], | |
position: index, | |
task_group_id: taskGroupId, | |
} | |
const { error } = yield changeEntity(updateTasksAttributesEntityActions, apiv2.editTask, [ | |
{ token, body }, | |
]) | |
} | |
export function* fetchTaskGroupPreferences(action) { | |
const token = yield select(getAuthToken) | |
const { error, response } = yield fetchEntity( | |
taskGroupPreferencesFetchEntityAction, | |
api.fetchTaskGroupPreferences, | |
[token], | |
action, | |
) | |
} | |
export function* updateTaskGroupPreference(action) { | |
const { id, color } = action.payload | |
const token = yield select(getAuthToken) | |
const { error, response } = yield changeEntity( | |
taskGroupPreferenceUpdateEntityAction, | |
api.updateTaskGroupPreference, | |
[token, id, color], | |
action, | |
) | |
} | |
export function* createTaskGroupPreference(action) { | |
const { id, color } = action.payload | |
const token = yield select(getAuthToken) | |
const { error, response } = yield changeEntity( | |
taskGroupPreferenceCreateEntityAction, | |
api.createTaskGroupPreference, | |
[token, id, color], | |
action, | |
) | |
} | |
export function* deleteTaskGroupPreference(action) { | |
const { id, color } = action.payload | |
const token = yield select(getAuthToken) | |
const { error, response } = yield changeEntity( | |
taskGroupPreferenceDeleteEntityAction, | |
api.deleteTaskGroupPreference, | |
[token, id, color], | |
action, | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const getTasks = state => state.tasksList.tasks | |
export const getEditingTaskId = state => state.tasksList.editingTaskId | |
export const getPropsTaskId = (state, ownProps) => | |
ownProps && (ownProps.taskId || (ownProps.task && ownProps.taskId)) | |
export const getNewTaskComments = (state, ownProps) => ownProps.newTaskComments || emptyArray | |
export const getFilterState = state => state.tasksList.filter | |
export const getSearchText = state => state.tasksList.searchText | |
export const getProjectTasksMap = state => state.tasksList.projectTasks || emptyObj | |
export const getSelectedGroup = state => state.groups.selectedGroupId | |
export const getTaskListBatchState = state => state.tasksList.batchMode | |
export const getTaskGroupFromProps = (state, ownProps) => | |
(ownProps && ownProps.group) || { task_order: emptyArray } | |
export const getPrevTaskFromTasksList = state => state.tasksList.prevTask | |
// PROJECT GETTERS | |
export const getSelectedProject = state => state.projects.selectedProject | |
export const getProjectTaskGroupsState = state => state.projectTaskGroups | |
export const makeGetTaskFromTasksList = () => { | |
return createSelector( | |
getTasks, | |
getProjectTasksMap, | |
getPropsTaskId, | |
(tasks, projectTasksMap, taskId) => | |
tasks.find(({ id }) => id === taskId) || projectTasksMap[taskId] || {}, | |
) | |
} | |
export const makeGetProjectFromTask = () => { | |
const getTaskFromTasksList = makeGetTaskFromTasksList() | |
return createSelector( | |
getTaskFromTasksList, | |
getMyProjects, | |
(task, myProjects) => myProjects.find(({ id }) => id === task.project_id), | |
) | |
} | |
export const getTaskGroups = createSelector( | |
getSelectedProjectTaskGroups, | |
getSelectedProjectTaskGroupOrder, | |
getRouterProjectId, | |
getOwnProjectId, | |
(taskGroups, taskGroupOrders, projectId, ownProjectId) => { | |
const theProjectId = projectId || ownProjectId | |
return taskGroupOrders[theProjectId] | |
? taskGroupOrders[theProjectId].map(id => taskGroups[id]).filter(group => group) | |
: emptyArray | |
}, | |
) | |
export const getTaskGroupsMap = createSelector( | |
getSelectedProjectTaskGroups, | |
getSelectedProjectTaskGroupOrder, | |
getRouterProjectId, | |
getOwnProjectId, | |
(taskGroups, taskGroupOrders, projectId, ownProjectId) => { | |
const theProjectId = projectId || ownProjectId | |
const taskMap = | |
taskGroupOrders[theProjectId] && | |
taskGroupOrders[theProjectId].map(id => taskGroups[id]).filter(group => group) | |
return keyBy(taskMap, byId) | |
}, | |
) | |
export const getTaskGroupTasks = createSelector( | |
getTaskGroups, | |
getProjectTasksMap, | |
(taskGroups, projectTasksMap) => { | |
let tasks = [] | |
taskGroups.map(group => { | |
tasks.push({ ...group, isHeader: true }) | |
if (!group.collapsed) | |
group.task_order.map((id, index) => | |
tasks.push({ | |
...projectTasksMap[id], | |
taskGroupIsFetching: group.isFetching, | |
isLastInGroup: index === getLength(group.task_order) - 1, | |
}), | |
) | |
}) | |
tasks = tasks.filter(task => task) | |
const stickyHeaderIndices = [] | |
tasks.map((task, index) => { | |
if (task.isHeader) stickyHeaderIndices.push(index) | |
}) | |
return { tasks, stickyHeaderIndices } | |
}, | |
) | |
export const getIsFetchingTaskGroups = createSelector( | |
getProjectTaskGroups, | |
taskGroups => Object.values(taskGroups).some(taskGroup => taskGroup.isFetching), | |
) | |
export const getQueuedTaskGroupFetches = createSelector( | |
getProjectTaskGroupsState, | |
projectTaskGroups => projectTaskGroups.queuedFetches, | |
) | |
export const getAllTaskGroupsCollapsed = createSelector( | |
getTaskGroupsMap, | |
taskGroups => Object.values(taskGroups).every(taskGroup => taskGroup.collapsed), | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment