guesstimate/client/src/hooks/useProjectReducer.ts

396 lines
10 KiB
TypeScript

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<ProjectState>(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,
};
}