diff --git a/api/graph/generated.go b/api/graph/generated.go index aac0788..97b0880 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -86,10 +86,12 @@ type ComplexityRoot struct { DeleteTaskGroup func(childComplexity int, input DeleteTaskGroupInput) int LogoutUser func(childComplexity int, input LogoutUser) int RemoveTaskLabel func(childComplexity int, input *RemoveTaskLabelInput) int + ToggleTaskLabel func(childComplexity int, input ToggleTaskLabelInput) int UnassignTask func(childComplexity int, input *UnassignTaskInput) int UpdateProjectLabel func(childComplexity int, input UpdateProjectLabel) int UpdateProjectLabelColor func(childComplexity int, input UpdateProjectLabelColor) int UpdateProjectLabelName func(childComplexity int, input UpdateProjectLabelName) int + UpdateProjectName func(childComplexity int, input *UpdateProjectName) int UpdateTaskDescription func(childComplexity int, input UpdateTaskDescriptionInput) int UpdateTaskGroupLocation func(childComplexity int, input NewTaskGroupLocation) int UpdateTaskLocation func(childComplexity int, input NewTaskLocation) int @@ -166,11 +168,9 @@ type ComplexityRoot struct { } TaskLabel struct { - AssignedDate func(childComplexity int) int - ColorHex func(childComplexity int) int - ID func(childComplexity int) int - Name func(childComplexity int) int - ProjectLabelID func(childComplexity int) int + AssignedDate func(childComplexity int) int + ID func(childComplexity int) int + ProjectLabel func(childComplexity int) int } Team struct { @@ -179,6 +179,11 @@ type ComplexityRoot struct { Name func(childComplexity int) int } + ToggleTaskLabelPayload struct { + Active func(childComplexity int) int + Task func(childComplexity int) int + } + UserAccount struct { CreatedAt func(childComplexity int) int Email func(childComplexity int) int @@ -198,6 +203,7 @@ type MutationResolver interface { CreateUserAccount(ctx context.Context, input NewUserAccount) (*pg.UserAccount, error) CreateTeam(ctx context.Context, input NewTeam) (*pg.Team, error) CreateProject(ctx context.Context, input NewProject) (*pg.Project, error) + UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*pg.Project, error) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*pg.ProjectLabel, error) DeleteProjectLabel(ctx context.Context, input DeleteProjectLabel) (*pg.ProjectLabel, error) UpdateProjectLabel(ctx context.Context, input UpdateProjectLabel) (*pg.ProjectLabel, error) @@ -208,6 +214,7 @@ type MutationResolver interface { DeleteTaskGroup(ctx context.Context, input DeleteTaskGroupInput) (*DeleteTaskGroupPayload, error) AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*pg.Task, error) RemoveTaskLabel(ctx context.Context, input *RemoveTaskLabelInput) (*pg.Task, error) + ToggleTaskLabel(ctx context.Context, input ToggleTaskLabelInput) (*ToggleTaskLabelPayload, error) CreateTask(ctx context.Context, input NewTask) (*pg.Task, error) UpdateTaskDescription(ctx context.Context, input UpdateTaskDescriptionInput) (*pg.Task, error) UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*pg.Task, error) @@ -261,9 +268,7 @@ type TaskGroupResolver interface { } type TaskLabelResolver interface { ID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUID, error) - - ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error) - Name(ctx context.Context, obj *pg.TaskLabel) (*string, error) + ProjectLabel(ctx context.Context, obj *pg.TaskLabel) (*pg.ProjectLabel, error) } type TeamResolver interface { ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error) @@ -513,6 +518,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.RemoveTaskLabel(childComplexity, args["input"].(*RemoveTaskLabelInput)), true + case "Mutation.toggleTaskLabel": + if e.complexity.Mutation.ToggleTaskLabel == nil { + break + } + + args, err := ec.field_Mutation_toggleTaskLabel_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.ToggleTaskLabel(childComplexity, args["input"].(ToggleTaskLabelInput)), true + case "Mutation.unassignTask": if e.complexity.Mutation.UnassignTask == nil { break @@ -561,6 +578,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.UpdateProjectLabelName(childComplexity, args["input"].(UpdateProjectLabelName)), true + case "Mutation.updateProjectName": + if e.complexity.Mutation.UpdateProjectName == nil { + break + } + + args, err := ec.field_Mutation_updateProjectName_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UpdateProjectName(childComplexity, args["input"].(*UpdateProjectName)), true + case "Mutation.updateTaskDescription": if e.complexity.Mutation.UpdateTaskDescription == nil { break @@ -951,13 +980,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.TaskLabel.AssignedDate(childComplexity), true - case "TaskLabel.colorHex": - if e.complexity.TaskLabel.ColorHex == nil { - break - } - - return e.complexity.TaskLabel.ColorHex(childComplexity), true - case "TaskLabel.id": if e.complexity.TaskLabel.ID == nil { break @@ -965,19 +987,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.TaskLabel.ID(childComplexity), true - case "TaskLabel.name": - if e.complexity.TaskLabel.Name == nil { + case "TaskLabel.projectLabel": + if e.complexity.TaskLabel.ProjectLabel == nil { break } - return e.complexity.TaskLabel.Name(childComplexity), true - - case "TaskLabel.projectLabelID": - if e.complexity.TaskLabel.ProjectLabelID == nil { - break - } - - return e.complexity.TaskLabel.ProjectLabelID(childComplexity), true + return e.complexity.TaskLabel.ProjectLabel(childComplexity), true case "Team.createdAt": if e.complexity.Team.CreatedAt == nil { @@ -1000,6 +1015,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Team.Name(childComplexity), true + case "ToggleTaskLabelPayload.active": + if e.complexity.ToggleTaskLabelPayload.Active == nil { + break + } + + return e.complexity.ToggleTaskLabelPayload.Active(childComplexity), true + + case "ToggleTaskLabelPayload.task": + if e.complexity.ToggleTaskLabelPayload.Task == nil { + break + } + + return e.complexity.ToggleTaskLabelPayload.Task(childComplexity), true + case "UserAccount.createdAt": if e.complexity.UserAccount.CreatedAt == nil { break @@ -1132,10 +1161,8 @@ type LabelColor { type TaskLabel { id: ID! - projectLabelID: UUID! + projectLabel: ProjectLabel! assignedDate: Time! - colorHex: String! - name: String } type ProfileIcon { @@ -1319,11 +1346,10 @@ input UpdateTaskDescriptionInput { input AddTaskLabelInput { taskID: UUID! - labelColorID: UUID! + projectLabelID: UUID! } input RemoveTaskLabelInput { - taskID: UUID! taskLabelID: UUID! } @@ -1353,6 +1379,21 @@ input UpdateProjectLabelColor { labelColorID: UUID! } +input ToggleTaskLabelInput { + taskID: UUID! + projectLabelID: UUID! +} + +type ToggleTaskLabelPayload { + active: Boolean! + task: Task! +} + +input UpdateProjectName { + projectID: UUID! + name: String! +} + type Mutation { createRefreshToken(input: NewRefreshToken!): RefreshToken! @@ -1361,6 +1402,7 @@ type Mutation { createTeam(input: NewTeam!): Team! createProject(input: NewProject!): Project! + updateProjectName(input: UpdateProjectName): Project! createProjectLabel(input: NewProjectLabel!): ProjectLabel! deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel! @@ -1374,6 +1416,7 @@ type Mutation { addTaskLabel(input: AddTaskLabelInput): Task! removeTaskLabel(input: RemoveTaskLabelInput): Task! + toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload! createTask(input: NewTask!): Task! updateTaskDescription(input: UpdateTaskDescriptionInput!): Task! @@ -1589,6 +1632,20 @@ func (ec *executionContext) field_Mutation_removeTaskLabel_args(ctx context.Cont return args, nil } +func (ec *executionContext) field_Mutation_toggleTaskLabel_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 ToggleTaskLabelInput + if tmp, ok := rawArgs["input"]; ok { + arg0, err = ec.unmarshalNToggleTaskLabelInput2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_unassignTask_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1645,6 +1702,20 @@ func (ec *executionContext) field_Mutation_updateProjectLabel_args(ctx context.C return args, nil } +func (ec *executionContext) field_Mutation_updateProjectName_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *UpdateProjectName + if tmp, ok := rawArgs["input"]; ok { + arg0, err = ec.unmarshalOUpdateProjectName2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateProjectName(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_updateTaskDescription_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2243,6 +2314,47 @@ func (ec *executionContext) _Mutation_createProject(ctx context.Context, field g return ec.marshalNProject2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProject(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_updateProjectName(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_updateProjectName_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdateProjectName(rctx, args["input"].(*UpdateProjectName)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*pg.Project) + fc.Result = res + return ec.marshalNProject2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProject(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_createProjectLabel(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2653,6 +2765,47 @@ func (ec *executionContext) _Mutation_removeTaskLabel(ctx context.Context, field return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_toggleTaskLabel(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_toggleTaskLabel_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().ToggleTaskLabel(rctx, args["input"].(ToggleTaskLabelInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*ToggleTaskLabelPayload) + fc.Result = res + return ec.marshalNToggleTaskLabelPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelPayload(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_createTask(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4627,7 +4780,7 @@ func (ec *executionContext) _TaskLabel_id(ctx context.Context, field graphql.Col return ec.marshalNID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res) } -func (ec *executionContext) _TaskLabel_projectLabelID(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) { +func (ec *executionContext) _TaskLabel_projectLabel(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -4638,13 +4791,13 @@ func (ec *executionContext) _TaskLabel_projectLabelID(ctx context.Context, field Object: "TaskLabel", Field: field, Args: nil, - IsMethod: false, + IsMethod: true, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.ProjectLabelID, nil + return ec.resolvers.TaskLabel().ProjectLabel(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -4656,9 +4809,9 @@ func (ec *executionContext) _TaskLabel_projectLabelID(ctx context.Context, field } return graphql.Null } - res := resTmp.(uuid.UUID) + res := resTmp.(*pg.ProjectLabel) fc.Result = res - return ec.marshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res) + return ec.marshalNProjectLabel2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProjectLabel(ctx, field.Selections, res) } func (ec *executionContext) _TaskLabel_assignedDate(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) { @@ -4695,71 +4848,6 @@ func (ec *executionContext) _TaskLabel_assignedDate(ctx context.Context, field g return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _TaskLabel_colorHex(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "TaskLabel", - Field: field, - Args: nil, - IsMethod: true, - } - - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.TaskLabel().ColorHex(rctx, obj) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - -func (ec *executionContext) _TaskLabel_name(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "TaskLabel", - Field: field, - Args: nil, - IsMethod: true, - } - - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.TaskLabel().Name(rctx, obj) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - return graphql.Null - } - res := resTmp.(*string) - fc.Result = res - return ec.marshalOString2ᚖstring(ctx, field.Selections, res) -} - func (ec *executionContext) _Team_id(ctx context.Context, field graphql.CollectedField, obj *pg.Team) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4862,6 +4950,74 @@ func (ec *executionContext) _Team_name(ctx context.Context, field graphql.Collec return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _ToggleTaskLabelPayload_active(ctx context.Context, field graphql.CollectedField, obj *ToggleTaskLabelPayload) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ToggleTaskLabelPayload", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Active, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) _ToggleTaskLabelPayload_task(ctx context.Context, field graphql.CollectedField, obj *ToggleTaskLabelPayload) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ToggleTaskLabelPayload", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Task, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*pg.Task) + fc.Result = res + return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res) +} + func (ec *executionContext) _UserAccount_id(ctx context.Context, field graphql.CollectedField, obj *pg.UserAccount) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6167,9 +6323,9 @@ func (ec *executionContext) unmarshalInputAddTaskLabelInput(ctx context.Context, if err != nil { return it, err } - case "labelColorID": + case "projectLabelID": var err error - it.LabelColorID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) + it.ProjectLabelID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) if err != nil { return it, err } @@ -6609,6 +6765,24 @@ func (ec *executionContext) unmarshalInputRemoveTaskLabelInput(ctx context.Conte var it RemoveTaskLabelInput var asMap = obj.(map[string]interface{}) + for k, v := range asMap { + switch k { + case "taskLabelID": + var err error + it.TaskLabelID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputToggleTaskLabelInput(ctx context.Context, obj interface{}) (ToggleTaskLabelInput, error) { + var it ToggleTaskLabelInput + var asMap = obj.(map[string]interface{}) + for k, v := range asMap { switch k { case "taskID": @@ -6617,9 +6791,9 @@ func (ec *executionContext) unmarshalInputRemoveTaskLabelInput(ctx context.Conte if err != nil { return it, err } - case "taskLabelID": + case "projectLabelID": var err error - it.TaskLabelID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) + it.ProjectLabelID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) if err != nil { return it, err } @@ -6731,6 +6905,30 @@ func (ec *executionContext) unmarshalInputUpdateProjectLabelName(ctx context.Con return it, nil } +func (ec *executionContext) unmarshalInputUpdateProjectName(ctx context.Context, obj interface{}) (UpdateProjectName, error) { + var it UpdateProjectName + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "projectID": + var err error + it.ProjectID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v) + if err != nil { + return it, err + } + case "name": + var err error + it.Name, err = ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputUpdateTaskDescriptionInput(ctx context.Context, obj interface{}) (UpdateTaskDescriptionInput, error) { var it UpdateTaskDescriptionInput var asMap = obj.(map[string]interface{}) @@ -6937,6 +7135,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "updateProjectName": + out.Values[i] = ec._Mutation_updateProjectName(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "createProjectLabel": out.Values[i] = ec._Mutation_createProjectLabel(ctx, field) if out.Values[i] == graphql.Null { @@ -6987,6 +7190,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "toggleTaskLabel": + out.Values[i] = ec._Mutation_toggleTaskLabel(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "createTask": out.Values[i] = ec._Mutation_createTask(ctx, field) if out.Values[i] == graphql.Null { @@ -7691,17 +7899,7 @@ func (ec *executionContext) _TaskLabel(ctx context.Context, sel ast.SelectionSet } return res }) - case "projectLabelID": - out.Values[i] = ec._TaskLabel_projectLabelID(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - case "assignedDate": - out.Values[i] = ec._TaskLabel_assignedDate(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - case "colorHex": + case "projectLabel": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -7709,23 +7907,17 @@ func (ec *executionContext) _TaskLabel(ctx context.Context, sel ast.SelectionSet ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._TaskLabel_colorHex(ctx, field, obj) + res = ec._TaskLabel_projectLabel(ctx, field, obj) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } return res }) - case "name": - field := field - out.Concurrently(i, func() (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._TaskLabel_name(ctx, field, obj) - return res - }) + case "assignedDate": + out.Values[i] = ec._TaskLabel_assignedDate(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -7783,6 +7975,38 @@ func (ec *executionContext) _Team(ctx context.Context, sel ast.SelectionSet, obj return out } +var toggleTaskLabelPayloadImplementors = []string{"ToggleTaskLabelPayload"} + +func (ec *executionContext) _ToggleTaskLabelPayload(ctx context.Context, sel ast.SelectionSet, obj *ToggleTaskLabelPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, toggleTaskLabelPayloadImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ToggleTaskLabelPayload") + case "active": + out.Values[i] = ec._ToggleTaskLabelPayload_active(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "task": + out.Values[i] = ec._ToggleTaskLabelPayload_task(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var userAccountImplementors = []string{"UserAccount"} func (ec *executionContext) _UserAccount(ctx context.Context, sel ast.SelectionSet, obj *pg.UserAccount) graphql.Marshaler { @@ -8668,6 +8892,24 @@ func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel as return res } +func (ec *executionContext) unmarshalNToggleTaskLabelInput2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelInput(ctx context.Context, v interface{}) (ToggleTaskLabelInput, error) { + return ec.unmarshalInputToggleTaskLabelInput(ctx, v) +} + +func (ec *executionContext) marshalNToggleTaskLabelPayload2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelPayload(ctx context.Context, sel ast.SelectionSet, v ToggleTaskLabelPayload) graphql.Marshaler { + return ec._ToggleTaskLabelPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNToggleTaskLabelPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelPayload(ctx context.Context, sel ast.SelectionSet, v *ToggleTaskLabelPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._ToggleTaskLabelPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx context.Context, v interface{}) (uuid.UUID, error) { return UnmarshalUUID(v) } @@ -9085,6 +9327,18 @@ func (ec *executionContext) unmarshalOUnassignTaskInput2ᚖgithubᚗcomᚋjordan return &res, err } +func (ec *executionContext) unmarshalOUpdateProjectName2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateProjectName(ctx context.Context, v interface{}) (UpdateProjectName, error) { + return ec.unmarshalInputUpdateProjectName(ctx, v) +} + +func (ec *executionContext) unmarshalOUpdateProjectName2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateProjectName(ctx context.Context, v interface{}) (*UpdateProjectName, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalOUpdateProjectName2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateProjectName(ctx, v) + return &res, err +} + func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/api/graph/models_gen.go b/api/graph/models_gen.go index a2f5972..70dc61b 100644 --- a/api/graph/models_gen.go +++ b/api/graph/models_gen.go @@ -8,8 +8,8 @@ import ( ) type AddTaskLabelInput struct { - TaskID uuid.UUID `json:"taskID"` - LabelColorID uuid.UUID `json:"labelColorID"` + TaskID uuid.UUID `json:"taskID"` + ProjectLabelID uuid.UUID `json:"projectLabelID"` } type AssignTaskInput struct { @@ -125,10 +125,19 @@ type ProjectsFilter struct { } type RemoveTaskLabelInput struct { - TaskID uuid.UUID `json:"taskID"` TaskLabelID uuid.UUID `json:"taskLabelID"` } +type ToggleTaskLabelInput struct { + TaskID uuid.UUID `json:"taskID"` + ProjectLabelID uuid.UUID `json:"projectLabelID"` +} + +type ToggleTaskLabelPayload struct { + Active bool `json:"active"` + Task *pg.Task `json:"task"` +} + type UnassignTaskInput struct { TaskID uuid.UUID `json:"taskID"` UserID uuid.UUID `json:"userID"` @@ -150,6 +159,11 @@ type UpdateProjectLabelName struct { Name string `json:"name"` } +type UpdateProjectName struct { + ProjectID uuid.UUID `json:"projectID"` + Name string `json:"name"` +} + type UpdateTaskDescriptionInput struct { TaskID uuid.UUID `json:"taskID"` Description string `json:"description"` diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 66b3b13..164ed5e 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -17,10 +17,8 @@ type LabelColor { type TaskLabel { id: ID! - projectLabelID: UUID! + projectLabel: ProjectLabel! assignedDate: Time! - colorHex: String! - name: String } type ProfileIcon { @@ -204,11 +202,10 @@ input UpdateTaskDescriptionInput { input AddTaskLabelInput { taskID: UUID! - labelColorID: UUID! + projectLabelID: UUID! } input RemoveTaskLabelInput { - taskID: UUID! taskLabelID: UUID! } @@ -238,6 +235,21 @@ input UpdateProjectLabelColor { labelColorID: UUID! } +input ToggleTaskLabelInput { + taskID: UUID! + projectLabelID: UUID! +} + +type ToggleTaskLabelPayload { + active: Boolean! + task: Task! +} + +input UpdateProjectName { + projectID: UUID! + name: String! +} + type Mutation { createRefreshToken(input: NewRefreshToken!): RefreshToken! @@ -246,6 +258,7 @@ type Mutation { createTeam(input: NewTeam!): Team! createProject(input: NewProject!): Project! + updateProjectName(input: UpdateProjectName): Project! createProjectLabel(input: NewProjectLabel!): ProjectLabel! deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel! @@ -259,6 +272,7 @@ type Mutation { addTaskLabel(input: AddTaskLabelInput): Task! removeTaskLabel(input: RemoveTaskLabelInput): Task! + toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload! createTask(input: NewTask!): Task! updateTaskDescription(input: UpdateTaskDescriptionInput!): Task! diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index d1723bc..08462d5 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -49,6 +49,14 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject) return &project, err } +func (r *mutationResolver) UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*pg.Project, error) { + project, err := r.Repository.UpdateProjectNameByID(ctx, pg.UpdateProjectNameByIDParams{ProjectID: input.ProjectID, Name: input.Name}) + if err != nil { + return &pg.Project{}, err + } + return &project, nil +} + func (r *mutationResolver) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*pg.ProjectLabel, error) { createdAt := time.Now().UTC() @@ -130,7 +138,7 @@ func (r *mutationResolver) DeleteTaskGroup(ctx context.Context, input DeleteTask func (r *mutationResolver) AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*pg.Task, error) { assignedDate := time.Now().UTC() - _, err := r.Repository.CreateTaskLabelForTask(ctx, pg.CreateTaskLabelForTaskParams{input.TaskID, input.LabelColorID, assignedDate}) + _, err := r.Repository.CreateTaskLabelForTask(ctx, pg.CreateTaskLabelForTaskParams{input.TaskID, input.ProjectLabelID, assignedDate}) if err != nil { return &pg.Task{}, err } @@ -139,7 +147,56 @@ func (r *mutationResolver) AddTaskLabel(ctx context.Context, input *AddTaskLabel } func (r *mutationResolver) RemoveTaskLabel(ctx context.Context, input *RemoveTaskLabelInput) (*pg.Task, error) { - panic(fmt.Errorf("not implemented")) + taskLabel, err := r.Repository.GetTaskLabelByID(ctx, input.TaskLabelID) + if err != nil { + return &pg.Task{}, err + } + task, err := r.Repository.GetTaskByID(ctx, taskLabel.TaskID) + if err != nil { + return &pg.Task{}, err + } + err = r.Repository.DeleteTaskLabelByID(ctx, input.TaskLabelID) + return &task, err +} + +func (r *mutationResolver) ToggleTaskLabel(ctx context.Context, input ToggleTaskLabelInput) (*ToggleTaskLabelPayload, error) { + task, err := r.Repository.GetTaskByID(ctx, input.TaskID) + if err != nil { + return &ToggleTaskLabelPayload{}, err + } + + _, err = r.Repository.GetTaskLabelForTaskByProjectLabelID(ctx, pg.GetTaskLabelForTaskByProjectLabelIDParams{TaskID: input.TaskID, ProjectLabelID: input.ProjectLabelID}) + createdAt := time.Now().UTC() + + if err == sql.ErrNoRows { + log.WithFields(log.Fields{"err": err}).Warning("no rows") + _, err := r.Repository.CreateTaskLabelForTask(ctx, pg.CreateTaskLabelForTaskParams{ + TaskID: input.TaskID, + ProjectLabelID: input.ProjectLabelID, + AssignedDate: createdAt, + }) + if err != nil { + return &ToggleTaskLabelPayload{}, err + } + payload := ToggleTaskLabelPayload{Active: true, Task: &task} + return &payload, nil + } + + if err != nil { + return &ToggleTaskLabelPayload{}, err + } + + err = r.Repository.DeleteTaskLabelForTaskByProjectLabelID(ctx, pg.DeleteTaskLabelForTaskByProjectLabelIDParams{ + TaskID: input.TaskID, + ProjectLabelID: input.ProjectLabelID, + }) + + if err != nil { + return &ToggleTaskLabelPayload{}, err + } + + payload := ToggleTaskLabelPayload{Active: false, Task: &task} + return &payload, nil } func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*pg.Task, error) { @@ -434,28 +491,9 @@ func (r *taskLabelResolver) ID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUI return obj.TaskLabelID, nil } -func (r *taskLabelResolver) ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error) { +func (r *taskLabelResolver) ProjectLabel(ctx context.Context, obj *pg.TaskLabel) (*pg.ProjectLabel, error) { projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID) - if err != nil { - return "", err - } - labelColor, err := r.Repository.GetLabelColorByID(ctx, projectLabel.LabelColorID) - if err != nil { - return "", err - } - return labelColor.ColorHex, nil -} - -func (r *taskLabelResolver) Name(ctx context.Context, obj *pg.TaskLabel) (*string, error) { - projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID) - if err != nil { - return nil, err - } - name := projectLabel.Name - if !name.Valid { - return nil, err - } - return &name.String, err + return &projectLabel, err } func (r *teamResolver) ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error) { @@ -523,6 +561,28 @@ type userAccountResolver struct{ *Resolver } // - When renaming or deleting a resolver the old code will be put in here. You can safely delete // it when you're done. // - You have helper methods in this file. Move them out to keep these resolver files clean. +func (r *taskLabelResolver) ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error) { + projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID) + if err != nil { + return "", err + } + labelColor, err := r.Repository.GetLabelColorByID(ctx, projectLabel.LabelColorID) + if err != nil { + return "", err + } + return labelColor.ColorHex, nil +} +func (r *taskLabelResolver) Name(ctx context.Context, obj *pg.TaskLabel) (*string, error) { + projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID) + if err != nil { + return nil, err + } + name := projectLabel.Name + if !name.Valid { + return nil, err + } + return &name.String, err +} func (r *projectLabelResolver) ColorHex(ctx context.Context, obj *pg.ProjectLabel) (string, error) { labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID) if err != nil { diff --git a/api/migrations/0019_add-unique-constraint-project-label-and-task-on-task-label.up.sql b/api/migrations/0019_add-unique-constraint-project-label-and-task-on-task-label.up.sql new file mode 100644 index 0000000..30d559c --- /dev/null +++ b/api/migrations/0019_add-unique-constraint-project-label-and-task-on-task-label.up.sql @@ -0,0 +1 @@ +ALTER TABLE task_label ADD UNIQUE (project_label_id, task_id); diff --git a/api/pg/pg.go b/api/pg/pg.go index 3e826ec..0a97df7 100644 --- a/api/pg/pg.go +++ b/api/pg/pg.go @@ -22,6 +22,13 @@ type Repository interface { GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error) + GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error) + + DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error + GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error) + UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error) + + DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error) GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error) GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error) diff --git a/api/pg/project.sql.go b/api/pg/project.sql.go index 1b7f965..daa6061 100644 --- a/api/pg/project.sql.go +++ b/api/pg/project.sql.go @@ -121,3 +121,25 @@ func (q *Queries) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Proj ) return i, err } + +const updateProjectNameByID = `-- name: UpdateProjectNameByID :one +UPDATE project SET name = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name, owner +` + +type UpdateProjectNameByIDParams struct { + ProjectID uuid.UUID `json:"project_id"` + Name string `json:"name"` +} + +func (q *Queries) UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error) { + row := q.db.QueryRowContext(ctx, updateProjectNameByID, arg.ProjectID, arg.Name) + var i Project + err := row.Scan( + &i.ProjectID, + &i.TeamID, + &i.CreatedAt, + &i.Name, + &i.Owner, + ) + return i, err +} diff --git a/api/pg/querier.go b/api/pg/querier.go index 356afda..03cfa3d 100644 --- a/api/pg/querier.go +++ b/api/pg/querier.go @@ -27,6 +27,8 @@ type Querier interface { DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error) DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error) + DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error + DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error) DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error GetAllOrganizations(ctx context.Context) ([]Organization, error) @@ -46,6 +48,8 @@ type Querier interface { GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error) GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error) GetTaskGroupsForProject(ctx context.Context, projectID uuid.UUID) ([]TaskGroup, error) + GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error) + GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error) GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskLabel, error) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error) GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error) @@ -55,6 +59,7 @@ type Querier interface { UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error) UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error) UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error) + UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error) UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescriptionParams) (Task, error) UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error) UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error) diff --git a/api/pg/task_label.sql.go b/api/pg/task_label.sql.go index 2c07303..dfc7106 100644 --- a/api/pg/task_label.sql.go +++ b/api/pg/task_label.sql.go @@ -33,6 +33,66 @@ func (q *Queries) CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabe return i, err } +const deleteTaskLabelByID = `-- name: DeleteTaskLabelByID :exec +DELETE FROM task_label WHERE task_label_id = $1 +` + +func (q *Queries) DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error { + _, err := q.db.ExecContext(ctx, deleteTaskLabelByID, taskLabelID) + return err +} + +const deleteTaskLabelForTaskByProjectLabelID = `-- name: DeleteTaskLabelForTaskByProjectLabelID :exec +DELETE FROM task_label WHERE project_label_id = $2 AND task_id = $1 +` + +type DeleteTaskLabelForTaskByProjectLabelIDParams struct { + TaskID uuid.UUID `json:"task_id"` + ProjectLabelID uuid.UUID `json:"project_label_id"` +} + +func (q *Queries) DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error { + _, err := q.db.ExecContext(ctx, deleteTaskLabelForTaskByProjectLabelID, arg.TaskID, arg.ProjectLabelID) + return err +} + +const getTaskLabelByID = `-- name: GetTaskLabelByID :one +SELECT task_label_id, task_id, project_label_id, assigned_date FROM task_label WHERE task_label_id = $1 +` + +func (q *Queries) GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error) { + row := q.db.QueryRowContext(ctx, getTaskLabelByID, taskLabelID) + var i TaskLabel + err := row.Scan( + &i.TaskLabelID, + &i.TaskID, + &i.ProjectLabelID, + &i.AssignedDate, + ) + return i, err +} + +const getTaskLabelForTaskByProjectLabelID = `-- name: GetTaskLabelForTaskByProjectLabelID :one +SELECT task_label_id, task_id, project_label_id, assigned_date FROM task_label WHERE task_id = $1 AND project_label_id = $2 +` + +type GetTaskLabelForTaskByProjectLabelIDParams struct { + TaskID uuid.UUID `json:"task_id"` + ProjectLabelID uuid.UUID `json:"project_label_id"` +} + +func (q *Queries) GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error) { + row := q.db.QueryRowContext(ctx, getTaskLabelForTaskByProjectLabelID, arg.TaskID, arg.ProjectLabelID) + var i TaskLabel + err := row.Scan( + &i.TaskLabelID, + &i.TaskID, + &i.ProjectLabelID, + &i.AssignedDate, + ) + return i, err +} + const getTaskLabelsForTaskID = `-- name: GetTaskLabelsForTaskID :many SELECT task_label_id, task_id, project_label_id, assigned_date FROM task_label WHERE task_id = $1 ` diff --git a/api/query/project.sql b/api/query/project.sql index 5514793..53a6df3 100644 --- a/api/query/project.sql +++ b/api/query/project.sql @@ -9,3 +9,6 @@ SELECT * FROM project WHERE project_id = $1; -- name: CreateProject :one INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RETURNING *; + +-- name: UpdateProjectNameByID :one +UPDATE project SET name = $2 WHERE project_id = $1 RETURNING *; diff --git a/api/query/task_label.sql b/api/query/task_label.sql index b014927..fff38da 100644 --- a/api/query/task_label.sql +++ b/api/query/task_label.sql @@ -4,3 +4,15 @@ INSERT INTO task_label (task_id, project_label_id, assigned_date) -- name: GetTaskLabelsForTaskID :many SELECT * FROM task_label WHERE task_id = $1; + +-- name: GetTaskLabelByID :one +SELECT * FROM task_label WHERE task_label_id = $1; + +-- name: DeleteTaskLabelByID :exec +DELETE FROM task_label WHERE task_label_id = $1; + +-- name: GetTaskLabelForTaskByProjectLabelID :one +SELECT * FROM task_label WHERE task_id = $1 AND project_label_id = $2; + +-- name: DeleteTaskLabelForTaskByProjectLabelID :exec +DELETE FROM task_label WHERE project_label_id = $2 AND task_id = $1; diff --git a/web/src/App/TopNavbar.tsx b/web/src/App/TopNavbar.tsx index 6a5d4f2..b246fb4 100644 --- a/web/src/App/TopNavbar.tsx +++ b/web/src/App/TopNavbar.tsx @@ -8,8 +8,9 @@ import { useMeQuery } from 'shared/generated/graphql'; type GlobalTopNavbarProps = { name: string; projectMembers?: null | Array; + onSaveProjectName?: (projectName: string) => void; }; -const GlobalTopNavbar: React.FC = ({ name, projectMembers }) => { +const GlobalTopNavbar: React.FC = ({ name, projectMembers, onSaveProjectName }) => { const { loading, data } = useMeQuery(); const history = useHistory(); const { userID, setUserID } = useContext(UserIDContext); @@ -52,6 +53,7 @@ const GlobalTopNavbar: React.FC = ({ name, projectMembers onNotificationClick={() => {}} projectMembers={projectMembers} onProfileClick={onProfileClick} + onSaveProjectName={onSaveProjectName} /> {menu.isOpen && ( = ({ if (!data) { return
loading
; } - const taskMembers = data.findTask.assigned.map(assigned => { - return { - userID: assigned.id, - displayName: `${assigned.firstName} ${assigned.lastName}`, - profileIcon: { - url: null, - initials: assigned.profileIcon.initials ?? null, - bgColor: assigned.profileIcon.bgColor ?? null, - }, - }; - }); return ( <> = ({ renderContent={() => { return ( history.push(projectURL)} onMemberProfile={($targetRef, memberID) => { + const member = data.findTask.assigned.find(m => m.id === memberID); + const profileIcon = member ? member.profileIcon : null; showPopup( $targetRef, {}} tab={0}> = ({ {}}> { if (isActive) { assignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } }); diff --git a/web/src/Projects/Project/KanbanBoard/index.tsx b/web/src/Projects/Project/KanbanBoard/index.tsx index 17252e1..716b0e5 100644 --- a/web/src/Projects/Project/KanbanBoard/index.tsx +++ b/web/src/Projects/Project/KanbanBoard/index.tsx @@ -5,7 +5,6 @@ import Lists from 'shared/components/Lists'; import { Board } from './Styles'; type KanbanBoardProps = { - listsData: BoardState; onOpenListActionsPopup: ($targetRef: React.RefObject, taskGroupID: string) => void; onCardDrop: (task: Task) => void; onListDrop: (taskGroup: TaskGroup) => void; @@ -16,7 +15,6 @@ type KanbanBoardProps = { }; const KanbanBoard: React.FC = ({ - listsData, onOpenListActionsPopup, onQuickEditorOpen, onCardCreate, @@ -27,25 +25,7 @@ const KanbanBoard: React.FC = ({ }) => { const match = useRouteMatch(); const history = useHistory(); - return ( - - { - history.push(`${match.url}/c/${task.taskID}`); - }} - onExtraMenuOpen={(taskGroupID, $targetRef) => { - onOpenListActionsPopup($targetRef, taskGroupID); - }} - onQuickEditorOpen={onQuickEditorOpen} - onCardCreate={onCardCreate} - onCardDrop={onCardDrop} - onListDrop={onListDrop} - {...listsData} - onCreateList={onCreateList} - onCardMemberClick={onCardMemberClick} - /> - - ); + return ; }; export default KanbanBoard; diff --git a/web/src/Projects/Project/index.tsx b/web/src/Projects/Project/index.tsx index 365e326..457ecd1 100644 --- a/web/src/Projects/Project/index.tsx +++ b/web/src/Projects/Project/index.tsx @@ -1,11 +1,12 @@ import React, { useState, useRef } from 'react'; -import * as BoardStateUtils from 'shared/utils/boardState'; import GlobalTopNavbar from 'App/TopNavbar'; import styled from 'styled-components/macro'; import { Bolt, ToggleOn, Tags } from 'shared/icons'; import { usePopup, Popup } from 'shared/components/PopupMenu'; import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom'; import { + useToggleTaskLabelMutation, + useUpdateProjectNameMutation, useFindProjectQuery, useUpdateTaskNameMutation, useUpdateProjectLabelMutation, @@ -29,12 +30,14 @@ import ListActions from 'shared/components/ListActions'; import MemberManager from 'shared/components/MemberManager'; import { LabelsPopup } from 'shared/components/PopupMenu/PopupMenu.stories'; import KanbanBoard from 'Projects/Project/KanbanBoard'; +import SimpleLists from 'shared/components/Lists'; import { mixin } from 'shared/utils/styles'; import LabelManager from 'shared/components/PopupMenu/LabelManager'; import LabelEditor from 'shared/components/PopupMenu/LabelEditor'; import produce from 'immer'; import MiniProfile from 'shared/components/MiniProfile'; import Details from './Details'; +import { useApolloClient } from '@apollo/react-hooks'; const getCacheData = (client: any, projectID: string) => { const cacheData: any = client.readQuery({ @@ -85,12 +88,20 @@ const ProjectMembers = styled.div` `; type LabelManagerEditorProps = { - labels: React.RefObject>; + labels: React.RefObject>; + taskLabels: null | React.RefObject>; projectID: string; labelColors: Array; + onLabelToggle?: (labelId: string) => void; }; -const LabelManagerEditor: React.FC = ({ labels: labelsRef, projectID, labelColors }) => { +const LabelManagerEditor: React.FC = ({ + labels: labelsRef, + projectID, + labelColors, + onLabelToggle, + taskLabels: taskLabelsRef, +}) => { const [currentLabel, setCurrentLabel] = useState(''); const [createProjectLabel] = useCreateProjectLabelMutation({ update: (client, newLabelData) => { @@ -116,12 +127,16 @@ const LabelManagerEditor: React.FC = ({ labels: labelsR }, }); const labels = labelsRef.current ? labelsRef.current : []; + const taskLabels = taskLabelsRef && taskLabelsRef.current ? taskLabelsRef.current : []; + const [currentTaskLabels, setCurrentTaskLabels] = useState(taskLabels); + console.log(taskLabels); const { setTab } = usePopup(); return ( <> {}}> { setTab(2); }} @@ -130,18 +145,34 @@ const LabelManagerEditor: React.FC = ({ labels: labelsR setTab(1); }} onLabelToggle={labelId => { - setCurrentLabel(labelId); - setTab(1); + if (onLabelToggle) { + if (currentTaskLabels.find(t => t.projectLabel.id === labelId)) { + setCurrentTaskLabels(currentTaskLabels.filter(t => t.projectLabel.id !== labelId)); + } else { + const newProjectLabel = labels.find(l => l.id === labelId); + if (newProjectLabel) { + setCurrentTaskLabels([ + ...currentTaskLabels, + { id: '', assignedDate: '', projectLabel: { ...newProjectLabel } }, + ]); + } + } + setCurrentLabel(labelId); + onLabelToggle(labelId); + } else { + setCurrentLabel(labelId); + setTab(1); + } }} /> {}} title="Edit label" tab={1}> label.labelId === currentLabel) ?? null} + label={labels.find(label => label.id === currentLabel) ?? null} onLabelEdit={(projectLabelID, name, color) => { if (projectLabelID) { - updateProjectLabel({ variables: { projectLabelID, labelColorID: color.id, name } }); + updateProjectLabel({ variables: { projectLabelID, labelColorID: color.id, name: name ?? '' } }); } setTab(0); }} @@ -156,7 +187,7 @@ const LabelManagerEditor: React.FC = ({ labels: labelsR labelColors={labelColors} label={null} onLabelEdit={(_labelId, name, color) => { - createProjectLabel({ variables: { projectID, labelColorID: color.id, name } }); + createProjectLabel({ variables: { projectID, labelColorID: color.id, name: name ?? '' } }); setTab(0); }} /> @@ -169,10 +200,7 @@ interface ProjectParams { projectID: string; } -const initialState: BoardState = { tasks: {}, columns: {} }; -const initialPopupState = { left: 0, top: 0, isOpen: false, taskGroupID: '' }; const initialQuickCardEditorState: QuickCardEditorState = { isOpen: false, top: 0, left: 0 }; -const initialTaskDetailsState = { isOpen: false, taskID: '' }; const ProjectBar = styled.div` display: flex; @@ -209,18 +237,16 @@ const ProjectActionText = styled.span` const Project = () => { const { projectID } = useParams(); + const history = useHistory(); const match = useRouteMatch(); const [updateTaskDescription] = useUpdateTaskDescriptionMutation(); - const [listsData, setListsData] = useState(initialState); const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState); const [updateTaskLocation] = useUpdateTaskLocationMutation(); - const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation(); + const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({}); const [deleteTaskGroup] = useDeleteTaskGroupMutation({ - onCompleted: deletedTaskGroupData => { - setListsData(BoardStateUtils.deleteTaskGroup(listsData, deletedTaskGroupData.deleteTaskGroup.taskGroup.id)); - }, + onCompleted: deletedTaskGroupData => {}, update: (client, deletedTaskGroupData) => { const cacheData = getCacheData(client, projectID); const newData = { @@ -235,14 +261,7 @@ const Project = () => { }); const [createTaskGroup] = useCreateTaskGroupMutation({ - onCompleted: newTaskGroupData => { - const newTaskGroup = { - taskGroupID: newTaskGroupData.createTaskGroup.id, - tasks: [], - ...newTaskGroupData.createTaskGroup, - }; - setListsData(BoardStateUtils.addTaskGroup(listsData, newTaskGroup)); - }, + onCompleted: newTaskGroupData => {}, update: (client, newTaskGroupData) => { const cacheData = getCacheData(client, projectID); const newData = { @@ -255,15 +274,7 @@ const Project = () => { }); const [createTask] = useCreateTaskMutation({ - onCompleted: newTaskData => { - const newTask = { - ...newTaskData.createTask, - taskID: newTaskData.createTask.id, - taskGroup: { taskGroupID: newTaskData.createTask.taskGroup.id }, - labels: [], - }; - setListsData(BoardStateUtils.addTask(listsData, newTask)); - }, + onCompleted: newTaskData => {}, update: (client, newTaskData) => { const cacheData = getCacheData(client, projectID); const newTaskGroups = produce(cacheData.findProject.taskGroups, (draftState: any) => { @@ -284,158 +295,144 @@ const Project = () => { }); const [deleteTask] = useDeleteTaskMutation({ - onCompleted: deletedTask => { - setListsData(BoardStateUtils.deleteTask(listsData, deletedTask.deleteTask.taskID)); - }, + onCompleted: deletedTask => {}, }); const [updateTaskName] = useUpdateTaskNameMutation({ - onCompleted: newTaskData => { - setListsData( - BoardStateUtils.updateTaskName(listsData, newTaskData.updateTaskName.id, newTaskData.updateTaskName.name), - ); + onCompleted: newTaskData => {}, + }); + const [toggleTaskLabel] = useToggleTaskLabelMutation({ + onCompleted: newTaskLabel => { + taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels; + console.log(taskLabelsRef.current); }, }); const { loading, data, refetch } = useFindProjectQuery({ variables: { projectId: projectID }, + onCompleted: newData => {}, }); const onCardCreate = (taskGroupID: string, name: string) => { - const taskGroupTasks = Object.values(listsData.tasks).filter( - (task: Task) => task.taskGroup.taskGroupID === taskGroupID, - ); - let position = 65535; - if (taskGroupTasks.length !== 0) { - const [lastTask] = taskGroupTasks.sort((a: any, b: any) => a.position - b.position).slice(-1); - position = Math.ceil(lastTask.position) * 2 + 1; - } + if (data) { + const taskGroupTasks = data.findProject.taskGroups.filter(t => t.id === taskGroupID); + if (taskGroupTasks) { + let position = 65535; + if (taskGroupTasks.length !== 0) { + const [lastTask] = taskGroupTasks.sort((a: any, b: any) => a.position - b.position).slice(-1); + position = Math.ceil(lastTask.position) * 2 + 1; + } - createTask({ variables: { taskGroupID, name, position } }); + createTask({ variables: { taskGroupID, name, position } }); + } + } + }; + + const onCreateTask = (taskGroupID: string, name: string) => { + if (data) { + const taskGroup = data.findProject.taskGroups.find(t => t.id === taskGroupID); + console.log(`taskGroup ${taskGroup}`); + if (taskGroup) { + let position = 65535; + if (taskGroup.tasks.length !== 0) { + const [lastTask] = taskGroup.tasks.sort((a: any, b: any) => a.position - b.position).slice(-1); + position = Math.ceil(lastTask.position) * 2 + 1; + } + + console.log(`position ${position}`); + createTask({ variables: { taskGroupID, name, position } }); + } + } }; const onCardDrop = (droppedTask: Task) => { updateTaskLocation({ variables: { - taskID: droppedTask.taskID, - taskGroupID: droppedTask.taskGroup.taskGroupID, + taskID: droppedTask.id, + taskGroupID: droppedTask.taskGroup.id, position: droppedTask.position, }, optimisticResponse: { updateTaskLocation: { name: droppedTask.name, - id: droppedTask.taskID, + id: droppedTask.id, position: droppedTask.position, createdAt: '', }, }, }); - setListsData(BoardStateUtils.updateTask(listsData, droppedTask)); }; const onListDrop = (droppedColumn: TaskGroup) => { - console.log(`list drop ${droppedColumn.taskGroupID}`); + console.log(`list drop ${droppedColumn.id}`); + const cacheData = getCacheData(client, projectID); + const newData = produce(cacheData, (draftState: any) => { + const taskGroupIdx = cacheData.findProject.taskGroups.findIndex((t: any) => t.id === droppedColumn.id); + cacheData.findProject.taskGroups[taskGroupIdx].position = droppedColumn.position; + }); + writeCacheData(client, projectID, cacheData, newData); updateTaskGroupLocation({ - variables: { taskGroupID: droppedColumn.taskGroupID, position: droppedColumn.position }, + variables: { taskGroupID: droppedColumn.id, position: droppedColumn.position }, optimisticResponse: { updateTaskGroupLocation: { - id: droppedColumn.taskGroupID, + id: droppedColumn.id, position: droppedColumn.position, }, }, }); - // setListsData(BoardStateUtils.updateTaskGroup(listsData, droppedColumn)); }; const onCreateList = (listName: string) => { - const [lastColumn] = Object.values(listsData.columns) - .sort((a, b) => a.position - b.position) - .slice(-1); - let position = 65535; - if (lastColumn) { - position = lastColumn.position * 2 + 1; + if (data) { + const [lastColumn] = data.findProject.taskGroups.sort((a, b) => a.position - b.position).slice(-1); + let position = 65535; + if (lastColumn) { + position = lastColumn.position * 2 + 1; + } + createTaskGroup({ variables: { projectID, name: listName, position } }); } - createTaskGroup({ variables: { projectID, name: listName, position } }); }; const [assignTask] = useAssignTaskMutation(); + const [updateProjectName] = useUpdateProjectNameMutation(); + + const client = useApolloClient(); + const { showPopup, hidePopup } = usePopup(); const $labelsRef = useRef(null); - const labelsRef = useRef>([]); + const labelsRef = useRef>([]); + const taskLabelsRef = useRef>([]); if (loading) { return ( <> - - Error Loading + {}} name="Loading..." /> ); } if (data) { - console.log(data); - const currentListsData: BoardState = { tasks: {}, columns: {} }; - data.findProject.taskGroups.forEach(taskGroup => { - currentListsData.columns[taskGroup.id] = { - taskGroupID: taskGroup.id, - name: taskGroup.name, - position: taskGroup.position, - tasks: [], - }; - taskGroup.tasks.forEach(task => { - const taskMembers = task.assigned.map(assigned => { - return { - userID: assigned.id, - displayName: `${assigned.firstName} ${assigned.lastName}`, - profileIcon: { - url: null, - initials: assigned.profileIcon.initials ?? '', - bgColor: assigned.profileIcon.bgColor ?? '#7367F0', - }, - }; - }); - currentListsData.tasks[task.id] = { - taskID: task.id, - taskGroup: { - taskGroupID: taskGroup.id, - }, - name: task.name, - labels: [], - position: task.position, - description: task.description ?? undefined, - members: taskMembers, - }; - }); - }); - const availableMembers = data.findProject.members.map(member => { - return { - displayName: `${member.firstName} ${member.lastName}`, - profileIcon: { - url: null, - initials: member.profileIcon.initials ?? null, - bgColor: member.profileIcon.bgColor ?? null, - }, - userID: member.id, - }; - }); const onQuickEditorOpen = (e: ContextMenuEvent) => { - const currentTask = Object.values(currentListsData.tasks).find(task => task.taskID === e.taskID); - setQuickCardEditor({ - top: e.top, - left: e.left, - isOpen: true, - task: currentTask, - }); + const taskGroup = data.findProject.taskGroups.find(t => t.id === e.taskGroupID); + const currentTask = taskGroup ? taskGroup.tasks.find(t => t.id === e.taskID) : null; + if (currentTask) { + setQuickCardEditor({ + top: e.top, + left: e.left, + isOpen: true, + task: currentTask, + }); + } }; - labelsRef.current = data.findProject.labels.map(label => { - return { - labelId: label.id, - name: label.name ?? '', - labelColor: label.labelColor, - active: false, - }; - }); + labelsRef.current = data.findProject.labels; + return ( <> - + { + updateProjectName({ variables: { projectID, name: projectName } }); + }} + projectMembers={data.findProject.members} + name={data.findProject.name} + /> { onClick={() => { showPopup( $labelsRef, - , + , ); }} > @@ -460,18 +462,53 @@ const Project = () => { - { + history.push(`${match.url}/c/${task.id}`); + }} + onTaskDrop={droppedTask => { + updateTaskLocation({ + variables: { + taskID: droppedTask.id, + taskGroupID: droppedTask.taskGroup.id, + position: droppedTask.position, + }, + optimisticResponse: { + __typename: 'Mutation', + updateTaskLocation: { + name: droppedTask.name, + id: droppedTask.id, + position: droppedTask.position, + createdAt: '', + __typename: 'Task', + }, + }, + }); + }} + onTaskGroupDrop={droppedTaskGroup => { + updateTaskGroupLocation({ + variables: { taskGroupID: droppedTaskGroup.id, position: droppedTaskGroup.position }, + optimisticResponse: { + __typename: 'Mutation', + updateTaskGroupLocation: { + id: droppedTaskGroup.id, + position: droppedTaskGroup.position, + __typename: 'TaskGroup', + }, + }, + }); + }} + taskGroups={data.findProject.taskGroups} + onCreateTask={onCreateTask} + onCreateTaskGroup={onCreateList} onCardMemberClick={($targetRef, taskID, memberID) => { + const member = data.findProject.members.find(m => m.id === memberID); + const profileIcon = member ? member.profileIcon : null; showPopup( $targetRef, {}} tab={0}> { ); }} onQuickEditorOpen={onQuickEditorOpen} - onOpenListActionsPopup={($targetRef, taskGroupID) => { + onExtraMenuOpen={(taskGroupID: string, $targetRef: any) => { showPopup( $targetRef, {}}> @@ -501,8 +538,8 @@ const Project = () => { {quickCardEditor.isOpen && ( setQuickCardEditor(initialQuickCardEditorState)} onEditCard={(_listId: string, cardId: string, cardName: string) => { @@ -537,19 +574,33 @@ const Project = () => { render={(routeProps: RouteComponentProps) => (
{}} - availableMembers={availableMembers} + availableMembers={data.findProject.members} projectURL={match.url} taskID={routeProps.match.params.taskID} onTaskNameChange={(updatedTask, newName) => { - updateTaskName({ variables: { taskID: updatedTask.taskID, name: newName } }); + updateTaskName({ variables: { taskID: updatedTask.id, name: newName } }); }} onTaskDescriptionChange={(updatedTask, newDescription) => { - updateTaskDescription({ variables: { taskID: updatedTask.taskID, description: newDescription } }); + updateTaskDescription({ variables: { taskID: updatedTask.id, description: newDescription } }); }} onDeleteTask={deletedTask => { - deleteTask({ variables: { taskID: deletedTask.taskID } }); + deleteTask({ variables: { taskID: deletedTask.id } }); + }} + onOpenAddLabelPopup={(task, $targetRef) => { + taskLabelsRef.current = task.labels; + showPopup( + $targetRef, + { + toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } }); + }} + labelColors={data.labelColors} + labels={labelsRef} + taskLabels={taskLabelsRef} + projectID={projectID} + />, + ); }} - onOpenAddLabelPopup={(task, $targetRef) => {}} /> )} /> diff --git a/web/src/Projects/index.tsx b/web/src/Projects/index.tsx index 9a2f3b9..ca25ab9 100644 --- a/web/src/Projects/index.tsx +++ b/web/src/Projects/index.tsx @@ -41,7 +41,7 @@ const Projects = () => { const { projects } = data; return ( <> - + {}} name="Projects" /> {projects.map(project => ( diff --git a/web/src/citadel.d.ts b/web/src/citadel.d.ts index d6a9a0e..d61698e 100644 --- a/web/src/citadel.d.ts +++ b/web/src/citadel.d.ts @@ -3,18 +3,6 @@ interface JWTToken { iat: string; exp: string; } -interface ColumnState { - [key: string]: TaskGroup; -} - -interface TaskState { - [key: string]: Task; -} - -interface BoardState { - columns: ColumnState; - tasks: TaskState; -} interface DraggableElement { id: string; @@ -28,66 +16,13 @@ type ContextMenuEvent = { taskGroupID: string; }; -type InnerTaskGroup = { - taskGroupID: string; - name?: string; - position?: number; -}; - -type ProfileIcon = { - url: string | null; - initials: string | null; - bgColor: string | null; -}; - type TaskUser = { - userID: string; - displayName: string; + id: string; + firstName: string; + lastName: string; profileIcon: ProfileIcon; }; -type Task = { - taskID: string; - taskGroup: InnerTaskGroup; - name: string; - position: number; - labels: Label[]; - description?: string | null; - members?: Array; -}; - -type TaskGroup = { - taskGroupID: string; - name: string; - position: number; - tasks: Task[]; -}; - -type Project = { - projectID: string; - name: string; - color?: string; - teamTitle?: string; - taskGroups: TaskGroup[]; -}; - -type Organization = { - name: string; - teams: Team[]; -}; - -type Team = { - name: string; - projects: Project[]; -}; - -type Label = { - labelId: string; - name: string; - labelColor: LabelColor; - active: boolean; -}; - type RefreshTokenResponse = { accessToken: string; }; @@ -117,17 +52,9 @@ type ElementSize = { height: number; }; -type LabelColor = { - id: string; - name: string; - colorHex: string; - position: number; -}; - type OnCardMemberClick = ($targetRef: RefObject, taskID: string, memberID: string) => void; type ElementBounds = { size: ElementSize; position: ElementPosition; }; - diff --git a/web/src/projects.d.ts b/web/src/projects.d.ts new file mode 100644 index 0000000..a8e0e14 --- /dev/null +++ b/web/src/projects.d.ts @@ -0,0 +1,66 @@ +type ProfileIcon = { + url?: string | null; + initials?: string | null; + bgColor?: string | null; +}; + +type TaskGroup = { + id: string; + name: string; + position: number; + tasks: Task[]; +}; + +type LabelColor = { + id: string; + name: string; + colorHex: string; + position: number; +}; + +type InnerTaskGroup = { + id: string; + name?: string; + position?: number; +}; + +type TaskLabel = { + id: string; + assignedDate: string; + projectLabel: ProjectLabel; +}; + +type Task = { + id: string; + taskGroup: InnerTaskGroup; + name: string; + position: number; + labels: TaskLabel[]; + description?: string | null; + assigned?: Array; +}; + +type Project = { + projectID: string; + name: string; + color?: string; + teamTitle?: string; + taskGroups: TaskGroup[]; +}; + +type Organization = { + name: string; + teams: Team[]; +}; + +type Team = { + name: string; + projects: Project[]; +}; + +type ProjectLabel = { + id: string; + createdDate: string; + name?: string | null; + labelColor: LabelColor; +}; diff --git a/web/src/shared/components/Card/Card.stories.tsx b/web/src/shared/components/Card/Card.stories.tsx index ea781e5..d499ec0 100644 --- a/web/src/shared/components/Card/Card.stories.tsx +++ b/web/src/shared/components/Card/Card.stories.tsx @@ -14,28 +14,17 @@ export default { }, }; -const labelData = [ +const labelData: Array = [ { - labelId: 'development', + id: 'development', name: 'Development', + createdDate: new Date().toString(), labelColor: { id: '1', colorHex: LabelColors.BLUE, name: 'blue', position: 1, }, - active: false, - }, - { - labelId: 'general', - name: 'General', - labelColor: { - id: '2', - colorHex: LabelColors.PINK, - name: 'pink', - position: 2, - }, - active: false, }, ]; diff --git a/web/src/shared/components/Card/index.tsx b/web/src/shared/components/Card/index.tsx index 7ea6c37..74b103b 100644 --- a/web/src/shared/components/Card/index.tsx +++ b/web/src/shared/components/Card/index.tsx @@ -47,10 +47,10 @@ const Member: React.FC = ({ onCardMemberClick, taskID, member }) => onClick={e => { if (onCardMemberClick) { e.stopPropagation(); - onCardMemberClick($targetRef, taskID, member.userID); + onCardMemberClick($targetRef, taskID, member.id); } }} - key={member.userID} + key={member.id} bgColor={member.profileIcon.bgColor ?? '#7367F0'} > {member.profileIcon.initials} @@ -68,7 +68,7 @@ type Props = { dueDate?: DueDate; checklists?: Checklist; watched?: boolean; - labels?: Label[]; + labels?: Array; wrapperProps?: any; members?: Array | null; onCardMemberClick?: OnCardMemberClick; @@ -136,7 +136,7 @@ const Card = React.forwardRef( {labels && labels.map(label => ( - + {label.name} ))} @@ -169,7 +169,7 @@ const Card = React.forwardRef( {members && members.map(member => ( - + ))} diff --git a/web/src/shared/components/DueDateManager/DueDateManager.stories.tsx b/web/src/shared/components/DueDateManager/DueDateManager.stories.tsx index 6d476ac..2c66a67 100644 --- a/web/src/shared/components/DueDateManager/DueDateManager.stories.tsx +++ b/web/src/shared/components/DueDateManager/DueDateManager.stories.tsx @@ -17,21 +17,35 @@ export const Default = () => { return ( = [ { - labelId: 'development', + id: 'development', name: 'Development', + createdDate: new Date().toString(), labelColor: { id: '1', colorHex: LabelColors.BLUE, name: 'blue', position: 1, }, - active: false, - }, - { - labelId: 'general', - name: 'General', - labelColor: { - id: '2', - colorHex: LabelColors.PINK, - name: 'pink', - position: 2, - }, - active: false, }, ]; @@ -68,7 +57,6 @@ export const Default = () => { isComposerOpen={false} onSaveName={action('on save name')} onOpenComposer={action('on open composer')} - tasks={[]} onExtraMenuOpen={action('extra menu open')} > @@ -94,7 +82,6 @@ export const WithCardComposer = () => { isComposerOpen onSaveName={action('on save name')} onOpenComposer={action('on open composer')} - tasks={[]} onExtraMenuOpen={action('extra menu open')} > @@ -121,7 +108,6 @@ export const WithCard = () => { isComposerOpen={false} onSaveName={action('on save name')} onOpenComposer={action('on open composer')} - tasks={[]} onExtraMenuOpen={action('extra menu open')} > @@ -160,7 +146,6 @@ export const WithCardAndComposer = () => { isComposerOpen onSaveName={action('on save name')} onOpenComposer={action('on open composer')} - tasks={[]} onExtraMenuOpen={action('extra menu open')} > diff --git a/web/src/shared/components/List/Styles.ts b/web/src/shared/components/List/Styles.ts index b24801d..7ad9640 100644 --- a/web/src/shared/components/List/Styles.ts +++ b/web/src/shared/components/List/Styles.ts @@ -97,9 +97,7 @@ export const Header = styled.div<{ isEditing: boolean }>` props.isEditing && css` & ${HeaderName} { - background: #fff; - border: none; - box-shadow: inset 0 0 0 2px #0079bf; + box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px; } `} `; diff --git a/web/src/shared/components/List/index.tsx b/web/src/shared/components/List/index.tsx index 28d2d9d..9a37379 100644 --- a/web/src/shared/components/List/index.tsx +++ b/web/src/shared/components/List/index.tsx @@ -22,7 +22,6 @@ type Props = { onSaveName: (name: string) => void; isComposerOpen: boolean; onOpenComposer: (id: string) => void; - tasks: Task[]; wrapperProps?: any; headerProps?: any; index?: number; diff --git a/web/src/shared/components/Lists/Lists.stories.tsx b/web/src/shared/components/Lists/Lists.stories.tsx index 7cdc2cd..e4665b4 100644 --- a/web/src/shared/components/Lists/Lists.stories.tsx +++ b/web/src/shared/components/Lists/Lists.stories.tsx @@ -70,14 +70,12 @@ export const Default = () => { ...listsData, tasks: { ...listsData.tasks, - [droppedTask.taskID]: droppedTask, + [droppedTask.id]: droppedTask, }, }; - console.log(newState); setListsData(newState); }; const onListDrop = (droppedColumn: any) => { - console.log(droppedColumn); const newState = { ...listsData, columns: { @@ -85,44 +83,9 @@ export const Default = () => { [droppedColumn.taskGroupID]: droppedColumn, }, }; - console.log(newState); setListsData(newState); }; - return ( - { - const [lastColumn] = Object.values(listsData.columns) - .sort((a, b) => a.position - b.position) - .slice(-1); - let position = 1; - if (lastColumn) { - position = lastColumn.position + 1; - } - const taskGroupID = Math.random().toString(); - const newListsData = { - ...listsData, - columns: { - ...listsData.columns, - [taskGroupID]: { - taskGroupID, - name: listName, - position, - tasks: [], - }, - }, - }; - setListsData(newListsData); - }} - /> - ); + return ; }; const createColumn = (id: any, name: any, position: any) => { @@ -202,13 +165,13 @@ export const ListsWithManyList = () => { }; return ( diff --git a/web/src/shared/components/Lists/Styles.ts b/web/src/shared/components/Lists/Styles.ts index 69b13aa..ee66afc 100644 --- a/web/src/shared/components/Lists/Styles.ts +++ b/web/src/shared/components/Lists/Styles.ts @@ -11,5 +11,7 @@ export const Container = styled.div` export const BoardWrapper = styled.div` display: flex; + margin-top: 12px; + margin-left: 8px; `; export default Container; diff --git a/web/src/shared/components/Lists/index.tsx b/web/src/shared/components/Lists/index.tsx index e346e28..ec3b7d9 100644 --- a/web/src/shared/components/Lists/index.tsx +++ b/web/src/shared/components/Lists/index.tsx @@ -13,37 +13,29 @@ import { import { Container, BoardWrapper } from './Styles'; -interface Columns { - [key: string]: TaskGroup; -} -interface Tasks { - [key: string]: Task; -} +interface SimpleProps { + taskGroups: Array; + onTaskDrop: (task: Task) => void; + onTaskGroupDrop: (taskGroup: TaskGroup) => void; -type Props = { - columns: Columns; - tasks: Tasks; - onCardClick: (task: Task) => void; - onCardDrop: (task: Task) => void; - onListDrop: (taskGroup: TaskGroup) => void; - onCardCreate: (taskGroupID: string, name: string) => void; + onTaskClick: (task: Task) => void; + onCreateTask: (taskGroupID: string, name: string) => void; onQuickEditorOpen: (e: ContextMenuEvent) => void; - onCreateList: (listName: string) => void; + onCreateTaskGroup: (listName: string) => void; onExtraMenuOpen: (taskGroupID: string, $targetRef: React.RefObject) => void; onCardMemberClick: OnCardMemberClick; -}; +} -const Lists: React.FC = ({ - columns, - tasks, - onCardClick, - onCardDrop, - onListDrop, - onCardCreate, +const SimpleLists: React.FC = ({ + taskGroups, + onTaskDrop, + onTaskGroupDrop, + onTaskClick, + onCreateTask, onQuickEditorOpen, - onCreateList, - onCardMemberClick, + onCreateTaskGroup, onExtraMenuOpen, + onCardMemberClick, }) => { const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => { if (typeof destination === 'undefined') return; @@ -51,64 +43,78 @@ const Lists: React.FC = ({ const isList = type === 'column'; const isSameList = destination.droppableId === source.droppableId; - const droppedDraggable: DraggableElement = isList - ? { - id: draggableId, - position: columns[draggableId].position, - } - : { - id: draggableId, - position: tasks[draggableId].position, - }; - const beforeDropDraggables = isList - ? getSortedDraggables( - Object.values(columns).map(column => { - return { id: column.taskGroupID, position: column.position }; - }), - ) - : getSortedDraggables( - Object.values(tasks) - .filter((t: any) => t.taskGroup.taskGroupID === destination.droppableId) - .map(task => { - return { id: task.taskID, position: task.position }; - }), - ); - - const afterDropDraggables = getAfterDropDraggableList( - beforeDropDraggables, - droppedDraggable, - isList, - isSameList, - destination, - ); - const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index); + let droppedDraggable: DraggableElement | null = null; + let beforeDropDraggables: Array | null = null; if (isList) { - const droppedList = columns[droppedDraggable.id]; - onListDrop({ - ...droppedList, - position: newPosition, - }); + const droppedGroup = taskGroups.find(taskGroup => taskGroup.id === draggableId); + if (droppedGroup) { + droppedDraggable = { + id: draggableId, + position: droppedGroup.position, + }; + beforeDropDraggables = getSortedDraggables( + taskGroups.map(taskGroup => { + return { id: taskGroup.id, position: taskGroup.position }; + }), + ); + if (droppedDraggable === null || beforeDropDraggables === null) { + throw new Error('before drop draggables is null'); + } + const afterDropDraggables = getAfterDropDraggableList( + beforeDropDraggables, + droppedDraggable, + isList, + isSameList, + destination, + ); + const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index); + onTaskGroupDrop({ + ...droppedGroup, + position: newPosition, + }); + } else { + throw { error: 'task group can not be found' }; + } } else { - const droppedCard = tasks[droppedDraggable.id]; - const newCard = { - ...droppedCard, - position: newPosition, - taskGroup: { - taskGroupID: destination.droppableId, - }, - }; - onCardDrop(newCard); + const targetGroup = taskGroups.findIndex( + taskGroup => taskGroup.tasks.findIndex(task => task.id === draggableId) !== -1, + ); + const droppedTask = taskGroups[targetGroup].tasks.find(task => task.id === draggableId); + + if (droppedTask) { + droppedDraggable = { + id: draggableId, + position: droppedTask.position, + }; + beforeDropDraggables = getSortedDraggables( + taskGroups[targetGroup].tasks.map(task => { + return { id: task.id, position: task.position }; + }), + ); + if (droppedDraggable === null || beforeDropDraggables === null) { + throw new Error('before drop draggables is null'); + } + const afterDropDraggables = getAfterDropDraggableList( + beforeDropDraggables, + droppedDraggable, + isList, + isSameList, + destination, + ); + const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index); + const newTask = { + ...droppedTask, + position: newPosition, + taskGroup: { + id: destination.droppableId, + }, + }; + onTaskDrop(newTask); + } } }; - const orderedColumns = getSortedDraggables( - Object.values(columns).map(column => { - return { id: column.taskGroupID, position: column.position }; - }), - ); - console.log(orderedColumns); - const [currentComposer, setCurrentComposer] = useState(''); return ( @@ -116,85 +122,92 @@ const Lists: React.FC = ({ {provided => ( - {orderedColumns.map((columnDraggable, index: number) => { - const column = columns[columnDraggable.id]; - const columnCards = Object.values(tasks) - .filter((t: Task) => t.taskGroup.taskGroupID === column.taskGroupID) - .sort((a, b) => a.position - b.position); - return ( - - {columnDragProvided => ( - - {(columnDropProvided, snapshot) => ( - setCurrentComposer(id)} - isComposerOpen={currentComposer === column.taskGroupID} - onSaveName={name => {}} - tasks={columnCards} - ref={columnDragProvided.innerRef} - wrapperProps={columnDragProvided.draggableProps} - headerProps={columnDragProvided.dragHandleProps} - onExtraMenuOpen={onExtraMenuOpen} - id={column.taskGroupID} - key={column.taskGroupID} - index={index} - > - - {columnCards.map((task: Task, taskIndex: any) => { - return ( - - {taskProvided => { - return ( - onCardClick(task)} - onCardMemberClick={onCardMemberClick} - onContextMenu={onQuickEditorOpen} - /> - ); + {taskGroups + .slice() + .sort((a: any, b: any) => a.position - b.position) + .map((taskGroup: TaskGroup, index: number) => { + return ( + + {columnDragProvided => ( + + {(columnDropProvided, snapshot) => ( + setCurrentComposer(id)} + isComposerOpen={currentComposer === taskGroup.id} + onSaveName={name => {}} + ref={columnDragProvided.innerRef} + wrapperProps={columnDragProvided.draggableProps} + headerProps={columnDragProvided.dragHandleProps} + onExtraMenuOpen={onExtraMenuOpen} + id={taskGroup.id} + key={taskGroup.id} + index={index} + > + + {taskGroup.tasks + .slice() + .sort((a: any, b: any) => a.position - b.position) + .map((task: Task, taskIndex: any) => { + return ( + + {taskProvided => { + return ( + label.projectLabel)} + title={task.name} + members={task.assigned} + onClick={() => { + onTaskClick(task); + }} + onCardMemberClick={onCardMemberClick} + onContextMenu={onQuickEditorOpen} + /> + ); + }} + + ); + })} + {columnDropProvided.placeholder} + {currentComposer === taskGroup.id && ( + { + setCurrentComposer(''); }} - - ); - })} - {columnDropProvided.placeholder} - {currentComposer === column.taskGroupID && ( - { - setCurrentComposer(''); - }} - onCreateCard={name => { - onCardCreate(column.taskGroupID, name); - }} - isOpen - /> - )} - - - )} - - )} - - ); - })} + onCreateCard={name => { + onCreateTask(taskGroup.id, name); + }} + isOpen + /> + )} + + + )} + + )} + + ); + })} {provided.placeholder} )} - + { + onCreateTaskGroup(listName); + }} + /> ); }; -export default Lists; +export default SimpleLists; diff --git a/web/src/shared/components/MemberManager/index.tsx b/web/src/shared/components/MemberManager/index.tsx index ecd93d6..3a57707 100644 --- a/web/src/shared/components/MemberManager/index.tsx +++ b/web/src/shared/components/MemberManager/index.tsx @@ -39,16 +39,18 @@ const MemberManager: React.FC = ({ {availableMembers .filter( - member => currentSearch === '' || member.displayName.toLowerCase().startsWith(currentSearch.toLowerCase()), + member => + currentSearch === '' || + `${member.firstName} ${member.lastName}`.toLowerCase().startsWith(currentSearch.toLowerCase()), ) .map(member => { return ( - + { - const isActive = activeMembers.findIndex(m => m.userID === member.userID) !== -1; + const isActive = activeMembers.findIndex(m => m.id === member.id) !== -1; if (isActive) { - setActiveMembers(activeMembers.filter(m => m.userID !== member.userID)); + setActiveMembers(activeMembers.filter(m => m.id !== member.id)); } else { setActiveMembers([...activeMembers, member]); } @@ -56,8 +58,8 @@ const MemberManager: React.FC = ({ }} > JK - {member.displayName} - {activeMembers.findIndex(m => m.userID === member.userID) !== -1 && ( + {`${member.firstName} ${member.lastName}`} + {activeMembers.findIndex(m => m.id === member.id) !== -1 && ( diff --git a/web/src/shared/components/MiniProfile/index.tsx b/web/src/shared/components/MiniProfile/index.tsx index 7a0dcfc..458af4a 100644 --- a/web/src/shared/components/MiniProfile/index.tsx +++ b/web/src/shared/components/MiniProfile/index.tsx @@ -16,14 +16,14 @@ type MiniProfileProps = { displayName: string; username: string; bio: string; - profileIcon: ProfileIcon; + profileIcon: ProfileIcon | null; onRemoveFromTask: () => void; }; const MiniProfile: React.FC = ({ displayName, username, bio, profileIcon, onRemoveFromTask }) => { return ( <> - {profileIcon.initials} + {profileIcon && {profileIcon.initials}} {displayName} {username} diff --git a/web/src/shared/components/PopupMenu/LabelEditor.tsx b/web/src/shared/components/PopupMenu/LabelEditor.tsx index 1febe7a..69827f8 100644 --- a/web/src/shared/components/PopupMenu/LabelEditor.tsx +++ b/web/src/shared/components/PopupMenu/LabelEditor.tsx @@ -5,7 +5,7 @@ import { SaveButton, DeleteButton, LabelBox, EditLabelForm, FieldLabel, FieldNam type Props = { labelColors: Array; - label: Label | null; + label: ProjectLabel | null; onLabelEdit: (labelId: string | null, labelName: string, labelColor: LabelColor) => void; onLabelDelete?: (labelId: string) => void; }; @@ -32,7 +32,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props) onChange={e => { setCurrentLabel(e.currentTarget.value); }} - value={currentLabel} + value={currentLabel ?? ''} /> Select a color
@@ -56,7 +56,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props) e.preventDefault(); console.log(currentColor); if (currentColor) { - onLabelEdit(label ? label.labelId : null, currentLabel, currentColor); + onLabelEdit(label ? label.id : null, currentLabel ?? '', currentColor); } }} /> @@ -66,7 +66,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props) type="submit" onClick={e => { e.preventDefault(); - onLabelDelete(label.labelId); + onLabelDelete(label.id); }} /> )} diff --git a/web/src/shared/components/PopupMenu/LabelManager.tsx b/web/src/shared/components/PopupMenu/LabelManager.tsx index b381c00..a07c92a 100644 --- a/web/src/shared/components/PopupMenu/LabelManager.tsx +++ b/web/src/shared/components/PopupMenu/LabelManager.tsx @@ -14,12 +14,14 @@ import { } from './Styles'; type Props = { - labels?: Label[]; + labels?: Array; + taskLabels?: Array; onLabelToggle: (labelId: string) => void; onLabelEdit: (labelId: string) => void; onLabelCreate: () => void; }; -const LabelManager: React.FC = ({ labels, onLabelToggle, onLabelEdit, onLabelCreate }) => { + +const LabelManager: React.FC = ({ labels, taskLabels, onLabelToggle, onLabelEdit, onLabelCreate }) => { const $fieldName = useRef(null); const [currentLabel, setCurrentLabel] = useState(''); const [currentSearch, setCurrentSearch] = useState(''); @@ -44,27 +46,31 @@ const LabelManager: React.FC = ({ labels, onLabelToggle, onLabelEdit, onL {labels && labels - .filter(label => currentSearch === '' || label.name.toLowerCase().startsWith(currentSearch.toLowerCase())) + .filter( + label => + currentSearch === '' || + (label.name && label.name.toLowerCase().startsWith(currentSearch.toLowerCase())), + ) .map(label => ( -