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"; import { toast } from '../../components/Toast'; import PageLoading from "../../components/PageLoading"; import { ErrorType } from "../../helpers/types"; import AddNewColumn from "./components/AddNewColumn"; import Column from "./components/Column"; import HeaderWorkspace from "./components/HeaderWorkspace"; import TaskDetailModal from "./components/task-detail/TaskDetailModal"; import { useGetProject, useGetProjectDetail } from "./project/hooks/useProjectData"; import ProjectSettingsPanel from "./project/components/ProjectSettingsPanel"; import type { ProjectDetailType } from "./project/types/ProjectTypes"; import { getProjectBackgroundStyle } from "./project/utils/getProjectBackgroundStyle"; 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"; import { reorderTaskItems } from "./utils/reorderTaskItems"; import { getTaskPhaseItems, insertTaskPhaseItem, updateTaskPhaseItems, type TasksByTaskPhaseInfiniteData } from "./utils/taskPhaseQueryCache"; const Workspace: FC = () => { const queryClient = useQueryClient(); const { slug: projectId = "" } = useParams<{ slug: string }>(); const { data: project, isPending } = useGetProject(projectId); const { data: projectDetailData } = useGetProjectDetail(projectId); const projectDetail: ProjectDetailType | undefined = projectDetailData?.data?.project ?? projectDetailData?.data; const { mutate: changeTaskPhase } = useChangeTaskPhase(); const { mutate: changeTaskPriority } = useChangeTaskPriority(); const { mutate: updateTaskPhase } = useUpdateTaskPhase(); const [columns, setColumns] = useState([]); const [draggedTask, setDraggedTask] = useState(null); const [draggedColumnId, setDraggedColumnId] = useState(null); const [selectedTask, setSelectedTask] = useState(null); const [isSettingsOpen, setIsSettingsOpen] = useState(false); useEffect(() => { if (!project?.data?.taskPhases) return; setColumns( [...project.data.taskPhases] .sort((a, b) => a.order - b.order) .map((phase) => ({ id: phase.id, title: phase.name, color: phase.color, order: phase.order, })), ); }, [project?.data?.taskPhases]); 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 handleDrop = (columnId: string, index: number, activeTask: Task) => { const sourceColumnId = activeTask.columnId; const columnChanged = sourceColumnId !== columnId; if (!columnChanged) { const previousData = queryClient.getQueryData(["tasks-by-task-phase", columnId]); const currentItems = getTaskPhaseItems(previousData); const destTaskId = getPriorityDestinationTaskId(currentItems, activeTask.id, index); if (!destTaskId) return; queryClient.setQueryData(["tasks-by-task-phase", columnId], (current) => updateTaskPhaseItems(current, (items) => reorderTaskItems(items, activeTask.id, index)), ); changeTaskPriority( { srcId: activeTask.id, destId: destTaskId, taskPhaseId: columnId, projectId }, { onError: (error: ErrorType) => { if (previousData) { queryClient.setQueryData(["tasks-by-task-phase", columnId], previousData); } toast(error.response?.data?.error.message[0], 'error'); }, }, ); return; } const previousSource = queryClient.getQueryData(["tasks-by-task-phase", sourceColumnId]); const previousDest = queryClient.getQueryData(["tasks-by-task-phase", columnId]); queryClient.setQueryData(["tasks-by-task-phase", sourceColumnId], (current) => updateTaskPhaseItems(current, (items) => items.filter((task) => task.id !== activeTask.id)), ); queryClient.setQueryData(["tasks-by-task-phase", columnId], (current) => insertTaskPhaseItem( current, { id: activeTask.id, title: activeTask.title, taskPhaseId: columnId, order: index, priority: index, tag: activeTask.tag, dateRange: activeTask.dateRange, color: activeTask.color, attachments: activeTask.attachments, checklistDone: activeTask.checklistDone, checklistTotal: activeTask.checklistTotal, }, index, ), ); const destTaskId = getPriorityDestinationTaskId(getTaskPhaseItems(previousDest), activeTask.id, index, false); if (selectedTask?.id === activeTask.id) { setSelectedTask({ ...activeTask, columnId }); } changeTaskPhase( { id: activeTask.id, params: { taskPhaseId: columnId }, projectId }, { onSuccess: () => { if (destTaskId) { changeTaskPriority( { srcId: activeTask.id, destId: destTaskId, taskPhaseId: columnId, projectId }, { onError: (error: ErrorType) => { queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", sourceColumnId] }); queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", columnId] }); toast(error.response?.data?.error.message[0], 'error'); }, }, ); return; } queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", sourceColumnId] }); queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", columnId] }); }, onError: (error: ErrorType) => { if (previousSource) { queryClient.setQueryData(["tasks-by-task-phase", sourceColumnId], previousSource); } if (previousDest) { queryClient.setQueryData(["tasks-by-task-phase", columnId], previousDest); } if (selectedTask?.id === activeTask.id) { setSelectedTask(activeTask); } toast(error.response?.data?.error.message[0], 'error'); }, }, ); }; 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; if (sourceColumnId) { queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", sourceColumnId] }); } queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", taskPhaseId] }); setSelectedTask((prev) => (prev?.id === taskId ? { ...prev, columnId: taskPhaseId } : prev)); }; const handleColumnDragStart = (columnId: string) => { setDraggedColumnId(columnId); }; const handleColumnDragEnd = () => { setDraggedColumnId(null); }; const handleColumnDrop = (overColumnId: string) => { if (!draggedColumnId || draggedColumnId === overColumnId) return; const previousColumns = columns; const nextColumns = reorderColumns(columns, draggedColumnId, overColumnId); const movedColumn = nextColumns.find((column) => column.id === draggedColumnId); setColumns(nextColumns); setDraggedColumnId(null); if (!movedColumn?.order) return; updateTaskPhase( { id: draggedColumnId, params: { order: movedColumn.order }, projectId }, { onError: (error: ErrorType) => { setColumns(previousColumns); toast(error.response?.data?.error.message[0], 'error'); }, }, ); }; const nextColumnOrder = columns.length ? Math.max(...columns.map((column) => column.order ?? 0)) + 1 : 1; const handleColumnCreated = (phase: TaskPhaseItemType) => { setColumns((prev) => [...prev, { id: phase.id, title: phase.name, color: phase.color, order: phase.order }].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))); }; const handleColumnDeleted = (columnId: string) => { setColumns((prev) => prev.filter((column) => column.id !== columnId)); queryClient.removeQueries({ queryKey: ["tasks-by-task-phase", columnId] }); if (selectedTask?.columnId === columnId) { setSelectedTask(null); } }; const handleColumnTitleUpdated = (columnId: string, title: string) => { setColumns((prev) => prev.map((column) => (column.id === columnId ? { ...column, title } : column))); }; if (isPending) { return ; } return (
setIsSettingsOpen(true)} />
{columns.map((column) => ( ))}
setSelectedTask(null)} onTaskPhaseChanged={handleTaskPhaseChanged} /> setIsSettingsOpen(false)} projectId={projectId} project={projectDetail} />
); }; export default Workspace;