feat(ui+backend): task categories edition
This commit is contained in:
parent
833213e5fe
commit
e3274cdecf
|
@ -27,14 +27,20 @@ fragment FullProjectParams on ProjectParams {
|
|||
}
|
||||
`;
|
||||
|
||||
export const FRAGMENT_FULL_TASK_CATEGORY = gql`
|
||||
fragment FullTaskCategory on TaskCategory {
|
||||
id
|
||||
label
|
||||
costPerTimeUnit
|
||||
}
|
||||
`;
|
||||
|
||||
export const FRAGMENT_FULL_PROJECT = gql`
|
||||
fragment FullProject on Project {
|
||||
id
|
||||
title
|
||||
taskCategories {
|
||||
id
|
||||
label
|
||||
costPerTimeUnit
|
||||
...FullTaskCategory
|
||||
}
|
||||
tasks {
|
||||
...FullTask
|
||||
|
@ -45,4 +51,5 @@ fragment FullProject on Project {
|
|||
}
|
||||
${FRAGMENT_FULL_TASK}
|
||||
${FRAGMENT_FULL_PROJECT_PARAMS}
|
||||
${FRAGMENT_FULL_TASK_CATEGORY}
|
||||
`
|
|
@ -1,5 +1,5 @@
|
|||
import { gql, useMutation, PureQueryOptions } from '@apollo/client';
|
||||
import { FRAGMENT_FULL_PROJECT, FRAGMENT_FULL_TASK, FRAGMENT_FULL_PROJECT_PARAMS } from '../fragments/project';
|
||||
import { FRAGMENT_FULL_PROJECT, FRAGMENT_FULL_TASK, FRAGMENT_FULL_PROJECT_PARAMS, FRAGMENT_FULL_TASK_CATEGORY } from '../fragments/project';
|
||||
import { QUERY_PROJECTS } from '../queries/project';
|
||||
|
||||
export const MUTATION_CREATE_PROJECT = gql`
|
||||
|
@ -82,3 +82,39 @@ ${FRAGMENT_FULL_PROJECT_PARAMS}
|
|||
export function useUpdateProjectParamsMutation() {
|
||||
return useMutation(MUTATION_UPDATE_PROJECT_PARAMS);
|
||||
}
|
||||
|
||||
export const MUTATION_ADD_PROJECT_TASK_CATEGORY = gql`
|
||||
mutation addProjectTaskCategory($projectId: ID!, $changes: ProjectTaskCategoryChanges!) {
|
||||
addProjectTaskCategory(projectId: $projectId, changes: $changes) {
|
||||
...FullTaskCategory
|
||||
}
|
||||
}
|
||||
${FRAGMENT_FULL_TASK_CATEGORY}
|
||||
`;
|
||||
|
||||
export function useAddProjectTaskCategoryMutation() {
|
||||
return useMutation(MUTATION_ADD_PROJECT_TASK_CATEGORY);
|
||||
}
|
||||
|
||||
export const MUTATION_UPDATE_PROJECT_TASK_CATEGORY = gql`
|
||||
mutation updateProjectTaskCategory($projectId: ID!, $taskCategoryId: ID!, $changes: ProjectTaskCategoryChanges!) {
|
||||
updateProjectTaskCategory(projectId: $projectId, taskCategoryId: $taskCategoryId, changes: $changes) {
|
||||
...FullTaskCategory
|
||||
}
|
||||
}
|
||||
${FRAGMENT_FULL_TASK_CATEGORY}
|
||||
`;
|
||||
|
||||
export function useUpdateProjectTaskCategoryMutation() {
|
||||
return useMutation(MUTATION_UPDATE_PROJECT_TASK_CATEGORY);
|
||||
}
|
||||
|
||||
export const MUTATION_REMOVE_PROJECT_TASK_CATEGORY = gql`
|
||||
mutation removeProjectTaskCategory($projectId: ID!, $taskCategoryId: ID!) {
|
||||
removeProjectTaskCategory(projectId: $projectId, taskCategoryId: $taskCategoryId)
|
||||
}
|
||||
`;
|
||||
|
||||
export function useRemoveProjectTaskCategoryMutation() {
|
||||
return useMutation(MUTATION_REMOVE_PROJECT_TASK_CATEGORY);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,22 @@
|
|||
import { all, select, takeLatest, put, delay } from "redux-saga/effects";
|
||||
import { client } from '../gql/client';
|
||||
import { MUTATION_CREATE_PROJECT, MUTATION_UPDATE_PROJECT_TITLE, MUTATION_ADD_PROJECT_TASK, MUTATION_REMOVE_PROJECT_TASK, MUTATION_UPDATE_PROJECT_TASK, MUTATION_UPDATE_PROJECT_PARAMS } from "../gql/mutations/project";
|
||||
import { UPDATE_PROJECT_TITLE, resetProject, ADD_TASK, taskSaved, AddTask, taskRemoved, RemoveTask, REMOVE_TASK, UPDATE_TASK_ESTIMATION, updateTaskEstimation, UpdateTaskEstimation, UpdateTaskLabel, UPDATE_TASK_LABEL, UpdateParam, paramsSaved, UPDATE_PARAM } from "./useProjectReducer";
|
||||
import {
|
||||
MUTATION_CREATE_PROJECT, MUTATION_UPDATE_PROJECT_TITLE,
|
||||
MUTATION_ADD_PROJECT_TASK, MUTATION_REMOVE_PROJECT_TASK,
|
||||
MUTATION_UPDATE_PROJECT_TASK, MUTATION_UPDATE_PROJECT_PARAMS,
|
||||
MUTATION_ADD_PROJECT_TASK_CATEGORY,
|
||||
MUTATION_UPDATE_PROJECT_TASK_CATEGORY,
|
||||
MUTATION_REMOVE_PROJECT_TASK_CATEGORY
|
||||
} from "../gql/mutations/project";
|
||||
import {
|
||||
UPDATE_PROJECT_TITLE, resetProject,
|
||||
ADD_TASK, taskSaved, AddTask,
|
||||
taskRemoved, RemoveTask, REMOVE_TASK,
|
||||
UPDATE_TASK_ESTIMATION, UpdateTaskEstimation,
|
||||
UpdateTaskLabel, UPDATE_TASK_LABEL, UpdateParam,
|
||||
paramsSaved, UPDATE_PARAM, AddTaskCategory,
|
||||
ADD_TASK_CATEGORY, taskCategorySaved, UpdateTaskCategoryLabel, UPDATE_TASK_CATEGORY_LABEL, UPDATE_TASK_CATEGORY_COST, updateTaskCategoryCost, UpdateTaskCategoryCost, RemoveTaskCategory, taskCategoryRemoved, REMOVE_TASK_CATEGORY, backendError
|
||||
} from "./useProjectReducer";
|
||||
import { Project } from "../types/project";
|
||||
|
||||
export function* rootSaga() {
|
||||
|
@ -13,6 +28,10 @@ export function* rootSaga() {
|
|||
takeLatest(UPDATE_PARAM, updateProjectParamsSaga),
|
||||
takeLatest(ADD_TASK, addTaskSaga),
|
||||
takeLatest(REMOVE_TASK, removeTaskSaga),
|
||||
takeLatest(ADD_TASK_CATEGORY, addProjectTaskCategorySaga),
|
||||
takeLatest(UPDATE_TASK_CATEGORY_LABEL, updateProjectTaskCategoryLabelSaga),
|
||||
takeLatest(UPDATE_TASK_CATEGORY_COST, updateProjectTaskCategoryCostSaga),
|
||||
takeLatest(REMOVE_TASK_CATEGORY, removeProjectTaskCategorySaga),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -163,4 +182,91 @@ export function* updateProjectParamsSaga({ name, value }: UpdateParam) {
|
|||
});
|
||||
|
||||
yield put(paramsSaved({ ...data.updateProjectParams }));
|
||||
}
|
||||
|
||||
export function* addProjectTaskCategorySaga({ taskCategory }: AddTaskCategory) {
|
||||
let project: Project = yield select();
|
||||
|
||||
if (project.id === undefined) {
|
||||
project = yield createProjectSaga();
|
||||
}
|
||||
|
||||
const { data } = yield client.mutate({
|
||||
mutation: MUTATION_ADD_PROJECT_TASK_CATEGORY,
|
||||
variables: {
|
||||
projectId: project.id,
|
||||
changes: {
|
||||
label: taskCategory.label,
|
||||
costPerTimeUnit: taskCategory.costPerTimeUnit,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
yield put(taskCategorySaved({ ...data.addProjectTaskCategory }));
|
||||
}
|
||||
|
||||
export function* updateProjectTaskCategoryLabelSaga({ categoryId, label }: UpdateTaskCategoryLabel) {
|
||||
let project: Project = yield select();
|
||||
|
||||
if (project.id === undefined) {
|
||||
project = yield createProjectSaga();
|
||||
}
|
||||
|
||||
const { data } = yield client.mutate({
|
||||
mutation: MUTATION_UPDATE_PROJECT_TASK_CATEGORY,
|
||||
variables: {
|
||||
projectId: project.id,
|
||||
taskCategoryId: categoryId,
|
||||
changes: {
|
||||
label,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
yield put(taskCategorySaved({ ...data.updateProjectTaskCategory }));
|
||||
}
|
||||
|
||||
export function* updateProjectTaskCategoryCostSaga({ categoryId, costPerTimeUnit }: UpdateTaskCategoryCost) {
|
||||
let project: Project = yield select();
|
||||
|
||||
if (project.id === undefined) {
|
||||
project = yield createProjectSaga();
|
||||
}
|
||||
|
||||
const { data } = yield client.mutate({
|
||||
mutation: MUTATION_UPDATE_PROJECT_TASK_CATEGORY,
|
||||
variables: {
|
||||
projectId: project.id,
|
||||
taskCategoryId: categoryId,
|
||||
changes: {
|
||||
costPerTimeUnit,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
yield put(taskCategorySaved({ ...data.updateProjectTaskCategory }));
|
||||
}
|
||||
|
||||
export function* removeProjectTaskCategorySaga({ taskCategoryId }: RemoveTaskCategory) {
|
||||
let project: Project = yield select();
|
||||
|
||||
if (project.id === undefined) {
|
||||
project = yield createProjectSaga();
|
||||
}
|
||||
|
||||
try {
|
||||
yield client.mutate({
|
||||
mutation: MUTATION_REMOVE_PROJECT_TASK_CATEGORY,
|
||||
variables: {
|
||||
projectId: project.id,
|
||||
taskCategoryId,
|
||||
}
|
||||
});
|
||||
} catch(err) {
|
||||
yield put(backendError(err));
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
yield put(taskCategoryRemoved(taskCategoryId));
|
||||
}
|
|
@ -5,6 +5,10 @@ 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
|
||||
}
|
||||
|
@ -23,13 +27,16 @@ UpdateTaskCategoryLabel |
|
|||
UpdateTaskCategoryCost |
|
||||
AddTaskCategory |
|
||||
RemoveTaskCategory |
|
||||
ResetProject
|
||||
TaskCategoryRemoved |
|
||||
TaskCategorySaved |
|
||||
ResetProject |
|
||||
BackendError
|
||||
|
||||
export function useProjectReducer(project: Project) {
|
||||
return useReducerAndSaga<Project>(projectReducer, project, rootSaga);
|
||||
return useReducerAndSaga<ProjectState>(projectReducer, project, rootSaga);
|
||||
}
|
||||
|
||||
export function projectReducer(project: Project, action: ProjectReducerActions): Project {
|
||||
export function projectReducer(project: ProjectState, action: ProjectReducerActions): ProjectState {
|
||||
console.log(action.type, action);
|
||||
|
||||
switch(action.type) {
|
||||
|
@ -50,21 +57,21 @@ export function projectReducer(project: Project, action: ProjectReducerActions):
|
|||
|
||||
case UPDATE_PARAM:
|
||||
return handleUpdateParam(project, action as UpdateParam);
|
||||
|
||||
case ADD_TASK_CATEGORY:
|
||||
return handleAddTaskCategory(project, action as AddTaskCategory);
|
||||
|
||||
case REMOVE_TASK_CATEGORY:
|
||||
return handleRemoveTaskCategory(project, action as RemoveTaskCategory);
|
||||
|
||||
case UPDATE_TASK_CATEGORY_LABEL:
|
||||
return handleUpdateTaskCategoryLabel(project, action as UpdateTaskCategoryLabel);
|
||||
|
||||
case UPDATE_TASK_CATEGORY_COST:
|
||||
return handleUpdateTaskCategoryCost(project, action as UpdateTaskCategoryCost);
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -95,7 +102,7 @@ export function taskSaved(task: Task): TaskSaved {
|
|||
return { type: TASK_SAVED, task };
|
||||
}
|
||||
|
||||
export function handleTaskSaved(project: Project, action: TaskSaved): Project {
|
||||
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) {
|
||||
|
@ -130,7 +137,7 @@ export function taskRemoved(id: number): TaskRemoved {
|
|||
}
|
||||
|
||||
|
||||
export function handleTaskRemoved(project: Project, action: TaskRemoved): Project {
|
||||
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;
|
||||
|
@ -153,7 +160,7 @@ export function updateTaskEstimation(id: number, confidence: EstimationConfidenc
|
|||
return { type: UPDATE_TASK_ESTIMATION, id, confidence, value };
|
||||
}
|
||||
|
||||
export function handleUpdateTaskEstimation(project: Project, action: UpdateTaskEstimation): Project {
|
||||
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;
|
||||
|
@ -182,7 +189,7 @@ export function updateProjectTitle(title: string): UpdateProjectTitle {
|
|||
return { type: UPDATE_PROJECT_TITLE, title };
|
||||
}
|
||||
|
||||
export function handleUpdateProjectTitle(project: Project, action: UpdateProjectTitle): Project {
|
||||
export function handleUpdateProjectTitle(project: ProjectState, action: UpdateProjectTitle): ProjectState {
|
||||
return {
|
||||
...project,
|
||||
title: action.title
|
||||
|
@ -200,7 +207,7 @@ export function updateTaskLabel(id: number, label: string): UpdateTaskLabel {
|
|||
return { type: UPDATE_TASK_LABEL, id, label };
|
||||
}
|
||||
|
||||
export function handleUpdateTaskLabel(project: Project, action: UpdateTaskLabel): Project {
|
||||
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;
|
||||
|
@ -224,7 +231,7 @@ export function updateParam(name: string, value: any): UpdateParam {
|
|||
return { type: UPDATE_PARAM, name, value };
|
||||
}
|
||||
|
||||
export function handleUpdateParam(project: Project, action: UpdateParam): Project {
|
||||
export function handleUpdateParam(project: ProjectState, action: UpdateParam): ProjectState {
|
||||
return {
|
||||
...project,
|
||||
params: {
|
||||
|
@ -244,6 +251,41 @@ 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
|
||||
|
@ -255,19 +297,6 @@ export function updateTaskCategoryLabel(categoryId: TaskCategoryID, label: strin
|
|||
return { type: UPDATE_TASK_CATEGORY_LABEL, categoryId, label };
|
||||
}
|
||||
|
||||
export function handleUpdateTaskCategoryLabel(project: Project, action: UpdateTaskCategoryLabel): Project {
|
||||
return {
|
||||
...project,
|
||||
taskCategories: {
|
||||
...project.taskCategories,
|
||||
[action.categoryId]: {
|
||||
...project.taskCategories[action.categoryId],
|
||||
label: action.label
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateTaskCategoryCost extends Action {
|
||||
categoryId: TaskCategoryID
|
||||
costPerTimeUnit: number
|
||||
|
@ -279,19 +308,6 @@ export function updateTaskCategoryCost(categoryId: TaskCategoryID, costPerTimeUn
|
|||
return { type: UPDATE_TASK_CATEGORY_COST, categoryId, costPerTimeUnit };
|
||||
}
|
||||
|
||||
export function handleUpdateTaskCategoryCost(project: Project, action: UpdateTaskCategoryCost): Project {
|
||||
return {
|
||||
...project,
|
||||
taskCategories: {
|
||||
...project.taskCategories,
|
||||
[action.categoryId]: {
|
||||
...project.taskCategories[action.categoryId],
|
||||
costPerTimeUnit: action.costPerTimeUnit
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ADD_TASK_CATEGORY = "ADD_TASK_CATEGORY";
|
||||
|
||||
export interface AddTaskCategory extends Action {
|
||||
|
@ -302,17 +318,6 @@ export function addTaskCategory(taskCategory: TaskCategory): AddTaskCategory {
|
|||
return { type: ADD_TASK_CATEGORY, taskCategory };
|
||||
}
|
||||
|
||||
export function handleAddTaskCategory(project: Project, action: AddTaskCategory): Project {
|
||||
const taskCategory = { ...action.taskCategory };
|
||||
return {
|
||||
...project,
|
||||
taskCategories: {
|
||||
...project.taskCategories,
|
||||
[taskCategory.id]: taskCategory,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface RemoveTaskCategory extends Action {
|
||||
taskCategoryId: TaskCategoryID
|
||||
}
|
||||
|
@ -323,18 +328,28 @@ export function removeTaskCategory(taskCategoryId: TaskCategoryID): RemoveTaskCa
|
|||
return { type: REMOVE_TASK_CATEGORY, taskCategoryId };
|
||||
}
|
||||
|
||||
export function handleRemoveTaskCategory(project: Project, action: RemoveTaskCategory): Project {
|
||||
const taskCategories = { ...project.taskCategories };
|
||||
delete taskCategories[action.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: {
|
||||
...project.taskCategories,
|
||||
...taskCategories
|
||||
}
|
||||
taskCategories
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface ResetProject extends Action {
|
||||
project: Project
|
||||
}
|
||||
|
@ -355,9 +370,26 @@ export function resetProject(project: Project): ResetProject {
|
|||
return { type: RESET_PROJECT, project: newProject };
|
||||
}
|
||||
|
||||
export function handleResetProject(project: Project, action: ResetProject): Project {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package graph
|
||||
|
||||
import (
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAssociatedTaskExist = &gqlerror.Error{
|
||||
Message: "",
|
||||
Extensions: map[string]interface{}{
|
||||
"code": "associated-task-exist",
|
||||
},
|
||||
}
|
||||
)
|
|
@ -30,6 +30,11 @@ input TimeUnitChanges {
|
|||
acronym: String
|
||||
}
|
||||
|
||||
input ProjectTaskCategoryChanges {
|
||||
label: String
|
||||
costPerTimeUnit: Float
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
updateUser(id: ID!, changes: UserChanges!): User!
|
||||
createProject(changes: CreateProjectChanges!): Project!
|
||||
|
@ -37,5 +42,8 @@ type Mutation {
|
|||
addProjectTask(projectId: ID!, changes: ProjectTaskChanges!): Task!
|
||||
removeProjectTask(projectId: ID!, taskId: ID!): Boolean!
|
||||
updateProjectTask(projectId: ID!, taskId: ID!, changes: ProjectTaskChanges!): Task!
|
||||
addProjectTaskCategory(projectId: ID!, changes: ProjectTaskCategoryChanges!): TaskCategory!
|
||||
updateProjectTaskCategory(projectId: ID!, taskCategoryId: ID!, changes: ProjectTaskCategoryChanges!): TaskCategory!
|
||||
removeProjectTaskCategory(projectId: ID!, taskCategoryId: ID!): Boolean!
|
||||
updateProjectParams(projectId: ID!, changes: ProjectParamsChanges!): ProjectParams!
|
||||
}
|
|
@ -34,6 +34,18 @@ func (r *mutationResolver) UpdateProjectTask(ctx context.Context, projectID int6
|
|||
return handleUpdateProjectTask(ctx, projectID, taskID, changes)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) AddProjectTaskCategory(ctx context.Context, projectID int64, changes model.ProjectTaskCategoryChanges) (*model.TaskCategory, error) {
|
||||
return handleAddProjectTaskCategory(ctx, projectID, changes)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UpdateProjectTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64, changes model.ProjectTaskCategoryChanges) (*model.TaskCategory, error) {
|
||||
return handleUpdateProjectTaskCategory(ctx, projectID, taskCategoryID, changes)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveProjectTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64) (bool, error) {
|
||||
return handleRemoveProjectTaskCategory(ctx, projectID, taskCategoryID)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UpdateProjectParams(ctx context.Context, projectID int64, changes model.ProjectParamsChanges) (*model.ProjectParams, error) {
|
||||
return handleUpdateProjectParams(ctx, projectID, changes)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package graph
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
|
||||
"forge.cadoles.com/Cadoles/guesstimate/internal/model"
|
||||
model1 "forge.cadoles.com/Cadoles/guesstimate/internal/model"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -126,3 +128,56 @@ func handleUpdateProjectParams(ctx context.Context, projectID int64, changes mod
|
|||
|
||||
return project.Params, nil
|
||||
}
|
||||
|
||||
func handleAddProjectTaskCategory(ctx context.Context, projectID int64, changes model.ProjectTaskCategoryChanges) (*model.TaskCategory, error) {
|
||||
db, err := getDB(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
repo := model.NewProjectRepository(db)
|
||||
|
||||
taskCategory, err := repo.AddTaskCategory(ctx, projectID, changes)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return taskCategory, nil
|
||||
}
|
||||
|
||||
func handleUpdateProjectTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64, changes model.ProjectTaskCategoryChanges) (*model.TaskCategory, error) {
|
||||
db, err := getDB(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
repo := model.NewProjectRepository(db)
|
||||
|
||||
taskCategory, err := repo.UpdateTaskCategory(ctx, projectID, taskCategoryID, changes)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return taskCategory, nil
|
||||
}
|
||||
|
||||
func handleRemoveProjectTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64) (bool, error) {
|
||||
db, err := getDB(ctx)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
repo := model.NewProjectRepository(db)
|
||||
|
||||
if err := repo.RemoveTaskCategory(ctx, projectID, taskCategoryID); err != nil {
|
||||
if errors.Is(err, model.ErrAssociatedTaskExist) {
|
||||
graphql.AddError(ctx, ErrAssociatedTaskExist)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package model
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrAssociatedTaskExist = errors.New("associated task exist")
|
||||
)
|
|
@ -144,10 +144,6 @@ func (r *ProjectRepository) AddTask(ctx context.Context, projectID int64, change
|
|||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := tx.Save(task).Error; err != nil {
|
||||
return errors.Wrap(err, "could not create task")
|
||||
}
|
||||
|
||||
err := tx.Model(project).Association("Tasks").Append(task).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not add task")
|
||||
|
@ -279,6 +275,94 @@ func (r *ProjectRepository) UpdateParams(ctx context.Context, projectID int64, c
|
|||
return project, nil
|
||||
}
|
||||
|
||||
func (r *ProjectRepository) AddTaskCategory(ctx context.Context, projectID int64, changes ProjectTaskCategoryChanges) (*TaskCategory, error) {
|
||||
project := &Project{}
|
||||
project.ID = projectID
|
||||
taskCategory := &TaskCategory{}
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := updateTaskCategoryWithChanges(tx, taskCategory, changes); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err := tx.Model(project).Association("TaskCategories").Append(taskCategory).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not add task category")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not add task category")
|
||||
}
|
||||
|
||||
return taskCategory, nil
|
||||
}
|
||||
|
||||
func (r *ProjectRepository) RemoveTaskCategory(ctx context.Context, projectID int64, taskCategoryID int64) error {
|
||||
project := &Project{}
|
||||
project.ID = projectID
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
taskCategory := &TaskCategory{}
|
||||
taskCategory.ID = taskCategoryID
|
||||
|
||||
var totalAssociatedTasks int
|
||||
if err := tx.Model(&Task{}).Where("category_id = ?", taskCategoryID).Count(&totalAssociatedTasks).Error; err != nil {
|
||||
return errors.Wrap(err, "could not count associated tasks")
|
||||
}
|
||||
|
||||
if totalAssociatedTasks != 0 {
|
||||
return errors.WithStack(ErrAssociatedTaskExist)
|
||||
}
|
||||
|
||||
err := tx.Model(project).Association("TaskCategories").Delete(taskCategory).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not remove task category relationship")
|
||||
}
|
||||
|
||||
err = tx.Delete(taskCategory, "id = ? AND project_id = ?", taskCategoryID, projectID).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not delete task category")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not remove task category")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ProjectRepository) UpdateTaskCategory(ctx context.Context, projectID, taskCategoryID int64, changes ProjectTaskCategoryChanges) (*TaskCategory, error) {
|
||||
taskCategory := &TaskCategory{}
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Model(taskCategory).
|
||||
First(taskCategory, "id = ? AND project_id = ?", taskCategoryID, projectID).
|
||||
Error
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := updateTaskCategoryWithChanges(tx, taskCategory, changes); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := tx.Save(taskCategory).Error; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not update task category")
|
||||
}
|
||||
|
||||
return taskCategory, nil
|
||||
}
|
||||
|
||||
func updateTaskWithChanges(db *gorm.DB, task *Task, changes ProjectTaskChanges) error {
|
||||
if changes.Label != nil {
|
||||
task.Label = changes.Label
|
||||
|
@ -330,6 +414,18 @@ func updateTaskWithChanges(db *gorm.DB, task *Task, changes ProjectTaskChanges)
|
|||
return nil
|
||||
}
|
||||
|
||||
func updateTaskCategoryWithChanges(db *gorm.DB, taskCategory *TaskCategory, changes ProjectTaskCategoryChanges) error {
|
||||
if changes.Label != nil {
|
||||
taskCategory.Label = *changes.Label
|
||||
}
|
||||
|
||||
if changes.CostPerTimeUnit != nil {
|
||||
taskCategory.CostPerTimeUnit = *changes.CostPerTimeUnit
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewProjectRepository(db *gorm.DB) *ProjectRepository {
|
||||
return &ProjectRepository{db}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue