Add filter field, fix validation

This commit is contained in:
Gordon 2021-04-07 00:07:35 +01:00
parent 7ea93c6290
commit 63bf065331
6 changed files with 195 additions and 116 deletions

View File

@ -137,21 +137,29 @@ export default class KanbnTaskPanel {
// Create a task // Create a task
case 'kanbn.create': case 'kanbn.create':
// TODO create task // TODO convert dates
vscode.window.showInformationMessage('create task'); // await this._kanbn.createTask(message.taskData, message.taskData.column);
vscode.window.showInformationMessage(`Created task ${message.taskData.name}.`);
return; return;
// Update a task // Update a task
case 'kanbn.update': case 'kanbn.update':
// TODO update task // TODO convert dates
vscode.window.showInformationMessage('update task'); // await this._kanbn.updateTask(message.taskData.id, message.taskData, message.taskData.column);
vscode.window.showInformationMessage(`Updated task ${message.taskData.name}.`);
return; return;
// Delete a task // Delete a task
case 'kanbn.delete': case 'kanbn.delete':
// TODO delete task vscode.window.showInformationMessage(`Delete task ${message.taskData.name}?`, 'Yes', 'No').then(
// TODO add yes/no confirmation buttons to information message, then delete task and close task panel async value => {
vscode.window.showInformationMessage('delete task'); if (value === 'Yes') {
// await this._kanbn.deleteTask(message.taskId, true);
vscode.window.showInformationMessage(`Deleted task ${message.taskData.name}.`);
// TODO close panel, will need to generate uuid for each panel
}
}
);
return; return;
} }
}, null, this._disposables); }, null, this._disposables);

View File

