diff --git a/src/pages/taskmanager/components/Column.tsx b/src/pages/taskmanager/components/Column.tsx index 2ac33f9..ed61a4c 100644 --- a/src/pages/taskmanager/components/Column.tsx +++ b/src/pages/taskmanager/components/Column.tsx @@ -1,3 +1,4 @@ +import { Draggable, Droppable } from "@hello-pangea/dnd"; import { Add, AddCircle, CloseCircle } from "iconsax-react"; import { useMemo, useRef, useState, type FC } from "react"; import { useTranslation } from "react-i18next"; @@ -15,11 +16,7 @@ import Task from "./Task"; type Props = { column: ColumnType; projectId: string; - draggedTaskId: string | null; draggedColumnId: string | null; - onDragStart: (task: TaskType) => void; - onDragEnd: () => void; - onDrop: (columnId: string, index: number) => void; onColumnDragStart: (columnId: string) => void; onColumnDragEnd: () => void; onColumnDrop: (overColumnId: string) => void; @@ -30,11 +27,7 @@ type Props = { const Column: FC = ({ column, projectId, - draggedTaskId, draggedColumnId, - onDragStart, - onDragEnd, - onDrop, onColumnDragStart, onColumnDragEnd, onColumnDrop, @@ -63,7 +56,6 @@ const Column: FC = ({ const [isAdding, setIsAdding] = useState(false); const [title, setTitle] = useState(""); const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false); - const [isDragOver, setIsDragOver] = useState(false); const [isColumnDragOver, setIsColumnDragOver] = useState(false); const columnRef = useRef(null); const dragImageRef = useRef(null); @@ -94,11 +86,7 @@ const Column: FC = ({ dragImageRef.current = clone; const rect = columnEl.getBoundingClientRect(); - e.dataTransfer.setDragImage( - clone, - e.clientX - rect.left, - e.clientY - rect.top, - ); + e.dataTransfer.setDragImage(clone, e.clientX - rect.left, e.clientY - rect.top); e.dataTransfer.effectAllowed = "move"; } @@ -113,28 +101,6 @@ const Column: FC = ({ 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(); @@ -204,25 +170,12 @@ const Column: FC = ({ return (
{ - handleColumnDragOver(e); - handleDragOver(e); - }} - onDragLeave={(e) => { - handleColumnDragLeave(e); - handleDragLeave(e); - }} - onDrop={(e) => { - handleColumnDrop(e); - if (!draggedColumnId) { - handleDrop(e, tasks.length); - } - }} + className={`bg-[#F0F3F7] rounded-xl p-3 sm:p-4 xl:p-6 w-[272px] sm:w-[288px] xl:w-[310px] shrink-0 flex flex-col h-full min-h-0 transition-colors ${ + isColumnDragOver ? "ring-2 ring-black/30" : "" + } ${isDraggingColumn ? "opacity-50" : ""}`} + onDragOver={handleColumnDragOver} + onDragLeave={handleColumnDragLeave} + onDrop={handleColumnDrop} >
= ({ label={t("taskmanager.delete_column_confirm")} /> -
- {isTasksPending - ? null - : tasks.map((task, index) => ( -
{ - if (draggedColumnId) return; - e.preventDefault(); - e.stopPropagation(); - }} - onDrop={(e) => handleDrop(e, index)} - > - -
- ))} - {isFetchingNextPage ? ( -
{t("loading")}
- ) : null} -
+ + {(provided, snapshot) => ( +
+ {isTasksPending + ? null + : tasks.map((task, index) => ( + + {(dragProvided, dragSnapshot) => ( +
+ +
+ )} +
+ ))} + {provided.placeholder} + {isFetchingNextPage ? ( +
{t("loading")}
+ ) : null} +
+ )} +
{isAdding ? (
diff --git a/src/pages/taskmanager/components/Task.tsx b/src/pages/taskmanager/components/Task.tsx index 3d85189..1aca8c9 100644 --- a/src/pages/taskmanager/components/Task.tsx +++ b/src/pages/taskmanager/components/Task.tsx @@ -1,39 +1,35 @@ import { AttachCircle, Calendar, TickSquare } from "iconsax-react"; -import { useRef, type FC } from "react"; +import { useEffect, useRef, type FC } from "react"; import type { Task as TaskType } from "../types"; type Props = { task: TaskType; isDragging?: boolean; - onDragStart: (task: TaskType) => void; - onDragEnd: () => void; onClick?: (task: TaskType) => void; }; -const Task: FC = ({ task, isDragging, onDragStart, onDragEnd, onClick }) => { - const hasDraggedRef = useRef(false); +const Task: FC = ({ task, isDragging, onClick }) => { + const suppressClickRef = useRef(false); + + useEffect(() => { + if (isDragging) { + suppressClickRef.current = true; + } + }, [isDragging]); const handleClick = () => { - if (hasDraggedRef.current) return; + if (suppressClickRef.current) { + suppressClickRef.current = false; + return; + } onClick?.(task); }; return (
{ - hasDraggedRef.current = true; - onDragStart(task); - }} - onDragEnd={() => { - onDragEnd(); - requestAnimationFrame(() => { - hasDraggedRef.current = false; - }); - }} onClick={handleClick} - className={`bg-white rounded-lg p-2 cursor-grab active:cursor-grabbing transition-opacity ${ - isDragging ? "opacity-40" : "" + className={`bg-white rounded-lg p-2 cursor-grab active:cursor-grabbing transition-opacity touch-manipulation ${ + isDragging ? "opacity-40 shadow-lg" : "" }`} > {task.color ?
: null} diff --git a/src/pages/taskmanager/workspace.tsx b/src/pages/taskmanager/workspace.tsx index afad71f..c719b8d 100644 --- a/src/pages/taskmanager/workspace.tsx +++ b/src/pages/taskmanager/workspace.tsx @@ -1,3 +1,4 @@ +import { DragDropContext, type DragStart, type DropResult } from "@hello-pangea/dnd"; import { useQueryClient } from "@tanstack/react-query"; import { useEffect, useState, type FC } from "react"; import { useParams } from "react-router-dom"; @@ -12,6 +13,8 @@ import { useGetProject } from "./project/hooks/useProjectData"; import { useUpdateTaskPhase } from "./task-phase/hooks/useTaskPhaseData"; import type { TaskPhaseItemType } from "./task-phase/types/TaskPhaseTypes"; import { useChangeTaskPhase, useChangeTaskPriority } from "./task/hooks/useTaskData"; +import type { TaskItemType } from "./task/types/TaskTypes"; +import { mapTaskItemToTask } from "./task/utils/mapTaskItemToTask"; import type { Column as ColumnType, Task } from "./types"; import { getPriorityDestinationTaskId } from "./utils/getPriorityDestinationTaskId"; import { reorderColumns } from "./utils/reorderColumns"; @@ -45,22 +48,21 @@ const Workspace: FC = () => { ); }, [project?.data?.taskPhases]); - const handleDragStart = (task: Task) => { - setDraggedTask(task); + const handleTaskDragStart = (start: DragStart) => { + if (start.type !== "TASK") return; + + const sourceColumnId = start.source.droppableId; + const previousData = queryClient.getQueryData(["tasks-by-task-phase", sourceColumnId]); + const items = getTaskPhaseItems(previousData); + const item = items.find((task) => task.id === start.draggableId); + if (!item) return; + + setDraggedTask(mapTaskItemToTask(item as TaskItemType, sourceColumnId, start.source.index)); }; - const handleDragEnd = () => { - setDraggedTask(null); - }; - - const handleDrop = (columnId: string, index: number) => { - if (!draggedTask) return; - - const sourceColumnId = draggedTask.columnId; + const handleDrop = (columnId: string, index: number, activeTask: Task) => { + const sourceColumnId = activeTask.columnId; const columnChanged = sourceColumnId !== columnId; - const activeTask = draggedTask; - - setDraggedTask(null); if (!columnChanged) { const previousData = queryClient.getQueryData(["tasks-by-task-phase", columnId]); @@ -69,7 +71,9 @@ const Workspace: FC = () => { if (!destTaskId) return; - queryClient.setQueryData(["tasks-by-task-phase", columnId], (current) => updateTaskPhaseItems(current, (items) => reorderTaskItems(items, activeTask.id, index))); + queryClient.setQueryData(["tasks-by-task-phase", columnId], (current) => + updateTaskPhaseItems(current, (items) => reorderTaskItems(items, activeTask.id, index)), + ); changeTaskPriority.mutate( { srcId: activeTask.id, destId: destTaskId, taskPhaseId: columnId, projectId }, @@ -155,6 +159,18 @@ const Workspace: FC = () => { ); }; + const handleTaskDragEnd = (result: DropResult) => { + const activeTask = draggedTask; + setDraggedTask(null); + + if (!activeTask || !result.destination || result.type !== "TASK") return; + + const { source, destination } = result; + if (source.droppableId === destination.droppableId && source.index === destination.index) return; + + handleDrop(destination.droppableId, destination.index, activeTask); + }; + const handleTaskPhaseChanged = (taskId: string, taskPhaseId: string) => { const sourceColumnId = selectedTask?.columnId; @@ -216,34 +232,34 @@ const Workspace: FC = () => { } return ( -
+
-
-
- {columns.map((column) => ( - - ))} +
+ +
+
+ {columns.map((column) => ( + + ))} - -
+ +
+
+
{ {!isWorkspace && }
-
-
+
+