feature: UI changes

This commit is contained in:
Jordan Knott 2020-04-20 18:04:27 -05:00
parent c38024e692
commit 7e78ee36b4
45 changed files with 1569 additions and 137 deletions

File diff suppressed because it is too large Load Diff

View File

@ -57,6 +57,12 @@ type NewProject struct {
Name string `json:"name"` Name string `json:"name"`
} }
type NewProjectLabel struct {
ProjectID uuid.UUID `json:"projectID"`
LabelColorID uuid.UUID `json:"labelColorID"`
Name *string `json:"name"`
}
type NewRefreshToken struct { type NewRefreshToken struct {
UserID string `json:"userId"` UserID string `json:"userId"`
} }
@ -100,6 +106,7 @@ type NewUserAccount struct {
type ProfileIcon struct { type ProfileIcon struct {
URL *string `json:"url"` URL *string `json:"url"`
Initials *string `json:"initials"` Initials *string `json:"initials"`
BgColor *string `json:"bgColor"`
} }
type ProjectMember struct { type ProjectMember struct {
@ -118,6 +125,11 @@ type RemoveTaskLabelInput struct {
TaskLabelID uuid.UUID `json:"taskLabelID"` TaskLabelID uuid.UUID `json:"taskLabelID"`
} }
type UnassignTaskInput struct {
TaskID uuid.UUID `json:"taskID"`
UserID uuid.UUID `json:"userID"`
}
type UpdateTaskDescriptionInput struct { type UpdateTaskDescriptionInput struct {
TaskID uuid.UUID `json:"taskID"` TaskID uuid.UUID `json:"taskID"`
Description string `json:"description"` Description string `json:"description"`

View File

@ -1,15 +1,25 @@
scalar Time scalar Time
scalar UUID scalar UUID
type ProjectLabel {
projectLabelID: ID!
createdDate: Time!
colorHex: String!
name: String
}
type TaskLabel { type TaskLabel {
taskLabelID: ID! taskLabelID: ID!
labelColorID: UUID! projectLabelID: UUID!
assignedDate: Time!
colorHex: String! colorHex: String!
name: String
} }
type ProfileIcon { type ProfileIcon {
url: String url: String
initials: String initials: String
bgColor: String
} }
type ProjectMember { type ProjectMember {
@ -50,6 +60,7 @@ type Project {
owner: ProjectMember! owner: ProjectMember!
taskGroups: [TaskGroup!]! taskGroups: [TaskGroup!]!
members: [ProjectMember!]! members: [ProjectMember!]!
labels: [ProjectLabel!]!
} }
type TaskGroup { type TaskGroup {
@ -174,6 +185,10 @@ input AssignTaskInput {
userID: UUID! userID: UUID!
} }
input UnassignTaskInput {
taskID: UUID!
userID: UUID!
}
input UpdateTaskDescriptionInput { input UpdateTaskDescriptionInput {
taskID: UUID! taskID: UUID!
description: String! description: String!
@ -189,6 +204,12 @@ input RemoveTaskLabelInput {
taskLabelID: UUID! taskLabelID: UUID!
} }
input NewProjectLabel {
projectID: UUID!
labelColorID: UUID!
name: String
}
type Mutation { type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken! createRefreshToken(input: NewRefreshToken!): RefreshToken!
@ -197,6 +218,7 @@ type Mutation {
createTeam(input: NewTeam!): Team! createTeam(input: NewTeam!): Team!
createProject(input: NewProject!): Project! createProject(input: NewProject!): Project!
createProjectLabel(input: NewProjectLabel!): ProjectLabel!
createTaskGroup(input: NewTaskGroup!): TaskGroup! createTaskGroup(input: NewTaskGroup!): TaskGroup!
updateTaskGroupLocation(input: NewTaskGroupLocation!): TaskGroup! updateTaskGroupLocation(input: NewTaskGroupLocation!): TaskGroup!
@ -211,6 +233,7 @@ type Mutation {
updateTaskName(input: UpdateTaskName!): Task! updateTaskName(input: UpdateTaskName!): Task!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload! deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
assignTask(input: AssignTaskInput): Task! assignTask(input: AssignTaskInput): Task!
unassignTask(input: UnassignTaskInput): Task!
logoutUser(input: LogoutUser!): Boolean! logoutUser(input: LogoutUser!): Boolean!
} }

View File

@ -45,6 +45,10 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
return &project, err return &project, err
} }
func (r *mutationResolver) CreateProjectLabel(ctx context.Context, input NewProjectLabel) (*pg.ProjectLabel, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) CreateTaskGroup(ctx context.Context, input NewTaskGroup) (*pg.TaskGroup, error) { func (r *mutationResolver) CreateTaskGroup(ctx context.Context, input NewTaskGroup) (*pg.TaskGroup, error) {
createdAt := time.Now().UTC() createdAt := time.Now().UTC()
projectID, err := uuid.Parse(input.ProjectID) projectID, err := uuid.Parse(input.ProjectID)
@ -164,6 +168,18 @@ func (r *mutationResolver) AssignTask(ctx context.Context, input *AssignTaskInpu
return &task, err return &task, err
} }
func (r *mutationResolver) UnassignTask(ctx context.Context, input *UnassignTaskInput) (*pg.Task, error) {
task, err := r.Repository.GetTaskByID(ctx, input.TaskID)
if err != nil {
return &pg.Task{}, err
}
_, err = r.Repository.DeleteTaskAssignedByID(ctx, pg.DeleteTaskAssignedByIDParams{input.TaskID, input.UserID})
if err != nil {
return &pg.Task{}, err
}
return &task, nil
}
func (r *mutationResolver) LogoutUser(ctx context.Context, input LogoutUser) (bool, error) { func (r *mutationResolver) LogoutUser(ctx context.Context, input LogoutUser) (bool, error) {
userID, err := uuid.Parse(input.UserID) userID, err := uuid.Parse(input.UserID)
if err != nil { if err != nil {
@ -185,7 +201,7 @@ func (r *projectResolver) Owner(ctx context.Context, obj *pg.Project) (*ProjectM
return &ProjectMember{}, err return &ProjectMember{}, err
} }
initials := string([]rune(user.FirstName)[0]) + string([]rune(user.LastName)[0]) initials := string([]rune(user.FirstName)[0]) + string([]rune(user.LastName)[0])
profileIcon := &ProfileIcon{nil, &initials} profileIcon := &ProfileIcon{nil, &initials, &user.ProfileBgColor}
return &ProjectMember{obj.Owner, user.FirstName, user.LastName, profileIcon}, nil return &ProjectMember{obj.Owner, user.FirstName, user.LastName, profileIcon}, nil
} }
@ -200,11 +216,28 @@ func (r *projectResolver) Members(ctx context.Context, obj *pg.Project) ([]Proje
return members, err return members, err
} }
initials := string([]rune(user.FirstName)[0]) + string([]rune(user.LastName)[0]) initials := string([]rune(user.FirstName)[0]) + string([]rune(user.LastName)[0])
profileIcon := &ProfileIcon{nil, &initials} profileIcon := &ProfileIcon{nil, &initials, &user.ProfileBgColor}
members = append(members, ProjectMember{obj.Owner, user.FirstName, user.LastName, profileIcon}) members = append(members, ProjectMember{obj.Owner, user.FirstName, user.LastName, profileIcon})
return members, nil return members, nil
} }
func (r *projectResolver) Labels(ctx context.Context, obj *pg.Project) ([]pg.ProjectLabel, error) {
labels, err := r.Repository.GetProjectLabelsForProject(ctx, obj.ProjectID)
return labels, err
}
func (r *projectLabelResolver) ColorHex(ctx context.Context, obj *pg.ProjectLabel) (string, error) {
labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID)
if err != nil {
return "", err
}
return labelColor.ColorHex, nil
}
func (r *projectLabelResolver) Name(ctx context.Context, obj *pg.ProjectLabel) (*string, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *queryResolver) Users(ctx context.Context) ([]pg.UserAccount, error) { func (r *queryResolver) Users(ctx context.Context) ([]pg.UserAccount, error) {
return r.Repository.GetAllUserAccounts(ctx) return r.Repository.GetAllUserAccounts(ctx)
} }
@ -306,7 +339,7 @@ func (r *taskResolver) Assigned(ctx context.Context, obj *pg.Task) ([]ProjectMem
return taskMembers, err return taskMembers, err
} }
initials := string([]rune(user.FirstName)[0]) + string([]rune(user.LastName)[0]) initials := string([]rune(user.FirstName)[0]) + string([]rune(user.LastName)[0])
profileIcon := &ProfileIcon{nil, &initials} profileIcon := &ProfileIcon{nil, &initials, &user.ProfileBgColor}
taskMembers = append(taskMembers, ProjectMember{taskMemberLink.UserID, user.FirstName, user.LastName, profileIcon}) taskMembers = append(taskMembers, ProjectMember{taskMemberLink.UserID, user.FirstName, user.LastName, profileIcon})
} }
return taskMembers, nil return taskMembers, nil
@ -326,16 +359,32 @@ func (r *taskGroupResolver) Tasks(ctx context.Context, obj *pg.TaskGroup) ([]pg.
} }
func (r *taskLabelResolver) ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error) { func (r *taskLabelResolver) ColorHex(ctx context.Context, obj *pg.TaskLabel) (string, error) {
labelColor, err := r.Repository.GetLabelColorByID(ctx, obj.LabelColorID) projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID)
if err != nil {
return "", err
}
labelColor, err := r.Repository.GetLabelColorByID(ctx, projectLabel.LabelColorID)
if err != nil { if err != nil {
return "", err return "", err
} }
return labelColor.ColorHex, nil return labelColor.ColorHex, nil
} }
func (r *taskLabelResolver) Name(ctx context.Context, obj *pg.TaskLabel) (*string, error) {
projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID)
if err != nil {
return nil, err
}
name := projectLabel.Name
if !name.Valid {
return nil, err
}
return &name.String, err
}
func (r *userAccountResolver) ProfileIcon(ctx context.Context, obj *pg.UserAccount) (*ProfileIcon, error) { func (r *userAccountResolver) ProfileIcon(ctx context.Context, obj *pg.UserAccount) (*ProfileIcon, error) {
initials := string([]rune(obj.FirstName)[0]) + string([]rune(obj.LastName)[0]) initials := string([]rune(obj.FirstName)[0]) + string([]rune(obj.LastName)[0])
profileIcon := &ProfileIcon{nil, &initials} profileIcon := &ProfileIcon{nil, &initials, &obj.ProfileBgColor}
return profileIcon, nil return profileIcon, nil
} }
@ -345,6 +394,9 @@ func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
// Project returns ProjectResolver implementation. // Project returns ProjectResolver implementation.
func (r *Resolver) Project() ProjectResolver { return &projectResolver{r} } func (r *Resolver) Project() ProjectResolver { return &projectResolver{r} }
// ProjectLabel returns ProjectLabelResolver implementation.
func (r *Resolver) ProjectLabel() ProjectLabelResolver { return &projectLabelResolver{r} }
// Query returns QueryResolver implementation. // Query returns QueryResolver implementation.
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
@ -362,6 +414,7 @@ func (r *Resolver) UserAccount() UserAccountResolver { return &userAccountResolv
type mutationResolver struct{ *Resolver } type mutationResolver struct{ *Resolver }
type projectResolver struct{ *Resolver } type projectResolver struct{ *Resolver }
type projectLabelResolver struct{ *Resolver }
type queryResolver struct{ *Resolver } type queryResolver struct{ *Resolver }
type taskResolver struct{ *Resolver } type taskResolver struct{ *Resolver }
type taskGroupResolver struct{ *Resolver } type taskGroupResolver struct{ *Resolver }
@ -374,6 +427,9 @@ type userAccountResolver struct{ *Resolver }
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete // - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done. // it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean. // - You have helper methods in this file. Move them out to keep these resolver files clean.
func (r *taskLabelResolver) ProjectLabelID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUID, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *userAccountResolver) DisplayName(ctx context.Context, obj *pg.UserAccount) (string, error) { func (r *userAccountResolver) DisplayName(ctx context.Context, obj *pg.UserAccount) (string, error) {
return obj.FirstName + " " + obj.LastName, nil return obj.FirstName + " " + obj.LastName, nil
} }

