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"`
|
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 {
|
type DeleteProjectLabel struct {
|
||||||
ProjectLabelID uuid.UUID `json:"projectLabelID"`
|
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 {
|
type DeleteTaskGroupInput struct {
|
||||||
TaskGroupID uuid.UUID `json:"taskGroupID"`
|
TaskGroupID uuid.UUID `json:"taskGroupID"`
|
||||||
}
|
}
|
||||||
@ -129,6 +155,20 @@ type RemoveTaskLabelInput struct {
|
|||||||
TaskLabelID uuid.UUID `json:"taskLabelID"`
|
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 {
|
type ToggleTaskLabelInput struct {
|
||||||
TaskID uuid.UUID `json:"taskID"`
|
TaskID uuid.UUID `json:"taskID"`
|
||||||
ProjectLabelID uuid.UUID `json:"projectLabelID"`
|
ProjectLabelID uuid.UUID `json:"projectLabelID"`
|
||||||
@ -165,6 +205,11 @@ type UpdateProjectName struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateTaskChecklistItemName struct {
|
||||||
|
TaskChecklistItemID uuid.UUID `json:"taskChecklistItemID"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateTaskDescriptionInput struct {
|
type UpdateTaskDescriptionInput struct {
|
||||||
TaskID uuid.UUID `json:"taskID"`
|
TaskID uuid.UUID `json:"taskID"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
@ -77,6 +77,15 @@ type TaskGroup {
|
|||||||
tasks: [Task!]!
|
tasks: [Task!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChecklistBadge {
|
||||||
|
complete: Int!
|
||||||
|
total: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskBadges {
|
||||||
|
checklist: ChecklistBadge
|
||||||
|
}
|
||||||
|
|
||||||
type Task {
|
type Task {
|
||||||
id: ID!
|
id: ID!
|
||||||
taskGroup: TaskGroup!
|
taskGroup: TaskGroup!
|
||||||
@ -85,8 +94,11 @@ type Task {
|
|||||||
position: Float!
|
position: Float!
|
||||||
description: String
|
description: String
|
||||||
dueDate: Time
|
dueDate: Time
|
||||||
|
complete: Boolean!
|
||||||
assigned: [ProjectMember!]!
|
assigned: [ProjectMember!]!
|
||||||
labels: [TaskLabel!]!
|
labels: [TaskLabel!]!
|
||||||
|
checklists: [TaskChecklist!]!
|
||||||
|
badges: TaskBadges!
|
||||||
}
|
}
|
||||||
|
|
||||||
input ProjectsFilter {
|
input ProjectsFilter {
|
||||||
@ -188,6 +200,27 @@ type DeleteTaskGroupPayload {
|
|||||||
taskGroup: TaskGroup!
|
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 {
|
input AssignTaskInput {
|
||||||
taskID: UUID!
|
taskID: UUID!
|
||||||
userID: UUID!
|
userID: UUID!
|
||||||
@ -267,6 +300,36 @@ input UpdateTaskDueDate {
|
|||||||
dueDate: Time
|
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 {
|
type Mutation {
|
||||||
createRefreshToken(input: NewRefreshToken!): RefreshToken!
|
createRefreshToken(input: NewRefreshToken!): RefreshToken!
|
||||||
|
|
||||||
@ -293,10 +356,17 @@ type Mutation {
|
|||||||
removeTaskLabel(input: RemoveTaskLabelInput): Task!
|
removeTaskLabel(input: RemoveTaskLabelInput): Task!
|
||||||
toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
|
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!
|
createTask(input: NewTask!): Task!
|
||||||
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
|
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
|
||||||
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
|
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
|
||||||
updateTaskName(input: UpdateTaskName!): Task!
|
updateTaskName(input: UpdateTaskName!): Task!
|
||||||
|
setTaskComplete(input: SetTaskComplete!): Task!
|
||||||
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
|
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
|
||||||
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
|
||||||
assignTask(input: AssignTaskInput): Task!
|
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) {
|
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) {
|
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
|
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) {
|
func (r *mutationResolver) CreateTask(ctx context.Context, input NewTask) (*pg.Task, error) {
|
||||||
taskGroupID, err := uuid.Parse(input.TaskGroupID)
|
taskGroupID, err := uuid.Parse(input.TaskGroupID)
|
||||||
createdAt := time.Now().UTC()
|
createdAt := time.Now().UTC()
|
||||||
@ -260,6 +333,14 @@ func (r *mutationResolver) UpdateTaskName(ctx context.Context, input UpdateTaskN
|
|||||||
return &task, err
|
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) {
|
func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTaskDueDate) (*pg.Task, error) {
|
||||||
var dueDate sql.NullTime
|
var dueDate sql.NullTime
|
||||||
if input.DueDate == nil {
|
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)
|
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) {
|
func (r *taskGroupResolver) ID(ctx context.Context, obj *pg.TaskGroup) (uuid.UUID, error) {
|
||||||
return obj.TaskGroupID, nil
|
return obj.TaskGroupID, nil
|
||||||
}
|
}
|
||||||
@ -591,6 +717,12 @@ func (r *Resolver) RefreshToken() RefreshTokenResolver { return &refreshTokenRes
|
|||||||
// Task returns TaskResolver implementation.
|
// Task returns TaskResolver implementation.
|
||||||
func (r *Resolver) Task() TaskResolver { return &taskResolver{r} }
|
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.
|
// TaskGroup returns TaskGroupResolver implementation.
|
||||||
func (r *Resolver) TaskGroup() TaskGroupResolver { return &taskGroupResolver{r} }
|
func (r *Resolver) TaskGroup() TaskGroupResolver { return &taskGroupResolver{r} }
|
||||||
|
|
||||||
@ -610,6 +742,8 @@ type projectLabelResolver struct{ *Resolver }
|
|||||||
type queryResolver struct{ *Resolver }
|
type queryResolver struct{ *Resolver }
|
||||||
type refreshTokenResolver struct{ *Resolver }
|
type refreshTokenResolver struct{ *Resolver }
|
||||||
type taskResolver struct{ *Resolver }
|
type taskResolver struct{ *Resolver }
|
||||||
|
type taskChecklistResolver struct{ *Resolver }
|
||||||
|
type taskChecklistItemResolver struct{ *Resolver }
|
||||||
type taskGroupResolver struct{ *Resolver }
|
type taskGroupResolver struct{ *Resolver }
|
||||||
type taskLabelResolver struct{ *Resolver }
|
type taskLabelResolver struct{ *Resolver }
|
||||||
type teamResolver 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"`
|
Position float64 `json:"position"`
|
||||||
Description sql.NullString `json:"description"`
|
Description sql.NullString `json:"description"`
|
||||||
DueDate sql.NullTime `json:"due_date"`
|
DueDate sql.NullTime `json:"due_date"`
|
||||||
|
Complete bool `json:"complete"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskAssigned struct {
|
type TaskAssigned struct {
|
||||||
@ -62,6 +63,24 @@ type TaskAssigned struct {
|
|||||||
AssignedDate time.Time `json:"assigned_date"`
|
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 {
|
type TaskGroup struct {
|
||||||
TaskGroupID uuid.UUID `json:"task_group_id"`
|
TaskGroupID uuid.UUID `json:"task_group_id"`
|
||||||
ProjectID uuid.UUID `json:"project_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)
|
GetAllProjectsForTeam(ctx context.Context, teamID uuid.UUID) ([]Project, error)
|
||||||
GetProjectByID(ctx context.Context, projectID 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)
|
UpdateUserAccountProfileAvatarURL(ctx context.Context, arg UpdateUserAccountProfileAvatarURLParams) (UserAccount, error)
|
||||||
|
|
||||||
CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error)
|
CreateUserAccount(ctx context.Context, arg CreateUserAccountParams) (UserAccount, error)
|
||||||
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
|
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
|
||||||
GetUserAccountByUsername(ctx context.Context, username string) (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)
|
GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, error)
|
||||||
|
|
||||||
|
SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams) (Task, error)
|
||||||
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
|
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
|
||||||
GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error)
|
GetTaskLabelForTaskByProjectLabelID(ctx context.Context, arg GetTaskLabelForTaskByProjectLabelIDParams) (TaskLabel, error)
|
||||||
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
|
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
|
||||||
@ -47,6 +58,7 @@ type Repository interface {
|
|||||||
DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
|
DeleteRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) error
|
||||||
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
|
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
|
||||||
|
|
||||||
|
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
|
||||||
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
|
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
|
||||||
DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
|
DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
|
||||||
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
|
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
|
||||||
|
@ -16,6 +16,8 @@ type Querier interface {
|
|||||||
CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
|
CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshToken, error)
|
||||||
CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
|
CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error)
|
||||||
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
|
CreateTaskAssigned(ctx context.Context, arg CreateTaskAssignedParams) (TaskAssigned, error)
|
||||||
|
CreateTaskChecklist(ctx context.Context, arg CreateTaskChecklistParams) (TaskChecklist, error)
|
||||||
|
CreateTaskChecklistItem(ctx context.Context, arg CreateTaskChecklistItemParams) (TaskChecklistItem, error)
|
||||||
CreateTaskGroup(ctx context.Context, arg CreateTaskGroupParams) (TaskGroup, error)
|
CreateTaskGroup(ctx context.Context, arg CreateTaskGroupParams) (TaskGroup, error)
|
||||||
CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabelForTaskParams) (TaskLabel, error)
|
CreateTaskLabelForTask(ctx context.Context, arg CreateTaskLabelForTaskParams) (TaskLabel, error)
|
||||||
CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error)
|
CreateTeam(ctx context.Context, arg CreateTeamParams) (Team, error)
|
||||||
@ -26,6 +28,7 @@ type Querier interface {
|
|||||||
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
|
DeleteRefreshTokenByUserID(ctx context.Context, userID uuid.UUID) error
|
||||||
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
|
DeleteTaskAssignedByID(ctx context.Context, arg DeleteTaskAssignedByIDParams) (TaskAssigned, error)
|
||||||
DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error
|
DeleteTaskByID(ctx context.Context, taskID uuid.UUID) error
|
||||||
|
DeleteTaskChecklistItem(ctx context.Context, taskChecklistItemID uuid.UUID) error
|
||||||
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
|
DeleteTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (int64, error)
|
||||||
DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error
|
DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error
|
||||||
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
|
DeleteTaskLabelForTaskByProjectLabelID(ctx context.Context, arg DeleteTaskLabelForTaskByProjectLabelIDParams) error
|
||||||
@ -46,6 +49,9 @@ type Querier interface {
|
|||||||
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
|
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
|
||||||
GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
|
GetRefreshTokenByID(ctx context.Context, tokenID uuid.UUID) (RefreshToken, error)
|
||||||
GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error)
|
GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error)
|
||||||
|
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)
|
GetTaskGroupByID(ctx context.Context, taskGroupID uuid.UUID) (TaskGroup, error)
|
||||||
GetTaskGroupsForProject(ctx context.Context, projectID uuid.UUID) ([]TaskGroup, error)
|
GetTaskGroupsForProject(ctx context.Context, projectID uuid.UUID) ([]TaskGroup, error)
|
||||||
GetTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) (TaskLabel, 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)
|
GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error)
|
||||||
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
|
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
|
||||||
GetUserAccountByUsername(ctx context.Context, username string) (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)
|
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
|
||||||
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
|
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
|
||||||
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
|
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
|
||||||
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
|
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
|
||||||
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, 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)
|
UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescriptionParams) (Task, error)
|
||||||
UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error)
|
UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error)
|
||||||
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
|
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
const createTask = `-- name: CreateTask :one
|
const createTask = `-- name: CreateTask :one
|
||||||
INSERT INTO task (task_group_id, created_at, name, position)
|
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 {
|
type CreateTaskParams struct {
|
||||||
@ -39,6 +39,7 @@ func (q *Queries) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, e
|
|||||||
&i.Position,
|
&i.Position,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
|
&i.Complete,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@ -65,7 +66,7 @@ func (q *Queries) DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getAllTasks = `-- name: GetAllTasks :many
|
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) {
|
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.Position,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
|
&i.Complete,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -100,7 +102,7 @@ func (q *Queries) GetAllTasks(ctx context.Context) ([]Task, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getTaskByID = `-- name: GetTaskByID :one
|
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) {
|
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.Position,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
|
&i.Complete,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTasksForTaskGroupID = `-- name: GetTasksForTaskGroupID :many
|
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) {
|
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.Position,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
|
&i.Complete,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -153,8 +157,33 @@ func (q *Queries) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.U
|
|||||||
return items, nil
|
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
|
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 {
|
type UpdateTaskDescriptionParams struct {
|
||||||
@ -173,12 +202,13 @@ func (q *Queries) UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescr
|
|||||||
&i.Position,
|
&i.Position,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
|
&i.Complete,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTaskDueDate = `-- name: UpdateTaskDueDate :one
|
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 {
|
type UpdateTaskDueDateParams struct {
|
||||||
@ -197,12 +227,13 @@ func (q *Queries) UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDatePa
|
|||||||
&i.Position,
|
&i.Position,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
|
&i.Complete,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTaskLocation = `-- name: UpdateTaskLocation :one
|
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 {
|
type UpdateTaskLocationParams struct {
|
||||||
@ -222,12 +253,13 @@ func (q *Queries) UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocation
|
|||||||
&i.Position,
|
&i.Position,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
|
&i.Complete,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTaskName = `-- name: UpdateTaskName :one
|
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 {
|
type UpdateTaskNameParams struct {
|
||||||
@ -246,6 +278,7 @@ func (q *Queries) UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams)
|
|||||||
&i.Position,
|
&i.Position,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
|
&i.Complete,
|
||||||
)
|
)
|
||||||
return i, err
|
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
|
-- name: UpdateTaskDueDate :one
|
||||||
UPDATE task SET due_date = $2 WHERE task_id = $1 RETURNING *;
|
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
|
overwrite: true
|
||||||
schema:
|
schema:
|
||||||
- '../api/graph/schema.graphqls'
|
- '../api/graph/schema.graphqls'
|
||||||
documents: 'src/shared/graphql/*.graphqls'
|
documents:
|
||||||
|
- 'src/shared/graphql/*.graphqls'
|
||||||
|
- 'src/shared/graphql/**/*.ts'
|
||||||
generates:
|
generates:
|
||||||
src/shared/generated/graphql.tsx:
|
src/shared/generated/graphql.tsx:
|
||||||
plugins:
|
plugins:
|
||||||
|
@ -112,7 +112,7 @@ export default createGlobalStyle`
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 12px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
|
@ -10,9 +10,12 @@ import Profile from 'Profile';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const MainContent = styled.div`
|
const MainContent = styled.div`
|
||||||
padding: 0 0 50px 80px;
|
padding: 0 0 0 80px;
|
||||||
background: #262c49;
|
background: #262c49;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
`;
|
`;
|
||||||
type RoutesProps = {
|
type RoutesProps = {
|
||||||
history: H.History;
|
history: H.History;
|
||||||
|
@ -26,23 +26,3 @@ const theme: DefaultTheme = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export { theme };
|
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 styled, { ThemeProvider } from 'styled-components';
|
||||||
import NormalizeStyles from './NormalizeStyles';
|
import NormalizeStyles from './NormalizeStyles';
|
||||||
import BaseStyles from './BaseStyles';
|
import BaseStyles from './BaseStyles';
|
||||||
import ThemeStyles, { theme } from './ThemeStyles';
|
import { theme } from './ThemeStyles';
|
||||||
import Routes from './Routes';
|
import Routes from './Routes';
|
||||||
import { UserIDContext } from './context';
|
import { UserIDContext } from './context';
|
||||||
import Navbar from './Navbar';
|
import Navbar from './Navbar';
|
||||||
@ -44,7 +44,6 @@ const App = () => {
|
|||||||
<PopupProvider>
|
<PopupProvider>
|
||||||
<NormalizeStyles />
|
<NormalizeStyles />
|
||||||
<BaseStyles />
|
<BaseStyles />
|
||||||
<ThemeStyles />
|
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div>loading</div>
|
<div>loading</div>
|
||||||
|
@ -9,10 +9,16 @@ import {
|
|||||||
useUpdateTaskDueDateMutation,
|
useUpdateTaskDueDateMutation,
|
||||||
useAssignTaskMutation,
|
useAssignTaskMutation,
|
||||||
useUnassignTaskMutation,
|
useUnassignTaskMutation,
|
||||||
|
useSetTaskChecklistItemCompleteMutation,
|
||||||
|
useDeleteTaskChecklistItemMutation,
|
||||||
|
useUpdateTaskChecklistItemNameMutation,
|
||||||
|
useCreateTaskChecklistItemMutation,
|
||||||
|
FindTaskDocument,
|
||||||
} from 'shared/generated/graphql';
|
} from 'shared/generated/graphql';
|
||||||
import UserIDContext from 'App/context';
|
import UserIDContext from 'App/context';
|
||||||
import MiniProfile from 'shared/components/MiniProfile';
|
import MiniProfile from 'shared/components/MiniProfile';
|
||||||
import DueDateManager from 'shared/components/DueDateManager';
|
import DueDateManager from 'shared/components/DueDateManager';
|
||||||
|
import produce from 'immer';
|
||||||
|
|
||||||
type DetailsProps = {
|
type DetailsProps = {
|
||||||
taskID: string;
|
taskID: string;
|
||||||
@ -43,6 +49,64 @@ const Details: React.FC<DetailsProps> = ({
|
|||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
const [currentMemberTask, setCurrentMemberTask] = useState('');
|
const [currentMemberTask, setCurrentMemberTask] = useState('');
|
||||||
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
|
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 { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
|
||||||
const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
|
const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
|
||||||
onCompleted: () => {
|
onCompleted: () => {
|
||||||
@ -83,7 +147,19 @@ const Details: React.FC<DetailsProps> = ({
|
|||||||
onTaskNameChange={onTaskNameChange}
|
onTaskNameChange={onTaskNameChange}
|
||||||
onTaskDescriptionChange={onTaskDescriptionChange}
|
onTaskDescriptionChange={onTaskDescriptionChange}
|
||||||
onDeleteTask={onDeleteTask}
|
onDeleteTask={onDeleteTask}
|
||||||
|
onChangeItemName={(itemID, itemName) => {
|
||||||
|
updateTaskChecklistItemName({ variables: { taskChecklistItemID: itemID, name: itemName } });
|
||||||
|
}}
|
||||||
onCloseModal={() => history.push(projectURL)}
|
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) => {
|
onMemberProfile={($targetRef, memberID) => {
|
||||||
const member = data.findTask.assigned.find(m => m.id === memberID);
|
const member = data.findTask.assigned.find(m => m.id === memberID);
|
||||||
const profileIcon = member ? member.profileIcon : null;
|
const profileIcon = member ? member.profileIcon : null;
|
||||||
@ -124,7 +200,6 @@ const Details: React.FC<DetailsProps> = ({
|
|||||||
onOpenDueDatePopop={(task, $targetRef) => {
|
onOpenDueDatePopop={(task, $targetRef) => {
|
||||||
showPopup(
|
showPopup(
|
||||||
$targetRef,
|
$targetRef,
|
||||||
|
|
||||||
<Popup
|
<Popup
|
||||||
title={'Change Due Date'}
|
title={'Change Due Date'}
|
||||||
tab={0}
|
tab={0}
|
||||||
@ -134,8 +209,11 @@ const Details: React.FC<DetailsProps> = ({
|
|||||||
>
|
>
|
||||||
<DueDateManager
|
<DueDateManager
|
||||||
task={task}
|
task={task}
|
||||||
|
onRemoveDueDate={t => {
|
||||||
|
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null } });
|
||||||
|
hidePopup();
|
||||||
|
}}
|
||||||
onDueDateChange={(t, newDueDate) => {
|
onDueDateChange={(t, newDueDate) => {
|
||||||
console.log(`${newDueDate}`);
|
|
||||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
|
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
|
||||||
hidePopup();
|
hidePopup();
|
||||||
}}
|
}}
|
||||||
|
@ -5,9 +5,11 @@ import { Bolt, ToggleOn, Tags } from 'shared/icons';
|
|||||||
import { usePopup, Popup } from 'shared/components/PopupMenu';
|
import { usePopup, Popup } from 'shared/components/PopupMenu';
|
||||||
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom';
|
import { useParams, Route, useRouteMatch, useHistory, RouteComponentProps } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
|
useSetTaskCompleteMutation,
|
||||||
useToggleTaskLabelMutation,
|
useToggleTaskLabelMutation,
|
||||||
useUpdateProjectNameMutation,
|
useUpdateProjectNameMutation,
|
||||||
useFindProjectQuery,
|
useFindProjectQuery,
|
||||||
|
useUpdateTaskGroupNameMutation,
|
||||||
useUpdateTaskNameMutation,
|
useUpdateTaskNameMutation,
|
||||||
useUpdateProjectLabelMutation,
|
useUpdateProjectLabelMutation,
|
||||||
useCreateTaskMutation,
|
useCreateTaskMutation,
|
||||||
@ -23,6 +25,7 @@ import {
|
|||||||
FindProjectDocument,
|
FindProjectDocument,
|
||||||
useCreateProjectLabelMutation,
|
useCreateProjectLabelMutation,
|
||||||
useUnassignTaskMutation,
|
useUnassignTaskMutation,
|
||||||
|
useUpdateTaskDueDateMutation,
|
||||||
} from 'shared/generated/graphql';
|
} from 'shared/generated/graphql';
|
||||||
|
|
||||||
import TaskAssignee from 'shared/components/TaskAssignee';
|
import TaskAssignee from 'shared/components/TaskAssignee';
|
||||||
@ -40,6 +43,7 @@ import MiniProfile from 'shared/components/MiniProfile';
|
|||||||
import Details from './Details';
|
import Details from './Details';
|
||||||
import { useApolloClient } from '@apollo/react-hooks';
|
import { useApolloClient } from '@apollo/react-hooks';
|
||||||
import UserIDContext from 'App/context';
|
import UserIDContext from 'App/context';
|
||||||
|
import DueDateManager from 'shared/components/DueDateManager';
|
||||||
|
|
||||||
const getCacheData = (client: any, projectID: string) => {
|
const getCacheData = (client: any, projectID: string) => {
|
||||||
const cacheData: any = client.readQuery({
|
const cacheData: any = client.readQuery({
|
||||||
@ -69,6 +73,7 @@ interface QuickCardEditorState {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
left: number;
|
left: number;
|
||||||
top: number;
|
top: number;
|
||||||
|
width: number;
|
||||||
taskID: string | null;
|
taskID: string | null;
|
||||||
taskGroupID: string | null;
|
taskGroupID: string | null;
|
||||||
}
|
}
|
||||||
@ -209,6 +214,7 @@ const initialQuickCardEditorState: QuickCardEditorState = {
|
|||||||
isOpen: false,
|
isOpen: false,
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
width: 272,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectBar = styled.div`
|
const ProjectBar = styled.div`
|
||||||
@ -367,12 +373,36 @@ const Project = () => {
|
|||||||
if (taskGroup) {
|
if (taskGroup) {
|
||||||
let position = 65535;
|
let position = 65535;
|
||||||
if (taskGroup.tasks.length !== 0) {
|
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;
|
position = Math.ceil(lastTask.position) * 2 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`position ${position}`);
|
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 [assignTask] = useAssignTaskMutation();
|
||||||
const [unassignTask] = useUnassignTaskMutation();
|
const [unassignTask] = useUnassignTaskMutation();
|
||||||
|
|
||||||
|
const [updateTaskGroupName] = useUpdateTaskGroupNameMutation({});
|
||||||
|
|
||||||
|
const [updateTaskDueDate] = useUpdateTaskDueDateMutation();
|
||||||
|
|
||||||
const [updateProjectName] = useUpdateProjectNameMutation({
|
const [updateProjectName] = useUpdateProjectNameMutation({
|
||||||
update: (client, newName) => {
|
update: (client, newName) => {
|
||||||
const cacheData = getCacheData(client, projectID);
|
const cacheData = getCacheData(client, projectID);
|
||||||
@ -421,6 +455,8 @@ const Project = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [setTaskComplete] = useSetTaskCompleteMutation();
|
||||||
|
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const { userID } = useContext(UserIDContext);
|
const { userID } = useContext(UserIDContext);
|
||||||
|
|
||||||
@ -441,6 +477,7 @@ const Project = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (data) {
|
if (data) {
|
||||||
|
console.log(data.findProject);
|
||||||
const onQuickEditorOpen = (e: ContextMenuEvent) => {
|
const onQuickEditorOpen = (e: ContextMenuEvent) => {
|
||||||
const taskGroup = data.findProject.taskGroups.find(t => t.id === e.taskGroupID);
|
const taskGroup = data.findProject.taskGroups.find(t => t.id === e.taskGroupID);
|
||||||
const currentTask = taskGroup ? taskGroup.tasks.find(t => t.id === e.taskID) : null;
|
const currentTask = taskGroup ? taskGroup.tasks.find(t => t.id === e.taskID) : null;
|
||||||
@ -448,6 +485,7 @@ const Project = () => {
|
|||||||
setQuickCardEditor({
|
setQuickCardEditor({
|
||||||
top: e.top,
|
top: e.top,
|
||||||
left: e.left,
|
left: e.left,
|
||||||
|
width: e.width,
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
taskID: currentTask.id,
|
taskID: currentTask.id,
|
||||||
taskGroupID: currentTask.taskGroup.id,
|
taskGroupID: currentTask.taskGroup.id,
|
||||||
@ -567,7 +605,9 @@ const Project = () => {
|
|||||||
</Popup>,
|
</Popup>,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onChangeTaskGroupName={(taskGroupID, name) => {}}
|
onChangeTaskGroupName={(taskGroupID, name) => {
|
||||||
|
updateTaskGroupName({ variables: { taskGroupID, name } });
|
||||||
|
}}
|
||||||
onQuickEditorOpen={onQuickEditorOpen}
|
onQuickEditorOpen={onQuickEditorOpen}
|
||||||
onExtraMenuOpen={(taskGroupID: string, $targetRef: any) => {
|
onExtraMenuOpen={(taskGroupID: string, $targetRef: any) => {
|
||||||
showPopup(
|
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}
|
top={quickCardEditor.top}
|
||||||
left={quickCardEditor.left}
|
left={quickCardEditor.left}
|
||||||
|
width={quickCardEditor.width}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Route
|
<Route
|
||||||
|
1
web/src/citadel.d.ts
vendored
1
web/src/citadel.d.ts
vendored
@ -12,6 +12,7 @@ interface DraggableElement {
|
|||||||
type ContextMenuEvent = {
|
type ContextMenuEvent = {
|
||||||
left: number;
|
left: number;
|
||||||
top: number;
|
top: number;
|
||||||
|
width: number;
|
||||||
taskID: string;
|
taskID: string;
|
||||||
taskGroupID: 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;
|
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 = {
|
type Task = {
|
||||||
id: string;
|
id: string;
|
||||||
taskGroup: InnerTaskGroup;
|
taskGroup: InnerTaskGroup;
|
||||||
name: string;
|
name: string;
|
||||||
position: number;
|
position: number;
|
||||||
dueDate?: string;
|
dueDate?: string;
|
||||||
|
complete?: boolean;
|
||||||
labels: TaskLabel[];
|
labels: TaskLabel[];
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
assigned?: Array<TaskUser>;
|
assigned?: Array<TaskUser>;
|
||||||
|
checklists?: Array<TaskChecklist> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Project = {
|
type Project = {
|
||||||
|
@ -3,7 +3,15 @@ import TextareaAutosize from 'react-autosize-textarea/lib';
|
|||||||
import { mixin } from 'shared/utils/styles';
|
import { mixin } from 'shared/utils/styles';
|
||||||
import Button from 'shared/components/Button';
|
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 }>`
|
export const Wrapper = styled.div<{ editorOpen: boolean }>`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -62,7 +62,7 @@ const NameEditor: React.FC<NameEditorProps> = ({ onSave, onCancel }) => {
|
|||||||
Save
|
Save
|
||||||
</AddListButton>
|
</AddListButton>
|
||||||
<CancelAdd onClick={() => onCancel()}>
|
<CancelAdd onClick={() => onCancel()}>
|
||||||
<Cross color="#c2c6dc" />
|
<Cross width={16} height={16} />
|
||||||
</CancelAdd>
|
</CancelAdd>
|
||||||
</ListAddControls>
|
</ListAddControls>
|
||||||
</>
|
</>
|
||||||
|
@ -2,6 +2,7 @@ import styled, { css } from 'styled-components';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { mixin } from 'shared/utils/styles';
|
import { mixin } from 'shared/utils/styles';
|
||||||
import TextareaAutosize from 'react-autosize-textarea';
|
import TextareaAutosize from 'react-autosize-textarea';
|
||||||
|
import { CheckCircle } from 'shared/icons';
|
||||||
import { RefObject } from 'react';
|
import { RefObject } from 'react';
|
||||||
|
|
||||||
export const ClockIcon = styled(FontAwesomeIcon)``;
|
export const ClockIcon = styled(FontAwesomeIcon)``;
|
||||||
@ -20,9 +21,9 @@ export const EditorTextarea = styled(TextareaAutosize)`
|
|||||||
max-height: 162px;
|
max-height: 162px;
|
||||||
min-height: 54px;
|
min-height: 54px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
line-height: 20px;
|
line-height: 16px;
|
||||||
color: rgba(${props => props.theme.colors.text.secondary});
|
color: rgba(${props => props.theme.colors.text.primary});
|
||||||
&:focus {
|
&:focus {
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -92,11 +93,13 @@ export const ListCardInnerContainer = styled.div`
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ListCardDetails = styled.div`
|
export const ListCardDetails = styled.div<{ complete: boolean }>`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 6px 8px 2px;
|
padding: 6px 8px 2px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
|
${props => props.complete && 'opacity: 0.6;'}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ListCardLabels = styled.div`
|
export const ListCardLabels = styled.div`
|
||||||
@ -140,15 +143,28 @@ export const ListCardOperation = styled.span`
|
|||||||
|
|
||||||
export const CardTitle = styled.span`
|
export const CardTitle = styled.span`
|
||||||
clear: both;
|
clear: both;
|
||||||
display: block;
|
|
||||||
margin: 0 0 4px;
|
margin: 0 0 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
line-height: 16px;
|
||||||
|
font-size: 14px;
|
||||||
color: rgba(${props => props.theme.colors.text.primary});
|
color: rgba(${props => props.theme.colors.text.primary});
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CardMembers = styled.div`
|
export const CardMembers = styled.div`
|
||||||
float: right;
|
float: right;
|
||||||
margin: 0 -2px 4px 0;
|
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 { faClock, faCheckSquare, faEye } from '@fortawesome/free-regular-svg-icons';
|
||||||
import {
|
import {
|
||||||
EditorTextarea,
|
EditorTextarea,
|
||||||
|
EditorContent,
|
||||||
|
CompleteIcon,
|
||||||
DescriptionBadge,
|
DescriptionBadge,
|
||||||
DueDateCardBadge,
|
DueDateCardBadge,
|
||||||
ListCardBadges,
|
ListCardBadges,
|
||||||
@ -35,6 +37,7 @@ type Props = {
|
|||||||
title: string;
|
title: string;
|
||||||
taskID: string;
|
taskID: string;
|
||||||
taskGroupID: string;
|
taskGroupID: string;
|
||||||
|
complete?: boolean;
|
||||||
onContextMenu?: (e: ContextMenuEvent) => void;
|
onContextMenu?: (e: ContextMenuEvent) => void;
|
||||||
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
description?: null | string;
|
description?: null | string;
|
||||||
@ -57,6 +60,7 @@ const Card = React.forwardRef(
|
|||||||
onContextMenu,
|
onContextMenu,
|
||||||
taskID,
|
taskID,
|
||||||
taskGroupID,
|
taskGroupID,
|
||||||
|
complete,
|
||||||
onClick,
|
onClick,
|
||||||
labels,
|
labels,
|
||||||
title,
|
title,
|
||||||
@ -101,6 +105,7 @@ const Card = React.forwardRef(
|
|||||||
const pos = $innerCardRef.current.getBoundingClientRect();
|
const pos = $innerCardRef.current.getBoundingClientRect();
|
||||||
if (onContextMenu) {
|
if (onContextMenu) {
|
||||||
onContextMenu({
|
onContextMenu({
|
||||||
|
width: pos.width,
|
||||||
top: pos.top,
|
top: pos.top,
|
||||||
left: pos.left,
|
left: pos.left,
|
||||||
taskGroupID,
|
taskGroupID,
|
||||||
@ -140,7 +145,7 @@ const Card = React.forwardRef(
|
|||||||
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
|
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
|
||||||
</ListCardOperation>
|
</ListCardOperation>
|
||||||
)}
|
)}
|
||||||
<ListCardDetails>
|
<ListCardDetails complete={complete ?? false}>
|
||||||
<ListCardLabels>
|
<ListCardLabels>
|
||||||
{labels &&
|
{labels &&
|
||||||
labels.map(label => (
|
labels.map(label => (
|
||||||
@ -150,22 +155,28 @@ const Card = React.forwardRef(
|
|||||||
))}
|
))}
|
||||||
</ListCardLabels>
|
</ListCardLabels>
|
||||||
{editable ? (
|
{editable ? (
|
||||||
<EditorTextarea
|
<EditorContent>
|
||||||
onChange={e => {
|
{complete && <CompleteIcon width={16} height={16} />}
|
||||||
setCardTitle(e.currentTarget.value);
|
<EditorTextarea
|
||||||
if (onCardTitleChange) {
|
onChange={e => {
|
||||||
onCardTitleChange(e.currentTarget.value);
|
setCardTitle(e.currentTarget.value);
|
||||||
}
|
if (onCardTitleChange) {
|
||||||
}}
|
onCardTitleChange(e.currentTarget.value);
|
||||||
onClick={e => {
|
}
|
||||||
e.stopPropagation();
|
}}
|
||||||
}}
|
onClick={e => {
|
||||||
onKeyDown={handleKeyDown}
|
e.stopPropagation();
|
||||||
value={currentCardTitle}
|
}}
|
||||||
ref={$editorRef}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
value={currentCardTitle}
|
||||||
|
ref={$editorRef}
|
||||||
|
/>
|
||||||
|
</EditorContent>
|
||||||
) : (
|
) : (
|
||||||
<CardTitle>{title}</CardTitle>
|
<CardTitle>
|
||||||
|
{complete && <CompleteIcon width={16} height={16} />}
|
||||||
|
{title}
|
||||||
|
</CardTitle>
|
||||||
)}
|
)}
|
||||||
<ListCardBadges>
|
<ListCardBadges>
|
||||||
{watched && (
|
{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')}
|
onCancel={action('cancel')}
|
||||||
onDueDateChange={action('due date change')}
|
onDueDateChange={action('due date change')}
|
||||||
|
onRemoveDueDate={action('remove due date')}
|
||||||
/>
|
/>
|
||||||
</Popup>
|
</Popup>
|
||||||
</PopupWrapper>
|
</PopupWrapper>
|
||||||
|
@ -102,11 +102,15 @@ export const DueDatePickerWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const ConfirmAddDueDate = styled(Button)`
|
export const ConfirmAddDueDate = styled(Button)`
|
||||||
float: left;
|
|
||||||
margin: 0 4px 0 0;
|
margin: 0 4px 0 0;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const RemoveDueDate = styled(Button)`
|
||||||
|
padding: 6px 12px;
|
||||||
|
margin: 0 0 0 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
export const CancelDueDate = styled.div`
|
export const CancelDueDate = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -126,4 +130,5 @@ export const ActionWrapper = styled.div`
|
|||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
`;
|
`;
|
||||||
|
@ -5,7 +5,15 @@ import DatePicker from 'react-datepicker';
|
|||||||
import { Cross } from 'shared/icons';
|
import { Cross } from 'shared/icons';
|
||||||
import _ from 'lodash';
|
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 'react-datepicker/dist/react-datepicker.css';
|
||||||
import { getYear, getMonth } from 'date-fns';
|
import { getYear, getMonth } from 'date-fns';
|
||||||
@ -14,6 +22,7 @@ import { useForm } from 'react-hook-form';
|
|||||||
type DueDateManagerProps = {
|
type DueDateManagerProps = {
|
||||||
task: Task;
|
task: Task;
|
||||||
onDueDateChange: (task: Task, newDueDate: Date) => void;
|
onDueDateChange: (task: Task, newDueDate: Date) => void;
|
||||||
|
onRemoveDueDate: (task: Task) => void;
|
||||||
onCancel: () => 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 now = moment();
|
||||||
const [textStartDate, setTextStartDate] = useState(now.format('YYYY-MM-DD'));
|
const [textStartDate, setTextStartDate] = useState(now.format('YYYY-MM-DD'));
|
||||||
const [startDate, setStartDate] = useState(new Date());
|
const [startDate, setStartDate] = useState(new Date());
|
||||||
@ -260,19 +269,18 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
|||||||
/>
|
/>
|
||||||
</DueDatePickerWrapper>
|
</DueDatePickerWrapper>
|
||||||
<ActionWrapper>
|
<ActionWrapper>
|
||||||
<ConfirmAddDueDate
|
<ConfirmAddDueDate type="submit" onClick={() => {}}>
|
||||||
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());
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Save
|
Save
|
||||||
</ConfirmAddDueDate>
|
</ConfirmAddDueDate>
|
||||||
<CancelDueDate onClick={onCancel}>
|
<RemoveDueDate
|
||||||
<Cross size={16} color="#c2c6dc" />
|
variant="outline"
|
||||||
</CancelDueDate>
|
color="danger"
|
||||||
|
onClick={() => {
|
||||||
|
onRemoveDueDate(task);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</RemoveDueDate>
|
||||||
</ActionWrapper>
|
</ActionWrapper>
|
||||||
</Form>
|
</Form>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
@ -7,11 +7,41 @@ export const Container = styled.div`
|
|||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
padding-bottom: 8px;
|
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`
|
export const BoardWrapper = styled.div`
|
||||||
display: flex;
|
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;
|
export default Container;
|
||||||
|
@ -10,10 +10,10 @@ import {
|
|||||||
getNewDraggablePosition,
|
getNewDraggablePosition,
|
||||||
getAfterDropDraggableList,
|
getAfterDropDraggableList,
|
||||||
} from 'shared/utils/draggables';
|
} from 'shared/utils/draggables';
|
||||||
|
|
||||||
import { Container, BoardWrapper } from './Styles';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { Container, BoardContainer, BoardWrapper } from './Styles';
|
||||||
|
|
||||||
interface SimpleProps {
|
interface SimpleProps {
|
||||||
taskGroups: Array<TaskGroup>;
|
taskGroups: Array<TaskGroup>;
|
||||||
onTaskDrop: (task: Task, previousTaskGroupID: string) => void;
|
onTaskDrop: (task: Task, previousTaskGroupID: string) => void;
|
||||||
@ -120,104 +120,107 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
|
|
||||||
const [currentComposer, setCurrentComposer] = useState('');
|
const [currentComposer, setCurrentComposer] = useState('');
|
||||||
return (
|
return (
|
||||||
<BoardWrapper>
|
<BoardContainer>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<BoardWrapper>
|
||||||
<Droppable direction="horizontal" type="column" droppableId="root">
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
{provided => (
|
<Droppable direction="horizontal" type="column" droppableId="root">
|
||||||
<Container {...provided.droppableProps} ref={provided.innerRef}>
|
{provided => (
|
||||||
{taskGroups
|
<Container {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
.slice()
|
{taskGroups
|
||||||
.sort((a: any, b: any) => a.position - b.position)
|
.slice()
|
||||||
.map((taskGroup: TaskGroup, index: number) => {
|
.sort((a: any, b: any) => a.position - b.position)
|
||||||
return (
|
.map((taskGroup: TaskGroup, index: number) => {
|
||||||
<Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
|
return (
|
||||||
{columnDragProvided => (
|
<Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
|
||||||
<Droppable type="tasks" droppableId={taskGroup.id}>
|
{columnDragProvided => (
|
||||||
{(columnDropProvided, snapshot) => (
|
<Droppable type="tasks" droppableId={taskGroup.id}>
|
||||||
<List
|
{(columnDropProvided, snapshot) => (
|
||||||
name={taskGroup.name}
|
<List
|
||||||
onOpenComposer={id => setCurrentComposer(id)}
|
name={taskGroup.name}
|
||||||
isComposerOpen={currentComposer === taskGroup.id}
|
onOpenComposer={id => setCurrentComposer(id)}
|
||||||
onSaveName={name => onChangeTaskGroupName(taskGroup.id, name)}
|
isComposerOpen={currentComposer === taskGroup.id}
|
||||||
ref={columnDragProvided.innerRef}
|
onSaveName={name => onChangeTaskGroupName(taskGroup.id, name)}
|
||||||
wrapperProps={columnDragProvided.draggableProps}
|
ref={columnDragProvided.innerRef}
|
||||||
headerProps={columnDragProvided.dragHandleProps}
|
wrapperProps={columnDragProvided.draggableProps}
|
||||||
onExtraMenuOpen={onExtraMenuOpen}
|
headerProps={columnDragProvided.dragHandleProps}
|
||||||
id={taskGroup.id}
|
onExtraMenuOpen={onExtraMenuOpen}
|
||||||
key={taskGroup.id}
|
id={taskGroup.id}
|
||||||
index={index}
|
key={taskGroup.id}
|
||||||
>
|
index={index}
|
||||||
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
|
>
|
||||||
{taskGroup.tasks
|
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
|
||||||
.slice()
|
{taskGroup.tasks
|
||||||
.sort((a: any, b: any) => a.position - b.position)
|
.slice()
|
||||||
.map((task: Task, taskIndex: any) => {
|
.sort((a: any, b: any) => a.position - b.position)
|
||||||
return (
|
.map((task: Task, taskIndex: any) => {
|
||||||
<Draggable key={task.id} draggableId={task.id} index={taskIndex}>
|
return (
|
||||||
{taskProvided => {
|
<Draggable key={task.id} draggableId={task.id} index={taskIndex}>
|
||||||
return (
|
{taskProvided => {
|
||||||
<Card
|
return (
|
||||||
wrapperProps={{
|
<Card
|
||||||
...taskProvided.draggableProps,
|
wrapperProps={{
|
||||||
...taskProvided.dragHandleProps,
|
...taskProvided.draggableProps,
|
||||||
}}
|
...taskProvided.dragHandleProps,
|
||||||
ref={taskProvided.innerRef}
|
}}
|
||||||
taskID={task.id}
|
ref={taskProvided.innerRef}
|
||||||
taskGroupID={taskGroup.id}
|
taskID={task.id}
|
||||||
description=""
|
complete={task.complete ?? false}
|
||||||
labels={task.labels.map(label => label.projectLabel)}
|
taskGroupID={taskGroup.id}
|
||||||
dueDate={
|
description=""
|
||||||
task.dueDate
|
labels={task.labels.map(label => label.projectLabel)}
|
||||||
? {
|
dueDate={
|
||||||
isPastDue: false,
|
task.dueDate
|
||||||
formattedDate: moment(task.dueDate).format('MMM D, YYYY'),
|
? {
|
||||||
}
|
isPastDue: false,
|
||||||
: undefined
|
formattedDate: moment(task.dueDate).format('MMM D, YYYY'),
|
||||||
}
|
}
|
||||||
title={task.name}
|
: undefined
|
||||||
members={task.assigned}
|
}
|
||||||
onClick={() => {
|
title={task.name}
|
||||||
onTaskClick(task);
|
members={task.assigned}
|
||||||
}}
|
onClick={() => {
|
||||||
onCardMemberClick={onCardMemberClick}
|
onTaskClick(task);
|
||||||
onContextMenu={onQuickEditorOpen}
|
}}
|
||||||
/>
|
onCardMemberClick={onCardMemberClick}
|
||||||
);
|
onContextMenu={onQuickEditorOpen}
|
||||||
}}
|
/>
|
||||||
</Draggable>
|
);
|
||||||
);
|
}}
|
||||||
})}
|
</Draggable>
|
||||||
{columnDropProvided.placeholder}
|
);
|
||||||
{currentComposer === taskGroup.id && (
|
})}
|
||||||
<CardComposer
|
{columnDropProvided.placeholder}
|
||||||
onClose={() => {
|
{currentComposer === taskGroup.id && (
|
||||||
setCurrentComposer('');
|
<CardComposer
|
||||||
}}
|
onClose={() => {
|
||||||
onCreateCard={name => {
|
setCurrentComposer('');
|
||||||
onCreateTask(taskGroup.id, name);
|
}}
|
||||||
}}
|
onCreateCard={name => {
|
||||||
isOpen
|
onCreateTask(taskGroup.id, name);
|
||||||
/>
|
}}
|
||||||
)}
|
isOpen
|
||||||
</ListCards>
|
/>
|
||||||
</List>
|
)}
|
||||||
)}
|
</ListCards>
|
||||||
</Droppable>
|
</List>
|
||||||
)}
|
)}
|
||||||
</Draggable>
|
</Droppable>
|
||||||
);
|
)}
|
||||||
})}
|
</Draggable>
|
||||||
{provided.placeholder}
|
);
|
||||||
</Container>
|
})}
|
||||||
)}
|
<AddList
|
||||||
</Droppable>
|
onSave={listName => {
|
||||||
</DragDropContext>
|
onCreateTaskGroup(listName);
|
||||||
<AddList
|
}}
|
||||||
onSave={listName => {
|
/>
|
||||||
onCreateTaskGroup(listName);
|
{provided.placeholder}
|
||||||
}}
|
</Container>
|
||||||
/>
|
)}
|
||||||
</BoardWrapper>
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
</BoardWrapper>
|
||||||
|
</BoardContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ const MemberManager: React.FC<MemberManagerProps> = ({
|
|||||||
<MemberName>{member.fullName}</MemberName>
|
<MemberName>{member.fullName}</MemberName>
|
||||||
{activeMembers.findIndex(m => m.id === member.id) !== -1 && (
|
{activeMembers.findIndex(m => m.id === member.id) !== -1 && (
|
||||||
<ActiveIconWrapper>
|
<ActiveIconWrapper>
|
||||||
<Checkmark size={16} color="#42526e" />
|
<Checkmark width={16} height={16} />
|
||||||
</ActiveIconWrapper>
|
</ActiveIconWrapper>
|
||||||
)}
|
)}
|
||||||
</BoardMemberListItemContent>
|
</BoardMemberListItemContent>
|
||||||
|
@ -232,7 +232,7 @@ const NewProject: React.FC<NewProjectProps> = ({ teams, onClose, onCreateProject
|
|||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Cross color="#c2c6dc" />
|
<Cross width={16} height={16} />
|
||||||
</HeaderRight>
|
</HeaderRight>
|
||||||
</Header>
|
</Header>
|
||||||
<Container>
|
<Container>
|
||||||
|
@ -44,7 +44,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props)
|
|||||||
setCurrentColor(labelColor);
|
setCurrentColor(labelColor);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{currentColor && labelColor.id === currentColor.id && <Checkmark color="#fff" size={12} />}
|
{currentColor && labelColor.id === currentColor.id && <Checkmark width={12} height={12} />}
|
||||||
</LabelBox>
|
</LabelBox>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,7 +72,7 @@ const LabelManager: React.FC<Props> = ({ labels, taskLabels, onLabelToggle, onLa
|
|||||||
{label.name}
|
{label.name}
|
||||||
{taskLabels && taskLabels.find(t => t.projectLabel.id === label.id) && (
|
{taskLabels && taskLabels.find(t => t.projectLabel.id === label.id) && (
|
||||||
<ActiveIcon>
|
<ActiveIcon>
|
||||||
<Checkmark color="#fff" />
|
<Checkmark width={16} height={16} />
|
||||||
</ActiveIcon>
|
</ActiveIcon>
|
||||||
)}
|
)}
|
||||||
</CardLabel>
|
</CardLabel>
|
||||||
|
@ -265,6 +265,7 @@ export const DueDateManagerPopup = () => {
|
|||||||
{popupData.isOpen && (
|
{popupData.isOpen && (
|
||||||
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
|
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
|
||||||
<DueDateManager
|
<DueDateManager
|
||||||
|
onRemoveDueDate={action('remove due date')}
|
||||||
task={{
|
task={{
|
||||||
id: '1',
|
id: '1',
|
||||||
taskGroup: { name: 'General', id: '1', position: 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 { Cross, AngleLeft } from 'shared/icons';
|
||||||
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
@ -36,10 +36,19 @@ type PopupContainerProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PopupContainer: React.FC<PopupContainerProps> = ({ width, top, left, onClose, children, invert }) => {
|
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);
|
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 (
|
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}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
@ -91,11 +100,12 @@ export const PopupProvider: React.FC = ({ children }) => {
|
|||||||
const show = (target: RefObject<HTMLElement>, content: JSX.Element, width?: number | string) => {
|
const show = (target: RefObject<HTMLElement>, content: JSX.Element, width?: number | string) => {
|
||||||
if (target && target.current) {
|
if (target && target.current) {
|
||||||
const bounds = target.current.getBoundingClientRect();
|
const bounds = target.current.getBoundingClientRect();
|
||||||
|
const top = bounds.top + bounds.height;
|
||||||
if (bounds.left + 304 + 30 > window.innerWidth) {
|
if (bounds.left + 304 + 30 > window.innerWidth) {
|
||||||
setState({
|
setState({
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
left: bounds.left + bounds.width,
|
left: bounds.left + bounds.width,
|
||||||
top: bounds.top + bounds.height,
|
top,
|
||||||
invert: true,
|
invert: true,
|
||||||
currentTab: 0,
|
currentTab: 0,
|
||||||
previousTab: 0,
|
previousTab: 0,
|
||||||
@ -106,7 +116,7 @@ export const PopupProvider: React.FC = ({ children }) => {
|
|||||||
setState({
|
setState({
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
left: bounds.left,
|
left: bounds.left,
|
||||||
top: bounds.top + bounds.height,
|
top,
|
||||||
invert: false,
|
invert: false,
|
||||||
currentTab: 0,
|
currentTab: 0,
|
||||||
previousTab: 0,
|
previousTab: 0,
|
||||||
@ -176,7 +186,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PopupMenu: React.FC<Props> = ({ width, title, top, left, onClose, noHeader, children, onPrevious }) => {
|
const PopupMenu: React.FC<Props> = ({ width, title, top, left, onClose, noHeader, children, onPrevious }) => {
|
||||||
const $containerRef = useRef();
|
const $containerRef = useRef<HTMLDivElement>(null);
|
||||||
useOnOutsideClick($containerRef, true, onClose, null);
|
useOnOutsideClick($containerRef, true, onClose, null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -189,13 +199,13 @@ const PopupMenu: React.FC<Props> = ({ width, title, top, left, onClose, noHeader
|
|||||||
)}
|
)}
|
||||||
{noHeader ? (
|
{noHeader ? (
|
||||||
<CloseButton onClick={() => onClose()}>
|
<CloseButton onClick={() => onClose()}>
|
||||||
<Cross color="#c2c6dc" />
|
<Cross width={16} height={16} />
|
||||||
</CloseButton>
|
</CloseButton>
|
||||||
) : (
|
) : (
|
||||||
<Header>
|
<Header>
|
||||||
<HeaderTitle>{title}</HeaderTitle>
|
<HeaderTitle>{title}</HeaderTitle>
|
||||||
<CloseButton onClick={() => onClose()}>
|
<CloseButton onClick={() => onClose()}>
|
||||||
<Cross color="#c2c6dc" />
|
<Cross width={16} height={16} />
|
||||||
</CloseButton>
|
</CloseButton>
|
||||||
</Header>
|
</Header>
|
||||||
)}
|
)}
|
||||||
@ -230,7 +240,7 @@ export const Popup: React.FC<PopupProps> = ({ title, onClose, tab, children }) =
|
|||||||
)}
|
)}
|
||||||
{onClose && (
|
{onClose && (
|
||||||
<CloseButton onClick={() => onClose()}>
|
<CloseButton onClick={() => onClose()}>
|
||||||
<Cross color="#c2c6dc" />
|
<Cross width={16} height={16} />
|
||||||
</CloseButton>
|
</CloseButton>
|
||||||
)}
|
)}
|
||||||
<Content>{children}</Content>
|
<Content>{children}</Content>
|
||||||
|
@ -57,7 +57,9 @@ export const Default = () => {
|
|||||||
onCloseEditor={() => setEditorOpen(false)}
|
onCloseEditor={() => setEditorOpen(false)}
|
||||||
onEditCard={action('edit card')}
|
onEditCard={action('edit card')}
|
||||||
onOpenLabelsPopup={action('open popup')}
|
onOpenLabelsPopup={action('open popup')}
|
||||||
|
onOpenDueDatePopup={action('open popup')}
|
||||||
onOpenMembersPopup={action('open popup')}
|
onOpenMembersPopup={action('open popup')}
|
||||||
|
onToggleComplete={action('complete')}
|
||||||
onArchiveCard={action('archive card')}
|
onArchiveCard={action('archive card')}
|
||||||
top={top}
|
top={top}
|
||||||
left={left}
|
left={left}
|
||||||
|
@ -14,9 +14,9 @@ export const Wrapper = styled.div<{ open: boolean }>`
|
|||||||
visibility: ${props => (props.open ? 'show' : 'hidden')};
|
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;
|
position: absolute;
|
||||||
width: 256px;
|
width: ${props => props.width}px;
|
||||||
top: ${props => props.top}px;
|
top: ${props => props.top}px;
|
||||||
left: ${props => props.left}px;
|
left: ${props => props.left}px;
|
||||||
`;
|
`;
|
||||||
|
@ -14,12 +14,15 @@ type Props = {
|
|||||||
task: Task;
|
task: Task;
|
||||||
onCloseEditor: () => void;
|
onCloseEditor: () => void;
|
||||||
onEditCard: (taskGroupID: string, taskID: string, cardName: string) => void;
|
onEditCard: (taskGroupID: string, taskID: string, cardName: string) => void;
|
||||||
|
onToggleComplete: (task: Task) => void;
|
||||||
onOpenLabelsPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
onOpenLabelsPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
||||||
onOpenMembersPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
onOpenMembersPopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
||||||
|
onOpenDueDatePopup: ($targetRef: React.RefObject<HTMLElement>, task: Task) => void;
|
||||||
onArchiveCard: (taskGroupID: string, taskID: string) => void;
|
onArchiveCard: (taskGroupID: string, taskID: string) => void;
|
||||||
onCardMemberClick?: OnCardMemberClick;
|
onCardMemberClick?: OnCardMemberClick;
|
||||||
top: number;
|
top: number;
|
||||||
left: number;
|
left: number;
|
||||||
|
width?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const QuickCardEditor = ({
|
const QuickCardEditor = ({
|
||||||
@ -27,14 +30,18 @@ const QuickCardEditor = ({
|
|||||||
onCloseEditor,
|
onCloseEditor,
|
||||||
onOpenLabelsPopup,
|
onOpenLabelsPopup,
|
||||||
onOpenMembersPopup,
|
onOpenMembersPopup,
|
||||||
|
onOpenDueDatePopup,
|
||||||
|
onToggleComplete,
|
||||||
onCardMemberClick,
|
onCardMemberClick,
|
||||||
onArchiveCard,
|
onArchiveCard,
|
||||||
onEditCard,
|
onEditCard,
|
||||||
|
width = 272,
|
||||||
top,
|
top,
|
||||||
left,
|
left,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [currentCardTitle, setCardTitle] = useState(task.name);
|
const [currentCardTitle, setCardTitle] = useState(task.name);
|
||||||
const $labelsRef: any = useRef();
|
const $labelsRef: any = useRef();
|
||||||
|
const $dueDate: any = useRef();
|
||||||
const $membersRef: any = useRef();
|
const $membersRef: any = useRef();
|
||||||
|
|
||||||
const handleCloseEditor = (e: any) => {
|
const handleCloseEditor = (e: any) => {
|
||||||
@ -45,9 +52,9 @@ const QuickCardEditor = ({
|
|||||||
return (
|
return (
|
||||||
<Wrapper onClick={handleCloseEditor} open>
|
<Wrapper onClick={handleCloseEditor} open>
|
||||||
<CloseButton onClick={handleCloseEditor}>
|
<CloseButton onClick={handleCloseEditor}>
|
||||||
<Cross size={16} color="#000" />
|
<Cross width={16} height={16} />
|
||||||
</CloseButton>
|
</CloseButton>
|
||||||
<Container left={left} top={top}>
|
<Container width={width} left={left} top={top}>
|
||||||
<Card
|
<Card
|
||||||
editable
|
editable
|
||||||
onCardMemberClick={onCardMemberClick}
|
onCardMemberClick={onCardMemberClick}
|
||||||
@ -56,6 +63,7 @@ const QuickCardEditor = ({
|
|||||||
onEditCard(taskGroupID, taskID, name);
|
onEditCard(taskGroupID, taskID, name);
|
||||||
onCloseEditor();
|
onCloseEditor();
|
||||||
}}
|
}}
|
||||||
|
complete={task.complete ?? false}
|
||||||
members={task.assigned}
|
members={task.assigned}
|
||||||
taskID={task.id}
|
taskID={task.id}
|
||||||
taskGroupID={task.taskGroup.id}
|
taskGroupID={task.taskGroup.id}
|
||||||
@ -63,6 +71,14 @@ const QuickCardEditor = ({
|
|||||||
/>
|
/>
|
||||||
<SaveButton onClick={() => onEditCard(task.taskGroup.id, task.id, currentCardTitle)}>Save</SaveButton>
|
<SaveButton onClick={() => onEditCard(task.taskGroup.id, task.id, currentCardTitle)}>Save</SaveButton>
|
||||||
<EditorButtons>
|
<EditorButtons>
|
||||||
|
<EditorButton
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onToggleComplete(task);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{task.complete ? 'Mark Incomplete' : 'Mark Complete'}
|
||||||
|
</EditorButton>
|
||||||
<EditorButton
|
<EditorButton
|
||||||
ref={$membersRef}
|
ref={$membersRef}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
@ -72,6 +88,15 @@ const QuickCardEditor = ({
|
|||||||
>
|
>
|
||||||
Edit Assigned
|
Edit Assigned
|
||||||
</EditorButton>
|
</EditorButton>
|
||||||
|
<EditorButton
|
||||||
|
ref={$dueDate}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onOpenDueDatePopup($labelsRef, task);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit Due Date
|
||||||
|
</EditorButton>
|
||||||
<EditorButton
|
<EditorButton
|
||||||
ref={$labelsRef}
|
ref={$labelsRef}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
|
@ -148,6 +148,20 @@ export const TaskDetailsMarkdown = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #c2c6dc;
|
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 {
|
p {
|
||||||
margin: 0 0 8px;
|
margin: 0 0 8px;
|
||||||
}
|
}
|
||||||
@ -155,6 +169,15 @@ export const TaskDetailsMarkdown = styled.div`
|
|||||||
strong {
|
strong {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul > li {
|
||||||
|
margin: 8px 8px 8px 24px;
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TaskDetailsControls = styled.div`
|
export const TaskDetailsControls = styled.div`
|
||||||
|
@ -28,6 +28,8 @@ export const Default = () => {
|
|||||||
renderContent={() => {
|
renderContent={() => {
|
||||||
return (
|
return (
|
||||||
<TaskDetails
|
<TaskDetails
|
||||||
|
onDeleteItem={action('delete item')}
|
||||||
|
onChangeItemName={action('change item name')}
|
||||||
task={{
|
task={{
|
||||||
id: '1',
|
id: '1',
|
||||||
taskGroup: { name: 'General', id: '1' },
|
taskGroup: { name: 'General', id: '1' },
|
||||||
@ -65,6 +67,8 @@ export const Default = () => {
|
|||||||
onCloseModal={action('close modal')}
|
onCloseModal={action('close modal')}
|
||||||
onMemberProfile={action('profile')}
|
onMemberProfile={action('profile')}
|
||||||
onOpenAddMemberPopup={action('open add member popup')}
|
onOpenAddMemberPopup={action('open add member popup')}
|
||||||
|
onAddItem={action('add item')}
|
||||||
|
onToggleChecklistItem={action('toggle checklist item')}
|
||||||
onOpenAddLabelPopup={action('open add label popup')}
|
onOpenAddLabelPopup={action('open add label popup')}
|
||||||
onOpenDueDatePopop={action('open due date 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 useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import TaskAssignee from 'shared/components/TaskAssignee';
|
import TaskAssignee from 'shared/components/TaskAssignee';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NoDueDateLabel,
|
NoDueDateLabel,
|
||||||
@ -37,15 +38,33 @@ import {
|
|||||||
TaskDetailAssignees,
|
TaskDetailAssignees,
|
||||||
TaskDetailsAddMemberIcon,
|
TaskDetailsAddMemberIcon,
|
||||||
} from './Styles';
|
} from './Styles';
|
||||||
|
import Checklist from '../Checklist';
|
||||||
import convertDivElementRefToBounds from 'shared/utils/boundingRect';
|
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
type TaskContentProps = {
|
type TaskContentProps = {
|
||||||
onEditContent: () => void;
|
onEditContent: () => void;
|
||||||
description: string;
|
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 }) => {
|
const TaskContent: React.FC<TaskContentProps> = ({ description, onEditContent }) => {
|
||||||
return description === '' ? (
|
return description === '' ? (
|
||||||
<TaskDetailsAddDetailsButton onClick={onEditContent}>Add a more detailed description</TaskDetailsAddDetailsButton>
|
<TaskDetailsAddDetailsButton onClick={onEditContent}>Add a more detailed description</TaskDetailsAddDetailsButton>
|
||||||
@ -105,6 +124,10 @@ type TaskDetailsProps = {
|
|||||||
onTaskNameChange: (task: Task, newName: string) => void;
|
onTaskNameChange: (task: Task, newName: string) => void;
|
||||||
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
|
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
|
||||||
onDeleteTask: (task: Task) => 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;
|
onOpenAddMemberPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
|
||||||
onOpenAddLabelPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
|
onOpenAddLabelPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
|
||||||
onOpenDueDatePopop: (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,
|
task,
|
||||||
onTaskNameChange,
|
onTaskNameChange,
|
||||||
onTaskDescriptionChange,
|
onTaskDescriptionChange,
|
||||||
|
onChangeItemName,
|
||||||
|
onDeleteItem,
|
||||||
onDeleteTask,
|
onDeleteTask,
|
||||||
onCloseModal,
|
onCloseModal,
|
||||||
onOpenAddMemberPopup,
|
onOpenAddMemberPopup,
|
||||||
onOpenAddLabelPopup,
|
onOpenAddLabelPopup,
|
||||||
onOpenDueDatePopop,
|
onOpenDueDatePopop,
|
||||||
|
onAddItem,
|
||||||
|
onToggleChecklistItem,
|
||||||
onMemberProfile,
|
onMemberProfile,
|
||||||
}) => {
|
}) => {
|
||||||
const [editorOpen, setEditorOpen] = useState(false);
|
const [editorOpen, setEditorOpen] = useState(false);
|
||||||
@ -158,7 +185,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
|||||||
<Bin size={20} color="#c2c6dc" />
|
<Bin size={20} color="#c2c6dc" />
|
||||||
</TaskAction>
|
</TaskAction>
|
||||||
<TaskAction onClick={onCloseModal}>
|
<TaskAction onClick={onCloseModal}>
|
||||||
<Cross size={20} color="#c2c6dc" />
|
<Cross width={16} height={16} />
|
||||||
</TaskAction>
|
</TaskAction>
|
||||||
</TaskActions>
|
</TaskActions>
|
||||||
<TaskHeader>
|
<TaskHeader>
|
||||||
@ -195,6 +222,33 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<TaskContent description={description} onEditContent={handleClick} />
|
<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>
|
</TaskDetailsContent>
|
||||||
<TaskDetailsSidebar>
|
<TaskDetailsSidebar>
|
||||||
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
|
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
|
||||||
@ -221,9 +275,13 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
|||||||
<TaskDetailLabels>
|
<TaskDetailLabels>
|
||||||
{task.labels.map(label => {
|
{task.labels.map(label => {
|
||||||
return (
|
return (
|
||||||
<TaskDetailLabel key={label.projectLabel.id} color={label.projectLabel.labelColor.colorHex}>
|
<TaskLabelItem
|
||||||
{label.projectLabel.name}
|
key={label.projectLabel.id}
|
||||||
</TaskDetailLabel>
|
label={label}
|
||||||
|
onClick={$target => {
|
||||||
|
onOpenAddLabelPopup(task, $target);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<TaskDetailsAddLabel ref={$addLabelRef} onClick={onAddLabel}>
|
<TaskDetailsAddLabel ref={$addLabelRef} onClick={onAddLabel}>
|
||||||
|
@ -111,8 +111,10 @@ export type Task = {
|
|||||||
position: Scalars['Float'];
|
position: Scalars['Float'];
|
||||||
description?: Maybe<Scalars['String']>;
|
description?: Maybe<Scalars['String']>;
|
||||||
dueDate?: Maybe<Scalars['Time']>;
|
dueDate?: Maybe<Scalars['Time']>;
|
||||||
|
complete: Scalars['Boolean'];
|
||||||
assigned: Array<ProjectMember>;
|
assigned: Array<ProjectMember>;
|
||||||
labels: Array<TaskLabel>;
|
labels: Array<TaskLabel>;
|
||||||
|
checklists: Array<TaskChecklist>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProjectsFilter = {
|
export type ProjectsFilter = {
|
||||||
@ -239,6 +241,30 @@ export type DeleteTaskGroupPayload = {
|
|||||||
taskGroup: TaskGroup;
|
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 = {
|
export type AssignTaskInput = {
|
||||||
taskID: Scalars['UUID'];
|
taskID: Scalars['UUID'];
|
||||||
userID: Scalars['UUID'];
|
userID: Scalars['UUID'];
|
||||||
@ -321,6 +347,37 @@ export type UpdateTaskDueDate = {
|
|||||||
dueDate?: Maybe<Scalars['Time']>;
|
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 = {
|
export type Mutation = {
|
||||||
__typename?: 'Mutation';
|
__typename?: 'Mutation';
|
||||||
createRefreshToken: RefreshToken;
|
createRefreshToken: RefreshToken;
|
||||||
@ -341,10 +398,16 @@ export type Mutation = {
|
|||||||
addTaskLabel: Task;
|
addTaskLabel: Task;
|
||||||
removeTaskLabel: Task;
|
removeTaskLabel: Task;
|
||||||
toggleTaskLabel: ToggleTaskLabelPayload;
|
toggleTaskLabel: ToggleTaskLabelPayload;
|
||||||
|
createTaskChecklist: TaskChecklist;
|
||||||
|
createTaskChecklistItem: TaskChecklistItem;
|
||||||
|
updateTaskChecklistItemName: TaskChecklistItem;
|
||||||
|
setTaskChecklistItemComplete: TaskChecklistItem;
|
||||||
|
deleteTaskChecklistItem: DeleteTaskChecklistItemPayload;
|
||||||
createTask: Task;
|
createTask: Task;
|
||||||
updateTaskDescription: Task;
|
updateTaskDescription: Task;
|
||||||
updateTaskLocation: UpdateTaskLocationPayload;
|
updateTaskLocation: UpdateTaskLocationPayload;
|
||||||
updateTaskName: Task;
|
updateTaskName: Task;
|
||||||
|
setTaskComplete: Task;
|
||||||
updateTaskDueDate: Task;
|
updateTaskDueDate: Task;
|
||||||
deleteTask: DeleteTaskPayload;
|
deleteTask: DeleteTaskPayload;
|
||||||
assignTask: Task;
|
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 = {
|
export type MutationCreateTaskArgs = {
|
||||||
input: NewTask;
|
input: NewTask;
|
||||||
};
|
};
|
||||||
@ -458,6 +546,11 @@ export type MutationUpdateTaskNameArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationSetTaskCompleteArgs = {
|
||||||
|
input: SetTaskComplete;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationUpdateTaskDueDateArgs = {
|
export type MutationUpdateTaskDueDateArgs = {
|
||||||
input: UpdateTaskDueDate;
|
input: UpdateTaskDueDate;
|
||||||
};
|
};
|
||||||
@ -564,7 +657,7 @@ export type CreateTaskMutation = (
|
|||||||
{ __typename?: 'Mutation' }
|
{ __typename?: 'Mutation' }
|
||||||
& { createTask: (
|
& { createTask: (
|
||||||
{ __typename?: 'Task' }
|
{ __typename?: 'Task' }
|
||||||
& Pick<Task, 'id' | 'name' | 'position' | 'description'>
|
& Pick<Task, 'id' | 'name' | 'position' | 'description' | 'dueDate'>
|
||||||
& { taskGroup: (
|
& { taskGroup: (
|
||||||
{ __typename?: 'TaskGroup' }
|
{ __typename?: 'TaskGroup' }
|
||||||
& Pick<TaskGroup, 'id' | 'name' | 'position'>
|
& Pick<TaskGroup, 'id' | 'name' | 'position'>
|
||||||
@ -681,29 +774,7 @@ export type FindProjectQuery = (
|
|||||||
& Pick<TaskGroup, 'id' | 'name' | 'position'>
|
& Pick<TaskGroup, 'id' | 'name' | 'position'>
|
||||||
& { tasks: Array<(
|
& { tasks: Array<(
|
||||||
{ __typename?: 'Task' }
|
{ __typename?: 'Task' }
|
||||||
& Pick<Task, 'id' | 'name' | 'position' | 'description' | 'dueDate'>
|
& TaskFieldsFragment
|
||||||
& { 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'>
|
|
||||||
) }
|
|
||||||
)> }
|
|
||||||
)> }
|
)> }
|
||||||
)> }
|
)> }
|
||||||
), labelColors: Array<(
|
), labelColors: Array<(
|
||||||
@ -725,7 +796,14 @@ export type FindTaskQuery = (
|
|||||||
& { taskGroup: (
|
& { taskGroup: (
|
||||||
{ __typename?: 'TaskGroup' }
|
{ __typename?: 'TaskGroup' }
|
||||||
& Pick<TaskGroup, 'id'>
|
& 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' }
|
{ __typename?: 'TaskLabel' }
|
||||||
& Pick<TaskLabel, 'id' | 'assignedDate'>
|
& Pick<TaskLabel, 'id' | 'assignedDate'>
|
||||||
& { projectLabel: (
|
& { 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 = {};
|
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 = {
|
export type ToggleTaskLabelMutationVariables = {
|
||||||
taskID: Scalars['UUID'];
|
taskID: Scalars['UUID'];
|
||||||
projectLabelID: 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`
|
export const AssignTaskDocument = gql`
|
||||||
mutation assignTask($taskID: UUID!, $userID: UUID!) {
|
mutation assignTask($taskID: UUID!, $userID: UUID!) {
|
||||||
assignTask(input: {taskID: $taskID, userID: $userID}) {
|
assignTask(input: {taskID: $taskID, userID: $userID}) {
|
||||||
@ -1103,6 +1332,7 @@ export const CreateTaskDocument = gql`
|
|||||||
name
|
name
|
||||||
position
|
position
|
||||||
description
|
description
|
||||||
|
dueDate
|
||||||
taskGroup {
|
taskGroup {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -1331,40 +1561,7 @@ export const FindProjectDocument = gql`
|
|||||||
name
|
name
|
||||||
position
|
position
|
||||||
tasks {
|
tasks {
|
||||||
id
|
...TaskFields
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1375,7 +1572,7 @@ export const FindProjectDocument = gql`
|
|||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
${TaskFieldsFragmentDoc}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useFindProjectQuery__
|
* __useFindProjectQuery__
|
||||||
@ -1413,6 +1610,18 @@ export const FindTaskDocument = gql`
|
|||||||
taskGroup {
|
taskGroup {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
checklists {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
position
|
||||||
|
items {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
taskChecklistID
|
||||||
|
complete
|
||||||
|
position
|
||||||
|
}
|
||||||
|
}
|
||||||
labels {
|
labels {
|
||||||
id
|
id
|
||||||
assignedDate
|
assignedDate
|
||||||
@ -1546,6 +1755,218 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
|
|||||||
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
|
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
|
||||||
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
|
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
|
||||||
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
|
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
|
||||||
|
export const 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`
|
export const ToggleTaskLabelDocument = gql`
|
||||||
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
|
mutation toggleTaskLabel($taskID: UUID!, $projectLabelID: UUID!) {
|
||||||
toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) {
|
toggleTaskLabel(input: {taskID: $taskID, projectLabelID: $projectLabelID}) {
|
||||||
|
@ -4,6 +4,7 @@ mutation createTask($taskGroupID: String!, $name: String!, $position: Float!) {
|
|||||||
name
|
name
|
||||||
position
|
position
|
||||||
description
|
description
|
||||||
|
dueDate
|
||||||
taskGroup {
|
taskGroup {
|
||||||
id
|
id
|
||||||
name
|
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 {
|
taskGroup {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
checklists {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
position
|
||||||
|
items {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
taskChecklistID
|
||||||
|
complete
|
||||||
|
position
|
||||||
|
}
|
||||||
|
}
|
||||||
labels {
|
labels {
|
||||||
id
|
id
|
||||||
assignedDate
|
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 React from 'react';
|
||||||
|
import Icon, { IconProps } from './Icon';
|
||||||
|
|
||||||
type Props = {
|
const Checkmark: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
|
||||||
size: number | string;
|
|
||||||
color: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Checkmark = ({ size, color }: Props) => {
|
|
||||||
return (
|
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" />
|
<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;
|
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 React from 'react';
|
||||||
|
import Icon, { IconProps } from './Icon';
|
||||||
|
|
||||||
type Props = {
|
const Cross: React.FC<IconProps> = ({ width = '16px', height = '16px', className, onClick }) => {
|
||||||
size: number | string;
|
|
||||||
color: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Cross = ({ size, color }: Props) => {
|
|
||||||
return (
|
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" />
|
<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;
|
export default Cross;
|
||||||
|
@ -4,6 +4,8 @@ import styled from 'styled-components/macro';
|
|||||||
export type IconProps = {
|
export type IconProps = {
|
||||||
width: number | string;
|
width: number | string;
|
||||||
height: number | string;
|
height: number | string;
|
||||||
|
className?: string;
|
||||||
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -11,15 +13,27 @@ type Props = {
|
|||||||
height: number | string;
|
height: number | string;
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Svg = styled.svg`
|
const Svg = styled.svg`
|
||||||
fill: rgba(${props => props.theme.colors.text.primary});
|
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 (
|
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}
|
{children}
|
||||||
</Svg>
|
</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 Cross from './Cross';
|
||||||
import Cog from './Cog';
|
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 ArrowLeft from './ArrowLeft';
|
||||||
|
import CheckSquareOutline from './CheckSquareOutline';
|
||||||
|
import Square from './Square';
|
||||||
import Bolt from './Bolt';
|
import Bolt from './Bolt';
|
||||||
import Plus from './Plus';
|
import Plus from './Plus';
|
||||||
import Bell from './Bell';
|
import Bell from './Bell';
|
||||||
@ -42,8 +49,15 @@ export {
|
|||||||
Citadel,
|
Citadel,
|
||||||
Checkmark,
|
Checkmark,
|
||||||
User,
|
User,
|
||||||
|
Trash,
|
||||||
Users,
|
Users,
|
||||||
Lock,
|
Lock,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
CheckCircle,
|
||||||
|
AccountPlus,
|
||||||
|
Clock,
|
||||||
|
CheckSquareOutline,
|
||||||
|
CheckSquare,
|
||||||
|
Square,
|
||||||
ToggleOn,
|
ToggleOn,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user