Initial kanban board
This commit is contained in:
146
src/App.tsx
Normal file
146
src/App.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
DndContext,
|
||||
DragOverlay,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import type { DragEndEvent, DragOverEvent, DragStartEvent } from '@dnd-kit/core';
|
||||
import { Column } from './components/Column';
|
||||
import { TaskCard } from './components/TaskCard';
|
||||
import type { Task, Column as ColumnType, ColumnId } from './types';
|
||||
import './App.css';
|
||||
|
||||
const defaultColumns: ColumnType[] = [
|
||||
{ id: 'backlog', title: '📋 Backlog', tasks: [] },
|
||||
{ id: 'todo', title: '📝 To Do', tasks: [] },
|
||||
{ id: 'doing', title: '🔨 Doing', tasks: [] },
|
||||
{ id: 'done', title: '✅ Done', tasks: [] },
|
||||
];
|
||||
|
||||
function App() {
|
||||
const [columns, setColumns] = useState<ColumnType[]>(() => {
|
||||
const saved = localStorage.getItem('kanban-data');
|
||||
return saved ? JSON.parse(saved) : defaultColumns;
|
||||
});
|
||||
const [activeTask, setActiveTask] = useState<Task | null>(null);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 5 } })
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('kanban-data', JSON.stringify(columns));
|
||||
}, [columns]);
|
||||
|
||||
const findTask = (id: string): { task: Task; columnId: string } | null => {
|
||||
for (const column of columns) {
|
||||
const task = column.tasks.find((t) => t.id === id);
|
||||
if (task) return { task, columnId: column.id };
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleDragStart = (event: DragStartEvent) => {
|
||||
const found = findTask(event.active.id as string);
|
||||
if (found) setActiveTask(found.task);
|
||||
};
|
||||
|
||||
const handleDragOver = (event: DragOverEvent) => {
|
||||
const { active, over } = event;
|
||||
if (!over) return;
|
||||
|
||||
const activeId = active.id as string;
|
||||
const overId = over.id as string;
|
||||
|
||||
const activeData = findTask(activeId);
|
||||
if (!activeData) return;
|
||||
|
||||
const overColumn = columns.find((c) => c.id === overId);
|
||||
const overTask = findTask(overId);
|
||||
|
||||
const targetColumnId = overColumn?.id || overTask?.columnId;
|
||||
if (!targetColumnId || targetColumnId === activeData.columnId) return;
|
||||
|
||||
setColumns((cols) => {
|
||||
const newCols = cols.map((c) => ({ ...c, tasks: [...c.tasks] }));
|
||||
const sourceCol = newCols.find((c) => c.id === activeData.columnId)!;
|
||||
const targetCol = newCols.find((c) => c.id === targetColumnId)!;
|
||||
|
||||
const taskIndex = sourceCol.tasks.findIndex((t) => t.id === activeId);
|
||||
const [task] = sourceCol.tasks.splice(taskIndex, 1);
|
||||
targetCol.tasks.push(task);
|
||||
|
||||
return newCols;
|
||||
});
|
||||
};
|
||||
|
||||
const handleDragEnd = (_event: DragEndEvent) => {
|
||||
setActiveTask(null);
|
||||
};
|
||||
|
||||
const addTask = (columnId: ColumnId, title: string) => {
|
||||
const newTask: Task = {
|
||||
id: crypto.randomUUID(),
|
||||
title,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
setColumns((cols) =>
|
||||
cols.map((c) =>
|
||||
c.id === columnId ? { ...c, tasks: [...c.tasks, newTask] } : c
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const deleteTask = (taskId: string) => {
|
||||
setColumns((cols) =>
|
||||
cols.map((c) => ({
|
||||
...c,
|
||||
tasks: c.tasks.filter((t) => t.id !== taskId),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
const updateTask = (taskId: string, updates: Partial<Task>) => {
|
||||
setColumns((cols) =>
|
||||
cols.map((c) => ({
|
||||
...c,
|
||||
tasks: c.tasks.map((t) =>
|
||||
t.id === taskId ? { ...t, ...updates } : t
|
||||
),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<header>
|
||||
<h1>🐾 Clawd's Kanban</h1>
|
||||
</header>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<div className="board">
|
||||
{columns.map((column) => (
|
||||
<Column
|
||||
key={column.id}
|
||||
column={column}
|
||||
onAddTask={(title) => addTask(column.id as ColumnId, title)}
|
||||
onDeleteTask={deleteTask}
|
||||
onUpdateTask={updateTask}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<DragOverlay>
|
||||
{activeTask && <TaskCard task={activeTask} isDragging />}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user