This commit is contained in:
@@ -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<Props> = ({
|
||||
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<HTMLDivElement>(null);
|
||||
const dragImageRef = useRef<HTMLElement | null>(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 (
|
||||
<div
|
||||
ref={columnRef}
|
||||
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 max-h-[calc(100dvh-140px)] sm:max-h-[calc(100dvh-152px)] xl:max-h-[calc(100dvh-220px)] transition-colors ${
|
||||
isDragOver ? "ring-2 ring-primary/40" : ""
|
||||
} ${isColumnDragOver ? "ring-2 ring-black/30" : ""} ${
|
||||
isDraggingColumn ? "opacity-50" : ""
|
||||
}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={(e) => 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);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between items-center shrink-0">
|
||||
<div
|
||||
draggable
|
||||
onDragStart={handleColumnDragStart}
|
||||
onDragEnd={handleColumnDragEnd}
|
||||
className="flex justify-between items-center shrink-0 cursor-grab active:cursor-grabbing"
|
||||
>
|
||||
<div className="font-bold text-sm truncate">{column.title}</div>
|
||||
<More size={20} color="black" className="shrink-0" />
|
||||
<More size={20} color="black" className="shrink-0 pointer-events-none" />
|
||||
</div>
|
||||
|
||||
<div className="mt-3 sm:mt-4 xl:mt-5 flex-1 min-h-0 overflow-y-auto overscroll-y-contain flex flex-col gap-3 sm:gap-4">
|
||||
@@ -63,6 +154,7 @@ const Column: FC<Props> = ({
|
||||
<div
|
||||
key={task.id}
|
||||
onDragOver={(e) => {
|
||||
if (draggedColumnId) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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<Task[]>(tasksData.tasks as Task[]);
|
||||
const [draggedTaskId, setDraggedTaskId] = useState<string | null>(null);
|
||||
const [draggedColumnId, setDraggedColumnId] = useState<string | null>(null);
|
||||
const [selectedTask, setSelectedTask] = useState<Task | null>(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}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user