feature: add checklist

This commit is contained in:
Jordan Knott 2020-06-18 18:12:15 -05:00
parent b6f0e8b6b2
commit 9d6c67f791
73 changed files with 4582 additions and 390 deletions

File diff suppressed because it is too large Load Diff

View File

@ -19,10 +19,36 @@ type AssignTaskInput struct {
UserID uuid.UUID `json:"userID"`
}
type ChecklistBadge struct {
Complete int `json:"complete"`
Total int `json:"total"`
}
type CreateTaskChecklist struct {
TaskID uuid.UUID `json:"taskID"`
Name string `json:"name"`
Position float64 `json:"position"`
}
type CreateTaskChecklistItem struct {
TaskChecklistID uuid.UUID `json:"taskChecklistID"`
Name string `json:"name"`
Position float64 `json:"position"`
}
type DeleteProjectLabel struct {
ProjectLabelID uuid.UUID `json:"projectLabelID"`
}
type DeleteTaskChecklistItem struct {
TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"`
}
type DeleteTaskChecklistItemPayload struct {
Ok bool `json:"ok"`
TaskChecklistItem *pg.TaskChecklistItem `json:"taskChecklistItem"`
}
type DeleteTaskGroupInput struct {
TaskGroupID uuid.UUID `json:"taskGroupID"`
}
@ -129,6 +155,20 @@ type RemoveTaskLabelInput struct {
TaskLabelID uuid.UUID `json:"taskLabelID"`
}
type SetTaskChecklistItemComplete struct {
TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"`
Complete bool `json:"complete"`
}
type SetTaskComplete struct {
TaskID uuid.UUID `json:"taskID"`
Complete bool `json:"complete"`
}
type TaskBadges struct {
Checklist *ChecklistBadge `json:"checklist"`
}
type ToggleTaskLabelInput struct {
TaskID uuid.UUID `json:"taskID"`
ProjectLabelID uuid.UUID `json:"projectLabelID"`
@ -165,6 +205,11 @@ type UpdateProjectName struct {
Name string `json:"name"`
}
type UpdateTaskChecklistItemName struct {
TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"`
Name string `json:"name"`
}
type UpdateTaskDescriptionInput struct {
TaskID uuid.UUID `json:"taskID"`
Description string `json:"description"`

View File

@ -77,6 +77,15 @@ type TaskGroup {
tasks: [Task!]!
}
type ChecklistBadge {
complete: Int!
total: Int!
}
type TaskBadges {
checklist: ChecklistBadge
}
type Task {
id: ID!
taskGroup: TaskGroup!
@ -85,8 +94,11 @@ type Task {
position: Float!
description: String
dueDate: Time
complete: Boolean!
assigned: [ProjectMember!]!
labels: [TaskLabel!]!
checklists: [TaskChecklist!]!
badges: TaskBadges!
}
input ProjectsFilter {
@ -188,6 +200,27 @@ type DeleteTaskGroupPayload {
taskGroup: TaskGroup!
}
type DeleteTaskChecklistItemPayload {
ok: Boolean!
taskChecklistItem: TaskChecklistItem!
}
type TaskChecklistItem {
id: ID!
name: String!
taskChecklistID: UUID!
complete: Boolean!
position: Float!
dueDate: Time!
}
type TaskChecklist {
id: ID!
name: String!
position: Float!
items: [TaskChecklistItem!]!
}
input AssignTaskInput {
taskID: UUID!
userID: UUID!
@ -267,6 +300,36 @@ input UpdateTaskDueDate {
dueDate: Time
}
input SetTaskComplete {
taskID: UUID!
complete: Boolean!
}
input CreateTaskChecklist {
taskID: UUID!
name: String!
position: Float!
}
input CreateTaskChecklistItem {
taskChecklistID: UUID!
name: String!
position: Float!
}
input SetTaskChecklistItemComplete {
taskChecklistItemID: UUID!
complete: Boolean!
}
input DeleteTaskChecklistItem {
taskChecklistItemID: UUID!
}
input UpdateTaskChecklistItemName {
taskChecklistItemID: UUID!
name: String!
}
type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken!
@ -293,10 +356,17 @@ type Mutation {
removeTaskLabel(input: RemoveTaskLabelInput): Task!
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
createTaskChecklist(input: CreateTaskChecklist!): TaskChecklist!
createTaskChecklistItem(input: CreateTaskChecklistItem!): TaskChecklistItem!
updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!): TaskChecklistItem!
setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!): TaskChecklistItem!
deleteTaskChecklistItem(input: DeleteTaskChecklistItem!): DeleteTaskChecklistItemPayload!
createTask(input: NewTask!): Task!
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
updateTaskName(input: UpdateTaskName!): Task!
setTaskComplete(input: SetTaskComplete!): Task!
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
assignTask(input: AssignTaskInput): Task!

View File

@ -143,7 +143,11 @@ func (r *mutationResolver) UpdateTaskGroupLocation(ctx context.Context, input Ne
}
func (r *mutationResolver) UpdateTaskGroupName(ctx context.Context, input UpdateTaskGroupName) (*pg.TaskGroup, error) {
panic(fmt.Errorf("not implemented"))
taskGroup, err := r.Repository.SetTaskGroupName(ctx, pg.SetTaskGroupNameParams{TaskGroupID: input.TaskGroupID, Name: input.Name})
if err != nil {
return &pg.TaskGroup{}, err
}
return &taskGroup, nil
}
func (r *mutationResolver) DeleteTaskGroup(ctx context.Context, input DeleteTaskGroupInput) (*DeleteTaskGroupPayload, error) {
@ -225,6 +229,75 @@ func (r *mutationResolver) ToggleTaskLabel(ctx context.Context, input ToggleTask
return &payload, nil
}
func (r *mutationResolver) CreateTaskChecklist(ctx context.Context, input CreateTaskChecklist) (*pg.TaskChecklist, error) {
createdAt := time.Now().UTC()
taskChecklist, err := r.Repository.CreateTaskChecklist(ctx, pg.CreateTaskChecklistParams{
TaskID: input.TaskID,
CreatedAt: createdAt,
Name: input.Name,
Position: input.Position,
})
if err != nil {
return &pg.TaskChecklist{}, err
}
return &taskChecklist, nil
}
func (r *mutationResolver) CreateTaskChecklistItem(ctx context.Context, input CreateTaskChecklistItem) (*pg.TaskChecklistItem, error) {
createdAt := time.Now().UTC()
taskChecklistItem, err := r.Repository.CreateTaskChecklistItem(ctx, pg.CreateTaskChecklistItemParams{
TaskChecklistID: input.TaskChecklistID,
CreatedAt: createdAt,
Name: input.Name,
Position: input.Position,
})
if err != nil {
return &pg.TaskChecklistItem{}, err
}
return &taskChecklistItem, nil
}
func (r *mutationResolver) UpdateTaskChecklistItemName(ctx context.Context, input UpdateTaskChecklistItemName) (*pg.TaskChecklistItem, error) {
task, err := r.Repository.UpdateTaskChecklistItemName(ctx, pg.UpdateTaskChecklistItemNameParams{TaskChecklistItemID: input.TaskChecklistItemID,
Name: input.Name,
})
if err != nil {
return &pg.TaskChecklistItem{}, err
}
return &task, nil
}
func (r *mutationResolver) SetTaskChecklistItemComplete(ctx context.Context, input SetTaskChecklistItemComplete) (*pg.TaskChecklistItem, error) {
item, err := r.Repository.SetTaskChecklistItemComplete(ctx, pg.SetTaskChecklistItemCompleteParams{TaskChecklistItemID: input.TaskChecklistItemID, Complete: input.Complete})
if err != nil {
return &pg.TaskChecklistItem{}, err
}
return &item, nil
}
func (r *mutationResolver) DeleteTaskChecklistItem(ctx context.Context, input DeleteTaskChecklistItem) (*DeleteTaskChecklistItemPayload, error) {
item, err := r.Repository.GetTaskChecklistItemByID(ctx, input.TaskChecklistItemID)
if err != nil {
return &DeleteTaskChecklistItemPayload{
Ok: false,
TaskChecklistItem: &pg.TaskChecklistItem{},
}, err
}
err = r.Repository.DeleteTaskChecklistItem(ctx, input.TaskChecklistItemID)
if err != nil {
return &DeleteTaskChecklistItemPayload{
Ok: false,
TaskChecklistItem: &pg.TaskChecklistItem{},
}, err
}
return &DeleteTaskChecklistItemPayload{
Ok: true,
TaskChecklistItem: &item,
}, err
}
func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*pg.Task, error) {
taskGroupID, err := uuid.Parse(input.TaskGroupID)
createdAt := time.Now().UTC()
@ -260,6 +333,14 @@ func (r *mutationResolver) UpdateTaskName(ctx context.Context, input UpdateTaskN
return &task, err
}
func (r *mutationResolver) SetTaskComplete(ctx context.Context, input SetTaskComplete) (*pg.Task, error) {
task, err := r.Repository.SetTaskComplete(ctx, pg.SetTaskCompleteParams{TaskID: input.TaskID, Complete: input.Complete})
if err != nil {
return &pg.Task{}, err
}
return &task, nil
}
func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTaskDueDate) (*pg.Task, error) {
var dueDate sql.NullTime
if input.DueDate == nil {
@ -531,6 +612,51 @@ func (r *taskResolver) Labels(ctx context.Context, obj *pg.Task) ([]pg.TaskLabel
return r.Repository.GetTaskLabelsForTaskID(ctx, obj.TaskID)
}
func (r *taskResolver) Checklists(ctx context.Context, obj *pg.Task) ([]pg.TaskChecklist, error) {
return r.Repository.GetTaskChecklistsForTask(ctx, obj.TaskID)
}
func (r *taskResolver) Badges(ctx context.Context, obj *pg.Task) (*TaskBadges, error) {
checklists, err := r.Repository.GetTaskChecklistsForTask(ctx, obj.TaskID)
if err != nil {
return &TaskBadges{}, err
}
if len(checklists) == 0 {
return &TaskBadges{Checklist: nil}, err
}
complete := 0
total := 0
for _, checklist := range checklists {
items, err := r.Repository.GetTaskChecklistItemsForTaskChecklist(ctx, checklist.TaskChecklistID)
if err != nil {
return &TaskBadges{}, err
}
for _, item := range items {
total += 1
if item.Complete {
complete += 1
}
}
}
return &TaskBadges{Checklist: &ChecklistBadge{Total: total, Complete: complete}}, nil
}
func (r *taskChecklistResolver) ID(ctx context.Context, obj *pg.TaskChecklist) (uuid.UUID, error) {
return obj.TaskChecklistID, nil
}
func (r *taskChecklistResolver) Items(ctx context.Context, obj *pg.TaskChecklist) ([]pg.TaskChecklistItem, error) {
return r.Repository.GetTaskChecklistItemsForTaskChecklist(ctx, obj.TaskChecklistID)
}
func (r *taskChecklistItemResolver) ID(ctx context.Context, obj *pg.TaskChecklistItem) (uuid.UUID, error) {
return obj.TaskChecklistItemID, nil
}
func (r *taskChecklistItemResolver) DueDate(ctx context.Context, obj *pg.TaskChecklistItem) (*time.Time, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *taskGroupResolver) ID(ctx context.Context, obj *pg.TaskGroup) (uuid.UUID, error) {
return obj.TaskGroupID, nil
}
@ -591,6 +717,12 @@ func (r *Resolver) RefreshToken() RefreshTokenResolver { return &refreshTokenRes
// Task returns TaskResolver implementation.
func (r *Resolver) Task() TaskResolver { return &taskResolver{r} }
// TaskChecklist returns TaskChecklistResolver implementation.
func (r *Resolver) TaskChecklist() TaskChecklistResolver { return &taskChecklistResolver{r} }
// TaskChecklistItem returns TaskChecklistItemResolver implementation.
func (r *Resolver) TaskChecklistItem() TaskChecklistItemResolver { return &taskChecklistItemResolver{r} }
// TaskGroup returns TaskGroupResolver implementation.
func (r *Resolver) TaskGroup() TaskGroupResolver { return &taskGroupResolver{r} }
@ -610,6 +742,8 @@ type projectLabelResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type refreshTokenResolver struct{ *Resolver }
type taskResolver struct{ *Resolver }
type taskChecklistResolver struct{ *Resolver }
type taskChecklistItemResolver struct{ *Resolver }
type taskGroupResolver struct{ *Resolver }
type taskLabelResolver struct{ *Resolver }
type teamResolver struct{ *Resolver }

View File

@ -0,0 +1,6 @@
ALTER TABLE task_label DROP CONSTRAINT task_label_task_id_fkey;
ALTER TABLE task_label
ADD CONSTRAINT task_label_task_id_fkey
FOREIGN KEY (task_id)
REFERENCES task(task_id)
ON DELETE CASCADE;

View File

@ -0,0 +1,6 @@
ALTER TABLE task_assigned DROP CONSTRAINT task_assigned_task_id_fkey;
ALTER TABLE task_assigned
ADD CONSTRAINT task_assigned_task_id_fkey
FOREIGN KEY (task_id)
REFERENCES task(task_id)
ON DELETE CASCADE;

View File

@ -0,0 +1,6 @@
ALTER TABLE task DROP CONSTRAINT task_task_group_id_fkey;
ALTER TABLE task
ADD CONSTRAINT task_task_group_id_fkey
FOREIGN KEY (task_group_id)
REFERENCES task_group(task_group_id)
ON DELETE CASCADE;

View File

@ -0,0 +1 @@
ALTER TABLE task ADD COLUMN complete boolean NOT NULL DEFAULT FALSE;

View File

@ -0,0 +1,7 @@
CREATE TABLE task_checklist (
task_checklist_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
task_id uuid NOT NULL REFERENCES task(task_id) ON DELETE CASCADE,
created_at timestamptz NOT NULL,
name text NOT NULL,
position float NOT NULL
);

View File

@ -0,0 +1,9 @@
CREATE TABLE task_checklist_item (
task_checklist_item_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
task_checklist_id uuid NOT NULL REFERENCES task_checklist(task_checklist_id) ON DELETE CASCADE,
created_at timestamptz NOT NULL,
complete boolean NOT NULL DEFAULT false,
name text NOT NULL,
position float NOT NULL,
due_date timestamptz
);

View File

@ -53,6 +53,7 @@ type Task struct {
Position float64 `json:"position"`
Description sql.NullString `json:"description"`
DueDate sql.NullTime `json:"due_date"`
Complete bool `json:"complete"`
}
type TaskAssigned struct {
@ -62,6 +63,24 @@ type TaskAssigned struct {
AssignedDate time.Time `json:"assigned_date"`
}
type TaskChecklist struct {
TaskChecklistID uuid.UUID `json:"task_checklist_id"`
TaskID uuid.UUID `json:"task_id"`
CreatedAt time.Time `json:"created_at"`
Name string `json:"name"`
Position float64 `json:"position"`
}
type TaskChecklistItem struct {
TaskChecklistItemID uuid.UUID `json:"task_checklist_item_id"`
TaskChecklistID uuid.UUID `json:"task_checklist_id"`
CreatedAt time.Time `json:"created_at"`
Complete bool `json:"complete"`
Name string `json:"name"`
Position float64 `json:"position"`
DueDate sql.NullTime `json:"due_date"`
}
type TaskGroup struct {
TaskGroupID uuid.UUID `json:"task_group_id"`
ProjectID uuid.UUID `json:"project_id"`

View File

@ -17,7 +17,17 @@ type Repository interface {
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
GetProjectByID(ctx context.Context, projectID uuid.UUID) (Project, error)
UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error)
GetTaskChecklistItemByID(ctx context.Context, taskChecklistItemID uuid.UUID) (TaskChecklistItem, error)
CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error)
CreateTaskChecklistItem(ctx context.Context, arg CreateTaskChecklistItemParams) (TaskChecklistItem, error)
GetTaskChecklistItemsForTaskChecklist(ctx context.Context, taskChecklistID uuid.UUID) ([]TaskChecklistItem, error)
GetTaskChecklistsForTask(ctx context.Context, taskID uuid.UUID) ([]TaskChecklist, error)
SetTaskChecklistItemComplete(ctx context.Context, arg SetTaskChecklistItemCompleteParams) (TaskChecklistItem, error)
DeleteTaskChecklistItem(ctx context.Context, taskChecklistItemID uuid.UUID) error
UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error)
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
@ -25,6 +35,7 @@ type Repository interface {
GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error)
SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error)
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
@ -47,6 +58,7 @@ type Repository interface {
DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)

View File

@ -16,6 +16,8 @@ type Querier interface {
CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error)
CreateTaskChecklistItem(ctx context.Context, arg CreateTaskChecklistItemParams) (TaskChecklistItem, error)
CreateTaskGroup(ctx context.Context, arg CreateTaskGroupParams) (TaskGroup, error)
CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabelForTaskParams) (TaskLabel, error)
CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error)
@ -26,6 +28,7 @@ type Querier interface {
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error
DeleteTaskChecklistItem(ctx context.Context, taskChecklistItemID uuid.UUID) error
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
@ -46,6 +49,9 @@ type Querier interface {
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)
GetTaskChecklistItemByID(ctx context.Context, taskChecklistItemID uuid.UUID) (TaskChecklistItem, error)
GetTaskChecklistItemsForTaskChecklist(ctx context.Context, taskChecklistID uuid.UUID) ([]TaskChecklistItem, error)
GetTaskChecklistsForTask(ctx context.Context, taskID uuid.UUID) ([]TaskChecklist, error)
GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error)
GetTaskGroupsForProject(ctx context.Context, projectID uuid.UUID) ([]TaskGroup, error)
GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error)
@ -56,11 +62,14 @@ type Querier interface {
GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error)
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
SetTaskChecklistItemComplete(ctx context.Context, arg SetTaskChecklistItemCompleteParams) (TaskChecklistItem, error)
SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error)
UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescriptionParams) (Task, error)
UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error)
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)