View File

@ -0,0 +1,7 @@
CREATE TABLE project_label (
project_label_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
project_id uuid NOT NULL REFERENCES project(project_id),
label_color_id uuid NOT NULL REFERENCES label_color(label_color_id),
created_date timestamptz NOT NULL,
name text
);

View File

@ -1,6 +1,6 @@
CREATE TABLE task_label ( CREATE TABLE task_label (
task_label_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), task_label_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
task_id uuid NOT NULL REFERENCES task(task_id), task_id uuid NOT NULL REFERENCES task(task_id),
label_color_id uuid NOT NULL REFERENCES label_color(label_color_id), project_label_id uuid NOT NULL REFERENCES project_label(project_label_id),
assigned_date timestamptz NOT NULL assigned_date timestamptz NOT NULL
); );

View File

@ -0,0 +1 @@
ALTER TABLE user_account ADD COLUMN profile_bg_color text NOT NULL DEFAULT '#7367F0';

View File

@ -29,6 +29,14 @@ type Project struct {
Owner uuid.UUID `json:"owner"` Owner uuid.UUID `json:"owner"`
} }
type ProjectLabel struct {
ProjectLabelID uuid.UUID `json:"project_label_id"`
ProjectID uuid.UUID `json:"project_id"`
LabelColorID uuid.UUID `json:"label_color_id"`
CreatedDate time.Time `json:"created_date"`
Name sql.NullString `json:"name"`
}
type RefreshToken struct { type RefreshToken struct {
TokenID uuid.UUID `json:"token_id"` TokenID uuid.UUID `json:"token_id"`
UserID uuid.UUID `json:"user_id"` UserID uuid.UUID `json:"user_id"`
@ -62,10 +70,10 @@ type TaskGroup struct {
} }
type TaskLabel struct { type TaskLabel struct {
TaskLabelID uuid.UUID `json:"task_label_id"` TaskLabelID uuid.UUID `json:"task_label_id"`
TaskID uuid.UUID `json:"task_id"` TaskID uuid.UUID `json:"task_id"`
LabelColorID uuid.UUID `json:"label_color_id"` ProjectLabelID uuid.UUID `json:"project_label_id"`
AssignedDate time.Time `json:"assigned_date"` AssignedDate time.Time `json:"assigned_date"`
} }
type Team struct { type Team struct {
@ -76,11 +84,12 @@ type Team struct {
} }
type UserAccount struct { type UserAccount struct {
UserID uuid.UUID `json:"user_id"` UserID uuid.UUID `json:"user_id"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
LastName string `json:"last_name"` LastName string `json:"last_name"`
Email string `json:"email"` Email string `json:"email"`
Username string `json:"username"` Username string `json:"username"`
PasswordHash string `json:"password_hash"` PasswordHash string `json:"password_hash"`
ProfileBgColor string `json:"profile_bg_color"`
} }

View File

@ -22,6 +22,10 @@ type Repository interface {
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
GetAllUserAccounts(ctx context.Context) ([]UserAccount, error) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error)
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error)
CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error) CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error) GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
@ -55,6 +59,7 @@ type Repository interface {
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error) CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error) GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error)
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
} }
type repoSvc struct { type repoSvc struct {

View File

@ -0,0 +1,92 @@
// Code generated by sqlc. DO NOT EDIT.
// source: project_label.sql
package pg
import (
"context"
"database/sql"
"time"
"github.com/google/uuid"
)
const createProjectLabel = `-- name: CreateProjectLabel :one
INSERT INTO project_label (project_id, label_color_id, created_date, name)
VALUES ($1, $2, $3, $4) RETURNING project_label_id, project_id, label_color_id, created_date, name
`
type CreateProjectLabelParams struct {
ProjectID uuid.UUID `json:"project_id"`
LabelColorID uuid.UUID `json:"label_color_id"`
CreatedDate time.Time `json:"created_date"`
Name sql.NullString `json:"name"`
}
func (q *Queries) CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error) {
row := q.db.QueryRowContext(ctx, createProjectLabel,
arg.ProjectID,
arg.LabelColorID,
arg.CreatedDate,
arg.Name,
)
var i ProjectLabel
err := row.Scan(
&i.ProjectLabelID,
&i.ProjectID,
&i.LabelColorID,
&i.CreatedDate,
&i.Name,
)
return i, err
}
const getProjectLabelByID = `-- name: GetProjectLabelByID :one
SELECT project_label_id, project_id, label_color_id, created_date, name FROM project_label WHERE project_label_id = $1
`
func (q *Queries) GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error) {
row := q.db.QueryRowContext(ctx, getProjectLabelByID, projectLabelID)
var i ProjectLabel
err := row.Scan(
&i.ProjectLabelID,
&i.ProjectID,
&i.LabelColorID,
&i.CreatedDate,
&i.Name,
)
return i, err
}
const getProjectLabelsForProject = `-- name: GetProjectLabelsForProject :many
SELECT project_label_id, project_id, label_color_id, created_date, name FROM project_label WHERE project_id = $1
`
func (q *Queries) GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error) {
rows, err := q.db.QueryContext(ctx, getProjectLabelsForProject, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ProjectLabel
for rows.Next() {
var i ProjectLabel
if err := rows.Scan(
&i.ProjectLabelID,
&i.ProjectID,
&i.LabelColorID,
&i.CreatedDate,
&i.Name,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@ -11,6 +11,7 @@ import (
type Querier interface { type Querier interface {
CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, error) CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, error)
CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error)
CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error)
CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error) CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error) CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
@ -21,6 +22,7 @@ type Querier interface {
DeleteExpiredTokens(ctx context.Context) error DeleteExpiredTokens(ctx context.Context) error
DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error) DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error) DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
@ -35,6 +37,8 @@ type Querier interface {
GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error) GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error)
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error) GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, error)
GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error) GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error)
GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error)
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error) GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error) GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error)
GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error) GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error)

View File

@ -33,6 +33,27 @@ func (q *Queries) CreateTaskAssigned(ctx context.Context, arg CreateTaskAssigned
return i, err return i, err
} }
const deleteTaskAssignedByID = `-- name: DeleteTaskAssignedByID :one
DELETE FROM task_assigned WHERE task_id = $1 AND user_id = $2 RETURNING task_assigned_id, task_id, user_id, assigned_date
`
type DeleteTaskAssignedByIDParams struct {
TaskID uuid.UUID `json:"task_id"`
UserID uuid.UUID `json:"user_id"`
}
func (q *Queries) DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error) {
row := q.db.QueryRowContext(ctx, deleteTaskAssignedByID, arg.TaskID, arg.UserID)
var i TaskAssigned
err := row.Scan(
&i.TaskAssignedID,
&i.TaskID,
&i.UserID,
&i.AssignedDate,
)
return i, err
}
const getAssignedMembersForTask = `-- name: GetAssignedMembersForTask :many const getAssignedMembersForTask = `-- name: GetAssignedMembersForTask :many
SELECT task_assigned_id, task_id, user_id, assigned_date FROM task_assigned WHERE task_id = $1 SELECT task_assigned_id, task_id, user_id, assigned_date FROM task_assigned WHERE task_id = $1
` `

View File

@ -11,30 +11,30 @@ import (
) )
const createTaskLabelForTask = `-- name: CreateTaskLabelForTask :one const createTaskLabelForTask = `-- name: CreateTaskLabelForTask :one
INSERT INTO task_label (task_id, label_color_id, assigned_date) INSERT INTO task_label (task_id, project_label_id, assigned_date)
VALUES ($1, $2, $3) RETURNING task_label_id, task_id, label_color_id, assigned_date VALUES ($1, $2, $3) RETURNING task_label_id, task_id, project_label_id, assigned_date
` `
type CreateTaskLabelForTaskParams struct { type CreateTaskLabelForTaskParams struct {
TaskID uuid.UUID `json:"task_id"` TaskID uuid.UUID `json:"task_id"`
LabelColorID uuid.UUID `json:"label_color_id"` ProjectLabelID uuid.UUID `json:"project_label_id"`
AssignedDate time.Time `json:"assigned_date"` AssignedDate time.Time `json:"assigned_date"`
} }
func (q *Queries) CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabelForTaskParams) (TaskLabel, error) { func (q *Queries) CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabelForTaskParams) (TaskLabel, error) {
row := q.db.QueryRowContext(ctx, createTaskLabelForTask, arg.TaskID, arg.LabelColorID, arg.AssignedDate) row := q.db.QueryRowContext(ctx, createTaskLabelForTask, arg.TaskID, arg.ProjectLabelID, arg.AssignedDate)
var i TaskLabel var i TaskLabel
err := row.Scan( err := row.Scan(
&i.TaskLabelID, &i.TaskLabelID,
&i.TaskID, &i.TaskID,
&i.LabelColorID, &i.ProjectLabelID,
&i.AssignedDate, &i.AssignedDate,
) )
return i, err return i, err
} }
const getTaskLabelsForTaskID = `-- name: GetTaskLabelsForTaskID :many const getTaskLabelsForTaskID = `-- name: GetTaskLabelsForTaskID :many
SELECT task_label_id, task_id, label_color_id, assigned_date FROM task_label WHERE task_id = $1 SELECT task_label_id, task_id, project_label_id, assigned_date FROM task_label WHERE task_id = $1
` `
func (q *Queries) GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskLabel, error) { func (q *Queries) GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskLabel, error) {
@ -49,7 +49,7 @@ func (q *Queries) GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID)
if err := rows.Scan( if err := rows.Scan(
&i.TaskLabelID, &i.TaskLabelID,
&i.TaskID, &i.TaskID,
&i.LabelColorID, &i.ProjectLabelID,
&i.AssignedDate, &i.AssignedDate,
); err != nil { ); err != nil {
return nil, err return nil, err

View File

@ -13,7 +13,7 @@ import (
const createUserAccount = `-- name: CreateUserAccount :one const createUserAccount = `-- name: CreateUserAccount :one
INSERT INTO user_account(first_name, last_name, email, username, created_at, password_hash) INSERT INTO user_account(first_name, last_name, email, username, created_at, password_hash)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6)
RETURNING user_id, created_at, first_name, last_name, email, username, password_hash RETURNING user_id, created_at, first_name, last_name, email, username, password_hash, profile_bg_color
` `
type CreateUserAccountParams struct { type CreateUserAccountParams struct {
@ -43,12 +43,13 @@ func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountPa
&i.Email, &i.Email,
&i.Username, &i.Username,
&i.PasswordHash, &i.PasswordHash,
&i.ProfileBgColor,
) )
return i, err return i, err
} }
const getAllUserAccounts = `-- name: GetAllUserAccounts :many const getAllUserAccounts = `-- name: GetAllUserAccounts :many
SELECT user_id, created_at, first_name, last_name, email, username, password_hash FROM user_account SELECT user_id, created_at, first_name, last_name, email, username, password_hash, profile_bg_color FROM user_account
` `
func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error) { func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error) {
@ -68,6 +69,7 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
&i.Email, &i.Email,
&i.Username, &i.Username,
&i.PasswordHash, &i.PasswordHash,
&i.ProfileBgColor,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -83,7 +85,7 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
} }
const getUserAccountByID = `-- name: GetUserAccountByID :one const getUserAccountByID = `-- name: GetUserAccountByID :one
SELECT user_id, created_at, first_name, last_name, email, username, password_hash FROM user_account WHERE user_id = $1 SELECT user_id, created_at, first_name, last_name, email, username, password_hash, profile_bg_color FROM user_account WHERE user_id = $1
` `
func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error) { func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error) {
@ -97,12 +99,13 @@ func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (Use
&i.Email, &i.Email,
&i.Username, &i.Username,
&i.PasswordHash, &i.PasswordHash,
&i.ProfileBgColor,
) )
return i, err return i, err
} }
const getUserAccountByUsername = `-- name: GetUserAccountByUsername :one const getUserAccountByUsername = `-- name: GetUserAccountByUsername :one
SELECT user_id, created_at, first_name, last_name, email, username, password_hash FROM user_account WHERE username = $1 SELECT user_id, created_at, first_name, last_name, email, username, password_hash, profile_bg_color FROM user_account WHERE username = $1
` `
func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) { func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error) {
@ -116,6 +119,7 @@ func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string)
&i.Email, &i.Email,
&i.Username, &i.Username,
&i.PasswordHash, &i.PasswordHash,
&i.ProfileBgColor,
) )
return i, err return i, err
} }

View File

@ -0,0 +1,9 @@
-- name: CreateProjectLabel :one
INSERT INTO project_label (project_id, label_color_id, created_date, name)
VALUES ($1, $2, $3, $4) RETURNING *;
-- name: GetProjectLabelsForProject :many
SELECT * FROM project_label WHERE project_id = $1;
-- name: GetProjectLabelByID :one
SELECT * FROM project_label WHERE project_label_id = $1;

View File

@ -4,3 +4,6 @@ INSERT INTO task_assigned (task_id, user_id, assigned_date)
-- name: GetAssignedMembersForTask :many -- name: GetAssignedMembersForTask :many
SELECT * FROM task_assigned WHERE task_id = $1; SELECT * FROM task_assigned WHERE task_id = $1;
-- name: DeleteTaskAssignedByID :one
DELETE FROM task_assigned WHERE task_id = $1 AND user_id = $2 RETURNING *;

View File

@ -1,5 +1,5 @@
-- name: CreateTaskLabelForTask :one -- name: CreateTaskLabelForTask :one
INSERT INTO task_label (task_id, label_color_id, assigned_date) INSERT INTO task_label (task_id, project_label_id, assigned_date)
VALUES ($1, $2, $3) RETURNING *; VALUES ($1, $2, $3) RETURNING *;
-- name: GetTaskLabelsForTaskID :many -- name: GetTaskLabelsForTaskID :many

View File

@ -41,13 +41,27 @@ const GlobalTopNavbar: React.FC = () => {
return ( return (
<> <>
<TopNavbar <TopNavbar
bgColor={data ? data.me.profileIcon.bgColor ?? '#7367F0' : '#7367F0'}
firstName={data ? data.me.firstName : ''} firstName={data ? data.me.firstName : ''}
lastName={data ? data.me.lastName : ''} lastName={data ? data.me.lastName : ''}
initials={!data ? '' : data.me.profileIcon.initials ?? ''} initials={!data ? '' : data.me.profileIcon.initials ?? ''}
onNotificationClick={() => console.log('beep')} onNotificationClick={() => console.log('beep')}
onProfileClick={onProfileClick} onProfileClick={onProfileClick}
/> />
{menu.isOpen && <DropdownMenu onLogout={onLogout} left={menu.left} top={menu.top} />} {menu.isOpen && (
<DropdownMenu
onCloseDropdown={() => {
setMenu({
top: 0,
left: 0,
isOpen: false,
});
}}
onLogout={onLogout}
left={menu.left}
top={menu.top}
/>
)}
</> </>
); );
}; };

View File

@ -4,7 +4,7 @@ import TaskDetails from 'shared/components/TaskDetails';
import PopupMenu from 'shared/components/PopupMenu'; import PopupMenu from 'shared/components/PopupMenu';
import MemberManager from 'shared/components/MemberManager'; import MemberManager from 'shared/components/MemberManager';
import { useRouteMatch, useHistory } from 'react-router'; import { useRouteMatch, useHistory } from 'react-router';
import { useFindTaskQuery, useAssignTaskMutation } from 'shared/generated/graphql'; import { useFindTaskQuery, useAssignTaskMutation, useUnassignTaskMutation } from 'shared/generated/graphql';
import UserIDContext from 'App/context'; import UserIDContext from 'App/context';
type DetailsProps = { type DetailsProps = {
@ -15,6 +15,7 @@ type DetailsProps = {
onDeleteTask: (task: Task) => void; onDeleteTask: (task: Task) => void;
onOpenAddLabelPopup: (task: Task, bounds: ElementBounds) => void; onOpenAddLabelPopup: (task: Task, bounds: ElementBounds) => void;
availableMembers: Array<TaskUser>; availableMembers: Array<TaskUser>;
refreshCache: () => void;
}; };
const initialMemberPopupState = { taskID: '', isOpen: false, top: 0, left: 0 }; const initialMemberPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
@ -27,13 +28,25 @@ const Details: React.FC<DetailsProps> = ({
onDeleteTask, onDeleteTask,
onOpenAddLabelPopup, onOpenAddLabelPopup,
availableMembers, availableMembers,
refreshCache,
}) => { }) => {
const { userID } = useContext(UserIDContext); const { userID } = useContext(UserIDContext);
const history = useHistory(); const history = useHistory();
const match = useRouteMatch(); const match = useRouteMatch();
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState); const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const { loading, data } = useFindTaskQuery({ variables: { taskID } }); const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
const [assignTask] = useAssignTaskMutation(); const [assignTask] = useAssignTaskMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
const [unassignTask] = useUnassignTaskMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
if (loading) { if (loading) {
return <div>loading</div>; return <div>loading</div>;
} }
@ -47,6 +60,7 @@ const Details: React.FC<DetailsProps> = ({
profileIcon: { profileIcon: {
url: null, url: null,
initials: assigned.profileIcon.initials ?? null, initials: assigned.profileIcon.initials ?? null,
bgColor: assigned.profileIcon.bgColor ?? null,
}, },
}; };
}); });
@ -93,10 +107,13 @@ const Details: React.FC<DetailsProps> = ({
> >
<MemberManager <MemberManager
availableMembers={availableMembers} availableMembers={availableMembers}
activeMembers={[]} activeMembers={taskMembers}
onMemberChange={(member, isActive) => { onMemberChange={(member, isActive) => {
console.log(`is active ${member.userID} - ${isActive}`);
if (isActive) { if (isActive) {
assignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } }); assignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } });
} else {
unassignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } });
} }
console.log(member, isActive); console.log(member, isActive);
}} }}

View File

@ -1,5 +1,6 @@
import styled from 'styled-components'; import styled from 'styled-components';
export const Board = styled.div` export const Board = styled.div`
margin-left: 36px; margin-top: 12px;
margin-left: 8px;
`; `;

View File

@ -109,9 +109,10 @@ const Project = () => {
); );
}, },
}); });
const { loading, data } = useFindProjectQuery({ const { loading, data, refetch } = useFindProjectQuery({
variables: { projectId }, variables: { projectId },
onCompleted: newData => { onCompleted: newData => {
console.log('beep!');
const newListsData: BoardState = { tasks: {}, columns: {} }; const newListsData: BoardState = { tasks: {}, columns: {} };
newData.findProject.taskGroups.forEach(taskGroup => { newData.findProject.taskGroups.forEach(taskGroup => {
newListsData.columns[taskGroup.taskGroupID] = { newListsData.columns[taskGroup.taskGroupID] = {
@ -121,15 +122,27 @@ const Project = () => {
tasks: [], tasks: [],
}; };
taskGroup.tasks.forEach(task => { taskGroup.tasks.forEach(task => {
const taskMembers = task.assigned.map(assigned => {
return {
userID: assigned.userID,
displayName: `${assigned.firstName} ${assigned.lastName}`,
profileIcon: {
url: null,
initials: assigned.profileIcon.initials ?? '',
bgColor: assigned.profileIcon.bgColor ?? '#7367F0',
},
};
});
newListsData.tasks[task.taskID] = { newListsData.tasks[task.taskID] = {
taskID: task.taskID, taskID: task.taskID,
taskGroup: { taskGroup: {
taskGroupID: taskGroup.taskGroupID, taskGroupID: taskGroup.taskGroupID,
}, },
name: task.name, name: task.name,
position: task.position,
labels: [], labels: [],
position: task.position,
description: task.description ?? undefined, description: task.description ?? undefined,
members: taskMembers,
}; };
}); });
}); });
@ -196,15 +209,16 @@ const Project = () => {
const availableMembers = data.findProject.members.map(member => { const availableMembers = data.findProject.members.map(member => {
return { return {
displayName: `${member.firstName} ${member.lastName}`, displayName: `${member.firstName} ${member.lastName}`,
profileIcon: { url: null, initials: member.profileIcon.initials ?? null }, profileIcon: {
url: null,
initials: member.profileIcon.initials ?? null,
bgColor: member.profileIcon.bgColor ?? null,
},
userID: member.userID, userID: member.userID,
}; };
}); });
return ( return (
<> <>
<TitleWrapper>
<Title>{data.findProject.name}</Title>
</TitleWrapper>
<KanbanBoard <KanbanBoard
listsData={listsData} listsData={listsData}
onCardDrop={onCardDrop} onCardDrop={onCardDrop}
@ -253,6 +267,10 @@ const Project = () => {
path={`${match.path}/c/:taskID`} path={`${match.path}/c/:taskID`}
render={(routeProps: RouteComponentProps<TaskRouteProps>) => ( render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
<Details <Details
refreshCache={() => {
console.log('beep 2!');
refetch();
}}
availableMembers={availableMembers} availableMembers={availableMembers}
projectURL={match.url} projectURL={match.url}
taskID={routeProps.match.params.taskID} taskID={routeProps.match.params.taskID}

View File

@ -37,6 +37,7 @@ type InnerTaskGroup = {
type ProfileIcon = { type ProfileIcon = {
url: string | null; url: string | null;
initials: string | null; initials: string | null;
bgColor: string | null;
}; };
type TaskUser = { type TaskUser = {

View File

@ -98,9 +98,6 @@ export const ListCardOperation = styled.span`
display: flex; display: flex;
align-content: center; align-content: center;
justify-content: center; justify-content: center;
background-color: ${props => mixin.darken('#262c49', 0.15)};
background-clip: padding-box;
background-origin: padding-box;
border-radius: 3px; border-radius: 3px;
opacity: 0.8; opacity: 0.8;
padding: 6px; padding: 6px;
@ -108,6 +105,10 @@ export const ListCardOperation = styled.span`
right: 2px; right: 2px;
top: 2px; top: 2px;
z-index: 10; z-index: 10;
&:hover {
background-color: ${props => mixin.darken('#262c49', 0.45)};
}
`; `;
export const CardTitle = styled.span` export const CardTitle = styled.span`
@ -120,3 +121,34 @@ export const CardTitle = styled.span`
word-wrap: break-word; word-wrap: break-word;
color: #c2c6dc; color: #c2c6dc;
`; `;
export const CardMembers = styled.div`
float: right;
margin: 0 -2px 0 0;
`;
export const CardMember = styled.div<{ bgColor: string }>`
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;
`;

View File

@ -18,6 +18,9 @@ import {
ListCardLabel, ListCardLabel,
ListCardOperation, ListCardOperation,
CardTitle, CardTitle,
CardMembers,
CardMember,
CardMemberInitials,
} from './Styles'; } from './Styles';
type DueDate = { type DueDate = {
@ -42,6 +45,7 @@ type Props = {
watched?: boolean; watched?: boolean;
labels?: Label[]; labels?: Label[];
wrapperProps?: any; wrapperProps?: any;
members?: Array<TaskUser> | null;
}; };
const Card = React.forwardRef( const Card = React.forwardRef(
@ -58,6 +62,7 @@ const Card = React.forwardRef(
description, description,
checklists, checklists,
watched, watched,
members,
}: Props, }: Props,
$cardRef: any, $cardRef: any,
) => { ) => {
@ -95,9 +100,11 @@ const Card = React.forwardRef(
{...wrapperProps} {...wrapperProps}
> >
<ListCardInnerContainer ref={$innerCardRef}> <ListCardInnerContainer ref={$innerCardRef}>
<ListCardOperation> {isActive && (
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} /> <ListCardOperation>
</ListCardOperation> <FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
</ListCardOperation>
)}
<ListCardDetails> <ListCardDetails>
<ListCardLabels> <ListCardLabels>
{labels && {labels &&
@ -132,6 +139,14 @@ const Card = React.forwardRef(
</ListCardBadge> </ListCardBadge>
)} )}
</ListCardBadges> </ListCardBadges>
<CardMembers>
{members &&
members.map(member => (
<CardMember key={member.userID} bgColor={member.profileIcon.bgColor ?? '#7367F0'}>
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
</CardMember>
))}
</CardMembers>
</ListCardDetails> </ListCardDetails>
</ListCardInnerContainer> </ListCardInnerContainer>
</ListCardContainer> </ListCardContainer>

View File

@ -1,8 +1,7 @@
import React, { createRef, useState } from 'react'; import React, { createRef, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import DropdownMenu from '.';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import DropdownMenu from '.';
export default { export default {
component: DropdownMenu, component: DropdownMenu,
@ -50,7 +49,16 @@ export const Default = () => {
Click me Click me
</Button> </Button>
</Container> </Container>
{menu.isOpen && <DropdownMenu onLogout={action('on logout')} left={menu.left} top={menu.top} />} {menu.isOpen && (
<DropdownMenu
onCloseDropdown={() => {
setMenu({ top: 0, left: 0, isOpen: false });
}}
onLogout={action('on logout')}
left={menu.left}
top={menu.top}
/>
)}
</> </>
); );
}; };

View File

@ -1,5 +1,5 @@
import React from 'react'; import React, { useRef } from 'react';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { Exit, User } from 'shared/icons'; import { Exit, User } from 'shared/icons';
import { Separator, Container, WrapperDiamond, Wrapper, ActionsList, ActionItem, ActionTitle } from './Styles'; import { Separator, Container, WrapperDiamond, Wrapper, ActionsList, ActionItem, ActionTitle } from './Styles';
@ -7,11 +7,14 @@ type DropdownMenuProps = {
left: number; left: number;
top: number; top: number;
onLogout: () => void; onLogout: () => void;
onCloseDropdown: () => void;
}; };
const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout }) => { const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout, onCloseDropdown }) => {
const $containerRef = useRef<HTMLDivElement>(null);
useOnOutsideClick($containerRef, true, onCloseDropdown, null);
return ( return (
<Container left={left} top={top}> <Container ref={$containerRef} left={left} top={top}>
<Wrapper> <Wrapper>
<ActionItem> <ActionItem>
<User size={16} color="#c2c6dc" /> <User size={16} color="#c2c6dc" />

View File

@ -23,7 +23,9 @@ export const Default = () => {
position: 1, position: 1,
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }], labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
description: 'hello!', description: 'hello!',
members: [{ userID: '1', profileIcon: { url: null, initials: null }, displayName: 'Jordan Knott' }], members: [
{ userID: '1', profileIcon: { url: null, initials: null, bgColor: null }, displayName: 'Jordan Knott' },
],
}} }}
onCancel={action('cancel')} onCancel={action('cancel')}
onDueDateChange={action('due date change')} onDueDateChange={action('due date change')}

