cursor pagination
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useMemo, useState, type FC } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useEffect, useState, type FC } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import PageLoading from "../../components/PageLoading";
|
||||
@@ -10,21 +11,24 @@ import TaskDetailModal from "./components/task-detail/TaskDetailModal";
|
||||
import { useGetProject } from "./project/hooks/useProjectData";
|
||||
import { useUpdateTaskPhase } from "./task-phase/hooks/useTaskPhaseData";
|
||||
import { useChangeTaskPhase } from "./task/hooks/useTaskData";
|
||||
import { mapTaskItemToTask } from "./task/utils/mapTaskItemToTask";
|
||||
import type { TaskItemType } from "./task/types/TaskTypes";
|
||||
import type { TaskPhaseItemType } from "./task-phase/types/TaskPhaseTypes";
|
||||
import type { Column as ColumnType, Task } from "./types";
|
||||
import { reorderColumns } from "./utils/reorderColumns";
|
||||
import { reorderTasks } from "./utils/reorderTasks";
|
||||
import { reorderTaskItems } from "./utils/reorderTaskItems";
|
||||
import {
|
||||
insertTaskPhaseItem,
|
||||
type TasksByTaskPhaseInfiniteData,
|
||||
updateTaskPhaseItems,
|
||||
} from "./utils/taskPhaseQueryCache";
|
||||
|
||||
const Workspace: FC = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { slug: projectId = "" } = useParams<{ slug: string }>();
|
||||
const { data: project, isPending } = useGetProject(projectId);
|
||||
const changeTaskPhase = useChangeTaskPhase();
|
||||
const updateTaskPhase = useUpdateTaskPhase();
|
||||
const [columns, setColumns] = useState<ColumnType[]>([]);
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
const [draggedTaskId, setDraggedTaskId] = useState<string | null>(null);
|
||||
const [draggedTask, setDraggedTask] = useState<Task | null>(null);
|
||||
const [draggedColumnId, setDraggedColumnId] = useState<string | null>(null);
|
||||
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
||||
|
||||
@@ -43,60 +47,79 @@ const Workspace: FC = () => {
|
||||
);
|
||||
}, [project?.data?.taskPhases]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!project?.data?.taskPhases) return;
|
||||
|
||||
const apiTasks = project.data.taskPhases.flatMap((phase) =>
|
||||
(phase.tasks ?? []).map((task, index) =>
|
||||
mapTaskItemToTask(task as TaskItemType, phase.id, index),
|
||||
),
|
||||
);
|
||||
|
||||
setTasks(apiTasks);
|
||||
}, [project?.data?.taskPhases]);
|
||||
|
||||
const tasksByColumn = useMemo(() => {
|
||||
return columns.reduce<Record<string, Task[]>>((acc, column) => {
|
||||
acc[column.id] = tasks.filter((task) => task.columnId === column.id).sort((a, b) => a.order - b.order);
|
||||
return acc;
|
||||
}, {});
|
||||
}, [columns, tasks]);
|
||||
|
||||
const handleDragStart = (taskId: string) => {
|
||||
setDraggedTaskId(taskId);
|
||||
const handleDragStart = (task: Task) => {
|
||||
setDraggedTask(task);
|
||||
};
|
||||
|
||||
const handleDragEnd = () => {
|
||||
setDraggedTaskId(null);
|
||||
setDraggedTask(null);
|
||||
};
|
||||
|
||||
const handleDrop = (columnId: string, index: number) => {
|
||||
if (!draggedTaskId) return;
|
||||
|
||||
const draggedTask = tasks.find((task) => task.id === draggedTaskId);
|
||||
if (!draggedTask) return;
|
||||
|
||||
const previousTasks = tasks;
|
||||
const columnChanged = draggedTask.columnId !== columnId;
|
||||
const nextTasks = reorderTasks(tasks, draggedTaskId, columnId, index);
|
||||
const movedTask = nextTasks.find((task) => task.id === draggedTaskId) ?? null;
|
||||
const sourceColumnId = draggedTask.columnId;
|
||||
const columnChanged = sourceColumnId !== columnId;
|
||||
const activeTask = draggedTask;
|
||||
|
||||
setTasks(nextTasks);
|
||||
setDraggedTaskId(null);
|
||||
setDraggedTask(null);
|
||||
|
||||
if (selectedTask?.id === draggedTaskId && movedTask) {
|
||||
setSelectedTask(movedTask);
|
||||
if (!columnChanged) {
|
||||
queryClient.setQueryData<TasksByTaskPhaseInfiniteData>(["tasks-by-task-phase", columnId], (current) =>
|
||||
updateTaskPhaseItems(current, (items) => reorderTaskItems(items, activeTask.id, index)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!columnChanged) return;
|
||||
const previousSource = queryClient.getQueryData<TasksByTaskPhaseInfiniteData>([
|
||||
"tasks-by-task-phase",
|
||||
sourceColumnId,
|
||||
]);
|
||||
const previousDest = queryClient.getQueryData<TasksByTaskPhaseInfiniteData>([
|
||||
"tasks-by-task-phase",
|
||||
columnId,
|
||||
]);
|
||||
|
||||
queryClient.setQueryData<TasksByTaskPhaseInfiniteData>(["tasks-by-task-phase", sourceColumnId], (current) =>
|
||||
updateTaskPhaseItems(current, (items) => items.filter((task) => task.id !== activeTask.id)),
|
||||
);
|
||||
|
||||
queryClient.setQueryData<TasksByTaskPhaseInfiniteData>(["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),
|
||||
);
|
||||
|
||||
if (selectedTask?.id === activeTask.id) {
|
||||
setSelectedTask({ ...activeTask, columnId });
|
||||
}
|
||||
|
||||
changeTaskPhase.mutate(
|
||||
{ id: draggedTaskId, params: { taskPhaseId: columnId }, projectId },
|
||||
{ id: activeTask.id, params: { taskPhaseId: columnId }, projectId },
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", sourceColumnId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", columnId] });
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
setTasks(previousTasks);
|
||||
if (selectedTask?.id === draggedTaskId) {
|
||||
setSelectedTask(draggedTask);
|
||||
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(error.response?.data?.error.message[0]);
|
||||
},
|
||||
@@ -105,13 +128,12 @@ const Workspace: FC = () => {
|
||||
};
|
||||
|
||||
const handleTaskPhaseChanged = (taskId: string, taskPhaseId: string) => {
|
||||
setTasks((prev) => {
|
||||
const task = prev.find((item) => item.id === taskId);
|
||||
if (!task || task.columnId === taskPhaseId) return prev;
|
||||
const sourceColumnId = selectedTask?.columnId;
|
||||
|
||||
const destinationIndex = prev.filter((item) => item.columnId === taskPhaseId).length;
|
||||
return reorderTasks(prev, taskId, taskPhaseId, destinationIndex);
|
||||
});
|
||||
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));
|
||||
};
|
||||
@@ -161,19 +183,12 @@ const Workspace: FC = () => {
|
||||
|
||||
const handleColumnDeleted = (columnId: string) => {
|
||||
setColumns((prev) => prev.filter((column) => column.id !== columnId));
|
||||
setTasks((prev) => prev.filter((task) => task.columnId !== columnId));
|
||||
queryClient.removeQueries({ queryKey: ["tasks-by-task-phase", columnId] });
|
||||
if (selectedTask?.columnId === columnId) {
|
||||
setSelectedTask(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTaskCreated = (task: TaskItemType, taskPhaseId: string) => {
|
||||
setTasks((prev) => [
|
||||
...prev,
|
||||
mapTaskItemToTask(task, taskPhaseId, prev.filter((item) => item.columnId === taskPhaseId).length),
|
||||
]);
|
||||
};
|
||||
|
||||
if (isPending) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
@@ -188,8 +203,7 @@ const Workspace: FC = () => {
|
||||
key={column.id}
|
||||
column={column}
|
||||
projectId={projectId}
|
||||
tasks={tasksByColumn[column.id] ?? []}
|
||||
draggedTaskId={draggedTaskId}
|
||||
draggedTaskId={draggedTask?.id ?? null}
|
||||
draggedColumnId={draggedColumnId}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
@@ -199,7 +213,6 @@ const Workspace: FC = () => {
|
||||
onColumnDrop={handleColumnDrop}
|
||||
onTaskClick={setSelectedTask}
|
||||
onColumnDeleted={handleColumnDeleted}
|
||||
onTaskCreated={handleTaskCreated}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user