feature: add projects & quick card members button
This commit is contained in:
parent
67ac88856b
commit
4c02df9061
@ -137,6 +137,7 @@ type ComplexityRoot struct {
|
|||||||
Me func(childComplexity int) int
|
Me func(childComplexity int) int
|
||||||
Projects func(childComplexity int, input *ProjectsFilter) int
|
Projects func(childComplexity int, input *ProjectsFilter) int
|
||||||
TaskGroups func(childComplexity int) int
|
TaskGroups func(childComplexity int) int
|
||||||
|
Teams func(childComplexity int) int
|
||||||
Users func(childComplexity int) int
|
Users func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +185,11 @@ type ComplexityRoot struct {
|
|||||||
Task func(childComplexity int) int
|
Task func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateTaskLocationPayload struct {
|
||||||
|
PreviousTaskGroupID func(childComplexity int) int
|
||||||
|
Task func(childComplexity int) int
|
||||||
|
}
|
||||||
|
|
||||||
UserAccount struct {
|
UserAccount struct {
|
||||||
CreatedAt func(childComplexity int) int
|
CreatedAt func(childComplexity int) int
|
||||||
Email func(childComplexity int) int
|
Email func(childComplexity int) int
|
||||||
@ -217,7 +223,7 @@ type MutationResolver interface {
|
|||||||
ToggleTaskLabel(ctx context.Context, input ToggleTaskLabelInput) (*ToggleTaskLabelPayload, error)
|
ToggleTaskLabel(ctx context.Context, input ToggleTaskLabelInput) (*ToggleTaskLabelPayload, error)
|
||||||
CreateTask(ctx context.Context, input NewTask) (*pg.Task, error)
|
CreateTask(ctx context.Context, input NewTask) (*pg.Task, error)
|
||||||
UpdateTaskDescription(ctx context.Context, input UpdateTaskDescriptionInput) (*pg.Task, error)
|
UpdateTaskDescription(ctx context.Context, input UpdateTaskDescriptionInput) (*pg.Task, error)
|
||||||
UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*pg.Task, error)
|
UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*UpdateTaskLocationPayload, error)
|
||||||
UpdateTaskName(ctx context.Context, input UpdateTaskName) (*pg.Task, error)
|
UpdateTaskName(ctx context.Context, input UpdateTaskName) (*pg.Task, error)
|
||||||
DeleteTask(ctx context.Context, input DeleteTaskInput) (*DeleteTaskPayload, error)
|
DeleteTask(ctx context.Context, input DeleteTaskInput) (*DeleteTaskPayload, error)
|
||||||
AssignTask(ctx context.Context, input *AssignTaskInput) (*pg.Task, error)
|
AssignTask(ctx context.Context, input *AssignTaskInput) (*pg.Task, error)
|
||||||
@ -245,6 +251,7 @@ type QueryResolver interface {
|
|||||||
FindProject(ctx context.Context, input FindProject) (*pg.Project, error)
|
FindProject(ctx context.Context, input FindProject) (*pg.Project, error)
|
||||||
FindTask(ctx context.Context, input FindTask) (*pg.Task, error)
|
FindTask(ctx context.Context, input FindTask) (*pg.Task, error)
|
||||||
Projects(ctx context.Context, input *ProjectsFilter) ([]pg.Project, error)
|
Projects(ctx context.Context, input *ProjectsFilter) ([]pg.Project, error)
|
||||||
|
Teams(ctx context.Context) ([]pg.Team, error)
|
||||||
LabelColors(ctx context.Context) ([]pg.LabelColor, error)
|
LabelColors(ctx context.Context) ([]pg.LabelColor, error)
|
||||||
TaskGroups(ctx context.Context) ([]pg.TaskGroup, error)
|
TaskGroups(ctx context.Context) ([]pg.TaskGroup, error)
|
||||||
Me(ctx context.Context) (*pg.UserAccount, error)
|
Me(ctx context.Context) (*pg.UserAccount, error)
|
||||||
@ -840,6 +847,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.Query.TaskGroups(childComplexity), true
|
return e.complexity.Query.TaskGroups(childComplexity), true
|
||||||
|
|
||||||
|
case "Query.teams":
|
||||||
|
if e.complexity.Query.Teams == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.Query.Teams(childComplexity), true
|
||||||
|
|
||||||
case "Query.users":
|
case "Query.users":
|
||||||
if e.complexity.Query.Users == nil {
|
if e.complexity.Query.Users == nil {
|
||||||
break
|
break
|
||||||
@ -1029,6 +1043,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.ToggleTaskLabelPayload.Task(childComplexity), true
|
return e.complexity.ToggleTaskLabelPayload.Task(childComplexity), true
|
||||||
|
|
||||||
|
case "UpdateTaskLocationPayload.previousTaskGroupID":
|
||||||
|
if e.complexity.UpdateTaskLocationPayload.PreviousTaskGroupID == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.UpdateTaskLocationPayload.PreviousTaskGroupID(childComplexity), true
|
||||||
|
|
||||||
|
case "UpdateTaskLocationPayload.task":
|
||||||
|
if e.complexity.UpdateTaskLocationPayload.Task == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.UpdateTaskLocationPayload.Task(childComplexity), true
|
||||||
|
|
||||||
case "UserAccount.createdAt":
|
case "UserAccount.createdAt":
|
||||||
if e.complexity.UserAccount.CreatedAt == nil {
|
if e.complexity.UserAccount.CreatedAt == nil {
|
||||||
break
|
break
|
||||||
@ -1254,6 +1282,7 @@ type Query {
|
|||||||
findProject(input: FindProject!): Project!
|
findProject(input: FindProject!): Project!
|
||||||
findTask(input: FindTask!): Task!
|
findTask(input: FindTask!): Task!
|
||||||
projects(input: ProjectsFilter): [Project!]!
|
projects(input: ProjectsFilter): [Project!]!
|
||||||
|
teams: [Team!]!
|
||||||
labelColors: [LabelColor!]!
|
labelColors: [LabelColor!]!
|
||||||
taskGroups: [TaskGroup!]!
|
taskGroups: [TaskGroup!]!
|
||||||
me: UserAccount!
|
me: UserAccount!
|
||||||
@ -1297,8 +1326,8 @@ input NewTask {
|
|||||||
position: Float!
|
position: Float!
|
||||||
}
|
}
|
||||||
input NewTaskLocation {
|
input NewTaskLocation {
|
||||||
taskID: String!
|
taskID: UUID!
|
||||||
taskGroupID: String!
|
taskGroupID: UUID!
|
||||||
position: Float!
|
position: Float!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1394,6 +1423,10 @@ input UpdateProjectName {
|
|||||||
name: String!
|
name: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateTaskLocationPayload {
|
||||||
|
previousTaskGroupID: UUID!
|
||||||
|
task: Task!
|
||||||
|
}
|
||||||
type Mutation {
|
type Mutation {
|
||||||
createRefreshToken(input: NewRefreshToken!): RefreshToken!
|
createRefreshToken(input: NewRefreshToken!): RefreshToken!
|
||||||
|
|
||||||
@ -1420,7 +1453,7 @@ type Mutation {
|
|||||||
|
|
||||||
createTask(input: NewTask!): Task!
|
createTask(input: NewTask!): Task!
|
||||||
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
|
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
|
||||||
updateTaskLocation(input: NewTaskLocation!): Task!
|
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
|
||||||
updateTaskName(input: UpdateTaskName!): Task!
|
updateTaskName(input: UpdateTaskName!): Task!
|
||||||
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
||||||
assignTask(input: AssignTaskInput): Task!
|
assignTask(input: AssignTaskInput): Task!
|
||||||
@ -2924,9 +2957,9 @@ func (ec *executionContext) _Mutation_updateTaskLocation(ctx context.Context, fi
|
|||||||
}
|
}
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
res := resTmp.(*pg.Task)
|
res := resTmp.(*UpdateTaskLocationPayload)
|
||||||
fc.Result = res
|
fc.Result = res
|
||||||
return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res)
|
return ec.marshalNUpdateTaskLocationPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateTaskLocationPayload(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Mutation_updateTaskName(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Mutation_updateTaskName(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
@ -3966,6 +3999,40 @@ func (ec *executionContext) _Query_projects(ctx context.Context, field graphql.C
|
|||||||
return ec.marshalNProject2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProjectᚄ(ctx, field.Selections, res)
|
return ec.marshalNProject2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐProjectᚄ(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _Query_teams(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: "Query",
|
||||||
|
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.Query().Teams(rctx)
|
||||||
|
})
|
||||||
|
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.Team)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNTeam2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTeamᚄ(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Query_labelColors(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Query_labelColors(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -5018,6 +5085,74 @@ func (ec *executionContext) _ToggleTaskLabelPayload_task(ctx context.Context, fi
|
|||||||
return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res)
|
return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _UpdateTaskLocationPayload_previousTaskGroupID(ctx context.Context, field graphql.CollectedField, obj *UpdateTaskLocationPayload) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "UpdateTaskLocationPayload",
|
||||||
|
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.PreviousTaskGroupID, 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.(uuid.UUID)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _UpdateTaskLocationPayload_task(ctx context.Context, field graphql.CollectedField, obj *UpdateTaskLocationPayload) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "UpdateTaskLocationPayload",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Task, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*pg.Task)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _UserAccount_id(ctx context.Context, field graphql.CollectedField, obj *pg.UserAccount) (ret graphql.Marshaler) {
|
func (ec *executionContext) _UserAccount_id(ctx context.Context, field graphql.CollectedField, obj *pg.UserAccount) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -6655,13 +6790,13 @@ func (ec *executionContext) unmarshalInputNewTaskLocation(ctx context.Context, o
|
|||||||
switch k {
|
switch k {
|
||||||
case "taskID":
|
case "taskID":
|
||||||
var err error
|
var err error
|
||||||
it.TaskID, err = ec.unmarshalNString2string(ctx, v)
|
it.TaskID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
case "taskGroupID":
|
case "taskGroupID":
|
||||||
var err error
|
var err error
|
||||||
it.TaskGroupID, err = ec.unmarshalNString2string(ctx, v)
|
it.TaskGroupID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
@ -7583,6 +7718,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
|
case "teams":
|
||||||
|
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._Query_teams(ctx, field)
|
||||||
|
if res == graphql.Null {
|
||||||
|
atomic.AddUint32(&invalids, 1)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
})
|
||||||
case "labelColors":
|
case "labelColors":
|
||||||
field := field
|
field := field
|
||||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||||
@ -8007,6 +8156,38 @@ func (ec *executionContext) _ToggleTaskLabelPayload(ctx context.Context, sel ast
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updateTaskLocationPayloadImplementors = []string{"UpdateTaskLocationPayload"}
|
||||||
|
|
||||||
|
func (ec *executionContext) _UpdateTaskLocationPayload(ctx context.Context, sel ast.SelectionSet, obj *UpdateTaskLocationPayload) graphql.Marshaler {
|
||||||
|
fields := graphql.CollectFields(ec.OperationContext, sel, updateTaskLocationPayloadImplementors)
|
||||||
|
|
||||||
|
out := graphql.NewFieldSet(fields)
|
||||||
|
var invalids uint32
|
||||||
|
for i, field := range fields {
|
||||||
|
switch field.Name {
|
||||||
|
case "__typename":
|
||||||
|
out.Values[i] = graphql.MarshalString("UpdateTaskLocationPayload")
|
||||||
|
case "previousTaskGroupID":
|
||||||
|
out.Values[i] = ec._UpdateTaskLocationPayload_previousTaskGroupID(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
case "task":
|
||||||
|
out.Values[i] = ec._UpdateTaskLocationPayload_task(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.Dispatch()
|
||||||
|
if invalids > 0 {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
var userAccountImplementors = []string{"UserAccount"}
|
var userAccountImplementors = []string{"UserAccount"}
|
||||||
|
|
||||||
func (ec *executionContext) _UserAccount(ctx context.Context, sel ast.SelectionSet, obj *pg.UserAccount) graphql.Marshaler {
|
func (ec *executionContext) _UserAccount(ctx context.Context, sel ast.SelectionSet, obj *pg.UserAccount) graphql.Marshaler {
|
||||||
@ -8868,6 +9049,43 @@ func (ec *executionContext) marshalNTeam2githubᚗcomᚋjordanknottᚋprojectᚑ
|
|||||||
return ec._Team(ctx, sel, &v)
|
return ec._Team(ctx, sel, &v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalNTeam2ᚕgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTeamᚄ(ctx context.Context, sel ast.SelectionSet, v []pg.Team) graphql.Marshaler {
|
||||||
|
ret := make(graphql.Array, len(v))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
isLen1 := len(v) == 1
|
||||||
|
if !isLen1 {
|
||||||
|
wg.Add(len(v))
|
||||||
|
}
|
||||||
|
for i := range v {
|
||||||
|
i := i
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Index: &i,
|
||||||
|
Result: &v[i],
|
||||||
|
}
|
||||||
|
ctx := graphql.WithFieldContext(ctx, fc)
|
||||||
|
f := func(i int) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if !isLen1 {
|
||||||
|
defer wg.Done()
|
||||||
|
}
|
||||||
|
ret[i] = ec.marshalNTeam2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTeam(ctx, sel, v[i])
|
||||||
|
}
|
||||||
|
if isLen1 {
|
||||||
|
f(i)
|
||||||
|
} else {
|
||||||
|
go f(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) marshalNTeam2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTeam(ctx context.Context, sel ast.SelectionSet, v *pg.Team) graphql.Marshaler {
|
func (ec *executionContext) marshalNTeam2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTeam(ctx context.Context, sel ast.SelectionSet, v *pg.Team) graphql.Marshaler {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||||
@ -8940,6 +9158,20 @@ func (ec *executionContext) unmarshalNUpdateTaskDescriptionInput2githubᚗcomᚋ
|
|||||||
return ec.unmarshalInputUpdateTaskDescriptionInput(ctx, v)
|
return ec.unmarshalInputUpdateTaskDescriptionInput(ctx, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalNUpdateTaskLocationPayload2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateTaskLocationPayload(ctx context.Context, sel ast.SelectionSet, v UpdateTaskLocationPayload) graphql.Marshaler {
|
||||||
|
return ec._UpdateTaskLocationPayload(ctx, sel, &v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalNUpdateTaskLocationPayload2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateTaskLocationPayload(ctx context.Context, sel ast.SelectionSet, v *UpdateTaskLocationPayload) graphql.Marshaler {
|
||||||
|
if v == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return ec._UpdateTaskLocationPayload(ctx, sel, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalNUpdateTaskName2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateTaskName(ctx context.Context, v interface{}) (UpdateTaskName, error) {
|
func (ec *executionContext) unmarshalNUpdateTaskName2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateTaskName(ctx context.Context, v interface{}) (UpdateTaskName, error) {
|
||||||
return ec.unmarshalInputUpdateTaskName(ctx, v)
|
return ec.unmarshalInputUpdateTaskName(ctx, v)
|
||||||
}
|
}
|
||||||
|
@ -89,9 +89,9 @@ type NewTaskGroupLocation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NewTaskLocation struct {
|
type NewTaskLocation struct {
|
||||||
TaskID string `json:"taskID"`
|
TaskID uuid.UUID `json:"taskID"`
|
||||||
TaskGroupID string `json:"taskGroupID"`
|
TaskGroupID uuid.UUID `json:"taskGroupID"`
|
||||||
Position float64 `json:"position"`
|
Position float64 `json:"position"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewTeam struct {
|
type NewTeam struct {
|
||||||
@ -169,6 +169,11 @@ type UpdateTaskDescriptionInput struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateTaskLocationPayload struct {
|
||||||
|
PreviousTaskGroupID uuid.UUID `json:"previousTaskGroupID"`
|
||||||
|
Task *pg.Task `json:"task"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateTaskName struct {
|
type UpdateTaskName struct {
|
||||||
TaskID string `json:"taskID"`
|
TaskID string `json:"taskID"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -110,6 +110,7 @@ type Query {
|
|||||||
findProject(input: FindProject!): Project!
|
findProject(input: FindProject!): Project!
|
||||||
findTask(input: FindTask!): Task!
|
findTask(input: FindTask!): Task!
|
||||||
projects(input: ProjectsFilter): [Project!]!
|
projects(input: ProjectsFilter): [Project!]!
|
||||||
|
teams: [Team!]!
|
||||||
labelColors: [LabelColor!]!
|
labelColors: [LabelColor!]!
|
||||||
taskGroups: [TaskGroup!]!
|
taskGroups: [TaskGroup!]!
|
||||||
me: UserAccount!
|
me: UserAccount!
|
||||||
@ -153,8 +154,8 @@ input NewTask {
|
|||||||
position: Float!
|
position: Float!
|
||||||
}
|
}
|
||||||
input NewTaskLocation {
|
input NewTaskLocation {
|
||||||
taskID: String!
|
taskID: UUID!
|
||||||
taskGroupID: String!
|
taskGroupID: UUID!
|
||||||
position: Float!
|
position: Float!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,6 +251,10 @@ input UpdateProjectName {
|
|||||||
name: String!
|
name: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateTaskLocationPayload {
|
||||||
|
previousTaskGroupID: UUID!
|
||||||
|
task: Task!
|
||||||
|
}
|
||||||
type Mutation {
|
type Mutation {
|
||||||
createRefreshToken(input: NewRefreshToken!): RefreshToken!
|
createRefreshToken(input: NewRefreshToken!): RefreshToken!
|
||||||
|
|
||||||
@ -276,7 +281,7 @@ type Mutation {
|
|||||||
|
|
||||||
createTask(input: NewTask!): Task!
|
createTask(input: NewTask!): Task!
|
||||||
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
|
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
|
||||||
updateTaskLocation(input: NewTaskLocation!): Task!
|
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
|
||||||
updateTaskName(input: UpdateTaskName!): Task!
|
updateTaskName(input: UpdateTaskName!): Task!
|
||||||
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
||||||
assignTask(input: AssignTaskInput): Task!
|
assignTask(input: AssignTaskInput): Task!
|
||||||
|
@ -215,18 +215,14 @@ func (r *mutationResolver) UpdateTaskDescription(ctx context.Context, input Upda
|
|||||||
return &task, err
|
return &task, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*pg.Task, error) {
|
func (r *mutationResolver) UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*UpdateTaskLocationPayload, error) {
|
||||||
taskID, err := uuid.Parse(input.TaskID)
|
previousTask, err := r.Repository.GetTaskByID(ctx, input.TaskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &pg.Task{}, err
|
return &UpdateTaskLocationPayload{}, err
|
||||||
}
|
}
|
||||||
taskGroupID, err := uuid.Parse(input.TaskGroupID)
|
task, err := r.Repository.UpdateTaskLocation(ctx, pg.UpdateTaskLocationParams{input.TaskID, input.TaskGroupID, input.Position})
|
||||||
if err != nil {
|
|
||||||
return &pg.Task{}, err
|
|
||||||
}
|
|
||||||
task, err := r.Repository.UpdateTaskLocation(ctx, pg.UpdateTaskLocationParams{taskID, taskGroupID, input.Position})
|
|
||||||
|
|
||||||
return &task, err
|
return &UpdateTaskLocationPayload{Task: &task, PreviousTaskGroupID: previousTask.TaskGroupID}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) UpdateTaskName(ctx context.Context, input UpdateTaskName) (*pg.Task, error) {
|
func (r *mutationResolver) UpdateTaskName(ctx context.Context, input UpdateTaskName) (*pg.Task, error) {
|
||||||
@ -405,6 +401,10 @@ func (r *queryResolver) Projects(ctx context.Context, input *ProjectsFilter) ([]
|
|||||||
return r.Repository.GetAllProjects(ctx)
|
return r.Repository.GetAllProjects(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) Teams(ctx context.Context) ([]pg.Team, error) {
|
||||||
|
return r.Repository.GetAllTeams(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) LabelColors(ctx context.Context) ([]pg.LabelColor, error) {
|
func (r *queryResolver) LabelColors(ctx context.Context) ([]pg.LabelColor, error) {
|
||||||
return r.Repository.GetLabelColors(ctx)
|
return r.Repository.GetLabelColors(ctx)
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"@types/react-dom": "^16.9.5",
|
"@types/react-dom": "^16.9.5",
|
||||||
"@types/react-router": "^5.1.4",
|
"@types/react-router": "^5.1.4",
|
||||||
"@types/react-router-dom": "^5.1.3",
|
"@types/react-router-dom": "^5.1.3",
|
||||||
|
"@types/react-select": "^3.0.13",
|
||||||
"@types/styled-components": "^5.0.0",
|
"@types/styled-components": "^5.0.0",
|
||||||
"@welldone-software/why-did-you-render": "^4.2.2",
|
"@welldone-software/why-did-you-render": "^4.2.2",
|
||||||
"apollo-cache-inmemory": "^1.6.5",
|
"apollo-cache-inmemory": "^1.6.5",
|
||||||
@ -57,6 +58,7 @@
|
|||||||
"react-router": "^5.1.2",
|
"react-router": "^5.1.2",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
"react-scripts": "3.4.0",
|
"react-scripts": "3.4.0",
|
||||||
|
"react-select": "^3.1.0",
|
||||||
"styled-components": "^5.0.1",
|
"styled-components": "^5.0.1",
|
||||||
"typescript": "~3.7.2"
|
"typescript": "~3.7.2"
|
||||||
},
|
},
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React, { useState, useContext } from 'react';
|
import React, { useState, useContext } from 'react';
|
||||||
import TopNavbar from 'shared/components/TopNavbar';
|
import TopNavbar from 'shared/components/TopNavbar';
|
||||||
import DropdownMenu from 'shared/components/DropdownMenu';
|
import DropdownMenu from 'shared/components/DropdownMenu';
|
||||||
|
import ProjectSettings from 'shared/components/ProjectSettings';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import UserIDContext from 'App/context';
|
import UserIDContext from 'App/context';
|
||||||
import { useMeQuery } from 'shared/generated/graphql';
|
import { useMeQuery } from 'shared/generated/graphql';
|
||||||
|
import { usePopup, Popup } from 'shared/components/PopupMenu';
|
||||||
|
|
||||||
type GlobalTopNavbarProps = {
|
type GlobalTopNavbarProps = {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
@ -12,6 +14,7 @@ type GlobalTopNavbarProps = {
|
|||||||
};
|
};
|
||||||
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers, onSaveProjectName }) => {
|
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers, onSaveProjectName }) => {
|
||||||
const { loading, data } = useMeQuery();
|
const { loading, data } = useMeQuery();
|
||||||
|
const { showPopup } = usePopup();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { userID, setUserID } = useContext(UserIDContext);
|
const { userID, setUserID } = useContext(UserIDContext);
|
||||||
const [menu, setMenu] = useState({
|
const [menu, setMenu] = useState({
|
||||||
@ -27,6 +30,16 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers,
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onOpenSettings = ($target: React.RefObject<HTMLElement>) => {
|
||||||
|
showPopup(
|
||||||
|
$target,
|
||||||
|
<Popup title={null} tab={0}>
|
||||||
|
<ProjectSettings />
|
||||||
|
</Popup>,
|
||||||
|
185,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const onLogout = () => {
|
const onLogout = () => {
|
||||||
fetch('http://localhost:3333/auth/logout', {
|
fetch('http://localhost:3333/auth/logout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -54,6 +67,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({ name, projectMembers,
|
|||||||
projectMembers={projectMembers}
|
projectMembers={projectMembers}
|
||||||
onProfileClick={onProfileClick}
|
onProfileClick={onProfileClick}
|
||||||
onSaveProjectName={onSaveProjectName}
|
onSaveProjectName={onSaveProjectName}
|
||||||
|
onOpenSettings={onOpenSettings}
|
||||||
/>
|
/>
|
||||||
{menu.isOpen && (
|
{menu.isOpen && (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef, useContext } from 'react';
|
||||||
import GlobalTopNavbar from 'App/TopNavbar';
|
import GlobalTopNavbar from 'App/TopNavbar';
|
||||||
import styled from 'styled-components/macro';
|
import styled from 'styled-components/macro';
|
||||||
import { Bolt, ToggleOn, Tags } from 'shared/icons';
|
import { Bolt, ToggleOn, Tags } from 'shared/icons';
|
||||||
@ -22,6 +22,7 @@ import {
|
|||||||
DeleteTaskDocument,
|
DeleteTaskDocument,
|
||||||
FindProjectDocument,
|
FindProjectDocument,
|
||||||
useCreateProjectLabelMutation,
|
useCreateProjectLabelMutation,
|
||||||
|
useUnassignTaskMutation,
|
||||||
} from 'shared/generated/graphql';
|
} from 'shared/generated/graphql';
|
||||||
|
|
||||||
import TaskAssignee from 'shared/components/TaskAssignee';
|
import TaskAssignee from 'shared/components/TaskAssignee';
|
||||||
@ -38,6 +39,7 @@ import produce from 'immer';
|
|||||||
import MiniProfile from 'shared/components/MiniProfile';
|
import MiniProfile from 'shared/components/MiniProfile';
|
||||||
import Details from './Details';
|
import Details from './Details';
|
||||||
import { useApolloClient } from '@apollo/react-hooks';
|
import { useApolloClient } from '@apollo/react-hooks';
|
||||||
|
import UserIDContext from 'App/context';
|
||||||
|
|
||||||
const getCacheData = (client: any, projectID: string) => {
|
const getCacheData = (client: any, projectID: string) => {
|
||||||
const cacheData: any = client.readQuery({
|
const cacheData: any = client.readQuery({
|
||||||
@ -249,7 +251,31 @@ const Project = () => {
|
|||||||
|
|
||||||
const [updateTaskDescription] = useUpdateTaskDescriptionMutation();
|
const [updateTaskDescription] = useUpdateTaskDescriptionMutation();
|
||||||
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
|
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
|
||||||
const [updateTaskLocation] = useUpdateTaskLocationMutation();
|
const [updateTaskLocation] = useUpdateTaskLocationMutation({
|
||||||
|
update: (client, newTask) => {
|
||||||
|
const cacheData = getCacheData(client, projectID);
|
||||||
|
console.log(cacheData);
|
||||||
|
console.log(newTask);
|
||||||
|
|
||||||
|
const newTaskGroups = produce(cacheData.findProject.taskGroups, (draftState: Array<TaskGroup>) => {
|
||||||
|
const { previousTaskGroupID, task } = newTask.data.updateTaskLocation;
|
||||||
|
if (previousTaskGroupID !== task.taskGroup.id) {
|
||||||
|
const oldTaskGroupIdx = draftState.findIndex((t: TaskGroup) => t.id === previousTaskGroupID);
|
||||||
|
const newTaskGroupIdx = draftState.findIndex((t: TaskGroup) => t.id === task.taskGroup.id);
|
||||||
|
if (oldTaskGroupIdx !== -1 && newTaskGroupIdx !== -1) {
|
||||||
|
draftState[oldTaskGroupIdx].tasks = draftState[oldTaskGroupIdx].tasks.filter((t: Task) => t.id !== task.id);
|
||||||
|
draftState[newTaskGroupIdx].tasks = [...draftState[newTaskGroupIdx].tasks, { ...task }];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const newData = {
|
||||||
|
...cacheData.findProject,
|
||||||
|
taskGroups: newTaskGroups,
|
||||||
|
};
|
||||||
|
writeCacheData(client, projectID, cacheData, newData);
|
||||||
|
},
|
||||||
|
});
|
||||||
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({});
|
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation({});
|
||||||
|
|
||||||
const [deleteTaskGroup] = useDeleteTaskGroupMutation({
|
const [deleteTaskGroup] = useDeleteTaskGroupMutation({
|
||||||
@ -351,23 +377,6 @@ const Project = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCardDrop = (droppedTask: Task) => {
|
|
||||||
updateTaskLocation({
|
|
||||||
variables: {
|
|
||||||
taskID: droppedTask.id,
|
|
||||||
taskGroupID: droppedTask.taskGroup.id,
|
|
||||||
position: droppedTask.position,
|
|
||||||
},
|
|
||||||
optimisticResponse: {
|
|
||||||
updateTaskLocation: {
|
|
||||||
name: droppedTask.name,
|
|
||||||
id: droppedTask.id,
|
|
||||||
position: droppedTask.position,
|
|
||||||
createdAt: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const onListDrop = (droppedColumn: TaskGroup) => {
|
const onListDrop = (droppedColumn: TaskGroup) => {
|
||||||
console.log(`list drop ${droppedColumn.id}`);
|
console.log(`list drop ${droppedColumn.id}`);
|
||||||
const cacheData = getCacheData(client, projectID);
|
const cacheData = getCacheData(client, projectID);
|
||||||
@ -399,10 +408,21 @@ const Project = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [assignTask] = useAssignTaskMutation();
|
const [assignTask] = useAssignTaskMutation();
|
||||||
|
const [unassignTask] = useUnassignTaskMutation();
|
||||||
|
|
||||||
const [updateProjectName] = useUpdateProjectNameMutation();
|
const [updateProjectName] = useUpdateProjectNameMutation({
|
||||||
|
update: (client, newName) => {
|
||||||
|
const cacheData = getCacheData(client, projectID);
|
||||||
|
const newData = {
|
||||||
|
...cacheData.findProject,
|
||||||
|
name: newName.data.updateProjectName.name,
|
||||||
|
};
|
||||||
|
writeCacheData(client, projectID, cacheData, newData);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
const { userID } = useContext(UserIDContext);
|
||||||
|
|
||||||
const { showPopup, hidePopup } = usePopup();
|
const { showPopup, hidePopup } = usePopup();
|
||||||
const $labelsRef = useRef<HTMLDivElement>(null);
|
const $labelsRef = useRef<HTMLDivElement>(null);
|
||||||
@ -482,7 +502,7 @@ const Project = () => {
|
|||||||
onTaskClick={task => {
|
onTaskClick={task => {
|
||||||
history.push(`${match.url}/c/${task.id}`);
|
history.push(`${match.url}/c/${task.id}`);
|
||||||
}}
|
}}
|
||||||
onTaskDrop={droppedTask => {
|
onTaskDrop={(droppedTask, previousTaskGroupID) => {
|
||||||
updateTaskLocation({
|
updateTaskLocation({
|
||||||
variables: {
|
variables: {
|
||||||
taskID: droppedTask.id,
|
taskID: droppedTask.id,
|
||||||
@ -492,11 +512,18 @@ const Project = () => {
|
|||||||
optimisticResponse: {
|
optimisticResponse: {
|
||||||
__typename: 'Mutation',
|
__typename: 'Mutation',
|
||||||
updateTaskLocation: {
|
updateTaskLocation: {
|
||||||
name: droppedTask.name,
|
previousTaskGroupID,
|
||||||
id: droppedTask.id,
|
task: {
|
||||||
position: droppedTask.position,
|
name: droppedTask.name,
|
||||||
createdAt: '',
|
id: droppedTask.id,
|
||||||
__typename: 'Task',
|
position: droppedTask.position,
|
||||||
|
taskGroup: {
|
||||||
|
id: droppedTask.taskGroup.id,
|
||||||
|
__typename: 'TaskGroup',
|
||||||
|
},
|
||||||
|
createdAt: '',
|
||||||
|
__typename: 'Task',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -558,6 +585,42 @@ const Project = () => {
|
|||||||
onEditCard={(_listId: string, cardId: string, cardName: string) => {
|
onEditCard={(_listId: string, cardId: string, cardName: string) => {
|
||||||
updateTaskName({ variables: { taskID: cardId, name: cardName } });
|
updateTaskName({ variables: { taskID: cardId, name: cardName } });
|
||||||
}}
|
}}
|
||||||
|
onOpenMembersPopup={($targetRef, task) => {
|
||||||
|
showPopup(
|
||||||
|
$targetRef,
|
||||||
|
<Popup title="Members" tab={0} onClose={() => {}}>
|
||||||
|
<MemberManager
|
||||||
|
availableMembers={data.findProject.members}
|
||||||
|
activeMembers={task.assigned ?? []}
|
||||||
|
onMemberChange={(member, isActive) => {
|
||||||
|
if (isActive) {
|
||||||
|
assignTask({ variables: { taskID: task.id, userID: userID ?? '' } });
|
||||||
|
} else {
|
||||||
|
unassignTask({ variables: { taskID: task.id, userID: userID ?? '' } });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popup>,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
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={profileIcon}
|
||||||
|
displayName="Jordan Knott"
|
||||||
|
username="@jordanthedev"
|
||||||
|
bio="None"
|
||||||
|
onRemoveFromTask={() => {
|
||||||
|
/* unassignTask({ variables: { taskID: data.findTask.id, userID: userID ?? '' } }); */
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popup>,
|
||||||
|
);
|
||||||
|
}}
|
||||||
onOpenLabelsPopup={($targetRef, task) => {
|
onOpenLabelsPopup={($targetRef, task) => {
|
||||||
taskLabelsRef.current = task.labels;
|
taskLabelsRef.current = task.labels;
|
||||||
showPopup(
|
showPopup(
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useContext } from 'react';
|
||||||
import styled from 'styled-components/macro';
|
import styled from 'styled-components/macro';
|
||||||
import GlobalTopNavbar from 'App/TopNavbar';
|
import GlobalTopNavbar from 'App/TopNavbar';
|
||||||
import { useGetProjectsQuery } from 'shared/generated/graphql';
|
import { useGetProjectsQuery, useCreateProjectMutation, GetProjectsDocument } from 'shared/generated/graphql';
|
||||||
|
|
||||||
import ProjectGridItem from 'shared/components/ProjectGridItem';
|
import ProjectGridItem, { AddProjectItem } from 'shared/components/ProjectGridItem';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Navbar from 'App/Navbar';
|
import Navbar from 'App/Navbar';
|
||||||
|
import NewProject from 'shared/components/NewProject';
|
||||||
|
import UserIDContext from 'App/context';
|
||||||
|
|
||||||
const MainContent = styled.div`
|
const MainContent = styled.div`
|
||||||
padding: 0 0 50px 80px;
|
padding: 0 0 50px 80px;
|
||||||
@ -17,19 +19,37 @@ const ProjectGrid = styled.div`
|
|||||||
width: 60%;
|
width: 60%;
|
||||||
max-width: 780px;
|
max-width: 780px;
|
||||||
margin: 25px auto;
|
margin: 25px auto;
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap;
|
grid-template-columns: 240px 240px 240px;
|
||||||
align-items: center;
|
gap: 20px 10px;
|
||||||
justify-content: center;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ProjectLink = styled(Link)`
|
const ProjectLink = styled(Link)``;
|
||||||
flex: 1 0 33%;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Projects = () => {
|
const Projects = () => {
|
||||||
const { loading, data } = useGetProjectsQuery();
|
const { loading, data } = useGetProjectsQuery();
|
||||||
|
const [createProject] = useCreateProjectMutation({
|
||||||
|
update: (client, newProject) => {
|
||||||
|
const cacheData: any = client.readQuery({
|
||||||
|
query: GetProjectsDocument,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(cacheData);
|
||||||
|
console.log(newProject);
|
||||||
|
|
||||||
|
const newData = {
|
||||||
|
...cacheData,
|
||||||
|
projects: [...cacheData.projects, { ...newProject.data.createProject }],
|
||||||
|
};
|
||||||
|
console.log(newData);
|
||||||
|
client.writeQuery({
|
||||||
|
query: GetProjectsDocument,
|
||||||
|
data: newData,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [showNewProject, setShowNewProject] = useState(false);
|
||||||
|
const { userID, setUserID } = useContext(UserIDContext);
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -38,7 +58,7 @@ const Projects = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (data) {
|
if (data) {
|
||||||
const { projects } = data;
|
const { projects, teams } = data;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GlobalTopNavbar onSaveProjectName={() => {}} name={null} />
|
<GlobalTopNavbar onSaveProjectName={() => {}} name={null} />
|
||||||
@ -50,7 +70,26 @@ const Projects = () => {
|
|||||||
/>
|
/>
|
||||||
</ProjectLink>
|
</ProjectLink>
|
||||||
))}
|
))}
|
||||||
|
<AddProjectItem
|
||||||
|
onAddProject={() => {
|
||||||
|
setShowNewProject(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</ProjectGrid>
|
</ProjectGrid>
|
||||||
|
{showNewProject && (
|
||||||
|
<NewProject
|
||||||
|
onCreateProject={(name, teamID) => {
|
||||||
|
if (userID) {
|
||||||
|
createProject({ variables: { teamID, name, userID } });
|
||||||
|
setShowNewProject(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
setShowNewProject(false);
|
||||||
|
}}
|
||||||
|
teams={teams}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
3
web/src/projects.d.ts
vendored
3
web/src/projects.d.ts
vendored
@ -54,8 +54,9 @@ type Organization = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Team = {
|
type Team = {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
projects: Project[];
|
createdAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProjectLabel = {
|
type ProjectLabel = {
|
||||||
|
@ -128,28 +128,3 @@ export const CardMembers = styled.div`
|
|||||||
margin: 0 -2px 0 0;
|
margin: 0 -2px 0 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CardMember = styled.div<{ bgColor: string; ref: any }>`
|
|
||||||
height: 28px;
|
|
||||||
width: 28px;
|
|
||||||
float: right;
|
|
||||||
margin: 0 0 4px 4px;
|
|
||||||
|
|
||||||
background-color: ${props => props.bgColor};
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 25em;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
overflow: visible;
|
|
||||||
position: relative;
|
|
||||||
text-decoration: none;
|
|
||||||
z-index: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CardMemberInitials = styled.div`
|
|
||||||
height: 28px;
|
|
||||||
width: 28px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 14px;
|
|
||||||
`;
|
|
||||||
|
@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react';
|
|||||||
import { DraggableProvidedDraggableProps } from 'react-beautiful-dnd';
|
import { DraggableProvidedDraggableProps } from 'react-beautiful-dnd';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import Member from 'shared/components/Member';
|
||||||
import { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons';
|
import { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faClock, faCheckSquare, faEye } from '@fortawesome/free-regular-svg-icons';
|
import { faClock, faCheckSquare, faEye } from '@fortawesome/free-regular-svg-icons';
|
||||||
import {
|
import {
|
||||||
@ -19,8 +20,6 @@ import {
|
|||||||
ListCardOperation,
|
ListCardOperation,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardMembers,
|
CardMembers,
|
||||||
CardMember,
|
|
||||||
CardMemberInitials,
|
|
||||||
} from './Styles';
|
} from './Styles';
|
||||||
|
|
||||||
type DueDate = {
|
type DueDate = {
|
||||||
@ -33,31 +32,6 @@ type Checklist = {
|
|||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MemberProps = {
|
|
||||||
onCardMemberClick?: OnCardMemberClick;
|
|
||||||
taskID: string;
|
|
||||||
member: TaskUser;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Member: React.FC<MemberProps> = ({ onCardMemberClick, taskID, member }) => {
|
|
||||||
const $targetRef = useRef<HTMLDivElement>();
|
|
||||||
return (
|
|
||||||
<CardMember
|
|
||||||
ref={$targetRef}
|
|
||||||
onClick={e => {
|
|
||||||
if (onCardMemberClick) {
|
|
||||||
e.stopPropagation();
|
|
||||||
onCardMemberClick($targetRef, taskID, member.id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
key={member.id}
|
|
||||||
bgColor={member.profileIcon.bgColor ?? '#7367F0'}
|
|
||||||
>
|
|
||||||
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
|
|
||||||
</CardMember>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -15,7 +15,7 @@ import { Container, BoardWrapper } from './Styles';
|
|||||||
|
|
||||||
interface SimpleProps {
|
interface SimpleProps {
|
||||||
taskGroups: Array<TaskGroup>;
|
taskGroups: Array<TaskGroup>;
|
||||||
onTaskDrop: (task: Task) => void;
|
onTaskDrop: (task: Task, previousTaskGroupID: string) => void;
|
||||||
onTaskGroupDrop: (taskGroup: TaskGroup) => void;
|
onTaskGroupDrop: (taskGroup: TaskGroup) => void;
|
||||||
|
|
||||||
onTaskClick: (task: Task) => void;
|
onTaskClick: (task: Task) => void;
|
||||||
@ -110,7 +110,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
id: destination.droppableId,
|
id: destination.droppableId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
onTaskDrop(newTask);
|
onTaskDrop(newTask, droppedTask.taskGroup.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
55
web/src/shared/components/Member/index.tsx
Normal file
55
web/src/shared/components/Member/index.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const CardMember = styled.div<{ bgColor: string; ref: any }>`
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
float: right;
|
||||||
|
margin: 0 0 4px 4px;
|
||||||
|
|
||||||
|
background-color: ${props => props.bgColor};
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 25em;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
overflow: visible;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
z-index: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CardMemberInitials = styled.div`
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type MemberProps = {
|
||||||
|
onCardMemberClick?: OnCardMemberClick;
|
||||||
|
taskID: string;
|
||||||
|
member: TaskUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Member: React.FC<MemberProps> = ({ onCardMemberClick, taskID, member }) => {
|
||||||
|
const $targetRef = useRef<HTMLDivElement>();
|
||||||
|
return (
|
||||||
|
<CardMember
|
||||||
|
ref={$targetRef}
|
||||||
|
onClick={e => {
|
||||||
|
if (onCardMemberClick) {
|
||||||
|
e.stopPropagation();
|
||||||
|
onCardMemberClick($targetRef, taskID, member.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
key={member.id}
|
||||||
|
bgColor={member.profileIcon.bgColor ?? '#7367F0'}
|
||||||
|
>
|
||||||
|
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
|
||||||
|
</CardMember>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Member;
|
32
web/src/shared/components/NewProject/NewProject.stories.tsx
Normal file
32
web/src/shared/components/NewProject/NewProject.stories.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React, { useState, useRef, createRef } from 'react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import NormalizeStyles from 'App/NormalizeStyles';
|
||||||
|
import BaseStyles from 'App/BaseStyles';
|
||||||
|
|
||||||
|
import NewProject from '.';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: NewProject,
|
||||||
|
title: 'NewProject',
|
||||||
|
parameters: {
|
||||||
|
backgrounds: [
|
||||||
|
{ name: 'white', value: '#ffffff', default: true },
|
||||||
|
{ name: 'gray', value: '#f8f8f8' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NormalizeStyles />
|
||||||
|
<BaseStyles />
|
||||||
|
<NewProject
|
||||||
|
onCreateProject={action('create project')}
|
||||||
|
teams={[{ name: 'General', id: 'general', createdAt: '' }]}
|
||||||
|
onClose={() => {}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
280
web/src/shared/components/NewProject/index.tsx
Normal file
280
web/src/shared/components/NewProject/index.tsx
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { mixin } from 'shared/utils/styles';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import { ArrowLeft, Cross } from 'shared/icons';
|
||||||
|
|
||||||
|
const Overlay = styled.div`
|
||||||
|
z-index: 10000;
|
||||||
|
background: #262c49;
|
||||||
|
bottom: 0;
|
||||||
|
color: #fff;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
const Header = styled.div`
|
||||||
|
height: 64px;
|
||||||
|
padding: 0 24px;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
justify-content: space-between;
|
||||||
|
transition: box-shadow 250ms;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HeaderLeft = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
const HeaderRight = styled.div`
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
padding: 32px 0;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ContainerContent = styled.div`
|
||||||
|
width: 520px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled.h1`
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #c2c6dc;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ProjectName = styled.input`
|
||||||
|
margin: 0 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #262c49;
|
||||||
|
outline: none;
|
||||||
|
color: #c2c6dc;
|
||||||
|
|
||||||
|
border-radius: 3px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
border-image: initial;
|
||||||
|
border-color: #414561;
|
||||||
|
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: ${mixin.darken('#262c49', 0.15)};
|
||||||
|
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const ProjectNameLabel = styled.label`
|
||||||
|
color: #c2c6dc;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
`;
|
||||||
|
const ProjectInfo = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ProjectField = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 15px;
|
||||||
|
flex-grow: 1;
|
||||||
|
`;
|
||||||
|
const ProjectTeamField = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const colourStyles = {
|
||||||
|
control: (styles: any, data: any) => {
|
||||||
|
return {
|
||||||
|
...styles,
|
||||||
|
backgroundColor: data.isMenuOpen ? mixin.darken('#262c49', 0.15) : '#262c49',
|
||||||
|
boxShadow: data.menuIsOpen ? 'rgb(115, 103, 240) 0px 0px 0px 1px' : 'none',
|
||||||
|
borderRadius: '3px',
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderImage: 'initial',
|
||||||
|
borderColor: '#414561',
|
||||||
|
':hover': {
|
||||||
|
boxShadow: 'rgb(115, 103, 240) 0px 0px 0px 1px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderImage: 'initial',
|
||||||
|
borderColor: '#414561',
|
||||||
|
},
|
||||||
|
':active': {
|
||||||
|
boxShadow: 'rgb(115, 103, 240) 0px 0px 0px 1px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderImage: 'initial',
|
||||||
|
borderColor: 'rgb(115, 103, 240)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
menu: (styles: any) => {
|
||||||
|
return {
|
||||||
|
...styles,
|
||||||
|
backgroundColor: mixin.darken('#262c49', 0.15),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
dropdownIndicator: (styles: any) => ({ ...styles, color: '#c2c6dc', ':hover': { color: '#c2c6dc' } }),
|
||||||
|
indicatorSeparator: (styles: any) => ({ ...styles, color: '#c2c6dc' }),
|
||||||
|
option: (styles: any, { data, isDisabled, isFocused, isSelected }: any) => {
|
||||||
|
return {
|
||||||
|
...styles,
|
||||||
|
backgroundColor: isDisabled
|
||||||
|
? null
|
||||||
|
: isSelected
|
||||||
|
? mixin.darken('#262c49', 0.25)
|
||||||
|
: isFocused
|
||||||
|
? mixin.darken('#262c49', 0.15)
|
||||||
|
: null,
|
||||||
|
color: isDisabled ? '#ccc' : isSelected ? '#fff' : '#c2c6dc',
|
||||||
|
cursor: isDisabled ? 'not-allowed' : 'default',
|
||||||
|
':active': {
|
||||||
|
...styles[':active'],
|
||||||
|
backgroundColor: !isDisabled && (isSelected ? mixin.darken('#262c49', 0.25) : '#fff'),
|
||||||
|
},
|
||||||
|
':hover': {
|
||||||
|
...styles[':hover'],
|
||||||
|
backgroundColor: !isDisabled && (isSelected ? 'rgb(115, 103, 240)' : 'rgb(115, 103, 240)'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
placeholder: (styles: any) => ({ ...styles, color: '#c2c6dc' }),
|
||||||
|
clearIndicator: (styles: any) => ({ ...styles, color: '#c2c6dc', ':hover': { color: '#c2c6dc' } }),
|
||||||
|
input: (styles: any) => ({
|
||||||
|
...styles,
|
||||||
|
color: '#fff',
|
||||||
|
}),
|
||||||
|
singleValue: (styles: any) => {
|
||||||
|
return {
|
||||||
|
...styles,
|
||||||
|
color: '#fff',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const CreateButton = styled.button`
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: none;
|
||||||
|
text-align: center;
|
||||||
|
color: #c2c6dc;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
border-radius: 3px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent;
|
||||||
|
border-image: initial;
|
||||||
|
border-color: #414561;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: rgb(115, 103, 240);
|
||||||
|
border-color: rgb(115, 103, 240);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
type NewProjectProps = {
|
||||||
|
teams: Array<Team>;
|
||||||
|
onClose: () => void;
|
||||||
|
onCreateProject: (projectName: string, teamID: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewProject: React.FC<NewProjectProps> = ({ teams, onClose, onCreateProject }) => {
|
||||||
|
const [projectName, setProjectName] = useState('');
|
||||||
|
const [team, setTeam] = useState<null | string>(null);
|
||||||
|
const options = teams.map(t => ({ label: t.name, value: t.id }));
|
||||||
|
return (
|
||||||
|
<Overlay>
|
||||||
|
<Content>
|
||||||
|
<Header>
|
||||||
|
<HeaderLeft
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowLeft color="#c2c6dc" />
|
||||||
|
</HeaderLeft>
|
||||||
|
<HeaderRight
|
||||||
|
onClick={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Cross color="#c2c6dc" />
|
||||||
|
</HeaderRight>
|
||||||
|
</Header>
|
||||||
|
<Container>
|
||||||
|
<ContainerContent>
|
||||||
|
<Title>Add project details</Title>
|
||||||
|
<ProjectInfo>
|
||||||
|
<ProjectField>
|
||||||
|
<ProjectNameLabel>Project name</ProjectNameLabel>
|
||||||
|
<ProjectName
|
||||||
|
value={projectName}
|
||||||
|
onChange={(e: any) => {
|
||||||
|
setProjectName(e.currentTarget.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ProjectField>
|
||||||
|
<ProjectTeamField>
|
||||||
|
<ProjectNameLabel>Team</ProjectNameLabel>
|
||||||
|
<Select
|
||||||
|
onChange={(e: any) => {
|
||||||
|
setTeam(e.value);
|
||||||
|
}}
|
||||||
|
value={options.filter(d => d.value === team)}
|
||||||
|
styles={colourStyles}
|
||||||
|
classNamePrefix="teamSelect"
|
||||||
|
options={options}
|
||||||
|
/>
|
||||||
|
</ProjectTeamField>
|
||||||
|
</ProjectInfo>
|
||||||
|
<CreateButton
|
||||||
|
onClick={() => {
|
||||||
|
if (team && projectName !== '') {
|
||||||
|
onCreateProject(projectName, team);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create project
|
||||||
|
</CreateButton>
|
||||||
|
</ContainerContent>
|
||||||
|
</Container>
|
||||||
|
</Content>
|
||||||
|
</Overlay>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewProject;
|
@ -1,12 +1,12 @@
|
|||||||
import styled, { css } from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
import { mixin } from 'shared/utils/styles';
|
import { mixin } from 'shared/utils/styles';
|
||||||
|
|
||||||
export const Container = styled.div<{ invert: boolean; top: number; left: number; ref: any }>`
|
export const Container = styled.div<{ invert: boolean; top: number; left: number; ref: any; width: number | string }>`
|
||||||
left: ${props => props.left}px;
|
left: ${props => props.left}px;
|
||||||
top: ${props => props.top}px;
|
top: ${props => props.top}px;
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 316px;
|
width: ${props => props.width}px;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
height: auto;
|
height: auto;
|
||||||
z-index: 40000;
|
z-index: 40000;
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
} from './Styles';
|
} from './Styles';
|
||||||
|
|
||||||
type PopupContextState = {
|
type PopupContextState = {
|
||||||
show: (target: RefObject<HTMLElement>, content: JSX.Element) => void;
|
show: (target: RefObject<HTMLElement>, content: JSX.Element, width?: string | number) => void;
|
||||||
setTab: (newTab: number) => void;
|
setTab: (newTab: number) => void;
|
||||||
getCurrentTab: () => number;
|
getCurrentTab: () => number;
|
||||||
hide: () => void;
|
hide: () => void;
|
||||||
@ -23,7 +23,7 @@ type PopupContextState = {
|
|||||||
|
|
||||||
type PopupProps = {
|
type PopupProps = {
|
||||||
title: string | null;
|
title: string | null;
|
||||||
onClose: () => void;
|
onClose?: () => void;
|
||||||
tab: number;
|
tab: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,17 +32,23 @@ type PopupContainerProps = {
|
|||||||
left: number;
|
left: number;
|
||||||
invert: boolean;
|
invert: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
width?: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PopupContainer: React.FC<PopupContainerProps> = ({ top, left, onClose, children, invert }) => {
|
const PopupContainer: React.FC<PopupContainerProps> = ({ width, top, left, onClose, children, invert }) => {
|
||||||
const $containerRef = useRef();
|
const $containerRef = useRef();
|
||||||
useOnOutsideClick($containerRef, true, onClose, null);
|
useOnOutsideClick($containerRef, true, onClose, null);
|
||||||
return (
|
return (
|
||||||
<Container left={left} top={top} ref={$containerRef} invert={invert}>
|
<Container width={width ?? 316} left={left} top={top} ref={$containerRef} invert={invert}>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PopupContainer.defaultProps = {
|
||||||
|
width: 316,
|
||||||
|
};
|
||||||
|
|
||||||
const PopupContext = createContext<PopupContextState>({
|
const PopupContext = createContext<PopupContextState>({
|
||||||
show: () => {},
|
show: () => {},
|
||||||
setTab: () => {},
|
setTab: () => {},
|
||||||
@ -63,6 +69,7 @@ type PopupState = {
|
|||||||
currentTab: number;
|
currentTab: number;
|
||||||
previousTab: number;
|
previousTab: number;
|
||||||
content: JSX.Element | null;
|
content: JSX.Element | null;
|
||||||
|
width?: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { Provider, Consumer } = PopupContext;
|
const { Provider, Consumer } = PopupContext;
|
||||||
@ -81,7 +88,7 @@ const defaultState = {
|
|||||||
|
|
||||||
export const PopupProvider: React.FC = ({ children }) => {
|
export const PopupProvider: React.FC = ({ children }) => {
|
||||||
const [currentState, setState] = useState<PopupState>(defaultState);
|
const [currentState, setState] = useState<PopupState>(defaultState);
|
||||||
const show = (target: RefObject<HTMLElement>, content: JSX.Element) => {
|
const show = (target: RefObject<HTMLElement>, content: JSX.Element, width?: number | string) => {
|
||||||
if (target && target.current) {
|
if (target && target.current) {
|
||||||
const bounds = target.current.getBoundingClientRect();
|
const bounds = target.current.getBoundingClientRect();
|
||||||
if (bounds.left + 304 + 30 > window.innerWidth) {
|
if (bounds.left + 304 + 30 > window.innerWidth) {
|
||||||
@ -93,6 +100,7 @@ export const PopupProvider: React.FC = ({ children }) => {
|
|||||||
currentTab: 0,
|
currentTab: 0,
|
||||||
previousTab: 0,
|
previousTab: 0,
|
||||||
content,
|
content,
|
||||||
|
width: width ?? 316,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState({
|
setState({
|
||||||
@ -103,6 +111,7 @@ export const PopupProvider: React.FC = ({ children }) => {
|
|||||||
currentTab: 0,
|
currentTab: 0,
|
||||||
previousTab: 0,
|
previousTab: 0,
|
||||||
content,
|
content,
|
||||||
|
width: width ?? 316,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,6 +153,7 @@ export const PopupProvider: React.FC = ({ children }) => {
|
|||||||
top={currentState.top}
|
top={currentState.top}
|
||||||
left={currentState.left}
|
left={currentState.left}
|
||||||
onClose={() => setState(defaultState)}
|
onClose={() => setState(defaultState)}
|
||||||
|
width={currentState.width ?? 316}
|
||||||
>
|
>
|
||||||
{currentState.content}
|
{currentState.content}
|
||||||
<ContainerDiamond invert={currentState.invert} />
|
<ContainerDiamond invert={currentState.invert} />
|
||||||
@ -162,14 +172,15 @@ type Props = {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onPrevious?: () => void | null;
|
onPrevious?: () => void | null;
|
||||||
noHeader?: boolean | null;
|
noHeader?: boolean | null;
|
||||||
|
width?: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PopupMenu: React.FC<Props> = ({ title, top, left, onClose, noHeader, children, onPrevious }) => {
|
const PopupMenu: React.FC<Props> = ({ width, title, top, left, onClose, noHeader, children, onPrevious }) => {
|
||||||
const $containerRef = useRef();
|
const $containerRef = useRef();
|
||||||
useOnOutsideClick($containerRef, true, onClose, null);
|
useOnOutsideClick($containerRef, true, onClose, null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container invert={false} left={left} top={top} ref={$containerRef}>
|
<Container width={width ?? 316} invert={false} left={left} top={top} ref={$containerRef}>
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{onPrevious && (
|
{onPrevious && (
|
||||||
<PreviousButton onClick={onPrevious}>
|
<PreviousButton onClick={onPrevious}>
|
||||||
@ -217,9 +228,11 @@ export const Popup: React.FC<PopupProps> = ({ title, onClose, tab, children }) =
|
|||||||
<HeaderTitle>{title}</HeaderTitle>
|
<HeaderTitle>{title}</HeaderTitle>
|
||||||
</Header>
|
</Header>
|
||||||
)}
|
)}
|
||||||
<CloseButton onClick={() => onClose()}>
|
{onClose && (
|
||||||
<Cross color="#c2c6dc" />
|
<CloseButton onClick={() => onClose()}>
|
||||||
</CloseButton>
|
<Cross color="#c2c6dc" />
|
||||||
|
</CloseButton>
|
||||||
|
)}
|
||||||
<Content>{children}</Content>
|
<Content>{children}</Content>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</>
|
</>
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { mixin } from 'shared/utils/styles';
|
import { mixin } from 'shared/utils/styles';
|
||||||
|
|
||||||
|
export const AddProjectLabel = styled.span`
|
||||||
|
padding-top: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #c2c6dc;
|
||||||
|
`;
|
||||||
|
|
||||||
export const ProjectContent = styled.div`
|
export const ProjectContent = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -20,8 +26,7 @@ export const TeamTitle = styled.span`
|
|||||||
|
|
||||||
export const ProjectWrapper = styled.div<{ color: string }>`
|
export const ProjectWrapper = styled.div<{ color: string }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 15px 25px;
|
padding: 15px 25px; border-radius: 20px;
|
||||||
border-radius: 20px;
|
|
||||||
${mixin.boxShadowCard}
|
${mixin.boxShadowCard}
|
||||||
background: ${props => mixin.darken(props.color, 0.35)};
|
background: ${props => mixin.darken(props.color, 0.35)};
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@ -31,8 +36,30 @@ export const ProjectWrapper = styled.div<{ color: string }>`
|
|||||||
height: 100px;
|
height: 100px;
|
||||||
transition: transform 0.25s ease;
|
transition: transform 0.25s ease;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const AddProjectWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
padding: 15px 25px;
|
||||||
|
border-radius: 20px;
|
||||||
|
${mixin.boxShadowCard}
|
||||||
|
border: 1px dashed;
|
||||||
|
border-color: #c2c6dc;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 10px;
|
||||||
|
width: 240px;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100px;
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -1,7 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { ProjectWrapper, ProjectContent, ProjectTitle, TeamTitle } from './Styles';
|
import { Plus } from 'shared/icons';
|
||||||
|
import { AddProjectWrapper, AddProjectLabel, ProjectWrapper, ProjectContent, ProjectTitle, TeamTitle } from './Styles';
|
||||||
|
|
||||||
|
type AddProjectItemProps = {
|
||||||
|
onAddProject: () => void;
|
||||||
|
};
|
||||||
|
export const AddProjectItem: React.FC<AddProjectItemProps> = ({ onAddProject }) => {
|
||||||
|
return (
|
||||||
|
<AddProjectWrapper
|
||||||
|
onClick={() => {
|
||||||
|
onAddProject();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus size={20} color="#c2c6dc" />
|
||||||
|
<AddProjectLabel>New Project</AddProjectLabel>
|
||||||
|
</AddProjectWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
type Props = {
|
type Props = {
|
||||||
project: Project;
|
project: Project;
|
||||||
};
|
};
|
||||||
|
51
web/src/shared/components/ProjectSettings/index.tsx
Normal file
51
web/src/shared/components/ProjectSettings/index.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const ListActionsWrapper = styled.ul`
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ListActionItemWrapper = styled.li`
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
`;
|
||||||
|
export const ListActionItem = styled.span`
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #c2c6dc;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 6px 12px;
|
||||||
|
position: relative;
|
||||||
|
margin: 0 -12px;
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
background: rgb(115, 103, 240);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ListSeparator = styled.hr`
|
||||||
|
background-color: #414561;
|
||||||
|
border: 0;
|
||||||
|
height: 1px;
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
const ProjectSettings: React.FC<Props> = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ListActionsWrapper>
|
||||||
|
<ListActionItemWrapper onClick={() => {}}>
|
||||||
|
<ListActionItem>Delete Project</ListActionItem>
|
||||||
|
</ListActionItemWrapper>
|
||||||
|
</ListActionsWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ProjectSettings;
|
@ -56,6 +56,7 @@ export const Default = () => {
|
|||||||
onCloseEditor={() => setEditorOpen(false)}
|
onCloseEditor={() => setEditorOpen(false)}
|
||||||
onEditCard={action('edit card')}
|
onEditCard={action('edit card')}
|
||||||
onOpenLabelsPopup={action('open popup')}
|
onOpenLabelsPopup={action('open popup')}
|
||||||
|
onOpenMembersPopup={action('open popup')}
|
||||||
onArchiveCard={action('archive card')}
|
onArchiveCard={action('archive card')}
|
||||||
top={top}
|
top={top}
|
||||||
left={left}
|
left={left}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React, { useRef, useState, useEffect } from 'react';
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
import Cross from 'shared/icons/Cross';
|
import Cross from 'shared/icons/Cross';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import Member from 'shared/components/Member';
|
||||||
import {
|
import {
|
||||||
Wrapper,
|
Wrapper,
|
||||||
Container,
|
Container,
|
||||||
@ -14,20 +16,39 @@ import {
|
|||||||
ListCardLabel,
|
ListCardLabel,
|
||||||
} from './Styles';
|
} from './Styles';
|
||||||
|
|
||||||
|
export const CardMembers = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
task: Task;
|
task: Task;
|
||||||
onCloseEditor: () => void;
|
onCloseEditor: () => void;
|
||||||
onEditCard: (taskGroupID: string, taskID: string, cardName: string) => void;
|
onEditCard: (taskGroupID: string, taskID: string, cardName: string) => void;
|
||||||
onOpenLabelsPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
onOpenLabelsPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
||||||
|
onOpenMembersPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
||||||
onArchiveCard: (taskGroupID: string, taskID: string) => void;
|
onArchiveCard: (taskGroupID: string, taskID: string) => void;
|
||||||
|
onCardMemberClick?: OnCardMemberClick;
|
||||||
top: number;
|
top: number;
|
||||||
left: number;
|
left: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const QuickCardEditor = ({ task, onCloseEditor, onOpenLabelsPopup, onArchiveCard, onEditCard, top, left }: Props) => {
|
const QuickCardEditor = ({
|
||||||
|
task,
|
||||||
|
onCloseEditor,
|
||||||
|
onOpenLabelsPopup,
|
||||||
|
onOpenMembersPopup,
|
||||||
|
onCardMemberClick,
|
||||||
|
onArchiveCard,
|
||||||
|
onEditCard,
|
||||||
|
top,
|
||||||
|
left,
|
||||||
|
}: Props) => {
|
||||||
const [currentCardTitle, setCardTitle] = useState(task.name);
|
const [currentCardTitle, setCardTitle] = useState(task.name);
|
||||||
const $editorRef: any = useRef();
|
const $editorRef: any = useRef();
|
||||||
const $labelsRef: any = useRef();
|
const $labelsRef: any = useRef();
|
||||||
|
const $membersRef: any = useRef();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
$editorRef.current.focus();
|
$editorRef.current.focus();
|
||||||
$editorRef.current.select();
|
$editorRef.current.select();
|
||||||
@ -71,10 +92,25 @@ const QuickCardEditor = ({ task, onCloseEditor, onOpenLabelsPopup, onArchiveCard
|
|||||||
value={currentCardTitle}
|
value={currentCardTitle}
|
||||||
ref={$editorRef}
|
ref={$editorRef}
|
||||||
/>
|
/>
|
||||||
|
<CardMembers>
|
||||||
|
{task.assigned &&
|
||||||
|
task.assigned.map(member => (
|
||||||
|
<Member key={member.id} taskID={task.id} member={member} onCardMemberClick={onCardMemberClick} />
|
||||||
|
))}
|
||||||
|
</CardMembers>
|
||||||
</EditorDetails>
|
</EditorDetails>
|
||||||
</Editor>
|
</Editor>
|
||||||
<SaveButton onClick={e => onEditCard(task.taskGroup.id, task.id, currentCardTitle)}>Save</SaveButton>
|
<SaveButton onClick={e => onEditCard(task.taskGroup.id, task.id, currentCardTitle)}>Save</SaveButton>
|
||||||
<EditorButtons>
|
<EditorButtons>
|
||||||
|
<EditorButton
|
||||||
|
ref={$membersRef}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onOpenMembersPopup($membersRef, task);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit Assigned
|
||||||
|
</EditorButton>
|
||||||
<EditorButton
|
<EditorButton
|
||||||
ref={$labelsRef}
|
ref={$labelsRef}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
|
@ -44,6 +44,7 @@ export const Default = () => {
|
|||||||
lastName="Knott"
|
lastName="Knott"
|
||||||
initials="JK"
|
initials="JK"
|
||||||
onNotificationClick={action('notifications click')}
|
onNotificationClick={action('notifications click')}
|
||||||
|
onOpenSettings={action('open settings')}
|
||||||
onProfileClick={onClick}
|
onProfileClick={onClick}
|
||||||
/>
|
/>
|
||||||
{menu.isOpen && (
|
{menu.isOpen && (
|
||||||
|
@ -32,9 +32,14 @@ import MiniProfile from 'shared/components/MiniProfile';
|
|||||||
type ProjectHeadingProps = {
|
type ProjectHeadingProps = {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
onSaveProjectName?: (projectName: string) => void;
|
onSaveProjectName?: (projectName: string) => void;
|
||||||
|
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectHeading: React.FC<ProjectHeadingProps> = ({ projectName: initialProjectName, onSaveProjectName }) => {
|
const ProjectHeading: React.FC<ProjectHeadingProps> = ({
|
||||||
|
projectName: initialProjectName,
|
||||||
|
onSaveProjectName,
|
||||||
|
onOpenSettings,
|
||||||
|
}) => {
|
||||||
const [isEditProjectName, setEditProjectName] = useState(false);
|
const [isEditProjectName, setEditProjectName] = useState(false);
|
||||||
const [projectName, setProjectName] = useState(initialProjectName);
|
const [projectName, setProjectName] = useState(initialProjectName);
|
||||||
const $projectName = useRef<HTMLTextAreaElement>(null);
|
const $projectName = useRef<HTMLTextAreaElement>(null);
|
||||||
@ -66,6 +71,7 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({ projectName: initialPro
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $settings = useRef<HTMLButtonElement>(null);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Separator>»</Separator>
|
<Separator>»</Separator>
|
||||||
@ -87,7 +93,12 @@ const ProjectHeading: React.FC<ProjectHeadingProps> = ({ projectName: initialPro
|
|||||||
{projectName}
|
{projectName}
|
||||||
</ProjectName>
|
</ProjectName>
|
||||||
)}
|
)}
|
||||||
<ProjectSettingsButton>
|
<ProjectSettingsButton
|
||||||
|
onClick={() => {
|
||||||
|
onOpenSettings($settings);
|
||||||
|
}}
|
||||||
|
ref={$settings}
|
||||||
|
>
|
||||||
<AngleDown color="#c2c6dc" />
|
<AngleDown color="#c2c6dc" />
|
||||||
</ProjectSettingsButton>
|
</ProjectSettingsButton>
|
||||||
<ProjectSettingsButton>
|
<ProjectSettingsButton>
|
||||||
@ -103,6 +114,7 @@ type NavBarProps = {
|
|||||||
onSaveProjectName?: (projectName: string) => void;
|
onSaveProjectName?: (projectName: string) => void;
|
||||||
onNotificationClick: () => void;
|
onNotificationClick: () => void;
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
|
onOpenSettings: ($target: React.RefObject<HTMLElement>) => void;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
initials: string;
|
initials: string;
|
||||||
@ -119,6 +131,7 @@ const NavBar: React.FC<NavBarProps> = ({
|
|||||||
initials,
|
initials,
|
||||||
bgColor,
|
bgColor,
|
||||||
projectMembers,
|
projectMembers,
|
||||||
|
onOpenSettings,
|
||||||
}) => {
|
}) => {
|
||||||
const $profileRef: any = useRef(null);
|
const $profileRef: any = useRef(null);
|
||||||
const handleProfileClick = () => {
|
const handleProfileClick = () => {
|
||||||
@ -147,7 +160,13 @@ const NavBar: React.FC<NavBarProps> = ({
|
|||||||
<ProjectActions>
|
<ProjectActions>
|
||||||
<ProjectMeta>
|
<ProjectMeta>
|
||||||
<ProjectSwitcher>Projects</ProjectSwitcher>
|
<ProjectSwitcher>Projects</ProjectSwitcher>
|
||||||
{projectName && <ProjectHeading projectName={projectName} onSaveProjectName={onSaveProjectName} />}
|
{projectName && (
|
||||||
|
<ProjectHeading
|
||||||
|
onOpenSettings={onOpenSettings}
|
||||||
|
projectName={projectName}
|
||||||
|
onSaveProjectName={onSaveProjectName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</ProjectMeta>
|
</ProjectMeta>
|
||||||
{projectName && (
|
{projectName && (
|
||||||
<ProjectTabs>
|
<ProjectTabs>
|
||||||
|
@ -136,6 +136,7 @@ export type Query = {
|
|||||||
findProject: Project;
|
findProject: Project;
|
||||||
findTask: Task;
|
findTask: Task;
|
||||||
projects: Array<Project>;
|
projects: Array<Project>;
|
||||||
|
teams: Array<Team>;
|
||||||
labelColors: Array<LabelColor>;
|
labelColors: Array<LabelColor>;
|
||||||
taskGroups: Array<TaskGroup>;
|
taskGroups: Array<TaskGroup>;
|
||||||
me: UserAccount;
|
me: UserAccount;
|
||||||
@ -201,8 +202,8 @@ export type NewTask = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type NewTaskLocation = {
|
export type NewTaskLocation = {
|
||||||
taskID: Scalars['String'];
|
taskID: Scalars['UUID'];
|
||||||
taskGroupID: Scalars['String'];
|
taskGroupID: Scalars['UUID'];
|
||||||
position: Scalars['Float'];
|
position: Scalars['Float'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -302,6 +303,12 @@ export type UpdateProjectName = {
|
|||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UpdateTaskLocationPayload = {
|
||||||
|
__typename?: 'UpdateTaskLocationPayload';
|
||||||
|
previousTaskGroupID: Scalars['UUID'];
|
||||||
|
task: Task;
|
||||||
|
};
|
||||||
|
|
||||||
export type Mutation = {
|
export type Mutation = {
|
||||||
__typename?: 'Mutation';
|
__typename?: 'Mutation';
|
||||||
createRefreshToken: RefreshToken;
|
createRefreshToken: RefreshToken;
|
||||||
@ -322,7 +329,7 @@ export type Mutation = {
|
|||||||
toggleTaskLabel: ToggleTaskLabelPayload;
|
toggleTaskLabel: ToggleTaskLabelPayload;
|
||||||
createTask: Task;
|
createTask: Task;
|
||||||
updateTaskDescription: Task;
|
updateTaskDescription: Task;
|
||||||
updateTaskLocation: Task;
|
updateTaskLocation: UpdateTaskLocationPayload;
|
||||||
updateTaskName: Task;
|
updateTaskName: Task;
|
||||||
deleteTask: DeleteTaskPayload;
|
deleteTask: DeleteTaskPayload;
|
||||||
assignTask: Task;
|
assignTask: Task;
|
||||||
@ -468,6 +475,25 @@ export type AssignTaskMutation = (
|
|||||||
) }
|
) }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type CreateProjectMutationVariables = {
|
||||||
|
teamID: Scalars['UUID'];
|
||||||
|
userID: Scalars['UUID'];
|
||||||
|
name: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type CreateProjectMutation = (
|
||||||
|
{ __typename?: 'Mutation' }
|
||||||
|
& { createProject: (
|
||||||
|
{ __typename?: 'Project' }
|
||||||
|
& Pick<Project, 'id' | 'name'>
|
||||||
|
& { team: (
|
||||||
|
{ __typename?: 'Team' }
|
||||||
|
& Pick<Team, 'id' | 'name'>
|
||||||
|
) }
|
||||||
|
) }
|
||||||
|
);
|
||||||
|
|
||||||
export type CreateProjectLabelMutationVariables = {
|
export type CreateProjectLabelMutationVariables = {
|
||||||
projectID: Scalars['UUID'];
|
projectID: Scalars['UUID'];
|
||||||
labelColorID: Scalars['UUID'];
|
labelColorID: Scalars['UUID'];
|
||||||
@ -686,7 +712,10 @@ export type GetProjectsQueryVariables = {};
|
|||||||
|
|
||||||
export type GetProjectsQuery = (
|
export type GetProjectsQuery = (
|
||||||
{ __typename?: 'Query' }
|
{ __typename?: 'Query' }
|
||||||
& { projects: Array<(
|
& { teams: Array<(
|
||||||
|
{ __typename?: 'Team' }
|
||||||
|
& Pick<Team, 'id' | 'name' | 'createdAt'>
|
||||||
|
)>, projects: Array<(
|
||||||
{ __typename?: 'Project' }
|
{ __typename?: 'Project' }
|
||||||
& Pick<Project, 'id' | 'name'>
|
& Pick<Project, 'id' | 'name'>
|
||||||
& { team: (
|
& { team: (
|
||||||
@ -821,8 +850,8 @@ export type UpdateTaskGroupLocationMutation = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export type UpdateTaskLocationMutationVariables = {
|
export type UpdateTaskLocationMutationVariables = {
|
||||||
taskID: Scalars['String'];
|
taskID: Scalars['UUID'];
|
||||||
taskGroupID: Scalars['String'];
|
taskGroupID: Scalars['UUID'];
|
||||||
position: Scalars['Float'];
|
position: Scalars['Float'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -830,8 +859,16 @@ export type UpdateTaskLocationMutationVariables = {
|
|||||||
export type UpdateTaskLocationMutation = (
|
export type UpdateTaskLocationMutation = (
|
||||||
{ __typename?: 'Mutation' }
|
{ __typename?: 'Mutation' }
|
||||||
& { updateTaskLocation: (
|
& { updateTaskLocation: (
|
||||||
{ __typename?: 'Task' }
|
{ __typename?: 'UpdateTaskLocationPayload' }
|
||||||
& Pick<Task, 'id' | 'createdAt' | 'name' | 'position'>
|
& Pick<UpdateTaskLocationPayload, 'previousTaskGroupID'>
|
||||||
|
& { task: (
|
||||||
|
{ __typename?: 'Task' }
|
||||||
|
& Pick<Task, 'id' | 'createdAt' | 'name' | 'position'>
|
||||||
|
& { taskGroup: (
|
||||||
|
{ __typename?: 'TaskGroup' }
|
||||||
|
& Pick<TaskGroup, 'id'>
|
||||||
|
) }
|
||||||
|
) }
|
||||||
) }
|
) }
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -888,6 +925,45 @@ export function useAssignTaskMutation(baseOptions?: ApolloReactHooks.MutationHoo
|
|||||||
export type AssignTaskMutationHookResult = ReturnType<typeof useAssignTaskMutation>;
|
export type AssignTaskMutationHookResult = ReturnType<typeof useAssignTaskMutation>;
|
||||||
export type AssignTaskMutationResult = ApolloReactCommon.MutationResult<AssignTaskMutation>;
|
export type AssignTaskMutationResult = ApolloReactCommon.MutationResult<AssignTaskMutation>;
|
||||||
export type AssignTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<AssignTaskMutation, AssignTaskMutationVariables>;
|
export type AssignTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<AssignTaskMutation, AssignTaskMutationVariables>;
|
||||||
|
export const CreateProjectDocument = gql`
|
||||||
|
mutation createProject($teamID: UUID!, $userID: UUID!, $name: String!) {
|
||||||
|
createProject(input: {teamID: $teamID, userID: $userID, name: $name}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
team {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type CreateProjectMutationFn = ApolloReactCommon.MutationFunction<CreateProjectMutation, CreateProjectMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useCreateProjectMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useCreateProjectMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useCreateProjectMutation` 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 [createProjectMutation, { data, loading, error }] = useCreateProjectMutation({
|
||||||
|
* variables: {
|
||||||
|
* teamID: // value for 'teamID'
|
||||||
|
* userID: // value for 'userID'
|
||||||
|
* name: // value for 'name'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useCreateProjectMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateProjectMutation, CreateProjectMutationVariables>) {
|
||||||
|
return ApolloReactHooks.useMutation<CreateProjectMutation, CreateProjectMutationVariables>(CreateProjectDocument, baseOptions);
|
||||||
|
}
|
||||||
|
export type CreateProjectMutationHookResult = ReturnType<typeof useCreateProjectMutation>;
|
||||||
|
export type CreateProjectMutationResult = ApolloReactCommon.MutationResult<CreateProjectMutation>;
|
||||||
|
export type CreateProjectMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateProjectMutation, CreateProjectMutationVariables>;
|
||||||
export const CreateProjectLabelDocument = gql`
|
export const CreateProjectLabelDocument = gql`
|
||||||
mutation createProjectLabel($projectID: UUID!, $labelColorID: UUID!, $name: String!) {
|
mutation createProjectLabel($projectID: UUID!, $labelColorID: UUID!, $name: String!) {
|
||||||
createProjectLabel(input: {projectID: $projectID, labelColorID: $labelColorID, name: $name}) {
|
createProjectLabel(input: {projectID: $projectID, labelColorID: $labelColorID, name: $name}) {
|
||||||
@ -1304,6 +1380,11 @@ export type FindTaskLazyQueryHookResult = ReturnType<typeof useFindTaskLazyQuery
|
|||||||
export type FindTaskQueryResult = ApolloReactCommon.QueryResult<FindTaskQuery, FindTaskQueryVariables>;
|
export type FindTaskQueryResult = ApolloReactCommon.QueryResult<FindTaskQuery, FindTaskQueryVariables>;
|
||||||
export const GetProjectsDocument = gql`
|
export const GetProjectsDocument = gql`
|
||||||
query getProjects {
|
query getProjects {
|
||||||
|
teams {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
projects {
|
projects {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -1609,12 +1690,18 @@ export type UpdateTaskGroupLocationMutationHookResult = ReturnType<typeof useUpd
|
|||||||
export type UpdateTaskGroupLocationMutationResult = ApolloReactCommon.MutationResult<UpdateTaskGroupLocationMutation>;
|
export type UpdateTaskGroupLocationMutationResult = ApolloReactCommon.MutationResult<UpdateTaskGroupLocationMutation>;
|
||||||
export type UpdateTaskGroupLocationMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskGroupLocationMutation, UpdateTaskGroupLocationMutationVariables>;
|
export type UpdateTaskGroupLocationMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskGroupLocationMutation, UpdateTaskGroupLocationMutationVariables>;
|
||||||
export const UpdateTaskLocationDocument = gql`
|
export const UpdateTaskLocationDocument = gql`
|
||||||
mutation updateTaskLocation($taskID: String!, $taskGroupID: String!, $position: Float!) {
|
mutation updateTaskLocation($taskID: UUID!, $taskGroupID: UUID!, $position: Float!) {
|
||||||
updateTaskLocation(input: {taskID: $taskID, taskGroupID: $taskGroupID, position: $position}) {
|
updateTaskLocation(input: {taskID: $taskID, taskGroupID: $taskGroupID, position: $position}) {
|
||||||
id
|
previousTaskGroupID
|
||||||
createdAt
|
task {
|
||||||
name
|
id
|
||||||
position
|
createdAt
|
||||||
|
name
|
||||||
|
position
|
||||||
|
taskGroup {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
10
web/src/shared/graphql/createProject.graphqls
Normal file
10
web/src/shared/graphql/createProject.graphqls
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
mutation createProject($teamID: UUID!, $userID: UUID!, $name: String!) {
|
||||||
|
createProject(input: {teamID: $teamID, userID: $userID, name: $name}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
team {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,9 @@
|
|||||||
query getProjects {
|
query getProjects {
|
||||||
|
teams {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
projects {
|
projects {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
mutation updateTaskLocation($taskID: String!, $taskGroupID: String!, $position: Float!) {
|
mutation updateTaskLocation($taskID: UUID!, $taskGroupID: UUID!, $position: Float!) {
|
||||||
updateTaskLocation(input: { taskID: $taskID, taskGroupID: $taskGroupID, position: $position }) {
|
updateTaskLocation(input: { taskID: $taskID, taskGroupID: $taskGroupID, position: $position }) {
|
||||||
id
|
previousTaskGroupID
|
||||||
createdAt
|
task {
|
||||||
name
|
id
|
||||||
position
|
createdAt
|
||||||
|
name
|
||||||
|
position
|
||||||
|
taskGroup {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
web/src/shared/icons/ArrowLeft.tsx
Normal file
27
web/src/shared/icons/ArrowLeft.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
width: number | string;
|
||||||
|
height: number | string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArrowLeft = ({ width, height, color }: Props) => {
|
||||||
|
return (
|
||||||
|
<svg width={width} height={height} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||||
|
<path
|
||||||
|
fill={color}
|
||||||
|
d="M257.5 445.1l-22.2 22.2c-9.4 9.4-24.6 9.4-33.9 0L7 273c-9.4-9.4-9.4-24.6 0-33.9L201.4 44.7c9.4-9.4 24.6-9.4 33.9 0l22.2 22.2c9.5 9.5 9.3 25-.4 34.3L136.6 216H424c13.3 0 24 10.7 24 24v32c0 13.3-10.7 24-24 24H136.6l120.5 114.8c9.8 9.3 10 24.8.4 34.3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ArrowLeft.defaultProps = {
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
color: '#000',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ArrowLeft;
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
import Cross from './Cross';
|
import Cross from './Cross';
|
||||||
import Cog from './Cog';
|
import Cog from './Cog';
|
||||||
|
import ArrowLeft from './ArrowLeft';
|
||||||
import Bolt from './Bolt';
|
import Bolt from './Bolt';
|
||||||
import Plus from './Plus';
|
import Plus from './Plus';
|
||||||
import Bell from './Bell';
|
import Bell from './Bell';
|
||||||
@ -43,5 +44,6 @@ export {
|
|||||||
User,
|
User,
|
||||||
Users,
|
Users,
|
||||||
Lock,
|
Lock,
|
||||||
|
ArrowLeft,
|
||||||
ToggleOn,
|
ToggleOn,
|
||||||
};
|
};
|
||||||
|
@ -3224,6 +3224,15 @@
|
|||||||
"@types/history" "*"
|
"@types/history" "*"
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-select@^3.0.13":
|
||||||
|
version "3.0.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.13.tgz#b1a05eae0f65fb4f899b4db1f89b8420cb9f3656"
|
||||||
|
integrity sha512-JxmSArGgzAOtb37+Jz2+3av8rVmp/3s3DGwlcP+g59/a3owkiuuU4/Jajd+qA32beDPHy4gJR2kkxagPY3j9kg==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
"@types/react-dom" "*"
|
||||||
|
"@types/react-transition-group" "*"
|
||||||
|
|
||||||
"@types/react-syntax-highlighter@11.0.2":
|
"@types/react-syntax-highlighter@11.0.2":
|
||||||
version "11.0.2"
|
version "11.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.2.tgz#a2e3ff657d7c47813f80ca930f3d959c31ec51e3"
|
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.2.tgz#a2e3ff657d7c47813f80ca930f3d959c31ec51e3"
|
||||||
@ -3245,6 +3254,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-transition-group@*":
|
||||||
|
version "4.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
|
||||||
|
integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^16.9.21":
|
"@types/react@*", "@types/react@^16.9.21":
|
||||||
version "16.9.21"
|
version "16.9.21"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.21.tgz#99e274e2ecfab6bb93920e918341daa3198b348d"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.21.tgz#99e274e2ecfab6bb93920e918341daa3198b348d"
|
||||||
@ -13677,7 +13693,7 @@ react-scripts@3.4.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "2.1.2"
|
fsevents "2.1.2"
|
||||||
|
|
||||||
react-select@^3.0.8:
|
react-select@^3.0.8, react-select@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27"
|
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27"
|
||||||
integrity sha512-wBFVblBH1iuCBprtpyGtd1dGMadsG36W5/t2Aj8OE6WbByDg5jIFyT7X5gT+l0qmT5TqWhxX+VsKJvCEl2uL9g==
|
integrity sha512-wBFVblBH1iuCBprtpyGtd1dGMadsG36W5/t2Aj8OE6WbByDg5jIFyT7X5gT+l0qmT5TqWhxX+VsKJvCEl2uL9g==
|
||||||
|
Loading…
Reference in New Issue
Block a user