import { Project } from "../types/project"; import { Task, TaskID, EstimationConfidence, TaskCategoryID, TaskCategory } from "../types/task"; import { useReducerAndSaga } from "./useReducerAndSaga"; import { rootSaga } from "./useProjectReducer.sagas"; import { uuid } from "../util/uuid"; import { Params } from "../types/params"; export interface ProjectState extends Project { lastBackendError: Error } export interface Action { type: string } export type ProjectReducerActions = AddTask | TaskSaved | RemoveTask | TaskRemoved | UpdateTaskEstimation | UpdateProjectTitle | UpdateTaskLabel | UpdateParam | ParamsSaved | UpdateTaskCategoryLabel | UpdateTaskCategoryCost | AddTaskCategory | RemoveTaskCategory | TaskCategoryRemoved | TaskCategorySaved | ResetProject | BackendError export function useProjectReducer(project: Project) { return useReducerAndSaga(projectReducer, project, rootSaga); } export function projectReducer(project: ProjectState, action: ProjectReducerActions): ProjectState { console.log(action.type, action); switch(action.type) { case TASK_SAVED: return handleTaskSaved(project, action as TaskSaved); case TASK_REMOVED: return handleTaskRemoved(project, action as RemoveTask); case UPDATE_TASK_ESTIMATION: return handleUpdateTaskEstimation(project, action as UpdateTaskEstimation); case UPDATE_PROJECT_TITLE: return handleUpdateProjectTitle(project, action as UpdateProjectTitle); case UPDATE_TASK_LABEL: return handleUpdateTaskLabel(project, action as UpdateTaskLabel); case UPDATE_PARAM: return handleUpdateParam(project, action as UpdateParam); case PARAMS_SAVED: return handleParamsSaved(project, action as ParamsSaved); case TASK_CATEGORY_SAVED: return handleTaskCategorySaved(project, action as TaskCategorySaved); case TASK_CATEGORY_REMOVED: return handleTaskCategoryRemoved(project, action as TaskCategoryRemoved); case RESET_PROJECT: return handleResetProject(project, action as ResetProject); case BACKEND_ERROR: return handleBackendError(project, action as BackendError); } return project; } export interface AddTask extends Action { task: Task } export const ADD_TASK = "ADD_TASK"; export function addTask(task: Task): AddTask { return { type: ADD_TASK, task }; } export interface TaskAdded extends Action { task: Task } export const TASK_SAVED = "TASK_SAVED"; export interface TaskSaved extends Action { task: Task } export function taskSaved(task: Task): TaskSaved { return { type: TASK_SAVED, task }; } export function handleTaskSaved(project: ProjectState, action: TaskSaved): ProjectState { const taskIndex = project.tasks.findIndex(t => t.id === action.task.id); const tasks = [ ...project.tasks ]; if (taskIndex === -1) { tasks.push({ ...action.task }); } else { tasks[taskIndex] = { ...tasks[taskIndex], ...action.task }; } return { ...project, tasks }; } export interface RemoveTask extends Action { id: number } export const REMOVE_TASK = "REMOVE_TASK"; export function removeTask(id: number): RemoveTask { return { type: REMOVE_TASK, id }; } export interface TaskRemoved extends Action { id: number } export const TASK_REMOVED = "TASK_REMOVED"; export function taskRemoved(id: number): TaskRemoved { return { type: TASK_REMOVED, id }; } export function handleTaskRemoved(project: ProjectState, action: TaskRemoved): ProjectState { const tasks = [...project.tasks]; const taskIndex = project.tasks.findIndex(t => t.id === action.id); if (taskIndex === -1) return project; tasks.splice(taskIndex, 1); return { ...project, tasks }; } export interface UpdateTaskEstimation extends Action { id: number confidence: string value: number } export const UPDATE_TASK_ESTIMATION = "UPDATE_TASK_ESTIMATION"; export function updateTaskEstimation(id: number, confidence: EstimationConfidence, value: number): UpdateTaskEstimation { return { type: UPDATE_TASK_ESTIMATION, id, confidence, value }; } export function handleUpdateTaskEstimation(project: ProjectState, action: UpdateTaskEstimation): ProjectState { const tasks = [...project.tasks]; const taskIndex = project.tasks.findIndex(t => t.id === action.id); if (taskIndex === -1) return project; const estimations = { ...project.tasks[taskIndex].estimations, [action.confidence]: action.value }; project.tasks[taskIndex] = { ...project.tasks[taskIndex], estimations }; return { ...project, tasks }; } export interface UpdateProjectTitle extends Action { title: string } export const UPDATE_PROJECT_TITLE = "UPDATE_PROJECT_TITLE"; export function updateProjectTitle(title: string): UpdateProjectTitle { return { type: UPDATE_PROJECT_TITLE, title }; } export function handleUpdateProjectTitle(project: ProjectState, action: UpdateProjectTitle): ProjectState { return { ...project, title: action.title }; } export interface UpdateTaskLabel extends Action { id: number label: string } export const UPDATE_TASK_LABEL = "UPDATE_TASK_LABEL"; export function updateTaskLabel(id: number, label: string): UpdateTaskLabel { return { type: UPDATE_TASK_LABEL, id, label }; } export function handleUpdateTaskLabel(project: ProjectState, action: UpdateTaskLabel): ProjectState { const tasks = [...project.tasks]; const taskIndex = project.tasks.findIndex(t => t.id === action.id); if (taskIndex === -1) return project; tasks[taskIndex] = { ...tasks[taskIndex], label: action.label }; return { ...project, tasks }; } export interface UpdateParam extends Action { name: string value: any } export const UPDATE_PARAM = "UPDATE_PARAM"; export function updateParam(name: string, value: any): UpdateParam { return { type: UPDATE_PARAM, name, value }; } export function handleUpdateParam(project: ProjectState, action: UpdateParam): ProjectState { return { ...project, params: { ...project.params, [action.name]: action.value, } }; } export interface ParamsSaved extends Action { params: Params } export const PARAMS_SAVED = "PARAMS_SAVED"; export function paramsSaved(params: Params): ParamsSaved { return { type: PARAMS_SAVED, params }; } function handleParamsSaved(project: ProjectState, action: ParamsSaved): ProjectState { return { ...project, params: { ...action.params } } } export interface TaskCategorySaved extends Action { taskCategory: TaskCategory } export const TASK_CATEGORY_SAVED = "TASK_CATEGORY_SAVED"; export function taskCategorySaved(taskCategory: TaskCategory): TaskCategorySaved { return { type: TASK_CATEGORY_SAVED, taskCategory }; } export function handleTaskCategorySaved(project: ProjectState, action: TaskCategorySaved): ProjectState { const taskCategories = [...project.taskCategories]; const taskCategoryIndex = taskCategories.findIndex(tc => tc.id === action.taskCategory.id); if (taskCategoryIndex === -1) { taskCategories.push({ ...action.taskCategory }); } else { taskCategories[taskCategoryIndex] = { ...action.taskCategory }; } return { ...project, taskCategories }; } export interface UpdateTaskCategoryLabel extends Action { categoryId: TaskCategoryID label: string } export const UPDATE_TASK_CATEGORY_LABEL = "UPDATE_TASK_CATEGORY_LABEL"; export function updateTaskCategoryLabel(categoryId: TaskCategoryID, label: string): UpdateTaskCategoryLabel { return { type: UPDATE_TASK_CATEGORY_LABEL, categoryId, label }; } export interface UpdateTaskCategoryCost extends Action { categoryId: TaskCategoryID costPerTimeUnit: number } export const UPDATE_TASK_CATEGORY_COST = "UPDATE_TASK_CATEGORY_COST"; export function updateTaskCategoryCost(categoryId: TaskCategoryID, costPerTimeUnit: number): UpdateTaskCategoryCost { return { type: UPDATE_TASK_CATEGORY_COST, categoryId, costPerTimeUnit }; } export const ADD_TASK_CATEGORY = "ADD_TASK_CATEGORY"; export interface AddTaskCategory extends Action { taskCategory: TaskCategory } export function addTaskCategory(taskCategory: TaskCategory): AddTaskCategory { return { type: ADD_TASK_CATEGORY, taskCategory }; } export interface RemoveTaskCategory extends Action { taskCategoryId: TaskCategoryID } export const REMOVE_TASK_CATEGORY = "REMOVE_TASK_CATEGORY"; export function removeTaskCategory(taskCategoryId: TaskCategoryID): RemoveTaskCategory { return { type: REMOVE_TASK_CATEGORY, taskCategoryId }; } export interface TaskCategoryRemoved extends Action { id: number } export const TASK_CATEGORY_REMOVED = "TASK_CATEGORY_REMOVED"; export function taskCategoryRemoved(id: number): TaskCategoryRemoved { return { type: TASK_CATEGORY_REMOVED, id }; } export function handleTaskCategoryRemoved(project: ProjectState, action: TaskCategoryRemoved): ProjectState { const taskCategories = [...project.taskCategories]; const taskCategoryIndex = project.taskCategories.findIndex(tc => tc.id === action.id); if (taskCategoryIndex === -1) return project; taskCategories.splice(taskCategoryIndex, 1); return { ...project, taskCategories }; } export interface ResetProject extends Action { project: Project } export const RESET_PROJECT = "RESET_PROJECT"; export function resetProject(project: Project): ResetProject { const newProject = JSON.parse(JSON.stringify(project)); newProject.tasks.forEach(t => { if (!t.localId) t.localId = uuid(); }); newProject.taskCategories.forEach(tc => { if (!tc.localId) tc.localId = uuid(); }); return { type: RESET_PROJECT, project: newProject }; } export function handleResetProject(project: ProjectState, action: ResetProject): ProjectState { return { ...project, ...action.project, }; } export interface BackendError extends Action { err: Error } export const BACKEND_ERROR = "BACKEND_ERROR"; export function backendError(err: Error): BackendError { return { type: BACKEND_ERROR, err }; } export function handleBackendError(project: ProjectState, action: BackendError): ProjectState { return { ...project, lastBackendError: action.err, }; }