@ -1,4 +1,3 @@
import Header from './Header';
import Board from './Board'; import Board from './Board';
import TaskEditor from './TaskEditor'; import TaskEditor from './TaskEditor';
import React, { useState } from "react"; import React, { useState } from "react";
@ -53,19 +52,15 @@ function App() {
<React.Fragment> <React.Fragment>
{ {
type === 'index' && type === 'index' &&
<React.Fragment> <Board
<Header name={name}
name={name} description={description}
description={description} columns={columns}
/> startedColumns={startedColumns}
<Board completedColumns={completedColumns}
columns={columns} dateFormat={dateFormat}
startedColumns={startedColumns} vscode={vscode}
completedColumns={completedColumns} />
dateFormat={dateFormat}
vscode={vscode}
/>
</React.Fragment>
} }
{ {
type === 'task' && type === 'task' &&

View File

@ -55,7 +55,9 @@ const onDragEnd = (result, columns, setColumns, vscode: VSCodeApi) => {
}); });
}; };
const Board = ({ columns, startedColumns, completedColumns, dateFormat, vscode }: { const Board = ({ name, description, columns, startedColumns, completedColumns, dateFormat, vscode }: {
name: string,
description: string,
columns: Record<string, KanbnTask[]>, columns: Record<string, KanbnTask[]>,
startedColumns: string[], startedColumns: string[],
completedColumns: string[], completedColumns: string[],
@ -63,74 +65,102 @@ const Board = ({ columns, startedColumns, completedColumns, dateFormat, vscode }
vscode: VSCodeApi vscode: VSCodeApi
}) => { }) => {
const [, setColumns] = useState(columns); const [, setColumns] = useState(columns);
const filterTasks = () => {
console.log();
};
return ( return (
<div className="kanbn-board"> <React.Fragment>
<DragDropContext <div className="kanbn-header">
onDragEnd={result => onDragEnd(result, columns, setColumns, vscode)} <h1 className="kanbn-header-name">
> <p>{name}</p>
{Object.entries(columns).map(([columnName, column]) => { <div className="kanbn-filter">
return ( <input
<div className="kanbn-filter-input"
className={[ placeholder="Filter tasks"
'kanbn-column', />
`kanbn-column-${paramCase(columnName)}` <button
].join(' ')} type="button"
key={columnName} className="kanbn-filter-button"
onClick={filterTasks}
> >
<h2 className="kanbn-column-name"> <i className="codicon codicon-filter"></i>
{ </button>
startedColumns.indexOf(columnName) > -1 && </div>
<i className="codicon codicon-chevron-right"></i> </h1>
} <p className="kanbn-header-description">
{ {description}
completedColumns.indexOf(columnName) > -1 && </p>
<i className="codicon codicon-check"></i> </div>
} <div className="kanbn-board">
{columnName} <DragDropContext
<span className="kanbn-column-count">{column.length || ''}</span> onDragEnd={result => onDragEnd(result, columns, setColumns, vscode)}
<button >
type="button" {Object.entries(columns).map(([columnName, column]) => {
className="kanbn-create-task-button" return (
title={`Create task in ${columnName}`} <div
onClick={() => { className={[
vscode.postMessage({ 'kanbn-column',
command: 'kanbn.addTask', `kanbn-column-${paramCase(columnName)}`
columnName ].join(' ')}
}) key={columnName}
}} >
> <h2 className="kanbn-column-name">
<i className="codicon codicon-add"></i> {
</button> startedColumns.indexOf(columnName) > -1 &&
</h2> <i className="codicon codicon-chevron-right"></i>
<div> }
<Droppable droppableId={columnName} key={columnName}> {
{(provided, snapshot) => { completedColumns.indexOf(columnName) > -1 &&
return ( <i className="codicon codicon-check"></i>
<div }
{...provided.droppableProps} {columnName}
ref={provided.innerRef} <span className="kanbn-column-count">{column.length || ''}</span>
className={[ <button
'kanbn-column-task-list', type="button"
snapshot.isDraggingOver ? 'drag-over' : null className="kanbn-create-task-button"
].filter(i => i).join(' ')} title={`Create task in ${columnName}`}
> onClick={() => {
{column.map((task, position) => <TaskItem vscode.postMessage({
task={task} command: 'kanbn.addTask',
position={position} columnName
dateFormat={dateFormat} })
vscode={vscode} }}
/>)} >
{provided.placeholder} <i className="codicon codicon-add"></i>
</div> </button>
); </h2>
}} <div>
</Droppable> <Droppable droppableId={columnName} key={columnName}>
{(provided, snapshot) => {
return (
<div
{...provided.droppableProps}
ref={provided.innerRef}
className={[
'kanbn-column-task-list',
snapshot.isDraggingOver ? 'drag-over' : null
].filter(i => i).join(' ')}
>
{column.map((task, position) => <TaskItem
task={task}
position={position}
dateFormat={dateFormat}
vscode={vscode}
/>)}
{provided.placeholder}
</div>
);
}}
</Droppable>
</div>
</div> </div>
</div> );
); })}
})} </DragDropContext>
</DragDropContext> </div>
</div> </React.Fragment>
); );
}; };

View File

@ -1,14 +0,0 @@
import React from "react";
const Header = ({ name, description }: { name: string, description: string }) => {
return (
<div className="kanbn-header">
<h1 className="kanbn-header-name">{name}</h1>
<p className="kanbn-header-description">
{description}
</p>
</div>
);
}
export default Header;

View File

@ -46,11 +46,27 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
}); });
}; };
// Called when the form is submitted
const handleSubmit = values => {
if (editing) {
vscode.postMessage({
command: 'kanbn.update',
taskData: values
});
} else {
vscode.postMessage({
command: 'kanbn.create',
taskData: values
});
}
};
// Called when the delete task button is clicked // Called when the delete task button is clicked
const handleRemoveTask = () => { const handleRemoveTask = values => {
vscode.postMessage({ vscode.postMessage({
command: 'kanbn.delete', command: 'kanbn.delete',
taskId: task!.id taskId: task!.id,
taskData: values
}); });
}; };
@ -94,7 +110,8 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
subTasks: task ? task.subTasks : [], subTasks: task ? task.subTasks : [],
comments: task ? task.comments : [] comments: task ? task.comments : []
}} }}
validate={(values: KanbnTaskValidationInput): KanbnTaskValidationOutput => { validate={(values: KanbnTaskValidationInput): KanbnTaskValidationOutput|{} => {
let hasErrors = false;
const errors: KanbnTaskValidationOutput = { const errors: KanbnTaskValidationOutput = {
name: '', name: '',
metadata: { metadata: {
@ -107,17 +124,20 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
// Task name cannot be empty // Task name cannot be empty
if (!values.name) { if (!values.name) {
errors.name = 'Task name is required.'; errors.name = 'Task name is required.';
hasErrors = true;
} }
// Check if the id is already in use // Check if the id is already in use
if (values.id in tasks && tasks[values.id].uuid !== values.uuid) { if (values.id in tasks && tasks[values.id].uuid !== values.uuid) {
errors.name = 'There is already a task with the same name or id.'; errors.name = 'There is already a task with the same name or id.';
hasErrors = true;
} }
// Tag names cannot be empty // Tag names cannot be empty
for (let i = 0; i < values.metadata.tags.length; i++) { for (let i = 0; i < values.metadata.tags.length; i++) {
if (!values.metadata.tags[i]) { if (!values.metadata.tags[i]) {
errors.metadata.tags[i] = 'Tag cannot be empty.'; errors.metadata.tags[i] = 'Tag cannot be empty.';
hasErrors = true;
} }
} }
@ -127,6 +147,7 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
errors.subTasks[i] = { errors.subTasks[i] = {
text: 'Sub-task text cannot be empty.' text: 'Sub-task text cannot be empty.'
}; };
hasErrors = true;
} }
} }
@ -136,22 +157,14 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
errors.comments[i] = { errors.comments[i] = {
text: 'Comment text cannot be empty.' text: 'Comment text cannot be empty.'
}; };
hasErrors = true;
} }
} }
return errors; return hasErrors ? errors : {};
}} }}
onSubmit={(values, { setSubmitting }) => { onSubmit={(values, { setSubmitting }) => {
if (editing) { handleSubmit(values);
vscode.postMessage({
command: 'kanbn.update'
});
} else {
vscode.postMessage({
command: 'kanbn.create'
});
}
console.log(values);
setSubmitting(false); setSubmitting(false);
}} }}
> >
@ -385,7 +398,9 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
type="button" type="button"
className="kanbn-task-editor-button kanbn-task-editor-button-delete" className="kanbn-task-editor-button kanbn-task-editor-button-delete"
title="Delete task" title="Delete task"
onClick={handleRemoveTask} onClick={() => {
handleRemoveTask(values);
}}
> >
<i className="codicon codicon-trash"></i>Delete <i className="codicon codicon-trash"></i>Delete
</button>} </button>}

View File

@ -12,6 +12,51 @@ body {
margin-top: 0; margin-top: 0;
padding-bottom: 0.5em; padding-bottom: 0.5em;
border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid; border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid;
display: flex;
}
.kanbn-header-name p {
display: inline-block;
flex: 1;
margin: 0;
padding: 4px 0;
}
.kanbn-filter {
flex: 1;
text-align: right;
}
.kanbn-filter-input {
box-sizing: border-box;
width: 80%;
padding: 8px;
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px transparent solid;
}
.kanbn-filter-input:hover, .kanbn-filter-input:focus {
border-color: var(--vscode-input-border);
}
.kanbn-filter-button {
outline: none;
border: none;
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
padding: 9px 1em;
margin-left: 8px;
}
.kanbn-filter-button .codicon {
font-size: 11px !important;
position: relative;
top: 1px;
}
.kanbn-filter-button:hover, .kanbn-filter-button:focus {
background-color: var(--vscode-button-hoverBackground);
} }
.kanbn-board { .kanbn-board {