import { useEffect, useMemo, useState } from "react"; const STORAGE_KEY = "kanban.tasks.v1"; const COLUMNS = [ { id: "todo", title: "To Do" }, { id: "in-progress", title: "In Progress" }, { id: "done", title: "Done" } ]; function loadTasks() { try { const stored = localStorage.getItem(STORAGE_KEY); return stored ? JSON.parse(stored) : []; } catch { return []; } } function createTask(title, description) { return { id: crypto.randomUUID(), title: title.trim(), description: description.trim(), status: "todo", createdAt: new Date().toISOString() }; } export default function App() { const [tasks, setTasks] = useState(loadTasks); const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); useEffect(() => { localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks)); }, [tasks]); const groupedTasks = useMemo(() => { return COLUMNS.reduce((acc, column) => { acc[column.id] = tasks.filter((task) => task.status === column.id); return acc; }, {}); }, [tasks]); const addTask = (event) => { event.preventDefault(); if (!title.trim()) { return; } setTasks((current) => [createTask(title, description), ...current]); setTitle(""); setDescription(""); }; const updateTaskStatus = (taskId, direction) => { setTasks((current) => current.map((task) => { if (task.id !== taskId) { return task; } const currentIndex = COLUMNS.findIndex((column) => column.id === task.status); const nextIndex = currentIndex + direction; if (nextIndex < 0 || nextIndex >= COLUMNS.length) { return task; } return { ...task, status: COLUMNS[nextIndex].id }; }) ); }; const removeTask = (taskId) => { setTasks((current) => current.filter((task) => task.id !== taskId)); }; return (

Kanban Task Manager

All data stays in your browser via localStorage.

setTitle(event.target.value)} aria-label="Task title" />