diff --git a/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx b/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx index 09d4f8b..8dae7ba 100644 --- a/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx +++ b/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx @@ -17,7 +17,8 @@ import TaskDetailMetadataPreview from "./TaskDetailMetadataPreview"; import type { TaskDetailTab } from "./types"; import TaskDetailUsersPopover from "./users/TaskDetailUsersPopover"; import type { TaskDetailType } from "../../task/types/TaskTypes"; -import { useCreateCheckListItem, useCreateTaskRemark, useUpdateTask } from "../../task/hooks/useTaskData"; +import { useCreateCheckListItem, useCreateTaskAttachment, useCreateTaskRemark, useUpdateTask } from "../../task/hooks/useTaskData"; +import { useSingleUpload } from "../../../service/hooks/useServiceData"; import { ErrorType } from "../../../../helpers/types"; type Props = { @@ -37,6 +38,8 @@ const TaskDetailToolbar: FC = ({ activeTab, onTabChange, taskDetail, task const { t } = useTranslation("global"); const createTaskRemark = useCreateTaskRemark(); const createCheckListItem = useCreateCheckListItem(); + const createTaskAttachment = useCreateTaskAttachment(); + const singleUpload = useSingleUpload(); const updateTask = useUpdateTask(); const getUsers = useGetUsers(""); const [labelsView, setLabelsView] = useState("list"); @@ -204,10 +207,46 @@ const TaskDetailToolbar: FC = ({ activeTab, onTabChange, taskDetail, task if (isAttachmentOpen) onTabChange("attachment"); }; - const handleAttachmentCreate = (_data: { file?: File; title: string; linkId: string }) => { - handleAttachmentClose(); + const handleAttachmentCreate = async (data: { file?: File; title: string; linkId: string }) => { + const effectiveTaskId = taskId || taskDetail?.id; + if (!effectiveTaskId) return; + + let fileUrl = data.linkId.trim(); + + if (data.file) { + const formData = new FormData(); + formData.append("file", data.file); + + try { + const uploadResult = await singleUpload.mutateAsync(formData); + fileUrl = uploadResult?.data?.url ?? ""; + } catch (error) { + toast.error((error as ErrorType).response?.data?.error.message[0]); + return; + } + } + + if (!fileUrl) return; + + const title = data.title.trim() || data.file?.name || ""; + const type = data.file ? "file" : "link"; + + createTaskAttachment.mutate( + { file: fileUrl, title, type, taskId: effectiveTaskId }, + { + onSuccess: () => { + handleAttachmentClose(); + toast.success(t("success")); + }, + onError: (error: ErrorType) => { + toast.error(error.response?.data?.error.message[0]); + }, + }, + ); }; + const isAttachmentSubmitting = singleUpload.isPending || createTaskAttachment.isPending; + const handleDateClose = () => { if (isDateOpen) onTabChange("date"); }; @@ -258,7 +297,11 @@ const TaskDetailToolbar: FC = ({ activeTab, onTabChange, taskDetail, task } attachmentPopover={ isAttachmentOpen ? ( - + ) : null } datePopover={ diff --git a/src/pages/taskmanager/components/task-detail/attachment/AttachmentItem.tsx b/src/pages/taskmanager/components/task-detail/attachment/AttachmentItem.tsx index 9e68fe1..2de91b1 100644 --- a/src/pages/taskmanager/components/task-detail/attachment/AttachmentItem.tsx +++ b/src/pages/taskmanager/components/task-detail/attachment/AttachmentItem.tsx @@ -5,7 +5,7 @@ import { timeAgo } from "../../../../../config/func"; type Props = { title: string; - createdAt: string; + createdAt?: string; type: "file" | "link"; url?: string; onEdit: () => void; @@ -21,14 +21,14 @@ const AttachmentItem: FC = ({ title, createdAt, type, url, onEdit, onDele
- {type === "link" && url ? ( + {url ? ( {title} ) : (
{title}
)} -
{timeAgo(createdAt)}
+ {createdAt &&
{timeAgo(createdAt)}
}
diff --git a/src/pages/taskmanager/components/task-detail/attachment/List.tsx b/src/pages/taskmanager/components/task-detail/attachment/List.tsx index 6536412..3d69ccb 100644 --- a/src/pages/taskmanager/components/task-detail/attachment/List.tsx +++ b/src/pages/taskmanager/components/task-detail/attachment/List.tsx @@ -14,13 +14,13 @@ const AttachmentList: FC = ({ attachments = [] }) => { const mappedAttachments: TaskAttachment[] = attachments.map((item) => ({ id: item.id, title: item.title, - fileName: item.fileName, - url: item.url, + type: item.type, + file: item.file, createdAt: item.createdAt, })); - const files = mappedAttachments.filter((item) => item.fileName); - const links = mappedAttachments.filter((item) => item.url); + const files = mappedAttachments.filter((item) => item.type === "file"); + const links = mappedAttachments.filter((item) => item.type === "link"); if (mappedAttachments.length === 0) return null; @@ -36,9 +36,10 @@ const AttachmentList: FC = ({ attachments = [] }) => { {files.map((item) => ( {}} onDelete={() => {}} /> @@ -58,7 +59,7 @@ const AttachmentList: FC = ({ attachments = [] }) => { title={item.title} createdAt={item.createdAt} type="link" - url={item.url} + url={item.file} onEdit={() => {}} onDelete={() => {}} /> diff --git a/src/pages/taskmanager/components/task-detail/attachment/TaskDetailAttachmentForm.tsx b/src/pages/taskmanager/components/task-detail/attachment/TaskDetailAttachmentForm.tsx index 52790ad..cba3e4b 100644 --- a/src/pages/taskmanager/components/task-detail/attachment/TaskDetailAttachmentForm.tsx +++ b/src/pages/taskmanager/components/task-detail/attachment/TaskDetailAttachmentForm.tsx @@ -7,9 +7,10 @@ import Select from "../../../../../components/Select"; type Props = { onClose: () => void; onSubmit: (data: { file?: File; title: string; linkId: string }) => void; + isSubmitting?: boolean; }; -const TaskDetailAttachmentForm: FC = ({ onClose, onSubmit }) => { +const TaskDetailAttachmentForm: FC = ({ onClose, onSubmit, isSubmitting = false }) => { const { t } = useTranslation("global"); const fileInputRef = useRef(null); const [file, setFile] = useState(null); @@ -64,10 +65,10 @@ const TaskDetailAttachmentForm: FC = ({ onClose, onSubmit }) => { />
- -
diff --git a/src/pages/taskmanager/components/task-detail/attachment/TaskDetailAttachmentPopover.tsx b/src/pages/taskmanager/components/task-detail/attachment/TaskDetailAttachmentPopover.tsx index 9fc8fec..df13614 100644 --- a/src/pages/taskmanager/components/task-detail/attachment/TaskDetailAttachmentPopover.tsx +++ b/src/pages/taskmanager/components/task-detail/attachment/TaskDetailAttachmentPopover.tsx @@ -4,10 +4,11 @@ import TaskDetailAttachmentForm from "./TaskDetailAttachmentForm"; type Props = { onClose: () => void; onSubmit: (data: { file?: File; title: string; linkId: string }) => void; + isSubmitting?: boolean; }; -const TaskDetailAttachmentPopover: FC = ({ onClose, onSubmit }) => { - return ; +const TaskDetailAttachmentPopover: FC = ({ onClose, onSubmit, isSubmitting }) => { + return ; }; export default TaskDetailAttachmentPopover; diff --git a/src/pages/taskmanager/components/task-detail/attachment/types.ts b/src/pages/taskmanager/components/task-detail/attachment/types.ts index 44c5a16..520c23e 100644 --- a/src/pages/taskmanager/components/task-detail/attachment/types.ts +++ b/src/pages/taskmanager/components/task-detail/attachment/types.ts @@ -1,8 +1,7 @@ export type TaskAttachment = { id: string; title: string; - createdAt: string; - linkId?: string; - fileName?: string; - url?: string; + type: "file" | "link"; + file?: string; + createdAt?: string; }; diff --git a/src/pages/taskmanager/task/hooks/useTaskData.ts b/src/pages/taskmanager/task/hooks/useTaskData.ts index 71f9b6e..50b6911 100644 --- a/src/pages/taskmanager/task/hooks/useTaskData.ts +++ b/src/pages/taskmanager/task/hooks/useTaskData.ts @@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import * as api from "../service/TaskService"; import { CreateCheckListItemType, + CreateTaskAttachmentType, CreateTaskRemarkType, CreateTaskType, UpdateCheckListItemType, @@ -88,3 +89,15 @@ export const useUpdateCheckListItem = () => { }, }); }; + +export const useCreateTaskAttachment = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (variables: CreateTaskAttachmentType) => api.createTaskAttachment(variables), + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ queryKey: ["task-detail", variables.taskId] }); + queryClient.invalidateQueries({ queryKey: ["project"] }); + }, + }); +}; diff --git a/src/pages/taskmanager/task/service/TaskService.ts b/src/pages/taskmanager/task/service/TaskService.ts index 0f7d852..6066723 100644 --- a/src/pages/taskmanager/task/service/TaskService.ts +++ b/src/pages/taskmanager/task/service/TaskService.ts @@ -2,6 +2,7 @@ import axios from "../../../../config/axios"; import { CreateCheckListItemResponse, CreateCheckListItemType, + CreateTaskAttachmentType, CreateTaskRemarkResponse, CreateTaskRemarkType, CreateTaskType, @@ -48,3 +49,8 @@ export const updateCheckListItem = async ( const { data } = await axios.patch(`/task-manager/check-list-items/${id}`, params); return data; }; + +export const createTaskAttachment = async (params: CreateTaskAttachmentType) => { + const { data } = await axios.post(`/task-manager/attachments`, params); + return data; +}; diff --git a/src/pages/taskmanager/task/types/TaskTypes.ts b/src/pages/taskmanager/task/types/TaskTypes.ts index 0f79922..544760a 100644 --- a/src/pages/taskmanager/task/types/TaskTypes.ts +++ b/src/pages/taskmanager/task/types/TaskTypes.ts @@ -74,11 +74,18 @@ export type TaskChecklistType = { export type TaskAttachmentType = { id: string; title: string; - fileName?: string; - url?: string; + type: "file" | "link"; + file?: string; createdAt?: string; }; +export type CreateTaskAttachmentType = { + file: string; + title: string; + type: "file" | "link"; + taskId: string; +}; + export type TaskDetailType = { id: string; createdAt: string;