View File

@ -159,6 +159,7 @@ const Lists: React.FC<Props> = ({
description="" description=""
title={task.name} title={task.name}
labels={task.labels} labels={task.labels}
members={task.members}
onClick={() => onCardClick(task)} onClick={() => onCardClick(task)}
onContextMenu={onQuickEditorOpen} onContextMenu={onQuickEditorOpen}
/> />

View File

@ -0,0 +1,50 @@
import styled from 'styled-components';
export const Profile = styled.div`
margin: 8px 0;
min-height: 56px;
position: relative;
`;
export const ProfileIcon = styled.div<{ bgColor: string }>`
float: left;
margin: 2px;
background-color: ${props => props.bgColor};
border-radius: 25em;
display: block;
height: 50px;
overflow: hidden;
position: relative;
width: 50px;
z-index: 1;
`;
export const ProfileInfo = styled.div`
margin: 0 0 0 64px;
word-wrap: break-word;
`;
export const InfoTitle = styled.h3`
margin: 0 40px 0 0;
font-size: 16px;
font-weight: 600;
line-height: 20px;
color: #172b4d;
`;
export const InfoUsername = styled.p`
color: #5e6c84;
font-size: 14px;
line-height: 20px;
`;
export const InfoBio = styled.p`
font-size: 14px;
line-height: 20px;
color: #5e6c84;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 0;
padding: 0;
`;

View File

@ -0,0 +1,26 @@
import React from 'react';
import { Profile, ProfileIcon, ProfileInfo, InfoTitle, InfoUsername, InfoBio } from './Styles';
type MiniProfileProps = {
displayName: string;
username: string;
bio: string;
profileIcon: ProfileIcon;
};
const MiniProfile: React.FC<MiniProfileProps> = ({ displayName, username, bio, profileIcon }) => {
return (
<>
<Profile>
<ProfileIcon bgColor={profileIcon.bgColor ?? ''}>{profileIcon.initials}</ProfileIcon>
<ProfileInfo>
<InfoTitle>{displayName}</InfoTitle>
<InfoUsername>{username}</InfoUsername>
<InfoBio>{bio}</InfoBio>
</ProfileInfo>
</Profile>
</>
);
};
export default MiniProfile;

View File

@ -17,13 +17,12 @@ export const ClickableOverlay = styled.div`
background: rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.4);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center;
padding: 50px;
`; `;
export const StyledModal = styled.div<{ width: number }>` export const StyledModal = styled.div<{ width: number }>`
display: inline-block; display: inline-block;
position: relative; position: relative;
margin: 48px 0 80px;
width: 100%; width: 100%;
background: #262c49; background: #262c49;
max-width: ${props => props.width}px; max-width: ${props => props.width}px;

View File

@ -1,32 +1,14 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
export const LogoWrapper = styled.div` export const Logo = styled.div``;
margin: 20px 0px 20px;
position: relative;
width: 100%;
height: 42px;
line-height: 42px;
padding-left: 64px;
color: rgb(222, 235, 255);
cursor: pointer;
user-select: none;
transition: color 0.1s ease 0s;
`;
export const Logo = styled.div`
position: absolute;
left: 19px;
`;
export const LogoTitle = styled.div` export const LogoTitle = styled.div`
position: relative; position: absolute;
right: 12px;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
transition: right 0.1s ease 0s, visibility, opacity, transform 0.25s ease; transition: visibility, opacity, transform 0.25s ease;
color: #7367f0; color: #7367f0;
`; `;
export const ActionContainer = styled.div` export const ActionContainer = styled.div`
@ -54,6 +36,10 @@ export const IconWrapper = styled.div`
export const ActionButtonContainer = styled.div` export const ActionButtonContainer = styled.div`
padding: 0 12px; padding: 0 12px;
position: relative; position: relative;
& > a:first-child > div {
padding-top: 48px;
}
`; `;
export const ActionButtonWrapper = styled.div<{ active?: boolean }>` export const ActionButtonWrapper = styled.div<{ active?: boolean }>`
@ -65,7 +51,7 @@ export const ActionButtonWrapper = styled.div<{ active?: boolean }>`
`} `}
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
padding: 10px 15px; padding: 24px 15px;
display: flex; display: flex;
align-items: center; align-items: center;
&:hover ${ActionButtonTitle} { &:hover ${ActionButtonTitle} {
@ -76,6 +62,20 @@ export const ActionButtonWrapper = styled.div<{ active?: boolean }>`
} }
`; `;
export const LogoWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: relative;
width: 100%;
height: 80px;
color: rgb(222, 235, 255);
cursor: pointer;
transition: color 0.1s ease 0s, border 0.1s ease 0s;
border-bottom: 1px solid rgba(65, 69, 97, 0.65);
`;
export const Container = styled.aside` export const Container = styled.aside`
z-index: 100; z-index: 100;
position: fixed; position: fixed;
@ -87,13 +87,15 @@ export const Container = styled.aside`
transform: translateZ(0px); transform: translateZ(0px);
background: #10163a; background: #10163a;
transition: all 0.1s ease 0s; transition: all 0.1s ease 0s;
border-right: 1px solid rgba(65, 69, 97, 0.65);
&:hover { &:hover {
width: 260px; width: 260px;
box-shadow: rgba(0, 0, 0, 0.6) 0px 0px 50px 0px; box-shadow: rgba(0, 0, 0, 0.6) 0px 0px 50px 0px;
border-right: 1px solid rgba(65, 69, 97, 0);
} }
&:hover ${LogoTitle} { &:hover ${LogoTitle} {
right: 0px; bottom: -12px;
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }
@ -102,4 +104,8 @@ export const Container = styled.aside`
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }
&:hover ${LogoWrapper} {
border-bottom: 1px solid rgba(65, 69, 97, 0);
}
`; `;

View File

@ -35,9 +35,7 @@ export const ButtonContainer: React.FC = ({ children }) => (
export const PrimaryLogo = () => { export const PrimaryLogo = () => {
return ( return (
<LogoWrapper> <LogoWrapper>
<Logo> <Citadel size={42} />
<Citadel size={42} />
</Logo>
<LogoTitle>Citadel</LogoTitle> <LogoTitle>Citadel</LogoTitle>
</LogoWrapper> </LogoWrapper>
); );

View File

@ -6,6 +6,7 @@ import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
import ListActions from 'shared/components/ListActions'; import ListActions from 'shared/components/ListActions';
import MemberManager from 'shared/components/MemberManager'; import MemberManager from 'shared/components/MemberManager';
import DueDateManager from 'shared/components/DueDateManager'; import DueDateManager from 'shared/components/DueDateManager';
import MiniProfile from 'shared/components/MiniProfile';
import PopupMenu from '.'; import PopupMenu from '.';
import NormalizeStyles from 'App/NormalizeStyles'; import NormalizeStyles from 'App/NormalizeStyles';
@ -115,7 +116,7 @@ export const MemberManagerPopup = () => {
<PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}> <PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<MemberManager <MemberManager
availableMembers={[ availableMembers={[
{ userID: '1', displayName: 'Jordan Knott', profileIcon: { url: null, initials: null } }, { userID: '1', displayName: 'Jordan Knott', profileIcon: { bgColor: null, url: null, initials: null } },
]} ]}
activeMembers={[]} activeMembers={[]}
onMemberChange={action('member change')} onMemberChange={action('member change')}
@ -158,7 +159,9 @@ export const DueDateManagerPopup = () => {
position: 1, position: 1,
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }], labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
description: 'hello!', description: 'hello!',
members: [{ userID: '1', profileIcon: { url: null, initials: null }, displayName: 'Jordan Knott' }], members: [
{ userID: '1', profileIcon: { bgColor: null, url: null, initials: null }, displayName: 'Jordan Knott' },
],
}} }}
onCancel={action('cancel')} onCancel={action('cancel')}
onDueDateChange={action('due date change')} onDueDateChange={action('due date change')}
@ -189,3 +192,47 @@ export const DueDateManagerPopup = () => {
</> </>
); );
}; };
export const MiniProfilePopup = () => {
const $buttonRef = useRef<HTMLButtonElement>(null);
const [popupData, setPopupData] = useState(initalState);
return (
<>
<NormalizeStyles />
<BaseStyles />
{popupData.isOpen && (
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<MiniProfile
displayName="Jordan Knott"
profileIcon={{ url: null, bgColor: '#000', initials: 'JK' }}
username="@jordanthedev"
bio="Stuff and things"
/>
</PopupMenu>
)}
<span
style={{
width: '60px',
textAlign: 'center',
margin: '25px auto',
cursor: 'pointer',
color: '#fff',
background: '#f00',
}}
ref={$buttonRef}
onClick={() => {
if ($buttonRef && $buttonRef.current) {
const pos = $buttonRef.current.getBoundingClientRect();
setPopupData({
isOpen: true,
left: pos.left,
top: pos.top + pos.height + 10,
});
}
}}
>
Open
</span>
</>
);
};

View File

@ -281,3 +281,9 @@ export const NoDueDateLabel = styled.span`
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
`; `;
export const UnassignedLabel = styled.div`
color: rgb(137, 147, 164);
font-size: 14px;
cursor: pointer;
`;

View File

@ -35,7 +35,13 @@ export const Default = () => {
position: 1, position: 1,
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }], labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
description, description,
members: [{ userID: '1', profileIcon: { url: null, initials: null }, displayName: 'Jordan Knott' }], members: [
{
userID: '1',
profileIcon: { bgColor: null, url: null, initials: null },
displayName: 'Jordan Knott',
},
],
}} }}
onTaskNameChange={action('task name change')} onTaskNameChange={action('task name change')}
onTaskDescriptionChange={(_task, desc) => setDescription(desc)} onTaskDescriptionChange={(_task, desc) => setDescription(desc)}

