diff --git a/src/components/common/FileDropZone.tsx b/src/components/common/FileDropZone.tsx index bc82ff4e..599ef010 100644 --- a/src/components/common/FileDropZone.tsx +++ b/src/components/common/FileDropZone.tsx @@ -68,7 +68,7 @@ export default function FileDropZone({ className="text-close" onClick={(e: React.MouseEvent) => { e.preventDefault(); - handleFileDeleteClick(id); + handleFileDeleteClick(fileId.toString()); }} /> diff --git a/src/components/modal/task/UpdateModalTask.tsx b/src/components/modal/task/UpdateModalTask.tsx index 746d56f4..e54b17bb 100644 --- a/src/components/modal/task/UpdateModalTask.tsx +++ b/src/components/modal/task/UpdateModalTask.tsx @@ -19,6 +19,7 @@ import { useReadStatuses } from '@hooks/query/useStatusQuery'; import { useAddAssignee, useDeleteAssignee, + useDeleteTaskFile, useReadAssignees, useReadStatusTasks, useReadTaskFiles, @@ -59,6 +60,7 @@ export default function UpdateModalTask({ project, taskId, onClose: handleClose const { mutate: updateTaskInfoMutate } = useUpdateTaskInfo(projectId, taskId); const { mutate: addAssigneeMutate } = useAddAssignee(projectId, taskId); const { mutate: deleteAssigneeMutate } = useDeleteAssignee(projectId, taskId); + const { mutate: deleteTaskFileMutate } = useDeleteTaskFile(projectId, taskId); const methods = useForm({ mode: 'onChange' }); const { @@ -123,8 +125,7 @@ export default function UpdateModalTask({ project, taskId, onClose: handleClose updateFiles(files); }; - // ToDo: 일정 파일 삭제 API 작업시 추가할 것 - const handleFileDeleteClick = (fileId: string) => {}; + const handleFileDeleteClick = (fileId: string) => deleteTaskFileMutate(Number(fileId)); if (isStatusLoading || isTaskLoading || isProjectUserRoleLoading || isTaskFileLoading || isAssigneeLoading) { return ; @@ -137,67 +138,77 @@ export default function UpdateModalTask({ project, taskId, onClose: handleClose return ( - -
- - - - - + ) : ( + <> + + + + + + + + + + + + + + )} +
+ {isProjectUserRoleLoading || isAssigneeLoading ? ( + + ) : ( +
+ - - - - - - + +
+ )}
- -
- + ) : ( + - -
- -
- - + )}
); diff --git a/src/hooks/query/useTaskQuery.ts b/src/hooks/query/useTaskQuery.ts index aa5f9f1c..194fe435 100644 --- a/src/hooks/query/useTaskQuery.ts +++ b/src/hooks/query/useTaskQuery.ts @@ -9,6 +9,7 @@ import { addAssignee, createTask, deleteAssignee, + deleteTaskFile, findAssignees, findTaskFiles, findTaskList, @@ -28,6 +29,7 @@ import type { TaskUpdateForm, TaskUploadForm, } from '@/types/TaskType'; +import { TaskFile } from '@/types/FileType'; function getTaskNameList(taskList: TaskListWithStatus[], excludedTaskName?: Task['name']) { const taskNameList = taskList @@ -126,7 +128,7 @@ export function useUpdateTasksOrder(projectId: Project['projectId']) { return { previousStatusTaskList }; }, onError: (err, newStatusTaskList, context) => { - toastError('일정 순서 변경에 실패 하였습니다. 잠시후 다시 진행해주세요.'); + toastError('일정 순서 변경에 실패 했습니다. 잠시후 다시 진행해주세요.'); queryClient.setQueryData(tasksQueryKey, context?.previousStatusTaskList); }, }); @@ -196,10 +198,10 @@ export function useAddAssignee(projectId: Project['projectId'], taskId: Task['ta const mutation = useMutation({ mutationFn: (userId: User['userId']) => addAssignee(projectId, taskId, userId), onError: () => { - toastError('수행자 추가에 실패 하였습니다. 잠시후 다시 시도해주세요.'); + toastError('수행자 추가에 실패 했습니다. 잠시후 다시 시도해주세요.'); }, onSuccess: () => { - toastSuccess('수행자를 추가 하였습니다.'); + toastSuccess('수행자를 추가 했습니다.'); queryClient.invalidateQueries({ queryKey: taskAssigneesQueryKey }); }, }); @@ -216,13 +218,31 @@ export function useDeleteAssignee(projectId: Project['projectId'], taskId: Task[ const mutation = useMutation({ mutationFn: (userId: User['userId']) => deleteAssignee(projectId, taskId, userId), onError: () => { - toastError('수행자 삭제에 실패 하였습니다. 잠시후 다시 시도해주세요.'); + toastError('수행자 삭제에 실패 했습니다. 잠시후 다시 시도해주세요.'); }, onSuccess: () => { - toastSuccess('수행자를 삭제 하였습니다.'); + toastSuccess('수행자를 삭제 했습니다.'); queryClient.invalidateQueries({ queryKey: taskAssigneesQueryKey }); }, }); return mutation; } + +// 일정 파일 삭제 +export function useDeleteTaskFile(projectId: Project['projectId'], taskId: Task['taskId']) { + const { toastSuccess, toastError } = useToast(); + const queryClient = useQueryClient(); + const taskFilesQueryKey = generateTaskFilesQueryKey(projectId, taskId); + + const mutation = useMutation({ + mutationFn: (fileId: TaskFile['fileId']) => deleteTaskFile(projectId, taskId, fileId), + onError: () => toastError('일정 파일 삭제에 실패 했습니다. 잠시후 다시 시도해주세요.'), + onSuccess: () => { + toastSuccess('일정 파일을 삭제 했습니다.'); + queryClient.invalidateQueries({ queryKey: taskFilesQueryKey }); + }, + }); + + return mutation; +} diff --git a/src/mocks/services/taskServiceHandler.ts b/src/mocks/services/taskServiceHandler.ts index dbbaf5d4..304b460f 100644 --- a/src/mocks/services/taskServiceHandler.ts +++ b/src/mocks/services/taskServiceHandler.ts @@ -93,6 +93,29 @@ const taskServiceHandler = [ return new HttpResponse(null, { status: 200 }); }), + // 일정 파일 삭제 API + http.delete(`${BASE_URL}/project/:projectId/task/:taskId/file/:fileId`, ({ request, params }) => { + const accessToken = request.headers.get('Authorization'); + const { projectId, taskId, fileId } = params; + + if (!accessToken) return new HttpResponse(null, { status: 401 }); + + // ToDo: JWT의 userId 정보를 가져와 프로젝트 권한 확인이 필요. + const task = TASK_DUMMY.find((task) => task.taskId === Number(taskId)); + if (!task) return new HttpResponse(null, { status: 404 }); + + const taskFileIndex = TASK_FILE_DUMMY.findIndex( + (taskFile) => taskFile.fileId === Number(fileId) && taskFile.taskId === Number(taskId), + ); + if (taskFileIndex === -1) return new HttpResponse(null, { status: 404 }); + TASK_FILE_DUMMY.splice(taskFileIndex, 1); + + const fileIndex = FILE_DUMMY.findIndex((file) => file.fileId === Number(fileId) && file.taskId === Number(taskId)); + if (fileIndex === -1) return new HttpResponse(null, { status: 404 }); + FILE_DUMMY.splice(fileIndex, 1); + + return new HttpResponse(null, { status: 204 }); + }), // 일정 순서 변경 API http.patch(`${BASE_URL}/project/:projectId/task/order`, async ({ request, params }) => { const accessToken = request.headers.get('Authorization'); diff --git a/src/services/taskService.ts b/src/services/taskService.ts index fd13412a..804211e6 100644 --- a/src/services/taskService.ts +++ b/src/services/taskService.ts @@ -171,3 +171,23 @@ export async function deleteAssignee( ) { return authAxios.delete(`/project/${projectId}/task/${taskId}/assignee/${userId}`, axiosConfig); } + +/** + * 일정 파일 삭제 API + * + * @export + * @async + * @param {Project['projectId']} projectId - 프로젝트 ID + * @param {Task['taskId']} taskId - 일정 ID + * @param {TaskFile['fileId']} fileId - 일정 파일 ID + * @param {AxiosRequestConfig} [axiosConfig={}] - axios 요청 옵션 설정 객체 + * @returns {Promise>} + */ +export async function deleteTaskFile( + projectId: Project['projectId'], + taskId: Task['taskId'], + fileId: TaskFile['fileId'], + axiosConfig: AxiosRequestConfig = {}, +) { + return authAxios.delete(`/project/${projectId}/task/${taskId}/file/${fileId}`, axiosConfig); +}