feature: add due dates to tasks

This commit is contained in:
Jordan Knott 2020-06-15 17:36:59 -05:00
parent a12e9c1e50
commit b6f0e8b6b2
32 changed files with 816 additions and 222 deletions

View File

@ -94,6 +94,7 @@ type ComplexityRoot struct {
UpdateProjectLabelName func(childComplexity int, input UpdateProjectLabelName) int
UpdateProjectName func(childComplexity int, input *UpdateProjectName) int
UpdateTaskDescription func(childComplexity int, input UpdateTaskDescriptionInput) int
UpdateTaskDueDate func(childComplexity int, input UpdateTaskDueDate) int
UpdateTaskGroupLocation func(childComplexity int, input NewTaskGroupLocation) int
UpdateTaskGroupName func(childComplexity int, input UpdateTaskGroupName) int
UpdateTaskLocation func(childComplexity int, input NewTaskLocation) int
@ -153,6 +154,7 @@ type ComplexityRoot struct {
Assigned func(childComplexity int) int
CreatedAt func(childComplexity int) int
Description func(childComplexity int) int
DueDate func(childComplexity int) int
ID func(childComplexity int) int
Labels func(childComplexity int) int
Name func(childComplexity int) int
@ -228,6 +230,7 @@ type MutationResolver interface {
UpdateTaskDescription(ctx context.Context, input UpdateTaskDescriptionInput) (*pg.Task, error)
UpdateTaskLocation(ctx context.Context, input NewTaskLocation) (*UpdateTaskLocationPayload, error)
UpdateTaskName(ctx context.Context, input UpdateTaskName) (*pg.Task, error)
UpdateTaskDueDate(ctx context.Context, input UpdateTaskDueDate) (*pg.Task, error)
DeleteTask(ctx context.Context, input DeleteTaskInput) (*DeleteTaskPayload, error)
AssignTask(ctx context.Context, input *AssignTaskInput) (*pg.Task, error)
UnassignTask(ctx context.Context, input *UnassignTaskInput) (*pg.Task, error)
@ -267,6 +270,7 @@ type TaskResolver interface {
TaskGroup(ctx context.Context, obj *pg.Task) (*pg.TaskGroup, error)
Description(ctx context.Context, obj *pg.Task) (*string, error)
DueDate(ctx context.Context, obj *pg.Task) (*time.Time, error)
Assigned(ctx context.Context, obj *pg.Task) ([]ProjectMember, error)
Labels(ctx context.Context, obj *pg.Task) ([]pg.TaskLabel, error)
}
@ -619,6 +623,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.UpdateTaskDescription(childComplexity, args["input"].(UpdateTaskDescriptionInput)), true
case "Mutation.updateTaskDueDate":
if e.complexity.Mutation.UpdateTaskDueDate == nil {
break
}
args, err := ec.field_Mutation_updateTaskDueDate_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.UpdateTaskDueDate(childComplexity, args["input"].(UpdateTaskDueDate)), true
case "Mutation.updateTaskGroupLocation":
if e.complexity.Mutation.UpdateTaskGroupLocation == nil {
break
@ -925,6 +941,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Task.Description(childComplexity), true
case "Task.dueDate":
if e.complexity.Task.DueDate == nil {
break
}
return e.complexity.Task.DueDate(childComplexity), true
case "Task.id":
if e.complexity.Task.ID == nil {
break
@ -1271,6 +1294,7 @@ type Task {
name: String!
position: Float!
description: String
dueDate: Time
assigned: [ProjectMember!]!
labels: [TaskLabel!]!
}
@ -1448,6 +1472,11 @@ input UpdateTaskGroupName {
name: String!
}
input UpdateTaskDueDate {
taskID: UUID!
dueDate: Time
}
type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken!
@ -1478,6 +1507,7 @@ type Mutation {
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
updateTaskName(input: UpdateTaskName!): Task!
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
assignTask(input: AssignTaskInput): Task!
unassignTask(input: UnassignTaskInput): Task!
@ -1786,6 +1816,20 @@ func (ec *executionContext) field_Mutation_updateTaskDescription_args(ctx contex
return args, nil
}
func (ec *executionContext) field_Mutation_updateTaskDueDate_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 UpdateTaskDueDate
if tmp, ok := rawArgs["input"]; ok {
arg0, err = ec.unmarshalNUpdateTaskDueDate2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateTaskDueDate(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_updateTaskGroupLocation_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -3115,6 +3159,47 @@ func (ec *executionContext) _Mutation_updateTaskName(ctx context.Context, field
return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_updateTaskDueDate(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_updateTaskDueDate_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().UpdateTaskDueDate(rctx, args["input"].(UpdateTaskDueDate))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*pg.Task)
fc.Result = res
return ec.marshalNTask2ᚖgithubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋpgᚐTask(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_deleteTask(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -4619,6 +4704,37 @@ func (ec *executionContext) _Task_description(ctx context.Context, field graphql
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Task_dueDate(ctx context.Context, field graphql.CollectedField, obj *pg.Task) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Task",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Task().DueDate(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*time.Time)
fc.Result = res
return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) _Task_assigned(ctx context.Context, field graphql.CollectedField, obj *pg.Task) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -7166,6 +7282,30 @@ func (ec *executionContext) unmarshalInputUpdateTaskDescriptionInput(ctx context
return it, nil
}
func (ec *executionContext) unmarshalInputUpdateTaskDueDate(ctx context.Context, obj interface{}) (UpdateTaskDueDate, error) {
var it UpdateTaskDueDate
var asMap = obj.(map[string]interface{})
for k, v := range asMap {
switch k {
case "taskID":
var err error
it.TaskID, err = ec.unmarshalNUUID2githubᚗcomᚋgoogleᚋuuidᚐUUID(ctx, v)
if err != nil {
return it, err
}
case "dueDate":
var err error
it.DueDate, err = ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputUpdateTaskGroupName(ctx context.Context, obj interface{}) (UpdateTaskGroupName, error) {
var it UpdateTaskGroupName
var asMap = obj.(map[string]interface{})
@ -7462,6 +7602,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
case "updateTaskDueDate":
out.Values[i] = ec._Mutation_updateTaskDueDate(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "deleteTask":
out.Values[i] = ec._Mutation_deleteTask(ctx, field)
if out.Values[i] == graphql.Null {
@ -8012,6 +8157,17 @@ func (ec *executionContext) _Task(ctx context.Context, sel ast.SelectionSet, obj
res = ec._Task_description(ctx, field, obj)
return res
})
case "dueDate":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Task_dueDate(ctx, field, obj)
return res
})
case "assigned":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
@ -9265,6 +9421,10 @@ func (ec *executionContext) unmarshalNUpdateTaskDescriptionInput2githubᚗcomᚋ
return ec.unmarshalInputUpdateTaskDescriptionInput(ctx, v)
}
func (ec *executionContext) unmarshalNUpdateTaskDueDate2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateTaskDueDate(ctx context.Context, v interface{}) (UpdateTaskDueDate, error) {
return ec.unmarshalInputUpdateTaskDueDate(ctx, v)
}
func (ec *executionContext) unmarshalNUpdateTaskGroupName2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUpdateTaskGroupName(ctx context.Context, v interface{}) (UpdateTaskGroupName, error) {
return ec.unmarshalInputUpdateTaskGroupName(ctx, v)
}
@ -9658,6 +9818,29 @@ func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel as
return ec.marshalOString2string(ctx, sel, *v)
}
func (ec *executionContext) unmarshalOTime2timeᚐTime(ctx context.Context, v interface{}) (time.Time, error) {
return graphql.UnmarshalTime(v)
}
func (ec *executionContext) marshalOTime2timeᚐTime(ctx context.Context, sel ast.SelectionSet, v time.Time) graphql.Marshaler {
return graphql.MarshalTime(v)
}
func (ec *executionContext) unmarshalOTime2ᚖtimeᚐTime(ctx context.Context, v interface{}) (*time.Time, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalOTime2timeᚐTime(ctx, v)
return &res, err
}
func (ec *executionContext) marshalOTime2ᚖtimeᚐTime(ctx context.Context, sel ast.SelectionSet, v *time.Time) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec.marshalOTime2timeᚐTime(ctx, sel, *v)
}
func (ec *executionContext) unmarshalOUnassignTaskInput2githubᚗcomᚋjordanknottᚋprojectᚑcitadelᚋapiᚋgraphᚐUnassignTaskInput(ctx context.Context, v interface{}) (UnassignTaskInput, error) {
return ec.unmarshalInputUnassignTaskInput(ctx, v)
}

View File

@ -3,6 +3,8 @@
package graph
import (
"time"
"github.com/google/uuid"
"github.com/jordanknott/project-citadel/api/pg"
)
@ -168,6 +170,11 @@ type UpdateTaskDescriptionInput struct {
Description string `json:"description"`
}
type UpdateTaskDueDate struct {
TaskID uuid.UUID `json:"taskID"`
DueDate *time.Time `json:"dueDate"`
}
type UpdateTaskGroupName struct {
TaskGroupID uuid.UUID `json:"taskGroupID"`
Name string `json:"name"`

View File

@ -84,6 +84,7 @@ type Task {
name: String!
position: Float!
description: String
dueDate: Time
assigned: [ProjectMember!]!
labels: [TaskLabel!]!
}
@ -261,6 +262,11 @@ input UpdateTaskGroupName {
name: String!
}
input UpdateTaskDueDate {
taskID: UUID!
dueDate: Time
}
type Mutation {
createRefreshToken(input: NewRefreshToken!): RefreshToken!
@ -291,6 +297,7 @@ type Mutation {
updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
updateTaskName(input: UpdateTaskName!): Task!
updateTaskDueDate(input: UpdateTaskDueDate!): Task!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
assignTask(input: AssignTaskInput): Task!
unassignTask(input: UnassignTaskInput): Task!

View File

@ -260,6 +260,21 @@ func (r *mutationResolver) UpdateTaskName(ctx context.Context, input UpdateTaskN
return &task, err
}
func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTaskDueDate) (*pg.Task, error) {
var dueDate sql.NullTime
if input.DueDate == nil {
dueDate = sql.NullTime{Valid: false, Time: time.Now()}
} else {
dueDate = sql.NullTime{Valid: true, Time: *input.DueDate}
}
task, err := r.Repository.UpdateTaskDueDate(ctx, pg.UpdateTaskDueDateParams{
TaskID: input.TaskID,
DueDate: dueDate,
})
return &task, err
}
func (r *mutationResolver) DeleteTask(ctx context.Context, input DeleteTaskInput) (*DeleteTaskPayload, error) {
taskID, err := uuid.Parse(input.TaskID)
if err != nil {
@ -484,6 +499,13 @@ func (r *taskResolver) Description(ctx context.Context, obj *pg.Task) (*string,
return &task.Description.String, nil
}
func (r *taskResolver) DueDate(ctx context.Context, obj *pg.Task) (*time.Time, error) {
if obj.DueDate.Valid {
return &obj.DueDate.Time, nil
}
return nil, nil
}
func (r *taskResolver) Assigned(ctx context.Context, obj *pg.Task) ([]ProjectMember, error) {
taskMemberLinks, err := r.Repository.GetAssignedMembersForTask(ctx, obj.TaskID)
taskMembers := []ProjectMember{}

View File

@ -30,6 +30,7 @@ type Repository interface {
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
DeleteTaskLabelByID(ctx context.Context, taskLabelID uuid.UUID) error
UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error)
CreateProjectLabel(ctx context.Context, arg CreateProjectLabelParams) (ProjectLabel, error)
GetProjectLabelsForProject(ctx context.Context, projectID uuid.UUID) ([]ProjectLabel, error)
GetProjectLabelByID(ctx context.Context, projectLabelID uuid.UUID) (ProjectLabel, error)

View File

@ -56,11 +56,13 @@ type Querier interface {
GetTeamsForOrganization(ctx context.Context, organizationID uuid.UUID) ([]Team, error)
GetUserAccountByID(ctx context.Context, userID uuid.UUID) (UserAccount, error)
GetUserAccountByUsername(ctx context.Context, username string) (UserAccount, error)
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
UpdateProjectNameByID(ctx context.Context, arg UpdateProjectNameByIDParams) (Project, error)
UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescriptionParams) (Task, error)
UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error)
UpdateTaskGroupLocation(ctx context.Context, arg UpdateTaskGroupLocationParams) (TaskGroup, error)
UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocationParams) (Task, error)
UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams) (Task, error)

View File

@ -177,6 +177,30 @@ func (q *Queries) UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescr
return i, err
}
const updateTaskDueDate = `-- name: UpdateTaskDueDate :one
UPDATE task SET due_date = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date
`
type UpdateTaskDueDateParams struct {
TaskID uuid.UUID `json:"task_id"`
DueDate sql.NullTime `json:"due_date"`
}
func (q *Queries) UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error) {
row := q.db.QueryRowContext(ctx, updateTaskDueDate, arg.TaskID, arg.DueDate)
var i Task
err := row.Scan(
&i.TaskID,
&i.TaskGroupID,
&i.CreatedAt,
&i.Name,
&i.Position,
&i.Description,
&i.DueDate,
)
return i, err
}
const updateTaskLocation = `-- name: UpdateTaskLocation :one
UPDATE task SET task_group_id = $2, position = $3 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date
`

View File

@ -135,6 +135,28 @@ func (q *Queries) GetTaskGroupsForProject(ctx context.Context, projectID uuid.UU
return items, nil
}
const setTaskGroupName = `-- name: SetTaskGroupName :one
UPDATE task_group SET name = $2 WHERE task_group_id = $1 RETURNING task_group_id, project_id, created_at, name, position
`
type SetTaskGroupNameParams struct {
TaskGroupID uuid.UUID `json:"task_group_id"`
Name string `json:"name"`
}
func (q *Queries) SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error) {
row := q.db.QueryRowContext(ctx, setTaskGroupName, arg.TaskGroupID, arg.Name)
var i TaskGroup
err := row.Scan(
&i.TaskGroupID,
&i.ProjectID,
&i.CreatedAt,
&i.Name,
&i.Position,
)
return i, err
}
const updateTaskGroupLocation = `-- name: UpdateTaskGroupLocation :one
UPDATE task_group SET position = $2 WHERE task_group_id = $1 RETURNING task_group_id, project_id, created_at, name, position
`

View File

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

View File

@ -111,5 +111,19 @@ export default createGlobalStyle`
resize: none;
}
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: #262c49;
border-radius: 20px;
}
::-webkit-scrollbar-thumb {
background: #7367f0;
border-radius: 20px;
}
${mixin.placeholderColor(color.textLight)}
`;

View File

@ -17,6 +17,7 @@ const theme: DefaultTheme = {
primary: '194, 198, 220',
secondary: '255, 255, 255',
},
border: '65, 69, 97',
bg: {
primary: '16, 22, 58',
secondary: '38, 44, 73',

View File

@ -4,9 +4,15 @@ import TaskDetails from 'shared/components/TaskDetails';
import PopupMenu, { Popup, usePopup } from 'shared/components/PopupMenu';
import MemberManager from 'shared/components/MemberManager';
import { useRouteMatch, useHistory } from 'react-router';
import { useFindTaskQuery, useAssignTaskMutation, useUnassignTaskMutation } from 'shared/generated/graphql';
import {
useFindTaskQuery,
useUpdateTaskDueDateMutation,
useAssignTaskMutation,
useUnassignTaskMutation,
} from 'shared/generated/graphql';
import UserIDContext from 'App/context';
import MiniProfile from 'shared/components/MiniProfile';
import DueDateManager from 'shared/components/DueDateManager';
type DetailsProps = {
taskID: string;
@ -32,12 +38,18 @@ const Details: React.FC<DetailsProps> = ({
refreshCache,
}) => {
const { userID } = useContext(UserIDContext);
const { showPopup } = usePopup();
const { showPopup, hidePopup } = usePopup();
const history = useHistory();
const match = useRouteMatch();
const [currentMemberTask, setCurrentMemberTask] = useState('');
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
const [updateTaskDueDate] = useUpdateTaskDueDateMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
const [assignTask] = useAssignTaskMutation({
onCompleted: () => {
refetch();
@ -56,6 +68,7 @@ const Details: React.FC<DetailsProps> = ({
if (!data) {
return <div>loading</div>;
}
console.log(data.findTask);
return (
<>
<Modal
@ -108,6 +121,29 @@ const Details: React.FC<DetailsProps> = ({
);
}}
onOpenAddLabelPopup={onOpenAddLabelPopup}
onOpenDueDatePopop={(task, $targetRef) => {
showPopup(
$targetRef,
<Popup
title={'Change Due Date'}
tab={0}
onClose={() => {
hidePopup();
}}
>
<DueDateManager
task={task}
onDueDateChange={(t, newDueDate) => {
console.log(`${newDueDate}`);
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
hidePopup();
}}
onCancel={() => {}}
/>
</Popup>,
);
}}
/>
);
}}

View File

@ -229,14 +229,14 @@ const ProjectAction = styled.div`
display: flex;
align-items: center;
font-size: 15px;
color: var(--color-text);
color: rgba(${props => props.theme.colors.text.primary});
&:not(:last-child) {
margin-right: 16px;
}
&:hover {
color: var(--color-text-hover);
color: rgba(${props => props.theme.colors.text.secondary});
}
`;
@ -490,15 +490,15 @@ const Project = () => {
);
}}
>
<Tags size={13} color="var(--color-icon)" />
<Tags width={13} height={13} />
<ProjectActionText>Labels</ProjectActionText>
</ProjectAction>
<ProjectAction>
<ToggleOn size={13} color="var(--color-icon)" />
<ToggleOn width={13} height={13} />
<ProjectActionText>Fields</ProjectActionText>
</ProjectAction>
<ProjectAction>
<Bolt size={13} color="var(--color-icon)" />
<Bolt width={13} height={13} />
<ProjectActionText>Rules</ProjectActionText>
</ProjectAction>
</ProjectActions>

View File

@ -32,8 +32,8 @@ type LoginFormData = {
};
type DueDateFormData = {
endDate: Date;
endTime: string | null;
endDate: string;
endTime: string;
};
type LoginProps = {

View File

@ -35,6 +35,7 @@ type Task = {
taskGroup: InnerTaskGroup;
name: string;
position: number;
dueDate?: string;
labels: TaskLabel[];
description?: string | null;
assigned?: Array<TaskUser>;

View File

@ -107,6 +107,7 @@ type ButtonProps = {
variant?: 'filled' | 'outline' | 'flat' | 'lineDown' | 'gradient' | 'relief';
color?: 'primary' | 'danger' | 'success' | 'warning' | 'dark';
disabled?: boolean;
type?: 'button' | 'submit';
className?: string;
onClick?: () => void;
};
@ -116,6 +117,7 @@ const Button: React.FC<ButtonProps> = ({
fontSize = '14px',
color = 'primary',
variant = 'filled',
type = 'button',
onClick,
className,
children,
@ -128,38 +130,38 @@ const Button: React.FC<ButtonProps> = ({
switch (variant) {
case 'filled':
return (
<Filled onClick={handleClick} className={className} disabled={disabled} color={color}>
<Filled type={type} onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Filled>
);
case 'outline':
return (
<Outline onClick={handleClick} className={className} disabled={disabled} color={color}>
<Outline type={type} onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Outline>
);
case 'flat':
return (
<Flat onClick={handleClick} className={className} disabled={disabled} color={color}>
<Flat type={type} onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Flat>
);
case 'lineDown':
return (
<LineDown onClick={handleClick} className={className} disabled={disabled} color={color}>
<LineDown type={type} onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
<LineX color={color} />
</LineDown>
);
case 'gradient':
return (
<Gradient onClick={handleClick} className={className} disabled={disabled} color={color}>
<Gradient type={type} onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Gradient>
);
case 'relief':
return (
<Relief onClick={handleClick} className={className} disabled={disabled} color={color}>
<Relief type={type} onClick={handleClick} className={className} disabled={disabled} color={color}>
<Text fontSize={fontSize}>{children}</Text>
</Relief>
);

View File

@ -22,7 +22,7 @@ export const EditorTextarea = styled(TextareaAutosize)`
padding: 0;
font-size: 16px;
line-height: 20px;
color: var(--color-input-text-focus);
color: rgba(${props => props.theme.colors.text.secondary});
&:focus {
border: none;
outline: none;
@ -84,7 +84,7 @@ export const ListCardContainer = styled.div<{ isActive: boolean; editable: boole
position: relative;
background-color: ${props =>
props.isActive && !props.editable ? mixin.darken('#262c49', 0.1) : 'var(--color-background)'};
props.isActive && !props.editable ? mixin.darken('#262c49', 0.1) : `rgba(${props.theme.colors.bg.secondary})`};
`;
export const ListCardInnerContainer = styled.div`
@ -145,7 +145,7 @@ export const CardTitle = styled.span`
overflow: hidden;
text-decoration: none;
word-wrap: break-word;
color: var(--color-text);
color: rgba(${props => props.theme.colors.text.primary});
`;
export const CardMembers = styled.div`

View File

@ -1,12 +1,16 @@
import React, { useRef } from 'react';
import { action } from '@storybook/addon-actions';
import DueDateManager from '.';
import BaseStyles from 'App/BaseStyles';
import NormalizeStyles from 'App/NormalizeStyles';
import { theme } from 'App/ThemeStyles';
import styled, { ThemeProvider } from 'styled-components';
import { Popup } from '../PopupMenu';
import styled from 'styled-components';
import DueDateManager from '.';
const PopupWrapper = styled.div`
width: 300px;
width: 310px;
`;
export default {
component: DueDateManager,
title: 'DueDateManager',
@ -20,6 +24,10 @@ export default {
export const Default = () => {
return (
<>
<NormalizeStyles />
<BaseStyles />
<ThemeProvider theme={theme}>
<PopupWrapper>
<Popup title={null} tab={0}>
<DueDateManager
@ -59,5 +67,7 @@ export const Default = () => {
/>
</Popup>
</PopupWrapper>
</ThemeProvider>
</>
);
};

View File

@ -1,5 +1,7 @@
import styled from 'styled-components';
import Button from 'shared/components/Button';
import { mixin } from 'shared/utils/styles';
import Input from 'shared/components/Input';
export const Wrapper = styled.div`
display: flex
@ -8,7 +10,37 @@ display: flex
background: #262c49;
font-family: 'Droid Sans', sans-serif;
border: none;
}
& .react-datepicker__triangle {
display: none;
}
& .react-datepicker-popper {
z-index: 10000;
margin-top: 0;
}
& .react-datepicker-time__header {
color: rgba(${props => props.theme.colors.text.primary});
}
& .react-datepicker__time-list-item {
color: rgba(${props => props.theme.colors.text.primary});
}
& .react-datepicker__time-container .react-datepicker__time
.react-datepicker__time-box ul.react-datepicker__time-list
li.react-datepicker__time-list-item:hover {
color: rgba(${props => props.theme.colors.text.secondary});
background: rgba(${props => props.theme.colors.bg.secondary});
}
& .react-datepicker__time-container .react-datepicker__time {
background: rgba(${props => props.theme.colors.bg.primary});
}
& .react-datepicker--time-only {
background: rgba(${props => props.theme.colors.bg.primary});
border: 1px solid rgba(${props => props.theme.colors.border});
}
& .react-datepicker * {
box-sizing: content-box;
}
& .react-datepicker__day-name {
color: #c2c6dc;
@ -56,6 +88,9 @@ display: flex
background: none;
border: none;
}
& .react-datepicker__header--time {
border-bottom: 1px solid rgba(${props => props.theme.colors.border});
}
`;
@ -66,21 +101,10 @@ export const DueDatePickerWrapper = styled.div`
justify-content: center;
`;
export const ConfirmAddDueDate = styled.div`
background-color: #5aac44;
box-shadow: none;
border: none;
color: #fff;
export const ConfirmAddDueDate = styled(Button)`
float: left;
margin: 0 4px 0 0;
cursor: pointer;
display: inline-block;
font-weight: 400;
line-height: 20px;
padding: 6px 12px;
text-align: center;
border-radius: 3px;
font-size: 14px;
`;
export const CancelDueDate = styled.div`
@ -92,6 +116,12 @@ export const CancelDueDate = styled.div`
cursor: pointer;
`;
export const DueDateInput = styled(Input)`
margin-top: 15px;
margin-bottom: 5px;
padding-right: 10px;
`;
export const ActionWrapper = styled.div`
padding-top: 8px;
width: 100%;

View File

@ -1,11 +1,11 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, forwardRef } from 'react';
import moment from 'moment';
import styled from 'styled-components';
import DatePicker from 'react-datepicker';
import { Cross } from 'shared/icons';
import _ from 'lodash';
import { Wrapper, ActionWrapper, DueDatePickerWrapper, ConfirmAddDueDate, CancelDueDate } from './Styles';
import { Wrapper, ActionWrapper, DueDateInput, DueDatePickerWrapper, ConfirmAddDueDate, CancelDueDate } from './Styles';
import 'react-datepicker/dist/react-datepicker.css';
import { getYear, getMonth } from 'date-fns';
@ -17,6 +17,14 @@ type DueDateManagerProps = {
onCancel: () => void;
};
const Form = styled.form`
padding-top: 25px;
`;
const FormField = styled.div`
width: 50%;
display: inline-block;
`;
const HeaderSelectLabel = styled.div`
display: inline-block;
position: relative;
@ -104,12 +112,17 @@ const HeaderActions = styled.div`
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onCancel }) => {
const now = moment();
const [textStartDate, setTextStartDate] = useState(now.format('YYYY-MM-DD'));
const [startDate, setStartDate] = useState(new Date());
useEffect(() => {
setTextStartDate(moment(startDate).format('YYYY-MM-DD'));
}, [startDate]);
const [textEndTime, setTextEndTime] = useState(now.format('h:mm A'));
const [endTime, setEndTime] = useState(now.toDate());
useEffect(() => {
setTextEndTime(moment(endTime).format('h:mm A'));
}, [endTime]);
const years = _.range(2010, getYear(new Date()) + 10, 1);
const months = [
'January',
@ -125,29 +138,75 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
'November',
'December',
];
const { register, handleSubmit, errors, setError, formState } = useForm<DueDateFormData>();
const { register, handleSubmit, errors, setValue, setError, formState } = useForm<DueDateFormData>();
const saveDueDate = (data: any) => {
console.log(data);
const newDate = moment(`${data.endDate} ${data.endTime}`, 'YYYY-MM-DD h:mm A');
if (newDate.isValid()) {
onDueDateChange(task, newDate.toDate());
}
};
console.log(errors);
register({ name: 'endTime' }, { required: 'End time is required' });
useEffect(() => {
setValue('endTime', now.format('h:mm A'));
}, []);
const CustomTimeInput = forwardRef(({ value, onClick }: any, $ref: any) => {
return (
<DueDateInput
id="endTime"
name="endTime"
ref={$ref}
onChange={e => {
console.log(`onCahnge ${e.currentTarget.value}`);
setTextEndTime(e.currentTarget.value);
setValue('endTime', e.currentTarget.value);
}}
width="100%"
variant="alternate"
label="Date"
onClick={onClick}
value={value}
/>
);
});
console.log(`textStartDate ${textStartDate}`);
return (
<Wrapper>
<form>
<input
type="text"
<Form onSubmit={handleSubmit(saveDueDate)}>
<FormField>
<DueDateInput
id="endDate"
name="endDate"
width="100%"
variant="alternate"
label="Date"
onChange={e => {
setTextStartDate(e.currentTarget.value);
}}
value={textStartDate}
ref={register({
required: 'End due date is required.',
validate: value => {
const isValid = moment(value, 'YYYY-MM-DD').isValid();
console.log(`${value} - ${isValid}`);
return isValid;
},
required: 'End date is required.',
})}
/>
</form>
</FormField>
<FormField>
<DatePicker
selected={endTime}
onChange={date => {
const changedDate = moment(date ?? new Date());
console.log(`changed ${date}`);
setEndTime(changedDate.toDate());
setValue('endTime', changedDate.format('h:mm A'));
}}
showTimeSelect
showTimeSelectOnly
timeIntervals={15}
timeCaption="Time"
dateFormat="h:mm aa"
customInput={<CustomTimeInput />}
/>
</FormField>
<DueDatePickerWrapper>
<DatePicker
useWeekdaysShort
@ -195,15 +254,27 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
)}
selected={startDate}
inline
onChange={date => setStartDate(date ?? new Date())}
onChange={date => {
setStartDate(date ?? new Date());
}}
/>
</DueDatePickerWrapper>
<ActionWrapper>
<ConfirmAddDueDate onClick={() => onDueDateChange(task, startDate)}>Save</ConfirmAddDueDate>
<ConfirmAddDueDate
type="submit"
onClick={() => {
// const newDate = moment(startDate).format('YYYY-MM-DD');
// const newTime = moment(endTime).format('h:mm A');
// onDueDateChange(task, moment(`${newDate} ${newTime}`, 'YYYY-MM-DD h:mm A').toDate());
}}
>
Save
</ConfirmAddDueDate>
<CancelDueDate onClick={onCancel}>
<Cross size={16} color="#c2c6dc" />
</CancelDueDate>
</ActionWrapper>
</Form>
</Wrapper>
);
};

View File

@ -1,5 +1,5 @@
import React from 'react';
import styled from 'styled-components/macro';
import React, { useState, useEffect } from 'react';
import styled, { css } from 'styled-components/macro';
const InputWrapper = styled.div<{ width: string }>`
position: relative;
@ -32,7 +32,13 @@ const InputLabel = styled.span<{ width: string }>`
}
`;
const InputInput = styled.input<{ hasIcon: boolean; width: string; focusBg: string; borderColor: string }>`
const InputInput = styled.input<{
hasValue: boolean;
hasIcon: boolean;
width: string;
focusBg: string;
borderColor: string;
}>`
width: ${props => props.width};
font-size: 14px;
border: 1px solid rgba(0, 0, 0, 0.2);
@ -54,6 +60,14 @@ const InputInput = styled.input<{ hasIcon: boolean; width: string; focusBg: stri
color: rgba(115, 103, 240);
transform: translate(-3px, -90%);
}
${props =>
props.hasValue &&
css`
& ~ ${InputLabel} {
color: rgba(115, 103, 240);
transform: translate(-3px, -90%);
}
`}
`;
const Icon = styled.div`
@ -68,14 +82,55 @@ type InputProps = {
width?: string;
placeholder?: string;
icon?: JSX.Element;
id?: string;
name?: string;
className?: string;
value?: string;
onClick?: (e: React.MouseEvent<HTMLInputElement>) => void;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
};
const Input: React.FC<InputProps> = ({ width = 'auto', variant = 'normal', label, placeholder, icon }) => {
const Input = React.forwardRef(
(
{
width = 'auto',
variant = 'normal',
label,
placeholder,
icon,
name,
onChange,
className,
onClick,
value: initialValue,
id,
}: InputProps,
$ref: any,
) => {
const [value, setValue] = useState(initialValue ?? '');
useEffect(() => {
if (initialValue) {
setValue(initialValue);
}
}, [initialValue]);
const borderColor = variant === 'normal' ? 'rgba(0, 0, 0, 0.2)' : '#414561';
const focusBg = variant === 'normal' ? 'rgba(38, 44, 73, )' : 'rgba(16, 22, 58, 1)';
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.currentTarget.value);
if (onChange) {
onChange(e);
}
};
return (
<InputWrapper width={width}>
<InputWrapper className={className} width={width}>
<InputInput
hasValue={value !== ''}
ref={$ref}
id={id}
name={name}
onClick={onClick}
onChange={handleChange}
value={value}
hasIcon={typeof icon !== 'undefined'}
width={width}
placeholder={placeholder}
@ -86,6 +141,7 @@ const Input: React.FC<InputProps> = ({ width = 'auto', variant = 'normal', label
<Icon>{icon && icon}</Icon>
</InputWrapper>
);
};
},
);
export default Input;

View File

@ -12,6 +12,7 @@ import {
} from 'shared/utils/draggables';
import { Container, BoardWrapper } from './Styles';
import moment from 'moment';
interface SimpleProps {
taskGroups: Array<TaskGroup>;
@ -165,6 +166,14 @@ const SimpleLists: React.FC<SimpleProps> = ({
taskGroupID={taskGroup.id}
description=""
labels={task.labels.map(label => label.projectLabel)}
dueDate={
task.dueDate
? {
isPastDue: false,
formattedDate: moment(task.dueDate).format('MMM D, YYYY'),
}
: undefined
}
title={task.name}
members={task.assigned}
onClick={() => {

View File

@ -66,6 +66,7 @@ export const Default = () => {
onMemberProfile={action('profile')}
onOpenAddMemberPopup={action('open add member popup')}
onOpenAddLabelPopup={action('open add label popup')}
onOpenDueDatePopop={action('open due date popup')}
/>
);
}}

View File

@ -39,6 +39,7 @@ import {
} from './Styles';
import convertDivElementRefToBounds from 'shared/utils/boundingRect';
import moment from 'moment';
type TaskContentProps = {
onEditContent: () => void;
@ -106,6 +107,7 @@ type TaskDetailsProps = {
onDeleteTask: (task: Task) => void;
onOpenAddMemberPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenAddLabelPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onOpenDueDatePopop: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
onMemberProfile: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
onCloseModal: () => void;
};
@ -118,6 +120,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
onCloseModal,
onOpenAddMemberPopup,
onOpenAddLabelPopup,
onOpenDueDatePopop,
onMemberProfile,
}) => {
const [editorOpen, setEditorOpen] = useState(false);
@ -143,6 +146,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
const onAddMember = () => {
onOpenAddMemberPopup(task, $addMemberRef);
};
const $dueDateLabel = useRef<HTMLDivElement>(null);
const $addLabelRef = useRef<HTMLDivElement>(null);
const onAddLabel = () => {
onOpenAddLabelPopup(task, $addLabelRef);
@ -229,7 +233,15 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
</TaskDetailsAddLabel>
</TaskDetailLabels>
<TaskDetailSectionTitle>Due Date</TaskDetailSectionTitle>
<NoDueDateLabel>No due date</NoDueDateLabel>
{task.dueDate ? (
<NoDueDateLabel ref={$dueDateLabel} onClick={() => onOpenDueDatePopop(task, $dueDateLabel)}>
{moment(task.dueDate).format('MMM D [at] h:mm A')}
</NoDueDateLabel>
) : (
<NoDueDateLabel ref={$dueDateLabel} onClick={() => onOpenDueDatePopop(task, $dueDateLabel)}>
No due date
</NoDueDateLabel>
)}
</TaskDetailsSidebar>
</TaskDetailsWrapper>
</>

View File

@ -110,6 +110,7 @@ export type Task = {
name: Scalars['String'];
position: Scalars['Float'];
description?: Maybe<Scalars['String']>;
dueDate?: Maybe<Scalars['Time']>;
assigned: Array<ProjectMember>;
labels: Array<TaskLabel>;
};
@ -310,6 +311,16 @@ export type UpdateTaskLocationPayload = {
task: Task;
};
export type UpdateTaskGroupName = {
taskGroupID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateTaskDueDate = {
taskID: Scalars['UUID'];
dueDate?: Maybe<Scalars['Time']>;
};
export type Mutation = {
__typename?: 'Mutation';
createRefreshToken: RefreshToken;
@ -325,6 +336,7 @@ export type Mutation = {
updateProjectLabelColor: ProjectLabel;
createTaskGroup: TaskGroup;
updateTaskGroupLocation: TaskGroup;
updateTaskGroupName: TaskGroup;
deleteTaskGroup: DeleteTaskGroupPayload;
addTaskLabel: Task;
removeTaskLabel: Task;
@ -333,6 +345,7 @@ export type Mutation = {
updateTaskDescription: Task;
updateTaskLocation: UpdateTaskLocationPayload;
updateTaskName: Task;
updateTaskDueDate: Task;
deleteTask: DeleteTaskPayload;
assignTask: Task;
unassignTask: Task;
@ -400,6 +413,11 @@ export type MutationUpdateTaskGroupLocationArgs = {
};
export type MutationUpdateTaskGroupNameArgs = {
input: UpdateTaskGroupName;
};
export type MutationDeleteTaskGroupArgs = {
input: DeleteTaskGroupInput;
};
@ -440,6 +458,11 @@ export type MutationUpdateTaskNameArgs = {
};
export type MutationUpdateTaskDueDateArgs = {
input: UpdateTaskDueDate;
};
export type MutationDeleteTaskArgs = {
input: DeleteTaskInput;
};
@ -658,7 +681,7 @@ export type FindProjectQuery = (
& Pick<TaskGroup, 'id' | 'name' | 'position'>
& { tasks: Array<(
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'position' | 'description'>
& Pick<Task, 'id' | 'name' | 'position' | 'description' | 'dueDate'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id' | 'name' | 'position'>
@ -698,7 +721,7 @@ export type FindTaskQuery = (
{ __typename?: 'Query' }
& { findTask: (
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'name' | 'description' | 'position'>
& Pick<Task, 'id' | 'name' | 'description' | 'dueDate' | 'position'>
& { taskGroup: (
{ __typename?: 'TaskGroup' }
& Pick<TaskGroup, 'id'>
@ -852,6 +875,20 @@ export type UpdateTaskDescriptionMutation = (
) }
);
export type UpdateTaskDueDateMutationVariables = {
taskID: Scalars['UUID'];
dueDate?: Maybe<Scalars['Time']>;
};
export type UpdateTaskDueDateMutation = (
{ __typename?: 'Mutation' }
& { updateTaskDueDate: (
{ __typename?: 'Task' }
& Pick<Task, 'id' | 'dueDate'>
) }
);
export type UpdateTaskGroupLocationMutationVariables = {
taskGroupID: Scalars['UUID'];
position: Scalars['Float'];
@ -1298,6 +1335,7 @@ export const FindProjectDocument = gql`
name
position
description
dueDate
taskGroup {
id
name
@ -1370,6 +1408,7 @@ export const FindTaskDocument = gql`
id
name
description
dueDate
position
taskGroup {
id
@ -1705,6 +1744,40 @@ export function useUpdateTaskDescriptionMutation(baseOptions?: ApolloReactHooks.
export type UpdateTaskDescriptionMutationHookResult = ReturnType<typeof useUpdateTaskDescriptionMutation>;
export type UpdateTaskDescriptionMutationResult = ApolloReactCommon.MutationResult<UpdateTaskDescriptionMutation>;
export type UpdateTaskDescriptionMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>;
export const UpdateTaskDueDateDocument = gql`
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time) {
updateTaskDueDate(input: {taskID: $taskID, dueDate: $dueDate}) {
id
dueDate
}
}
`;
export type UpdateTaskDueDateMutationFn = ApolloReactCommon.MutationFunction<UpdateTaskDueDateMutation, UpdateTaskDueDateMutationVariables>;
/**
* __useUpdateTaskDueDateMutation__
*
* To run a mutation, you first call `useUpdateTaskDueDateMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateTaskDueDateMutation` 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 [updateTaskDueDateMutation, { data, loading, error }] = useUpdateTaskDueDateMutation({
* variables: {
* taskID: // value for 'taskID'
* dueDate: // value for 'dueDate'
* },
* });
*/
export function useUpdateTaskDueDateMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateTaskDueDateMutation, UpdateTaskDueDateMutationVariables>) {
return ApolloReactHooks.useMutation<UpdateTaskDueDateMutation, UpdateTaskDueDateMutationVariables>(UpdateTaskDueDateDocument, baseOptions);
}
export type UpdateTaskDueDateMutationHookResult = ReturnType<typeof useUpdateTaskDueDateMutation>;
export type UpdateTaskDueDateMutationResult = ApolloReactCommon.MutationResult<UpdateTaskDueDateMutation>;
export type UpdateTaskDueDateMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskDueDateMutation, UpdateTaskDueDateMutationVariables>;
export const UpdateTaskGroupLocationDocument = gql`
mutation updateTaskGroupLocation($taskGroupID: UUID!, $position: Float!) {
updateTaskGroupLocation(input: {taskGroupID: $taskGroupID, position: $position}) {

View File

@ -30,6 +30,7 @@ query findProject($projectId: String!) {
name
position
description
dueDate
taskGroup {
id
name

View File

@ -3,6 +3,7 @@ query findTask($taskID: UUID!) {
id
name
description
dueDate
position
taskGroup {
id

View File

@ -0,0 +1,12 @@
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time) {
updateTaskDueDate (
input: {
taskID: $taskID
dueDate: $dueDate
}
) {
id
dueDate
}
}

View File

@ -1,24 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Bolt = ({ size, color }: Props) => {
const Bolt: React.FC<IconProps> = ({ width = '16px', height = '16px' }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" width={size} height={size}>
<path
fill={color}
d="M296 160H180.6l42.6-129.8C227.2 15 215.7 0 200 0H56C44 0 33.8 8.9 32.2 20.8l-32 240C-1.7 275.2 9.5 288 24 288h118.7L96.6 482.5c-3.6 15.2 8 29.5 23.3 29.5 8.4 0 16.4-4.4 20.8-12l176-304c9.3-15.9-2.2-36-20.7-36z"
/>
</svg>
<Icon width={width} height={height} viewBox="0 0 320 512">
<path d="M296 160H180.6l42.6-129.8C227.2 15 215.7 0 200 0H56C44 0 33.8 8.9 32.2 20.8l-32 240C-1.7 275.2 9.5 288 24 288h118.7L96.6 482.5c-3.6 15.2 8 29.5 23.3 29.5 8.4 0 16.4-4.4 20.8-12l176-304c9.3-15.9-2.2-36-20.7-36z" />
</Icon>
);
};
Bolt.defaultProps = {
size: 16,
color: '#000',
};
export default Bolt;

View File

@ -0,0 +1,28 @@
import React from 'react';
import styled from 'styled-components/macro';
export type IconProps = {
width: number | string;
height: number | string;
};
type Props = {
width: number | string;
height: number | string;
viewBox: string;
className?: string;
};
const Svg = styled.svg`
fill: rgba(${props => props.theme.colors.text.primary});
`;
const Icon: React.FC<Props> = ({ width, height, viewBox, className, children }) => {
return (
<Svg className={className} width={width} height={height} xmlns="http://www.w3.org/2000/svg" viewBox={viewBox}>
{children}
</Svg>
);
};
export default Icon;

View File

@ -1,24 +1,12 @@
import React from 'react';
import Icon, { IconProps } from './Icon';
type Props = {
size: number | string;
color: string;
};
const Tags = ({ size, color }: Props) => {
const Tags: React.FC<IconProps> = ({ width = '16px', height = '16px' }) => {
return (
<svg width={size} height={size} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512">
<path
fill={color}
d="M497.941 225.941L286.059 14.059A48 48 0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v204.118a48 48 0 0 0 14.059 33.941l211.882 211.882c18.744 18.745 49.136 18.746 67.882 0l204.118-204.118c18.745-18.745 18.745-49.137 0-67.882zM112 160c-26.51 0-48-21.49-48-48s21.49-48 48-48 48 21.49 48 48-21.49 48-48 48zm513.941 133.823L421.823 497.941c-18.745 18.745-49.137 18.745-67.882 0l-.36-.36L527.64 323.522c16.999-16.999 26.36-39.6 26.36-63.64s-9.362-46.641-26.36-63.64L331.397 0h48.721a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882z"
/>
</svg>
<Icon width={width} height={height} viewBox="0 0 640 512">
<path d="M497.941 225.941L286.059 14.059A48 48 0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v204.118a48 48 0 0 0 14.059 33.941l211.882 211.882c18.744 18.745 49.136 18.746 67.882 0l204.118-204.118c18.745-18.745 18.745-49.137 0-67.882zM112 160c-26.51 0-48-21.49-48-48s21.49-48 48-48 48 21.49 48 48-21.49 48-48 48zm513.941 133.823L421.823 497.941c-18.745 18.745-49.137 18.745-67.882 0l-.36-.36L527.64 323.522c16.999-16.999 26.36-39.6 26.36-63.64s-9.362-46.641-26.36-63.64L331.397 0h48.721a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882z" />
</Icon>
);
};
Tags.defaultProps = {
size: 16,
color: '#000',
};
export default Tags;

View File

@ -1,24 +1,13 @@
import React from 'react';
type Props = {
size: number | string;
color: string;
};
import Icon, { IconProps } from './Icon';
const ToggleOn = ({ size, color }: Props) => {
const ToggleOn: React.FC<IconProps> = ({ width = '16px', height = '16px' }) => {
return (
<svg width={size} height={size} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path
fill={color}
d="M384 64H192C86 64 0 150 0 256s86 192 192 192h192c106 0 192-86 192-192S490 64 384 64zm0 320c-70.8 0-128-57.3-128-128 0-70.8 57.3-128 128-128 70.8 0 128 57.3 128 128 0 70.8-57.3 128-128 128z"
/>
</svg>
<Icon width={width} height={height} viewBox="0 0 576 512">
<path d="M384 64H192C86 64 0 150 0 256s86 192 192 192h192c106 0 192-86 192-192S490 64 384 64zm0 320c-70.8 0-128-57.3-128-128 0-70.8 57.3-128 128-128 70.8 0 128 57.3 128 128 0 70.8-57.3 128-128 128z" />
</Icon>
);
};
ToggleOn.defaultProps = {
size: 16,
color: '#000',
};
export default ToggleOn;