feature: add labels & remove old types

This commit is contained in:
Jordan Knott 2020-05-30 23:11:19 -05:00
parent 539259effd
commit 2a59cddadb
48 changed files with 1671 additions and 834 deletions

View File

@ -86,10 +86,12 @@ type ComplexityRoot struct {
DeleteTaskGroup func(childComplexity int, input DeleteTaskGroupInput) int DeleteTaskGroup func(childComplexity int, input DeleteTaskGroupInput) int
LogoutUser func(childComplexity int, input LogoutUser) int LogoutUser func(childComplexity int, input LogoutUser) int
RemoveTaskLabel func(childComplexity int, input *RemoveTaskLabelInput) int RemoveTaskLabel func(childComplexity int, input *RemoveTaskLabelInput) int
ToggleTaskLabel func(childComplexity int, input ToggleTaskLabelInput) int
UnassignTask func(childComplexity int, input *UnassignTaskInput) int UnassignTask func(childComplexity int, input *UnassignTaskInput) int
UpdateProjectLabel func(childComplexity int, input UpdateProjectLabel) int UpdateProjectLabel func(childComplexity int, input UpdateProjectLabel) int
UpdateProjectLabelColor func(childComplexity int, input UpdateProjectLabelColor) int UpdateProjectLabelColor func(childComplexity int, input UpdateProjectLabelColor) int
UpdateProjectLabelName func(childComplexity int, input UpdateProjectLabelName) int UpdateProjectLabelName func(childComplexity int, input UpdateProjectLabelName) int
UpdateProjectName func(childComplexity int, input *UpdateProjectName) int
UpdateTaskDescription func(childComplexity int, input UpdateTaskDescriptionInput) int UpdateTaskDescription func(childComplexity int, input UpdateTaskDescriptionInput) int
UpdateTaskGroupLocation func(childComplexity int, input NewTaskGroupLocation) int UpdateTaskGroupLocation func(childComplexity int, input NewTaskGroupLocation) int
UpdateTaskLocation func(childComplexity int, input NewTaskLocation) int UpdateTaskLocation func(childComplexity int, input NewTaskLocation) int
@ -166,11 +168,9 @@ type ComplexityRoot struct {
} }
TaskLabel struct { TaskLabel struct {
AssignedDate func(childComplexity int) int AssignedDate func(childComplexity int) int
ColorHex func(childComplexity int) int ID func(childComplexity int) int
ID func(childComplexity int) int ProjectLabel func(childComplexity int) int
Name func(childComplexity int) int
ProjectLabelID func(childComplexity int) int
} }
Team struct { Team struct {
@ -179,6 +179,11 @@ type ComplexityRoot struct {
Name func(childComplexity int) int Name func(childComplexity int) int
} }
ToggleTaskLabelPayload struct {
Active func(childComplexity int) int
Task func(childComplexity int) int
}
UserAccount struct { UserAccount struct {
CreatedAt func(childComplexity int) int CreatedAt func(childComplexity int) int
Email 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) CreateUserAccount(ctx context.Context, input NewUserAccount) (*pg.UserAccount, error)
CreateTeam(ctx context.Context, input NewTeam) (*pg.Team, error) CreateTeam(ctx context.Context, input NewTeam) (*pg.Team, error)
CreateProject(ctx context.Context, input NewProject) (*pg.Project, 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) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*pg.ProjectLabel, error)
DeleteProjectLabel(ctx context.Context, input DeleteProjectLabel) (*pg.ProjectLabel, error) DeleteProjectLabel(ctx context.Context, input DeleteProjectLabel) (*pg.ProjectLabel, error)
UpdateProjectLabel(ctx context.Context, input UpdateProjectLabel) (*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) DeleteTaskGroup(ctx context.Context, input DeleteTaskGroupInput) (*DeleteTaskGroupPayload, error)
AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*pg.Task, error) AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*pg.Task, error)
RemoveTaskLabel(ctx context.Context, input *RemoveTaskLabelInput) (*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) CreateTask(ctx context.Context, input NewTask) (*pg.Task, error)
UpdateTaskDescription(ctx context.Context, input UpdateTaskDescriptionInput) (*pg.Task, error) UpdateTaskDescription(ctx context.Context, input UpdateTaskDescriptionInput) (*pg.Task, error)
UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*pg.Task, error) UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*pg.Task, error)
@ -261,9 +268,7 @@ type TaskGroupResolver interface {
} }
type TaskLabelResolver interface { type TaskLabelResolver interface {
ID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUID, error) ID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUID, error)
ProjectLabel(ctx context.Context, obj *pg.TaskLabel) (*pg.ProjectLabel, error)
ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error)
Name(ctx context.Context, obj *pg.TaskLabel) (*string, error)
} }
type TeamResolver interface { type TeamResolver interface {
ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error) 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 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": case "Mutation.unassignTask":
if e.complexity.Mutation.UnassignTask == nil { if e.complexity.Mutation.UnassignTask == nil {
break break
@ -561,6 +578,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.UpdateProjectLabelName(childComplexity, args["input"].(UpdateProjectLabelName)), true 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": case "Mutation.updateTaskDescription":
if e.complexity.Mutation.UpdateTaskDescription == nil { if e.complexity.Mutation.UpdateTaskDescription == nil {
break break
@ -951,13 +980,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.TaskLabel.AssignedDate(childComplexity), true 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": case "TaskLabel.id":
if e.complexity.TaskLabel.ID == nil { if e.complexity.TaskLabel.ID == nil {
break break
@ -965,19 +987,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.TaskLabel.ID(childComplexity), true return e.complexity.TaskLabel.ID(childComplexity), true
case "TaskLabel.name": case "TaskLabel.projectLabel":
if e.complexity.TaskLabel.Name == nil { if e.complexity.TaskLabel.ProjectLabel == nil {
break break
} }
return e.complexity.TaskLabel.Name(childComplexity), true return e.complexity.TaskLabel.ProjectLabel(childComplexity), true
case "TaskLabel.projectLabelID":
if e.complexity.TaskLabel.ProjectLabelID == nil {
break
}
return e.complexity.TaskLabel.ProjectLabelID(childComplexity), true
case "Team.createdAt": case "Team.createdAt":
if e.complexity.Team.CreatedAt == nil { 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 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": case "UserAccount.createdAt":
if e.complexity.UserAccount.CreatedAt == nil { if e.complexity.UserAccount.CreatedAt == nil {
break break
@ -1132,10 +1161,8 @@ type LabelColor {
type TaskLabel { type TaskLabel {
id: ID! id: ID!
projectLabelID: UUID! projectLabel: ProjectLabel!
assignedDate: Time! assignedDate: Time!
colorHex: String!
name: String
} }
type ProfileIcon { type ProfileIcon {
@ -1319,11 +1346,10 @@ input UpdateTaskDescriptionInput {
input AddTaskLabelInput { input AddTaskLabelInput {
taskID: UUID! taskID: UUID!
labelColorID: UUID! projectLabelID: UUID!
} }
input RemoveTaskLabelInput { input RemoveTaskLabelInput {
taskID: UUID!
taskLabelID: UUID! taskLabelID: UUID!
} }
@ -1353,6 +1379,21 @@ input UpdateProjectLabelColor {
labelColorID: UUID! labelColorID: UUID!
} }
input ToggleTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
}
type ToggleTaskLabelPayload {
active: Boolean!
task: Task!
}
input UpdateProjectName {
projectID: UUID!
name: String!
}
type Mutation { type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken! createRefreshToken(input: NewRefreshToken!): RefreshToken!
@ -1361,6 +1402,7 @@ type Mutation {
createTeam(input: NewTeam!): Team! createTeam(input: NewTeam!): Team!
createProject(input: NewProject!): Project! createProject(input: NewProject!): Project!
updateProjectName(input: UpdateProjectName): Project!
createProjectLabel(input: NewProjectLabel!): ProjectLabel! createProjectLabel(input: NewProjectLabel!): ProjectLabel!
deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel! deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
@ -1374,6 +1416,7 @@ type Mutation {
addTaskLabel(input: AddTaskLabelInput): Task! addTaskLabel(input: AddTaskLabelInput): Task!
removeTaskLabel(input: RemoveTaskLabelInput): Task! removeTaskLabel(input: RemoveTaskLabelInput): Task!
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
createTask(input: NewTask!): Task! createTask(input: NewTask!): Task!
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task! updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
@ -1589,6 +1632,20 @@ func (ec *executionContext) field_Mutation_removeTaskLabel_args(ctx context.Cont
return args, nil 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) { func (ec *executionContext) field_Mutation_unassignTask_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -1645,6 +1702,20 @@ func (ec *executionContext) field_Mutation_updateProjectLabel_args(ctx context.C
return args, nil 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) { func (ec *executionContext) field_Mutation_updateTaskDescription_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} 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) 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) { func (ec *executionContext) _Mutation_createProjectLabel(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { 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) 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) { func (ec *executionContext) _Mutation_createTask(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { 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) 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() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r)) ec.Error(ctx, ec.Recover(ctx, r))
@ -4638,13 +4791,13 @@ func (ec *executionContext) _TaskLabel_projectLabelID(ctx context.Context, field
Object: "TaskLabel", Object: "TaskLabel",
Field: field, Field: field,
Args: nil, Args: nil,
IsMethod: false, IsMethod: true,
} }
ctx = graphql.WithFieldContext(ctx, fc) ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children ctx = rctx // use context from middleware stack in children
return obj.ProjectLabelID, nil return ec.resolvers.TaskLabel().ProjectLabel(rctx, obj)
}) })
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@ -4656,9 +4809,9 @@ func (ec *executionContext) _TaskLabel_projectLabelID(ctx context.Context, field
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(uuid.UUID) res := resTmp.(*pg.ProjectLabel)
fc.Result = res 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) { 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) 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) { func (ec *executionContext) _Team_id(ctx context.Context, field graphql.CollectedField, obj *pg.Team) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { 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) 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) { func (ec *executionContext) _UserAccount_id(ctx context.Context, field graphql.CollectedField, obj *pg.UserAccount) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -6167,9 +6323,9 @@ func (ec *executionContext) unmarshalInputAddTaskLabelInput(ctx context.Context,
if err != nil { if err != nil {
return it, err return it, err
} }
case "labelColorID": case "projectLabelID":
var err error 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 { if err != nil {
return it, err return it, err
} }
@ -6609,6 +6765,24 @@ func (ec *executionContext) unmarshalInputRemoveTaskLabelInput(ctx context.Conte
var it RemoveTaskLabelInput var it RemoveTaskLabelInput
var asMap = obj.(map[string]interface{}) 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 { for k, v := range asMap {
switch k { switch k {
case "taskID": case "taskID":
@ -6617,9 +6791,9 @@ func (ec *executionContext) unmarshalInputRemoveTaskLabelInput(ctx context.Conte
if err != nil { if err != nil {
return it, err return it, err
} }
case "taskLabelID": case "projectLabelID":
var err error 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 { if err != nil {
return it, err return it, err
} }
@ -6731,6 +6905,30 @@ func (ec *executionContext) unmarshalInputUpdateProjectLabelName(ctx context.Con
return it, nil 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) { func (ec *executionContext) unmarshalInputUpdateTaskDescriptionInput(ctx context.Context, obj interface{}) (UpdateTaskDescriptionInput, error) {
var it UpdateTaskDescriptionInput var it UpdateTaskDescriptionInput
var asMap = obj.(map[string]interface{}) 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 { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "updateProjectName":
out.Values[i] = ec._Mutation_updateProjectName(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "createProjectLabel": case "createProjectLabel":
out.Values[i] = ec._Mutation_createProjectLabel(ctx, field) out.Values[i] = ec._Mutation_createProjectLabel(ctx, field)
if out.Values[i] == graphql.Null { 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 { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "toggleTaskLabel":
out.Values[i] = ec._Mutation_toggleTaskLabel(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "createTask": case "createTask":
out.Values[i] = ec._Mutation_createTask(ctx, field) out.Values[i] = ec._Mutation_createTask(ctx, field)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@ -7691,17 +7899,7 @@ func (ec *executionContext) _TaskLabel(ctx context.Context, sel ast.SelectionSet
} }
return res return res
}) })
case "projectLabelID": case "projectLabel":
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":
field := field field := field
out.Concurrently(i, func() (res graphql.Marshaler) { out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() { defer func() {
@ -7709,23 +7907,17 @@ func (ec *executionContext) _TaskLabel(ctx context.Context, sel ast.SelectionSet
ec.Error(ctx, ec.Recover(ctx, r)) 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 { if res == graphql.Null {
atomic.AddUint32(&invalids, 1) atomic.AddUint32(&invalids, 1)
} }
return res return res
}) })
case "name": case "assignedDate":
field := field out.Values[i] = ec._TaskLabel_assignedDate(ctx, field, obj)
out.Concurrently(i, func() (res graphql.Marshaler) { if out.Values[i] == graphql.Null {
defer func() { atomic.AddUint32(&invalids, 1)
if r := recover(); r != nil { }
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._TaskLabel_name(ctx, field, obj)
return res
})
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@ -7783,6 +7975,38 @@ func (ec *executionContext) _Team(ctx context.Context, sel ast.SelectionSet, obj
return out 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"} var userAccountImplementors = []string{"UserAccount"}
func (ec *executionContext) _UserAccount(ctx context.Context, sel ast.SelectionSet, obj *pg.UserAccount) graphql.Marshaler { 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 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) { func (ec *executionContext) unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx context.Context, v interface{}) (uuid.UUID, error) {
return UnmarshalUUID(v) return UnmarshalUUID(v)
} }
@ -9085,6 +9327,18 @@ func (ec *executionContext) unmarshalOUnassignTaskInput2ᚖgithubᚗcomᚋjordan
return &res, err 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 { 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 { if v == nil {
return graphql.Null return graphql.Null

View File

@ -8,8 +8,8 @@ import (
) )
type AddTaskLabelInput struct { type AddTaskLabelInput struct {
TaskID uuid.UUID `json:"taskID"` TaskID uuid.UUID `json:"taskID"`
LabelColorID uuid.UUID `json:"labelColorID"` ProjectLabelID uuid.UUID `json:"projectLabelID"`
} }
type AssignTaskInput struct { type AssignTaskInput struct {
@ -125,10 +125,19 @@ type ProjectsFilter struct {
} }
type RemoveTaskLabelInput struct { type RemoveTaskLabelInput struct {
TaskID uuid.UUID `json:"taskID"`
TaskLabelID uuid.UUID `json:"taskLabelID"` 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 { type UnassignTaskInput struct {
TaskID uuid.UUID `json:"taskID"` TaskID uuid.UUID `json:"taskID"`
UserID uuid.UUID `json:"userID"` UserID uuid.UUID `json:"userID"`
@ -150,6 +159,11 @@ type UpdateProjectLabelName struct {
Name string `json:"name"` Name string `json:"name"`
} }
type UpdateProjectName struct {
ProjectID uuid.UUID `json:"projectID"`
Name string `json:"name"`
}
type UpdateTaskDescriptionInput struct { type UpdateTaskDescriptionInput struct {
TaskID uuid.UUID `json:"taskID"` TaskID uuid.UUID `json:"taskID"`
Description string `json:"description"` Description string `json:"description"`

View File

@ -17,10 +17,8 @@ type LabelColor {
type TaskLabel { type TaskLabel {
id: ID! id: ID!
projectLabelID: UUID! projectLabel: ProjectLabel!
assignedDate: Time! assignedDate: Time!
colorHex: String!
name: String
} }
type ProfileIcon { type ProfileIcon {
@ -204,11 +202,10 @@ input UpdateTaskDescriptionInput {
input AddTaskLabelInput { input AddTaskLabelInput {
taskID: UUID! taskID: UUID!
labelColorID: UUID! projectLabelID: UUID!
} }
input RemoveTaskLabelInput { input RemoveTaskLabelInput {
taskID: UUID!
taskLabelID: UUID! taskLabelID: UUID!
} }
@ -238,6 +235,21 @@ input UpdateProjectLabelColor {
labelColorID: UUID! labelColorID: UUID!
} }
input ToggleTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
}
type ToggleTaskLabelPayload {
active: Boolean!
task: Task!
}
input UpdateProjectName {
projectID: UUID!
name: String!
}
type Mutation { type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken! createRefreshToken(input: NewRefreshToken!): RefreshToken!
@ -246,6 +258,7 @@ type Mutation {
createTeam(input: NewTeam!): Team! createTeam(input: NewTeam!): Team!
createProject(input: NewProject!): Project! createProject(input: NewProject!): Project!
updateProjectName(input: UpdateProjectName): Project!
createProjectLabel(input: NewProjectLabel!): ProjectLabel! createProjectLabel(input: NewProjectLabel!): ProjectLabel!
deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel! deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
@ -259,6 +272,7 @@ type Mutation {
addTaskLabel(input: AddTaskLabelInput): Task! addTaskLabel(input: AddTaskLabelInput): Task!
removeTaskLabel(input: RemoveTaskLabelInput): Task! removeTaskLabel(input: RemoveTaskLabelInput): Task!
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
createTask(input: NewTask!): Task! createTask(input: NewTask!): Task!
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task! updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!

View File

@ -49,6 +49,14 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
return &project, err 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) { func (r *mutationResolver) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*pg.ProjectLabel, error) {
createdAt := time.Now().UTC() 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) { func (r *mutationResolver) AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*pg.Task, error) {
assignedDate := time.Now().UTC() 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 { if err != nil {
return &pg.Task{}, err 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) { 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) { 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 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) projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID)
if err != nil { return &projectLabel, err
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 *teamResolver) ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error) { 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 // - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done. // it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean. // - 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) { func (r *projectLabelResolver) ColorHex(ctx context.Context, obj *pg.ProjectLabel) (string, error) {
labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID) labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID)
if err != nil { if err != nil {

View File

@ -0,0 +1 @@
ALTER TABLE task_label ADD UNIQUE (project_label_id, task_id);

View File

@ -22,6 +22,13 @@ type Repository interface {
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
GetAllUserAccounts(ctx context.Context) ([]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) CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error)
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error) GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error) GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error)

View File

@ -121,3 +121,25 @@ func (q *Queries) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Proj
) )
return i, err 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
}

View File

@ -27,6 +27,8 @@ type Querier interface {
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error) DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, 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) DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error
GetAllOrganizations(ctx context.Context) ([]Organization, error) GetAllOrganizations(ctx context.Context) ([]Organization, error)
@ -46,6 +48,8 @@ type Querier interface {
GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error) GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error)
GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error) GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error)
GetTaskGroupsForProject(ctx context.Context, projectID 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) GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskLabel, error)
GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error)
GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, 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) UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error) UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (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) UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescriptionParams) (Task, error)
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error) UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error) UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)

View File

@ -33,6 +33,66 @@ func (q *Queries) CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabe
return i, err 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 const getTaskLabelsForTaskID = `-- name: GetTaskLabelsForTaskID :many
SELECT task_label_id, task_id, project_label_id, assigned_date FROM task_label WHERE task_id = $1 SELECT task_label_id, task_id, project_label_id, assigned_date FROM task_label WHERE task_id = $1
` `

View File

@ -9,3 +9,6 @@ SELECT * FROM project WHERE project_id = $1;
-- name: CreateProject :one -- name: CreateProject :one
INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RETURNING *; 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 *;

View File

@ -4,3 +4,15 @@ INSERT INTO task_label (task_id, project_label_id, assigned_date)
-- name: GetTaskLabelsForTaskID :many -- name: GetTaskLabelsForTaskID :many
SELECT * FROM task_label WHERE task_id = $1; 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;

View File

@ -8,8 +8,9 @@ import { useMeQuery } from 'shared/generated/graphql';
type GlobalTopNavbarProps = { type GlobalTopNavbarProps = {
name: string; name: string;
projectMembers?: null | Array<TaskUser>; projectMembers?: null | Array<TaskUser>;
onSaveProjectName?: (projectName: string) => void;
}; };
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers }) => { const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers, onSaveProjectName }) => {
const { loading, data } = useMeQuery(); const { loading, data } = useMeQuery();
const history = useHistory(); const history = useHistory();
const { userID, setUserID } = useContext(UserIDContext); const { userID, setUserID } = useContext(UserIDContext);
@ -52,6 +53,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers
onNotificationClick={() => {}} onNotificationClick={() => {}}
projectMembers={projectMembers} projectMembers={projectMembers}
onProfileClick={onProfileClick} onProfileClick={onProfileClick}
onSaveProjectName={onSaveProjectName}
/> />
{menu.isOpen && ( {menu.isOpen && (
<DropdownMenu <DropdownMenu

View File

@ -56,17 +56,6 @@ const Details: React.FC<DetailsProps> = ({
if (!data) { if (!data) {
return <div>loading</div>; return <div>loading</div>;
} }
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 ( return (
<> <>
<Modal <Modal
@ -77,24 +66,19 @@ const Details: React.FC<DetailsProps> = ({
renderContent={() => { renderContent={() => {
return ( return (
<TaskDetails <TaskDetails
task={{ task={data.findTask}
...data.findTask,
taskID: data.findTask.id,
taskGroup: { taskGroupID: data.findTask.taskGroup.id },
members: taskMembers,
description: data.findTask.description ?? '',
labels: [],
}}
onTaskNameChange={onTaskNameChange} onTaskNameChange={onTaskNameChange}
onTaskDescriptionChange={onTaskDescriptionChange} onTaskDescriptionChange={onTaskDescriptionChange}
onDeleteTask={onDeleteTask} onDeleteTask={onDeleteTask}
onCloseModal={() => history.push(projectURL)} onCloseModal={() => history.push(projectURL)}
onMemberProfile={($targetRef, memberID) => { onMemberProfile={($targetRef, memberID) => {
const member = data.findTask.assigned.find(m => m.id === memberID);
const profileIcon = member ? member.profileIcon : null;
showPopup( showPopup(
$targetRef, $targetRef,
<Popup title={null} onClose={() => {}} tab={0}> <Popup title={null} onClose={() => {}} tab={0}>
<MiniProfile <MiniProfile
profileIcon={taskMembers[0].profileIcon} profileIcon={profileIcon}
displayName="Jordan Knott" displayName="Jordan Knott"
username="@jordanthedev" username="@jordanthedev"
bio="None" bio="None"
@ -111,7 +95,7 @@ const Details: React.FC<DetailsProps> = ({
<Popup title="Members" tab={0} onClose={() => {}}> <Popup title="Members" tab={0} onClose={() => {}}>
<MemberManager <MemberManager
availableMembers={availableMembers} availableMembers={availableMembers}
activeMembers={taskMembers} activeMembers={data.findTask.assigned}
onMemberChange={(member, isActive) => { onMemberChange={(member, isActive) => {
if (isActive) { if (isActive) {
assignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } }); assignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } });

View File

@ -5,7 +5,6 @@ import Lists from 'shared/components/Lists';
import { Board } from './Styles'; import { Board } from './Styles';
type KanbanBoardProps = { type KanbanBoardProps = {
listsData: BoardState;
onOpenListActionsPopup: ($targetRef: React.RefObject<HTMLElement>, taskGroupID: string) => void; onOpenListActionsPopup: ($targetRef: React.RefObject<HTMLElement>, taskGroupID: string) => void;
onCardDrop: (task: Task) => void; onCardDrop: (task: Task) => void;
onListDrop: (taskGroup: TaskGroup) => void; onListDrop: (taskGroup: TaskGroup) => void;
@ -16,7 +15,6 @@ type KanbanBoardProps = {
}; };
const KanbanBoard: React.FC<KanbanBoardProps> = ({ const KanbanBoard: React.FC<KanbanBoardProps> = ({
listsData,
onOpenListActionsPopup, onOpenListActionsPopup,
onQuickEditorOpen, onQuickEditorOpen,
onCardCreate, onCardCreate,
@ -27,25 +25,7 @@ const KanbanBoard: React.FC<KanbanBoardProps> = ({
}) => { }) => {
const match = useRouteMatch(); const match = useRouteMatch();
const history = useHistory(); const history = useHistory();
return ( return <Board></Board>;
<Board>
<Lists
onCardClick={task => {
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}
/>
</Board>
);
}; };
export default KanbanBoard; export default KanbanBoard;

View File

@ -1,11 +1,12 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import * as BoardStateUtils from 'shared/utils/boardState';
import GlobalTopNavbar from 'App/TopNavbar'; import GlobalTopNavbar from 'App/TopNavbar';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import { Bolt, ToggleOn, Tags } from 'shared/icons'; import { Bolt, ToggleOn, Tags } from 'shared/icons';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom'; import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom';
import { import {
useToggleTaskLabelMutation,
useUpdateProjectNameMutation,
useFindProjectQuery, useFindProjectQuery,
useUpdateTaskNameMutation, useUpdateTaskNameMutation,
useUpdateProjectLabelMutation, useUpdateProjectLabelMutation,
@ -29,12 +30,14 @@ import ListActions from 'shared/components/ListActions';
import MemberManager from 'shared/components/MemberManager'; import MemberManager from 'shared/components/MemberManager';
import { LabelsPopup } from 'shared/components/PopupMenu/PopupMenu.stories'; import { LabelsPopup } from 'shared/components/PopupMenu/PopupMenu.stories';
import KanbanBoard from 'Projects/Project/KanbanBoard'; import KanbanBoard from 'Projects/Project/KanbanBoard';
import SimpleLists from 'shared/components/Lists';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
import LabelManager from 'shared/components/PopupMenu/LabelManager'; import LabelManager from 'shared/components/PopupMenu/LabelManager';
import LabelEditor from 'shared/components/PopupMenu/LabelEditor'; import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
import produce from 'immer'; import produce from 'immer';
import MiniProfile from 'shared/components/MiniProfile'; import MiniProfile from 'shared/components/MiniProfile';
import Details from './Details'; import Details from './Details';
import { useApolloClient } from '@apollo/react-hooks';
const getCacheData = (client: any, projectID: string) => { const getCacheData = (client: any, projectID: string) => {
const cacheData: any = client.readQuery({ const cacheData: any = client.readQuery({
@ -85,12 +88,20 @@ const ProjectMembers = styled.div`
`; `;
type LabelManagerEditorProps = { type LabelManagerEditorProps = {
labels: React.RefObject<Array<Label>>; labels: React.RefObject<Array<ProjectLabel>>;
taskLabels: null | React.RefObject<Array<TaskLabel>>;
projectID: string; projectID: string;
labelColors: Array<LabelColor>; labelColors: Array<LabelColor>;
onLabelToggle?: (labelId: string) => void;
}; };
const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({ labels: labelsRef, projectID, labelColors }) => { const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({
labels: labelsRef,
projectID,
labelColors,
onLabelToggle,
taskLabels: taskLabelsRef,
}) => {
const [currentLabel, setCurrentLabel] = useState(''); const [currentLabel, setCurrentLabel] = useState('');
const [createProjectLabel] = useCreateProjectLabelMutation({ const [createProjectLabel] = useCreateProjectLabelMutation({
update: (client, newLabelData) => { update: (client, newLabelData) => {
@ -116,12 +127,16 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({ labels: labelsR
}, },
}); });
const labels = labelsRef.current ? labelsRef.current : []; const labels = labelsRef.current ? labelsRef.current : [];
const taskLabels = taskLabelsRef && taskLabelsRef.current ? taskLabelsRef.current : [];
const [currentTaskLabels, setCurrentTaskLabels] = useState(taskLabels);
console.log(taskLabels);
const { setTab } = usePopup(); const { setTab } = usePopup();
return ( return (
<> <>
<Popup title="Labels" tab={0} onClose={() => {}}> <Popup title="Labels" tab={0} onClose={() => {}}>
<LabelManager <LabelManager
labels={labels} labels={labels}
taskLabels={currentTaskLabels}
onLabelCreate={() => { onLabelCreate={() => {
setTab(2); setTab(2);
}} }}
@ -130,18 +145,34 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({ labels: labelsR
setTab(1); setTab(1);
}} }}
onLabelToggle={labelId => { onLabelToggle={labelId => {
setCurrentLabel(labelId); if (onLabelToggle) {
setTab(1); 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);
}
}} }}
/> />
</Popup> </Popup>
<Popup onClose={() => {}} title="Edit label" tab={1}> <Popup onClose={() => {}} title="Edit label" tab={1}>
<LabelEditor <LabelEditor
labelColors={labelColors} labelColors={labelColors}
label={labels.find(label => label.labelId === currentLabel) ?? null} label={labels.find(label => label.id === currentLabel) ?? null}
onLabelEdit={(projectLabelID, name, color) => { onLabelEdit={(projectLabelID, name, color) => {
if (projectLabelID) { if (projectLabelID) {
updateProjectLabel({ variables: { projectLabelID, labelColorID: color.id, name } }); updateProjectLabel({ variables: { projectLabelID, labelColorID: color.id, name: name ?? '' } });
} }
setTab(0); setTab(0);
}} }}
@ -156,7 +187,7 @@ const LabelManagerEditor: React.FC<LabelManagerEditorProps> = ({ labels: labelsR
labelColors={labelColors} labelColors={labelColors}
label={null} label={null}
onLabelEdit={(_labelId, name, color) => { onLabelEdit={(_labelId, name, color) => {
createProjectLabel({ variables: { projectID, labelColorID: color.id, name } }); createProjectLabel({ variables: { projectID, labelColorID: color.id, name: name ?? '' } });
setTab(0); setTab(0);
}} }}
/> />
@ -169,10 +200,7 @@ interface ProjectParams {
projectID: string; 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 initialQuickCardEditorState: QuickCardEditorState = { isOpen: false, top: 0, left: 0 };
const initialTaskDetailsState = { isOpen: false, taskID: '' };
const ProjectBar = styled.div` const ProjectBar = styled.div`
display: flex; display: flex;
@ -209,18 +237,16 @@ const ProjectActionText = styled.span`
const Project = () => { const Project = () => {
const { projectID } = useParams<ProjectParams>(); const { projectID } = useParams<ProjectParams>();
const history = useHistory();
const match = useRouteMatch(); const match = useRouteMatch();
const [updateTaskDescription] = useUpdateTaskDescriptionMutation(); const [updateTaskDescription] = useUpdateTaskDescriptionMutation();
const [listsData, setListsData] = useState(initialState);
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState); const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
const [updateTaskLocation] = useUpdateTaskLocationMutation(); const [updateTaskLocation] = useUpdateTaskLocationMutation();
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation(); const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({});
const [deleteTaskGroup] = useDeleteTaskGroupMutation({ const [deleteTaskGroup] = useDeleteTaskGroupMutation({
onCompleted: deletedTaskGroupData => { onCompleted: deletedTaskGroupData => {},
setListsData(BoardStateUtils.deleteTaskGroup(listsData, deletedTaskGroupData.deleteTaskGroup.taskGroup.id));
},
update: (client, deletedTaskGroupData) => { update: (client, deletedTaskGroupData) => {
const cacheData = getCacheData(client, projectID); const cacheData = getCacheData(client, projectID);
const newData = { const newData = {
@ -235,14 +261,7 @@ const Project = () => {
}); });
const [createTaskGroup] = useCreateTaskGroupMutation({ const [createTaskGroup] = useCreateTaskGroupMutation({
onCompleted: newTaskGroupData => { onCompleted: newTaskGroupData => {},
const newTaskGroup = {
taskGroupID: newTaskGroupData.createTaskGroup.id,
tasks: [],
...newTaskGroupData.createTaskGroup,
};
setListsData(BoardStateUtils.addTaskGroup(listsData, newTaskGroup));
},
update: (client, newTaskGroupData) => { update: (client, newTaskGroupData) => {
const cacheData = getCacheData(client, projectID); const cacheData = getCacheData(client, projectID);
const newData = { const newData = {
@ -255,15 +274,7 @@ const Project = () => {
}); });
const [createTask] = useCreateTaskMutation({ const [createTask] = useCreateTaskMutation({
onCompleted: newTaskData => { onCompleted: newTaskData => {},
const newTask = {
...newTaskData.createTask,
taskID: newTaskData.createTask.id,
taskGroup: { taskGroupID: newTaskData.createTask.taskGroup.id },
labels: [],
};
setListsData(BoardStateUtils.addTask(listsData, newTask));
},
update: (client, newTaskData) => { update: (client, newTaskData) => {
const cacheData = getCacheData(client, projectID); const cacheData = getCacheData(client, projectID);
const newTaskGroups = produce(cacheData.findProject.taskGroups, (draftState: any) => { const newTaskGroups = produce(cacheData.findProject.taskGroups, (draftState: any) => {
@ -284,158 +295,144 @@ const Project = () => {
}); });
const [deleteTask] = useDeleteTaskMutation({ const [deleteTask] = useDeleteTaskMutation({
onCompleted: deletedTask => { onCompleted: deletedTask => {},
setListsData(BoardStateUtils.deleteTask(listsData, deletedTask.deleteTask.taskID));
},
}); });
const [updateTaskName] = useUpdateTaskNameMutation({ const [updateTaskName] = useUpdateTaskNameMutation({
onCompleted: newTaskData => { onCompleted: newTaskData => {},
setListsData( });
BoardStateUtils.updateTaskName(listsData, newTaskData.updateTaskName.id, newTaskData.updateTaskName.name), const [toggleTaskLabel] = useToggleTaskLabelMutation({
); onCompleted: newTaskLabel => {
taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels;
console.log(taskLabelsRef.current);
}, },
}); });
const { loading, data, refetch } = useFindProjectQuery({ const { loading, data, refetch } = useFindProjectQuery({
variables: { projectId: projectID }, variables: { projectId: projectID },
onCompleted: newData => {},
}); });
const onCardCreate = (taskGroupID: string, name: string) => { const onCardCreate = (taskGroupID: string, name: string) => {
const taskGroupTasks = Object.values(listsData.tasks).filter( if (data) {
(task: Task) => task.taskGroup.taskGroupID === taskGroupID, const taskGroupTasks = data.findProject.taskGroups.filter(t => t.id === taskGroupID);
); if (taskGroupTasks) {
let position = 65535; let position = 65535;
if (taskGroupTasks.length !== 0) { if (taskGroupTasks.length !== 0) {
const [lastTask] = taskGroupTasks.sort((a: any, b: any) => a.position - b.position).slice(-1); const [lastTask] = taskGroupTasks.sort((a: any, b: any) => a.position - b.position).slice(-1);
position = Math.ceil(lastTask.position) * 2 + 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) => { const onCardDrop = (droppedTask: Task) => {
updateTaskLocation({ updateTaskLocation({
variables: { variables: {
taskID: droppedTask.taskID, taskID: droppedTask.id,
taskGroupID: droppedTask.taskGroup.taskGroupID, taskGroupID: droppedTask.taskGroup.id,
position: droppedTask.position, position: droppedTask.position,
}, },
optimisticResponse: { optimisticResponse: {
updateTaskLocation: { updateTaskLocation: {
name: droppedTask.name, name: droppedTask.name,
id: droppedTask.taskID, id: droppedTask.id,
position: droppedTask.position, position: droppedTask.position,
createdAt: '', createdAt: '',
}, },
}, },
}); });
setListsData(BoardStateUtils.updateTask(listsData, droppedTask));
}; };
const onListDrop = (droppedColumn: TaskGroup) => { 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({ updateTaskGroupLocation({
variables: { taskGroupID: droppedColumn.taskGroupID, position: droppedColumn.position }, variables: { taskGroupID: droppedColumn.id, position: droppedColumn.position },
optimisticResponse: { optimisticResponse: {
updateTaskGroupLocation: { updateTaskGroupLocation: {
id: droppedColumn.taskGroupID, id: droppedColumn.id,
position: droppedColumn.position, position: droppedColumn.position,
}, },
}, },
}); });
// setListsData(BoardStateUtils.updateTaskGroup(listsData, droppedColumn));
}; };
const onCreateList = (listName: string) => { const onCreateList = (listName: string) => {
const [lastColumn] = Object.values(listsData.columns) if (data) {
.sort((a, b) => a.position - b.position) const [lastColumn] = data.findProject.taskGroups.sort((a, b) => a.position - b.position).slice(-1);
.slice(-1); let position = 65535;
let position = 65535; if (lastColumn) {
if (lastColumn) { position = lastColumn.position * 2 + 1;
position = lastColumn.position * 2 + 1; }
createTaskGroup({ variables: { projectID, name: listName, position } });
} }
createTaskGroup({ variables: { projectID, name: listName, position } });
}; };
const [assignTask] = useAssignTaskMutation(); const [assignTask] = useAssignTaskMutation();
const [updateProjectName] = useUpdateProjectNameMutation();
const client = useApolloClient();
const { showPopup, hidePopup } = usePopup(); const { showPopup, hidePopup } = usePopup();
const $labelsRef = useRef<HTMLDivElement>(null); const $labelsRef = useRef<HTMLDivElement>(null);
const labelsRef = useRef<Array<Label>>([]); const labelsRef = useRef<Array<ProjectLabel>>([]);
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
if (loading) { if (loading) {
return ( return (
<> <>
<GlobalTopNavbar name="Project" /> <GlobalTopNavbar onSaveProjectName={projectName => {}} name="Loading..." />
<Title>Error Loading</Title>
</> </>
); );
} }
if (data) { 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 onQuickEditorOpen = (e: ContextMenuEvent) => {
const currentTask = Object.values(currentListsData.tasks).find(task => task.taskID === e.taskID); const taskGroup = data.findProject.taskGroups.find(t => t.id === e.taskGroupID);
setQuickCardEditor({ const currentTask = taskGroup ? taskGroup.tasks.find(t => t.id === e.taskID) : null;
top: e.top, if (currentTask) {
left: e.left, setQuickCardEditor({
isOpen: true, top: e.top,
task: currentTask, left: e.left,
}); isOpen: true,
task: currentTask,
});
}
}; };
labelsRef.current = data.findProject.labels.map(label => { labelsRef.current = data.findProject.labels;
return {
labelId: label.id,
name: label.name ?? '',
labelColor: label.labelColor,
active: false,
};
});
return ( return (
<> <>
<GlobalTopNavbar projectMembers={availableMembers} name={data.findProject.name} /> <GlobalTopNavbar
onSaveProjectName={projectName => {
updateProjectName({ variables: { projectID, name: projectName } });
}}
projectMembers={data.findProject.members}
name={data.findProject.name}
/>
<ProjectBar> <ProjectBar>
<ProjectActions> <ProjectActions>
<ProjectAction <ProjectAction
@ -443,7 +440,12 @@ const Project = () => {
onClick={() => { onClick={() => {
showPopup( showPopup(
$labelsRef, $labelsRef,
<LabelManagerEditor labelColors={data.labelColors} labels={labelsRef} projectID={projectID} />, <LabelManagerEditor
taskLabels={null}
labelColors={data.labelColors}
labels={labelsRef}
projectID={projectID}
/>,
); );
}} }}
> >
@ -460,18 +462,53 @@ const Project = () => {
</ProjectAction> </ProjectAction>
</ProjectActions> </ProjectActions>
</ProjectBar> </ProjectBar>
<KanbanBoard <SimpleLists
listsData={currentListsData} onTaskClick={task => {
onCardDrop={onCardDrop} history.push(`${match.url}/c/${task.id}`);
onListDrop={onListDrop} }}
onCardCreate={onCardCreate} onTaskDrop={droppedTask => {
onCreateList={onCreateList} 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) => { onCardMemberClick={($targetRef, taskID, memberID) => {
const member = data.findProject.members.find(m => m.id === memberID);
const profileIcon = member ? member.profileIcon : null;
showPopup( showPopup(
$targetRef, $targetRef,
<Popup title={null} onClose={() => {}} tab={0}> <Popup title={null} onClose={() => {}} tab={0}>
<MiniProfile <MiniProfile
profileIcon={availableMembers[0].profileIcon} profileIcon={profileIcon}
displayName="Jordan Knott" displayName="Jordan Knott"
username="@jordanthedev" username="@jordanthedev"
bio="None" bio="None"
@ -483,7 +520,7 @@ const Project = () => {
); );
}} }}
onQuickEditorOpen={onQuickEditorOpen} onQuickEditorOpen={onQuickEditorOpen}
onOpenListActionsPopup={($targetRef, taskGroupID) => { onExtraMenuOpen={(taskGroupID: string, $targetRef: any) => {
showPopup( showPopup(
$targetRef, $targetRef,
<Popup title="List actions" tab={0} onClose={() => {}}> <Popup title="List actions" tab={0} onClose={() => {}}>
@ -501,8 +538,8 @@ const Project = () => {
{quickCardEditor.isOpen && ( {quickCardEditor.isOpen && (
<QuickCardEditor <QuickCardEditor
isOpen isOpen
taskID={quickCardEditor.task ? quickCardEditor.task.taskID : ''} taskID={quickCardEditor.task ? quickCardEditor.task.id : ''}
taskGroupID={quickCardEditor.task ? quickCardEditor.task.taskGroup.taskGroupID : ''} taskGroupID={quickCardEditor.task ? quickCardEditor.task.taskGroup.id : ''}
cardTitle={quickCardEditor.task ? quickCardEditor.task.name : ''} cardTitle={quickCardEditor.task ? quickCardEditor.task.name : ''}
onCloseEditor={() => setQuickCardEditor(initialQuickCardEditorState)} onCloseEditor={() => setQuickCardEditor(initialQuickCardEditorState)}
onEditCard={(_listId: string, cardId: string, cardName: string) => { onEditCard={(_listId: string, cardId: string, cardName: string) => {
@ -537,19 +574,33 @@ const Project = () => {
render={(routeProps: RouteComponentProps<TaskRouteProps>) => ( render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
<Details <Details
refreshCache={() => {}} refreshCache={() => {}}
availableMembers={availableMembers} availableMembers={data.findProject.members}
projectURL={match.url} projectURL={match.url}
taskID={routeProps.match.params.taskID} taskID={routeProps.match.params.taskID}
onTaskNameChange={(updatedTask, newName) => { onTaskNameChange={(updatedTask, newName) => {
updateTaskName({ variables: { taskID: updatedTask.taskID, name: newName } }); updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
}} }}
onTaskDescriptionChange={(updatedTask, newDescription) => { onTaskDescriptionChange={(updatedTask, newDescription) => {
updateTaskDescription({ variables: { taskID: updatedTask.taskID, description: newDescription } }); updateTaskDescription({ variables: { taskID: updatedTask.id, description: newDescription } });
}} }}
onDeleteTask={deletedTask => { onDeleteTask={deletedTask => {
deleteTask({ variables: { taskID: deletedTask.taskID } }); deleteTask({ variables: { taskID: deletedTask.id } });
}}
onOpenAddLabelPopup={(task, $targetRef) => {
taskLabelsRef.current = task.labels;
showPopup(
$targetRef,
<LabelManagerEditor
onLabelToggle={labelID => {
toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } });
}}
labelColors={data.labelColors}
labels={labelsRef}
taskLabels={taskLabelsRef}
projectID={projectID}
/>,
);
}} }}
onOpenAddLabelPopup={(task, $targetRef) => {}}
/> />
)} )}
/> />

View File

@ -41,7 +41,7 @@ const Projects = () => {
const { projects } = data; const { projects } = data;
return ( return (
<> <>
<GlobalTopNavbar name="Projects" /> <GlobalTopNavbar onSaveProjectName={() => {}} name="Projects" />
<ProjectGrid> <ProjectGrid>
{projects.map(project => ( {projects.map(project => (
<ProjectLink key={project.id} to={`/projects/${project.id}`}> <ProjectLink key={project.id} to={`/projects/${project.id}`}>

79
web/src/citadel.d.ts vendored
View File

@ -3,18 +3,6 @@ interface JWTToken {
iat: string; iat: string;
exp: string; exp: string;
} }
interface ColumnState {
[key: string]: TaskGroup;
}
interface TaskState {
[key: string]: Task;
}
interface BoardState {
columns: ColumnState;
tasks: TaskState;
}
interface DraggableElement { interface DraggableElement {
id: string; id: string;
@ -28,66 +16,13 @@ type ContextMenuEvent = {
taskGroupID: string; taskGroupID: string;
}; };
type InnerTaskGroup = {
taskGroupID: string;
name?: string;
position?: number;
};
type ProfileIcon = {
url: string | null;
initials: string | null;
bgColor: string | null;
};
type TaskUser = { type TaskUser = {
userID: string; id: string;
displayName: string; firstName: string;
lastName: string;
profileIcon: ProfileIcon; profileIcon: ProfileIcon;
}; };
type Task = {
taskID: string;
taskGroup: InnerTaskGroup;
name: string;
position: number;
labels: Label[];
description?: string | null;
members?: Array<TaskUser>;
};
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 = { type RefreshTokenResponse = {
accessToken: string; accessToken: string;
}; };
@ -117,17 +52,9 @@ type ElementSize = {
height: number; height: number;
}; };
type LabelColor = {
id: string;
name: string;
colorHex: string;
position: number;
};
type OnCardMemberClick = ($targetRef: RefObject<HTMLElement>, taskID: string, memberID: string) => void; type OnCardMemberClick = ($targetRef: RefObject<HTMLElement>, taskID: string, memberID: string) => void;
type ElementBounds = { type ElementBounds = {
size: ElementSize; size: ElementSize;
position: ElementPosition; position: ElementPosition;
}; };

66
web/src/projects.d.ts vendored Normal file
View File

@ -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<TaskUser>;
};
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;
};

View File

@ -14,28 +14,17 @@ export default {
}, },
}; };
const labelData = [ const labelData: Array<ProjectLabel> = [
{ {
labelId: 'development', id: 'development',
name: 'Development', name: 'Development',
createdDate: new Date().toString(),
labelColor: { labelColor: {
id: '1', id: '1',
colorHex: LabelColors.BLUE, colorHex: LabelColors.BLUE,
name: 'blue', name: 'blue',
position: 1, position: 1,
}, },
active: false,
},
{
labelId: 'general',
name: 'General',
labelColor: {
id: '2',
colorHex: LabelColors.PINK,
name: 'pink',
position: 2,
},
active: false,
}, },
]; ];

View File

@ -47,10 +47,10 @@ const Member: React.FC<MemberProps> = ({ onCardMemberClick, taskID, member }) =>
onClick={e => { onClick={e => {
if (onCardMemberClick) { if (onCardMemberClick) {
e.stopPropagation(); e.stopPropagation();
onCardMemberClick($targetRef, taskID, member.userID); onCardMemberClick($targetRef, taskID, member.id);
} }
}} }}
key={member.userID} key={member.id}
bgColor={member.profileIcon.bgColor ?? '#7367F0'} bgColor={member.profileIcon.bgColor ?? '#7367F0'}
> >
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials> <CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
@ -68,7 +68,7 @@ type Props = {
dueDate?: DueDate; dueDate?: DueDate;
checklists?: Checklist; checklists?: Checklist;
watched?: boolean; watched?: boolean;
labels?: Label[]; labels?: Array<ProjectLabel>;
wrapperProps?: any; wrapperProps?: any;
members?: Array<TaskUser> | null; members?: Array<TaskUser> | null;
onCardMemberClick?: OnCardMemberClick; onCardMemberClick?: OnCardMemberClick;
@ -136,7 +136,7 @@ const Card = React.forwardRef(
<ListCardLabels> <ListCardLabels>
{labels && {labels &&
labels.map(label => ( labels.map(label => (
<ListCardLabel color={label.labelColor.colorHex} key={label.name}> <ListCardLabel color={label.labelColor.colorHex} key={label.id}>
{label.name} {label.name}
</ListCardLabel> </ListCardLabel>
))} ))}
@ -169,7 +169,7 @@ const Card = React.forwardRef(
<CardMembers> <CardMembers>
{members && {members &&
members.map(member => ( members.map(member => (
<Member key={member.userID} taskID={taskID} member={member} onCardMemberClick={onCardMemberClick} /> <Member key={member.id} taskID={taskID} member={member} onCardMemberClick={onCardMemberClick} />
))} ))}
</CardMembers> </CardMembers>
</ListCardDetails> </ListCardDetails>

View File

@ -17,21 +17,35 @@ export const Default = () => {
return ( return (
<DueDateManager <DueDateManager
task={{ task={{
taskID: '1', id: '1',
taskGroup: { name: 'General', taskGroupID: '1' }, taskGroup: { name: 'General', id: '1', position: 1 },
name: 'Hello, world', name: 'Hello, world',
position: 1, position: 1,
labels: [ labels: [
{ {
labelId: 'soft-skills', id: 'soft-skills',
labelColor: { id: '1', colorHex: '#fff', name: 'white', position: 1 }, assignedDate: new Date().toString(),
active: true, projectLabel: {
name: 'Soft Skills', createdDate: new Date().toString(),
id: 'label-soft-skills',
name: 'Soft Skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
},
},
}, },
], ],
description: 'hello!', description: 'hello!',
members: [ assigned: [
{ userID: '1', profileIcon: { url: null, initials: null, bgColor: null }, displayName: 'Jordan Knott' }, {
id: '1',
profileIcon: { url: null, initials: null, bgColor: null },
firstName: 'Jordan',
lastName: 'Knott',
},
], ],
}} }}
onCancel={action('cancel')} onCancel={action('cancel')}

View File

@ -16,28 +16,17 @@ export default {
}, },
}; };
const labelData = [ const labelData: Array<ProjectLabel> = [
{ {
labelId: 'development', id: 'development',
name: 'Development', name: 'Development',
createdDate: new Date().toString(),
labelColor: { labelColor: {
id: '1', id: '1',
colorHex: LabelColors.BLUE, colorHex: LabelColors.BLUE,
name: 'blue', name: 'blue',
position: 1, 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} isComposerOpen={false}
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={action('extra menu open')} onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>
@ -94,7 +82,6 @@ export const WithCardComposer = () => {
isComposerOpen isComposerOpen
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={action('extra menu open')} onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>
@ -121,7 +108,6 @@ export const WithCard = () => {
isComposerOpen={false} isComposerOpen={false}
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={action('extra menu open')} onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>
@ -160,7 +146,6 @@ export const WithCardAndComposer = () => {
isComposerOpen isComposerOpen
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={action('extra menu open')} onExtraMenuOpen={action('extra menu open')}
> >
<ListCards> <ListCards>

View File

@ -97,9 +97,7 @@ export const Header = styled.div<{ isEditing: boolean }>`
props.isEditing && props.isEditing &&
css` css`
& ${HeaderName} { & ${HeaderName} {
background: #fff; box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
border: none;
box-shadow: inset 0 0 0 2px #0079bf;
} }
`} `}
`; `;

View File

@ -22,7 +22,6 @@ type Props = {
onSaveName: (name: string) => void; onSaveName: (name: string) => void;
isComposerOpen: boolean; isComposerOpen: boolean;
onOpenComposer: (id: string) => void; onOpenComposer: (id: string) => void;
tasks: Task[];
wrapperProps?: any; wrapperProps?: any;
headerProps?: any; headerProps?: any;
index?: number; index?: number;

View File

@ -70,14 +70,12 @@ export const Default = () => {
...listsData, ...listsData,
tasks: { tasks: {
...listsData.tasks, ...listsData.tasks,
[droppedTask.taskID]: droppedTask, [droppedTask.id]: droppedTask,
}, },
}; };
console.log(newState);
setListsData(newState); setListsData(newState);
}; };
const onListDrop = (droppedColumn: any) => { const onListDrop = (droppedColumn: any) => {
console.log(droppedColumn);
const newState = { const newState = {
...listsData, ...listsData,
columns: { columns: {
@ -85,44 +83,9 @@ export const Default = () => {
[droppedColumn.taskGroupID]: droppedColumn, [droppedColumn.taskGroupID]: droppedColumn,
}, },
}; };
console.log(newState);
setListsData(newState); setListsData(newState);
}; };
return ( return <span />;
<Lists
{...listsData}
onCardClick={action('card click')}
onExtraMenuOpen={action('extra menu open')}
onQuickEditorOpen={action('card composer open')}
onCardDrop={onCardDrop}
onListDrop={onListDrop}
onCardMemberClick={action('card member click')}
onCardCreate={action('card create')}
onCreateList={listName => {
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);
}}
/>
);
}; };
const createColumn = (id: any, name: any, position: any) => { const createColumn = (id: any, name: any, position: any) => {
@ -202,13 +165,13 @@ export const ListsWithManyList = () => {
}; };
return ( return (
<Lists <Lists
{...listsData} taskGroups={[]}
onCardClick={action('card click')} onTaskClick={action('card click')}
onQuickEditorOpen={action('card composer open')} onQuickEditorOpen={action('card composer open')}
onCardCreate={action('card create')} onCreateTask={action('card create')}
onCardDrop={onCardDrop} onTaskDrop={onCardDrop}
onListDrop={onListDrop} onTaskGroupDrop={onListDrop}
onCreateList={action('create list')} onCreateTaskGroup={action('create list')}
onExtraMenuOpen={action('extra menu open')} onExtraMenuOpen={action('extra menu open')}
onCardMemberClick={action('card member click')} onCardMemberClick={action('card member click')}
/> />

View File

@ -11,5 +11,7 @@ export const Container = styled.div`
export const BoardWrapper = styled.div` export const BoardWrapper = styled.div`
display: flex; display: flex;
margin-top: 12px;
margin-left: 8px;
`; `;
export default Container; export default Container;

View File

@ -13,37 +13,29 @@ import {
import { Container, BoardWrapper } from './Styles'; import { Container, BoardWrapper } from './Styles';
interface Columns { interface SimpleProps {
[key: string]: TaskGroup; taskGroups: Array<TaskGroup>;
} onTaskDrop: (task: Task) => void;
interface Tasks { onTaskGroupDrop: (taskGroup: TaskGroup) => void;
[key: string]: Task;
}
type Props = { onTaskClick: (task: Task) => void;
columns: Columns; onCreateTask: (taskGroupID: string, name: string) => void;
tasks: Tasks;
onCardClick: (task: Task) => void;
onCardDrop: (task: Task) => void;
onListDrop: (taskGroup: TaskGroup) => void;
onCardCreate: (taskGroupID: string, name: string) => void;
onQuickEditorOpen: (e: ContextMenuEvent) => void; onQuickEditorOpen: (e: ContextMenuEvent) => void;
onCreateList: (listName: string) => void; onCreateTaskGroup: (listName: string) => void;
onExtraMenuOpen: (taskGroupID: string, $targetRef: React.RefObject<HTMLElement>) => void; onExtraMenuOpen: (taskGroupID: string, $targetRef: React.RefObject<HTMLElement>) => void;
onCardMemberClick: OnCardMemberClick; onCardMemberClick: OnCardMemberClick;
}; }
const Lists: React.FC<Props> = ({ const SimpleLists: React.FC<SimpleProps> = ({
columns, taskGroups,
tasks, onTaskDrop,
onCardClick, onTaskGroupDrop,
onCardDrop, onTaskClick,
onListDrop, onCreateTask,
onCardCreate,
onQuickEditorOpen, onQuickEditorOpen,
onCreateList, onCreateTaskGroup,
onCardMemberClick,
onExtraMenuOpen, onExtraMenuOpen,
onCardMemberClick,
}) => { }) => {
const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => { const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => {
if (typeof destination === 'undefined') return; if (typeof destination === 'undefined') return;
@ -51,64 +43,78 @@ const Lists: React.FC<Props> = ({
const isList = type === 'column'; const isList = type === 'column';
const isSameList = destination.droppableId === source.droppableId; const isSameList = destination.droppableId === source.droppableId;
const droppedDraggable: DraggableElement = isList let droppedDraggable: DraggableElement | null = null;
? { let beforeDropDraggables: Array<DraggableElement> | null = null;
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);
if (isList) { if (isList) {
const droppedList = columns[droppedDraggable.id]; const droppedGroup = taskGroups.find(taskGroup => taskGroup.id === draggableId);
onListDrop({ if (droppedGroup) {
...droppedList, droppedDraggable = {
position: newPosition, 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 { } else {
const droppedCard = tasks[droppedDraggable.id]; const targetGroup = taskGroups.findIndex(
const newCard = { taskGroup => taskGroup.tasks.findIndex(task => task.id === draggableId) !== -1,
...droppedCard, );
position: newPosition, const droppedTask = taskGroups[targetGroup].tasks.find(task => task.id === draggableId);
taskGroup: {
taskGroupID: destination.droppableId, if (droppedTask) {
}, droppedDraggable = {
}; id: draggableId,
onCardDrop(newCard); 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(''); const [currentComposer, setCurrentComposer] = useState('');
return ( return (
<BoardWrapper> <BoardWrapper>
@ -116,85 +122,92 @@ const Lists: React.FC<Props> = ({
<Droppable direction="horizontal" type="column" droppableId="root"> <Droppable direction="horizontal" type="column" droppableId="root">
{provided => ( {provided => (
<Container {...provided.droppableProps} ref={provided.innerRef}> <Container {...provided.droppableProps} ref={provided.innerRef}>
{orderedColumns.map((columnDraggable, index: number) => { {taskGroups
const column = columns[columnDraggable.id]; .slice()
const columnCards = Object.values(tasks) .sort((a: any, b: any) => a.position - b.position)
.filter((t: Task) => t.taskGroup.taskGroupID === column.taskGroupID) .map((taskGroup: TaskGroup, index: number) => {
.sort((a, b) => a.position - b.position); return (
return ( <Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
<Draggable draggableId={column.taskGroupID} key={column.taskGroupID} index={index}> {columnDragProvided => (
{columnDragProvided => ( <Droppable type="tasks" droppableId={taskGroup.id}>
<Droppable type="tasks" droppableId={column.taskGroupID}> {(columnDropProvided, snapshot) => (
{(columnDropProvided, snapshot) => ( <List
<List name={taskGroup.name}
name={column.name} onOpenComposer={id => setCurrentComposer(id)}
onOpenComposer={id => setCurrentComposer(id)} isComposerOpen={currentComposer === taskGroup.id}
isComposerOpen={currentComposer === column.taskGroupID} onSaveName={name => {}}
onSaveName={name => {}} ref={columnDragProvided.innerRef}
tasks={columnCards} wrapperProps={columnDragProvided.draggableProps}
ref={columnDragProvided.innerRef} headerProps={columnDragProvided.dragHandleProps}
wrapperProps={columnDragProvided.draggableProps} onExtraMenuOpen={onExtraMenuOpen}
headerProps={columnDragProvided.dragHandleProps} id={taskGroup.id}
onExtraMenuOpen={onExtraMenuOpen} key={taskGroup.id}
id={column.taskGroupID} index={index}
key={column.taskGroupID} >
index={index} <ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
> {taskGroup.tasks
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}> .slice()
{columnCards.map((task: Task, taskIndex: any) => { .sort((a: any, b: any) => a.position - b.position)
return ( .map((task: Task, taskIndex: any) => {
<Draggable key={task.taskID} draggableId={task.taskID} index={taskIndex}> return (
{taskProvided => { <Draggable key={task.id} draggableId={task.id} index={taskIndex}>
return ( {taskProvided => {
<Card return (
wrapperProps={{ <Card
...taskProvided.draggableProps, wrapperProps={{
...taskProvided.dragHandleProps, ...taskProvided.draggableProps,
}} ...taskProvided.dragHandleProps,
ref={taskProvided.innerRef} }}
taskID={task.taskID} ref={taskProvided.innerRef}
taskGroupID={column.taskGroupID} taskID={task.id}
description="" taskGroupID={taskGroup.id}
title={task.name} description=""
labels={task.labels} labels={task.labels.map(label => label.projectLabel)}
members={task.members} title={task.name}
onClick={() => onCardClick(task)} members={task.assigned}
onCardMemberClick={onCardMemberClick} onClick={() => {
onContextMenu={onQuickEditorOpen} onTaskClick(task);
/> }}
); onCardMemberClick={onCardMemberClick}
onContextMenu={onQuickEditorOpen}
/>
);
}}
</Draggable>
);
})}
{columnDropProvided.placeholder}
{currentComposer === taskGroup.id && (
<CardComposer
onClose={() => {
setCurrentComposer('');
}} }}
</Draggable> onCreateCard={name => {
); onCreateTask(taskGroup.id, name);
})} }}
{columnDropProvided.placeholder} isOpen
{currentComposer === column.taskGroupID && ( />
<CardComposer )}
onClose={() => { </ListCards>
setCurrentComposer(''); </List>
}} )}
onCreateCard={name => { </Droppable>
onCardCreate(column.taskGroupID, name); )}
}} </Draggable>
isOpen );
/> })}
)}
</ListCards>
</List>
)}
</Droppable>
)}
</Draggable>
);
})}
{provided.placeholder} {provided.placeholder}
</Container> </Container>
)} )}
</Droppable> </Droppable>
</DragDropContext> </DragDropContext>
<AddList onSave={onCreateList} /> <AddList
onSave={listName => {
onCreateTaskGroup(listName);
}}
/>
</BoardWrapper> </BoardWrapper>
); );
}; };
export default Lists; export default SimpleLists;

View File

@ -39,16 +39,18 @@ const MemberManager: React.FC<MemberManagerProps> = ({
<BoardMembersList> <BoardMembersList>
{availableMembers {availableMembers
.filter( .filter(
member => currentSearch === '' || member.displayName.toLowerCase().startsWith(currentSearch.toLowerCase()), member =>
currentSearch === '' ||
`${member.firstName} ${member.lastName}`.toLowerCase().startsWith(currentSearch.toLowerCase()),
) )
.map(member => { .map(member => {
return ( return (
<BoardMembersListItem key={member.userID}> <BoardMembersListItem key={member.id}>
<BoardMemberListItemContent <BoardMemberListItemContent
onClick={() => { onClick={() => {
const isActive = activeMembers.findIndex(m => m.userID === member.userID) !== -1; const isActive = activeMembers.findIndex(m => m.id === member.id) !== -1;
if (isActive) { if (isActive) {
setActiveMembers(activeMembers.filter(m => m.userID !== member.userID)); setActiveMembers(activeMembers.filter(m => m.id !== member.id));
} else { } else {
setActiveMembers([...activeMembers, member]); setActiveMembers([...activeMembers, member]);
} }
@ -56,8 +58,8 @@ const MemberManager: React.FC<MemberManagerProps> = ({
}} }}
> >
<ProfileIcon>JK</ProfileIcon> <ProfileIcon>JK</ProfileIcon>
<MemberName>{member.displayName}</MemberName> <MemberName>{`${member.firstName} ${member.lastName}`}</MemberName>
{activeMembers.findIndex(m => m.userID === member.userID) !== -1 && ( {activeMembers.findIndex(m => m.id === member.id) !== -1 && (
<ActiveIconWrapper> <ActiveIconWrapper>
<Checkmark size={16} color="#42526e" /> <Checkmark size={16} color="#42526e" />
</ActiveIconWrapper> </ActiveIconWrapper>

View File

@ -16,14 +16,14 @@ type MiniProfileProps = {
displayName: string; displayName: string;
username: string; username: string;
bio: string; bio: string;
profileIcon: ProfileIcon; profileIcon: ProfileIcon | null;
onRemoveFromTask: () => void; onRemoveFromTask: () => void;
}; };
const MiniProfile: React.FC<MiniProfileProps> = ({ displayName, username, bio, profileIcon, onRemoveFromTask }) => { const MiniProfile: React.FC<MiniProfileProps> = ({ displayName, username, bio, profileIcon, onRemoveFromTask }) => {
return ( return (
<> <>
<Profile> <Profile>
<ProfileIcon bgColor={profileIcon.bgColor ?? ''}>{profileIcon.initials}</ProfileIcon> {profileIcon && <ProfileIcon bgColor={profileIcon.bgColor ?? ''}>{profileIcon.initials}</ProfileIcon>}
<ProfileInfo> <ProfileInfo>
<InfoTitle>{displayName}</InfoTitle> <InfoTitle>{displayName}</InfoTitle>
<InfoUsername>{username}</InfoUsername> <InfoUsername>{username}</InfoUsername>

View File

@ -5,7 +5,7 @@ import { SaveButton, DeleteButton, LabelBox, EditLabelForm, FieldLabel, FieldNam
type Props = { type Props = {
labelColors: Array<LabelColor>; labelColors: Array<LabelColor>;
label: Label | null; label: ProjectLabel | null;
onLabelEdit: (labelId: string | null, labelName: string, labelColor: LabelColor) => void; onLabelEdit: (labelId: string | null, labelName: string, labelColor: LabelColor) => void;
onLabelDelete?: (labelId: string) => void; onLabelDelete?: (labelId: string) => void;
}; };
@ -32,7 +32,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props)
onChange={e => { onChange={e => {
setCurrentLabel(e.currentTarget.value); setCurrentLabel(e.currentTarget.value);
}} }}
value={currentLabel} value={currentLabel ?? ''}
/> />
<FieldLabel>Select a color</FieldLabel> <FieldLabel>Select a color</FieldLabel>
<div> <div>
@ -56,7 +56,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props)
e.preventDefault(); e.preventDefault();
console.log(currentColor); console.log(currentColor);
if (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" type="submit"
onClick={e => { onClick={e => {
e.preventDefault(); e.preventDefault();
onLabelDelete(label.labelId); onLabelDelete(label.id);
}} }}
/> />
)} )}

View File

@ -14,12 +14,14 @@ import {
} from './Styles'; } from './Styles';
type Props = { type Props = {
labels?: Label[]; labels?: Array<ProjectLabel>;
taskLabels?: Array<TaskLabel>;
onLabelToggle: (labelId: string) => void; onLabelToggle: (labelId: string) => void;
onLabelEdit: (labelId: string) => void; onLabelEdit: (labelId: string) => void;
onLabelCreate: () => void; onLabelCreate: () => void;
}; };
const LabelManager: React.FC<Props> = ({ labels, onLabelToggle, onLabelEdit, onLabelCreate }) => {
const LabelManager: React.FC<Props> = ({ labels, taskLabels, onLabelToggle, onLabelEdit, onLabelCreate }) => {
const $fieldName = useRef<HTMLInputElement>(null); const $fieldName = useRef<HTMLInputElement>(null);
const [currentLabel, setCurrentLabel] = useState(''); const [currentLabel, setCurrentLabel] = useState('');
const [currentSearch, setCurrentSearch] = useState(''); const [currentSearch, setCurrentSearch] = useState('');
@ -44,27 +46,31 @@ const LabelManager: React.FC<Props> = ({ labels, onLabelToggle, onLabelEdit, onL
<Labels> <Labels>
{labels && {labels &&
labels labels
.filter(label => currentSearch === '' || label.name.toLowerCase().startsWith(currentSearch.toLowerCase())) .filter(
label =>
currentSearch === '' ||
(label.name && label.name.toLowerCase().startsWith(currentSearch.toLowerCase())),
)
.map(label => ( .map(label => (
<Label key={label.labelId}> <Label key={label.id}>
<LabelIcon <LabelIcon
onClick={() => { onClick={() => {
onLabelEdit(label.labelId); onLabelEdit(label.id);
}} }}
> >
<Pencil color="#c2c6dc" /> <Pencil color="#c2c6dc" />
</LabelIcon> </LabelIcon>
<CardLabel <CardLabel
key={label.labelId} key={label.id}
color={label.labelColor.colorHex} color={label.labelColor.colorHex}
active={currentLabel === label.labelId} active={currentLabel === label.id}
onMouseEnter={() => { onMouseEnter={() => {
setCurrentLabel(label.labelId); setCurrentLabel(label.id);
}} }}
onClick={() => onLabelToggle(label.labelId)} onClick={() => onLabelToggle(label.id)}
> >
{label.name} {label.name}
{label.active && ( {taskLabels && taskLabels.find(t => t.projectLabel.id === label.id) && (
<ActiveIcon> <ActiveIcon>
<Checkmark color="#fff" /> <Checkmark color="#fff" />
</ActiveIcon> </ActiveIcon>

View File

@ -7,12 +7,13 @@ import ListActions from 'shared/components/ListActions';
import MemberManager from 'shared/components/MemberManager'; import MemberManager from 'shared/components/MemberManager';
import DueDateManager from 'shared/components/DueDateManager'; import DueDateManager from 'shared/components/DueDateManager';
import MiniProfile from 'shared/components/MiniProfile'; import MiniProfile from 'shared/components/MiniProfile';
import styled from 'styled-components';
import PopupMenu, { PopupProvider, usePopup, Popup } from '.'; import styled from 'styled-components';
import produce from 'immer';
import NormalizeStyles from 'App/NormalizeStyles'; import NormalizeStyles from 'App/NormalizeStyles';
import BaseStyles from 'App/BaseStyles'; import BaseStyles from 'App/BaseStyles';
import produce from 'immer';
import PopupMenu, { PopupProvider, usePopup, Popup } from '.';
export default { export default {
component: PopupMenu, component: PopupMenu,
@ -24,28 +25,17 @@ export default {
], ],
}, },
}; };
const labelData = [ const labelData: Array<ProjectLabel> = [
{ {
labelId: 'development', id: 'development',
name: 'Development', name: 'Development',
createdDate: new Date().toString(),
labelColor: { labelColor: {
id: '1', id: '1',
name: 'white',
colorHex: LabelColors.BLUE, colorHex: LabelColors.BLUE,
name: 'blue',
position: 1, position: 1,
}, },
active: false,
},
{
labelId: 'general',
name: 'General',
labelColor: {
id: '1',
name: 'white',
colorHex: LabelColors.PINK,
position: 1,
},
active: false,
}, },
]; ];
@ -74,9 +64,9 @@ const LabelManagerEditor = () => {
onLabelToggle={labelId => { onLabelToggle={labelId => {
setLabels( setLabels(
produce(labels, draftState => { produce(labels, draftState => {
const idx = labels.findIndex(label => label.labelId === labelId); const idx = labels.findIndex(label => label.id === labelId);
if (idx !== -1) { if (idx !== -1) {
draftState[idx] = { ...draftState[idx], active: !labels[idx].active }; draftState[idx] = { ...draftState[idx] };
} }
}), }),
); );
@ -86,13 +76,21 @@ const LabelManagerEditor = () => {
<Popup onClose={action('on close')} title="Edit label" tab={1}> <Popup onClose={action('on close')} title="Edit label" tab={1}>
<LabelEditor <LabelEditor
labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]} labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]}
label={labels.find(label => label.labelId === currentLabel) ?? null} label={labels.find(label => label.id === currentLabel) ?? null}
onLabelEdit={(_labelId, name, color) => { onLabelEdit={(_labelId, name, color) => {
setLabels( setLabels(
produce(labels, draftState => { produce(labels, draftState => {
const idx = labels.findIndex(label => label.labelId === currentLabel); const idx = labels.findIndex(label => label.id === currentLabel);
if (idx !== -1) { if (idx !== -1) {
draftState[idx] = { ...draftState[idx], name, labelColor: color }; draftState[idx] = {
...draftState[idx],
name,
labelColor: {
...draftState[idx].labelColor,
name: color.name ?? '',
colorHex: color.colorHex,
},
};
} }
}), }),
); );
@ -105,7 +103,20 @@ const LabelManagerEditor = () => {
label={null} label={null}
labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]} labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]}
onLabelEdit={(_labelId, name, color) => { onLabelEdit={(_labelId, name, color) => {
setLabels([...labels, { labelId: name, name, labelColor: color, active: false }]); setLabels([
...labels,
{
id: name,
name,
createdDate: new Date().toString(),
labelColor: {
id: color.id,
colorHex: color.colorHex,
name: color.name ?? '',
position: 1,
},
},
]);
setTab(0); setTab(0);
}} }}
/> />
@ -214,7 +225,12 @@ export const MemberManagerPopup = () => {
<PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}> <PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<MemberManager <MemberManager
availableMembers={[ availableMembers={[
{ userID: '1', displayName: 'Jordan Knott', profileIcon: { bgColor: null, url: null, initials: null } }, {
id: '1',
firstName: 'Jordan',
lastName: 'Knott',
profileIcon: { bgColor: null, url: null, initials: null },
},
]} ]}
activeMembers={[]} activeMembers={[]}
onMemberChange={action('member change')} onMemberChange={action('member change')}
@ -251,26 +267,35 @@ export const DueDateManagerPopup = () => {
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}> <PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<DueDateManager <DueDateManager
task={{ task={{
taskID: '1', id: '1',
taskGroup: { name: 'General', taskGroupID: '1' }, taskGroup: { name: 'General', id: '1', position: 1 },
name: 'Hello, world', name: 'Hello, world',
position: 1, position: 1,
labels: [ labels: [
{ {
labelId: 'soft-skills', id: 'soft-skills',
labelColor: { assignedDate: new Date().toString(),
id: '1', projectLabel: {
name: 'white', createdDate: new Date().toString(),
colorHex: '#fff', id: 'label-soft-skills',
position: 1, name: 'Soft Skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
},
}, },
active: true,
name: 'Soft Skills',
}, },
], ],
description: 'hello!', description: 'hello!',
members: [ assigned: [
{ userID: '1', profileIcon: { bgColor: null, url: null, initials: null }, displayName: 'Jordan Knott' }, {
id: '1',
profileIcon: { bgColor: null, url: null, initials: null },
firstName: 'Jordan',
lastName: 'Knott',
},
], ],
}} }}
onCancel={action('cancel')} onCancel={action('cancel')}

View File

@ -17,28 +17,17 @@ export default {
}, },
}; };
const labelData = [ const labelData: Array<ProjectLabel> = [
{ {
labelId: 'development', id: 'development',
name: 'Development', name: 'Development',
createdDate: 'date',
labelColor: { labelColor: {
id: '1', id: 'label-color-blue',
name: 'white',
colorHex: LabelColors.BLUE, colorHex: LabelColors.BLUE,
name: 'blue',
position: 1, position: 1,
}, },
active: false,
},
{
labelId: 'general',
name: 'General',
labelColor: {
id: '1',
name: 'white',
colorHex: LabelColors.PINK,
position: 1,
},
active: false,
}, },
]; ];
@ -70,7 +59,6 @@ export const Default = () => {
isComposerOpen={false} isComposerOpen={false}
onSaveName={action('on save name')} onSaveName={action('on save name')}
onOpenComposer={action('on open composer')} onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={(taskGroupID, $targetRef) => console.log(taskGroupID, $targetRef)} onExtraMenuOpen={(taskGroupID, $targetRef) => console.log(taskGroupID, $targetRef)}
> >
<ListCards> <ListCards>

View File

@ -22,7 +22,7 @@ type Props = {
onEditCard: (taskGroupID: string, taskID: string, cardName: string) => void; onEditCard: (taskGroupID: string, taskID: string, cardName: string) => void;
onOpenPopup: (popupType: number, top: number, left: number) => void; onOpenPopup: (popupType: number, top: number, left: number) => void;
onArchiveCard: (taskGroupID: string, taskID: string) => void; onArchiveCard: (taskGroupID: string, taskID: string) => void;
labels?: Label[]; labels?: Array<ProjectLabel>;
isOpen: boolean; isOpen: boolean;
top: number; top: number;
left: number; left: number;
@ -72,7 +72,7 @@ const QuickCardEditor = ({
<ListCardLabels> <ListCardLabels>
{labels && {labels &&
labels.map(label => ( labels.map(label => (
<ListCardLabel color={label.labelColor.colorHex} key={label.name}> <ListCardLabel color={label.labelColor.colorHex} key={label.id}>
{label.name} {label.name}
</ListCardLabel> </ListCardLabel>
))} ))}

View File

@ -31,7 +31,7 @@ type TaskAssigneeProps = {
const TaskAssignee: React.FC<TaskAssigneeProps> = ({ member, onMemberProfile, size }) => { const TaskAssignee: React.FC<TaskAssigneeProps> = ({ member, onMemberProfile, size }) => {
const $memberRef = useRef<HTMLDivElement>(null); const $memberRef = useRef<HTMLDivElement>(null);
return ( return (
<TaskDetailAssignee ref={$memberRef} onClick={() => onMemberProfile($memberRef, member.userID)} key={member.userID}> <TaskDetailAssignee ref={$memberRef} onClick={() => onMemberProfile($memberRef, member.id)} key={member.id}>
<ProfileIcon size={size}>{member.profileIcon.initials ?? ''}</ProfileIcon> <ProfileIcon size={size}>{member.profileIcon.initials ?? ''}</ProfileIcon>
</TaskDetailAssignee> </TaskDetailAssignee>
); );

View File

@ -244,11 +244,11 @@ export const TaskDetailLabels = styled.div`
flex-wrap: wrap; flex-wrap: wrap;
`; `;
export const TaskDetailLabel = styled.div` export const TaskDetailLabel = styled.div<{ color: string }>`
&:hover { &:hover {
opacity: 0.8; opacity: 0.8;
} }
background-color: #00c2e0; background-color: ${props => props.color};
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;

View File

@ -29,29 +29,34 @@ export const Default = () => {
return ( return (
<TaskDetails <TaskDetails
task={{ task={{
taskID: '1', id: '1',
taskGroup: { name: 'General', taskGroupID: '1' }, taskGroup: { name: 'General', id: '1' },
name: 'Hello, world', name: 'Hello, world',
position: 1, position: 1,
labels: [ labels: [
{ {
labelId: 'soft-skills', id: 'soft-skills',
labelColor: { assignedDate: new Date().toString(),
id: '1', projectLabel: {
name: 'white', createdDate: new Date().toString(),
colorHex: '#fff', id: 'label-soft-skills',
position: 1, name: 'Soft Skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
},
}, },
active: true,
name: 'Soft Skills',
}, },
], ],
description, description,
members: [ assigned: [
{ {
userID: '1', id: '1',
profileIcon: { bgColor: null, url: null, initials: null }, profileIcon: { bgColor: null, url: null, initials: null },
displayName: 'Jordan Knott', firstName: 'Jordan',
lastName: 'Knott',
}, },
], ],
}} }}

View File

@ -193,15 +193,15 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskDetailsSidebar> <TaskDetailsSidebar>
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle> <TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
<TaskDetailAssignees> <TaskDetailAssignees>
{task.members && task.members.length === 0 ? ( {task.assigned && task.assigned.length === 0 ? (
<UnassignedLabel ref={$unassignedRef} onClick={onUnassignedClick}> <UnassignedLabel ref={$unassignedRef} onClick={onUnassignedClick}>
Unassigned Unassigned
</UnassignedLabel> </UnassignedLabel>
) : ( ) : (
<> <>
{task.members && {task.assigned &&
task.members.map(member => ( task.assigned.map(member => (
<TaskAssignee size={32} member={member} onMemberProfile={onMemberProfile} /> <TaskAssignee key={member.id} size={32} member={member} onMemberProfile={onMemberProfile} />
))} ))}
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}> <TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}>
<TaskDetailsAddMemberIcon> <TaskDetailsAddMemberIcon>
@ -214,7 +214,11 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskDetailSectionTitle>Labels</TaskDetailSectionTitle> <TaskDetailSectionTitle>Labels</TaskDetailSectionTitle>
<TaskDetailLabels> <TaskDetailLabels>
{task.labels.map(label => { {task.labels.map(label => {
return <TaskDetailLabel>{label.name}</TaskDetailLabel>; return (
<TaskDetailLabel key={label.projectLabel.id} color={label.projectLabel.labelColor.colorHex}>
{label.projectLabel.name}
</TaskDetailLabel>
);
})} })}
<TaskDetailsAddLabel ref={$addLabelRef} onClick={onAddLabel}> <TaskDetailsAddLabel ref={$addLabelRef} onClick={onAddLabel}>
<TaskDetailsAddLabelIcon> <TaskDetailsAddLabelIcon>

View File

@ -1,4 +1,5 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea';
import { mixin } from 'shared/utils/styles'; import { mixin } from 'shared/utils/styles';
export const NavbarWrapper = styled.div` export const NavbarWrapper = styled.div`
@ -135,7 +136,36 @@ export const ProjectName = styled.h1`
color: #c2c6dc; color: #c2c6dc;
font-weight: 600; font-weight: 600;
font-size: 20px; font-size: 20px;
padding: 6px 10px 6px 8px; padding: 3px 10px 3px 8px;
font-family: 'Droid Sans';
margin: -4px 0;
`;
export const ProjectNameTextarea = styled(TextareaAutosize)`
font-family: 'Droid Sans';
border: none;
resize: none;
overflow: hidden;
overflow-wrap: break-word;
background: transparent;
border-radius: 3px;
box-shadow: none;
margin: -4px 0;
letter-spacing: normal;
word-spacing: normal;
text-transform: none;
text-indent: 0px;
text-shadow: none;
flex-direction: column;
text-align: start;
color: #c2c6dc;
font-weight: 600;
font-size: 20px;
padding: 3px 10px 3px 8px;
&:focus {
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
}
`; `;
export const ProjectSwitcher = styled.button` export const ProjectSwitcher = styled.button`

View File

@ -1,8 +1,9 @@
import React, { useRef } from 'react'; import React, { useRef, useState, useEffect } from 'react';
import { Star, Bell, Cog, AngleDown } from 'shared/icons'; import { Star, Ellipsis, Bell, Cog, AngleDown } from 'shared/icons';
import { import {
NotificationContainer, NotificationContainer,
ProjectNameTextarea,
InviteButton, InviteButton,
GlobalActions, GlobalActions,
ProjectActions, ProjectActions,
@ -28,9 +29,72 @@ import TaskAssignee from 'shared/components/TaskAssignee';
import { usePopup, Popup } from 'shared/components/PopupMenu'; import { usePopup, Popup } from 'shared/components/PopupMenu';
import MiniProfile from 'shared/components/MiniProfile'; import MiniProfile from 'shared/components/MiniProfile';
type ProjectHeadingProps = {
projectName: string;
onSaveProjectName?: (projectName: string) => void;
};
const ProjectHeading: React.FC<ProjectHeadingProps> = ({ projectName: initialProjectName, onSaveProjectName }) => {
const [isEditProjectName, setEditProjectName] = useState(false);
const [projectName, setProjectName] = useState(initialProjectName);
const $projectName = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
if (isEditProjectName && $projectName && $projectName.current) {
$projectName.current.focus();
$projectName.current.select();
}
}, [isEditProjectName]);
useEffect(() => {
setProjectName(initialProjectName);
}, [initialProjectName]);
const onProjectNameChange = (event: React.FormEvent<HTMLTextAreaElement>): void => {
setProjectName(event.currentTarget.value);
};
const onProjectNameBlur = () => {
if (onSaveProjectName) {
onSaveProjectName(projectName);
}
setEditProjectName(false);
};
const onProjectNameKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
if ($projectName && $projectName.current) {
$projectName.current.blur();
}
}
};
return (
<>
<Separator>»</Separator>
{isEditProjectName ? (
<ProjectNameTextarea
ref={$projectName}
onChange={onProjectNameChange}
onKeyDown={onProjectNameKeyDown}
onBlur={onProjectNameBlur}
spellCheck={false}
value={projectName}
/>
) : (
<ProjectName
onClick={() => {
setEditProjectName(true);
}}
>
{projectName}
</ProjectName>
)}
</>
);
};
type NavBarProps = { type NavBarProps = {
projectName: string; projectName: string;
onProfileClick: (bottom: number, right: number) => void; onProfileClick: (bottom: number, right: number) => void;
onSaveProjectName?: (projectName: string) => void;
onNotificationClick: () => void; onNotificationClick: () => void;
bgColor: string; bgColor: string;
firstName: string; firstName: string;
@ -38,8 +102,10 @@ type NavBarProps = {
initials: string; initials: string;
projectMembers?: Array<TaskUser> | null; projectMembers?: Array<TaskUser> | null;
}; };
const NavBar: React.FC<NavBarProps> = ({ const NavBar: React.FC<NavBarProps> = ({
projectName, projectName,
onSaveProjectName,
onProfileClick, onProfileClick,
onNotificationClick, onNotificationClick,
firstName, firstName,
@ -68,14 +134,14 @@ const NavBar: React.FC<NavBarProps> = ({
</Popup>, </Popup>,
); );
}; };
return ( return (
<NavbarWrapper> <NavbarWrapper>
<NavbarHeader> <NavbarHeader>
<ProjectActions> <ProjectActions>
<ProjectMeta> <ProjectMeta>
<ProjectSwitcher>Projects</ProjectSwitcher> <ProjectSwitcher>Projects</ProjectSwitcher>
<Separator>»</Separator> <ProjectHeading projectName={projectName} onSaveProjectName={onSaveProjectName} />
<ProjectName>{projectName}</ProjectName>
<ProjectSettingsButton> <ProjectSettingsButton>
<AngleDown color="#c2c6dc" /> <AngleDown color="#c2c6dc" />
</ProjectSettingsButton> </ProjectSettingsButton>
@ -94,7 +160,7 @@ const NavBar: React.FC<NavBarProps> = ({
{projectMembers && ( {projectMembers && (
<ProjectMembers> <ProjectMembers>
{projectMembers.map(member => ( {projectMembers.map(member => (
<TaskAssignee key={member.userID} size={28} member={member} onMemberProfile={onMemberProfile} /> <TaskAssignee key={member.id} size={28} member={member} onMemberProfile={onMemberProfile} />
))} ))}
<InviteButton>Invite</InviteButton> <InviteButton>Invite</InviteButton>
</ProjectMembers> </ProjectMembers>
@ -104,9 +170,7 @@ const NavBar: React.FC<NavBarProps> = ({
</NotificationContainer> </NotificationContainer>
<ProfileContainer> <ProfileContainer>
<ProfileNameWrapper> <ProfileNameWrapper>
<ProfileNamePrimary> <ProfileNamePrimary>{`${firstName} ${lastName}`}</ProfileNamePrimary>
{firstName} {lastName}
</ProfileNamePrimary>
<ProfileNameSecondary>Manager</ProfileNameSecondary> <ProfileNameSecondary>Manager</ProfileNameSecondary>
</ProfileNameWrapper> </ProfileNameWrapper>
<ProfileIcon ref={$profileRef} onClick={handleProfileClick} bgColor={bgColor}> <ProfileIcon ref={$profileRef} onClick={handleProfileClick} bgColor={bgColor}>

View File

@ -34,10 +34,8 @@ export type LabelColor = {
export type TaskLabel = { export type TaskLabel = {
__typename?: 'TaskLabel'; __typename?: 'TaskLabel';
id: Scalars['ID']; id: Scalars['ID'];
projectLabelID: Scalars['UUID']; projectLabel: ProjectLabel;
assignedDate: Scalars['Time']; assignedDate: Scalars['Time'];
colorHex: Scalars['String'];
name?: Maybe<Scalars['String']>;
}; };
export type ProfileIcon = { export type ProfileIcon = {
@ -255,11 +253,10 @@ export type UpdateTaskDescriptionInput = {
export type AddTaskLabelInput = { export type AddTaskLabelInput = {
taskID: Scalars['UUID']; taskID: Scalars['UUID'];
labelColorID: Scalars['UUID']; projectLabelID: Scalars['UUID'];
}; };
export type RemoveTaskLabelInput = { export type RemoveTaskLabelInput = {
taskID: Scalars['UUID'];
taskLabelID: Scalars['UUID']; taskLabelID: Scalars['UUID'];
}; };
@ -289,12 +286,29 @@ export type UpdateProjectLabelColor = {
labelColorID: Scalars['UUID']; labelColorID: Scalars['UUID'];
}; };
export type ToggleTaskLabelInput = {
taskID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
};
export type ToggleTaskLabelPayload = {
__typename?: 'ToggleTaskLabelPayload';
active: Scalars['Boolean'];
task: Task;
};
export type UpdateProjectName = {
projectID: Scalars['UUID'];
name: Scalars['String'];
};
export type Mutation = { export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
createRefreshToken: RefreshToken; createRefreshToken: RefreshToken;
createUserAccount: UserAccount; createUserAccount: UserAccount;
createTeam: Team; createTeam: Team;
createProject: Project; createProject: Project;
updateProjectName: Project;
createProjectLabel: ProjectLabel; createProjectLabel: ProjectLabel;
deleteProjectLabel: ProjectLabel; deleteProjectLabel: ProjectLabel;
updateProjectLabel: ProjectLabel; updateProjectLabel: ProjectLabel;
@ -305,6 +319,7 @@ export type Mutation = {
deleteTaskGroup: DeleteTaskGroupPayload; deleteTaskGroup: DeleteTaskGroupPayload;
addTaskLabel: Task; addTaskLabel: Task;
removeTaskLabel: Task; removeTaskLabel: Task;
toggleTaskLabel: ToggleTaskLabelPayload;
createTask: Task; createTask: Task;
updateTaskDescription: Task; updateTaskDescription: Task;
updateTaskLocation: Task; updateTaskLocation: Task;
@ -336,6 +351,11 @@ export type MutationCreateProjectArgs = {
}; };
export type MutationUpdateProjectNameArgs = {
input?: Maybe<UpdateProjectName>;
};
export type MutationCreateProjectLabelArgs = { export type MutationCreateProjectLabelArgs = {
input: NewProjectLabel; input: NewProjectLabel;
}; };
@ -386,6 +406,11 @@ export type MutationRemoveTaskLabelArgs = {
}; };
export type MutationToggleTaskLabelArgs = {
input: ToggleTaskLabelInput;
};
export type MutationCreateTaskArgs = { export type MutationCreateTaskArgs = {
input: NewTask; input: NewTask;
}; };
@ -476,8 +501,19 @@ export type CreateTaskMutation = (
& Pick<Task, 'id' | 'name' | 'position' | 'description'> & Pick<Task, 'id' | 'name' | 'position' | 'description'>
& { taskGroup: ( & { taskGroup: (
{ __typename?: 'TaskGroup' } { __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'> & Pick<TaskGroup, 'id' | 'name' | 'position'>
), assigned: Array<( ), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' } { __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'firstName' | 'lastName'> & Pick<ProjectMember, 'id' | 'firstName' | 'lastName'>
& { profileIcon: ( & { profileIcon: (
@ -580,7 +616,21 @@ export type FindProjectQuery = (
& { tasks: Array<( & { tasks: Array<(
{ __typename?: 'Task' } { __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'position' | 'description'> & Pick<Task, 'id' | 'name' | 'position' | 'description'>
& { assigned: Array<( & { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id' | 'name' | 'position'>
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' } { __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'firstName' | 'lastName'> & Pick<ProjectMember, 'id' | 'firstName' | 'lastName'>
& { profileIcon: ( & { profileIcon: (
@ -609,7 +659,18 @@ export type FindTaskQuery = (
& { taskGroup: ( & { taskGroup: (
{ __typename?: 'TaskGroup' } { __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'> & Pick<TaskGroup, 'id'>
), assigned: Array<( ), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' } { __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'firstName' | 'lastName'> & Pick<ProjectMember, 'id' | 'firstName' | 'lastName'>
& { profileIcon: ( & { profileIcon: (
@ -650,6 +711,36 @@ export type MeQuery = (
) } ) }
); );
export type ToggleTaskLabelMutationVariables = {
taskID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
};
export type ToggleTaskLabelMutation = (
{ __typename?: 'Mutation' }
& { toggleTaskLabel: (
{ __typename?: 'ToggleTaskLabelPayload' }
& Pick<ToggleTaskLabelPayload, 'active'>
& { task: (
{ __typename?: 'Task' }
& Pick<Task, 'id'>
& { labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'createdDate' | 'name'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'name' | 'position'>
) }
) }
)> }
) }
) }
);
export type UnassignTaskMutationVariables = { export type UnassignTaskMutationVariables = {
taskID: Scalars['UUID']; taskID: Scalars['UUID'];
userID: Scalars['UUID']; userID: Scalars['UUID'];
@ -687,6 +778,20 @@ export type UpdateProjectLabelMutation = (
) } ) }
); );
export type UpdateProjectNameMutationVariables = {
projectID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateProjectNameMutation = (
{ __typename?: 'Mutation' }
& { updateProjectName: (
{ __typename?: 'Project' }
& Pick<Project, 'id' | 'name'>
) }
);
export type UpdateTaskDescriptionMutationVariables = { export type UpdateTaskDescriptionMutationVariables = {
taskID: Scalars['UUID']; taskID: Scalars['UUID'];
description: Scalars['String']; description: Scalars['String'];
@ -834,6 +939,23 @@ export const CreateTaskDocument = gql`
description description
taskGroup { taskGroup {
id id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
} }
assigned { assigned {
id id
@ -1032,13 +1154,13 @@ export const FindProjectDocument = gql`
labels { labels {
id id
createdDate createdDate
name
labelColor { labelColor {
id id
name name
colorHex colorHex
position position
} }
name
} }
taskGroups { taskGroups {
id id
@ -1049,6 +1171,26 @@ export const FindProjectDocument = gql`
name name
position position
description description
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned { assigned {
id id
firstName firstName
@ -1106,6 +1248,21 @@ export const FindTaskDocument = gql`
taskGroup { taskGroup {
id id
} }
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned { assigned {
id id
firstName firstName
@ -1219,6 +1376,57 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
export type MeQueryHookResult = ReturnType<typeof useMeQuery>; export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>; export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>; export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
export const ToggleTaskLabelDocument = gql`
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) {
active
task {
id
labels {
id
assignedDate
projectLabel {
id
createdDate
labelColor {
id
colorHex
name
position
}
name
}
}
}
}
}
`;
export type ToggleTaskLabelMutationFn = ApolloReactCommon.MutationFunction<ToggleTaskLabelMutation, ToggleTaskLabelMutationVariables>;
/**
* __useToggleTaskLabelMutation__
*
* To run a mutation, you first call `useToggleTaskLabelMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useToggleTaskLabelMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [toggleTaskLabelMutation, { data, loading, error }] = useToggleTaskLabelMutation({
* variables: {
* taskID: // value for 'taskID'
* projectLabelID: // value for 'projectLabelID'
* },
* });
*/
export function useToggleTaskLabelMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<ToggleTaskLabelMutation, ToggleTaskLabelMutationVariables>) {
return ApolloReactHooks.useMutation<ToggleTaskLabelMutation, ToggleTaskLabelMutationVariables>(ToggleTaskLabelDocument, baseOptions);
}
export type ToggleTaskLabelMutationHookResult = ReturnType<typeof useToggleTaskLabelMutation>;
export type ToggleTaskLabelMutationResult = ApolloReactCommon.MutationResult<ToggleTaskLabelMutation>;
export type ToggleTaskLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<ToggleTaskLabelMutation, ToggleTaskLabelMutationVariables>;
export const UnassignTaskDocument = gql` export const UnassignTaskDocument = gql`
mutation unassignTask($taskID: UUID!, $userID: UUID!) { mutation unassignTask($taskID: UUID!, $userID: UUID!) {
unassignTask(input: {taskID: $taskID, userID: $userID}) { unassignTask(input: {taskID: $taskID, userID: $userID}) {
@ -1299,6 +1507,40 @@ export function useUpdateProjectLabelMutation(baseOptions?: ApolloReactHooks.Mut
export type UpdateProjectLabelMutationHookResult = ReturnType<typeof useUpdateProjectLabelMutation>; export type UpdateProjectLabelMutationHookResult = ReturnType<typeof useUpdateProjectLabelMutation>;
export type UpdateProjectLabelMutationResult = ApolloReactCommon.MutationResult<UpdateProjectLabelMutation>; export type UpdateProjectLabelMutationResult = ApolloReactCommon.MutationResult<UpdateProjectLabelMutation>;
export type UpdateProjectLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateProjectLabelMutation, UpdateProjectLabelMutationVariables>; export type UpdateProjectLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateProjectLabelMutation, UpdateProjectLabelMutationVariables>;
export const UpdateProjectNameDocument = gql`
mutation updateProjectName($projectID: UUID!, $name: String!) {
updateProjectName(input: {projectID: $projectID, name: $name}) {
id
name
}
}
`;
export type UpdateProjectNameMutationFn = ApolloReactCommon.MutationFunction<UpdateProjectNameMutation, UpdateProjectNameMutationVariables>;
/**
* __useUpdateProjectNameMutation__
*
* To run a mutation, you first call `useUpdateProjectNameMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateProjectNameMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [updateProjectNameMutation, { data, loading, error }] = useUpdateProjectNameMutation({
* variables: {
* projectID: // value for 'projectID'
* name: // value for 'name'
* },
* });
*/
export function useUpdateProjectNameMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateProjectNameMutation, UpdateProjectNameMutationVariables>) {
return ApolloReactHooks.useMutation<UpdateProjectNameMutation, UpdateProjectNameMutationVariables>(UpdateProjectNameDocument, baseOptions);
}
export type UpdateProjectNameMutationHookResult = ReturnType<typeof useUpdateProjectNameMutation>;
export type UpdateProjectNameMutationResult = ApolloReactCommon.MutationResult<UpdateProjectNameMutation>;
export type UpdateProjectNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateProjectNameMutation, UpdateProjectNameMutationVariables>;
export const UpdateTaskDescriptionDocument = gql` export const UpdateTaskDescriptionDocument = gql`
mutation updateTaskDescription($taskID: UUID!, $description: String!) { mutation updateTaskDescription($taskID: UUID!, $description: String!) {
updateTaskDescription(input: {taskID: $taskID, description: $description}) { updateTaskDescription(input: {taskID: $taskID, description: $description}) {

View File

@ -6,6 +6,23 @@ mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
description description
taskGroup { taskGroup {
id id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
} }
assigned { assigned {
id id

View File

@ -14,13 +14,13 @@ query findProject($projectId: String!) {
labels { labels {
id id
createdDate createdDate
name
labelColor { labelColor {
id id
name name
colorHex colorHex
position position
} }
name
} }
taskGroups { taskGroups {
id id
@ -31,6 +31,26 @@ query findProject($projectId: String!) {
name name
position position
description description
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned { assigned {
id id
firstName firstName

View File

@ -7,6 +7,21 @@ query findTask($taskID: UUID!) {
taskGroup { taskGroup {
id id
} }
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned { assigned {
id id
firstName firstName

View File

@ -0,0 +1,29 @@
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
toggleTaskLabel(
input: {
taskID: $taskID,
projectLabelID: $projectLabelID
}
) {
active
task {
id
labels {
id
assignedDate
projectLabel {
id
createdDate
labelColor {
id
colorHex
name
position
}
name
}
}
}
}
}

View File

@ -0,0 +1,6 @@
mutation updateProjectName($projectID: UUID!, $name: String!) {
updateProjectName(input: {projectID: $projectID, name: $name}) {
id
name
}
}

View File

@ -3,9 +3,17 @@ import React from 'react';
type Props = { type Props = {
size: number | string; size: number | string;
color: string; color: string;
vertical: boolean;
}; };
const Ellipsis = ({ size, color }: Props) => { const Ellipsis = ({ size, color, vertical }: Props) => {
if (vertical) {
return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 192 512">
<path d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z" />
</svg>
);
}
return ( return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 512 512"> <svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 512 512">
<path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z" /> <path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z" />
@ -16,6 +24,7 @@ const Ellipsis = ({ size, color }: Props) => {
Ellipsis.defaultProps = { Ellipsis.defaultProps = {
size: 16, size: 16,
color: '#000', color: '#000',
vertical: false,
}; };
export default Ellipsis; export default Ellipsis;

View File

@ -1,50 +0,0 @@
import produce from 'immer';
export const addTask = (currentState: BoardState, newTask: Task) => {
return produce(currentState, (draftState: BoardState) => {
draftState.tasks[newTask.taskID] = newTask;
});
};
export const deleteTask = (currentState: BoardState, taskID: string) => {
return produce(currentState, (draftState: BoardState) => {
delete draftState.tasks[taskID];
});
};
export const addTaskGroup = (currentState: BoardState, newTaskGroup: TaskGroup) => {
return produce(currentState, (draftState: BoardState) => {
draftState.columns[newTaskGroup.taskGroupID] = newTaskGroup;
});
};
export const updateTaskGroup = (currentState: BoardState, newTaskGroup: TaskGroup) => {
return produce(currentState, (draftState: BoardState) => {
draftState.columns[newTaskGroup.taskGroupID] = newTaskGroup;
});
};
export const updateTask = (currentState: BoardState, newTask: Task) => {
return produce(currentState, (draftState: BoardState) => {
draftState.tasks[newTask.taskID] = newTask;
});
};
export const deleteTaskGroup = (currentState: BoardState, deletedTaskGroupID: string) => {
return produce(currentState, (draftState: BoardState) => {
delete draftState.columns[deletedTaskGroupID];
const filteredTasks = Object.keys(currentState.tasks)
.filter(taskID => currentState.tasks[taskID].taskGroup.taskGroupID !== deletedTaskGroupID)
.reduce((obj: TaskState, key: string) => {
obj[key] = currentState.tasks[key];
return obj;
}, {});
draftState.tasks = filteredTasks;
});
};
export const updateTaskName = (currentState: BoardState, taskID: string, newName: string) => {
return produce(currentState, (draftState: BoardState) => {
draftState.tasks[taskID].name = newName;
});
};