moving column
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-06-20 10:22:06 +03:30
parent f836abd8f4
commit f3153c6793
3 changed files with 140 additions and 7 deletions
+98 -6
View File
@@ -1,5 +1,5 @@
import { Add, AddCircle, CloseCircle, More } from "iconsax-react"; 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 type { Column as ColumnType, Task as TaskType } from "../types";
import Task from "./Task"; import Task from "./Task";
@@ -7,9 +7,13 @@ type Props = {
column: ColumnType; column: ColumnType;
tasks: TaskType[]; tasks: TaskType[];
draggedTaskId: string | null; draggedTaskId: string | null;
draggedColumnId: string | null;
onDragStart: (taskId: string) => void; onDragStart: (taskId: string) => void;
onDragEnd: () => void; onDragEnd: () => void;
onDrop: (columnId: string, index: number) => void; onDrop: (columnId: string, index: number) => void;
onColumnDragStart: (columnId: string) => void;
onColumnDragEnd: () => void;
onColumnDrop: (overColumnId: string) => void;
onTaskClick?: (task: TaskType) => void; onTaskClick?: (task: TaskType) => void;
}; };
@@ -17,45 +21,132 @@ const Column: FC<Props> = ({
column, column,
tasks, tasks,
draggedTaskId, draggedTaskId,
draggedColumnId,
onDragStart, onDragStart,
onDragEnd, onDragEnd,
onDrop, onDrop,
onColumnDragStart,
onColumnDragEnd,
onColumnDrop,
onTaskClick, onTaskClick,
}) => { }) => {
const [isAdding, setIsAdding] = useState(false); const [isAdding, setIsAdding] = useState(false);
const [isDragOver, setIsDragOver] = 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) => { const handleDragOver = (e: React.DragEvent) => {
if (draggedColumnId) return;
e.preventDefault(); e.preventDefault();
e.dataTransfer.dropEffect = "move"; e.dataTransfer.dropEffect = "move";
setIsDragOver(true); setIsDragOver(true);
}; };
const handleDragLeave = (e: React.DragEvent) => { const handleDragLeave = (e: React.DragEvent) => {
if (draggedColumnId) return;
if (!e.currentTarget.contains(e.relatedTarget as Node)) { if (!e.currentTarget.contains(e.relatedTarget as Node)) {
setIsDragOver(false); setIsDragOver(false);
} }
}; };
const handleDrop = (e: React.DragEvent, index: number) => { const handleDrop = (e: React.DragEvent, index: number) => {
if (draggedColumnId) return;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setIsDragOver(false); setIsDragOver(false);
onDrop(column.id, index); 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 ( return (
<div <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 ${ 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" : "" isDragOver ? "ring-2 ring-primary/40" : ""
} ${isColumnDragOver ? "ring-2 ring-black/30" : ""} ${
isDraggingColumn ? "opacity-50" : ""
}`} }`}
onDragOver={handleDragOver} onDragOver={(e) => {
onDragLeave={handleDragLeave} handleColumnDragOver(e);
onDrop={(e) => handleDrop(e, tasks.length)} handleDragOver(e);
}}
onDragLeave={(e) => {
handleColumnDragLeave(e);
handleDragLeave(e);
}}
onDrop={(e) => {
handleColumnDrop(e);
if (!draggedColumnId) {
handleDrop(e, tasks.length);
}
}}
>
<div
draggable
onDragStart={handleColumnDragStart}
onDragEnd={handleColumnDragEnd}
className="flex justify-between items-center shrink-0 cursor-grab active:cursor-grabbing"
> >
<div className="flex justify-between items-center shrink-0">
<div className="font-bold text-sm truncate">{column.title}</div> <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>
<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"> <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 <div
key={task.id} key={task.id}
onDragOver={(e) => { onDragOver={(e) => {
if (draggedColumnId) return;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); 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;
};
+23 -1
View File
@@ -5,12 +5,16 @@ import HeaderWorkspace from "./components/HeaderWorkspace";
import TaskDetailModal from "./components/task-detail/TaskDetailModal"; import TaskDetailModal from "./components/task-detail/TaskDetailModal";
import tasksData from "./data/tasks.json"; import tasksData from "./data/tasks.json";
import type { Task, WorkspaceData } from "./types"; import type { Task, WorkspaceData } from "./types";
import { reorderColumns } from "./utils/reorderColumns";
import { reorderTasks } from "./utils/reorderTasks"; import { reorderTasks } from "./utils/reorderTasks";
const Workspace: FC = () => { 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 [tasks, setTasks] = useState<Task[]>(tasksData.tasks as Task[]);
const [draggedTaskId, setDraggedTaskId] = useState<string | null>(null); const [draggedTaskId, setDraggedTaskId] = useState<string | null>(null);
const [draggedColumnId, setDraggedColumnId] = useState<string | null>(null);
const [selectedTask, setSelectedTask] = useState<Task | null>(null); const [selectedTask, setSelectedTask] = useState<Task | null>(null);
const tasksByColumn = useMemo(() => { const tasksByColumn = useMemo(() => {
@@ -36,6 +40,20 @@ const Workspace: FC = () => {
setDraggedTaskId(null); 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 const selectedTaskColumn = selectedTask
? columns.find((column) => column.id === selectedTask.columnId) ? columns.find((column) => column.id === selectedTask.columnId)
: null; : null;
@@ -51,9 +69,13 @@ const Workspace: FC = () => {
column={column} column={column}
tasks={tasksByColumn[column.id] ?? []} tasks={tasksByColumn[column.id] ?? []}
draggedTaskId={draggedTaskId} draggedTaskId={draggedTaskId}
draggedColumnId={draggedColumnId}
onDragStart={handleDragStart} onDragStart={handleDragStart}
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
onDrop={handleDrop} onDrop={handleDrop}
onColumnDragStart={handleColumnDragStart}
onColumnDragEnd={handleColumnDragEnd}
onColumnDrop={handleColumnDrop}
onTaskClick={setSelectedTask} onTaskClick={setSelectedTask}
/> />
))} ))}