View File

@ -13,7 +13,7 @@ import (
const createTask = `-- name: CreateTask :one
INSERT INTO task (task_group_id, created_at, name, position)
VALUES($1, $2, $3, $4) RETURNING task_id, task_group_id, created_at, name, position, description, due_date
VALUES($1, $2, $3, $4) RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete
`
type CreateTaskParams struct {
@ -39,6 +39,7 @@ func (q *Queries) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, e
&i.Position,
&i.Description,
&i.DueDate,
&i.Complete,
)
return i, err
}
@ -65,7 +66,7 @@ func (q *Queries) DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid
}
const getAllTasks = `-- name: GetAllTasks :many
SELECT task_id, task_group_id, created_at, name, position, description, due_date FROM task
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete FROM task
`
func (q *Queries) GetAllTasks(ctx context.Context) ([]Task, error) {
@ -85,6 +86,7 @@ func (q *Queries) GetAllTasks(ctx context.Context) ([]Task, error) {
&i.Position,
&i.Description,
&i.DueDate,
&i.Complete,
); err != nil {
return nil, err
}
@ -100,7 +102,7 @@ func (q *Queries) GetAllTasks(ctx context.Context) ([]Task, error) {
}
const getTaskByID = `-- name: GetTaskByID :one
SELECT task_id, task_group_id, created_at, name, position, description, due_date FROM task WHERE task_id = $1
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete FROM task WHERE task_id = $1
`
func (q *Queries) GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error) {
@ -114,12 +116,13 @@ func (q *Queries) GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, erro
&i.Position,
&i.Description,
&i.DueDate,
&i.Complete,
)
return i, err
}
const getTasksForTaskGroupID = `-- name: GetTasksForTaskGroupID :many
SELECT task_id, task_group_id, created_at, name, position, description, due_date FROM task WHERE task_group_id = $1
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete FROM task WHERE task_group_id = $1
`
func (q *Queries) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error) {
@ -139,6 +142,7 @@ func (q *Queries) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.U
&i.Position,
&i.Description,
&i.DueDate,
&i.Complete,
); err != nil {
return nil, err
}
@ -153,8 +157,33 @@ func (q *Queries) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.U
return items, nil
}
const setTaskComplete = `-- name: SetTaskComplete :one
UPDATE task SET complete = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete
`
type SetTaskCompleteParams struct {
TaskID uuid.UUID `json:"task_id"`
Complete bool `json:"complete"`
}
func (q *Queries) SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error) {
row := q.db.QueryRowContext(ctx, setTaskComplete, arg.TaskID, arg.Complete)
var i Task
err := row.Scan(
&i.TaskID,
&i.TaskGroupID,
&i.CreatedAt,
&i.Name,
&i.Position,
&i.Description,
&i.DueDate,
&i.Complete,
)
return i, err
}
const updateTaskDescription = `-- name: UpdateTaskDescription :one
UPDATE task SET description = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date
UPDATE task SET description = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete
`
type UpdateTaskDescriptionParams struct {
@ -173,12 +202,13 @@ func (q *Queries) UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescr
&i.Position,
&i.Description,
&i.DueDate,
&i.Complete,
)
return i, err
}
const updateTaskDueDate = `-- name: UpdateTaskDueDate :one
UPDATE task SET due_date = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date
UPDATE task SET due_date = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete
`
type UpdateTaskDueDateParams struct {
@ -197,12 +227,13 @@ func (q *Queries) UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDatePa
&i.Position,
&i.Description,
&i.DueDate,
&i.Complete,
)
return i, err
}
const updateTaskLocation = `-- name: UpdateTaskLocation :one
UPDATE task SET task_group_id = $2, position = $3 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date
UPDATE task SET task_group_id = $2, position = $3 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete
`
type UpdateTaskLocationParams struct {
@ -222,12 +253,13 @@ func (q *Queries) UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocation
&i.Position,
&i.Description,
&i.DueDate,
&i.Complete,
)
return i, err
}
const updateTaskName = `-- name: UpdateTaskName :one
UPDATE task SET name = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date
UPDATE task SET name = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete
`
type UpdateTaskNameParams struct {
@ -246,6 +278,7 @@ func (q *Queries) UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams)
&i.Position,
&i.Description,
&i.DueDate,
&i.Complete,
)
return i, err
}

View File

