feature: add checklist
This commit is contained in:
parent
b6f0e8b6b2
commit
9d6c67f791
File diff suppressed because it is too large
Load Diff
@ -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"`
|
||||
|
@ -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!
|
||||
|
@ -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 }
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -0,0 +1 @@
|
||||
ALTER TABLE task ADD COLUMN complete boolean NOT NULL DEFAULT FALSE;
|
7
api/migrations/0029_add-task_checklist.up.sql
Normal file
7
api/migrations/0029_add-task_checklist.up.sql
Normal 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
|
||||
);
|
9
api/migrations/0030_add-task_checklist_item.up.sql
Normal file
9
api/migrations/0030_add-task_checklist_item.up.sql
Normal 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
|
||||
);
|
@ -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"`
|
||||
|
12
api/pg/pg.go
12
api/pg/pg.go
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
219
api/pg/task_checklist.sql.go
Normal file
219
api/pg/task_checklist.sql.go
Normal 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
|
||||
}
|
@ -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 *;
|
||||
|
27
api/query/task_checklist.sql
Normal file
27
api/query/task_checklist.sql
Normal 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
1
api/trello.json
Normal file
File diff suppressed because one or more lines are too long
@ -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:
|
||||
|
@ -112,7 +112,7 @@ export default createGlobalStyle`
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
`;
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}}
|
||||
|
@ -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
|
||||
|
1
web/src/citadel.d.ts
vendored
1
web/src/citadel.d.ts
vendored
@ -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
19
web/src/projects.d.ts
vendored
@ -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 = {
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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;
|
||||
`;
|
||||
|
@ -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 && (
|
||||
|
138
web/src/shared/components/Checklist/Checklist.stories.tsx
Normal file
138
web/src/shared/components/Checklist/Checklist.stories.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
596
web/src/shared/components/Checklist/index.tsx
Normal file
596
web/src/shared/components/Checklist/index.tsx
Normal 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;
|
@ -64,6 +64,7 @@ export const Default = () => {
|
||||
}}
|
||||
onCancel={action('cancel')}
|
||||
onDueDateChange={action('due date change')}
|
||||
onRemoveDueDate={action('remove due date')}
|
||||
/>
|
||||
</Popup>
|
||||
</PopupWrapper>
|
||||
|
@ -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;
|
||||
`;
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
|
@ -232,7 +232,7 @@ const NewProject: React.FC<NewProjectProps> = ({ teams, onClose, onCreateProject
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<Cross color="#c2c6dc" />
|
||||
<Cross width={16} height={16} />
|
||||
</HeaderRight>
|
||||
</Header>
|
||||
<Container>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 },
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
`;
|
||||
|
@ -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 => {
|
||||
|
@ -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`
|
||||
|
@ -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')}
|
||||
/>
|
||||
|
@ -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}>
|
||||
|
@ -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}) {
|
||||
|
@ -4,6 +4,7 @@ mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
|
||||
name
|
||||
position
|
||||
description
|
||||
dueDate
|
||||
taskGroup {
|
||||
id
|
||||
name
|
||||
|
@ -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
|
||||
}
|
||||
}
|
45
web/src/shared/graphql/findProject.ts
Normal file
45
web/src/shared/graphql/findProject.ts
Normal 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}
|
||||
}
|
||||
`;
|
@ -8,6 +8,18 @@ query findTask($taskID: UUID!) {
|
||||
taskGroup {
|
||||
id
|
||||
}
|
||||
checklists {
|
||||
id
|
||||
name
|
||||
position
|
||||
items {
|
||||
id
|
||||
name
|
||||
taskChecklistID
|
||||
complete
|
||||
position
|
||||
}
|
||||
}
|
||||
labels {
|
||||
id
|
||||
assignedDate
|
||||
|
41
web/src/shared/graphql/fragments/task.ts
Normal file
41
web/src/shared/graphql/fragments/task.ts
Normal 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;
|
13
web/src/shared/graphql/task/createTaskChecklistItem.ts
Normal file
13
web/src/shared/graphql/task/createTaskChecklistItem.ts
Normal 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
|
||||
}
|
||||
}
|
||||
`;
|
13
web/src/shared/graphql/task/deleteTaskChecklistItem.ts
Normal file
13
web/src/shared/graphql/task/deleteTaskChecklistItem.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
13
web/src/shared/graphql/task/setTaskChecklistItemComplete.ts
Normal file
13
web/src/shared/graphql/task/setTaskChecklistItemComplete.ts
Normal 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
|
||||
}
|
||||
}
|
||||
`;
|
11
web/src/shared/graphql/task/setTaskComplete.ts
Normal file
11
web/src/shared/graphql/task/setTaskComplete.ts
Normal 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}
|
||||
}
|
||||
`;
|
10
web/src/shared/graphql/task/updateTaskChecklistItemName.ts
Normal file
10
web/src/shared/graphql/task/updateTaskChecklistItemName.ts
Normal 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
|
||||
}
|
||||
}
|
||||
`;
|
12
web/src/shared/graphql/taskGroup/updateTaskGroupName.ts
Normal file
12
web/src/shared/graphql/taskGroup/updateTaskGroupName.ts
Normal 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}
|
||||
}
|
||||
`;
|
12
web/src/shared/icons/AccountPlus.tsx
Normal file
12
web/src/shared/icons/AccountPlus.tsx
Normal 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;
|
12
web/src/shared/icons/CheckCircle.tsx
Normal file
12
web/src/shared/icons/CheckCircle.tsx
Normal 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;
|
12
web/src/shared/icons/CheckSquare.tsx
Normal file
12
web/src/shared/icons/CheckSquare.tsx
Normal 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;
|
12
web/src/shared/icons/CheckSquareOutline.tsx
Normal file
12
web/src/shared/icons/CheckSquareOutline.tsx
Normal 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;
|
@ -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;
|
||||
|
12
web/src/shared/icons/Clock.tsx
Normal file
12
web/src/shared/icons/Clock.tsx
Normal 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;
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
|
12
web/src/shared/icons/Square.tsx
Normal file
12
web/src/shared/icons/Square.tsx
Normal 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;
|
12
web/src/shared/icons/Trash.tsx
Normal file
12
web/src/shared/icons/Trash.tsx
Normal 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;
|
@ -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,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user