cursor pagination
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
VITE_TOKEN_NAME = 'admin_token'
|
||||
VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token'
|
||||
VITE_BASE_URL = 'https://api.danakcorp.com'
|
||||
#VITE_BASE_URL = 'http://localhost:3500'
|
||||
#VITE_BASE_URL = 'https://api.danakcorp.com'
|
||||
|
||||
VITE_BASE_URL = 'http://10.85.127.88:3500'
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Add, AddCircle, CloseCircle } from "iconsax-react";
|
||||
import { useRef, useState, type FC } from "react";
|
||||
import { useMemo, useRef, useState, type FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "react-toastify";
|
||||
import ModalConfrim from "../../../components/ModalConfrim";
|
||||
import { ErrorType } from "../../../helpers/types";
|
||||
import { useCreateTask } from "../task/hooks/useTaskData";
|
||||
import { useCreateTask, useGetTasksByTaskPhase } from "../task/hooks/useTaskData";
|
||||
import type { TaskItemType } from "../task/types/TaskTypes";
|
||||
import { mapTaskItemToTask } from "../task/utils/mapTaskItemToTask";
|
||||
import { useDeleteTaskPhase } from "../task-phase/hooks/useTaskPhaseData";
|
||||
import type { Column as ColumnType, Task as TaskType } from "../types";
|
||||
import ColumnMenu from "./ColumnMenu";
|
||||
@@ -14,10 +15,9 @@ import Task from "./Task";
|
||||
type Props = {
|
||||
column: ColumnType;
|
||||
projectId: string;
|
||||
tasks: TaskType[];
|
||||
draggedTaskId: string | null;
|
||||
draggedColumnId: string | null;
|
||||
onDragStart: (taskId: string) => void;
|
||||
onDragStart: (task: TaskType) => void;
|
||||
onDragEnd: () => void;
|
||||
onDrop: (columnId: string, index: number) => void;
|
||||
onColumnDragStart: (columnId: string) => void;
|
||||
@@ -25,13 +25,11 @@ type Props = {
|
||||
onColumnDrop: (overColumnId: string) => void;
|
||||
onTaskClick?: (task: TaskType) => void;
|
||||
onColumnDeleted?: (columnId: string) => void;
|
||||
onTaskCreated?: (task: TaskItemType, taskPhaseId: string) => void;
|
||||
};
|
||||
|
||||
const Column: FC<Props> = ({
|
||||
column,
|
||||
projectId,
|
||||
tasks,
|
||||
draggedTaskId,
|
||||
draggedColumnId,
|
||||
onDragStart,
|
||||
@@ -42,11 +40,25 @@ const Column: FC<Props> = ({
|
||||
onColumnDrop,
|
||||
onTaskClick,
|
||||
onColumnDeleted,
|
||||
onTaskCreated,
|
||||
}) => {
|
||||
const { t } = useTranslation("global");
|
||||
const deleteTaskPhase = useDeleteTaskPhase();
|
||||
const createTask = useCreateTask();
|
||||
const {
|
||||
data: tasksData,
|
||||
isPending: isTasksPending,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
} = useGetTasksByTaskPhase(column.id);
|
||||
|
||||
const tasks = useMemo(() => {
|
||||
const items = tasksData?.pages.flatMap((page) => page.data?.items ?? []) ?? [];
|
||||
|
||||
return [...items]
|
||||
.sort((a, b) => (a.order ?? a.priority ?? 0) - (b.order ?? b.priority ?? 0))
|
||||
.map((task, index) => mapTaskItemToTask(task as TaskItemType, column.id, index));
|
||||
}, [tasksData?.pages, column.id]);
|
||||
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [title, setTitle] = useState("");
|
||||
@@ -57,6 +69,15 @@ const Column: FC<Props> = ({
|
||||
const dragImageRef = useRef<HTMLElement | null>(null);
|
||||
const isDraggingColumn = draggedColumnId === column.id;
|
||||
|
||||
const handleTasksScroll = (event: React.UIEvent<HTMLDivElement>) => {
|
||||
const target = event.currentTarget;
|
||||
const isNearBottom = target.scrollHeight - target.scrollTop - target.clientHeight < 80;
|
||||
|
||||
if (isNearBottom && hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
};
|
||||
|
||||
const handleColumnDragStart = (e: React.DragEvent) => {
|
||||
const columnEl = columnRef.current;
|
||||
if (columnEl) {
|
||||
@@ -153,9 +174,7 @@ const Column: FC<Props> = ({
|
||||
projectId,
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
const task = (data?.data ?? data) as TaskItemType;
|
||||
onTaskCreated?.(task, column.id);
|
||||
onSuccess: () => {
|
||||
toast.success(t("success"));
|
||||
resetTaskForm();
|
||||
},
|
||||
@@ -225,8 +244,13 @@ const Column: FC<Props> = ({
|
||||
label={t("taskmanager.delete_column_confirm")}
|
||||
/>
|
||||
|
||||
<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">
|
||||
{tasks.map((task, index) => (
|
||||
<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"
|
||||
onScroll={handleTasksScroll}
|
||||
>
|
||||
{isTasksPending
|
||||
? null
|
||||
: tasks.map((task, index) => (
|
||||
<div
|
||||
key={task.id}
|
||||
onDragOver={(e) => {
|
||||
@@ -245,6 +269,9 @@ const Column: FC<Props> = ({
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{isFetchingNextPage ? (
|
||||
<div className="text-center text-description text-xs py-2">{t("loading")}</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{isAdding ? (
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Task as TaskType } from "../types";
|
||||
type Props = {
|
||||
task: TaskType;
|
||||
isDragging?: boolean;
|
||||
onDragStart: (taskId: string) => void;
|
||||
onDragStart: (task: TaskType) => void;
|
||||
onDragEnd: () => void;
|
||||
onClick?: (task: TaskType) => void;
|
||||
};
|
||||
@@ -23,7 +23,7 @@ const Task: FC<Props> = ({ task, isDragging, onDragStart, onDragEnd, onClick })
|
||||
draggable
|
||||
onDragStart={() => {
|
||||
hasDraggedRef.current = true;
|
||||
onDragStart(task.id);
|
||||
onDragStart(task);
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
onDragEnd();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import * as api from "../service/TaskService";
|
||||
import {
|
||||
ChangeTaskPhaseType,
|
||||
@@ -18,6 +18,20 @@ export const useGetTaskDetail = (id: string, enabled = true) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetTasksByTaskPhase = (taskPhaseId: string, enabled = true) => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: ["tasks-by-task-phase", taskPhaseId],
|
||||
queryFn: ({ pageParam }) => api.getTasksByTaskPhase(taskPhaseId, pageParam),
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: (lastPage) => {
|
||||
const pager = lastPage.data?.pager;
|
||||
if (!pager?.hasNextPage) return undefined;
|
||||
return pager.nextCursor ?? undefined;
|
||||
},
|
||||
enabled: !!taskPhaseId && enabled,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateTask = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -25,6 +39,7 @@ export const useCreateTask = () => {
|
||||
mutationFn: (variables: CreateTaskType) => api.createTask(variables),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["project", variables.projectId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", variables.taskPhaseId] });
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -37,6 +52,7 @@ export const useUpdateTask = () => {
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["task-detail", variables.id] });
|
||||
queryClient.invalidateQueries({ queryKey: ["project"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -51,6 +67,7 @@ export const useChangeTaskPhase = () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["task-detail", variables.id] });
|
||||
queryClient.invalidateQueries({ queryKey: ["project", variables.projectId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["project"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase", variables.params.taskPhaseId] });
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -64,6 +81,7 @@ export const useDeleteTask = () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["task-detail", variables.id] });
|
||||
queryClient.invalidateQueries({ queryKey: ["project", variables.projectId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["project"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["tasks-by-task-phase"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
CreateTaskRemarkType,
|
||||
CreateTaskType,
|
||||
GetTaskDetailResponse,
|
||||
GetTasksByTaskPhaseResponse,
|
||||
UpdateCheckListItemResponse,
|
||||
UpdateCheckListItemType,
|
||||
UpdateTaskType,
|
||||
@@ -23,6 +24,20 @@ export const getTaskDetail = async (id: string): Promise<GetTaskDetailResponse>
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getTasksByTaskPhase = async (
|
||||
taskPhaseId: string,
|
||||
cursor?: string | null,
|
||||
limit = 10,
|
||||
): Promise<GetTasksByTaskPhaseResponse> => {
|
||||
const params = new URLSearchParams({ limit: String(limit) });
|
||||
if (cursor) {
|
||||
params.set("cursor", cursor);
|
||||
}
|
||||
|
||||
const { data } = await axios.get(`/task-manager/tasks/task-phase/${taskPhaseId}?${params.toString()}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateTask = async (id: string, params: UpdateTaskType) => {
|
||||
const { data } = await axios.patch(`/task-manager/tasks/${id}`, params);
|
||||
return data;
|
||||
|
||||
@@ -11,15 +11,34 @@ export type TaskItemType = {
|
||||
id: string;
|
||||
title: string;
|
||||
taskPhaseId: string;
|
||||
order: number;
|
||||
order?: number;
|
||||
priority?: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
description?: string | null;
|
||||
color?: string | null;
|
||||
startDate?: string | null;
|
||||
endDate?: string | null;
|
||||
tag?: string;
|
||||
dateRange?: string;
|
||||
color?: string | null;
|
||||
attachments?: number;
|
||||
checklistDone?: number;
|
||||
checklistTotal?: number;
|
||||
};
|
||||
|
||||
export type TaskPhaseTasksPagerType = {
|
||||
limit: number;
|
||||
nextCursor: string | null;
|
||||
previousCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
};
|
||||
|
||||
export type TaskPhaseTasksDataType = {
|
||||
items: TaskItemType[];
|
||||
pager: TaskPhaseTasksPagerType;
|
||||
};
|
||||
|
||||
export type TaskLabelType = {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -122,3 +141,7 @@ export type UpdateTaskType = Partial<
|
||||
export interface GetTaskDetailResponse extends IResponse<TaskDetailType> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export interface GetTasksByTaskPhaseResponse extends IResponse<TaskPhaseTasksDataType> {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { TaskItemType } from "../types/TaskTypes";
|
||||
export const mapTaskItemToTask = (task: TaskItemType, taskPhaseId: string, fallbackOrder: number): Task => ({
|
||||
id: task.id,
|
||||
columnId: task.taskPhaseId ?? taskPhaseId,
|
||||
order: task.order ?? fallbackOrder,
|
||||
order: task.order ?? task.priority ?? fallbackOrder,
|
||||
title: task.title,
|
||||
tag: task.tag ?? "",
|
||||
dateRange: task.dateRange ?? "",
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { TaskItemType } from "../task/types/TaskTypes";
|
||||
|
||||
const getTaskSortOrder = (task: TaskItemType) => task.order ?? task.priority ?? 0;
|
||||
|
||||
export const reorderTaskItems = (
|
||||
tasks: TaskItemType[],
|
||||
taskId: string,
|
||||
destIndex: number,
|
||||
): TaskItemType[] => {
|
||||
const task = tasks.find((item) => item.id === taskId);
|
||||
if (!task) return tasks;
|
||||
|
||||
const without = tasks.filter((item) => item.id !== taskId);
|
||||
const sorted = [...without].sort((a, b) => getTaskSortOrder(a) - getTaskSortOrder(b));
|
||||
sorted.splice(destIndex, 0, task);
|
||||
|
||||
return sorted.map((item, index) => ({ ...item, order: index, priority: index }));
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import type { InfiniteData } from "@tanstack/react-query";
|
||||
import type { GetTasksByTaskPhaseResponse, TaskItemType } from "../task/types/TaskTypes";
|
||||
|
||||
export type TasksByTaskPhaseInfiniteData = InfiniteData<GetTasksByTaskPhaseResponse, string | undefined>;
|
||||
|
||||
export const getTaskPhaseItems = (data?: TasksByTaskPhaseInfiniteData): TaskItemType[] =>
|
||||
data?.pages.flatMap((page) => page.data?.items ?? []) ?? [];
|
||||
|
||||
const redistributeItemsAcrossPages = (
|
||||
pages: GetTasksByTaskPhaseResponse[],
|
||||
items: TaskItemType[],
|
||||
): GetTasksByTaskPhaseResponse[] => {
|
||||
let offset = 0;
|
||||
|
||||
return pages.map((page) => {
|
||||
const pageSize = page.data?.items.length ?? 0;
|
||||
const pageItems = items.slice(offset, offset + pageSize);
|
||||
offset += pageSize;
|
||||
|
||||
return {
|
||||
...page,
|
||||
data: {
|
||||
...page.data,
|
||||
items: pageItems,
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const updateTaskPhaseItems = (
|
||||
current: TasksByTaskPhaseInfiniteData | undefined,
|
||||
updater: (items: TaskItemType[]) => TaskItemType[],
|
||||
): TasksByTaskPhaseInfiniteData | undefined => {
|
||||
if (!current?.pages.length) return current;
|
||||
|
||||
const updatedItems = updater(getTaskPhaseItems(current));
|
||||
|
||||
return {
|
||||
...current,
|
||||
pages: redistributeItemsAcrossPages(current.pages, updatedItems),
|
||||
};
|
||||
};
|
||||
|
||||
export const insertTaskPhaseItem = (
|
||||
current: TasksByTaskPhaseInfiniteData | undefined,
|
||||
item: TaskItemType,
|
||||
index: number,
|
||||
): TasksByTaskPhaseInfiniteData => {
|
||||
const items = getTaskPhaseItems(current);
|
||||
const nextItems = [...items];
|
||||
nextItems.splice(index, 0, item);
|
||||
|
||||
if (!current?.pages.length) {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
status: 200,
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
data: {
|
||||
items: nextItems,
|
||||
pager: {
|
||||
limit: 10,
|
||||
nextCursor: null,
|
||||
previousCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
pageParams: [undefined],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...current,
|
||||
pages: redistributeItemsAcrossPages(current.pages, nextItems),
|
||||
};
|
||||
};
|
||||
@@ -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