@ -0,0 +1,219 @@
// Code generated by sqlc. DO NOT EDIT.
// source: task_checklist.sql
package pg
import (
"context"
"time"
"github.com/google/uuid"
)
const createTaskChecklist = `-- name: CreateTaskChecklist :one
INSERT INTO task_checklist (task_id, created_at, name, position) VALUES ($1, $2, $3, $4)
RETURNING task_checklist_id, task_id, created_at, name, position
`
type CreateTaskChecklistParams struct {
TaskID uuid.UUID `json:"task_id"`
CreatedAt time.Time `json:"created_at"`
Name string `json:"name"`
Position float64 `json:"position"`
}
func (q *Queries) CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error) {
row := q.db.QueryRowContext(ctx, createTaskChecklist,
arg.TaskID,
arg.CreatedAt,
arg.Name,
arg.Position,
)
var i TaskChecklist
err := row.Scan(
&i.TaskChecklistID,
&i.TaskID,
&i.CreatedAt,
&i.Name,
&i.Position,
)
return i, err
}
const createTaskChecklistItem = `-- name: CreateTaskChecklistItem :one
INSERT INTO task_checklist_item (task_checklist_id, created_at, name, position, complete, due_date) VALUES ($1, $2, $3, $4, false, null)
RETURNING task_checklist_item_id, task_checklist_id, created_at, complete, name, position, due_date
`
type CreateTaskChecklistItemParams struct {
TaskChecklistID uuid.UUID `json:"task_checklist_id"`
CreatedAt time.Time `json:"created_at"`
Name string `json:"name"`
Position float64 `json:"position"`
}
func (q *Queries) CreateTaskChecklistItem(ctx context.Context, arg CreateTaskChecklistItemParams) (TaskChecklistItem, error) {
row := q.db.QueryRowContext(ctx, createTaskChecklistItem,
arg.TaskChecklistID,
arg.CreatedAt,
arg.Name,
arg.Position,
)
var i TaskChecklistItem
err := row.Scan(
&i.TaskChecklistItemID,
&i.TaskChecklistID,
&i.CreatedAt,
&i.Complete,
&i.Name,
&i.Position,
&i.DueDate,
)
return i, err
}
const deleteTaskChecklistItem = `-- name: DeleteTaskChecklistItem :exec
DELETE FROM task_checklist_item WHERE task_checklist_item_id = $1
`
func (q *Queries) DeleteTaskChecklistItem(ctx context.Context, taskChecklistItemID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteTaskChecklistItem, taskChecklistItemID)
return err
}
const getTaskChecklistItemByID = `-- name: GetTaskChecklistItemByID :one
SELECT task_checklist_item_id, task_checklist_id, created_at, complete, name, position, due_date FROM task_checklist_item WHERE task_checklist_item_id = $1
`
func (q *Queries) GetTaskChecklistItemByID(ctx context.Context, taskChecklistItemID uuid.UUID) (TaskChecklistItem, error) {
row := q.db.QueryRowContext(ctx, getTaskChecklistItemByID, taskChecklistItemID)
var i TaskChecklistItem
err := row.Scan(
&i.TaskChecklistItemID,
&i.TaskChecklistID,
&i.CreatedAt,
&i.Complete,
&i.Name,
&i.Position,
&i.DueDate,
)
return i, err
}
const getTaskChecklistItemsForTaskChecklist = `-- name: GetTaskChecklistItemsForTaskChecklist :many
SELECT task_checklist_item_id, task_checklist_id, created_at, complete, name, position, due_date FROM task_checklist_item WHERE task_checklist_id = $1
`
func (q *Queries) GetTaskChecklistItemsForTaskChecklist(ctx context.Context, taskChecklistID uuid.UUID) ([]TaskChecklistItem, error) {
rows, err := q.db.QueryContext(ctx, getTaskChecklistItemsForTaskChecklist, taskChecklistID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TaskChecklistItem
for rows.Next() {
var i TaskChecklistItem
if err := rows.Scan(
&i.TaskChecklistItemID,
&i.TaskChecklistID,
&i.CreatedAt,
&i.Complete,
&i.Name,
&i.Position,
&i.DueDate,
); 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
}
const getTaskChecklistsForTask = `-- name: GetTaskChecklistsForTask :many
SELECT task_checklist_id, task_id, created_at, name, position FROM task_checklist WHERE task_id = $1
`
func (q *Queries) GetTaskChecklistsForTask(ctx context.Context, taskID uuid.UUID) ([]TaskChecklist, error) {
rows, err := q.db.QueryContext(ctx, getTaskChecklistsForTask, taskID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TaskChecklist
for rows.Next() {
var i TaskChecklist
if err := rows.Scan(
&i.TaskChecklistID,
&i.TaskID,
&i.CreatedAt,
&i.Name,
&i.Position,
); 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
}
const setTaskChecklistItemComplete = `-- name: SetTaskChecklistItemComplete :one
UPDATE task_checklist_item SET complete = $2 WHERE task_checklist_item_id = $1
RETURNING task_checklist_item_id, task_checklist_id, created_at, complete, name, position, due_date
`
type SetTaskChecklistItemCompleteParams struct {
TaskChecklistItemID uuid.UUID `json:"task_checklist_item_id"`
Complete bool `json:"complete"`
}
func (q *Queries) SetTaskChecklistItemComplete(ctx context.Context, arg SetTaskChecklistItemCompleteParams) (TaskChecklistItem, error) {
row := q.db.QueryRowContext(ctx, setTaskChecklistItemComplete, arg.TaskChecklistItemID, arg.Complete)
var i TaskChecklistItem
err := row.Scan(
&i.TaskChecklistItemID,
&i.TaskChecklistID,
&i.CreatedAt,
&i.Complete,
&i.Name,
&i.Position,
&i.DueDate,
)
return i, err
}
const updateTaskChecklistItemName = `-- name: UpdateTaskChecklistItemName :one
UPDATE task_checklist_item SET name = $2 WHERE task_checklist_item_id = $1
RETURNING task_checklist_item_id, task_checklist_id, created_at, complete, name, position, due_date
`
type UpdateTaskChecklistItemNameParams struct {
TaskChecklistItemID uuid.UUID `json:"task_checklist_item_id"`
Name string `json:"name"`
}
func (q *Queries) UpdateTaskChecklistItemName(ctx context.Context, arg UpdateTaskChecklistItemNameParams) (TaskChecklistItem, error) {
row := q.db.QueryRowContext(ctx, updateTaskChecklistItemName, arg.TaskChecklistItemID, arg.Name)
var i TaskChecklistItem
err := row.Scan(
&i.TaskChecklistItemID,
&i.TaskChecklistID,
&i.CreatedAt,
&i.Complete,
&i.Name,
&i.Position,
&i.DueDate,
)
return i, err
}

View File

@ -28,3 +28,6 @@ DELETE FROM task where task_group_id = $1;
-- name: UpdateTaskDueDate :one
UPDATE task SET due_date = $2 WHERE task_id = $1 RETURNING *;
-- name: SetTaskComplete :one
UPDATE task SET complete = $2 WHERE task_id = $1 RETURNING *;

View File

@ -0,0 +1,27 @@
-- name: CreateTaskChecklist :one
INSERT INTO task_checklist (task_id, created_at, name, position) VALUES ($1, $2, $3, $4)
RETURNING *;
-- name: GetTaskChecklistsForTask :many
SELECT * FROM task_checklist WHERE task_id = $1;
-- name: CreateTaskChecklistItem :one
INSERT INTO task_checklist_item (task_checklist_id, created_at, name, position, complete, due_date) VALUES ($1, $2, $3, $4, false, null)
RETURNING *;
-- name: GetTaskChecklistItemsForTaskChecklist :many
SELECT * FROM task_checklist_item WHERE task_checklist_id = $1;
-- name: SetTaskChecklistItemComplete :one
UPDATE task_checklist_item SET complete = $2 WHERE task_checklist_item_id = $1
RETURNING *;
-- name: DeleteTaskChecklistItem :exec
DELETE FROM task_checklist_item WHERE task_checklist_item_id = $1;
-- name: GetTaskChecklistItemByID :one
SELECT * FROM task_checklist_item WHERE task_checklist_item_id = $1;
-- name: UpdateTaskChecklistItemName :one
UPDATE task_checklist_item SET name = $2 WHERE task_checklist_item_id = $1
RETURNING *;

1
api/trello.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,9 @@
overwrite: true
schema:
- '../api/graph/schema.graphqls'
documents: 'src/shared/graphql/*.graphqls'
documents:
- 'src/shared/graphql/*.graphqls'
- 'src/shared/graphql/**/*.ts'
generates:
src/shared/generated/graphql.tsx:
plugins:

View File

@ -112,7 +112,7 @@ export default createGlobalStyle`
}
::-webkit-scrollbar {
width: 12px;
width: 10px;
}
::-webkit-scrollbar-track {

View File

@ -10,9 +10,12 @@ import Profile from 'Profile';
import styled from 'styled-components';
const MainContent = styled.div`
padding: 0 0 50px 80px;
padding: 0 0 0 80px;
background: #262c49;
height: 100%;
display: flex;
flex-direction: column;
flex-grow: 1;
`;
type RoutesProps = {
history: H.History;

View File

@ -26,23 +26,3 @@ const theme: DefaultTheme = {
};
export { theme };
export default createGlobalStyle`
:root {
--color-text: #c2c6dc;
--color-text-hover: #fff;
--color-primary: rgba(115, 103, 240);
--color-button-text: #c2c6dc;
--color-button-text-hover: #fff;
--color-button-background: rgba(115, 103, 240);
--color-background: #262c49;
--color-background-dark: #10163a;
--color-input-text: #c2c6dc;
--color-input-text-focus: #fff;
--color-icon: #c2c6dc;
--color-active-icon: rgba(115, 103, 240);
}
`;

View File

@ -5,7 +5,7 @@ import { setAccessToken } from 'shared/utils/accessToken';
import styled, { ThemeProvider } from 'styled-components';
import NormalizeStyles from './NormalizeStyles';
import BaseStyles from './BaseStyles';
import ThemeStyles, { theme } from './ThemeStyles';
import { theme } from './ThemeStyles';
import Routes from './Routes';
import { UserIDContext } from './context';
import Navbar from './Navbar';
@ -44,7 +44,6 @@ const App = () => {
<PopupProvider>
<NormalizeStyles />
<BaseStyles />
<ThemeStyles />
<Router history={history}>
{loading ? (
<div>loading</div>

View File

@ -9,10 +9,16 @@ import {
useUpdateTaskDueDateMutation,
useAssignTaskMutation,
useUnassignTaskMutation,
useSetTaskChecklistItemCompleteMutation,
useDeleteTaskChecklistItemMutation,
useUpdateTaskChecklistItemNameMutation,
useCreateTaskChecklistItemMutation,
FindTaskDocument,
} from 'shared/generated/graphql';
import UserIDContext from 'App/context';
import MiniProfile from 'shared/components/MiniProfile';
import DueDateManager from 'shared/components/DueDateManager';
import produce from 'immer';
type DetailsProps = {
taskID: string;
@ -43,6 +49,64 @@ const Details: React.FC<DetailsProps> = ({
const match = useRouteMatch();
const [currentMemberTask, setCurrentMemberTask] = useState('');
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const [setTaskChecklistItemComplete] = useSetTaskChecklistItemCompleteMutation();
const [updateTaskChecklistItemName] = useUpdateTaskChecklistItemNameMutation();
const [deleteTaskChecklistItem] = useDeleteTaskChecklistItemMutation({
update: (client, deleteData) => {
const cacheData: any = client.readQuery({
query: FindTaskDocument,
variables: { taskID },
});
console.log(deleteData);
const newData = produce(cacheData.findTask, (draftState: any) => {
const idx = draftState.checklists.findIndex(
(checklist: TaskChecklist) =>
checklist.id === deleteData.data.deleteTaskChecklistItem.taskChecklistItem.taskChecklistID,
);
console.log(`idx ${idx}`);
if (idx !== -1) {
draftState.checklists[idx].items = cacheData.findTask.checklists[idx].items.filter(
(item: any) => item.id !== deleteData.data.deleteTaskChecklistItem.taskChecklistItem.id,
);
}
});
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
data: {
findTask: newData,
},
});
},
});
const [createTaskChecklistItem] = useCreateTaskChecklistItemMutation({
update: (client, newTaskItem) => {
const cacheData: any = client.readQuery({
query: FindTaskDocument,
variables: { taskID },
});
console.log(cacheData);
console.log(newTaskItem);
const newData = produce(cacheData.findTask, (draftState: any) => {
const idx = draftState.checklists.findIndex(
(checklist: TaskChecklist) => checklist.id === newTaskItem.data.createTaskChecklistItem.taskChecklistID,
);
if (idx !== -1) {
draftState.checklists[idx].items = [
...cacheData.findTask.checklists[idx].items,
{ ...newTaskItem.data.createTaskChecklistItem },
];
}
});
client.writeQuery({
query: FindTaskDocument,
variables: { taskID },
data: {
findTask: newData,
},
});
},
});
const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
onCompleted: () => {
@ -83,7 +147,19 @@ const Details: React.FC<DetailsProps> = ({
onTaskNameChange={onTaskNameChange}
onTaskDescriptionChange={onTaskDescriptionChange}
onDeleteTask={onDeleteTask}
onChangeItemName={(itemID, itemName) => {
updateTaskChecklistItemName({ variables: { taskChecklistItemID: itemID, name: itemName } });
}}
onCloseModal={() => history.push(projectURL)}
onDeleteItem={itemID => {
deleteTaskChecklistItem({ variables: { taskChecklistItemID: itemID } });
}}
onToggleChecklistItem={(itemID, complete) => {
setTaskChecklistItemComplete({ variables: { taskChecklistItemID: itemID, complete } });
}}
onAddItem={(taskChecklistID, name, position) => {
createTaskChecklistItem({ variables: { taskChecklistID, name, position } });
}}
onMemberProfile={($targetRef, memberID) => {
const member = data.findTask.assigned.find(m => m.id === memberID);
const profileIcon = member ? member.profileIcon : null;
@ -124,7 +200,6 @@ const Details: React.FC<DetailsProps> = ({
onOpenDueDatePopop={(task, $targetRef) => {
showPopup(
$targetRef,
<Popup
title={'Change Due Date'}
tab={0}
@ -134,8 +209,11 @@ const Details: React.FC<DetailsProps> = ({
>
<DueDateManager
task={task}
onRemoveDueDate={t => {
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null } });
hidePopup();
}}
onDueDateChange={(t, newDueDate) => {
console.log(`${newDueDate}`);
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup();
}}

View File

@ -5,9 +5,11 @@ import { Bolt, ToggleOn, Tags } from 'shared/icons';
import { usePopup, Popup } from 'shared/components/PopupMenu';
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom';
import {
useSetTaskCompleteMutation,
useToggleTaskLabelMutation,
useUpdateProjectNameMutation,
useFindProjectQuery,
useUpdateTaskGroupNameMutation,
useUpdateTaskNameMutation,
useUpdateProjectLabelMutation,
useCreateTaskMutation,
@ -23,6 +25,7 @@ import {
FindProjectDocument,
useCreateProjectLabelMutation,
useUnassignTaskMutation,
useUpdateTaskDueDateMutation,
} from 'shared/generated/graphql';
import TaskAssignee from 'shared/components/TaskAssignee';
@ -40,6 +43,7 @@ import MiniProfile from 'shared/components/MiniProfile';
import Details from './Details';
import { useApolloClient } from '@apollo/react-hooks';
import UserIDContext from 'App/context';
import DueDateManager from 'shared/components/DueDateManager';
const getCacheData = (client: any, projectID: string) => {
const cacheData: any = client.readQuery({
@ -69,6 +73,7 @@ interface QuickCardEditorState {
isOpen: boolean;
left: number;
top: number;
width: number;
taskID: string | null;
taskGroupID: string | null;
}
@ -209,6 +214,7 @@ const initialQuickCardEditorState: QuickCardEditorState = {
isOpen: false,
top: 0,
left: 0,
width: 272,
};
const ProjectBar = styled.div`
@ -367,12 +373,36 @@ const Project = () => {
if (taskGroup) {
let position = 65535;
if (taskGroup.tasks.length !== 0) {
const [lastTask] = taskGroup.tasks.sort((a: any, b: any) => a.position - b.position).slice(-1);
const [lastTask] = taskGroup.tasks
.slice()
.sort((a: any, b: any) => a.position - b.position)
.slice(-1);
position = Math.ceil(lastTask.position) * 2 + 1;
}
console.log(`position ${position}`);
createTask({ variables: { taskGroupID, name, position } });
createTask({
variables: { taskGroupID, name, position },
optimisticResponse: {
__typename: 'Mutation',
createTask: {
__typename: 'Task',
id: '' + Math.round(Math.random() * -1000000),
name: name,
taskGroup: {
__typename: 'TaskGroup',
id: taskGroup.id,
name: taskGroup.name,
position: taskGroup.position,
},
position: position,
dueDate: null,
description: null,
labels: [],
assigned: [],
},
},
});
}
}
};
@ -410,6 +440,10 @@ const Project = () => {
const [assignTask] = useAssignTaskMutation();
const [unassignTask] = useUnassignTaskMutation();
const [updateTaskGroupName] = useUpdateTaskGroupNameMutation({});
const [updateTaskDueDate] = useUpdateTaskDueDateMutation();
const [updateProjectName] = useUpdateProjectNameMutation({
update: (client, newName) => {
const cacheData = getCacheData(client, projectID);
@ -421,6 +455,8 @@ const Project = () => {
},
});
const [setTaskComplete] = useSetTaskCompleteMutation();
const client = useApolloClient();
const { userID } = useContext(UserIDContext);
@ -441,6 +477,7 @@ const Project = () => {
);
}
if (data) {
console.log(data.findProject);
const onQuickEditorOpen = (e: ContextMenuEvent) => {
const taskGroup = data.findProject.taskGroups.find(t => t.id === e.taskGroupID);
const currentTask = taskGroup ? taskGroup.tasks.find(t => t.id === e.taskID) : null;
@ -448,6 +485,7 @@ const Project = () => {
setQuickCardEditor({
top: e.top,
left: e.left,
width: e.width,
isOpen: true,
taskID: currentTask.id,
taskGroupID: currentTask.taskGroup.id,
@ -567,7 +605,9 @@ const Project = () => {
</Popup>,
);
}}
onChangeTaskGroupName={(taskGroupID, name) => {}}
onChangeTaskGroupName={(taskGroupID, name) => {
updateTaskGroupName({ variables: { taskGroupID, name } });
}}
onQuickEditorOpen={onQuickEditorOpen}
onExtraMenuOpen={(taskGroupID: string, $targetRef: any) => {
showPopup(
@ -660,8 +700,37 @@ const Project = () => {
},
})
}
onOpenDueDatePopup={($targetRef, task) => {
showPopup(
$targetRef,
<Popup
title={'Change Due Date'}
tab={0}
onClose={() => {
hidePopup();
}}
>
<DueDateManager
task={task}
onRemoveDueDate={t => {
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null } });
hidePopup();
}}
onDueDateChange={(t, newDueDate) => {
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup();
}}
onCancel={() => {}}
/>
</Popup>,
);
}}
onToggleComplete={task => {
setTaskComplete({ variables: { taskID: task.id, complete: !task.complete } });
}}
top={quickCardEditor.top}
left={quickCardEditor.left}
width={quickCardEditor.width}
/>
)}
<Route

View File

@ -12,6 +12,7 @@ interface DraggableElement {
type ContextMenuEvent = {
left: number;
top: number;
width: number;
taskID: string;
taskGroupID: string;
};

19
web/src/projects.d.ts vendored
View File

@ -30,15 +30,34 @@ type TaskLabel = {
projectLabel: ProjectLabel;
};
type TaskChecklist = {
id: string;
position: number;
name: string;
items: Array<TaskChecklistItem>;
};
type TaskChecklistItem = {
id: string;
complete: boolean;
position: number;
name: string;
taskChecklistID: string;
assigned?: null | TaskUser;
dueDate?: null | string;
};
type Task = {
id: string;
taskGroup: InnerTaskGroup;
name: string;
position: number;
dueDate?: string;
complete?: boolean;
labels: TaskLabel[];
description?: string | null;
assigned?: Array<TaskUser>;
checklists?: Array<TaskChecklist> | null;
};
type Project = {

View File

@ -3,7 +3,15 @@ import TextareaAutosize from 'react-autosize-textarea/lib';
import { mixin } from 'shared/utils/styles';
import Button from 'shared/components/Button';
export const Container = styled.div``;
export const Container = styled.div`
width: 272px;
margin: 0 4px;
height: 100%;
box-sizing: border-box;
display: inline-block;
vertical-align: top;
white-space: nowrap;
`;
export const Wrapper = styled.div<{ editorOpen: boolean }>`
display: inline-block;

View File

@ -62,7 +62,7 @@ const NameEditor: React.FC<NameEditorProps> = ({ onSave, onCancel }) => {
Save
</AddListButton>
<CancelAdd onClick={() => onCancel()}>
<Cross color="#c2c6dc" />
<Cross width={16} height={16} />
</CancelAdd>
</ListAddControls>
</>

View File

@ -2,6 +2,7 @@ import styled, { css } from 'styled-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { mixin } from 'shared/utils/styles';
import TextareaAutosize from 'react-autosize-textarea';
import { CheckCircle } from 'shared/icons';
import { RefObject } from 'react';
export const ClockIcon = styled(FontAwesomeIcon)``;
@ -20,9 +21,9 @@ export const EditorTextarea = styled(TextareaAutosize)`
max-height: 162px;
min-height: 54px;
padding: 0;
font-size: 16px;
line-height: 20px;
color: rgba(${props => props.theme.colors.text.secondary});
font-size: 14px;
line-height: 16px;
color: rgba(${props => props.theme.colors.text.primary});
&:focus {
border: none;
outline: none;
@ -92,11 +93,13 @@ export const ListCardInnerContainer = styled.div`
height: 100%;
`;
export const ListCardDetails = styled.div`
export const ListCardDetails = styled.div<{ complete: boolean }>`
overflow: hidden;
padding: 6px 8px 2px;
position: relative;
z-index: 10;
${props => props.complete && 'opacity: 0.6;'}
`;
export const ListCardLabels = styled.div`
@ -140,15 +143,28 @@ export const ListCardOperation = styled.span`
export const CardTitle = styled.span`
clear: both;
display: block;
margin: 0 0 4px;
overflow: hidden;
text-decoration: none;
word-wrap: break-word;
line-height: 16px;
font-size: 14px;
color: rgba(${props => props.theme.colors.text.primary});
display: flex;
align-items: center;
`;
export const CardMembers = styled.div`
float: right;
margin: 0 -2px 4px 0;
`;
export const CompleteIcon = styled(CheckCircle)`
fill: rgba(${props => props.theme.colors.success});
margin-right: 4px;
`;
export const EditorContent = styled.div`
display: flex;
`;

View File

@ -5,6 +5,8 @@ import { faPencilAlt, faList } from '@fortawesome/free-solid-svg-icons';
import { faClock, faCheckSquare, faEye } from '@fortawesome/free-regular-svg-icons';
import {
EditorTextarea,
EditorContent,
CompleteIcon,
DescriptionBadge,
DueDateCardBadge,
ListCardBadges,
@ -35,6 +37,7 @@ type Props = {
title: string;
taskID: string;
taskGroupID: string;
complete?: boolean;
onContextMenu?: (e: ContextMenuEvent) => void;
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
description?: null | string;
@ -57,6 +60,7 @@ const Card = React.forwardRef(
onContextMenu,
taskID,
taskGroupID,
complete,
onClick,
labels,
title,
@ -101,6 +105,7 @@ const Card = React.forwardRef(
const pos = $innerCardRef.current.getBoundingClientRect();
if (onContextMenu) {
onContextMenu({
width: pos.width,
top: pos.top,
left: pos.left,
taskGroupID,
@ -140,7 +145,7 @@ const Card = React.forwardRef(
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
</ListCardOperation>
)}
<ListCardDetails>
<ListCardDetails complete={complete ?? false}>
<ListCardLabels>
{labels &&
labels.map(label => (
@ -150,22 +155,28 @@ const Card = React.forwardRef(
))}
</ListCardLabels>
{editable ? (
<EditorTextarea
onChange={e => {
setCardTitle(e.currentTarget.value);
if (onCardTitleChange) {
onCardTitleChange(e.currentTarget.value);
}
}}
onClick={e => {
e.stopPropagation();
}}
onKeyDown={handleKeyDown}
value={currentCardTitle}
ref={$editorRef}
/>
<EditorContent>
{complete && <CompleteIcon width={16} height={16} />}
<EditorTextarea
onChange={e => {
setCardTitle(e.currentTarget.value);
if (onCardTitleChange) {
onCardTitleChange(e.currentTarget.value);
}
}}
onClick={e => {
e.stopPropagation();
}}
onKeyDown={handleKeyDown}
value={currentCardTitle}
ref={$editorRef}
/>
</EditorContent>
) : (
<CardTitle>{title}</CardTitle>
<CardTitle>
{complete && <CompleteIcon width={16} height={16} />}
{title}
</CardTitle>
)}
<ListCardBadges>
{watched && (

View File

@ -0,0 +1,138 @@
import React, { useState } from 'react';
import { action } from '@storybook/addon-actions';
import BaseStyles from 'App/BaseStyles';
import NormalizeStyles from 'App/NormalizeStyles';
import { theme } from 'App/ThemeStyles';
import produce from 'immer';
import styled, { ThemeProvider } from 'styled-components';
import Checklist from '.';
export default {
component: Checklist,
title: 'Checklist',
parameters: {
backgrounds: [
{ name: 'gray', value: '#f8f8f8', default: true },
{ name: 'white', value: '#ffffff' },
],
},
};
const Container = styled.div`
width: 552px;
margin: 25px;
border: 1px solid rgba(${props => props.theme.colors.bg.primary});
`;
const defaultItems = [
{
id: '1',
position: 1,
taskChecklistID: '1',
complete: false,
name: 'Tasks',
assigned: null,
dueDate: null,
},
{
id: '2',
taskChecklistID: '1',
position: 2,
complete: false,
name: 'Projects',
assigned: null,
dueDate: null,
},
{
id: '3',
position: 3,
taskChecklistID: '1',
complete: false,
name: 'Teams',
assigned: null,
dueDate: null,
},
{
id: '4',
position: 4,
complete: false,
taskChecklistID: '1',
name: 'Organizations',
assigned: null,
dueDate: null,
},
];
export const Default = () => {
const [checklistName, setChecklistName] = useState('Checklist');
const [items, setItems] = useState(defaultItems);
const onToggleItem = (itemID: string, complete: boolean) => {
setItems(
produce(items, draftState => {
const idx = items.findIndex(item => item.id === itemID);
if (idx !== -1) {
draftState[idx] = {
...draftState[idx],
complete,
};
}
}),
);
};
return (
<>
<BaseStyles />
<NormalizeStyles />
<ThemeProvider theme={theme}>
<Container>
<Checklist
name={checklistName}
checklistID="checklist-one"
items={items}
onDeleteChecklist={action('delete checklist')}
onChangeName={currentName => {
setChecklistName(currentName);
}}
onAddItem={itemName => {
let position = 1;
const lastItem = items[-1];
if (lastItem) {
position = lastItem.position * 2 + 1;
}
setItems([
...items,
{
id: `${Math.random()}`,
name: itemName,
complete: false,
assigned: null,
dueDate: null,
position,
taskChecklistID: '1',
},
]);
}}
onDeleteItem={itemID => {
console.log(`itemID ${itemID}`);
setItems(items.filter(item => item.id !== itemID));
}}
onChangeItemName={(itemID, currentName) => {
setItems(
produce(items, draftState => {
const idx = items.findIndex(item => item.id === itemID);
if (idx !== -1) {
draftState[idx] = {
...draftState[idx],
name: currentName,
};
}
}),
);
}}
onToggleItem={onToggleItem}
/>
</Container>
</ThemeProvider>
</>
);
};

View File

@ -0,0 +1,596 @@
import React, { useState, useRef, useEffect } from 'react';
import styled from 'styled-components';
import { CheckSquare, Trash, Square, CheckSquareOutline, Clock, Cross, AccountPlus } from 'shared/icons';
import Button from 'shared/components/Button';
import TextareaAutosize from 'react-autosize-textarea';
import Control from 'react-select/src/components/Control';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
const Wrapper = styled.div`
margin-bottom: 24px;
`;
const WindowTitle = styled.div`
padding: 8px 0;
position: relative;
margin: 0 0 4px 40px;
`;
const WindowTitleIcon = styled(CheckSquareOutline)`
top: 10px;
left: -40px;
position: absolute;
`;
const WindowChecklistTitle = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
flex-flow: row wrap;
`;
const WindowTitleText = styled.h3`
cursor: pointer;
color: rgba(${props => props.theme.colors.text.primary});
margin: 6px 0;
display: inline-block;
width: auto;
min-height: 18px;
font-size: 16px;
line-height: 20px;
min-width: 40px;
`;
const WindowOptions = styled.div`
margin: 0 2px 0 auto;
float: right;
`;
const DeleteButton = styled(Button)`
padding: 6px 12px;
`;
const ChecklistProgress = styled.div`
margin-bottom: 6px;
position: relative;
`;
const ChecklistProgressPercent = styled.span`
color: #5e6c84;
font-size: 11px;
line-height: 10px;
position: absolute;
left: 5px;
top: -1px;
text-align: center;
width: 32px;
`;
const ChecklistProgressBar = styled.div`
background: rgba(${props => props.theme.colors.bg.primary});
border-radius: 4px;
clear: both;
height: 8px;
margin: 0 0 0 40px;
overflow: hidden;
position: relative;
`;
const ChecklistProgressBarCurrent = styled.div<{ width: number }>`
width: ${props => props.width}%;
background: rgba(${props => (props.width === 100 ? props.theme.colors.success : props.theme.colors.primary)});
bottom: 0;
left: 0;
position: absolute;
top: 0;
transition: width 0.14s ease-in, background 0.14s ease-in;
`;
const ChecklistItems = styled.div`
min-height: 8px;
`;
const ChecklistItemUncheckedIcon = styled(Square)``;
const ChecklistIcon = styled.div`
cursor: pointer;
position: absolute;
left: 0;
top: 0;
margin: 10px;
text-align: center;
&:hover {
opacity: 0.8;
}
`;
const ChecklistItemCheckedIcon = styled(CheckSquare)`
fill: rgba(${props => props.theme.colors.primary});
`;
const ChecklistItemDetails = styled.div`
word-break: break-word;
word-wrap: break-word;
overflow-wrap: break-word;
`;
const ChecklistItemRow = styled.div`
cursor: pointer;
display: flex;
flex-direction: row;
`;
const ChecklistItemTextControls = styled.div`
padding: 6px 0;
width: 100%;
display: inline-flex;
`;
const ChecklistItemText = styled.span<{ complete: boolean }>`
color: ${props => (props.complete ? '#5e6c84' : `rgba(${props.theme.colors.text.primary})`)};
${props => props.complete && 'text-decoration: line-through;'}
line-height: 20px;
font-size: 16px;
min-height: 20px;
margin-bottom: 0;
align-self: center;
flex: 1;
`;
const ChecklistControls = styled.div`
display: inline-flex;
flex-direction: row;
float: right;
`;
const ControlButton = styled.div`
opacity: 0;
margin-left: 4px;
padding: 4px 6px;
border-radius: 6px;
background-color: rgba(${props => props.theme.colors.bg.primary}, 0.8);
&:hover {
background-color: rgba(${props => props.theme.colors.primary}, 1);
}
`;
const ChecklistNameEditorWrapper = styled.div`
display: block;
float: left;
padding-top: 6px;
padding-bottom: 8px;
z-index: 50;
width: 100%;
`;
export const ChecklistNameEditor = styled(TextareaAutosize)`
overflow: hidden;
overflow-wrap: break-word;
resize: none;
height: 54px;
width: 100%;
background: none;
border: none;
box-shadow: none;
max-height: 162px;
min-height: 54px;
padding: 8px 12px;
font-size: 16px;
line-height: 20px;
border: 1px solid rgba(${props => props.theme.colors.primary});
border-radius: 3px;
color: rgba(${props => props.theme.colors.text.primary});
border-color: rgba(${props => props.theme.colors.border});
background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4);
&:focus {
border-color: rgba(${props => props.theme.colors.primary});
}
`;
const AssignUserButton = styled(AccountPlus)`
fill: rgba(${props => props.theme.colors.text.primary});
`;
const ClockButton = styled(Clock)`
fill: rgba(${props => props.theme.colors.text.primary});
`;
const TrashButton = styled(Trash)`
fill: rgba(${props => props.theme.colors.text.primary});
`;
const ChecklistItemWrapper = styled.div`
user-select: none;
clear: both;
padding-left: 40px;
position: relative;
border-radius: 6px;
transform-origin: left bottom;
transition-property: transform, opacity, height, padding, margin;
transition-duration: 0.14s;
transition-timing-function: ease-in;
&:hover {
background-color: rgba(${props => props.theme.colors.bg.primary}, 0.4);
}
&:hover ${ControlButton} {
opacity: 1;
}
`;
const EditControls = styled.div`
clear: both;
display: flex;
padding-bottom: 9px;
flex-direction: row;
`;
const SaveButton = styled(Button)`
margin-right: 4px;
padding: 6px 12px;
`;
const CancelButton = styled.div`
cursor: pointer;
margin: 5px;
& svg {
fill: rgba(${props => props.theme.colors.text.primary});
}
&:hover svg {
fill: rgba(${props => props.theme.colors.text.secondary});
}
`;
const Spacer = styled.div`
flex: 1;
`;
const EditableDeleteButton = styled.button`
cursor: pointer;
display: flex;
margin: 0 2px;
padding: 6px 8px;
border-radius: 3px;
&:hover {
background: rgba(${props => props.theme.colors.primary}, 0.8);
}
`;
const NewItemButton = styled(Button)`
padding: 6px 8px;
`;
const ChecklistNewItem = styled.div`
margin: 8px 0;
margin-left: 40px;
`;
type ChecklistItemProps = {
itemID: string;
complete: boolean;
name: string;
onChangeName: (itemID: string, currentName: string) => void;
onToggleItem: (itemID: string, complete: boolean) => void;
onDeleteItem: (itemID: string) => void;
};
const ChecklistItem: React.FC<ChecklistItemProps> = ({
itemID,
complete,
name,
onChangeName,
onToggleItem,
onDeleteItem,
}) => {
const $item = useRef<HTMLDivElement>(null);
const $editor = useRef<HTMLTextAreaElement>(null);
const [editting, setEditting] = useState(false);
const [currentName, setCurrentName] = useState(name);
useEffect(() => {
if (editting && $editor && $editor.current) {
$editor.current.focus();
$editor.current.select();
}
}, [editting]);
useOnOutsideClick($item, true, () => setEditting(false), null);
return (
<ChecklistItemWrapper ref={$item}>
<ChecklistIcon
onClick={e => {
e.stopPropagation();
onToggleItem(itemID, !complete);
}}
>
{complete ? (
<ChecklistItemCheckedIcon width={20} height={20} />
) : (
<ChecklistItemUncheckedIcon width={20} height={20} />
)}
</ChecklistIcon>
{editting ? (
<>
<ChecklistNameEditorWrapper>
<ChecklistNameEditor
ref={$editor}
onKeyDown={e => {
if (e.key === 'Enter') {
onChangeName(itemID, currentName);
setEditting(false);
}
}}
onChange={e => {
setCurrentName(e.currentTarget.value);
}}
value={currentName}
/>
</ChecklistNameEditorWrapper>
<EditControls>
<SaveButton
onClick={() => {
onChangeName(itemID, currentName);
setEditting(false);
}}
variant="relief"
>
Save
</SaveButton>
<CancelButton
onClick={e => {
e.stopPropagation();
setEditting(false);
}}
>
<Cross width={20} height={20} />
</CancelButton>
<Spacer />
<EditableDeleteButton
onClick={e => {
e.stopPropagation();
setEditting(false);
onDeleteItem(itemID);
}}
>
<Trash width={16} height={16} />
</EditableDeleteButton>
</EditControls>
</>
) : (
<ChecklistItemDetails
onClick={() => {
setEditting(true);
}}
>
<ChecklistItemRow>
<ChecklistItemTextControls>
<ChecklistItemText complete={complete}>{name}</ChecklistItemText>
<ChecklistControls>
<ControlButton>
<AssignUserButton width={14} height={14} />
</ControlButton>
<ControlButton>
<ClockButton width={14} height={14} />
</ControlButton>
<ControlButton
onClick={e => {
e.stopPropagation();
onDeleteItem(itemID);
}}
>
<TrashButton width={14} height={14} />
</ControlButton>
</ChecklistControls>
</ChecklistItemTextControls>
</ChecklistItemRow>
</ChecklistItemDetails>
)}
</ChecklistItemWrapper>
);
};
type AddNewItemProps = {
onAddItem: (name: string) => void;
};
const AddNewItem: React.FC<AddNewItemProps> = ({ onAddItem }) => {
const $editor = useRef<HTMLTextAreaElement>(null);
const $wrapper = useRef<HTMLDivElement>(null);
const [currentName, setCurrentName] = useState('');
const [editting, setEditting] = useState(false);
useEffect(() => {
if (editting && $editor && $editor.current) {
$editor.current.focus();
$editor.current.select();
}
}, [editting]);
useOnOutsideClick($wrapper, true, () => setEditting(false), null);
return (
<ChecklistNewItem ref={$wrapper}>
{editting ? (
<>
<ChecklistNameEditorWrapper>
<ChecklistNameEditor
ref={$editor}
onKeyDown={e => {
if (e.key === 'Enter') {
e.preventDefault();
onAddItem(currentName);
setCurrentName('');
}
}}
onChange={e => {
setCurrentName(e.currentTarget.value);
}}
value={currentName}
/>
</ChecklistNameEditorWrapper>
<EditControls>
<SaveButton
onClick={() => {
onAddItem(currentName);
setCurrentName('');
if (editting && $editor && $editor.current) {
$editor.current.focus();
$editor.current.select();
}
}}
variant="relief"
>
Save
</SaveButton>
<CancelButton
onClick={e => {
e.stopPropagation();
setEditting(false);
}}
>
<Cross width={20} height={20} />
</CancelButton>
</EditControls>
</>
) : (
<NewItemButton onClick={() => setEditting(true)}>Add an item</NewItemButton>
)}
</ChecklistNewItem>
);
};
type ChecklistTitleEditorProps = {
name: string;
onChangeName: (item: string) => void;
onCancel: () => void;
};
const ChecklistTitleEditor = React.forwardRef(
({ name, onChangeName, onCancel }: ChecklistTitleEditorProps, $name: any) => {
const [currentName, setCurrentName] = useState(name);
return (
<>
<ChecklistNameEditor
ref={$name}
value={currentName}
onChange={e => {
setCurrentName(e.currentTarget.value);
}}
onKeyDown={e => {
if (e.key === 'Enter') {
onChangeName(currentName);
}
}}
/>
<EditControls>
<SaveButton
onClick={() => {
onChangeName(currentName);
}}
variant="relief"
>
Save
</SaveButton>
<CancelButton
onClick={e => {
e.stopPropagation();
onCancel();
}}
>
<Cross width={20} height={20} />
</CancelButton>
</EditControls>
</>
);
},
);
type ChecklistProps = {
checklistID: string;
onDeleteChecklist: (checklistID: string) => void;
name: string;
onChangeName: (item: string) => void;
onToggleItem: (taskID: string, complete: boolean) => void;
onChangeItemName: (itemID: string, currentName: string) => void;
onDeleteItem: (itemID: string) => void;
onAddItem: (itemName: string) => void;
items: Array<TaskChecklistItem>;
};
const Checklist: React.FC<ChecklistProps> = ({
checklistID,
onDeleteChecklist,
name,
items,
onToggleItem,
onAddItem,
onChangeItemName,
onChangeName,
onDeleteItem,
}) => {
const $name = useRef<HTMLTextAreaElement>(null);
const complete = items.reduce((prev, item) => prev + (item.complete ? 1 : 0), 0);
const percent = items.length === 0 ? 0 : Math.floor((complete / items.length) * 100);
const [editting, setEditting] = useState(false);
// useOnOutsideClick($name, true, () => setEditting(false), null);
useEffect(() => {
if (editting && $name && $name.current) {
$name.current.focus();
$name.current.select();
}
}, [editting]);
return (
<Wrapper>
<WindowTitle>
<WindowTitleIcon width={24} height={24} />
{editting ? (
<ChecklistTitleEditor
ref={$name}
name={name}
onChangeName={currentName => {
onChangeName(currentName);
setEditting(false);
}}
onCancel={() => {
setEditting(false);
}}
/>
) : (
<WindowChecklistTitle>
<WindowTitleText onClick={() => setEditting(true)}>{name}</WindowTitleText>
<WindowOptions>
<DeleteButton
onClick={() => {
onDeleteChecklist(checklistID);
}}
color="danger"
variant="outline"
>
Delete
</DeleteButton>
</WindowOptions>
</WindowChecklistTitle>
)}
</WindowTitle>
<ChecklistProgress>
<ChecklistProgressPercent>{`${percent}%`}</ChecklistProgressPercent>
<ChecklistProgressBar>
<ChecklistProgressBarCurrent width={percent} />
</ChecklistProgressBar>
</ChecklistProgress>
<ChecklistItems>
{items
.slice()
.sort((a, b) => a.position - b.position)
.map(item => (
<ChecklistItem
key={item.id}
itemID={item.id}
name={item.name}
complete={item.complete}
onDeleteItem={onDeleteItem}
onChangeName={onChangeItemName}
onToggleItem={onToggleItem}
/>
))}
</ChecklistItems>
<AddNewItem onAddItem={onAddItem} />
</Wrapper>
);
};
export default Checklist;

View File

@ -64,6 +64,7 @@ export const Default = () => {
}}
onCancel={action('cancel')}
onDueDateChange={action('due date change')}
onRemoveDueDate={action('remove due date')}
/>
</Popup>
</PopupWrapper>

View File

@ -102,11 +102,15 @@ export const DueDatePickerWrapper = styled.div`
`;
export const ConfirmAddDueDate = styled(Button)`
float: left;
margin: 0 4px 0 0;
padding: 6px 12px;
`;
export const RemoveDueDate = styled(Button)`
padding: 6px 12px;
margin: 0 0 0 4px;
`;
export const CancelDueDate = styled.div`
display: flex;
align-items: center;
@ -126,4 +130,5 @@ export const ActionWrapper = styled.div`
padding-top: 8px;
width: 100%;
display: flex;
justify-content: space-between;
`;

View File

@ -5,7 +5,15 @@ import DatePicker from 'react-datepicker';
import { Cross } from 'shared/icons';
import _ from 'lodash';
import { Wrapper, ActionWrapper, DueDateInput, DueDatePickerWrapper, ConfirmAddDueDate, CancelDueDate } from './Styles';
import {
Wrapper,
ActionWrapper,
RemoveDueDate,
DueDateInput,
DueDatePickerWrapper,
ConfirmAddDueDate,
CancelDueDate,
} from './Styles';
import 'react-datepicker/dist/react-datepicker.css';
import { getYear, getMonth } from 'date-fns';
@ -14,6 +22,7 @@ import { useForm } from 'react-hook-form';
type DueDateManagerProps = {
task: Task;
onDueDateChange: (task: Task, newDueDate: Date) => void;
onRemoveDueDate: (task: Task) => void;
onCancel: () => void;
};
@ -109,7 +118,7 @@ const HeaderActions = styled.div`
}
`;
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onCancel }) => {
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => {
const now = moment();
const [textStartDate, setTextStartDate] = useState(now.format('YYYY-MM-DD'));
const [startDate, setStartDate] = useState(new Date());
@ -260,19 +269,18 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
/>
</DueDatePickerWrapper>
<ActionWrapper>
<ConfirmAddDueDate
type="submit"
onClick={() => {
// const newDate = moment(startDate).format('YYYY-MM-DD');
// const newTime = moment(endTime).format('h:mm A');
// onDueDateChange(task, moment(`${newDate} ${newTime}`, 'YYYY-MM-DD h:mm A').toDate());
}}
>
<ConfirmAddDueDate type="submit" onClick={() => {}}>
Save
</ConfirmAddDueDate>
<CancelDueDate onClick={onCancel}>
<Cross size={16} color="#c2c6dc" />
</CancelDueDate>
<RemoveDueDate
variant="outline"
color="danger"
onClick={() => {
onRemoveDueDate(task);
}}
>
Remove
</RemoveDueDate>
</ActionWrapper>
</Form>
</Wrapper>

View File

@ -7,11 +7,41 @@ export const Container = styled.div`
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 8px;
::-webkit-scrollbar {
height: 10px;
}
::-webkit-scrollbar-thumb {
background: #7367f0;
border-radius: 6px;
}
::-webkit-scrollbar-track {
background: #10163a;
border-radius: 6px;
}
`;
export const BoardContainer = styled.div`
position: relative;
overflow-y: auto;
outline: none;
flex-grow: 1;
`;
export const BoardWrapper = styled.div`
display: flex;
margin-top: 12px;
margin-left: 8px;
user-select: none;
white-space: nowrap;
margin-bottom: 8px;
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 8px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
`;
export default Container;

View File

@ -10,10 +10,10 @@ import {
getNewDraggablePosition,
getAfterDropDraggableList,
} from 'shared/utils/draggables';
import { Container, BoardWrapper } from './Styles';
import moment from 'moment';
import { Container, BoardContainer, BoardWrapper } from './Styles';
interface SimpleProps {
taskGroups: Array<TaskGroup>;
onTaskDrop: (task: Task, previousTaskGroupID: string) => void;
@ -120,104 +120,107 @@ const SimpleLists: React.FC<SimpleProps> = ({
const [currentComposer, setCurrentComposer] = useState('');
return (
<BoardWrapper>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable direction="horizontal" type="column" droppableId="root">
{provided => (
<Container {...provided.droppableProps} ref={provided.innerRef}>
{taskGroups
.slice()
.sort((a: any, b: any) => a.position - b.position)
.map((taskGroup: TaskGroup, index: number) => {
return (
<Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
{columnDragProvided => (
<Droppable type="tasks" droppableId={taskGroup.id}>
{(columnDropProvided, snapshot) => (
<List
name={taskGroup.name}
onOpenComposer={id => setCurrentComposer(id)}
isComposerOpen={currentComposer === taskGroup.id}
onSaveName={name => onChangeTaskGroupName(taskGroup.id, name)}
ref={columnDragProvided.innerRef}
wrapperProps={columnDragProvided.draggableProps}
headerProps={columnDragProvided.dragHandleProps}
onExtraMenuOpen={onExtraMenuOpen}
id={taskGroup.id}
key={taskGroup.id}
index={index}
>
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
{taskGroup.tasks
.slice()
.sort((a: any, b: any) => a.position - b.position)
.map((task: Task, taskIndex: any) => {
return (
<Draggable key={task.id} draggableId={task.id} index={taskIndex}>
{taskProvided => {
return (
<Card
wrapperProps={{
...taskProvided.draggableProps,
...taskProvided.dragHandleProps,
}}
ref={taskProvided.innerRef}
taskID={task.id}
taskGroupID={taskGroup.id}
description=""
labels={task.labels.map(label => label.projectLabel)}
dueDate={
task.dueDate
? {
isPastDue: false,
formattedDate: moment(task.dueDate).format('MMM D, YYYY'),
}
: undefined
}
title={task.name}
members={task.assigned}
onClick={() => {
onTaskClick(task);
}}
onCardMemberClick={onCardMemberClick}
onContextMenu={onQuickEditorOpen}
/>
);
}}
</Draggable>
);
})}
{columnDropProvided.placeholder}
{currentComposer === taskGroup.id && (
<CardComposer
onClose={() => {
setCurrentComposer('');
}}
onCreateCard={name => {
onCreateTask(taskGroup.id, name);
}}
isOpen
/>
)}
</ListCards>
</List>
)}
</Droppable>
)}
</Draggable>
);
})}
{provided.placeholder}
</Container>
)}
</Droppable>
</DragDropContext>
<AddList
onSave={listName => {
onCreateTaskGroup(listName);
}}
/>
</BoardWrapper>
<BoardContainer>
<BoardWrapper>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable direction="horizontal" type="column" droppableId="root">
{provided => (
<Container {...provided.droppableProps} ref={provided.innerRef}>
{taskGroups
.slice()
.sort((a: any, b: any) => a.position - b.position)
.map((taskGroup: TaskGroup, index: number) => {
return (
<Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
{columnDragProvided => (
<Droppable type="tasks" droppableId={taskGroup.id}>
{(columnDropProvided, snapshot) => (
<List
name={taskGroup.name}
onOpenComposer={id => setCurrentComposer(id)}
isComposerOpen={currentComposer === taskGroup.id}
onSaveName={name => onChangeTaskGroupName(taskGroup.id, name)}
ref={columnDragProvided.innerRef}
wrapperProps={columnDragProvided.draggableProps}
headerProps={columnDragProvided.dragHandleProps}
onExtraMenuOpen={onExtraMenuOpen}
id={taskGroup.id}
key={taskGroup.id}
index={index}
>
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
{taskGroup.tasks
.slice()
.sort((a: any, b: any) => a.position - b.position)
.map((task: Task, taskIndex: any) => {
return (
<Draggable key={task.id} draggableId={task.id} index={taskIndex}>
{taskProvided => {
return (
<Card
wrapperProps={{
...taskProvided.draggableProps,
...taskProvided.dragHandleProps,
}}
ref={taskProvided.innerRef}
taskID={task.id}
complete={task.complete ?? false}
taskGroupID={taskGroup.id}
description=""
labels={task.labels.map(label => label.projectLabel)}
dueDate={
task.dueDate
? {
isPastDue: false,
formattedDate: moment(task.dueDate).format('MMM D, YYYY'),
}
: undefined
}
title={task.name}
members={task.assigned}
onClick={() => {
onTaskClick(task);
}}
onCardMemberClick={onCardMemberClick}
onContextMenu={onQuickEditorOpen}
/>
);
}}
</Draggable>
);
})}
{columnDropProvided.placeholder}
{currentComposer === taskGroup.id && (
<CardComposer
onClose={() => {
setCurrentComposer('');
}}
onCreateCard={name => {
onCreateTask(taskGroup.id, name);
}}
isOpen
/>
)}
</ListCards>
</List>
)}
</Droppable>
)}
</Draggable>
);
})}
<AddList
onSave={listName => {
onCreateTaskGroup(listName);
}}
/>
{provided.placeholder}
</Container>
)}
</Droppable>
</DragDropContext>
</BoardWrapper>
</BoardContainer>
);
};

View File

@ -59,7 +59,7 @@ const MemberManager: React.FC<MemberManagerProps> = ({
<MemberName>{member.fullName}</MemberName>
{activeMembers.findIndex(m => m.id === member.id) !== -1 && (
<ActiveIconWrapper>
<Checkmark size={16} color="#42526e" />
<Checkmark width={16} height={16} />
</ActiveIconWrapper>
)}
</BoardMemberListItemContent>

View File

@ -232,7 +232,7 @@ const NewProject: React.FC<NewProjectProps> = ({ teams, onClose, onCreateProject
onClose();
}}
>
<Cross color="#c2c6dc" />
<Cross width={16} height={16} />
</HeaderRight>
</Header>
<Container>

View File

@ -44,7 +44,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props)
setCurrentColor(labelColor);
}}
>
{currentColor && labelColor.id === currentColor.id && <Checkmark color="#fff" size={12} />}
{currentColor && labelColor.id === currentColor.id && <Checkmark width={12} height={12} />}
</LabelBox>
))}
</div>

View File

@ -72,7 +72,7 @@ const LabelManager: React.FC<Props> = ({ labels, taskLabels, onLabelToggle, onLa
{label.name}
{taskLabels && taskLabels.find(t => t.projectLabel.id === label.id) && (
<ActiveIcon>
<Checkmark color="#fff" />
<Checkmark width={16} height={16} />
</ActiveIcon>
)}
</CardLabel>

View File

@ -265,6 +265,7 @@ export const DueDateManagerPopup = () => {
{popupData.isOpen && (
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<DueDateManager
onRemoveDueDate={action('remove due date')}
task={{
id: '1',
taskGroup: { name: 'General', id: '1', position: 1 },

View File

@ -1,4 +1,4 @@
import React, { useRef, createContext, RefObject, useState, useContext } from 'react';
import React, { useRef, createContext, RefObject, useState, useContext, useEffect } from 'react';
import { Cross, AngleLeft } from 'shared/icons';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { createPortal } from 'react-dom';
@ -36,10 +36,19 @@ type PopupContainerProps = {
};
const PopupContainer: React.FC<PopupContainerProps> = ({ width, top, left, onClose, children, invert }) => {
const $containerRef = useRef();
const $containerRef = useRef<HTMLDivElement>(null);
const [currentTop, setCurrentTop] = useState(top);
useOnOutsideClick($containerRef, true, onClose, null);
useEffect(() => {
if ($containerRef && $containerRef.current) {
const bounding = $containerRef.current.getBoundingClientRect();
if (bounding.bottom > (window.innerHeight || document.documentElement.clientHeight)) {
setCurrentTop(44);
}
}
}, []);
return (
<Container width={width ?? 316} left={left} top={top} ref={$containerRef} invert={invert}>
<Container width={width ?? 316} left={left} top={currentTop} ref={$containerRef} invert={invert}>
{children}
</Container>
);
@ -91,11 +100,12 @@ export const PopupProvider: React.FC = ({ children }) => {
const show = (target: RefObject<HTMLElement>, content: JSX.Element, width?: number | string) => {
if (target && target.current) {
const bounds = target.current.getBoundingClientRect();
const top = bounds.top + bounds.height;
if (bounds.left + 304 + 30 > window.innerWidth) {
setState({
isOpen: true,
left: bounds.left + bounds.width,
top: bounds.top + bounds.height,
top,
invert: true,
currentTab: 0,
previousTab: 0,
@ -106,7 +116,7 @@ export const PopupProvider: React.FC = ({ children }) => {
setState({
isOpen: true,
left: bounds.left,
top: bounds.top + bounds.height,
top,
invert: false,
currentTab: 0,
previousTab: 0,
@ -176,7 +186,7 @@ type Props = {
};
const PopupMenu: React.FC<Props> = ({ width, title, top, left, onClose, noHeader, children, onPrevious }) => {
const $containerRef = useRef();
const $containerRef = useRef<HTMLDivElement>(null);
useOnOutsideClick($containerRef, true, onClose, null);
return (
@ -189,13 +199,13 @@ const PopupMenu: React.FC<Props> = ({ width, title, top, left, onClose, noHeader
)}
{noHeader ? (
<CloseButton onClick={() => onClose()}>
<Cross color="#c2c6dc" />
<Cross width={16} height={16} />
</CloseButton>
) : (
<Header>
<HeaderTitle>{title}</HeaderTitle>
<CloseButton onClick={() => onClose()}>
<Cross color="#c2c6dc" />
<Cross width={16} height={16} />
</CloseButton>
</Header>
)}
@ -230,7 +240,7 @@ export const Popup: React.FC<PopupProps> = ({ title, onClose, tab, children }) =
)}
{onClose && (
<CloseButton onClick={() => onClose()}>
<Cross color="#c2c6dc" />
<Cross width={16} height={16} />
</CloseButton>
)}
<Content>{children}</Content>

View File

@ -57,7 +57,9 @@ export const Default = () => {
onCloseEditor={() => setEditorOpen(false)}
onEditCard={action('edit card')}
onOpenLabelsPopup={action('open popup')}
onOpenDueDatePopup={action('open popup')}
onOpenMembersPopup={action('open popup')}
onToggleComplete={action('complete')}
onArchiveCard={action('archive card')}
top={top}
left={left}

View File

@ -14,9 +14,9 @@ export const Wrapper = styled.div<{ open: boolean }>`
visibility: ${props => (props.open ? 'show' : 'hidden')};
`;
export const Container = styled.div<{ top: number; left: number }>`
export const Container = styled.div<{ width: number; top: number; left: number }>`
position: absolute;
width: 256px;
width: ${props => props.width}px;
top: ${props => props.top}px;
left: ${props => props.left}px;
`;

View File

@ -14,12 +14,15 @@ type Props = {
task: Task;
onCloseEditor: () => void;
onEditCard: (taskGroupID: string, taskID: string, cardName: string) => void;
onToggleComplete: (task: Task) => void;
onOpenLabelsPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
onOpenMembersPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
onOpenDueDatePopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
onArchiveCard: (taskGroupID: string, taskID: string) => void;
onCardMemberClick?: OnCardMemberClick;
top: number;
left: number;
width?: number;
};
const QuickCardEditor = ({
@ -27,14 +30,18 @@ const QuickCardEditor = ({
onCloseEditor,
onOpenLabelsPopup,
onOpenMembersPopup,
onOpenDueDatePopup,
onToggleComplete,
onCardMemberClick,
onArchiveCard,
onEditCard,
width = 272,
top,
left,
}: Props) => {
const [currentCardTitle, setCardTitle] = useState(task.name);
const $labelsRef: any = useRef();
const $dueDate: any = useRef();
const $membersRef: any = useRef();
const handleCloseEditor = (e: any) => {
@ -45,9 +52,9 @@ const QuickCardEditor = ({
return (
<Wrapper onClick={handleCloseEditor} open>
<CloseButton onClick={handleCloseEditor}>
<Cross size={16} color="#000" />
<Cross width={16} height={16} />
</CloseButton>
<Container left={left} top={top}>
<Container width={width} left={left} top={top}>
<Card
editable
onCardMemberClick={onCardMemberClick}
@ -56,6 +63,7 @@ const QuickCardEditor = ({
onEditCard(taskGroupID, taskID, name);
onCloseEditor();
}}
complete={task.complete ?? false}
members={task.assigned}
taskID={task.id}
taskGroupID={task.taskGroup.id}
@ -63,6 +71,14 @@ const QuickCardEditor = ({
/>
<SaveButton onClick={() => onEditCard(task.taskGroup.id, task.id, currentCardTitle)}>Save</SaveButton>
<EditorButtons>
<EditorButton
onClick={e => {
e.stopPropagation();
onToggleComplete(task);
}}
>
{task.complete ? 'Mark Incomplete' : 'Mark Complete'}
</EditorButton>
<EditorButton
ref={$membersRef}
onClick={e => {
@ -72,6 +88,15 @@ const QuickCardEditor = ({
>
Edit Assigned
</EditorButton>
<EditorButton
ref={$dueDate}
onClick={e => {
e.stopPropagation();
onOpenDueDatePopup($labelsRef, task);
}}
>
Edit Due Date
</EditorButton>
<EditorButton
ref={$labelsRef}
onClick={e => {

View File

@ -148,6 +148,20 @@ export const TaskDetailsMarkdown = styled.div`
cursor: pointer;
color: #c2c6dc;
h1 {
font-size: 24px;
font-weight: 600;
line-height: 28px;
margin: 0 0 12px;
}
h2 {
font-weight: 600;
font-size: 20px;
line-height: 24px;
margin: 16px 0 8px;
}
p {
margin: 0 0 8px;
}
@ -155,6 +169,15 @@ export const TaskDetailsMarkdown = styled.div`
strong {
font-weight: 700;
}
ul {
margin: 8px 0;
}
ul > li {
margin: 8px 8px 8px 24px;
list-style: disc;
}
`;
export const TaskDetailsControls = styled.div`

View File

@ -28,6 +28,8 @@ export const Default = () => {
renderContent={() => {
return (
<TaskDetails
onDeleteItem={action('delete item')}
onChangeItemName={action('change item name')}
task={{
id: '1',
taskGroup: { name: 'General', id: '1' },
@ -65,6 +67,8 @@ export const Default = () => {
onCloseModal={action('close modal')}
onMemberProfile={action('profile')}
onOpenAddMemberPopup={action('open add member popup')}
onAddItem={action('add item')}
onToggleChecklistItem={action('toggle checklist item')}
onOpenAddLabelPopup={action('open add label popup')}
onOpenDueDatePopop={action('open due date popup')}
/>

View File

@ -3,6 +3,7 @@ import { Bin, Cross, Plus } from 'shared/icons';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import ReactMarkdown from 'react-markdown';
import TaskAssignee from 'shared/components/TaskAssignee';
import moment from 'moment';
import {
NoDueDateLabel,
@ -37,15 +38,33 @@ import {
TaskDetailAssignees,
TaskDetailsAddMemberIcon,
} from './Styles';
import convertDivElementRefToBounds from 'shared/utils/boundingRect';
import moment from 'moment';
import Checklist from '../Checklist';
type TaskContentProps = {
onEditContent: () => void;
description: string;
};
type TaskLabelProps = {
label: TaskLabel;
onClick: ($target: React.RefObject<HTMLElement>) => void;
};
const TaskLabelItem: React.FC<TaskLabelProps> = ({ label, onClick }) => {
const $label = useRef<HTMLDivElement>(null);
return (
<TaskDetailLabel
onClick={() => {
onClick($label);
}}
ref={$label}
color={label.projectLabel.labelColor.colorHex}
>
{label.projectLabel.name}
</TaskDetailLabel>
);
};
const TaskContent: React.FC<TaskContentProps> = ({ description, onEditContent }) => {
return description === '' ? (
<TaskDetailsAddDetailsButton onClick={onEditContent}>Add a more detailed description</TaskDetailsAddDetailsButton>
@ -105,6 +124,10 @@ type TaskDetailsProps = {
onTaskNameChange: (task: Task, newName: string) => void;
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
onDeleteTask: (task: Task) => void;
onAddItem: (checklistID: string, name: string, position: number) => void;
onDeleteItem: (itemID: string) => void;
onChangeItemName: (itemID: string, itemName: string) => void;
onToggleChecklistItem: (itemID: string, complete: boolean) => void;
onOpenAddMemberPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenAddLabelPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenDueDatePopop: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
@ -116,11 +139,15 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
task,
onTaskNameChange,
onTaskDescriptionChange,
onChangeItemName,
onDeleteItem,
onDeleteTask,
onCloseModal,
onOpenAddMemberPopup,
onOpenAddLabelPopup,
onOpenDueDatePopop,
onAddItem,
onToggleChecklistItem,
onMemberProfile,
}) => {
const [editorOpen, setEditorOpen] = useState(false);
@ -158,7 +185,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<Bin size={20} color="#c2c6dc" />
</TaskAction>
<TaskAction onClick={onCloseModal}>
<Cross size={20} color="#c2c6dc" />
<Cross width={16} height={16} />
</TaskAction>
</TaskActions>
<TaskHeader>
@ -195,6 +222,33 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
) : (
<TaskContent description={description} onEditContent={handleClick} />
)}
{task.checklists &&
task.checklists
.slice()
.sort((a, b) => a.position - b.position)
.map(checklist => (
<Checklist
key={checklist.id}
name={checklist.name}
checklistID={checklist.id}
items={checklist.items}
onDeleteChecklist={() => {}}
onChangeName={() => {}}
onToggleItem={onToggleChecklistItem}
onDeleteItem={onDeleteItem}
onAddItem={n => {
if (task.checklists) {
let position = 1;
const lastChecklist = task.checklists.sort((a, b) => a.position - b.position)[-1];
if (lastChecklist) {
position = lastChecklist.position * 2 + 1;
}
onAddItem(checklist.id, n, position);
}
}}
onChangeItemName={onChangeItemName}
/>
))}
</TaskDetailsContent>
<TaskDetailsSidebar>
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
@ -221,9 +275,13 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskDetailLabels>
{task.labels.map(label => {
return (
<TaskDetailLabel key={label.projectLabel.id} color={label.projectLabel.labelColor.colorHex}>
{label.projectLabel.name}
</TaskDetailLabel>
<TaskLabelItem
key={label.projectLabel.id}
label={label}
onClick={$target => {
onOpenAddLabelPopup(task, $target);
}}
/>
);
})}
<TaskDetailsAddLabel ref={$addLabelRef} onClick={onAddLabel}>

View File

@ -111,8 +111,10 @@ export type Task = {
position: Scalars['Float'];
description?: Maybe<Scalars['String']>;
dueDate?: Maybe<Scalars['Time']>;
complete: Scalars['Boolean'];
assigned: Array<ProjectMember>;
labels: Array<TaskLabel>;
checklists: Array<TaskChecklist>;
};
export type ProjectsFilter = {
@ -239,6 +241,30 @@ export type DeleteTaskGroupPayload = {
taskGroup: TaskGroup;
};
export type DeleteTaskChecklistItemPayload = {
__typename?: 'DeleteTaskChecklistItemPayload';
ok: Scalars['Boolean'];
taskChecklistItem: TaskChecklistItem;
};
export type TaskChecklistItem = {
__typename?: 'TaskChecklistItem';
id: Scalars['ID'];
name: Scalars['String'];
taskChecklistID: Scalars['UUID'];
complete: Scalars['Boolean'];
position: Scalars['Float'];
dueDate: Scalars['Time'];
};
export type TaskChecklist = {
__typename?: 'TaskChecklist';
id: Scalars['ID'];
name: Scalars['String'];
position: Scalars['Float'];
items: Array<TaskChecklistItem>;
};
export type AssignTaskInput = {
taskID: Scalars['UUID'];
userID: Scalars['UUID'];
@ -321,6 +347,37 @@ export type UpdateTaskDueDate = {
dueDate?: Maybe<Scalars['Time']>;
};
export type SetTaskComplete = {
taskID: Scalars['UUID'];
complete: Scalars['Boolean'];
};
export type CreateTaskChecklist = {
taskID: Scalars['UUID'];
name: Scalars['String'];
position: Scalars['Float'];
};
export type CreateTaskChecklistItem = {
taskChecklistID: Scalars['UUID'];
name: Scalars['String'];
position: Scalars['Float'];
};
export type SetTaskChecklistItemComplete = {
taskChecklistItemID: Scalars['UUID'];
complete: Scalars['Boolean'];
};
export type DeleteTaskChecklistItem = {
taskChecklistItemID: Scalars['UUID'];
};
export type UpdateTaskChecklistItemName = {
taskChecklistItemID: Scalars['UUID'];
name: Scalars['String'];
};
export type Mutation = {
__typename?: 'Mutation';
createRefreshToken: RefreshToken;
@ -341,10 +398,16 @@ export type Mutation = {
addTaskLabel: Task;
removeTaskLabel: Task;
toggleTaskLabel: ToggleTaskLabelPayload;
createTaskChecklist: TaskChecklist;
createTaskChecklistItem: TaskChecklistItem;
updateTaskChecklistItemName: TaskChecklistItem;
setTaskChecklistItemComplete: TaskChecklistItem;
deleteTaskChecklistItem: DeleteTaskChecklistItemPayload;
createTask: Task;
updateTaskDescription: Task;
updateTaskLocation: UpdateTaskLocationPayload;
updateTaskName: Task;
setTaskComplete: Task;
updateTaskDueDate: Task;
deleteTask: DeleteTaskPayload;
assignTask: Task;
@ -438,6 +501,31 @@ export type MutationToggleTaskLabelArgs = {
};
export type MutationCreateTaskChecklistArgs = {
input: CreateTaskChecklist;
};
export type MutationCreateTaskChecklistItemArgs = {
input: CreateTaskChecklistItem;
};
export type MutationUpdateTaskChecklistItemNameArgs = {
input: UpdateTaskChecklistItemName;
};
export type MutationSetTaskChecklistItemCompleteArgs = {
input: SetTaskChecklistItemComplete;
};
export type MutationDeleteTaskChecklistItemArgs = {
input: DeleteTaskChecklistItem;
};
export type MutationCreateTaskArgs = {
input: NewTask;
};
@ -458,6 +546,11 @@ export type MutationUpdateTaskNameArgs = {
};
export type MutationSetTaskCompleteArgs = {
input: SetTaskComplete;
};
export type MutationUpdateTaskDueDateArgs = {
input: UpdateTaskDueDate;
};
@ -564,7 +657,7 @@ export type CreateTaskMutation = (
{ __typename?: 'Mutation' }
& { createTask: (
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'position' | 'description'>
& Pick<Task, 'id' | 'name' | 'position' | 'description' | 'dueDate'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id' | 'name' | 'position'>
@ -681,29 +774,7 @@ export type FindProjectQuery = (
& Pick<TaskGroup, 'id' | 'name' | 'position'>
& { tasks: Array<(
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'position' | 'description' | 'dueDate'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id' | 'name' | 'position'>
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'fullName'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)> }
& TaskFieldsFragment
)> }
)> }
), labelColors: Array<(
@ -725,7 +796,14 @@ export type FindTaskQuery = (
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
), labels: Array<(
), checklists: Array<(
{ __typename?: 'TaskChecklist' }
& Pick<TaskChecklist, 'id' | 'name' | 'position'>
& { items: Array<(
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name' | 'taskChecklistID' | 'complete' | 'position'>
)> }
)>, labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
@ -747,6 +825,33 @@ export type FindTaskQuery = (
) }
);
export type TaskFieldsFragment = (
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'description' | 'dueDate' | 'complete' | 'position'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
), labels: Array<(
{ __typename?: 'TaskLabel' }
& Pick<TaskLabel, 'id' | 'assignedDate'>
& { projectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'name' | 'createdDate'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'position' | 'name'>
) }
) }
)>, assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'id' | 'fullName'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)> }
);
export type GetProjectsQueryVariables = {};
@ -780,6 +885,94 @@ export type MeQuery = (
) }
);
export type CreateTaskChecklistItemMutationVariables = {
taskChecklistID: Scalars['UUID'];
name: Scalars['String'];
position: Scalars['Float'];
};
export type CreateTaskChecklistItemMutation = (
{ __typename?: 'Mutation' }
& { createTaskChecklistItem: (
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name' | 'taskChecklistID' | 'position' | 'complete'>
) }
);
export type DeleteTaskChecklistItemMutationVariables = {
taskChecklistItemID: Scalars['UUID'];
};
export type DeleteTaskChecklistItemMutation = (
{ __typename?: 'Mutation' }
& { deleteTaskChecklistItem: (
{ __typename?: 'DeleteTaskChecklistItemPayload' }
& Pick<DeleteTaskChecklistItemPayload, 'ok'>
& { taskChecklistItem: (
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'taskChecklistID'>
) }
) }
);
export type SetTaskChecklistItemCompleteMutationVariables = {
taskChecklistItemID: Scalars['UUID'];
complete: Scalars['Boolean'];
};
export type SetTaskChecklistItemCompleteMutation = (
{ __typename?: 'Mutation' }
& { setTaskChecklistItemComplete: (
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name' | 'taskChecklistID' | 'complete' | 'position'>
) }
);
export type SetTaskCompleteMutationVariables = {
taskID: Scalars['UUID'];
complete: Scalars['Boolean'];
};
export type SetTaskCompleteMutation = (
{ __typename?: 'Mutation' }
& { setTaskComplete: (
{ __typename?: 'Task' }
& TaskFieldsFragment
) }
);
export type UpdateTaskChecklistItemNameMutationVariables = {
taskChecklistItemID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateTaskChecklistItemNameMutation = (
{ __typename?: 'Mutation' }
& { updateTaskChecklistItemName: (
{ __typename?: 'TaskChecklistItem' }
& Pick<TaskChecklistItem, 'id' | 'name'>
) }
);
export type UpdateTaskGroupNameMutationVariables = {
taskGroupID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateTaskGroupNameMutation = (
{ __typename?: 'Mutation' }
& { updateTaskGroupName: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id' | 'name'>
) }
);
export type ToggleTaskLabelMutationVariables = {
taskID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
@ -940,7 +1133,43 @@ export type UpdateTaskNameMutation = (
) }
);
export const TaskFieldsFragmentDoc = gql`
fragment TaskFields on Task {
id
name
description
dueDate
complete
position
taskGroup {
id
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
fullName
profileIcon {
url
initials
bgColor
}
}
}
`;
export const AssignTaskDocument = gql`
mutation assignTask($taskID: UUID!, $userID: UUID!) {
assignTask(input: {taskID: $taskID, userID: $userID}) {
@ -1103,6 +1332,7 @@ export const CreateTaskDocument = gql`
name
position
description
dueDate
taskGroup {
id
name
@ -1331,40 +1561,7 @@ export const FindProjectDocument = gql`
name
position
tasks {
id
name
position
description
dueDate
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
fullName
profileIcon {
url
initials
bgColor
}
}
...TaskFields
}
}
}
@ -1375,7 +1572,7 @@ export const FindProjectDocument = gql`
name
}
}
`;
${TaskFieldsFragmentDoc}`;
/**
* __useFindProjectQuery__
@ -1413,6 +1610,18 @@ export const FindTaskDocument = gql`
taskGroup {
id
}
checklists {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
labels {
id
assignedDate
@ -1546,6 +1755,218 @@ 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 CreateTaskChecklistItemDocument = gql`
mutation createTaskChecklistItem($taskChecklistID: UUID!, $name: String!, $position: Float!) {
createTaskChecklistItem(input: {taskChecklistID: $taskChecklistID, name: $name, position: $position}) {
id
name
taskChecklistID
position
complete
}
}
`;
export type CreateTaskChecklistItemMutationFn = ApolloReactCommon.MutationFunction<CreateTaskChecklistItemMutation, CreateTaskChecklistItemMutationVariables>;
/**
* __useCreateTaskChecklistItemMutation__
*
* To run a mutation, you first call `useCreateTaskChecklistItemMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateTaskChecklistItemMutation` 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 [createTaskChecklistItemMutation, { data, loading, error }] = useCreateTaskChecklistItemMutation({
* variables: {
* taskChecklistID: // value for 'taskChecklistID'
* name: // value for 'name'
* position: // value for 'position'
* },
* });
*/
export function useCreateTaskChecklistItemMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<CreateTaskChecklistItemMutation, CreateTaskChecklistItemMutationVariables>) {
return ApolloReactHooks.useMutation<CreateTaskChecklistItemMutation, CreateTaskChecklistItemMutationVariables>(CreateTaskChecklistItemDocument, baseOptions);
}
export type CreateTaskChecklistItemMutationHookResult = ReturnType<typeof useCreateTaskChecklistItemMutation>;
export type CreateTaskChecklistItemMutationResult = ApolloReactCommon.MutationResult<CreateTaskChecklistItemMutation>;
export type CreateTaskChecklistItemMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskChecklistItemMutation, CreateTaskChecklistItemMutationVariables>;
export const DeleteTaskChecklistItemDocument = gql`
mutation deleteTaskChecklistItem($taskChecklistItemID: UUID!) {
deleteTaskChecklistItem(input: {taskChecklistItemID: $taskChecklistItemID}) {
ok
taskChecklistItem {
id
taskChecklistID
}
}
}
`;
export type DeleteTaskChecklistItemMutationFn = ApolloReactCommon.MutationFunction<DeleteTaskChecklistItemMutation, DeleteTaskChecklistItemMutationVariables>;
/**
* __useDeleteTaskChecklistItemMutation__
*
* To run a mutation, you first call `useDeleteTaskChecklistItemMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteTaskChecklistItemMutation` 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 [deleteTaskChecklistItemMutation, { data, loading, error }] = useDeleteTaskChecklistItemMutation({
* variables: {
* taskChecklistItemID: // value for 'taskChecklistItemID'
* },
* });
*/
export function useDeleteTaskChecklistItemMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteTaskChecklistItemMutation, DeleteTaskChecklistItemMutationVariables>) {
return ApolloReactHooks.useMutation<DeleteTaskChecklistItemMutation, DeleteTaskChecklistItemMutationVariables>(DeleteTaskChecklistItemDocument, baseOptions);
}
export type DeleteTaskChecklistItemMutationHookResult = ReturnType<typeof useDeleteTaskChecklistItemMutation>;
export type DeleteTaskChecklistItemMutationResult = ApolloReactCommon.MutationResult<DeleteTaskChecklistItemMutation>;
export type DeleteTaskChecklistItemMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteTaskChecklistItemMutation, DeleteTaskChecklistItemMutationVariables>;
export const SetTaskChecklistItemCompleteDocument = gql`
mutation setTaskChecklistItemComplete($taskChecklistItemID: UUID!, $complete: Boolean!) {
setTaskChecklistItemComplete(input: {taskChecklistItemID: $taskChecklistItemID, complete: $complete}) {
id
name
taskChecklistID
complete
position
}
}
`;
export type SetTaskChecklistItemCompleteMutationFn = ApolloReactCommon.MutationFunction<SetTaskChecklistItemCompleteMutation, SetTaskChecklistItemCompleteMutationVariables>;
/**
* __useSetTaskChecklistItemCompleteMutation__
*
* To run a mutation, you first call `useSetTaskChecklistItemCompleteMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useSetTaskChecklistItemCompleteMutation` 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 [setTaskChecklistItemCompleteMutation, { data, loading, error }] = useSetTaskChecklistItemCompleteMutation({
* variables: {
* taskChecklistItemID: // value for 'taskChecklistItemID'
* complete: // value for 'complete'
* },
* });
*/
export function useSetTaskChecklistItemCompleteMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<SetTaskChecklistItemCompleteMutation, SetTaskChecklistItemCompleteMutationVariables>) {
return ApolloReactHooks.useMutation<SetTaskChecklistItemCompleteMutation, SetTaskChecklistItemCompleteMutationVariables>(SetTaskChecklistItemCompleteDocument, baseOptions);
}
export type SetTaskChecklistItemCompleteMutationHookResult = ReturnType<typeof useSetTaskChecklistItemCompleteMutation>;
export type SetTaskChecklistItemCompleteMutationResult = ApolloReactCommon.MutationResult<SetTaskChecklistItemCompleteMutation>;
export type SetTaskChecklistItemCompleteMutationOptions = ApolloReactCommon.BaseMutationOptions<SetTaskChecklistItemCompleteMutation, SetTaskChecklistItemCompleteMutationVariables>;
export const SetTaskCompleteDocument = gql`
mutation setTaskComplete($taskID: UUID!, $complete: Boolean!) {
setTaskComplete(input: {taskID: $taskID, complete: $complete}) {
...TaskFields
}
}
${TaskFieldsFragmentDoc}`;
export type SetTaskCompleteMutationFn = ApolloReactCommon.MutationFunction<SetTaskCompleteMutation, SetTaskCompleteMutationVariables>;
/**
* __useSetTaskCompleteMutation__
*
* To run a mutation, you first call `useSetTaskCompleteMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useSetTaskCompleteMutation` 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 [setTaskCompleteMutation, { data, loading, error }] = useSetTaskCompleteMutation({
* variables: {
* taskID: // value for 'taskID'
* complete: // value for 'complete'
* },
* });
*/
export function useSetTaskCompleteMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<SetTaskCompleteMutation, SetTaskCompleteMutationVariables>) {
return ApolloReactHooks.useMutation<SetTaskCompleteMutation, SetTaskCompleteMutationVariables>(SetTaskCompleteDocument, baseOptions);
}
export type SetTaskCompleteMutationHookResult = ReturnType<typeof useSetTaskCompleteMutation>;
export type SetTaskCompleteMutationResult = ApolloReactCommon.MutationResult<SetTaskCompleteMutation>;
export type SetTaskCompleteMutationOptions = ApolloReactCommon.BaseMutationOptions<SetTaskCompleteMutation, SetTaskCompleteMutationVariables>;
export const UpdateTaskChecklistItemNameDocument = gql`
mutation updateTaskChecklistItemName($taskChecklistItemID: UUID!, $name: String!) {
updateTaskChecklistItemName(input: {taskChecklistItemID: $taskChecklistItemID, name: $name}) {
id
name
}
}
`;
export type UpdateTaskChecklistItemNameMutationFn = ApolloReactCommon.MutationFunction<UpdateTaskChecklistItemNameMutation, UpdateTaskChecklistItemNameMutationVariables>;
/**
* __useUpdateTaskChecklistItemNameMutation__
*
* To run a mutation, you first call `useUpdateTaskChecklistItemNameMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateTaskChecklistItemNameMutation` 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 [updateTaskChecklistItemNameMutation, { data, loading, error }] = useUpdateTaskChecklistItemNameMutation({
* variables: {
* taskChecklistItemID: // value for 'taskChecklistItemID'
* name: // value for 'name'
* },
* });
*/
export function useUpdateTaskChecklistItemNameMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskChecklistItemNameMutation, UpdateTaskChecklistItemNameMutationVariables>) {
return ApolloReactHooks.useMutation<UpdateTaskChecklistItemNameMutation, UpdateTaskChecklistItemNameMutationVariables>(UpdateTaskChecklistItemNameDocument, baseOptions);
}
export type UpdateTaskChecklistItemNameMutationHookResult = ReturnType<typeof useUpdateTaskChecklistItemNameMutation>;
export type UpdateTaskChecklistItemNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskChecklistItemNameMutation>;
export type UpdateTaskChecklistItemNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskChecklistItemNameMutation, UpdateTaskChecklistItemNameMutationVariables>;
export const UpdateTaskGroupNameDocument = gql`
mutation updateTaskGroupName($taskGroupID: UUID!, $name: String!) {
updateTaskGroupName(input: {taskGroupID: $taskGroupID, name: $name}) {
id
name
}
}
`;
export type UpdateTaskGroupNameMutationFn = ApolloReactCommon.MutationFunction<UpdateTaskGroupNameMutation, UpdateTaskGroupNameMutationVariables>;
/**
* __useUpdateTaskGroupNameMutation__
*
* To run a mutation, you first call `useUpdateTaskGroupNameMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateTaskGroupNameMutation` 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 [updateTaskGroupNameMutation, { data, loading, error }] = useUpdateTaskGroupNameMutation({
* variables: {
* taskGroupID: // value for 'taskGroupID'
* name: // value for 'name'
* },
* });
*/
export function useUpdateTaskGroupNameMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskGroupNameMutation, UpdateTaskGroupNameMutationVariables>) {
return ApolloReactHooks.useMutation<UpdateTaskGroupNameMutation, UpdateTaskGroupNameMutationVariables>(UpdateTaskGroupNameDocument, baseOptions);
}
export type UpdateTaskGroupNameMutationHookResult = ReturnType<typeof useUpdateTaskGroupNameMutation>;
export type UpdateTaskGroupNameMutationResult = ApolloReactCommon.MutationResult<UpdateTaskGroupNameMutation>;
export type UpdateTaskGroupNameMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskGroupNameMutation, UpdateTaskGroupNameMutationVariables>;
export const ToggleTaskLabelDocument = gql`
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) {

View File

@ -4,6 +4,7 @@ mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
name
position
description
dueDate
taskGroup {
id
name

View File

@ -1,72 +0,0 @@
query findProject($projectId: String!) {
findProject(input: { projectId: $projectId }) {
name
members {
id
fullName
profileIcon {
url
initials
bgColor
}
}
labels {
id
createdDate
name
labelColor {
id
name
colorHex
position
}
}
taskGroups {
id
name
position
tasks {
id
name
position
description
dueDate
taskGroup {
id
name
position
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
fullName
profileIcon {
url
initials
bgColor
}
}
}
}
}
labelColors {
id
position
colorHex
name
}
}

View File

@ -0,0 +1,45 @@
import gql from 'graphql-tag';
import TASK_FRAGMENT from './fragments/task';
const FIND_PROJECT_QUERY = gql`
query findProject($projectId: String!) {
findProject(input: { projectId: $projectId }) {
name
members {
id
fullName
profileIcon {
url
initials
bgColor
}
}
labels {
id
createdDate
name
labelColor {
id
name
colorHex
position
}
}
taskGroups {
id
name
position
tasks {
...TaskFields
}
}
}
labelColors {
id
position
colorHex
name
}
${TASK_FRAGMENT}
}
`;

View File

@ -8,6 +8,18 @@ query findTask($taskID: UUID!) {
taskGroup {
id
}
checklists {
id
name
position
items {
id
name
taskChecklistID
complete
position
}
}
labels {
id
assignedDate

View File

@ -0,0 +1,41 @@
import gql from 'graphql-tag';
const TASK_FRAGMENT = gql`
fragment TaskFields on Task {
id
name
description
dueDate
complete
position
taskGroup {
id
}
labels {
id
assignedDate
projectLabel {
id
name
createdDate
labelColor {
id
colorHex
position
name
}
}
}
assigned {
id
fullName
profileIcon {
url
initials
bgColor
}
}
}
`;
export default TASK_FRAGMENT;

View File

@ -0,0 +1,13 @@
import gql from 'graphql-tag';
const CREATE_TASK_CHECKLIST_ITEM = gql`
mutation createTaskChecklistItem($taskChecklistID: UUID!, $name: String!, $position: Float!) {
createTaskChecklistItem(input: { taskChecklistID: $taskChecklistID, name: $name, position: $position }) {
id
name
taskChecklistID
position
complete
}
}
`;

View File

@ -0,0 +1,13 @@
import gql from 'graphql-tag';
const DELETE_TASK_CHECKLIST_ITEM = gql`
mutation deleteTaskChecklistItem($taskChecklistItemID: UUID!) {
deleteTaskChecklistItem(input: { taskChecklistItemID: $taskChecklistItemID }) {
ok
taskChecklistItem {
id
taskChecklistID
}
}
}
`;

View File

@ -0,0 +1,13 @@
import gql from 'graphql-tag';
const SET_TASK_CHECKLIST_ITEM_COMPLETE = gql`
mutation setTaskChecklistItemComplete($taskChecklistItemID: UUID!, $complete: Boolean!) {
setTaskChecklistItemComplete(input: { taskChecklistItemID: $taskChecklistItemID, complete: $complete }) {
id
name
taskChecklistID
complete
position
}
}
`;

View File

@ -0,0 +1,11 @@
import gql from 'graphql-tag';
import TASK_FRAGMENT from '../fragments/task';
const UPDATE_TASK_GROUP_NAME_MUTATION = gql`
mutation setTaskComplete($taskID: UUID!, $complete: Boolean!) {
setTaskComplete(input: { taskID: $taskID, complete: $complete }) {
...TaskFields
}
${TASK_FRAGMENT}
}
`;

View File

@ -0,0 +1,10 @@
import gql from 'graphql-tag';
const UPDATE_TASK_CHECKLIST_ITEM_NAME = gql`
mutation updateTaskChecklistItemName($taskChecklistItemID: UUID!, $name: String!) {
updateTaskChecklistItemName(input: { taskChecklistItemID: $taskChecklistItemID, name: $name }) {
id
name
}
}
`;

View File

@ -0,0 +1,12 @@
import gql from 'graphql-tag';
import TASK_FRAGMENT from '../fragments/task';
const UPDATE_TASK_GROUP_NAME_MUTATION = gql`
mutation updateTaskGroupName($taskGroupID: UUID!, $name: String!) {
updateTaskGroupName(input:{taskGroupID:$taskGroupID, name:$name}) {
id
name
}
${TASK_FRAGMENT}
}
`;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const AccountPlus: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 640 512">
<path d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4zm323-128.4l-27.8-28.1c-4.6-4.7-12.1-4.7-16.8-.1l-104.8 104-45.5-45.8c-4.6-4.7-12.1-4.7-16.8-.1l-28.1 27.9c-4.7 4.6-4.7 12.1-.1 16.8l81.7 82.3c4.6 4.7 12.1 4.7 16.8.1l141.3-140.2c4.6-4.7 4.7-12.2.1-16.8z" />
</Icon>
);
};
export default AccountPlus;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const CheckCircle: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
<path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" />
</Icon>
);
};
export default CheckCircle;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const CheckSquare: React.FC<IconProps> = ({ width = '16px', height = '16px', onClick, className }) => {
return (
<Icon width={width} onClick={onClick} height={height} className={className} viewBox="0 0 448 512">
<path d="M400 480H48c-26.51 0-48-21.49-48-48V80c0-26.51 21.49-48 48-48h352c26.51 0 48 21.49 48 48v352c0 26.51-21.49 48-48 48zm-204.686-98.059l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.248-16.379-6.249-22.628 0L184 302.745l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.25 16.379 6.25 22.628.001z" />
</Icon>
);
};
export default CheckSquare;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const CheckSquareOutline: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 448 512">
<path d="M400 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V80c0-26.51-21.49-48-48-48zm0 400H48V80h352v352zm-35.864-241.724L191.547 361.48c-4.705 4.667-12.303 4.637-16.97-.068l-90.781-91.516c-4.667-4.705-4.637-12.303.069-16.971l22.719-22.536c4.705-4.667 12.303-4.637 16.97.069l59.792 60.277 141.352-140.216c4.705-4.667 12.303-4.637 16.97.068l22.536 22.718c4.667 4.706 4.637 12.304-.068 16.971z" />
</Icon>
);
};
export default CheckSquareOutline;

View File

@ -1,21 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Checkmark = ({ size, color }: Props) => {
const Checkmark: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 16 16">
<Icon width={width} height={height} className={className} viewBox="0 0 16 16">
<path d="M13.5 2l-7.5 7.5-3.5-3.5-2.5 2.5 6 6 10-10z" />
</svg>
</Icon>
);
};
Checkmark.defaultProps = {
size: 16,
color: '#000',
};
export default Checkmark;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const Clock: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 512 512">
<path d="M256,8C119,8,8,119,8,256S119,504,256,504,504,393,504,256,393,8,256,8Zm92.49,313h0l-20,25a16,16,0,0,1-22.49,2.5h0l-67-49.72a40,40,0,0,1-15-31.23V112a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16V256l58,42.5A16,16,0,0,1,348.49,321Z" />
</Icon>
);
};
export default Clock;

View File

@ -1,21 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Cross = ({ size, color }: Props) => {
const Cross: React.FC<IconProps> = ({ width = '16px', height = '16px', className, onClick }) => {
return (
<svg fill={color} width={size} height={size} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512">
<Icon width={width} height={height} onClick={onClick} className={className} viewBox="0 0 352 512">
<path d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z" />
</svg>
</Icon>
);
};
Cross.defaultProps = {
size: 16,
color: '#000',
};
export default Cross;

View File

@ -4,6 +4,8 @@ import styled from 'styled-components/macro';
export type IconProps = {
width: number | string;
height: number | string;
className?: string;
onClick?: () => void;
};
type Props = {
@ -11,15 +13,27 @@ type Props = {
height: number | string;
viewBox: string;
className?: string;
onClick?: () => void;
};
const Svg = styled.svg`
fill: rgba(${props => props.theme.colors.text.primary});
`;
const Icon: React.FC<Props> = ({ width, height, viewBox, className, children }) => {
const Icon: React.FC<Props> = ({ width, height, viewBox, className, onClick, children }) => {
return (
<Svg className={className} width={width} height={height} xmlns="http://www.w3.org/2000/svg" viewBox={viewBox}>
<Svg
onClick={e => {
if (onClick) {
onClick();
}
}}
className={className}
width={width}
height={height}
xmlns="http://www.w3.org/2000/svg"
viewBox={viewBox}
>
{children}
</Svg>
);

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const Square: React.FC<IconProps> = ({ width = '16px', height = '16px', className, onClick }) => {
return (
<Icon onClick={onClick} width={width} height={height} className={className} viewBox="0 0 448 512">
<path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-6 400H54c-3.3 0-6-2.7-6-6V86c0-3.3 2.7-6 6-6h340c3.3 0 6 2.7 6 6v340c0 3.3-2.7 6-6 6z" />
</Icon>
);
};
export default Square;

View File

@ -0,0 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
const Trash: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
return (
<Icon width={width} height={height} className={className} viewBox="0 0 448 512">
<path d="M432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16zM53.2 467a48 48 0 0 0 47.9 45h245.8a48 48 0 0 0 47.9-45L416 128H32z" />
</Icon>
);
};
export default Trash;

View File

@ -1,6 +1,13 @@
import Cross from './Cross';
import Cog from './Cog';
import Trash from './Trash';
import CheckCircle from './CheckCircle';
import Clock from './Clock';
import AccountPlus from './AccountPlus';
import CheckSquare from './CheckSquare';
import ArrowLeft from './ArrowLeft';
import CheckSquareOutline from './CheckSquareOutline';
import Square from './Square';
import Bolt from './Bolt';
import Plus from './Plus';
import Bell from './Bell';
@ -42,8 +49,15 @@ export {
Citadel,
Checkmark,
User,
Trash,
Users,
Lock,
ArrowLeft,
CheckCircle,
AccountPlus,
Clock,
CheckSquareOutline,
CheckSquare,
Square,
ToggleOn,
};