This project implements a Kanban Board system with core features such as drag-and-drop functionality, task editing, and customizable columns. It offers an intuitive interface for managing tasks and workflows efficiently, enabling seamless updates and organization of tasks across multiple columns.
A Kanban Board is a visual tool used to plan, track and manage a job. It is usually organized in columns and cards on a board.
Columns (Columns): Represents the stages of the workflow (e.g. “To Do”, “In Progress”, “Completed”).
Cards (Tasks): Represents the work to be done and usually each card refers to a task or piece of work.
Objective:
Areas of Use: It can be applied in many areas such as software development, project management, production processes and personal task management.
In general, these information maps are generated as we go. I began with a simple example for Kanban Board Development.
Note: In the future, you may adjust server aspects like connection with the server, optimistic update or local-first, react server components, or make stylistic enhancements.
Basic: A simple example of assigning jobs from one location to another using three columns.
Dnd: An example of how to shift jobs from one area to another using drag and drop
Add Task: An example of how to add a new task, update its content, and delete it.
Modify Columns: This example shows how to add, remove, or rename a new column.significant
Note: Because the project codes for the examples below will be available for download at LearnReactUI.dev, I will simply highlight and discuss the most significant sections of the code.
I aimed to make the first example as simple and straightforward as possible. There are three columns and three tasks. We shall indicate which column these tasks are in based on their state.
As you can see below, we indicate these tasks and their status.
tasks: [
{id: 1, title: 'Task 1', status: 'To Do'},
{id: 2, title: 'Task 2', status: 'In Progress'},
{id: 3, title: 'Task 3', status: 'Done'},
],
After that, we are making the infrastructure to keep these tasks in the Zustand Store and update their status.
const useKanbanStore = create((set) => ({
tasks: [
{ id: 1, title: "Task 1", status: "To Do" },
{ id: 2, title: "Task 2", status: "In Progress" },
{ id: 3, title: "Task 3", status: "Done" },
],
updateTaskStatus: (id, newStatus) =>
set((state) => ({
tasks: state.tasks.map((task) =>
task.id === id ? { ...task, status: newStatus } : task
),
})),
}));
Then we need to create these Task components. We write the KanbanTask component for these. Here, when the option of the task changes, we just need to update the Store (updateTaskStatus)
const KanbanTask = ({ task }) => {
const { updateTaskStatus } = useKanbanStore();
const handleChange = (newStatus) => {
updateTaskStatus(task.id, newStatus);
};
return (
<Card
style={{ marginBottom: "10px" }}
title={task.title}
extra={
<Select
defaultValue={task.status}
style={{ paddingLeft: 8, width: 120 }}
onChange={handleChange}
>
<Select.Option value="To Do">To Do</Select.Option>
<Select.Option value="In Progress">In Progress</Select.Option>
<Select.Option value="Done">Done</Select.Option>
</Select>
}
>
Task details go here.
</Card>
);
};
After that we write the column component. KanbanColumn. This basically holds the components and is responsible for rendering them.
const KanbanColumn = ({ title, tasks }) => {
return (
<Styled.KanbanColumn>
<Typography.Title level={4}>{title}</Typography.Title>
{tasks.map((task) => (
<KanbanTask key={task.id} task={task} />
))}
</Styled.KanbanColumn>
);
};
Finally, to write the part that knows KanbanBoard, that is, the part that renders the tasks by associating the relevant column.
const KanbanBoard = () => {
const { tasks } = useKanbanStore();
const columns = ["To Do", "In Progress", "Done"];
return (
<Row style={{ height: "100vh" }}>
{columns.map((column) => (
<Col
key={column}
span={8}
style={{
borderRight: "1px solid #ccc",
padding: "10px",
}}
>
<KanbanColumn
title={column}
tasks={tasks.filter((task) => task.status === column)}
/>
</Col>
))}
</Row>
);
};
The second example is Dnd (Drag and Drop), which allows moving the tasks on the Kanban Board from one column to another. So we want to simplify and increase usability.
The first thing we need to do is to surround the KanbanBoard component with DndProvider, this will allow all our internal components to use Drag and Drop capabilities. (For detailed information about the Provider Pattern, you can read the document at this link)
<DndProvider backend={HTML5Backend}>
<Styled.KanbanBoard>
<KanbanBoard />
</Styled.KanbanBoard>
</DndProvider>
Secondarily, we will make the columns able to detect drop. We add drop capability to KanbanColumn component with useDrop.
const KanbanColumn = ({ title, tasks }) => {
const { updateTaskStatus } = useKanbanStore();
const [, drop] = useDrop({
accept: "task",
drop: (item) => updateTaskStatus(item.id, title),
});
return (
<Styled.KanbanColumn ref={drop}>
<Typography.Title level={4}>{title}</Typography.Title>
{tasks.map((task) => (
<KanbanTask key={task.id} task={task} />
))}
</Styled.KanbanColumn>
);
};
Thirdly, we need to make Tasks portable. We add a draggable feature to the KanbanTask component with useDragging
const KanbanTask = ({ task }) => {
const [{ isDragging }, drag] = useDrag(() => ({
type: "task",
item: { id: task.id },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}));
return (
<Card
ref={drag}
style={{
marginBottom: "10px",
opacity: isDragging ? 0.5 : 1,
cursor: "move",
}}
>
{task.title}
</Card>
);
};
We can now manage the status of tasks by drag and drop between columns.
In the third example, I want to improve our application a bit more and add the ability to add, delete and update new tasks.
The first thing we will do is add new functions related to Zustand Store Task, addTask, deleteTask, updateTask
// Add a new task
addTask: (title, status) =>
set((state) => ({
tasks: [...state.tasks, {id: Date.now().toString(), title, status}],
})),
// Delete a task by ID
deleteTask: (id) =>
set((state) => ({
tasks: state.tasks.filter((task) => task.id !== id),
})),
// Update task title by ID
updateTask: (id, newTitle) =>
set((state) => ({
tasks: state.tasks.map((task) => (task.id === id ? {...task, title: newTitle} : task)),
})),
Then we call these Zustand store functions in the background via the buttons and the Modal screens opened from them.
In our 4th and last example, we want to update, delete and add new columns
Here, the first thing we need to do is update the ZustandStore, addColumn, modifyColumn and deleteColumn functions.
// Add a new column
addColumn: (columnName) =>
set((state) => ({
columns: [...state.columns, columnName],
})),
// Rename a column
modifyColumn: (oldName, newName) =>
set((state) => ({
columns: state.columns.map((col) => (col === oldName ? newName : col)),
tasks: state.tasks.map((task) => (task.status === oldName ? {...task, status: newName} : task)),
})),
// Delete a column and its tasks
deleteColumn: (columnName) =>
set((state) => ({
columns: state.columns.filter((col) => col !== columnName),
tasks: state.tasks.filter((task) => task.status !== columnName),
})),
It remains to make button and modal screens that will call the functions in this store and trigger these functions from this section.