View File

@ -4,6 +4,7 @@ import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { import {
NoDueDateLabel, NoDueDateLabel,
UnassignedLabel,
TaskDetailsAddMember, TaskDetailsAddMember,
TaskGroupLabel, TaskGroupLabel,
TaskGroupLabelName, TaskGroupLabelName,
@ -126,11 +127,16 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
onTaskNameChange(task, taskName); onTaskNameChange(task, taskName);
} }
}; };
const $unassignedRef = useRef<HTMLDivElement>(null);
const $addMemberRef = useRef<HTMLDivElement>(null); const $addMemberRef = useRef<HTMLDivElement>(null);
const onUnassignedClick = () => {
const bounds = convertDivElementRefToBounds($unassignedRef);
if (bounds) {
onOpenAddMemberPopup(task, bounds);
}
};
const onAddMember = () => { const onAddMember = () => {
console.log('beep!');
const bounds = convertDivElementRefToBounds($addMemberRef); const bounds = convertDivElementRefToBounds($addMemberRef);
console.log(bounds);
if (bounds) { if (bounds) {
onOpenAddMemberPopup(task, bounds); onOpenAddMemberPopup(task, bounds);
} }
@ -191,20 +197,28 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskDetailsSidebar> <TaskDetailsSidebar>
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle> <TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
<TaskDetailAssignees> <TaskDetailAssignees>
{task.members && {task.members && task.members.length === 0 ? (
task.members.map(member => { <UnassignedLabel ref={$unassignedRef} onClick={onUnassignedClick}>
console.log(member); Unassigned
return ( </UnassignedLabel>
<TaskDetailAssignee key={member.userID}> ) : (
<ProfileIcon>{member.profileIcon.initials ?? ''}</ProfileIcon> <>
</TaskDetailAssignee> {task.members &&
); task.members.map(member => {
})} console.log(member);
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}> return (
<TaskDetailsAddMemberIcon> <TaskDetailAssignee key={member.userID}>
<Plus size={16} color="#c2c6dc" /> <ProfileIcon>{member.profileIcon.initials ?? ''}</ProfileIcon>
</TaskDetailsAddMemberIcon> </TaskDetailAssignee>
</TaskDetailsAddMember> );
})}
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}>
<TaskDetailsAddMemberIcon>
<Plus size={16} color="#c2c6dc" />
</TaskDetailsAddMemberIcon>
</TaskDetailsAddMember>
</>
)}
</TaskDetailAssignees> </TaskDetailAssignees>
<TaskDetailSectionTitle>Labels</TaskDetailSectionTitle> <TaskDetailSectionTitle>Labels</TaskDetailSectionTitle>
<TaskDetailLabels> <TaskDetailLabels>

