From fba4de631f729d95e62cca68557f1e363a4e6155 Mon Sep 17 00:00:00 2001 From: Jordan Knott Date: Tue, 26 May 2020 19:53:31 -0500 Subject: [PATCH] feature: add more to project pane --- api/graph/generated.go | 406 +++++++++++------- api/graph/models_gen.go | 2 +- api/graph/schema.graphqls | 18 +- api/graph/schema.resolvers.go | 59 ++- .../0017_add-profile-bg-delete-cascade.up.sql | 7 + assets/favicon.svg | 69 +++ web/public/favicon.ico | Bin 3150 -> 302435 bytes web/public/index.html | 2 +- web/src/App/TopNavbar.tsx | 6 +- web/src/App/index.tsx | 33 +- web/src/Projects/Project/Details/index.tsx | 77 ++-- .../Projects/Project/KanbanBoard/index.tsx | 6 +- web/src/Projects/Project/index.tsx | 343 +++++++++++---- web/src/Projects/index.tsx | 20 +- web/src/citadel.d.ts | 2 +- .../shared/components/CardComposer/Styles.ts | 9 +- .../shared/components/DropdownMenu/Styles.ts | 4 +- web/src/shared/components/List/Styles.ts | 7 +- web/src/shared/components/List/index.tsx | 19 +- .../shared/components/ListActions/Styles.ts | 6 +- web/src/shared/components/Lists/index.tsx | 2 +- .../shared/components/MemberManager/Styles.ts | 25 +- .../shared/components/MemberManager/index.tsx | 2 +- .../shared/components/MiniProfile/Styles.ts | 32 +- .../shared/components/MiniProfile/index.tsx | 26 +- web/src/shared/components/Modal/Styles.ts | 2 +- .../components/PopupMenu/LabelEditor.tsx | 40 +- .../components/PopupMenu/LabelManager.tsx | 86 ++-- .../PopupMenu/PopupMenu.stories.tsx | 118 ++++- web/src/shared/components/PopupMenu/Styles.ts | 147 +++++-- web/src/shared/components/PopupMenu/index.tsx | 217 +++++++++- .../QuickCardEditor.stories.tsx | 2 +- .../components/QuickCardEditor/Styles.ts | 2 +- .../shared/components/TaskDetails/Styles.ts | 3 + .../TaskDetails/TaskDetails.stories.tsx | 1 + .../shared/components/TaskDetails/index.tsx | 43 +- web/src/shared/components/TopNavbar/Styles.ts | 74 +++- .../TopNavbar/TopNavbar.stories.tsx | 1 + web/src/shared/components/TopNavbar/index.tsx | 20 +- web/src/shared/generated/graphql.tsx | 191 +++++--- web/src/shared/graphql/assignTask.graphqls | 4 +- .../graphql/createProjectLabel.graphqls | 8 + web/src/shared/graphql/createTask.graphqls | 19 +- .../shared/graphql/createTaskGroup.graphqls | 2 +- .../shared/graphql/deleteTaskGroup.graphqls | 4 +- web/src/shared/graphql/findProject.graphqls | 14 +- web/src/shared/graphql/findTask.graphqls | 6 +- web/src/shared/graphql/getProjects.graphqls | 4 +- web/src/shared/graphql/unassignTask.graphqls | 4 +- .../graphql/updateTaskDescription.graphqls | 2 +- .../graphql/updateTaskGroupLocation.graphqls | 2 +- .../graphql/updateTaskLocation.graphqls | 2 +- .../shared/graphql/updateTaskName.graphqls | 2 +- web/src/shared/icons/AngleDown.tsx | 27 ++ web/src/shared/icons/AngleLeft.tsx | 24 ++ web/src/shared/icons/Bell.tsx | 4 +- web/src/shared/icons/Bolt.tsx | 24 ++ web/src/shared/icons/Cog.tsx | 24 ++ web/src/shared/icons/Cross.tsx | 4 +- web/src/shared/icons/Star.tsx | 36 ++ web/src/shared/icons/Tags.tsx | 24 ++ web/src/shared/icons/ToggleOn.tsx | 24 ++ web/src/shared/icons/index.ts | 32 +- web/src/shared/utils/boardState.ts | 2 +- 64 files changed, 1845 insertions(+), 582 deletions(-) create mode 100644 api/migrations/0017_add-profile-bg-delete-cascade.up.sql create mode 100644 assets/favicon.svg create mode 100644 web/src/shared/graphql/createProjectLabel.graphqls create mode 100644 web/src/shared/icons/AngleDown.tsx create mode 100644 web/src/shared/icons/AngleLeft.tsx create mode 100644 web/src/shared/icons/Bolt.tsx create mode 100644 web/src/shared/icons/Cog.tsx create mode 100644 web/src/shared/icons/Star.tsx create mode 100644 web/src/shared/icons/Tags.tsx create mode 100644 web/src/shared/icons/ToggleOn.tsx diff --git a/api/graph/generated.go b/api/graph/generated.go index f4e8755..cfac87a 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -41,9 +41,11 @@ type ResolverRoot interface { Project() ProjectResolver ProjectLabel() ProjectLabelResolver Query() QueryResolver + RefreshToken() RefreshTokenResolver Task() TaskResolver TaskGroup() TaskGroupResolver TaskLabel() TaskLabelResolver + Team() TeamResolver UserAccount() UserAccountResolver } @@ -90,27 +92,27 @@ type ComplexityRoot struct { Project struct { CreatedAt func(childComplexity int) int + ID func(childComplexity int) int Labels func(childComplexity int) int Members func(childComplexity int) int Name func(childComplexity int) int Owner func(childComplexity int) int - ProjectID func(childComplexity int) int TaskGroups func(childComplexity int) int Team func(childComplexity int) int } ProjectLabel struct { - ColorHex func(childComplexity int) int - CreatedDate func(childComplexity int) int - Name func(childComplexity int) int - ProjectLabelID func(childComplexity int) int + ColorHex func(childComplexity int) int + CreatedDate func(childComplexity int) int + ID func(childComplexity int) int + Name func(childComplexity int) int } ProjectMember struct { FirstName func(childComplexity int) int + ID func(childComplexity int) int LastName func(childComplexity int) int ProfileIcon func(childComplexity int) int - UserID func(childComplexity int) int } Query struct { @@ -126,7 +128,7 @@ type ComplexityRoot struct { RefreshToken struct { CreatedAt func(childComplexity int) int ExpiresAt func(childComplexity int) int - TokenID func(childComplexity int) int + ID func(childComplexity int) int UserID func(childComplexity int) int } @@ -134,43 +136,43 @@ type ComplexityRoot struct { Assigned func(childComplexity int) int CreatedAt func(childComplexity int) int Description func(childComplexity int) int + ID func(childComplexity int) int Labels func(childComplexity int) int Name func(childComplexity int) int Position func(childComplexity int) int TaskGroup func(childComplexity int) int - TaskID func(childComplexity int) int } TaskGroup struct { - CreatedAt func(childComplexity int) int - Name func(childComplexity int) int - Position func(childComplexity int) int - ProjectID func(childComplexity int) int - TaskGroupID func(childComplexity int) int - Tasks func(childComplexity int) int + CreatedAt func(childComplexity int) int + ID func(childComplexity int) int + Name func(childComplexity int) int + Position func(childComplexity int) int + ProjectID func(childComplexity int) int + Tasks func(childComplexity int) int } 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 - TaskLabelID func(childComplexity int) int } Team struct { CreatedAt func(childComplexity int) int + ID func(childComplexity int) int Name func(childComplexity int) int - TeamID func(childComplexity int) int } UserAccount struct { CreatedAt func(childComplexity int) int Email func(childComplexity int) int FirstName func(childComplexity int) int + ID func(childComplexity int) int LastName func(childComplexity int) int ProfileIcon func(childComplexity int) int - UserID func(childComplexity int) int Username func(childComplexity int) int } } @@ -196,6 +198,8 @@ type MutationResolver interface { LogoutUser(ctx context.Context, input LogoutUser) (bool, error) } type ProjectResolver interface { + ID(ctx context.Context, obj *pg.Project) (uuid.UUID, error) + Team(ctx context.Context, obj *pg.Project) (*pg.Team, error) Owner(ctx context.Context, obj *pg.Project) (*ProjectMember, error) TaskGroups(ctx context.Context, obj *pg.Project) ([]pg.TaskGroup, error) @@ -203,6 +207,8 @@ type ProjectResolver interface { Labels(ctx context.Context, obj *pg.Project) ([]pg.ProjectLabel, error) } type ProjectLabelResolver interface { + ID(ctx context.Context, obj *pg.ProjectLabel) (uuid.UUID, error) + ColorHex(ctx context.Context, obj *pg.ProjectLabel) (string, error) Name(ctx context.Context, obj *pg.ProjectLabel) (*string, error) } @@ -215,7 +221,11 @@ type QueryResolver interface { TaskGroups(ctx context.Context) ([]pg.TaskGroup, error) Me(ctx context.Context) (*pg.UserAccount, error) } +type RefreshTokenResolver interface { + ID(ctx context.Context, obj *pg.RefreshToken) (uuid.UUID, error) +} type TaskResolver interface { + ID(ctx context.Context, obj *pg.Task) (uuid.UUID, error) TaskGroup(ctx context.Context, obj *pg.Task) (*pg.TaskGroup, error) Description(ctx context.Context, obj *pg.Task) (*string, error) @@ -223,15 +233,23 @@ type TaskResolver interface { Labels(ctx context.Context, obj *pg.Task) ([]pg.TaskLabel, error) } type TaskGroupResolver interface { + ID(ctx context.Context, obj *pg.TaskGroup) (uuid.UUID, error) ProjectID(ctx context.Context, obj *pg.TaskGroup) (string, error) Tasks(ctx context.Context, obj *pg.TaskGroup) ([]pg.Task, error) } 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) } +type TeamResolver interface { + ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error) +} type UserAccountResolver interface { + ID(ctx context.Context, obj *pg.UserAccount) (uuid.UUID, error) + ProfileIcon(ctx context.Context, obj *pg.UserAccount) (*ProfileIcon, error) } @@ -522,6 +540,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Project.CreatedAt(childComplexity), true + case "Project.id": + if e.complexity.Project.ID == nil { + break + } + + return e.complexity.Project.ID(childComplexity), true + case "Project.labels": if e.complexity.Project.Labels == nil { break @@ -550,13 +575,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Project.Owner(childComplexity), true - case "Project.projectID": - if e.complexity.Project.ProjectID == nil { - break - } - - return e.complexity.Project.ProjectID(childComplexity), true - case "Project.taskGroups": if e.complexity.Project.TaskGroups == nil { break @@ -585,6 +603,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ProjectLabel.CreatedDate(childComplexity), true + case "ProjectLabel.id": + if e.complexity.ProjectLabel.ID == nil { + break + } + + return e.complexity.ProjectLabel.ID(childComplexity), true + case "ProjectLabel.name": if e.complexity.ProjectLabel.Name == nil { break @@ -592,13 +617,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ProjectLabel.Name(childComplexity), true - case "ProjectLabel.projectLabelID": - if e.complexity.ProjectLabel.ProjectLabelID == nil { - break - } - - return e.complexity.ProjectLabel.ProjectLabelID(childComplexity), true - case "ProjectMember.firstName": if e.complexity.ProjectMember.FirstName == nil { break @@ -606,6 +624,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ProjectMember.FirstName(childComplexity), true + case "ProjectMember.id": + if e.complexity.ProjectMember.ID == nil { + break + } + + return e.complexity.ProjectMember.ID(childComplexity), true + case "ProjectMember.lastName": if e.complexity.ProjectMember.LastName == nil { break @@ -620,13 +645,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ProjectMember.ProfileIcon(childComplexity), true - case "ProjectMember.userID": - if e.complexity.ProjectMember.UserID == nil { - break - } - - return e.complexity.ProjectMember.UserID(childComplexity), true - case "Query.findProject": if e.complexity.Query.FindProject == nil { break @@ -710,12 +728,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RefreshToken.ExpiresAt(childComplexity), true - case "RefreshToken.tokenId": - if e.complexity.RefreshToken.TokenID == nil { + case "RefreshToken.id": + if e.complexity.RefreshToken.ID == nil { break } - return e.complexity.RefreshToken.TokenID(childComplexity), true + return e.complexity.RefreshToken.ID(childComplexity), true case "RefreshToken.userId": if e.complexity.RefreshToken.UserID == nil { @@ -745,6 +763,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Task.Description(childComplexity), true + case "Task.id": + if e.complexity.Task.ID == nil { + break + } + + return e.complexity.Task.ID(childComplexity), true + case "Task.labels": if e.complexity.Task.Labels == nil { break @@ -773,13 +798,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Task.TaskGroup(childComplexity), true - case "Task.taskID": - if e.complexity.Task.TaskID == nil { - break - } - - return e.complexity.Task.TaskID(childComplexity), true - case "TaskGroup.createdAt": if e.complexity.TaskGroup.CreatedAt == nil { break @@ -787,6 +805,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.TaskGroup.CreatedAt(childComplexity), true + case "TaskGroup.id": + if e.complexity.TaskGroup.ID == nil { + break + } + + return e.complexity.TaskGroup.ID(childComplexity), true + case "TaskGroup.name": if e.complexity.TaskGroup.Name == nil { break @@ -808,13 +833,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.TaskGroup.ProjectID(childComplexity), true - case "TaskGroup.taskGroupID": - if e.complexity.TaskGroup.TaskGroupID == nil { - break - } - - return e.complexity.TaskGroup.TaskGroupID(childComplexity), true - case "TaskGroup.tasks": if e.complexity.TaskGroup.Tasks == nil { break @@ -836,6 +854,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.TaskLabel.ColorHex(childComplexity), true + case "TaskLabel.id": + if e.complexity.TaskLabel.ID == nil { + break + } + + return e.complexity.TaskLabel.ID(childComplexity), true + case "TaskLabel.name": if e.complexity.TaskLabel.Name == nil { break @@ -850,13 +875,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.TaskLabel.ProjectLabelID(childComplexity), true - case "TaskLabel.taskLabelID": - if e.complexity.TaskLabel.TaskLabelID == nil { - break - } - - return e.complexity.TaskLabel.TaskLabelID(childComplexity), true - case "Team.createdAt": if e.complexity.Team.CreatedAt == nil { break @@ -864,6 +882,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Team.CreatedAt(childComplexity), true + case "Team.id": + if e.complexity.Team.ID == nil { + break + } + + return e.complexity.Team.ID(childComplexity), true + case "Team.name": if e.complexity.Team.Name == nil { break @@ -871,13 +896,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Team.Name(childComplexity), true - case "Team.teamID": - if e.complexity.Team.TeamID == nil { - break - } - - return e.complexity.Team.TeamID(childComplexity), true - case "UserAccount.createdAt": if e.complexity.UserAccount.CreatedAt == nil { break @@ -899,6 +917,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.UserAccount.FirstName(childComplexity), true + case "UserAccount.id": + if e.complexity.UserAccount.ID == nil { + break + } + + return e.complexity.UserAccount.ID(childComplexity), true + case "UserAccount.lastName": if e.complexity.UserAccount.LastName == nil { break @@ -913,13 +938,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.UserAccount.ProfileIcon(childComplexity), true - case "UserAccount.userID": - if e.complexity.UserAccount.UserID == nil { - break - } - - return e.complexity.UserAccount.UserID(childComplexity), true - case "UserAccount.username": if e.complexity.UserAccount.Username == nil { break @@ -995,14 +1013,14 @@ var sources = []*ast.Source{ scalar UUID type ProjectLabel { - projectLabelID: ID! + id: ID! createdDate: Time! colorHex: String! name: String } type TaskLabel { - taskLabelID: ID! + id: ID! projectLabelID: UUID! assignedDate: Time! colorHex: String! @@ -1016,21 +1034,21 @@ type ProfileIcon { } type ProjectMember { - userID: ID! + id: ID! firstName: String! lastName: String! profileIcon: ProfileIcon! } type RefreshToken { - tokenId: ID! + id: ID! userId: UUID! expiresAt: Time! createdAt: Time! } type UserAccount { - userID: ID! + id: ID! email: String! createdAt: Time! firstName: String! @@ -1040,13 +1058,13 @@ type UserAccount { } type Team { - teamID: ID! + id: ID! createdAt: Time! name: String! } type Project { - projectID: ID! + id: ID! createdAt: Time! name: String! team: Team! @@ -1057,7 +1075,7 @@ type Project { } type TaskGroup { - taskGroupID: ID! + id: ID! projectID: String! createdAt: Time! name: String! @@ -1066,7 +1084,7 @@ type TaskGroup { } type Task { - taskID: ID! + id: ID! taskGroup: TaskGroup! createdAt: Time! name: String! @@ -2563,7 +2581,7 @@ func (ec *executionContext) _ProfileIcon_bgColor(ctx context.Context, field grap return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) _Project_projectID(ctx context.Context, field graphql.CollectedField, obj *pg.Project) (ret graphql.Marshaler) { +func (ec *executionContext) _Project_id(ctx context.Context, field graphql.CollectedField, obj *pg.Project) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2574,13 +2592,13 @@ func (ec *executionContext) _Project_projectID(ctx context.Context, field graphq Object: "Project", 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.ProjectID, nil + return ec.resolvers.Project().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -2835,7 +2853,7 @@ func (ec *executionContext) _Project_labels(ctx context.Context, field graphql.C return ec.marshalNProjectLabel2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProjectLabelᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _ProjectLabel_projectLabelID(ctx context.Context, field graphql.CollectedField, obj *pg.ProjectLabel) (ret graphql.Marshaler) { +func (ec *executionContext) _ProjectLabel_id(ctx context.Context, field graphql.CollectedField, obj *pg.ProjectLabel) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2846,13 +2864,13 @@ func (ec *executionContext) _ProjectLabel_projectLabelID(ctx context.Context, fi Object: "ProjectLabel", 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.ProjectLabel().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -2968,7 +2986,7 @@ func (ec *executionContext) _ProjectLabel_name(ctx context.Context, field graphq return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) _ProjectMember_userID(ctx context.Context, field graphql.CollectedField, obj *ProjectMember) (ret graphql.Marshaler) { +func (ec *executionContext) _ProjectMember_id(ctx context.Context, field graphql.CollectedField, obj *ProjectMember) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2985,7 +3003,7 @@ func (ec *executionContext) _ProjectMember_userID(ctx context.Context, field gra 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.UserID, nil + return obj.ID, nil }) if err != nil { ec.Error(ctx, err) @@ -3439,7 +3457,7 @@ func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.C return ec.marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx, field.Selections, res) } -func (ec *executionContext) _RefreshToken_tokenId(ctx context.Context, field graphql.CollectedField, obj *pg.RefreshToken) (ret graphql.Marshaler) { +func (ec *executionContext) _RefreshToken_id(ctx context.Context, field graphql.CollectedField, obj *pg.RefreshToken) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3450,13 +3468,13 @@ func (ec *executionContext) _RefreshToken_tokenId(ctx context.Context, field gra Object: "RefreshToken", 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.TokenID, nil + return ec.resolvers.RefreshToken().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -3575,7 +3593,7 @@ func (ec *executionContext) _RefreshToken_createdAt(ctx context.Context, field g return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _Task_taskID(ctx context.Context, field graphql.CollectedField, obj *pg.Task) (ret graphql.Marshaler) { +func (ec *executionContext) _Task_id(ctx context.Context, field graphql.CollectedField, obj *pg.Task) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3586,13 +3604,13 @@ func (ec *executionContext) _Task_taskID(ctx context.Context, field graphql.Coll Object: "Task", 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.TaskID, nil + return ec.resolvers.Task().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -3844,7 +3862,7 @@ func (ec *executionContext) _Task_labels(ctx context.Context, field graphql.Coll return ec.marshalNTaskLabel2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTaskLabelᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _TaskGroup_taskGroupID(ctx context.Context, field graphql.CollectedField, obj *pg.TaskGroup) (ret graphql.Marshaler) { +func (ec *executionContext) _TaskGroup_id(ctx context.Context, field graphql.CollectedField, obj *pg.TaskGroup) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -3855,13 +3873,13 @@ func (ec *executionContext) _TaskGroup_taskGroupID(ctx context.Context, field gr Object: "TaskGroup", 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.TaskGroupID, nil + return ec.resolvers.TaskGroup().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -4048,7 +4066,7 @@ func (ec *executionContext) _TaskGroup_tasks(ctx context.Context, field graphql. return ec.marshalNTask2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTaskᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _TaskLabel_taskLabelID(ctx context.Context, field graphql.CollectedField, obj *pg.TaskLabel) (ret graphql.Marshaler) { +func (ec *executionContext) _TaskLabel_id(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)) @@ -4059,13 +4077,13 @@ func (ec *executionContext) _TaskLabel_taskLabelID(ctx context.Context, field gr 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.TaskLabelID, nil + return ec.resolvers.TaskLabel().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -4215,7 +4233,7 @@ func (ec *executionContext) _TaskLabel_name(ctx context.Context, field graphql.C return ec.marshalOString2ᚖstring(ctx, field.Selections, res) } -func (ec *executionContext) _Team_teamID(ctx context.Context, field graphql.CollectedField, obj *pg.Team) (ret graphql.Marshaler) { +func (ec *executionContext) _Team_id(ctx context.Context, field graphql.CollectedField, obj *pg.Team) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -4226,13 +4244,13 @@ func (ec *executionContext) _Team_teamID(ctx context.Context, field graphql.Coll Object: "Team", 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.TeamID, nil + return ec.resolvers.Team().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -4317,7 +4335,7 @@ func (ec *executionContext) _Team_name(ctx context.Context, field graphql.Collec return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _UserAccount_userID(ctx context.Context, field graphql.CollectedField, obj *pg.UserAccount) (ret graphql.Marshaler) { +func (ec *executionContext) _UserAccount_id(ctx context.Context, field graphql.CollectedField, obj *pg.UserAccount) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -4328,13 +4346,13 @@ func (ec *executionContext) _UserAccount_userID(ctx context.Context, field graph Object: "UserAccount", 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.UserID, nil + return ec.resolvers.UserAccount().ID(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -6365,11 +6383,20 @@ func (ec *executionContext) _Project(ctx context.Context, sel ast.SelectionSet, switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("Project") - case "projectID": - out.Values[i] = ec._Project_projectID(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Project_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "createdAt": out.Values[i] = ec._Project_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -6472,11 +6499,20 @@ func (ec *executionContext) _ProjectLabel(ctx context.Context, sel ast.Selection switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("ProjectLabel") - case "projectLabelID": - out.Values[i] = ec._ProjectLabel_projectLabelID(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._ProjectLabel_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "createdDate": out.Values[i] = ec._ProjectLabel_createdDate(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -6529,8 +6565,8 @@ func (ec *executionContext) _ProjectMember(ctx context.Context, sel ast.Selectio switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("ProjectMember") - case "userID": - out.Values[i] = ec._ProjectMember_userID(ctx, field, obj) + case "id": + out.Values[i] = ec._ProjectMember_id(ctx, field, obj) if out.Values[i] == graphql.Null { invalids++ } @@ -6699,25 +6735,34 @@ func (ec *executionContext) _RefreshToken(ctx context.Context, sel ast.Selection switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("RefreshToken") - case "tokenId": - out.Values[i] = ec._RefreshToken_tokenId(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._RefreshToken_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "userId": out.Values[i] = ec._RefreshToken_userId(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "expiresAt": out.Values[i] = ec._RefreshToken_expiresAt(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "createdAt": out.Values[i] = ec._RefreshToken_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } default: panic("unknown field " + strconv.Quote(field.Name)) @@ -6741,11 +6786,20 @@ func (ec *executionContext) _Task(ctx context.Context, sel ast.SelectionSet, obj switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("Task") - case "taskID": - out.Values[i] = ec._Task_taskID(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Task_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "taskGroup": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -6836,11 +6890,20 @@ func (ec *executionContext) _TaskGroup(ctx context.Context, sel ast.SelectionSet switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("TaskGroup") - case "taskGroupID": - out.Values[i] = ec._TaskGroup_taskGroupID(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._TaskGroup_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "projectID": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -6906,11 +6969,20 @@ func (ec *executionContext) _TaskLabel(ctx context.Context, sel ast.SelectionSet switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("TaskLabel") - case "taskLabelID": - out.Values[i] = ec._TaskLabel_taskLabelID(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._TaskLabel_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "projectLabelID": out.Values[i] = ec._TaskLabel_projectLabelID(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -6968,20 +7040,29 @@ func (ec *executionContext) _Team(ctx context.Context, sel ast.SelectionSet, obj switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("Team") - case "teamID": - out.Values[i] = ec._Team_teamID(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Team_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "createdAt": out.Values[i] = ec._Team_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "name": out.Values[i] = ec._Team_name(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } default: panic("unknown field " + strconv.Quote(field.Name)) @@ -7005,11 +7086,20 @@ func (ec *executionContext) _UserAccount(ctx context.Context, sel ast.SelectionS switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("UserAccount") - case "userID": - out.Values[i] = ec._UserAccount_userID(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&invalids, 1) - } + case "id": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._UserAccount_id(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "email": out.Values[i] = ec._UserAccount_email(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/api/graph/models_gen.go b/api/graph/models_gen.go index 9db2f97..709bce8 100644 --- a/api/graph/models_gen.go +++ b/api/graph/models_gen.go @@ -110,7 +110,7 @@ type ProfileIcon struct { } type ProjectMember struct { - UserID uuid.UUID `json:"userID"` + ID uuid.UUID `json:"id"` FirstName string `json:"firstName"` LastName string `json:"lastName"` ProfileIcon *ProfileIcon `json:"profileIcon"` diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 2131599..42ca110 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -2,14 +2,14 @@ scalar Time scalar UUID type ProjectLabel { - projectLabelID: ID! + id: ID! createdDate: Time! colorHex: String! name: String } type TaskLabel { - taskLabelID: ID! + id: ID! projectLabelID: UUID! assignedDate: Time! colorHex: String! @@ -23,21 +23,21 @@ type ProfileIcon { } type ProjectMember { - userID: ID! + id: ID! firstName: String! lastName: String! profileIcon: ProfileIcon! } type RefreshToken { - tokenId: ID! + id: ID! userId: UUID! expiresAt: Time! createdAt: Time! } type UserAccount { - userID: ID! + id: ID! email: String! createdAt: Time! firstName: String! @@ -47,13 +47,13 @@ type UserAccount { } type Team { - teamID: ID! + id: ID! createdAt: Time! name: String! } type Project { - projectID: ID! + id: ID! createdAt: Time! name: String! team: Team! @@ -64,7 +64,7 @@ type Project { } type TaskGroup { - taskGroupID: ID! + id: ID! projectID: String! createdAt: Time! name: String! @@ -73,7 +73,7 @@ type TaskGroup { } type Task { - taskID: ID! + id: ID! taskGroup: TaskGroup! createdAt: Time! name: String! diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 3f73773..e857b79 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -190,6 +190,10 @@ func (r *mutationResolver) LogoutUser(ctx context.Context, input LogoutUser) (bo return true, err } +func (r *projectResolver) ID(ctx context.Context, obj *pg.Project) (uuid.UUID, error) { + return obj.ProjectID, nil +} + func (r *projectResolver) Team(ctx context.Context, obj *pg.Project) (*pg.Team, error) { team, err := r.Repository.GetTeamByID(ctx, obj.TeamID) return &team, err @@ -226,6 +230,10 @@ func (r *projectResolver) Labels(ctx context.Context, obj *pg.Project) ([]pg.Pro return labels, err } +func (r *projectLabelResolver) ID(ctx context.Context, obj *pg.ProjectLabel) (uuid.UUID, error) { + return obj.ProjectLabelID, nil +} + func (r *projectLabelResolver) ColorHex(ctx context.Context, obj *pg.ProjectLabel) (string, error) { labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID) if err != nil { @@ -235,7 +243,11 @@ func (r *projectLabelResolver) ColorHex(ctx context.Context, obj *pg.ProjectLabe } func (r *projectLabelResolver) Name(ctx context.Context, obj *pg.ProjectLabel) (*string, error) { - panic(fmt.Errorf("not implemented")) + var name *string + if obj.Name.Valid { + name = &obj.Name.String + } + return name, nil } func (r *queryResolver) Users(ctx context.Context) ([]pg.UserAccount, error) { @@ -311,6 +323,14 @@ func (r *queryResolver) Me(ctx context.Context) (*pg.UserAccount, error) { return &user, err } +func (r *refreshTokenResolver) ID(ctx context.Context, obj *pg.RefreshToken) (uuid.UUID, error) { + return obj.TokenID, nil +} + +func (r *taskResolver) ID(ctx context.Context, obj *pg.Task) (uuid.UUID, error) { + return obj.TaskID, nil +} + func (r *taskResolver) TaskGroup(ctx context.Context, obj *pg.Task) (*pg.TaskGroup, error) { taskGroup, err := r.Repository.GetTaskGroupByID(ctx, obj.TaskGroupID) return &taskGroup, err @@ -349,6 +369,10 @@ func (r *taskResolver) Labels(ctx context.Context, obj *pg.Task) ([]pg.TaskLabel return r.Repository.GetTaskLabelsForTaskID(ctx, obj.TaskID) } +func (r *taskGroupResolver) ID(ctx context.Context, obj *pg.TaskGroup) (uuid.UUID, error) { + return obj.TaskGroupID, nil +} + func (r *taskGroupResolver) ProjectID(ctx context.Context, obj *pg.TaskGroup) (string, error) { return obj.ProjectID.String(), nil } @@ -358,6 +382,10 @@ func (r *taskGroupResolver) Tasks(ctx context.Context, obj *pg.TaskGroup) ([]pg. return tasks, err } +func (r *taskLabelResolver) ID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUID, error) { + return obj.TaskLabelID, nil +} + func (r *taskLabelResolver) ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error) { projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID) if err != nil { @@ -382,6 +410,14 @@ func (r *taskLabelResolver) Name(ctx context.Context, obj *pg.TaskLabel) (*strin return &name.String, err } +func (r *teamResolver) ID(ctx context.Context, obj *pg.Team) (uuid.UUID, error) { + return obj.TeamID, nil +} + +func (r *userAccountResolver) ID(ctx context.Context, obj *pg.UserAccount) (uuid.UUID, error) { + return obj.UserID, nil +} + func (r *userAccountResolver) ProfileIcon(ctx context.Context, obj *pg.UserAccount) (*ProfileIcon, error) { initials := string([]rune(obj.FirstName)[0]) + string([]rune(obj.LastName)[0]) profileIcon := &ProfileIcon{nil, &initials, &obj.ProfileBgColor} @@ -400,6 +436,9 @@ func (r *Resolver) ProjectLabel() ProjectLabelResolver { return &projectLabelRes // Query returns QueryResolver implementation. func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } +// RefreshToken returns RefreshTokenResolver implementation. +func (r *Resolver) RefreshToken() RefreshTokenResolver { return &refreshTokenResolver{r} } + // Task returns TaskResolver implementation. func (r *Resolver) Task() TaskResolver { return &taskResolver{r} } @@ -409,6 +448,9 @@ func (r *Resolver) TaskGroup() TaskGroupResolver { return &taskGroupResolver{r} // TaskLabel returns TaskLabelResolver implementation. func (r *Resolver) TaskLabel() TaskLabelResolver { return &taskLabelResolver{r} } +// Team returns TeamResolver implementation. +func (r *Resolver) Team() TeamResolver { return &teamResolver{r} } + // UserAccount returns UserAccountResolver implementation. func (r *Resolver) UserAccount() UserAccountResolver { return &userAccountResolver{r} } @@ -416,20 +458,9 @@ type mutationResolver struct{ *Resolver } type projectResolver struct{ *Resolver } type projectLabelResolver struct{ *Resolver } type queryResolver struct{ *Resolver } +type refreshTokenResolver struct{ *Resolver } type taskResolver struct{ *Resolver } type taskGroupResolver struct{ *Resolver } type taskLabelResolver struct{ *Resolver } +type teamResolver struct{ *Resolver } type userAccountResolver struct{ *Resolver } - -// !!! WARNING !!! -// The code below was going to be deleted when updating resolvers. It has been copied here so you have -// one last chance to move it out of harms way if you want. There are two reasons this happens: -// - 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) ProjectLabelID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUID, error) { - panic(fmt.Errorf("not implemented")) -} -func (r *userAccountResolver) DisplayName(ctx context.Context, obj *pg.UserAccount) (string, error) { - return obj.FirstName + " " + obj.LastName, nil -} diff --git a/api/migrations/0017_add-profile-bg-delete-cascade.up.sql b/api/migrations/0017_add-profile-bg-delete-cascade.up.sql new file mode 100644 index 0000000..b25f027 --- /dev/null +++ b/api/migrations/0017_add-profile-bg-delete-cascade.up.sql @@ -0,0 +1,7 @@ +ALTER TABLE task_assigned + DROP CONSTRAINT task_assigned_task_id_fkey, + ADD CONSTRAINT task_assigned_task_id_fkey + FOREIGN KEY (task_id) + REFERENCES task(task_id) + ON DELETE CASCADE; + diff --git a/assets/favicon.svg b/assets/favicon.svg new file mode 100644 index 0000000..04348ba --- /dev/null +++ b/assets/favicon.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/web/public/favicon.ico b/web/public/favicon.ico index bcd5dfd67cd0361b78123e95c2dd96031f27f743..467a6f67b20214492ead842d7271000ccb3c00ff 100644 GIT binary patch literal 302435 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 MDeX2GIKR{Qf5okjU;qFB diff --git a/web/public/index.html b/web/public/index.html index aa069f2..4176852 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + Citadel diff --git a/web/src/App/TopNavbar.tsx b/web/src/App/TopNavbar.tsx index 4919d2f..ab34bd9 100644 --- a/web/src/App/TopNavbar.tsx +++ b/web/src/App/TopNavbar.tsx @@ -5,7 +5,10 @@ import { useHistory } from 'react-router'; import UserIDContext from 'App/context'; import { useMeQuery } from 'shared/generated/graphql'; -const GlobalTopNavbar: React.FC = () => { +type GlobalTopNavbarProps = { + name: string; +}; +const GlobalTopNavbar: React.FC = ({ name }) => { const { loading, data } = useMeQuery(); const history = useHistory(); const { userID, setUserID } = useContext(UserIDContext); @@ -41,6 +44,7 @@ const GlobalTopNavbar: React.FC = () => { return ( <> { return ( <> - - - - {loading ? ( -
loading
- ) : ( - <> - - - - - - - )} -
+ + + + + {loading ? ( +
loading
+ ) : ( + <> + + + + + + )} +
+
); diff --git a/web/src/Projects/Project/Details/index.tsx b/web/src/Projects/Project/Details/index.tsx index 270ed8a..a47c196 100644 --- a/web/src/Projects/Project/Details/index.tsx +++ b/web/src/Projects/Project/Details/index.tsx @@ -1,11 +1,12 @@ import React, { useState, useContext } from 'react'; import Modal from 'shared/components/Modal'; import TaskDetails from 'shared/components/TaskDetails'; -import PopupMenu from 'shared/components/PopupMenu'; +import PopupMenu, { Popup, usePopup } from 'shared/components/PopupMenu'; import MemberManager from 'shared/components/MemberManager'; import { useRouteMatch, useHistory } from 'react-router'; import { useFindTaskQuery, useAssignTaskMutation, useUnassignTaskMutation } from 'shared/generated/graphql'; import UserIDContext from 'App/context'; +import MiniProfile from 'shared/components/MiniProfile'; type DetailsProps = { taskID: string; @@ -13,7 +14,7 @@ type DetailsProps = { onTaskNameChange: (task: Task, newName: string) => void; onTaskDescriptionChange: (task: Task, newDescription: string) => void; onDeleteTask: (task: Task) => void; - onOpenAddLabelPopup: (task: Task, bounds: ElementBounds) => void; + onOpenAddLabelPopup: (task: Task, $targetRef: React.RefObject) => void; availableMembers: Array; refreshCache: () => void; }; @@ -31,8 +32,10 @@ const Details: React.FC = ({ refreshCache, }) => { const { userID } = useContext(UserIDContext); + const { showPopup } = usePopup(); const history = useHistory(); const match = useRouteMatch(); + const [currentMemberTask, setCurrentMemberTask] = useState(''); const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState); const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } }); const [assignTask] = useAssignTaskMutation({ @@ -55,7 +58,7 @@ const Details: React.FC = ({ } const taskMembers = data.findTask.assigned.map(assigned => { return { - userID: assigned.userID, + userID: assigned.id, displayName: `${assigned.firstName} ${assigned.lastName}`, profileIcon: { url: null, @@ -76,6 +79,8 @@ const Details: React.FC = ({ = ({ onTaskDescriptionChange={onTaskDescriptionChange} onDeleteTask={onDeleteTask} onCloseModal={() => history.push(projectURL)} - onOpenAddMemberPopup={(task, bounds) => { - console.log(task, bounds); - setMemberPopupData({ - isOpen: true, - taskID: task.taskID, - top: bounds.position.top + bounds.size.height + 10, - left: bounds.position.left, - }); + onMemberProfile={($targetRef, memberID) => { + showPopup( + $targetRef, + {}} tab={0}> + { + unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } }); + }} + /> + , + ); + }} + onOpenAddMemberPopup={(task, $targetRef) => { + console.log(`task: ${task.taskID}`); + showPopup( + $targetRef, + {}}> + { + console.log(`is active ${member.userID} - ${isActive}`); + if (isActive) { + assignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } }); + } else { + unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } }); + } + console.log(member, isActive); + }} + /> + , + ); }} onOpenAddLabelPopup={onOpenAddLabelPopup} /> ); }} /> - {memberPopupData.isOpen && ( - setMemberPopupData(initialMemberPopupState)} - left={memberPopupData.left} - > - { - console.log(`is active ${member.userID} - ${isActive}`); - if (isActive) { - assignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } }); - } else { - unassignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } }); - } - console.log(member, isActive); - }} - /> - - )} ); }; diff --git a/web/src/Projects/Project/KanbanBoard/index.tsx b/web/src/Projects/Project/KanbanBoard/index.tsx index f8126c3..10660ee 100644 --- a/web/src/Projects/Project/KanbanBoard/index.tsx +++ b/web/src/Projects/Project/KanbanBoard/index.tsx @@ -6,7 +6,7 @@ import { Board } from './Styles'; type KanbanBoardProps = { listsData: BoardState; - onOpenListActionsPopup: (isOpen: boolean, left: number, top: number, taskGroupID: string) => void; + onOpenListActionsPopup: ($targetRef: React.RefObject, taskGroupID: string) => void; onCardDrop: (task: Task) => void; onListDrop: (taskGroup: TaskGroup) => void; onCardCreate: (taskGroupID: string, name: string) => void; @@ -31,8 +31,8 @@ const KanbanBoard: React.FC = ({ onCardClick={task => { history.push(`${match.url}/c/${task.taskID}`); }} - onExtraMenuOpen={(taskGroupID, pos, size) => { - onOpenListActionsPopup(true, pos.left, pos.top + size.height + 5, taskGroupID); + onExtraMenuOpen={(taskGroupID, $targetRef) => { + onOpenListActionsPopup($targetRef, taskGroupID); }} onQuickEditorOpen={onQuickEditorOpen} onCardCreate={onCardCreate} diff --git a/web/src/Projects/Project/index.tsx b/web/src/Projects/Project/index.tsx index 479eb60..48a27d7 100644 --- a/web/src/Projects/Project/index.tsx +++ b/web/src/Projects/Project/index.tsx @@ -1,6 +1,9 @@ -import React, { useState } from 'react'; +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 { useFindProjectQuery, @@ -13,14 +16,19 @@ import { useDeleteTaskGroupMutation, useUpdateTaskDescriptionMutation, useAssignTaskMutation, + DeleteTaskDocument, + FindProjectDocument, } from 'shared/generated/graphql'; import QuickCardEditor from 'shared/components/QuickCardEditor'; -import PopupMenu from 'shared/components/PopupMenu'; 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 { mixin } from 'shared/utils/styles'; +import LabelManager from 'shared/components/PopupMenu/LabelManager'; +import LabelEditor from 'shared/components/PopupMenu/LabelEditor'; +import produce from 'immer'; import Details from './Details'; type TaskRouteProps = { @@ -45,6 +53,67 @@ const Title = styled.span` color: #fff; `; +type LabelManagerEditorProps = { + labels: Array