feature: UI changes
This commit is contained in:
parent
c38024e692
commit
7e78ee36b4
File diff suppressed because it is too large
Load Diff
@ -57,6 +57,12 @@ type NewProject struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type NewProjectLabel struct {
|
||||
ProjectID uuid.UUID `json:"projectID"`
|
||||
LabelColorID uuid.UUID `json:"labelColorID"`
|
||||
Name *string `json:"name"`
|
||||
}
|
||||
|
||||
type NewRefreshToken struct {
|
||||
UserID string `json:"userId"`
|
||||
}
|
||||
@ -100,6 +106,7 @@ type NewUserAccount struct {
|
||||
type ProfileIcon struct {
|
||||
URL *string `json:"url"`
|
||||
Initials *string `json:"initials"`
|
||||
BgColor *string `json:"bgColor"`
|
||||
}
|
||||
|
||||
type ProjectMember struct {
|
||||
@ -118,6 +125,11 @@ type RemoveTaskLabelInput struct {
|
||||
TaskLabelID uuid.UUID `json:"taskLabelID"`
|
||||
}
|
||||
|
||||
type UnassignTaskInput struct {
|
||||
TaskID uuid.UUID `json:"taskID"`
|
||||
UserID uuid.UUID `json:"userID"`
|
||||
}
|
||||
|
||||
type UpdateTaskDescriptionInput struct {
|
||||
TaskID uuid.UUID `json:"taskID"`
|
||||
Description string `json:"description"`
|
||||
|
@ -1,15 +1,25 @@
|
||||
scalar Time
|
||||
scalar UUID
|
||||
|
||||
type ProjectLabel {
|
||||
projectLabelID: ID!
|
||||
createdDate: Time!
|
||||
colorHex: String!
|
||||
name: String
|
||||
}
|
||||
|
||||
type TaskLabel {
|
||||
taskLabelID: ID!
|
||||
labelColorID: UUID!
|
||||
projectLabelID: UUID!
|
||||
assignedDate: Time!
|
||||
colorHex: String!
|
||||
name: String
|
||||
}
|
||||
|
||||
type ProfileIcon {
|
||||
url: String
|
||||
initials: String
|
||||
bgColor: String
|
||||
}
|
||||
|
||||
type ProjectMember {
|
||||
@ -50,6 +60,7 @@ type Project {
|
||||
owner: ProjectMember!
|
||||
taskGroups: [TaskGroup!]!
|
||||
members: [ProjectMember!]!
|
||||
labels: [ProjectLabel!]!
|
||||
}
|
||||
|
||||
type TaskGroup {
|
||||
@ -174,6 +185,10 @@ input AssignTaskInput {
|
||||
userID: UUID!
|
||||
}
|
||||
|
||||
input UnassignTaskInput {
|
||||
taskID: UUID!
|
||||
userID: UUID!
|
||||
}
|
||||
input UpdateTaskDescriptionInput {
|
||||
taskID: UUID!
|
||||
description: String!
|
||||
@ -189,6 +204,12 @@ input RemoveTaskLabelInput {
|
||||
taskLabelID: UUID!
|
||||
}
|
||||
|
||||
input NewProjectLabel {
|
||||
projectID: UUID!
|
||||
labelColorID: UUID!
|
||||
name: String
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createRefreshToken(input: NewRefreshToken!): RefreshToken!
|
||||
|
||||
@ -197,6 +218,7 @@ type Mutation {
|
||||
createTeam(input: NewTeam!): Team!
|
||||
|
||||
createProject(input: NewProject!): Project!
|
||||
createProjectLabel(input: NewProjectLabel!): ProjectLabel!
|
||||
|
||||
createTaskGroup(input: NewTaskGroup!): TaskGroup!
|
||||
updateTaskGroupLocation(input: NewTaskGroupLocation!): TaskGroup!
|
||||
@ -211,6 +233,7 @@ type Mutation {
|
||||
updateTaskName(input: UpdateTaskName!): Task!
|
||||
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
||||
assignTask(input: AssignTaskInput): Task!
|
||||
unassignTask(input: UnassignTaskInput): Task!
|
||||
|
||||
logoutUser(input: LogoutUser!): Boolean!
|
||||
}
|
||||
|
@ -45,6 +45,10 @@ func (r *mutationResolver) CreateProject(ctx context.Context, input NewProject)
|
||||
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) {
|
||||
createdAt := time.Now().UTC()
|
||||
projectID, err := uuid.Parse(input.ProjectID)
|
||||
@ -164,6 +168,18 @@ func (r *mutationResolver) AssignTask(ctx context.Context, input *AssignTaskInpu
|
||||
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) {
|
||||
userID, err := uuid.Parse(input.UserID)
|
||||
if err != nil {
|
||||
@ -185,7 +201,7 @@ func (r *projectResolver) Owner(ctx context.Context, obj *pg.Project) (*ProjectM
|
||||
return &ProjectMember{}, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@ -200,11 +216,28 @@ func (r *projectResolver) Members(ctx context.Context, obj *pg.Project) ([]Proje
|
||||
return members, err
|
||||
}
|
||||
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})
|
||||
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) {
|
||||
return r.Repository.GetAllUserAccounts(ctx)
|
||||
}
|
||||
@ -306,7 +339,7 @@ func (r *taskResolver) Assigned(ctx context.Context, obj *pg.Task) ([]ProjectMem
|
||||
return taskMembers, err
|
||||
}
|
||||
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})
|
||||
}
|
||||
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) {
|
||||
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 {
|
||||
return "", err
|
||||
}
|
||||
return labelColor.ColorHex, nil
|
||||
}
|
||||
|
||||
func (r *taskLabelResolver) Name(ctx context.Context, obj *pg.TaskLabel) (*string, error) {
|
||||
projectLabel, err := r.Repository.GetProjectLabelByID(ctx, obj.ProjectLabelID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := projectLabel.Name
|
||||
if !name.Valid {
|
||||
return nil, err
|
||||
}
|
||||
return &name.String, err
|
||||
}
|
||||
|
||||
func (r *userAccountResolver) ProfileIcon(ctx context.Context, obj *pg.UserAccount) (*ProfileIcon, error) {
|
||||
initials := string([]rune(obj.FirstName)[0]) + string([]rune(obj.LastName)[0])
|
||||
profileIcon := &ProfileIcon{nil, &initials}
|
||||
profileIcon := &ProfileIcon{nil, &initials, &obj.ProfileBgColor}
|
||||
return profileIcon, nil
|
||||
}
|
||||
|
||||
@ -345,6 +394,9 @@ func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
|
||||
// Project returns ProjectResolver implementation.
|
||||
func (r *Resolver) Project() ProjectResolver { return &projectResolver{r} }
|
||||
|
||||
// ProjectLabel returns ProjectLabelResolver implementation.
|
||||
func (r *Resolver) ProjectLabel() ProjectLabelResolver { return &projectLabelResolver{r} }
|
||||
|
||||
// Query returns QueryResolver implementation.
|
||||
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
|
||||
|
||||
@ -362,6 +414,7 @@ func (r *Resolver) UserAccount() UserAccountResolver { return &userAccountResolv
|
||||
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type projectResolver struct{ *Resolver }
|
||||
type projectLabelResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
type taskResolver 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
|
||||
// it when you're done.
|
||||
// - You have helper methods in this file. Move them out to keep these resolver files clean.
|
||||
func (r *taskLabelResolver) ProjectLabelID(ctx context.Context, obj *pg.TaskLabel) (uuid.UUID, error) {
|
||||
panic(fmt.Errorf("not implemented"))
|
||||
}
|
||||
func (r *userAccountResolver) DisplayName(ctx context.Context, obj *pg.UserAccount) (string, error) {
|
||||
return obj.FirstName + " " + obj.LastName, nil
|
||||
}
|
||||
|
7
api/migrations/0012-add-project-label-table.up.sql
Normal file
7
api/migrations/0012-add-project-label-table.up.sql
Normal 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
|
||||
);
|
@ -1,6 +1,6 @@
|
||||
CREATE TABLE task_label (
|
||||
task_label_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
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
|
||||
);
|
@ -0,0 +1 @@
|
||||
ALTER TABLE user_account ADD COLUMN profile_bg_color text NOT NULL DEFAULT '#7367F0';
|
@ -29,6 +29,14 @@ type Project struct {
|
||||
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 {
|
||||
TokenID uuid.UUID `json:"token_id"`
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
@ -62,10 +70,10 @@ type TaskGroup struct {
|
||||
}
|
||||
|
||||
type TaskLabel struct {
|
||||
TaskLabelID uuid.UUID `json:"task_label_id"`
|
||||
TaskID uuid.UUID `json:"task_id"`
|
||||
LabelColorID uuid.UUID `json:"label_color_id"`
|
||||
AssignedDate time.Time `json:"assigned_date"`
|
||||
TaskLabelID uuid.UUID `json:"task_label_id"`
|
||||
TaskID uuid.UUID `json:"task_id"`
|
||||
ProjectLabelID uuid.UUID `json:"project_label_id"`
|
||||
AssignedDate time.Time `json:"assigned_date"`
|
||||
}
|
||||
|
||||
type Team struct {
|
||||
@ -76,11 +84,12 @@ type Team struct {
|
||||
}
|
||||
|
||||
type UserAccount struct {
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
ProfileBgColor string `json:"profile_bg_color"`
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ type Repository interface {
|
||||
GetUserAccountByUsername(ctx context.Context, username string) (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)
|
||||
GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
|
||||
DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
|
||||
@ -55,6 +59,7 @@ type Repository interface {
|
||||
|
||||
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
|
||||
GetAssignedMembersForTask(ctx context.Context, taskID uuid.UUID) ([]TaskAssigned, error)
|
||||
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
|
||||
}
|
||||
|
||||
type repoSvc struct {
|
||||
|
92
api/pg/project_label.sql.go
Normal file
92
api/pg/project_label.sql.go
Normal 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
|
||||
}
|
@ -11,6 +11,7 @@ import (
|
||||
type Querier interface {
|
||||
CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, 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)
|
||||
CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
|
||||
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
|
||||
@ -21,6 +22,7 @@ type Querier interface {
|
||||
DeleteExpiredTokens(ctx context.Context) error
|
||||
DeleteRefreshTokenByID(ctx context.Context, tokenID 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
|
||||
DeleteTaskGroupByID(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)
|
||||
GetLabelColorByID(ctx context.Context, labelColorID uuid.UUID) (LabelColor, 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)
|
||||
GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error)
|
||||
GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error)
|
||||
|
@ -33,6 +33,27 @@ func (q *Queries) CreateTaskAssigned(ctx context.Context, arg CreateTaskAssigned
|
||||
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
|
||||
SELECT task_assigned_id, task_id, user_id, assigned_date FROM task_assigned WHERE task_id = $1
|
||||
`
|
||||
|
@ -11,30 +11,30 @@ import (
|
||||
)
|
||||
|
||||
const createTaskLabelForTask = `-- name: CreateTaskLabelForTask :one
|
||||
INSERT INTO task_label (task_id, label_color_id, assigned_date)
|
||||
VALUES ($1, $2, $3) RETURNING task_label_id, 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, project_label_id, assigned_date
|
||||
`
|
||||
|
||||
type CreateTaskLabelForTaskParams struct {
|
||||
TaskID uuid.UUID `json:"task_id"`
|
||||
LabelColorID uuid.UUID `json:"label_color_id"`
|
||||
AssignedDate time.Time `json:"assigned_date"`
|
||||
TaskID uuid.UUID `json:"task_id"`
|
||||
ProjectLabelID uuid.UUID `json:"project_label_id"`
|
||||
AssignedDate time.Time `json:"assigned_date"`
|
||||
}
|
||||
|
||||
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
|
||||
err := row.Scan(
|
||||
&i.TaskLabelID,
|
||||
&i.TaskID,
|
||||
&i.LabelColorID,
|
||||
&i.ProjectLabelID,
|
||||
&i.AssignedDate,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -49,7 +49,7 @@ func (q *Queries) GetTaskLabelsForTaskID(ctx context.Context, taskID uuid.UUID)
|
||||
if err := rows.Scan(
|
||||
&i.TaskLabelID,
|
||||
&i.TaskID,
|
||||
&i.LabelColorID,
|
||||
&i.ProjectLabelID,
|
||||
&i.AssignedDate,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
const createUserAccount = `-- name: CreateUserAccount :one
|
||||
INSERT INTO user_account(first_name, last_name, email, username, created_at, password_hash)
|
||||
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 {
|
||||
@ -43,12 +43,13 @@ func (q *Queries) CreateUserAccount(ctx context.Context, arg CreateUserAccountPa
|
||||
&i.Email,
|
||||
&i.Username,
|
||||
&i.PasswordHash,
|
||||
&i.ProfileBgColor,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -68,6 +69,7 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
|
||||
&i.Email,
|
||||
&i.Username,
|
||||
&i.PasswordHash,
|
||||
&i.ProfileBgColor,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -83,7 +85,7 @@ func (q *Queries) GetAllUserAccounts(ctx context.Context) ([]UserAccount, error)
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -97,12 +99,13 @@ func (q *Queries) GetUserAccountByID(ctx context.Context, userID uuid.UUID) (Use
|
||||
&i.Email,
|
||||
&i.Username,
|
||||
&i.PasswordHash,
|
||||
&i.ProfileBgColor,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -116,6 +119,7 @@ func (q *Queries) GetUserAccountByUsername(ctx context.Context, username string)
|
||||
&i.Email,
|
||||
&i.Username,
|
||||
&i.PasswordHash,
|
||||
&i.ProfileBgColor,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
9
api/query/project_label.sql
Normal file
9
api/query/project_label.sql
Normal 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;
|
@ -4,3 +4,6 @@ INSERT INTO task_assigned (task_id, user_id, assigned_date)
|
||||
|
||||
-- name: GetAssignedMembersForTask :many
|
||||
SELECT * FROM task_assigned WHERE task_id = $1;
|
||||
|
||||
-- name: DeleteTaskAssignedByID :one
|
||||
DELETE FROM task_assigned WHERE task_id = $1 AND user_id = $2 RETURNING *;
|
||||
|
@ -1,5 +1,5 @@
|
||||
-- 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 *;
|
||||
|
||||
-- name: GetTaskLabelsForTaskID :many
|
||||
|
@ -41,13 +41,27 @@ const GlobalTopNavbar: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<TopNavbar
|
||||
bgColor={data ? data.me.profileIcon.bgColor ?? '#7367F0' : '#7367F0'}
|
||||
firstName={data ? data.me.firstName : ''}
|
||||
lastName={data ? data.me.lastName : ''}
|
||||
initials={!data ? '' : data.me.profileIcon.initials ?? ''}
|
||||
onNotificationClick={() => console.log('beep')}
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import TaskDetails from 'shared/components/TaskDetails';
|
||||
import PopupMenu from 'shared/components/PopupMenu';
|
||||
import MemberManager from 'shared/components/MemberManager';
|
||||
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';
|
||||
|
||||
type DetailsProps = {
|
||||
@ -15,6 +15,7 @@ type DetailsProps = {
|
||||
onDeleteTask: (task: Task) => void;
|
||||
onOpenAddLabelPopup: (task: Task, bounds: ElementBounds) => void;
|
||||
availableMembers: Array<TaskUser>;
|
||||
refreshCache: () => void;
|
||||
};
|
||||
|
||||
const initialMemberPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
|
||||
@ -27,13 +28,25 @@ const Details: React.FC<DetailsProps> = ({
|
||||
onDeleteTask,
|
||||
onOpenAddLabelPopup,
|
||||
availableMembers,
|
||||
refreshCache,
|
||||
}) => {
|
||||
const { userID } = useContext(UserIDContext);
|
||||
const history = useHistory();
|
||||
const match = useRouteMatch();
|
||||
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
|
||||
const { loading, data } = useFindTaskQuery({ variables: { taskID } });
|
||||
const [assignTask] = useAssignTaskMutation();
|
||||
const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
|
||||
const [assignTask] = useAssignTaskMutation({
|
||||
onCompleted: () => {
|
||||
refetch();
|
||||
refreshCache();
|
||||
},
|
||||
});
|
||||
const [unassignTask] = useUnassignTaskMutation({
|
||||
onCompleted: () => {
|
||||
refetch();
|
||||
refreshCache();
|
||||
},
|
||||
});
|
||||
if (loading) {
|
||||
return <div>loading</div>;
|
||||
}
|
||||
@ -47,6 +60,7 @@ const Details: React.FC<DetailsProps> = ({
|
||||
profileIcon: {
|
||||
url: null,
|
||||
initials: assigned.profileIcon.initials ?? null,
|
||||
bgColor: assigned.profileIcon.bgColor ?? null,
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -93,10 +107,13 @@ const Details: React.FC<DetailsProps> = ({
|
||||
>
|
||||
<MemberManager
|
||||
availableMembers={availableMembers}
|
||||
activeMembers={[]}
|
||||
activeMembers={taskMembers}
|
||||
onMemberChange={(member, isActive) => {
|
||||
console.log(`is active ${member.userID} - ${isActive}`);
|
||||
if (isActive) {
|
||||
assignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } });
|
||||
} else {
|
||||
unassignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } });
|
||||
}
|
||||
console.log(member, isActive);
|
||||
}}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Board = styled.div`
|
||||
margin-left: 36px;
|
||||
margin-top: 12px;
|
||||
margin-left: 8px;
|
||||
`;
|
||||
|
@ -109,9 +109,10 @@ const Project = () => {
|
||||
);
|
||||
},
|
||||
});
|
||||
const { loading, data } = useFindProjectQuery({
|
||||
const { loading, data, refetch } = useFindProjectQuery({
|
||||
variables: { projectId },
|
||||
onCompleted: newData => {
|
||||
console.log('beep!');
|
||||
const newListsData: BoardState = { tasks: {}, columns: {} };
|
||||
newData.findProject.taskGroups.forEach(taskGroup => {
|
||||
newListsData.columns[taskGroup.taskGroupID] = {
|
||||
@ -121,15 +122,27 @@ const Project = () => {
|
||||
tasks: [],
|
||||
};
|
||||
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] = {
|
||||
taskID: task.taskID,
|
||||
taskGroup: {
|
||||
taskGroupID: taskGroup.taskGroupID,
|
||||
},
|
||||
name: task.name,
|
||||
position: task.position,
|
||||
labels: [],
|
||||
position: task.position,
|
||||
description: task.description ?? undefined,
|
||||
members: taskMembers,
|
||||
};
|
||||
});
|
||||
});
|
||||
@ -196,15 +209,16 @@ const Project = () => {
|
||||
const availableMembers = data.findProject.members.map(member => {
|
||||
return {
|
||||
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,
|
||||
};
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<TitleWrapper>
|
||||
<Title>{data.findProject.name}</Title>
|
||||
</TitleWrapper>
|
||||
<KanbanBoard
|
||||
listsData={listsData}
|
||||
onCardDrop={onCardDrop}
|
||||
@ -253,6 +267,10 @@ const Project = () => {
|
||||
path={`${match.path}/c/:taskID`}
|
||||
render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
|
||||
<Details
|
||||
refreshCache={() => {
|
||||
console.log('beep 2!');
|
||||
refetch();
|
||||
}}
|
||||
availableMembers={availableMembers}
|
||||
projectURL={match.url}
|
||||
taskID={routeProps.match.params.taskID}
|
||||
|
1
web/src/citadel.d.ts
vendored
1
web/src/citadel.d.ts
vendored
@ -37,6 +37,7 @@ type InnerTaskGroup = {
|
||||
type ProfileIcon = {
|
||||
url: string | null;
|
||||
initials: string | null;
|
||||
bgColor: string | null;
|
||||
};
|
||||
|
||||
type TaskUser = {
|
||||
|
@ -98,9 +98,6 @@ export const ListCardOperation = styled.span`
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
background-color: ${props => mixin.darken('#262c49', 0.15)};
|
||||
background-clip: padding-box;
|
||||
background-origin: padding-box;
|
||||
border-radius: 3px;
|
||||
opacity: 0.8;
|
||||
padding: 6px;
|
||||
@ -108,6 +105,10 @@ export const ListCardOperation = styled.span`
|
||||
right: 2px;
|
||||
top: 2px;
|
||||
z-index: 10;
|
||||
|
||||
&:hover {
|
||||
background-color: ${props => mixin.darken('#262c49', 0.45)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const CardTitle = styled.span`
|
||||
@ -120,3 +121,34 @@ export const CardTitle = styled.span`
|
||||
word-wrap: break-word;
|
||||
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;
|
||||
`;
|
||||
|
@ -18,6 +18,9 @@ import {
|
||||
ListCardLabel,
|
||||
ListCardOperation,
|
||||
CardTitle,
|
||||
CardMembers,
|
||||
CardMember,
|
||||
CardMemberInitials,
|
||||
} from './Styles';
|
||||
|
||||
type DueDate = {
|
||||
@ -42,6 +45,7 @@ type Props = {
|
||||
watched?: boolean;
|
||||
labels?: Label[];
|
||||
wrapperProps?: any;
|
||||
members?: Array<TaskUser> | null;
|
||||
};
|
||||
|
||||
const Card = React.forwardRef(
|
||||
@ -58,6 +62,7 @@ const Card = React.forwardRef(
|
||||
description,
|
||||
checklists,
|
||||
watched,
|
||||
members,
|
||||
}: Props,
|
||||
$cardRef: any,
|
||||
) => {
|
||||
@ -95,9 +100,11 @@ const Card = React.forwardRef(
|
||||
{...wrapperProps}
|
||||
>
|
||||
<ListCardInnerContainer ref={$innerCardRef}>
|
||||
<ListCardOperation>
|
||||
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
|
||||
</ListCardOperation>
|
||||
{isActive && (
|
||||
<ListCardOperation>
|
||||
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
|
||||
</ListCardOperation>
|
||||
)}
|
||||
<ListCardDetails>
|
||||
<ListCardLabels>
|
||||
{labels &&
|
||||
@ -132,6 +139,14 @@ const Card = React.forwardRef(
|
||||
</ListCardBadge>
|
||||
)}
|
||||
</ListCardBadges>
|
||||
<CardMembers>
|
||||
{members &&
|
||||
members.map(member => (
|
||||
<CardMember key={member.userID} bgColor={member.profileIcon.bgColor ?? '#7367F0'}>
|
||||
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
|
||||
</CardMember>
|
||||
))}
|
||||
</CardMembers>
|
||||
</ListCardDetails>
|
||||
</ListCardInnerContainer>
|
||||
</ListCardContainer>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { createRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import DropdownMenu from '.';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import DropdownMenu from '.';
|
||||
|
||||
export default {
|
||||
component: DropdownMenu,
|
||||
@ -50,7 +49,16 @@ export const Default = () => {
|
||||
Click me
|
||||
</Button>
|
||||
</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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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 { Separator, Container, WrapperDiamond, Wrapper, ActionsList, ActionItem, ActionTitle } from './Styles';
|
||||
|
||||
@ -7,11 +7,14 @@ type DropdownMenuProps = {
|
||||
left: number;
|
||||
top: number;
|
||||
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 (
|
||||
<Container left={left} top={top}>
|
||||
<Container ref={$containerRef} left={left} top={top}>
|
||||
<Wrapper>
|
||||
<ActionItem>
|
||||
<User size={16} color="#c2c6dc" />
|
||||
|
@ -23,7 +23,9 @@ export const Default = () => {
|
||||
position: 1,
|
||||
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||
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')}
|
||||
onDueDateChange={action('due date change')}
|
||||
|
@ -159,6 +159,7 @@ const Lists: React.FC<Props> = ({
|
||||
description=""
|
||||
title={task.name}
|
||||
labels={task.labels}
|
||||
members={task.members}
|
||||
onClick={() => onCardClick(task)}
|
||||
onContextMenu={onQuickEditorOpen}
|
||||
/>
|
||||
|
50
web/src/shared/components/MiniProfile/Styles.ts
Normal file
50
web/src/shared/components/MiniProfile/Styles.ts
Normal 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;
|
||||
`;
|
26
web/src/shared/components/MiniProfile/index.tsx
Normal file
26
web/src/shared/components/MiniProfile/index.tsx
Normal 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;
|
@ -17,13 +17,12 @@ export const ClickableOverlay = styled.div`
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 50px;
|
||||
`;
|
||||
|
||||
export const StyledModal = styled.div<{ width: number }>`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin: 48px 0 80px;
|
||||
width: 100%;
|
||||
background: #262c49;
|
||||
max-width: ${props => props.width}px;
|
||||
|
@ -1,32 +1,14 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
export const LogoWrapper = 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 Logo = styled.div``;
|
||||
|
||||
export const LogoTitle = styled.div`
|
||||
position: relative;
|
||||
right: 12px;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
transition: right 0.1s ease 0s, visibility, opacity, transform 0.25s ease;
|
||||
transition: visibility, opacity, transform 0.25s ease;
|
||||
color: #7367f0;
|
||||
`;
|
||||
export const ActionContainer = styled.div`
|
||||
@ -54,6 +36,10 @@ export const IconWrapper = styled.div`
|
||||
export const ActionButtonContainer = styled.div`
|
||||
padding: 0 12px;
|
||||
position: relative;
|
||||
|
||||
& > a:first-child > div {
|
||||
padding-top: 48px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ActionButtonWrapper = styled.div<{ active?: boolean }>`
|
||||
@ -65,7 +51,7 @@ export const ActionButtonWrapper = styled.div<{ active?: boolean }>`
|
||||
`}
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
padding: 10px 15px;
|
||||
padding: 24px 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&: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`
|
||||
z-index: 100;
|
||||
position: fixed;
|
||||
@ -87,13 +87,15 @@ export const Container = styled.aside`
|
||||
transform: translateZ(0px);
|
||||
background: #10163a;
|
||||
transition: all 0.1s ease 0s;
|
||||
border-right: 1px solid rgba(65, 69, 97, 0.65);
|
||||
|
||||
&:hover {
|
||||
width: 260px;
|
||||
box-shadow: rgba(0, 0, 0, 0.6) 0px 0px 50px 0px;
|
||||
border-right: 1px solid rgba(65, 69, 97, 0);
|
||||
}
|
||||
&:hover ${LogoTitle} {
|
||||
right: 0px;
|
||||
bottom: -12px;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
@ -102,4 +104,8 @@ export const Container = styled.aside`
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover ${LogoWrapper} {
|
||||
border-bottom: 1px solid rgba(65, 69, 97, 0);
|
||||
}
|
||||
`;
|
||||
|
@ -35,9 +35,7 @@ export const ButtonContainer: React.FC = ({ children }) => (
|
||||
export const PrimaryLogo = () => {
|
||||
return (
|
||||
<LogoWrapper>
|
||||
<Logo>
|
||||
<Citadel size={42} />
|
||||
</Logo>
|
||||
<Citadel size={42} />
|
||||
<LogoTitle>Citadel</LogoTitle>
|
||||
</LogoWrapper>
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
|
||||
import ListActions from 'shared/components/ListActions';
|
||||
import MemberManager from 'shared/components/MemberManager';
|
||||
import DueDateManager from 'shared/components/DueDateManager';
|
||||
import MiniProfile from 'shared/components/MiniProfile';
|
||||
|
||||
import PopupMenu from '.';
|
||||
import NormalizeStyles from 'App/NormalizeStyles';
|
||||
@ -115,7 +116,7 @@ export const MemberManagerPopup = () => {
|
||||
<PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
|
||||
<MemberManager
|
||||
availableMembers={[
|
||||
{ userID: '1', displayName: 'Jordan Knott', profileIcon: { url: null, initials: null } },
|
||||
{ userID: '1', displayName: 'Jordan Knott', profileIcon: { bgColor: null, url: null, initials: null } },
|
||||
]}
|
||||
activeMembers={[]}
|
||||
onMemberChange={action('member change')}
|
||||
@ -158,7 +159,9 @@ export const DueDateManagerPopup = () => {
|
||||
position: 1,
|
||||
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||
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')}
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -281,3 +281,9 @@ export const NoDueDateLabel = styled.span`
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const UnassignedLabel = styled.div`
|
||||
color: rgb(137, 147, 164);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
@ -35,7 +35,13 @@ export const Default = () => {
|
||||
position: 1,
|
||||
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||
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')}
|
||||
onTaskDescriptionChange={(_task, desc) => setDescription(desc)}
|
||||
|
@ -4,6 +4,7 @@ import useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
||||
|
||||
import {
|
||||
NoDueDateLabel,
|
||||
UnassignedLabel,
|
||||
TaskDetailsAddMember,
|
||||
TaskGroupLabel,
|
||||
TaskGroupLabelName,
|
||||
@ -126,11 +127,16 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
onTaskNameChange(task, taskName);
|
||||
}
|
||||
};
|
||||
const $unassignedRef = useRef<HTMLDivElement>(null);
|
||||
const $addMemberRef = useRef<HTMLDivElement>(null);
|
||||
const onUnassignedClick = () => {
|
||||
const bounds = convertDivElementRefToBounds($unassignedRef);
|
||||
if (bounds) {
|
||||
onOpenAddMemberPopup(task, bounds);
|
||||
}
|
||||
};
|
||||
const onAddMember = () => {
|
||||
console.log('beep!');
|
||||
const bounds = convertDivElementRefToBounds($addMemberRef);
|
||||
console.log(bounds);
|
||||
if (bounds) {
|
||||
onOpenAddMemberPopup(task, bounds);
|
||||
}
|
||||
@ -191,20 +197,28 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
<TaskDetailsSidebar>
|
||||
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
|
||||
<TaskDetailAssignees>
|
||||
{task.members &&
|
||||
task.members.map(member => {
|
||||
console.log(member);
|
||||
return (
|
||||
<TaskDetailAssignee key={member.userID}>
|
||||
<ProfileIcon>{member.profileIcon.initials ?? ''}</ProfileIcon>
|
||||
</TaskDetailAssignee>
|
||||
);
|
||||
})}
|
||||
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}>
|
||||
<TaskDetailsAddMemberIcon>
|
||||
<Plus size={16} color="#c2c6dc" />
|
||||
</TaskDetailsAddMemberIcon>
|
||||
</TaskDetailsAddMember>
|
||||
{task.members && task.members.length === 0 ? (
|
||||
<UnassignedLabel ref={$unassignedRef} onClick={onUnassignedClick}>
|
||||
Unassigned
|
||||
</UnassignedLabel>
|
||||
) : (
|
||||
<>
|
||||
{task.members &&
|
||||
task.members.map(member => {
|
||||
console.log(member);
|
||||
return (
|
||||
<TaskDetailAssignee key={member.userID}>
|
||||
<ProfileIcon>{member.profileIcon.initials ?? ''}</ProfileIcon>
|
||||
</TaskDetailAssignee>
|
||||
);
|
||||
})}
|
||||
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}>
|
||||
<TaskDetailsAddMemberIcon>
|
||||
<Plus size={16} color="#c2c6dc" />
|
||||
</TaskDetailsAddMemberIcon>
|
||||
</TaskDetailsAddMember>
|
||||
</>
|
||||
)}
|
||||
</TaskDetailAssignees>
|
||||
<TaskDetailSectionTitle>Labels</TaskDetailSectionTitle>
|
||||
<TaskDetailLabels>
|
||||
|
@ -1,19 +1,18 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const NavbarWrapper = styled.div`
|
||||
height: 103px;
|
||||
padding: 1.3rem 2.2rem 2.2rem;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const NavbarHeader = styled.header`
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.8rem 1rem;
|
||||
height: 80px;
|
||||
padding: 0 1.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: rgb(16, 22, 58);
|
||||
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`
|
||||
color: rgb(94, 108, 132);
|
||||
@ -26,7 +25,14 @@ export const BreadcrumpSeparator = styled.span`
|
||||
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`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -55,7 +61,7 @@ export const ProfileNameSecondary = styled.small`
|
||||
color: #c2c6dc;
|
||||
`;
|
||||
|
||||
export const ProfileIcon = styled.div`
|
||||
export const ProfileIcon = styled.div<{ bgColor: string }>`
|
||||
margin-left: 10px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
@ -65,6 +71,48 @@ export const ProfileIcon = styled.div`
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
background: rgb(115, 103, 240);
|
||||
background: ${props => props.bgColor};
|
||||
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;
|
||||
`;
|
||||
|
@ -38,13 +38,23 @@ export const Default = () => {
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
<TopNavbar
|
||||
bgColor="#7367F0"
|
||||
firstName="Jordan"
|
||||
lastName="Knott"
|
||||
initials="JK"
|
||||
onNotificationClick={action('notifications click')}
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -5,6 +5,10 @@ import {
|
||||
NotificationContainer,
|
||||
GlobalActions,
|
||||
ProjectActions,
|
||||
ProjectMeta,
|
||||
ProjectName,
|
||||
ProjectTabs,
|
||||
ProjectTab,
|
||||
NavbarWrapper,
|
||||
NavbarHeader,
|
||||
Breadcrumbs,
|
||||
@ -19,11 +23,19 @@ import {
|
||||
type NavBarProps = {
|
||||
onProfileClick: (bottom: number, right: number) => void;
|
||||
onNotificationClick: () => void;
|
||||
bgColor: string;
|
||||
firstName: string;
|
||||
lastName: 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 handleProfileClick = () => {
|
||||
console.log('click');
|
||||
@ -34,13 +46,12 @@ const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick, fi
|
||||
<NavbarWrapper>
|
||||
<NavbarHeader>
|
||||
<ProjectActions>
|
||||
<Breadcrumbs>
|
||||
Projects
|
||||
<BreadcrumpSeparator>/</BreadcrumpSeparator>
|
||||
project name
|
||||
<BreadcrumpSeparator>/</BreadcrumpSeparator>
|
||||
Board
|
||||
</Breadcrumbs>
|
||||
<ProjectMeta>
|
||||
<ProjectName>Production Team</ProjectName>
|
||||
</ProjectMeta>
|
||||
<ProjectTabs>
|
||||
<ProjectTab>Board</ProjectTab>
|
||||
</ProjectTabs>
|
||||
</ProjectActions>
|
||||
<GlobalActions>
|
||||
<NotificationContainer onClick={onNotificationClick}>
|
||||
@ -53,7 +64,7 @@ const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick, fi
|
||||
</ProfileNamePrimary>
|
||||
<ProfileNameSecondary>Manager</ProfileNameSecondary>
|
||||
</ProfileNameWrapper>
|
||||
<ProfileIcon ref={$profileRef} onClick={handleProfileClick}>
|
||||
<ProfileIcon ref={$profileRef} onClick={handleProfileClick} bgColor={bgColor}>
|
||||
{initials}
|
||||
</ProfileIcon>
|
||||
</ProfileContainer>
|
||||
|
@ -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 = {
|
||||
__typename?: 'TaskLabel';
|
||||
taskLabelID: Scalars['ID'];
|
||||
labelColorID: Scalars['UUID'];
|
||||
projectLabelID: Scalars['UUID'];
|
||||
assignedDate: Scalars['Time'];
|
||||
colorHex: Scalars['String'];
|
||||
name?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ProfileIcon = {
|
||||
__typename?: 'ProfileIcon';
|
||||
url?: Maybe<Scalars['String']>;
|
||||
initials?: Maybe<Scalars['String']>;
|
||||
bgColor?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ProjectMember = {
|
||||
@ -71,6 +82,7 @@ export type Project = {
|
||||
owner: ProjectMember;
|
||||
taskGroups: Array<TaskGroup>;
|
||||
members: Array<ProjectMember>;
|
||||
labels: Array<ProjectLabel>;
|
||||
};
|
||||
|
||||
export type TaskGroup = {
|
||||
@ -222,6 +234,11 @@ export type AssignTaskInput = {
|
||||
userID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type UnassignTaskInput = {
|
||||
taskID: Scalars['UUID'];
|
||||
userID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type UpdateTaskDescriptionInput = {
|
||||
taskID: Scalars['UUID'];
|
||||
description: Scalars['String'];
|
||||
@ -237,12 +254,19 @@ export type RemoveTaskLabelInput = {
|
||||
taskLabelID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type NewProjectLabel = {
|
||||
projectID: Scalars['UUID'];
|
||||
labelColorID: Scalars['UUID'];
|
||||
name?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
createRefreshToken: RefreshToken;
|
||||
createUserAccount: UserAccount;
|
||||
createTeam: Team;
|
||||
createProject: Project;
|
||||
createProjectLabel: ProjectLabel;
|
||||
createTaskGroup: TaskGroup;
|
||||
updateTaskGroupLocation: TaskGroup;
|
||||
deleteTaskGroup: DeleteTaskGroupPayload;
|
||||
@ -254,6 +278,7 @@ export type Mutation = {
|
||||
updateTaskName: Task;
|
||||
deleteTask: DeleteTaskPayload;
|
||||
assignTask: Task;
|
||||
unassignTask: Task;
|
||||
logoutUser: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
@ -278,6 +303,11 @@ export type MutationCreateProjectArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateProjectLabelArgs = {
|
||||
input: NewProjectLabel;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateTaskGroupArgs = {
|
||||
input: NewTaskGroup;
|
||||
};
|
||||
@ -333,6 +363,11 @@ export type MutationAssignTaskArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUnassignTaskArgs = {
|
||||
input?: Maybe<UnassignTaskInput>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationLogoutUserArgs = {
|
||||
input: LogoutUser;
|
||||
};
|
||||
@ -438,7 +473,7 @@ export type FindProjectQuery = (
|
||||
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
|
||||
& { profileIcon: (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'url' | 'initials'>
|
||||
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
|
||||
) }
|
||||
)>, taskGroups: Array<(
|
||||
{ __typename?: 'TaskGroup' }
|
||||
@ -446,6 +481,14 @@ export type FindProjectQuery = (
|
||||
& { tasks: Array<(
|
||||
{ __typename?: 'Task' }
|
||||
& 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'>
|
||||
& { profileIcon: (
|
||||
{ __typename?: 'ProfileIcon' }
|
||||
& Pick<ProfileIcon, 'url' | 'initials'>
|
||||
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
|
||||
) }
|
||||
)> }
|
||||
) }
|
||||
@ -500,11 +543,29 @@ export type MeQuery = (
|
||||
& Pick<UserAccount, 'firstName' | 'lastName'>
|
||||
& { 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 = {
|
||||
taskID: Scalars['UUID'];
|
||||
description: Scalars['String'];
|
||||
@ -759,6 +820,7 @@ export const FindProjectDocument = gql`
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
taskGroups {
|
||||
@ -770,6 +832,16 @@ export const FindProjectDocument = gql`
|
||||
name
|
||||
position
|
||||
description
|
||||
assigned {
|
||||
userID
|
||||
firstName
|
||||
lastName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -818,6 +890,7 @@ export const FindTaskDocument = gql`
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -893,6 +966,7 @@ export const MeDocument = gql`
|
||||
lastName
|
||||
profileIcon {
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -922,6 +996,44 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
|
||||
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
|
||||
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
|
||||
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
|
||||
export const 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`
|
||||
mutation updateTaskDescription($taskID: UUID!, $description: String!) {
|
||||
updateTaskDescription(input: {taskID: $taskID, description: $description}) {
|
||||
|
@ -8,6 +8,7 @@ query findProject($projectId: String!) {
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
taskGroups {
|
||||
@ -19,6 +20,16 @@ query findProject($projectId: String!) {
|
||||
name
|
||||
position
|
||||
description
|
||||
assigned {
|
||||
userID
|
||||
firstName
|
||||
lastName
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ query findTask($taskID: UUID!) {
|
||||
profileIcon {
|
||||
url
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ query me {
|
||||
lastName
|
||||
profileIcon {
|
||||
initials
|
||||
bgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
web/src/shared/graphql/unassignTask.graphqls
Normal file
10
web/src/shared/graphql/unassignTask.graphqls
Normal file
@ -0,0 +1,10 @@
|
||||
mutation unassignTask($taskID: UUID!, $userID: UUID!) {
|
||||
unassignTask(input: {taskID: $taskID, userID: $userID}) {
|
||||
assigned {
|
||||
userID
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
taskID
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user