View File

@ -1,19 +1,18 @@
import styled from 'styled-components'; import styled from 'styled-components';
export const NavbarWrapper = styled.div` export const NavbarWrapper = styled.div`
height: 103px;
padding: 1.3rem 2.2rem 2.2rem;
width: 100%; width: 100%;
`; `;
export const NavbarHeader = styled.header` export const NavbarHeader = styled.header`
border-radius: 0.5rem; height: 80px;
padding: 0.8rem 1rem; padding: 0 1.75rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
background: rgb(16, 22, 58); background: rgb(16, 22, 58);
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.05); box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.05);
border-bottom: 1px solid rgba(65, 69, 97, 0.65);
`; `;
export const Breadcrumbs = styled.div` export const Breadcrumbs = styled.div`
color: rgb(94, 108, 132); color: rgb(94, 108, 132);
@ -26,7 +25,14 @@ export const BreadcrumpSeparator = styled.span`
margin: 0px 10px; margin: 0px 10px;
`; `;
export const ProjectActions = styled.div``; export const ProjectActions = styled.div`
align-items: flex-start;
display: flex;
flex: 1;
flex-direction: column;
min-width: 1px;
`;
export const GlobalActions = styled.div` export const GlobalActions = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
@ -55,7 +61,7 @@ export const ProfileNameSecondary = styled.small`
color: #c2c6dc; color: #c2c6dc;
`; `;
export const ProfileIcon = styled.div` export const ProfileIcon = styled.div<{ bgColor: string }>`
margin-left: 10px; margin-left: 10px;
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -65,6 +71,48 @@ export const ProfileIcon = styled.div`
justify-content: center; justify-content: center;
color: #fff; color: #fff;
font-weight: 700; font-weight: 700;
background: rgb(115, 103, 240); background: ${props => props.bgColor};
cursor: pointer; cursor: pointer;
`; `;
export const ProjectMeta = styled.div`
align-items: center;
display: flex;
max-width: 100%;
min-height: 51px;
`;
export const ProjectTabs = styled.div`
align-items: flex-end;
align-self: stretch;
display: flex;
flex: 1 0 auto;
justify-content: flex-start;
max-width: 100%;
`;
export const ProjectTab = styled.span`
font-size: 80%;
color: #c2c6dc;
font-size: 15px;
cursor: default;
display: flex;
line-height: normal;
min-width: 1px;
transition-duration: 0.2s;
transition-property: box-shadow, color;
white-space: nowrap;
flex: 0 1 auto;
padding-bottom: 12px;
box-shadow: inset 0 -2px #d85dd8;
color: #d85dd8;
`;
export const ProjectName = styled.h1`
color: #c2c6dc;
margin-top: 9px;
font-weight: 600;
font-size: 20px;
`;

