diff --git a/src/pages/taskmanager/components/Column.tsx b/src/pages/taskmanager/components/Column.tsx index 043321f..3396b34 100644 --- a/src/pages/taskmanager/components/Column.tsx +++ b/src/pages/taskmanager/components/Column.tsx @@ -1,5 +1,5 @@ import { Add, AddCircle, CloseCircle, More } from "iconsax-react"; -import { useState, type FC } from "react"; +import { useRef, useState, type FC } from "react"; import type { Column as ColumnType, Task as TaskType } from "../types"; import Task from "./Task"; @@ -7,9 +7,13 @@ type Props = { column: ColumnType; tasks: TaskType[]; draggedTaskId: string | null; + draggedColumnId: string | null; onDragStart: (taskId: string) => void; onDragEnd: () => void; onDrop: (columnId: string, index: number) => void; + onColumnDragStart: (columnId: string) => void; + onColumnDragEnd: () => void; + onColumnDrop: (overColumnId: string) => void; onTaskClick?: (task: TaskType) => void; }; @@ -17,45 +21,132 @@ const Column: FC = ({ column, tasks, draggedTaskId, + draggedColumnId, onDragStart, onDragEnd, onDrop, + onColumnDragStart, + onColumnDragEnd, + onColumnDrop, onTaskClick, }) => { const [isAdding, setIsAdding] = useState(false); const [isDragOver, setIsDragOver] = useState(false); + const [isColumnDragOver, setIsColumnDragOver] = useState(false); + const columnRef = useRef(null); + const dragImageRef = useRef(null); + const isDraggingColumn = draggedColumnId === column.id; + + const handleColumnDragStart = (e: React.DragEvent) => { + const columnEl = columnRef.current; + if (columnEl) { + const clone = columnEl.cloneNode(true) as HTMLElement; + clone.style.position = "fixed"; + clone.style.top = "-10000px"; + clone.style.left = "-10000px"; + clone.style.width = `${columnEl.offsetWidth}px`; + clone.style.opacity = "0.95"; + clone.style.boxShadow = "0 8px 24px rgba(0, 0, 0, 0.15)"; + clone.style.pointerEvents = "none"; + clone.style.zIndex = "9999"; + document.body.appendChild(clone); + dragImageRef.current = clone; + + const rect = columnEl.getBoundingClientRect(); + e.dataTransfer.setDragImage( + clone, + e.clientX - rect.left, + e.clientY - rect.top, + ); + e.dataTransfer.effectAllowed = "move"; + } + + onColumnDragStart(column.id); + }; + + const handleColumnDragEnd = () => { + if (dragImageRef.current) { + document.body.removeChild(dragImageRef.current); + dragImageRef.current = null; + } + onColumnDragEnd(); + }; const handleDragOver = (e: React.DragEvent) => { + if (draggedColumnId) return; e.preventDefault(); e.dataTransfer.dropEffect = "move"; setIsDragOver(true); }; const handleDragLeave = (e: React.DragEvent) => { + if (draggedColumnId) return; if (!e.currentTarget.contains(e.relatedTarget as Node)) { setIsDragOver(false); } }; const handleDrop = (e: React.DragEvent, index: number) => { + if (draggedColumnId) return; e.preventDefault(); e.stopPropagation(); setIsDragOver(false); onDrop(column.id, index); }; + const handleColumnDragOver = (e: React.DragEvent) => { + if (!draggedColumnId || draggedColumnId === column.id) return; + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + setIsColumnDragOver(true); + }; + + const handleColumnDragLeave = (e: React.DragEvent) => { + if (!draggedColumnId) return; + if (!e.currentTarget.contains(e.relatedTarget as Node)) { + setIsColumnDragOver(false); + } + }; + + const handleColumnDrop = (e: React.DragEvent) => { + if (!draggedColumnId || draggedColumnId === column.id) return; + e.preventDefault(); + e.stopPropagation(); + setIsColumnDragOver(false); + onColumnDrop(column.id); + }; + return (
handleDrop(e, tasks.length)} + onDragOver={(e) => { + handleColumnDragOver(e); + handleDragOver(e); + }} + onDragLeave={(e) => { + handleColumnDragLeave(e); + handleDragLeave(e); + }} + onDrop={(e) => { + handleColumnDrop(e); + if (!draggedColumnId) { + handleDrop(e, tasks.length); + } + }} > -
+
{column.title}
- +
@@ -63,6 +154,7 @@ const Column: FC = ({
{ + if (draggedColumnId) return; e.preventDefault(); e.stopPropagation(); }} diff --git a/src/pages/taskmanager/utils/reorderColumns.ts b/src/pages/taskmanager/utils/reorderColumns.ts new file mode 100644 index 0000000..4306bbb --- /dev/null +++ b/src/pages/taskmanager/utils/reorderColumns.ts @@ -0,0 +1,19 @@ +import type { Column } from "../types"; + +export const reorderColumns = ( + columns: Column[], + activeId: string, + overId: string, +): Column[] => { + const oldIndex = columns.findIndex((column) => column.id === activeId); + const newIndex = columns.findIndex((column) => column.id === overId); + + if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) { + return columns; + } + + const result = [...columns]; + const [removed] = result.splice(oldIndex, 1); + result.splice(newIndex, 0, removed); + return result; +}; diff --git a/src/pages/taskmanager/workspace.tsx b/src/pages/taskmanager/workspace.tsx index a6c01b5..9c5e905 100644 --- a/src/pages/taskmanager/workspace.tsx +++ b/src/pages/taskmanager/workspace.tsx @@ -5,12 +5,16 @@ import HeaderWorkspace from "./components/HeaderWorkspace"; import TaskDetailModal from "./components/task-detail/TaskDetailModal"; import tasksData from "./data/tasks.json"; import type { Task, WorkspaceData } from "./types"; +import { reorderColumns } from "./utils/reorderColumns"; import { reorderTasks } from "./utils/reorderTasks"; const Workspace: FC = () => { - const { columns } = tasksData as WorkspaceData; + const [columns, setColumns] = useState( + (tasksData as WorkspaceData).columns, + ); const [tasks, setTasks] = useState(tasksData.tasks as Task[]); const [draggedTaskId, setDraggedTaskId] = useState(null); + const [draggedColumnId, setDraggedColumnId] = useState(null); const [selectedTask, setSelectedTask] = useState(null); const tasksByColumn = useMemo(() => { @@ -36,6 +40,20 @@ const Workspace: FC = () => { setDraggedTaskId(null); }; + const handleColumnDragStart = (columnId: string) => { + setDraggedColumnId(columnId); + }; + + const handleColumnDragEnd = () => { + setDraggedColumnId(null); + }; + + const handleColumnDrop = (overColumnId: string) => { + if (!draggedColumnId || draggedColumnId === overColumnId) return; + setColumns((prev) => reorderColumns(prev, draggedColumnId, overColumnId)); + setDraggedColumnId(null); + }; + const selectedTaskColumn = selectedTask ? columns.find((column) => column.id === selectedTask.columnId) : null; @@ -51,9 +69,13 @@ const Workspace: FC = () => { column={column} tasks={tasksByColumn[column.id] ?? []} draggedTaskId={draggedTaskId} + draggedColumnId={draggedColumnId} onDragStart={handleDragStart} onDragEnd={handleDragEnd} onDrop={handleDrop} + onColumnDragStart={handleColumnDragStart} + onColumnDragEnd={handleColumnDragEnd} + onColumnDrop={handleColumnDrop} onTaskClick={setSelectedTask} /> ))}