Add relations, custom fields and card customisation
This commit is contained in:
@ -18,6 +18,7 @@ function App() {
|
||||
const [startedColumns, setStartedColumns] = useState([]);
|
||||
const [completedColumns, setCompletedColumns] = useState([]);
|
||||
const [columnSorting, setColumnSorting] = useState({});
|
||||
const [customFields, setCustomFields] = useState([]);
|
||||
const [dateFormat, setDateFormat] = useState('');
|
||||
const [task, setTask] = useState({});
|
||||
const [tasks, setTasks] = useState({});
|
||||
@ -48,6 +49,7 @@ function App() {
|
||||
setStartedColumns(event.data.startedColumns);
|
||||
setCompletedColumns(event.data.completedColumns);
|
||||
setColumnSorting(event.data.columnSorting);
|
||||
setCustomFields(event.data.customFields);
|
||||
setShowBurndownButton(event.data.showBurndownButton);
|
||||
setShowSprintButton(event.data.showSprintButton);
|
||||
|
||||
@ -101,6 +103,7 @@ function App() {
|
||||
startedColumns={startedColumns}
|
||||
completedColumns={completedColumns}
|
||||
columnSorting={columnSorting}
|
||||
customFields={customFields}
|
||||
dateFormat={dateFormat}
|
||||
showBurndownButton={showBurndownButton}
|
||||
showSprintButton={showSprintButton}
|
||||
|
||||
@ -140,6 +140,7 @@ const Board = ({
|
||||
startedColumns,
|
||||
completedColumns,
|
||||
columnSorting,
|
||||
customFields,
|
||||
dateFormat,
|
||||
showBurndownButton,
|
||||
showSprintButton,
|
||||
@ -153,6 +154,7 @@ const Board = ({
|
||||
startedColumns: string[],
|
||||
completedColumns: string[],
|
||||
columnSorting: { [columnName: string]: { field: string, order: 'ascending' | 'descending' }[] },
|
||||
customFields: { name: string, type: 'boolean' | 'date' | 'number' | 'string' }[],
|
||||
dateFormat: string,
|
||||
showBurndownButton: boolean,
|
||||
showSprintButton: boolean,
|
||||
@ -326,6 +328,8 @@ const Board = ({
|
||||
>
|
||||
{column.filter(task => filterTask(task, taskFilter)).map((task, position) => <TaskItem
|
||||
task={task}
|
||||
columnName={columnName}
|
||||
customFields={customFields}
|
||||
position={position}
|
||||
dateFormat={dateFormat}
|
||||
vscode={vscode}
|
||||
|
||||
203
src/TaskItem.tsx
203
src/TaskItem.tsx
@ -4,8 +4,10 @@ import formatDate from 'dateformat';
|
||||
import { paramCase } from '@basementuniverse/kanbn/src/utility';
|
||||
import VSCodeApi from "./VSCodeApi";
|
||||
|
||||
const TaskItem = ({ task, position, dateFormat, vscode }: {
|
||||
const TaskItem = ({ task, columnName, customFields, position, dateFormat, vscode }: {
|
||||
task: KanbnTask,
|
||||
columnName: string,
|
||||
customFields: { name: string, type: 'boolean' | 'date' | 'number' | 'string' }[],
|
||||
position: number,
|
||||
dateFormat: string,
|
||||
vscode: VSCodeApi
|
||||
@ -38,6 +40,9 @@ const TaskItem = ({ task, position, dateFormat, vscode }: {
|
||||
{...provided.dragHandleProps}
|
||||
className={[
|
||||
'kanbn-task',
|
||||
`kanbn-task-column-${paramCase(columnName)}`,
|
||||
checkOverdue(task) ? 'kanbn-task-overdue' : null,
|
||||
!!completedDate ? 'kanbn-task-completed' : null,
|
||||
snapshot.isDragging ? 'drag' : null
|
||||
].filter(i => i).join(' ')}
|
||||
style={{
|
||||
@ -45,10 +50,9 @@ const TaskItem = ({ task, position, dateFormat, vscode }: {
|
||||
...provided.draggableProps.style
|
||||
}}
|
||||
>
|
||||
<div className="kanbn-task-row">
|
||||
<div className="kanbn-task-data kanbn-task-data-name">
|
||||
<button
|
||||
type="button"
|
||||
className="kanbn-task-name"
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
command: 'kanbn.task',
|
||||
@ -61,76 +65,131 @@ const TaskItem = ({ task, position, dateFormat, vscode }: {
|
||||
{task.name}
|
||||
</button>
|
||||
</div>
|
||||
<div className="kanbn-task-row">
|
||||
{
|
||||
'tags' in task.metadata &&
|
||||
task.metadata.tags!.length > 0 &&
|
||||
<div className="kanbn-task-data kanbn-task-tags">
|
||||
{task.metadata.tags!.map(tag => {
|
||||
return (
|
||||
<span className={[
|
||||
'kanbn-task-tag',
|
||||
`kanbn-task-tag-${paramCase(tag)}`
|
||||
].join(' ')}>
|
||||
{tag}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
{
|
||||
'tags' in task.metadata &&
|
||||
task.metadata.tags!.length > 0 &&
|
||||
<div className="kanbn-task-data kanbn-task-data-tags">
|
||||
{task.metadata.tags!.map(tag => {
|
||||
return (
|
||||
<span className={[
|
||||
'kanbn-task-tag',
|
||||
`kanbn-task-tag-${paramCase(tag)}`
|
||||
].join(' ')}>
|
||||
{tag}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
customFields.map(customField => {
|
||||
if (customField.name in task.metadata) {
|
||||
return (
|
||||
<div className={[
|
||||
'kanbn-task-data kanbn-task-data-custom-field',
|
||||
`kanbn-task-data-${paramCase(customField.name)}`
|
||||
].join(' ')}>
|
||||
{
|
||||
customField.type === 'boolean'
|
||||
? (
|
||||
<>
|
||||
<i className={`codicon codicon-${task.metadata[customField.name]
|
||||
? 'pass-filled'
|
||||
: 'circle-large-outline'}`}></i>
|
||||
{customField.name}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i className="codicon codicon-json"></i>
|
||||
<span title={customField.name}>
|
||||
{task.metadata[customField.name]}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (<></>);
|
||||
})
|
||||
}
|
||||
{
|
||||
'assigned' in task.metadata &&
|
||||
!!task.metadata.assigned &&
|
||||
<div className="kanbn-task-data kanbn-task-data-assigned">
|
||||
<i className="codicon codicon-account"></i>{task.metadata.assigned}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
createdDate &&
|
||||
<div className="kanbn-task-data kanbn-task-data-created" title={`Created ${createdDate}`}>
|
||||
<i className="codicon codicon-clock"></i>{createdDate}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
updatedDate &&
|
||||
<div className="kanbn-task-data kanbn-task-data-updated" title={`Updated ${updatedDate}`}>
|
||||
<i className="codicon codicon-clock"></i>{updatedDate}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
startedDate &&
|
||||
<div className="kanbn-task-data kanbn-task-data-started" title={`Started ${startedDate}`}>
|
||||
<i className="codicon codicon-run"></i>{startedDate}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
dueDate &&
|
||||
<div className="kanbn-task-data kanbn-task-data-due" title={`Due ${dueDate}`}>
|
||||
<i className="codicon codicon-watch"></i>{dueDate}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
completedDate &&
|
||||
<div className="kanbn-task-data kanbn-task-data-completed" title={`Completed ${completedDate}`}>
|
||||
<i className="codicon codicon-check"></i>{completedDate}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
task.comments.length > 0 &&
|
||||
<div className="kanbn-task-data kanbn-task-data-comments">
|
||||
<i className="codicon codicon-comment"></i>{task.comments.length}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
task.subTasks.length > 0 &&
|
||||
<div className="kanbn-task-data kanbn-task-data-sub-tasks">
|
||||
<i className="codicon codicon-tasklist"></i>
|
||||
{task.subTasks.filter(subTask => subTask.completed).length} / {task.subTasks.length}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
task.workload !== undefined &&
|
||||
<div className="kanbn-task-data kanbn-task-data-workload">
|
||||
<i className="codicon codicon-run"></i>{task.workload}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
task.relations.length > 0 &&
|
||||
task.relations.map(relation => (
|
||||
<div className={[
|
||||
'kanbn-task-data kanbn-task-data-relation',
|
||||
relation.type ? `kanbn-task-data-relation-${relation.type}` : null,
|
||||
].join(' ')}>
|
||||
<i className="codicon codicon-link"></i>
|
||||
<span className="kanbn-task-data-label">
|
||||
{relation.type}
|
||||
</span> {relation.task}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="kanbn-task-row">
|
||||
{
|
||||
'assigned' in task.metadata &&
|
||||
!!task.metadata.assigned &&
|
||||
<div className="kanbn-task-data kanbn-task-assigned">
|
||||
<i className="codicon codicon-account"></i>{task.metadata.assigned}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
(createdDate || updatedDate) &&
|
||||
<div className="kanbn-task-data kanbn-task-date" title={[
|
||||
createdDate ? `Created ${createdDate}` : null,
|
||||
updatedDate ? `Updated ${updatedDate}` : null,
|
||||
startedDate ? `Started ${startedDate}` : null,
|
||||
dueDate ? `Due ${dueDate}` : null,
|
||||
completedDate ? `Completed ${completedDate}` : null
|
||||
].filter(i => i).join('\n')}>
|
||||
<i
|
||||
className={[
|
||||
'codicon codicon-clock',
|
||||
checkOverdue(task) ? 'kanbn-task-overdue' : null
|
||||
].filter(i => i).join(' ')}
|
||||
></i>{updatedDate || createdDate}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
task.comments.length > 0 &&
|
||||
<div className="kanbn-task-data kanbn-task-comments">
|
||||
<i className="codicon codicon-comment"></i>{task.comments.length}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
task.subTasks.length > 0 &&
|
||||
<div className="kanbn-task-data kanbn-task-sub-tasks">
|
||||
<i className="codicon codicon-tasklist"></i>
|
||||
{task.subTasks.filter(subTask => subTask.completed).length} / {task.subTasks.length}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
task.workload !== undefined &&
|
||||
<div className="kanbn-task-data kanbn-task-workload">
|
||||
<i className="codicon codicon-run"></i>{task.workload}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
task.workload !== undefined &&
|
||||
task.progress !== undefined &&
|
||||
<div className="kanbn-task-progress" style={{
|
||||
width: `${Math.min(1, Math.max(0, task.progress)) * 100}%`
|
||||
}}></div>
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{
|
||||
task.workload !== undefined &&
|
||||
task.progress !== undefined &&
|
||||
<div className="kanbn-task-progress" style={{
|
||||
width: `${Math.min(1, Math.max(0, task.progress)) * 100}%`
|
||||
}}></div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
||||
@ -220,7 +220,33 @@ body.vscode-dark .kanbn-task {
|
||||
border-color: var(--vscode-activityBar-foreground);
|
||||
}
|
||||
|
||||
.kanbn-task-name {
|
||||
.kanbn-task-data {
|
||||
display: inline-block;
|
||||
margin: 4px 8px 4px 0;
|
||||
min-width: 30%;
|
||||
opacity: 0.7;
|
||||
font-size: 0.9em;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.kanbn-task-data-label {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.kanbn-task-data-name {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.kanbn-task-data-tags {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.kanbn-task-data-relation {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.kanbn-task-data-name button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
@ -232,22 +258,13 @@ body.vscode-dark .kanbn-task {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.kanbn-task-name:focus, .kanbn-task-name:hover {
|
||||
.kanbn-task-data-name button:focus,
|
||||
.kanbn-task-data-name button:hover {
|
||||
border: none;
|
||||
outline: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.kanbn-task-data {
|
||||
display: inline-block;
|
||||
margin: 4px 8px 4px 0;
|
||||
min-width: 30%;
|
||||
opacity: 0.7;
|
||||
font-size: 0.9em;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.kanbn-task div .codicon {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
@ -291,7 +308,7 @@ body.vscode-dark .kanbn-task {
|
||||
background-color: #f42;
|
||||
}
|
||||
|
||||
.kanbn-task-overdue {
|
||||
.kanbn-task-overdue .kanbn-task-data-due {
|
||||
color: #f22 !important;
|
||||
}
|
||||
|
||||
@ -305,6 +322,30 @@ body.vscode-dark .kanbn-task {
|
||||
transition: width .5s ease-in-out;
|
||||
}
|
||||
|
||||
.kanbn-task-data-updated {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kanbn-task-data-started {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kanbn-task-data-due {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kanbn-task-data-completed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kanbn-task-data-comments {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.kanbn-task-data-relation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
Task editor styles
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
Reference in New Issue
Block a user