View File

@ -38,13 +38,23 @@ export const Default = () => {
<NormalizeStyles /> <NormalizeStyles />
<BaseStyles /> <BaseStyles />
<TopNavbar <TopNavbar
bgColor="#7367F0"
firstName="Jordan" firstName="Jordan"
lastName="Knott" lastName="Knott"
initials="JK" initials="JK"
onNotificationClick={action('notifications click')} onNotificationClick={action('notifications click')}
onProfileClick={onClick} onProfileClick={onClick}
/> />
{menu.isOpen && <DropdownMenu onLogout={action('on logout')} left={menu.left} top={menu.top} />} {menu.isOpen && (
<DropdownMenu
onCloseDropdown={() => {
setMenu({ left: 0, top: 0, isOpen: false });
}}
onLogout={action('on logout')}
left={menu.left}
top={menu.top}
/>
)}
</> </>
); );
}; };

View File

@ -5,6 +5,10 @@ import {
NotificationContainer, NotificationContainer,
GlobalActions, GlobalActions,
ProjectActions, ProjectActions,
ProjectMeta,
ProjectName,
ProjectTabs,
ProjectTab,
NavbarWrapper, NavbarWrapper,
NavbarHeader, NavbarHeader,
Breadcrumbs, Breadcrumbs,
@ -19,11 +23,19 @@ import {
type NavBarProps = { type NavBarProps = {
onProfileClick: (bottom: number, right: number) => void; onProfileClick: (bottom: number, right: number) => void;
onNotificationClick: () => void; onNotificationClick: () => void;
bgColor: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
initials: string; initials: string;
}; };
const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick, firstName, lastName, initials }) => { const NavBar: React.FC<NavBarProps> = ({
onProfileClick,
onNotificationClick,
firstName,
lastName,
initials,
bgColor,
}) => {
const $profileRef: any = useRef(null); const $profileRef: any = useRef(null);
const handleProfileClick = () => { const handleProfileClick = () => {
console.log('click'); console.log('click');
@ -34,13 +46,12 @@ const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick, fi
<NavbarWrapper> <NavbarWrapper>
<NavbarHeader> <NavbarHeader>
<ProjectActions> <ProjectActions>
<Breadcrumbs> <ProjectMeta>
Projects <ProjectName>Production Team</ProjectName>
<BreadcrumpSeparator>/</BreadcrumpSeparator> </ProjectMeta>
project name <ProjectTabs>
<BreadcrumpSeparator>/</BreadcrumpSeparator> <ProjectTab>Board</ProjectTab>
Board </ProjectTabs>
</Breadcrumbs>
</ProjectActions> </ProjectActions>
<GlobalActions> <GlobalActions>
<NotificationContainer onClick={onNotificationClick}> <NotificationContainer onClick={onNotificationClick}>
@ -53,7 +64,7 @@ const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick, fi
</ProfileNamePrimary> </ProfileNamePrimary>
<ProfileNameSecondary>Manager</ProfileNameSecondary> <ProfileNameSecondary>Manager</ProfileNameSecondary>
</ProfileNameWrapper> </ProfileNameWrapper>
<ProfileIcon ref={$profileRef} onClick={handleProfileClick}> <ProfileIcon ref={$profileRef} onClick={handleProfileClick} bgColor={bgColor}>
{initials} {initials}
</ProfileIcon> </ProfileIcon>
</ProfileContainer> </ProfileContainer>

View File

@ -15,17 +15,28 @@ export type Scalars = {
export type ProjectLabel = {
__typename?: 'ProjectLabel';
projectLabelID: Scalars['ID'];
createdDate: Scalars['Time'];
colorHex: Scalars['String'];
name?: Maybe<Scalars['String']>;
};
export type TaskLabel = { export type TaskLabel = {
__typename?: 'TaskLabel'; __typename?: 'TaskLabel';
taskLabelID: Scalars['ID']; taskLabelID: Scalars['ID'];
labelColorID: Scalars['UUID']; projectLabelID: Scalars['UUID'];
assignedDate: Scalars['Time'];
colorHex: Scalars['String']; colorHex: Scalars['String'];
name?: Maybe<Scalars['String']>;
}; };
export type ProfileIcon = { export type ProfileIcon = {
__typename?: 'ProfileIcon'; __typename?: 'ProfileIcon';
url?: Maybe<Scalars['String']>; url?: Maybe<Scalars['String']>;
initials?: Maybe<Scalars['String']>; initials?: Maybe<Scalars['String']>;
bgColor?: Maybe<Scalars['String']>;
}; };
export type ProjectMember = { export type ProjectMember = {
@ -71,6 +82,7 @@ export type Project = {
owner: ProjectMember; owner: ProjectMember;
taskGroups: Array<TaskGroup>; taskGroups: Array<TaskGroup>;
members: Array<ProjectMember>; members: Array<ProjectMember>;
labels: Array<ProjectLabel>;
}; };
export type TaskGroup = { export type TaskGroup = {
@ -222,6 +234,11 @@ export type AssignTaskInput = {
userID: Scalars['UUID']; userID: Scalars['UUID'];
}; };
export type UnassignTaskInput = {
taskID: Scalars['UUID'];
userID: Scalars['UUID'];
};
export type UpdateTaskDescriptionInput = { export type UpdateTaskDescriptionInput = {
taskID: Scalars['UUID']; taskID: Scalars['UUID'];
description: Scalars['String']; description: Scalars['String'];
@ -237,12 +254,19 @@ export type RemoveTaskLabelInput = {
taskLabelID: Scalars['UUID']; taskLabelID: Scalars['UUID'];
}; };
export type NewProjectLabel = {
projectID: Scalars['UUID'];
labelColorID: Scalars['UUID'];
name?: Maybe<Scalars['String']>;
};
export type Mutation = { export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
createRefreshToken: RefreshToken; createRefreshToken: RefreshToken;
createUserAccount: UserAccount; createUserAccount: UserAccount;
createTeam: Team; createTeam: Team;
createProject: Project; createProject: Project;
createProjectLabel: ProjectLabel;
createTaskGroup: TaskGroup; createTaskGroup: TaskGroup;
updateTaskGroupLocation: TaskGroup; updateTaskGroupLocation: TaskGroup;
deleteTaskGroup: DeleteTaskGroupPayload; deleteTaskGroup: DeleteTaskGroupPayload;
@ -254,6 +278,7 @@ export type Mutation = {
updateTaskName: Task; updateTaskName: Task;
deleteTask: DeleteTaskPayload; deleteTask: DeleteTaskPayload;
assignTask: Task; assignTask: Task;
unassignTask: Task;
logoutUser: Scalars['Boolean']; logoutUser: Scalars['Boolean'];
}; };
@ -278,6 +303,11 @@ export type MutationCreateProjectArgs = {
}; };
export type MutationCreateProjectLabelArgs = {
input: NewProjectLabel;
};
export type MutationCreateTaskGroupArgs = { export type MutationCreateTaskGroupArgs = {
input: NewTaskGroup; input: NewTaskGroup;
}; };
@ -333,6 +363,11 @@ export type MutationAssignTaskArgs = {
}; };
export type MutationUnassignTaskArgs = {
input?: Maybe<UnassignTaskInput>;
};
export type MutationLogoutUserArgs = { export type MutationLogoutUserArgs = {
input: LogoutUser; input: LogoutUser;
}; };
@ -438,7 +473,7 @@ export type FindProjectQuery = (
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'> & Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
& { profileIcon: ( & { profileIcon: (
{ __typename?: 'ProfileIcon' } { __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials'> & Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) } ) }
)>, taskGroups: Array<( )>, taskGroups: Array<(
{ __typename?: 'TaskGroup' } { __typename?: 'TaskGroup' }
@ -446,6 +481,14 @@ export type FindProjectQuery = (
& { tasks: Array<( & { tasks: Array<(
{ __typename?: 'Task' } { __typename?: 'Task' }
& Pick<Task, 'taskID' | 'name' | 'position' | 'description'> & Pick<Task, 'taskID' | 'name' | 'position' | 'description'>
& { assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)> }
)> } )> }
)> } )> }
) } ) }
@ -469,7 +512,7 @@ export type FindTaskQuery = (
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'> & Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
& { profileIcon: ( & { profileIcon: (
{ __typename?: 'ProfileIcon' } { __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials'> & Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) } ) }
)> } )> }
) } ) }
@ -500,11 +543,29 @@ export type MeQuery = (
& Pick<UserAccount, 'firstName' | 'lastName'> & Pick<UserAccount, 'firstName' | 'lastName'>
& { profileIcon: ( & { profileIcon: (
{ __typename?: 'ProfileIcon' } { __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'initials'> & Pick<ProfileIcon, 'initials' | 'bgColor'>
) } ) }
) } ) }
); );
export type UnassignTaskMutationVariables = {
taskID: Scalars['UUID'];
userID: Scalars['UUID'];
};
export type UnassignTaskMutation = (
{ __typename?: 'Mutation' }
& { unassignTask: (
{ __typename?: 'Task' }
& Pick<Task, 'taskID'>
& { assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
)> }
) }
);
export type UpdateTaskDescriptionMutationVariables = { export type UpdateTaskDescriptionMutationVariables = {
taskID: Scalars['UUID']; taskID: Scalars['UUID'];
description: Scalars['String']; description: Scalars['String'];
@ -759,6 +820,7 @@ export const FindProjectDocument = gql`
profileIcon { profileIcon {
url url
initials initials
bgColor
} }
} }
taskGroups { taskGroups {
@ -770,6 +832,16 @@ export const FindProjectDocument = gql`
name name
position position
description description
assigned {
userID
firstName
lastName
profileIcon {
url
initials
bgColor
}
}
} }
} }
} }
@ -818,6 +890,7 @@ export const FindTaskDocument = gql`
profileIcon { profileIcon {
url url
initials initials
bgColor
} }
} }
} }
@ -893,6 +966,7 @@ export const MeDocument = gql`
lastName lastName
profileIcon { profileIcon {
initials initials
bgColor
} }
} }
} }
@ -922,6 +996,44 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
export type MeQueryHookResult = ReturnType<typeof useMeQuery>; export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>; export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>; export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
export const UnassignTaskDocument = gql`
mutation unassignTask($taskID: UUID!, $userID: UUID!) {
unassignTask(input: {taskID: $taskID, userID: $userID}) {
assigned {
userID
firstName
lastName
}
taskID
}
}
`;
export type UnassignTaskMutationFn = ApolloReactCommon.MutationFunction<UnassignTaskMutation, UnassignTaskMutationVariables>;
/**
* __useUnassignTaskMutation__
*
* To run a mutation, you first call `useUnassignTaskMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUnassignTaskMutation` 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 [unassignTaskMutation, { data, loading, error }] = useUnassignTaskMutation({
* variables: {
* taskID: // value for 'taskID'
* userID: // value for 'userID'
* },
* });
*/
export function useUnassignTaskMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UnassignTaskMutation, UnassignTaskMutationVariables>) {
return ApolloReactHooks.useMutation<UnassignTaskMutation, UnassignTaskMutationVariables>(UnassignTaskDocument, baseOptions);
}
export type UnassignTaskMutationHookResult = ReturnType<typeof useUnassignTaskMutation>;
export type UnassignTaskMutationResult = ApolloReactCommon.MutationResult<UnassignTaskMutation>;
export type UnassignTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<UnassignTaskMutation, UnassignTaskMutationVariables>;
export const UpdateTaskDescriptionDocument = gql` export const UpdateTaskDescriptionDocument = gql`
mutation updateTaskDescription($taskID: UUID!, $description: String!) { mutation updateTaskDescription($taskID: UUID!, $description: String!) {
updateTaskDescription(input: {taskID: $taskID, description: $description}) { updateTaskDescription(input: {taskID: $taskID, description: $description}) {

View File

@ -8,6 +8,7 @@ query findProject($projectId: String!) {
profileIcon { profileIcon {
url url
initials initials
bgColor
} }
} }
taskGroups { taskGroups {
@ -19,6 +20,16 @@ query findProject($projectId: String!) {
name name
position position
description description
assigned {
userID
firstName
lastName
profileIcon {
url
initials
bgColor
}
}
} }
} }
} }

View File

@ -14,6 +14,7 @@ query findTask($taskID: UUID!) {
profileIcon { profileIcon {
url url
initials initials
bgColor
} }
} }
} }

View File

@ -4,6 +4,7 @@ query me {
lastName lastName
profileIcon { profileIcon {
initials initials
bgColor
} }
} }
} }

View File

@ -0,0 +1,10 @@
mutation unassignTask($taskID: UUID!, $userID: UUID!) {
unassignTask(input: {taskID: $taskID, userID: $userID}) {
assigned {
userID
firstName
lastName
}
taskID
}
}