,
@@ -36,35 +54,55 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
});
};
- // TODO progress bar below progress input
- // TODO auto-colour tags while typing
- // TODO comments
- // TODO make sure all buttons have title attributes, maybe remove labels from array delete buttons?
+ // Check if a task's due date is in the past
+ const checkOverdue = (values: { metadata: { due?: string } }) => {
+ if ('due' in values.metadata && values.metadata.due !== undefined) {
+ return Date.parse(values.metadata.due) < (new Date()).getTime();
+ }
+ return false;
+ };
return (
-
{editing ? 'Update task' : 'Create new task'}
+
+ {editing ? 'Update task' : 'Create new task'}
+ {editing &&
+ {
+ [
+ 'created' in task!.metadata ? `Created ${formatDate(task!.metadata.created, dateFormat)}` : null,
+ 'updated' in task!.metadata ? `Updated ${formatDate(task!.metadata.updated, dateFormat)}` : null
+ ].filter(i => i).join(', ')
+ }
+ }
+
{
- const errors: { name?: string } = {};
-
- // TODO validation
+ validate={(values: KanbnTaskValidationInput): KanbnTaskValidationOutput => {
+ const errors: KanbnTaskValidationOutput = {
+ name: '',
+ metadata: {
+ tags: []
+ },
+ subTasks: [],
+ comments: []
+ };
// Task name cannot be empty
if (!values.name) {
@@ -75,6 +113,32 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
if (values.id in tasks && tasks[values.id].uuid !== values.uuid) {
errors.name = 'There is already a task with the same name or id.';
}
+
+ // Tag names cannot be empty
+ for (let i = 0; i < values.metadata.tags.length; i++) {
+ if (!values.metadata.tags[i]) {
+ errors.metadata.tags[i] = 'Tag cannot be empty.';
+ }
+ }
+
+ // Sub-tasks text cannot be empty
+ for (let i = 0; i < values.subTasks.length; i++) {
+ if (!values.subTasks[i].text) {
+ errors.subTasks[i] = {
+ text: 'Sub-task text cannot be empty.'
+ };
+ }
+ }
+
+ // Comments text cannot be empty
+ for (let i = 0; i < values.comments.length; i++) {
+ if (!values.comments[i].text) {
+ errors.comments[i] = {
+ text: 'Comment text cannot be empty.'
+ };
+ }
+ }
+
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
@@ -134,6 +198,63 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
name="description"
/>
+
+
Sub-tasks
+
+ {({ insert, remove, push }) => (
+
+ {values.subTasks.length > 0 && values.subTasks.map((subTask, index) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+ )}
+
+
Relations
@@ -171,9 +292,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
@@ -182,54 +304,63 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
)}
-
-
Sub-tasks
-
+
+
Comments
+
{({ insert, remove, push }) => (
- {values.subTasks.length > 0 && values.subTasks.map((subTask, index) => (
-
-
-
-
+ {values.comments.length > 0 && values.comments.map((comment, index) => (
+
+
+
+
+
+
+
+ {formatDate(comment.date, dateFormat)}
+
+
+
+
-
-
-
-
-
-
+
))}
@@ -237,9 +368,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
@@ -252,7 +384,7 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
{editing &&
+
+
+
+
+
+
remove(index)}
>
- Delete
+
@@ -369,9 +545,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
push('')}
>
- Add tag
+ Add tag
diff --git a/src/TaskItem.tsx b/src/TaskItem.tsx
index 03001bf..bfac009 100644
--- a/src/TaskItem.tsx
+++ b/src/TaskItem.tsx
@@ -10,11 +10,20 @@ const TaskItem = ({ task, position, dateFormat, vscode }: {
dateFormat: string,
vscode: VSCodeApi
}) => {
- const createdDate = 'created' in task.metadata ? formatDate(task.metadata.created, dateFormat) : '';
- const updatedDate = 'updated' in task.metadata ? formatDate(task.metadata.updated, dateFormat) : '';
- const startedDate = 'started' in task.metadata ? formatDate(task.metadata.started, dateFormat) : '';
- const dueDate = 'due' in task.metadata ? formatDate(task.metadata.due, dateFormat) : '';
- const completedDate = 'completed' in task.metadata ? formatDate(task.metadata.completed, dateFormat) : '';
+ const createdDate = 'created' in task.metadata ? formatDate(task.metadata.created, dateFormat) : null;
+ const updatedDate = 'updated' in task.metadata ? formatDate(task.metadata.updated, dateFormat) : null;
+ const startedDate = 'started' in task.metadata ? formatDate(task.metadata.started, dateFormat) : null;
+ const dueDate = 'due' in task.metadata ? formatDate(task.metadata.due, dateFormat) : null;
+ const completedDate = 'completed' in task.metadata ? formatDate(task.metadata.completed, dateFormat) : null;
+
+ // Check if a task's due date is in the past
+ const checkOverdue = (task: KanbnTask) => {
+ if ('due' in task.metadata && task.metadata.due !== undefined) {
+ return Date.parse(task.metadata.due) < (new Date()).getTime();
+ }
+ return false;
+ };
+
return (
i).join('\n')}>
- {updatedDate || createdDate}
+ i).join(' ')}
+ >{updatedDate || createdDate}
}
{
@@ -111,7 +125,7 @@ const TaskItem = ({ task, position, dateFormat, vscode }: {
task.workload !== undefined &&
task.progress !== undefined &&
}
diff --git a/src/index.css b/src/index.css
index 9950fe5..ea5cc96 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,7 +1,8 @@
body {
margin: 0;
padding: 1em;
- font-family: sans-serif;
+ font-family: var(--vscode-font-family);
+ font-size: var(--vscode-font-size);
background-color: var(--vscode-editor-background);
color: var(--vscode-foreground);
}
@@ -204,6 +205,10 @@ body {
color: #333;
}
+.kanbn-task-overdue {
+ color: #f42 !important;
+}
+
.kanbn-task-progress {
position: absolute;
bottom: -2px;
@@ -220,14 +225,27 @@ body {
border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid;
}
+.kanbn-task-editor-dates {
+ font-size: var(--vscode-font-size);
+ font-style: italic;
+ font-weight: normal;
+ opacity: 0.8;
+ float: right;
+}
+
.kanbn-task-editor-form {
display: flex;
}
-.kanbn-task-editor-field .kanbn-task-editor-title {
- font-size: 1.1em;
- padding: 0.5em 0;
- border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid;
+.kanbn-task-editor-field .kanbn-task-editor-title,
+.kanbn-task-editor-field-label p {
+ color: var(--vscode-editor-foreground);
+ font-size: 0.8em;
+ letter-spacing: 0.1em;
+ font-weight: bold;
+ text-transform: uppercase;
+ padding: 4px 0;
+ border-bottom: none;
}
.kanbn-task-editor-column-left {
@@ -243,15 +261,6 @@ body {
margin-bottom: 1em;
}
-.kanbn-task-editor-field-label p {
- color: var(--vscode-editor-foreground);
- font-size: 0.8em;
- letter-spacing: 0.1em;
- font-weight: bold;
- text-transform: uppercase;
- padding: 4px 0;
-}
-
body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-picker-indicator {
filter: invert(1);
}
@@ -270,6 +279,11 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
border: 1px transparent solid;
}
+.kanbn-task-editor-field-input[type=date] {
+ font-family: var(--vscode-font-family);
+ font-size: var(--vscode-font-size);
+}
+
.kanbn-task-editor-field-select {
padding-bottom: 7px;
}
@@ -356,3 +370,46 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
.kanbn-task-editor-column-buttons .kanbn-task-editor-button {
margin: 8px 0;
}
+
+.kanbn-task-editor-field-progress {
+ position: relative;
+}
+
+.kanbn-task-editor-field-progress .kanbn-task-progress {
+ bottom: 0;
+ height: 4px;
+ opacity: 1;
+}
+
+.kanbn-task-editor-column .kanbn-task-editor-button-delete .codicon {
+ margin-right: 0;
+}
+
+.kanbn-task-editor-field-tag {
+ position: relative;
+}
+
+.kanbn-task-editor-tag-highlight {
+ position: absolute;
+ bottom: 8px;
+ left: 0;
+ height: 4px;
+ width: 100%;
+}
+
+.kanbn-task-editor-row-comment {
+ padding-bottom: 1em;
+ border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid;
+ margin-bottom: 1em;
+}
+
+.kanbn-task-editor-field-comment-date {
+ padding: 16px 0;
+ text-align: right;
+ font-style: italic;
+ opacity: 0.8;
+}
+
+.kanbn-task-editor-field-comment-text .kanbn-task-editor-field-textarea {
+ min-height: 90px;
+}