"use client"; import { useEffect, useState } from "react"; import { Task, getTasks, updateTask, createTask } from "@/lib/api"; import TaskModal from "@/components/TaskModal"; const COLUMNS = [ { key: "draft", label: "Backlog", color: "#737373" }, { key: "ready", label: "TODO", color: "#3b82f6" }, { key: "in_progress", label: "In Progress", color: "#f59e0b" }, { key: "review", label: "Review", color: "#a855f7" }, { key: "completed", label: "Done", color: "#22c55e" }, ]; const PRIORITY_COLORS: Record = { critical: "#ef4444", high: "#f59e0b", medium: "#3b82f6", low: "#737373", }; interface Props { projectId: string; } export default function KanbanBoard({ projectId }: Props) { const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); const [newTaskTitle, setNewTaskTitle] = useState(""); const [addingTo, setAddingTo] = useState(null); const [draggedTask, setDraggedTask] = useState(null); const [activeColumn, setActiveColumn] = useState(null); const [selectedTask, setSelectedTask] = useState(null); const loadTasks = async () => { try { const data = await getTasks(projectId); setTasks(data); } finally { setLoading(false); } }; useEffect(() => { loadTasks(); }, [projectId]); const handleDrop = async (status: string) => { if (!draggedTask) return; const task = tasks.find((t) => t.id === draggedTask); if (!task || task.status === status) return; setTasks((prev) => prev.map((t) => (t.id === draggedTask ? { ...t, status } : t))); setDraggedTask(null); try { await updateTask(draggedTask, { status }); } catch { loadTasks(); } }; const handleMoveTask = async (taskId: string, newStatus: string) => { setTasks((prev) => prev.map((t) => (t.id === taskId ? { ...t, status: newStatus } : t))); try { await updateTask(taskId, { status: newStatus }); } catch { loadTasks(); } }; const handleAddTask = async (status: string) => { if (!newTaskTitle.trim()) return; try { const task = await createTask({ project_id: projectId, title: newTaskTitle.trim(), status, }); setTasks((prev) => [...prev, task]); setNewTaskTitle(""); setAddingTo(null); } catch (e) { console.error(e); } }; if (loading) { return
Загрузка...
; } // Mobile: column selector + vertical list // Desktop: horizontal kanban return (
{/* Mobile column tabs */}
{COLUMNS.map((col) => { const count = tasks.filter((t) => t.status === col.key).length; return ( ); })}
{/* Mobile: single column view */}
{(() => { const col = COLUMNS.find((c) => c.key === (activeColumn || "draft"))!; const colTasks = tasks.filter((t) => t.status === col.key); const colIndex = COLUMNS.findIndex((c) => c.key === col.key); return (
{colTasks.map((task) => (
setSelectedTask(task)} >
{task.key}
{task.title}
{/* Move buttons */}
{colIndex > 0 && ( )} {colIndex < COLUMNS.length - 1 && ( )}
))} {addingTo === col.key ? ( setNewTaskTitle(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") handleAddTask(col.key); if (e.key === "Escape") setAddingTo(null); }} onBlur={() => !newTaskTitle.trim() && setAddingTo(null)} /> ) : ( )}
); })()}
{/* Desktop: horizontal kanban */}
{COLUMNS.map((col) => { const colTasks = tasks.filter((t) => t.status === col.key); return (
e.preventDefault()} onDrop={() => handleDrop(col.key)} >
{col.label} {colTasks.length}
{colTasks.map((task) => (
setDraggedTask(task.id)} onClick={() => setSelectedTask(task)} className="bg-[var(--card)] border border-[var(--border)] rounded-lg p-3 cursor-grab hover:border-[var(--accent)] transition-colors active:cursor-grabbing" >
{task.key}
{task.title}
))} {addingTo === col.key ? ( setNewTaskTitle(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") handleAddTask(col.key); if (e.key === "Escape") setAddingTo(null); }} onBlur={() => !newTaskTitle.trim() && setAddingTo(null)} /> ) : ( )}
); })}
{/* Task modal */} {selectedTask && ( setSelectedTask(null)} onUpdated={(updated) => { setTasks((prev) => prev.map((t) => (t.id === updated.id ? updated : t))); setSelectedTask(updated); }} onDeleted={(id) => { setTasks((prev) => prev.filter((t) => t.id !== id)); setSelectedTask(null); }} /> )}
); }