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
LogoutUser func(childComplexity int, input LogoutUser) int
RemoveTaskLabel func(childComplexity int, input *RemoveTaskLabelInput) int
ToggleTaskLabel func(childComplexity int, input ToggleTaskLabelInput) int
UnassignTask func(childComplexity int, input *UnassignTaskInput) int
UpdateProjectLabel func(childComplexity int, input UpdateProjectLabel) int
UpdateProjectLabelColor func(childComplexity int, input UpdateProjectLabelColor) int
UpdateProjectLabelName func(childComplexity int, input UpdateProjectLabelName) int
UpdateProjectName func(childComplexity int, input *UpdateProjectName) int
UpdateTaskDescription func(childComplexity int, input UpdateTaskDescriptionInput) int
UpdateTaskGroupLocation func(childComplexity int, input NewTaskGroupLocation) int
UpdateTaskLocation func(childComplexity int, input NewTaskLocation) int
@ -167,10 +169,8 @@ type ComplexityRoot struct {
TaskLabel struct {
AssignedDate func(childComplexity int) int
ColorHex func(childComplexity int) int
ID func(childComplexity int) int
Name func(childComplexity int) int
ProjectLabelID func(childComplexity int) int
ProjectLabel func(childComplexity int) int
}
Team struct {
@ -179,6 +179,11 @@ type ComplexityRoot struct {
Name func(childComplexity int) int
}
ToggleTaskLabelPayload struct {
Active func(childComplexity int) int
Task func(childComplexity int) int
}
UserAccount struct {
CreatedAt func(childComplexity int) int
Email func(childComplexity int) int
@ -198,6 +203,7 @@ type MutationResolver interface {
CreateUserAccount(ctx context.Context, input NewUserAccount) (*pg.UserAccount, error)
CreateTeam(ctx context.Context, input NewTeam) (*pg.Team, error)
CreateProject(ctx context.Context, input NewProject) (*pg.Project, error)
UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*pg.Project, error)
CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*pg.ProjectLabel, error)
DeleteProjectLabel(ctx context.Context, input DeleteProjectLabel) (*pg.ProjectLabel, error)
UpdateProjectLabel(ctx context.Context, input UpdateProjectLabel) (*pg.ProjectLabel, error)
@ -208,6 +214,7 @@ type MutationResolver interface {
DeleteTaskGroup(ctx context.Context, input DeleteTaskGroupInput) (*DeleteTaskGroupPayload, error)
AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*pg.Task, error)
RemoveTaskLabel(ctx context.Context, input *RemoveTaskLabelInput) (*pg.Task, error)
ToggleTaskLabel(ctx context.Context, input ToggleTaskLabelInput) (*ToggleTaskLabelPayload, error)
CreateTask(ctx context.Context, input NewTask) (*pg.Task, error)
UpdateTaskDescription(ctx context.Context, input UpdateTaskDescriptionInput) (*pg.Task, error)
UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*pg.Task, error)
@ -261,9 +268,7 @@ type TaskGroupResolver interface {
}
type TaskLabelResolver interface {
ID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUID, error)
ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error)
Name(ctx context.Context, obj *pg.TaskLabel) (*string, error)
ProjectLabel(ctx context.Context, obj *pg.TaskLabel) (*pg.ProjectLabel, error)
}
type TeamResolver interface {
ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error)
@ -513,6 +518,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.RemoveTaskLabel(childComplexity, args["input"].(*RemoveTaskLabelInput)), true
case "Mutation.toggleTaskLabel":
if e.complexity.Mutation.ToggleTaskLabel == nil {
break
}
args, err := ec.field_Mutation_toggleTaskLabel_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.ToggleTaskLabel(childComplexity, args["input"].(ToggleTaskLabelInput)), true
case "Mutation.unassignTask":
if e.complexity.Mutation.UnassignTask == nil {
break
@ -561,6 +578,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.UpdateProjectLabelName(childComplexity, args["input"].(UpdateProjectLabelName)), true
case "Mutation.updateProjectName":
if e.complexity.Mutation.UpdateProjectName == nil {
break
}
args, err := ec.field_Mutation_updateProjectName_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.UpdateProjectName(childComplexity, args["input"].(*UpdateProjectName)), true
case "Mutation.updateTaskDescription":
if e.complexity.Mutation.UpdateTaskDescription == nil {
break
@ -951,13 +980,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.TaskLabel.AssignedDate(childComplexity), true
case "TaskLabel.colorHex":
if e.complexity.TaskLabel.ColorHex == nil {
break
}
return e.complexity.TaskLabel.ColorHex(childComplexity), true
case "TaskLabel.id":
if e.complexity.TaskLabel.ID == nil {
break
@ -965,19 +987,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.TaskLabel.ID(childComplexity), true
case "TaskLabel.name":
if e.complexity.TaskLabel.Name == nil {
case "TaskLabel.projectLabel":
if e.complexity.TaskLabel.ProjectLabel == nil {
break
}
return e.complexity.TaskLabel.Name(childComplexity), true
case "TaskLabel.projectLabelID":
if e.complexity.TaskLabel.ProjectLabelID == nil {
break
}
return e.complexity.TaskLabel.ProjectLabelID(childComplexity), true
return e.complexity.TaskLabel.ProjectLabel(childComplexity), true
case "Team.createdAt":
if e.complexity.Team.CreatedAt == nil {
@ -1000,6 +1015,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Team.Name(childComplexity), true
case "ToggleTaskLabelPayload.active":
if e.complexity.ToggleTaskLabelPayload.Active == nil {
break
}
return e.complexity.ToggleTaskLabelPayload.Active(childComplexity), true
case "ToggleTaskLabelPayload.task":
if e.complexity.ToggleTaskLabelPayload.Task == nil {
break
}
return e.complexity.ToggleTaskLabelPayload.Task(childComplexity), true
case "UserAccount.createdAt":
if e.complexity.UserAccount.CreatedAt == nil {
break
@ -1132,10 +1161,8 @@ type LabelColor {
type TaskLabel {
id: ID!
projectLabelID: UUID!
projectLabel: ProjectLabel!
assignedDate: Time!
colorHex: String!
name: String
}
type ProfileIcon {
@ -1319,11 +1346,10 @@ input UpdateTaskDescriptionInput {
input AddTaskLabelInput {
taskID: UUID!
labelColorID: UUID!
projectLabelID: UUID!
}
input RemoveTaskLabelInput {
taskID: UUID!
taskLabelID: UUID!
}
@ -1353,6 +1379,21 @@ input UpdateProjectLabelColor {
labelColorID: UUID!
}
input ToggleTaskLabelInput {
taskID: UUID!
projectLabelID: UUID!
}
type ToggleTaskLabelPayload {
active: Boolean!
task: Task!
}
input UpdateProjectName {
projectID: UUID!
name: String!
}
type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken!
@ -1361,6 +1402,7 @@ type Mutation {
createTeam(input: NewTeam!): Team!
createProject(input: NewProject!): Project!
updateProjectName(input: UpdateProjectName): Project!
createProjectLabel(input: NewProjectLabel!): ProjectLabel!
deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
@ -1374,6 +1416,7 @@ type Mutation {
addTaskLabel(input: AddTaskLabelInput): Task!
removeTaskLabel(input: RemoveTaskLabelInput): Task!
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
createTask(input: NewTask!): Task!
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
@ -1589,6 +1632,20 @@ func (ec *executionContext) field_Mutation_removeTaskLabel_args(ctx context.Cont
return args, nil
}
func (ec *executionContext) field_Mutation_toggleTaskLabel_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 ToggleTaskLabelInput
if tmp, ok := rawArgs["input"]; ok {
arg0, err = ec.unmarshalNToggleTaskLabelInput2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_unassignTask_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -1645,6 +1702,20 @@ func (ec *executionContext) field_Mutation_updateProjectLabel_args(ctx context.C
return args, nil
}
func (ec *executionContext) field_Mutation_updateProjectName_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 *UpdateProjectName
if tmp, ok := rawArgs["input"]; ok {
arg0, err = ec.unmarshalOUpdateProjectName2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateProjectName(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_updateTaskDescription_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -2243,6 +2314,47 @@ func (ec *executionContext) _Mutation_createProject(ctx context.Context, field g
return ec.marshalNProject2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProject(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_updateProjectName(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_updateProjectName_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().UpdateProjectName(rctx, args["input"].(*UpdateProjectName))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*pg.Project)
fc.Result = res
return ec.marshalNProject2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProject(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_createProjectLabel(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -2653,6 +2765,47 @@ func (ec *executionContext) _Mutation_removeTaskLabel(ctx context.Context, field
return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_toggleTaskLabel(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_toggleTaskLabel_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().ToggleTaskLabel(rctx, args["input"].(ToggleTaskLabelInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*ToggleTaskLabelPayload)
fc.Result = res
return ec.marshalNToggleTaskLabelPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelPayload(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_createTask(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -4627,7 +4780,7 @@ func (ec *executionContext) _TaskLabel_id(ctx context.Context, field graphql.Col
return ec.marshalNID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskLabel_projectLabelID(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) {
func (ec *executionContext) _TaskLabel_projectLabel(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
@ -4638,13 +4791,13 @@ func (ec *executionContext) _TaskLabel_projectLabelID(ctx context.Context, field
Object: "TaskLabel",
Field: field,
Args: nil,
IsMethod: false,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ProjectLabelID, nil
return ec.resolvers.TaskLabel().ProjectLabel(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
@ -4656,9 +4809,9 @@ func (ec *executionContext) _TaskLabel_projectLabelID(ctx context.Context, field
}
return graphql.Null
}
res := resTmp.(uuid.UUID)
res := resTmp.(*pg.ProjectLabel)
fc.Result = res
return ec.marshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res)
return ec.marshalNProjectLabel2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProjectLabel(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskLabel_assignedDate(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) {
@ -4695,71 +4848,6 @@ func (ec *executionContext) _TaskLabel_assignedDate(ctx context.Context, field g
return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskLabel_colorHex(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TaskLabel",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.TaskLabel().ColorHex(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _TaskLabel_name(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TaskLabel",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.TaskLabel().Name(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Team_id(ctx context.Context, field graphql.CollectedField, obj *pg.Team) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -4862,6 +4950,74 @@ func (ec *executionContext) _Team_name(ctx context.Context, field graphql.Collec
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _ToggleTaskLabelPayload_active(ctx context.Context, field graphql.CollectedField, obj *ToggleTaskLabelPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ToggleTaskLabelPayload",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Active, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _ToggleTaskLabelPayload_task(ctx context.Context, field graphql.CollectedField, obj *ToggleTaskLabelPayload) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ToggleTaskLabelPayload",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Task, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*pg.Task)
fc.Result = res
return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res)
}
func (ec *executionContext) _UserAccount_id(ctx context.Context, field graphql.CollectedField, obj *pg.UserAccount) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -6167,9 +6323,9 @@ func (ec *executionContext) unmarshalInputAddTaskLabelInput(ctx context.Context,
if err != nil {
return it, err
}
case "labelColorID":
case "projectLabelID":
var err error
it.LabelColorID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
it.ProjectLabelID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
@ -6609,6 +6765,24 @@ func (ec *executionContext) unmarshalInputRemoveTaskLabelInput(ctx context.Conte
var it RemoveTaskLabelInput
var asMap = obj.(map[string]interface{})
for k, v := range asMap {
switch k {
case "taskLabelID":
var err error
it.TaskLabelID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputToggleTaskLabelInput(ctx context.Context, obj interface{}) (ToggleTaskLabelInput, error) {
var it ToggleTaskLabelInput
var asMap = obj.(map[string]interface{})
for k, v := range asMap {
switch k {
case "taskID":
@ -6617,9 +6791,9 @@ func (ec *executionContext) unmarshalInputRemoveTaskLabelInput(ctx context.Conte
if err != nil {
return it, err
}
case "taskLabelID":
case "projectLabelID":
var err error
it.TaskLabelID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
it.ProjectLabelID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
@ -6731,6 +6905,30 @@ func (ec *executionContext) unmarshalInputUpdateProjectLabelName(ctx context.Con
return it, nil
}
func (ec *executionContext) unmarshalInputUpdateProjectName(ctx context.Context, obj interface{}) (UpdateProjectName, error) {
var it UpdateProjectName
var asMap = obj.(map[string]interface{})
for k, v := range asMap {
switch k {
case "projectID":
var err error
it.ProjectID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
case "name":
var err error
it.Name, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputUpdateTaskDescriptionInput(ctx context.Context, obj interface{}) (UpdateTaskDescriptionInput, error) {
var it UpdateTaskDescriptionInput
var asMap = obj.(map[string]interface{})
@ -6937,6 +7135,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
case "updateProjectName":
out.Values[i] = ec._Mutation_updateProjectName(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "createProjectLabel":
out.Values[i] = ec._Mutation_createProjectLabel(ctx, field)
if out.Values[i] == graphql.Null {
@ -6987,6 +7190,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
case "toggleTaskLabel":
out.Values[i] = ec._Mutation_toggleTaskLabel(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "createTask":
out.Values[i] = ec._Mutation_createTask(ctx, field)
if out.Values[i] == graphql.Null {
@ -7691,17 +7899,7 @@ func (ec *executionContext) _TaskLabel(ctx context.Context, sel ast.SelectionSet
}
return res
})
case "projectLabelID":
out.Values[i] = ec._TaskLabel_projectLabelID(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
case "assignedDate":
out.Values[i] = ec._TaskLabel_assignedDate(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
case "colorHex":
case "projectLabel":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
@ -7709,23 +7907,17 @@ func (ec *executionContext) _TaskLabel(ctx context.Context, sel ast.SelectionSet
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._TaskLabel_colorHex(ctx, field, obj)
res = ec._TaskLabel_projectLabel(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "name":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
case "assignedDate":
out.Values[i] = ec._TaskLabel_assignedDate(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
}()
res = ec._TaskLabel_name(ctx, field, obj)
return res
})
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@ -7783,6 +7975,38 @@ func (ec *executionContext) _Team(ctx context.Context, sel ast.SelectionSet, obj
return out
}
var toggleTaskLabelPayloadImplementors = []string{"ToggleTaskLabelPayload"}
func (ec *executionContext) _ToggleTaskLabelPayload(ctx context.Context, sel ast.SelectionSet, obj *ToggleTaskLabelPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, toggleTaskLabelPayloadImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ToggleTaskLabelPayload")
case "active":
out.Values[i] = ec._ToggleTaskLabelPayload_active(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "task":
out.Values[i] = ec._ToggleTaskLabelPayload_task(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var userAccountImplementors = []string{"UserAccount"}
func (ec *executionContext) _UserAccount(ctx context.Context, sel ast.SelectionSet, obj *pg.UserAccount) graphql.Marshaler {
@ -8668,6 +8892,24 @@ func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel as
return res
}
func (ec *executionContext) unmarshalNToggleTaskLabelInput2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelInput(ctx context.Context, v interface{}) (ToggleTaskLabelInput, error) {
return ec.unmarshalInputToggleTaskLabelInput(ctx, v)
}
func (ec *executionContext) marshalNToggleTaskLabelPayload2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelPayload(ctx context.Context, sel ast.SelectionSet, v ToggleTaskLabelPayload) graphql.Marshaler {
return ec._ToggleTaskLabelPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNToggleTaskLabelPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐToggleTaskLabelPayload(ctx context.Context, sel ast.SelectionSet, v *ToggleTaskLabelPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._ToggleTaskLabelPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx context.Context, v interface{}) (uuid.UUID, error) {
return UnmarshalUUID(v)
}
@ -9085,6 +9327,18 @@ func (ec *executionContext) unmarshalOUnassignTaskInput2ᚖgithubᚗcomᚋjordan
return &res, err
}
func (ec *executionContext) unmarshalOUpdateProjectName2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateProjectName(ctx context.Context, v interface{}) (UpdateProjectName, error) {
return ec.unmarshalInputUpdateProjectName(ctx, v)
}
func (ec *executionContext) unmarshalOUpdateProjectName2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateProjectName(ctx context.Context, v interface{}) (*UpdateProjectName, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalOUpdateProjectName2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateProjectName(ctx, v)
return &res, err
}
func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler {
if v == nil {
return graphql.Null

View File

@ -9,7 +9,7 @@ import (
type AddTaskLabelInput struct {
TaskID uuid.UUID `json:"taskID"`
LabelColorID uuid.UUID `json:"labelColorID"`
ProjectLabelID uuid.UUID `json:"projectLabelID"`
}
type AssignTaskInput struct {
@ -125,10 +125,19 @@ type ProjectsFilter struct {
}
type RemoveTaskLabelInput struct {
TaskID uuid.UUID `json:"taskID"`
TaskLabelID uuid.UUID `json:"taskLabelID"`
}
type ToggleTaskLabelInput struct {
TaskID uuid.UUID `json:"taskID"`
ProjectLabelID uuid.UUID `json:"projectLabelID"`
}
type ToggleTaskLabelPayload struct {
Active bool `json:"active"`
Task *pg.Task `json:"task"`
}
type UnassignTaskInput struct {
TaskID uuid.UUID `json:"taskID"`
UserID uuid.UUID `json:"userID"`
@ -150,6 +159,11 @@ type UpdateProjectLabelName struct {
Name string `json:"name"`
}
type UpdateProjectName struct {
ProjectID uuid.UUID `json:"projectID"`
Name string `json:"name"`
}
type UpdateTaskDescriptionInput struct {
TaskID uuid.UUID `json:"taskID"`
Description string `json:"description"`

View File

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

View File

@ -49,6 +49,14 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
return &project, err
}
func (r *mutationResolver) UpdateProjectName(ctx context.Context, input *UpdateProjectName) (*pg.Project, error) {
project, err := r.Repository.UpdateProjectNameByID(ctx, pg.UpdateProjectNameByIDParams{ProjectID: input.ProjectID, Name: input.Name})
if err != nil {
return &pg.Project{}, err
}
return &project, nil
}
func (r *mutationResolver) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*pg.ProjectLabel, error) {
createdAt := time.Now().UTC()
@ -130,7 +138,7 @@ func (r *mutationResolver) DeleteTaskGroup(ctx context.Context, input DeleteTask
func (r *mutationResolver) AddTaskLabel(ctx context.Context, input *AddTaskLabelInput) (*pg.Task, error) {
assignedDate := time.Now().UTC()
_, err := r.Repository.CreateTaskLabelForTask(ctx, pg.CreateTaskLabelForTaskParams{input.TaskID, input.LabelColorID, assignedDate})
_, err := r.Repository.CreateTaskLabelForTask(ctx, pg.CreateTaskLabelForTaskParams{input.TaskID, input.ProjectLabelID, assignedDate})
if err != nil {
return &pg.Task{}, err
}
@ -139,7 +147,56 @@ func (r *mutationResolver) AddTaskLabel(ctx context.Context, input *AddTaskLabel
}
func (r *mutationResolver) RemoveTaskLabel(ctx context.Context, input *RemoveTaskLabelInput) (*pg.Task, error) {
panic(fmt.Errorf("not implemented"))
taskLabel, err := r.Repository.GetTaskLabelByID(ctx, input.TaskLabelID)
if err != nil {
return &pg.Task{}, err
}
task, err := r.Repository.GetTaskByID(ctx, taskLabel.TaskID)
if err != nil {
return &pg.Task{}, err
}
err = r.Repository.DeleteTaskLabelByID(ctx, input.TaskLabelID)
return &task, err
}
func (r *mutationResolver) ToggleTaskLabel(ctx context.Context, input ToggleTaskLabelInput) (*ToggleTaskLabelPayload, error) {
task, err := r.Repository.GetTaskByID(ctx, input.TaskID)
if err != nil {
return &ToggleTaskLabelPayload{}, err
}
_, err = r.Repository.GetTaskLabelForTaskByProjectLabelID(ctx, pg.GetTaskLabelForTaskByProjectLabelIDParams{TaskID: input.TaskID, ProjectLabelID: input.ProjectLabelID})
createdAt := time.Now().UTC()
if err == sql.ErrNoRows {
log.WithFields(log.Fields{"err": err}).Warning("no rows")
_, err := r.Repository.CreateTaskLabelForTask(ctx, pg.CreateTaskLabelForTaskParams{
TaskID: input.TaskID,
ProjectLabelID: input.ProjectLabelID,
AssignedDate: createdAt,
})
if err != nil {
return &ToggleTaskLabelPayload{}, err
}
payload := ToggleTaskLabelPayload{Active: true, Task: &task}
return &payload, nil
}
if err != nil {
return &ToggleTaskLabelPayload{}, err
}
err = r.Repository.DeleteTaskLabelForTaskByProjectLabelID(ctx, pg.DeleteTaskLabelForTaskByProjectLabelIDParams{
TaskID: input.TaskID,
ProjectLabelID: input.ProjectLabelID,
})
if err != nil {
return &ToggleTaskLabelPayload{}, err
}
payload := ToggleTaskLabelPayload{Active: false, Task: &task}
return &payload, nil
}
func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*pg.Task, error) {
@ -434,28 +491,9 @@ func (r *taskLabelResolver) ID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUI
return obj.TaskLabelID, nil
}
func (r *taskLabelResolver) ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error) {
func (r *taskLabelResolver) ProjectLabel(ctx context.Context, obj *pg.TaskLabel) (*pg.ProjectLabel, error) {
projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID)
if err != nil {
return "", err
}
labelColor, err := r.Repository.GetLabelColorByID(ctx, projectLabel.LabelColorID)
if err != nil {
return "", err
}
return labelColor.ColorHex, nil
}
func (r *taskLabelResolver) Name(ctx context.Context, obj *pg.TaskLabel) (*string, error) {
projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID)
if err != nil {
return nil, err
}
name := projectLabel.Name
if !name.Valid {
return nil, err
}
return &name.String, err
return &projectLabel, err
}
func (r *teamResolver) ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error) {
@ -523,6 +561,28 @@ type userAccountResolver struct{ *Resolver }
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
func (r *taskLabelResolver) ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error) {
projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID)
if err != nil {
return "", err
}
labelColor, err := r.Repository.GetLabelColorByID(ctx, projectLabel.LabelColorID)
if err != nil {
return "", err
}
return labelColor.ColorHex, nil
}
func (r *taskLabelResolver) Name(ctx context.Context, obj *pg.TaskLabel) (*string, error) {
projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID)
if err != nil {
return nil, err
}
name := projectLabel.Name
if !name.Valid {
return nil, err
}
return &name.String, err
}
func (r *projectLabelResolver) ColorHex(ctx context.Context, obj *pg.ProjectLabel) (string, error) {
labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID)
if err != nil {

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)
GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error)
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error)
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error
CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error)
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error)

View File

@ -121,3 +121,25 @@ func (q *Queries) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Proj
)
return i, err
}
const updateProjectNameByID = `-- name: UpdateProjectNameByID :one
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING project_id, team_id, created_at, name, owner
`
type UpdateProjectNameByIDParams struct {
ProjectID uuid.UUID `json:"project_id"`
Name string `json:"name"`
}
func (q *Queries) UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error) {
row := q.db.QueryRowContext(ctx, updateProjectNameByID, arg.ProjectID, arg.Name)
var i Project
err := row.Scan(
&i.ProjectID,
&i.TeamID,
&i.CreatedAt,
&i.Name,
&i.Owner,
)
return i, err
}

View File

@ -27,6 +27,8 @@ type Querier interface {
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTeamByID(ctx context.Context, teamID uuid.UUID) error
GetAllOrganizations(ctx context.Context) ([]Organization, error)
@ -46,6 +48,8 @@ type Querier interface {
GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error)
GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error)
GetTaskGroupsForProject(ctx context.Context, projectID uuid.UUID) ([]TaskGroup, error)
GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error)
GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error)
GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskLabel, error)
GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error)
GetTeamByID(ctx context.Context, teamID uuid.UUID) (Team, error)
@ -55,6 +59,7 @@ type Querier interface {
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescriptionParams) (Task, error)
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)

View File

@ -33,6 +33,66 @@ func (q *Queries) CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabe
return i, err
}
const deleteTaskLabelByID = `-- name: DeleteTaskLabelByID :exec
DELETE FROM task_label WHERE task_label_id = $1
`
func (q *Queries) DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteTaskLabelByID, taskLabelID)
return err
}
const deleteTaskLabelForTaskByProjectLabelID = `-- name: DeleteTaskLabelForTaskByProjectLabelID :exec
DELETE FROM task_label WHERE project_label_id = $2 AND task_id = $1
`
type DeleteTaskLabelForTaskByProjectLabelIDParams struct {
TaskID uuid.UUID `json:"task_id"`
ProjectLabelID uuid.UUID `json:"project_label_id"`
}
func (q *Queries) DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error {
_, err := q.db.ExecContext(ctx, deleteTaskLabelForTaskByProjectLabelID, arg.TaskID, arg.ProjectLabelID)
return err
}
const getTaskLabelByID = `-- name: GetTaskLabelByID :one
SELECT task_label_id, task_id, project_label_id, assigned_date FROM task_label WHERE task_label_id = $1
`
func (q *Queries) GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error) {
row := q.db.QueryRowContext(ctx, getTaskLabelByID, taskLabelID)
var i TaskLabel
err := row.Scan(
&i.TaskLabelID,
&i.TaskID,
&i.ProjectLabelID,
&i.AssignedDate,
)
return i, err
}
const getTaskLabelForTaskByProjectLabelID = `-- name: GetTaskLabelForTaskByProjectLabelID :one
SELECT task_label_id, task_id, project_label_id, assigned_date FROM task_label WHERE task_id = $1 AND project_label_id = $2
`
type GetTaskLabelForTaskByProjectLabelIDParams struct {
TaskID uuid.UUID `json:"task_id"`
ProjectLabelID uuid.UUID `json:"project_label_id"`
}
func (q *Queries) GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error) {
row := q.db.QueryRowContext(ctx, getTaskLabelForTaskByProjectLabelID, arg.TaskID, arg.ProjectLabelID)
var i TaskLabel
err := row.Scan(
&i.TaskLabelID,
&i.TaskID,
&i.ProjectLabelID,
&i.AssignedDate,
)
return i, err
}
const getTaskLabelsForTaskID = `-- name: GetTaskLabelsForTaskID :many
SELECT task_label_id, task_id, project_label_id, assigned_date FROM task_label WHERE task_id = $1
`

View File

@ -9,3 +9,6 @@ SELECT * FROM project WHERE project_id = $1;
-- name: CreateProject :one
INSERT INTO project(owner, team_id, created_at, name) VALUES ($1, $2, $3, $4) RETURNING *;
-- name: UpdateProjectNameByID :one
UPDATE project SET name = $2 WHERE project_id = $1 RETURNING *;

View File

@ -4,3 +4,15 @@ INSERT INTO task_label (task_id, project_label_id, assigned_date)
-- name: GetTaskLabelsForTaskID :many
SELECT * FROM task_label WHERE task_id = $1;
-- name: GetTaskLabelByID :one
SELECT * FROM task_label WHERE task_label_id = $1;
-- name: DeleteTaskLabelByID :exec
DELETE FROM task_label WHERE task_label_id = $1;
-- name: GetTaskLabelForTaskByProjectLabelID :one
SELECT * FROM task_label WHERE task_id = $1 AND project_label_id = $2;
-- name: DeleteTaskLabelForTaskByProjectLabelID :exec
DELETE FROM task_label WHERE project_label_id = $2 AND task_id = $1;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,12 +7,13 @@ import ListActions from 'shared/components/ListActions';
import MemberManager from 'shared/components/MemberManager';
import DueDateManager from 'shared/components/DueDateManager';
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 BaseStyles from 'App/BaseStyles';
import produce from 'immer';
import PopupMenu, { PopupProvider, usePopup, Popup } from '.';
export default {
component: PopupMenu,
@ -24,28 +25,17 @@ export default {
],
},
};
const labelData = [
const labelData: Array<ProjectLabel> = [
{
labelId: 'development',
id: 'development',
name: 'Development',
createdDate: new Date().toString(),
labelColor: {
id: '1',
name: 'white',
colorHex: LabelColors.BLUE,
name: 'blue',
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 => {
setLabels(
produce(labels, draftState => {
const idx = labels.findIndex(label => label.labelId === labelId);
const idx = labels.findIndex(label => label.id === labelId);
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}>
<LabelEditor
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) => {
setLabels(
produce(labels, draftState => {
const idx = labels.findIndex(label => label.labelId === currentLabel);
const idx = labels.findIndex(label => label.id === currentLabel);
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}
labelColors={[{ id: '1', colorHex: '#c2c6dc', position: 1, name: 'gray' }]}
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);
}}
/>
@ -214,7 +225,12 @@ export const MemberManagerPopup = () => {
<PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<MemberManager
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={[]}
onMemberChange={action('member change')}
@ -251,26 +267,35 @@ export const DueDateManagerPopup = () => {
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<DueDateManager
task={{
taskID: '1',
taskGroup: { name: 'General', taskGroupID: '1' },
id: '1',
taskGroup: { name: 'General', id: '1', position: 1 },
name: 'Hello, world',
position: 1,
labels: [
{
labelId: 'soft-skills',
id: 'soft-skills',
assignedDate: new Date().toString(),
projectLabel: {
createdDate: new Date().toString(),
id: 'label-soft-skills',
name: 'Soft Skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
},
active: true,
name: 'Soft Skills',
},
},
],
description: 'hello!',
members: [
{ userID: '1', profileIcon: { bgColor: null, url: null, initials: null }, displayName: 'Jordan Knott' },
assigned: [
{
id: '1',
profileIcon: { bgColor: null, url: null, initials: null },
firstName: 'Jordan',
lastName: 'Knott',
},
],
}}
onCancel={action('cancel')}

View File

@ -17,28 +17,17 @@ export default {
},
};
const labelData = [
const labelData: Array<ProjectLabel> = [
{
labelId: 'development',
id: 'development',
name: 'Development',
createdDate: 'date',
labelColor: {
id: '1',
name: 'white',
id: 'label-color-blue',
colorHex: LabelColors.BLUE,
name: 'blue',
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}
onSaveName={action('on save name')}
onOpenComposer={action('on open composer')}
tasks={[]}
onExtraMenuOpen={(taskGroupID, $targetRef) => console.log(taskGroupID, $targetRef)}
>
<ListCards>

View File

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

View File

@ -31,7 +31,7 @@ type TaskAssigneeProps = {
const TaskAssignee: React.FC<TaskAssigneeProps> = ({ member, onMemberProfile, size }) => {
const $memberRef = useRef<HTMLDivElement>(null);
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>
</TaskDetailAssignee>
);

View File

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

View File

@ -29,29 +29,34 @@ export const Default = () => {
return (
<TaskDetails
task={{
taskID: '1',
taskGroup: { name: 'General', taskGroupID: '1' },
id: '1',
taskGroup: { name: 'General', id: '1' },
name: 'Hello, world',
position: 1,
labels: [
{
labelId: 'soft-skills',
id: 'soft-skills',
assignedDate: new Date().toString(),
projectLabel: {
createdDate: new Date().toString(),
id: 'label-soft-skills',
name: 'Soft Skills',
labelColor: {
id: '1',
name: 'white',
colorHex: '#fff',
position: 1,
},
active: true,
name: 'Soft Skills',
},
},
],
description,
members: [
assigned: [
{
userID: '1',
id: '1',
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>
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
<TaskDetailAssignees>
{task.members && task.members.length === 0 ? (
{task.assigned && task.assigned.length === 0 ? (
<UnassignedLabel ref={$unassignedRef} onClick={onUnassignedClick}>
Unassigned
</UnassignedLabel>
) : (
<>
{task.members &&
task.members.map(member => (
<TaskAssignee size={32} member={member} onMemberProfile={onMemberProfile} />
{task.assigned &&
task.assigned.map(member => (
<TaskAssignee key={member.id} size={32} member={member} onMemberProfile={onMemberProfile} />
))}
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}>
<TaskDetailsAddMemberIcon>
@ -214,7 +214,11 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskDetailSectionTitle>Labels</TaskDetailSectionTitle>
<TaskDetailLabels>
{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}>
<TaskDetailsAddLabelIcon>

View File

@ -1,4 +1,5 @@
import styled, { css } from 'styled-components';
import TextareaAutosize from 'react-autosize-textarea';
import { mixin } from 'shared/utils/styles';
export const NavbarWrapper = styled.div`
@ -135,7 +136,36 @@ export const ProjectName = styled.h1`
color: #c2c6dc;
font-weight: 600;
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`

View File

@ -1,8 +1,9 @@
import React, { useRef } from 'react';
import { Star, Bell, Cog, AngleDown } from 'shared/icons';
import React, { useRef, useState, useEffect } from 'react';
import { Star, Ellipsis, Bell, Cog, AngleDown } from 'shared/icons';
import {
NotificationContainer,
ProjectNameTextarea,
InviteButton,
GlobalActions,
ProjectActions,
@ -28,9 +29,72 @@ import TaskAssignee from 'shared/components/TaskAssignee';
import { usePopup, Popup } from 'shared/components/PopupMenu';
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 = {
projectName: string;
onProfileClick: (bottom: number, right: number) => void;
onSaveProjectName?: (projectName: string) => void;
onNotificationClick: () => void;
bgColor: string;
firstName: string;
@ -38,8 +102,10 @@ type NavBarProps = {
initials: string;
projectMembers?: Array<TaskUser> | null;
};
const NavBar: React.FC<NavBarProps> = ({
projectName,
onSaveProjectName,
onProfileClick,
onNotificationClick,
firstName,
@ -68,14 +134,14 @@ const NavBar: React.FC<NavBarProps> = ({
</Popup>,
);
};
return (
<NavbarWrapper>
<NavbarHeader>
<ProjectActions>
<ProjectMeta>
<ProjectSwitcher>Projects</ProjectSwitcher>
<Separator>»</Separator>
<ProjectName>{projectName}</ProjectName>
<ProjectHeading projectName={projectName} onSaveProjectName={onSaveProjectName} />
<ProjectSettingsButton>
<AngleDown color="#c2c6dc" />
</ProjectSettingsButton>
@ -94,7 +160,7 @@ const NavBar: React.FC<NavBarProps> = ({
{projectMembers && (
<ProjectMembers>
{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>
</ProjectMembers>
@ -104,9 +170,7 @@ const NavBar: React.FC<NavBarProps> = ({
</NotificationContainer>
<ProfileContainer>
<ProfileNameWrapper>
<ProfileNamePrimary>
{firstName} {lastName}
</ProfileNamePrimary>
<ProfileNamePrimary>{`${firstName} ${lastName}`}</ProfileNamePrimary>
<ProfileNameSecondary>Manager</ProfileNameSecondary>
</ProfileNameWrapper>
<ProfileIcon ref={$profileRef} onClick={handleProfileClick} bgColor={bgColor}>

View File

@ -34,10 +34,8 @@ export type LabelColor = {
export type TaskLabel = {
__typename?: 'TaskLabel';
id: Scalars['ID'];
projectLabelID: Scalars['UUID'];
projectLabel: ProjectLabel;
assignedDate: Scalars['Time'];
colorHex: Scalars['String'];
name?: Maybe<Scalars['String']>;
};
export type ProfileIcon = {
@ -255,11 +253,10 @@ export type UpdateTaskDescriptionInput = {
export type AddTaskLabelInput = {
taskID: Scalars['UUID'];
labelColorID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
};
export type RemoveTaskLabelInput = {
taskID: Scalars['UUID'];
taskLabelID: Scalars['UUID'];
};
@ -289,12 +286,29 @@ export type UpdateProjectLabelColor = {
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 = {
__typename?: 'Mutation';
createRefreshToken: RefreshToken;
createUserAccount: UserAccount;
createTeam: Team;
createProject: Project;
updateProjectName: Project;
createProjectLabel: ProjectLabel;
deleteProjectLabel: ProjectLabel;
updateProjectLabel: ProjectLabel;
@ -305,6 +319,7 @@ export type Mutation = {
deleteTaskGroup: DeleteTaskGroupPayload;
addTaskLabel: Task;
removeTaskLabel: Task;
toggleTaskLabel: ToggleTaskLabelPayload;
createTask: Task;
updateTaskDescription: Task;
updateTaskLocation: Task;
@ -336,6 +351,11 @@ export type MutationCreateProjectArgs = {
};
export type MutationUpdateProjectNameArgs = {
input?: Maybe<UpdateProjectName>;
};
export type MutationCreateProjectLabelArgs = {
input: NewProjectLabel;
};
@ -386,6 +406,11 @@ export type MutationRemoveTaskLabelArgs = {
};
export type MutationToggleTaskLabelArgs = {
input: ToggleTaskLabelInput;
};
export type MutationCreateTaskArgs = {
input: NewTask;
};
@ -476,8 +501,19 @@ export type CreateTaskMutation = (
& Pick<Task, 'id' | 'name' | 'position' | 'description'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
), assigned: Array<(
& 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' }
& Pick<ProjectMember, 'id' | 'firstName' | 'lastName'>
& { profileIcon: (
@ -580,7 +616,21 @@ export type FindProjectQuery = (
& { tasks: Array<(
{ __typename?: 'Task' }
& 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' }
& Pick<ProjectMember, 'id' | 'firstName' | 'lastName'>
& { profileIcon: (
@ -609,7 +659,18 @@ export type FindTaskQuery = (
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& 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' }
& Pick<ProjectMember, 'id' | 'firstName' | 'lastName'>
& { 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 = {
taskID: 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 = {
taskID: Scalars['UUID'];
description: Scalars['String'];
@ -834,6 +939,23 @@ export const CreateTaskDocument = gql`
description
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
@ -1032,13 +1154,13 @@ export const FindProjectDocument = gql`
labels {
id
createdDate
name
labelColor {
id
name
colorHex
position
}
name
}
taskGroups {
id
@ -1049,6 +1171,26 @@ export const FindProjectDocument = gql`
name
position
description
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
firstName
@ -1106,6 +1248,21 @@ export const FindTaskDocument = gql`
taskGroup {
id
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
firstName
@ -1219,6 +1376,57 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
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`
mutation unassignTask($taskID: UUID!, $userID: UUID!) {
unassignTask(input: {taskID: $taskID, userID: $userID}) {
@ -1299,6 +1507,40 @@ export function useUpdateProjectLabelMutation(baseOptions?: ApolloReactHooks.Mut
export type UpdateProjectLabelMutationHookResult = ReturnType<typeof useUpdateProjectLabelMutation>;
export type UpdateProjectLabelMutationResult = ApolloReactCommon.MutationResult<UpdateProjectLabelMutation>;
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`
mutation updateTaskDescription($taskID: UUID!, $description: String!) {
updateTaskDescription(input: {taskID: $taskID, description: $description}) {

View File

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

View File

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

View File

@ -7,6 +7,21 @@ query findTask($taskID: UUID!) {
taskGroup {
id
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
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 = {
size: number | 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 (
<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" />
@ -16,6 +24,7 @@ const Ellipsis = ({ size, color }: Props) => {
Ellipsis.defaultProps = {
size: 16,
color: '#000',
vertical: false,
};
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;
});
};