import { type FC, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from '../../../../components/Toast'; import TaskDetailAttachmentPopover from "./attachment/TaskDetailAttachmentPopover"; import TaskDetailDatePopover from "./date/TaskDetailDatePopover"; import { parseApiDate, toApiDate } from "./date/utils"; import type DateObject from "react-date-object"; import TaskDetailChecklistPopover from "./checklist/TaskDetailChecklistPopover"; import TaskDetailLabelsPopover from "./labels/TaskDetailLabelsPopover"; import type { LabelsView, TaskLabel } from "./labels/types"; import TaskDetailActionBar from "./TaskDetailActionBar"; import TaskDetailMetadataPreview from "./TaskDetailMetadataPreview"; import type { TaskDetailTab } from "./types"; import TaskDetailUsersPopover from "./users/TaskDetailUsersPopover"; import TaskDetailTimePopover from "./time/TaskDetailTimePopover"; import type { TaskUser } from "./users/types"; import type { TaskDetailType } from "../../task/types/TaskTypes"; import { useCreateCheckListItem, useCreateTaskAttachment, useCreateTaskRemark, useDeleteTaskRemark, useUpdateTask, } from "../../task/hooks/useTaskData"; import { useGetProjectUsers } from "../../project/hooks/useProjectData"; import type { ProjectUserType } from "../../project/types/ProjectTypes"; import { useSingleUpload } from "../../../service/hooks/useServiceData"; import { ErrorType } from "../../../../helpers/types"; type Props = { activeTab: TaskDetailTab | null; onTabChange: (tab: TaskDetailTab) => void; taskDetail?: TaskDetailType; taskId: string; projectId: string; }; const TaskDetailToolbar: FC = ({ activeTab, onTabChange, taskDetail, taskId, projectId }) => { const { t } = useTranslation("global"); const createTaskRemark = useCreateTaskRemark(); const deleteTaskRemark = useDeleteTaskRemark(); const createCheckListItem = useCreateCheckListItem(); const createTaskAttachment = useCreateTaskAttachment(); const singleUpload = useSingleUpload(); const updateTask = useUpdateTask(); const isUsersOpen = activeTab === "users"; const [userSearch, setUserSearch] = useState(""); const { data: usersData, isPending: isUsersPending } = useGetProjectUsers(projectId, isUsersOpen); const [labelsView, setLabelsView] = useState("list"); const [labels, setLabels] = useState([]); const [selectedLabelIds, setSelectedLabelIds] = useState>(new Set()); const [selectedUserIds, setSelectedUserIds] = useState>(new Set()); const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); useEffect(() => { if (!taskDetail) return; const taskLabels = taskDetail.labels ?? taskDetail.remarks ?? []; if (taskLabels.length) { setLabels(taskLabels); setSelectedLabelIds(new Set(taskLabels.map((label) => label.id))); } else { setLabels([]); setSelectedLabelIds(new Set()); } if (taskDetail.users?.length) { setSelectedUserIds(new Set(taskDetail.users.map((user) => user.id))); } else { setSelectedUserIds(new Set()); } setStartDate(parseApiDate(taskDetail.startDate)); setEndDate(parseApiDate(taskDetail.endDate)); }, [taskDetail]); const selectedLabels = labels.filter((label) => selectedLabelIds.has(label.id)); const projectUsers = useMemo((): TaskUser[] => { const apiUsers: TaskUser[] = usersData?.data?.map((user: ProjectUserType) => ({ id: user.id, name: `${user.firstName} ${user.lastName}`.trim(), avatar: user.profilePic, })) ?? []; const taskUsers: TaskUser[] = taskDetail?.users?.map((user) => ({ id: user.id, name: `${user.firstName} ${user.lastName}`.trim(), avatar: user.profilePic, })) ?? []; const userMap = new Map(apiUsers.map((user) => [user.id, user])); taskUsers.forEach((user) => { const existing = userMap.get(user.id); if (existing) { if (!existing.avatar && user.avatar) { userMap.set(user.id, { ...existing, avatar: user.avatar }); } return; } userMap.set(user.id, user); }); return Array.from(userMap.values()); }, [usersData?.data, taskDetail?.users]); const listUsers = useMemo((): TaskUser[] => { const query = userSearch.trim().toLowerCase(); const apiUsers: TaskUser[] = usersData?.data?.map((user: ProjectUserType) => ({ id: user.id, name: `${user.firstName} ${user.lastName}`.trim(), avatar: user.profilePic, })) ?? []; if (!query) return apiUsers; return apiUsers.filter((user) => user.name.toLowerCase().includes(query)); }, [usersData?.data, userSearch]); const selectedUsers = projectUsers.filter((user) => selectedUserIds.has(user.id)); const isLabelsOpen = activeTab === "labels"; const isChecklistOpen = activeTab === "checklist"; const isAttachmentOpen = activeTab === "attachment"; const isDateOpen = activeTab === "date"; const isTimeOpen = activeTab === "time"; const handleTabChange = (tab: TaskDetailTab) => { if (tab === "labels" && activeTab !== "labels") { setLabelsView("list"); } onTabChange(tab); }; const handleAddLabelClick = () => { setLabelsView("create"); if (activeTab !== "labels") { onTabChange("labels"); } }; const handleAddUserClick = () => { if (activeTab !== "users") { onTabChange("users"); } }; const handleLabelToggle = (id: string) => { setSelectedLabelIds((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }; const handleUserToggle = (id: string) => { const effectiveTaskId = taskId || taskDetail?.id; if (!effectiveTaskId) return; const previous = selectedUserIds; const next = new Set(previous); if (next.has(id)) { next.delete(id); } else { next.add(id); } setSelectedUserIds(next); updateTask.mutate( { id: effectiveTaskId, params: { userIds: Array.from(next) } }, { onSuccess: () => { toast(t("success"), 'success'); }, onError: (error: ErrorType) => { toast(error.response?.data?.error.message[0], 'error'); setSelectedUserIds(previous); }, }, ); }; const handleCreate = (title: string, color: string) => { const effectiveTaskId = taskId || taskDetail?.id; if (!effectiveTaskId) return; createTaskRemark.mutate( { title, color, taskId: effectiveTaskId }, { onSuccess: (response) => { const remark = response.data?.remark; if (remark?.id) { const newLabel: TaskLabel = { id: remark.id, title: remark.title, color: remark.color, }; setLabels((prev) => [...prev, newLabel]); setSelectedLabelIds((prev) => new Set([...prev, newLabel.id])); } setLabelsView("list"); toast(t("success"), 'success'); }, onError: (error: ErrorType) => { toast(error.response?.data?.error.message[0], 'error'); }, }, ); }; const handleDeleteLabel = (id: string) => { const effectiveTaskId = taskId || taskDetail?.id; if (!effectiveTaskId) return; deleteTaskRemark.mutate( { id, taskId: effectiveTaskId }, { onSuccess: () => { setLabels((prev) => prev.filter((label) => label.id !== id)); setSelectedLabelIds((prev) => { const next = new Set(prev); next.delete(id); return next; }); toast(t("success"), "success"); }, onError: (error: ErrorType) => { toast(error.response?.data?.error.message[0], "error"); }, }, ); }; const handleChecklistClose = () => { if (isChecklistOpen) onTabChange("checklist"); }; const handleChecklistCreate = (title: string) => { const effectiveTaskId = taskId || taskDetail?.id; if (!effectiveTaskId) return; createCheckListItem.mutate( { title, isDone: false, taskId: effectiveTaskId }, { onSuccess: () => { handleChecklistClose(); toast(t("success"), 'success'); }, onError: (error: ErrorType) => { toast(error.response?.data?.error.message[0], 'error'); }, }, ); }; const handleAttachmentClose = () => { if (isAttachmentOpen) onTabChange("attachment"); }; 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 as ErrorType).response?.data?.error.message[0], 'error'); 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(t("success"), 'success'); }, onError: (error: ErrorType) => { toast(error.response?.data?.error.message[0], 'error'); }, }, ); }; const isAttachmentSubmitting = singleUpload.isPending || createTaskAttachment.isPending; const handleDateClose = () => { if (isDateOpen) onTabChange("date"); }; const handleDateSubmit = (data: { startDate: DateObject | null; endDate: DateObject | null }) => { const effectiveTaskId = taskId || taskDetail?.id; if (!effectiveTaskId) return; const previousStartDate = startDate; const previousEndDate = endDate; const params = { startDate: data.startDate ? toApiDate(data.startDate) : null, endDate: data.endDate ? toApiDate(data.endDate) : null, }; setStartDate(data.startDate); setEndDate(data.endDate); updateTask.mutate( { id: effectiveTaskId, params }, { onSuccess: () => { handleDateClose(); toast(t("success"), 'success'); }, onError: (error: ErrorType) => { toast(error.response?.data?.error.message[0], 'error'); setStartDate(previousStartDate); setEndDate(previousEndDate); }, }, ); }; return (
) : null } usersPopover={ isUsersOpen ? ( ) : null } checklistPopover={ isChecklistOpen ? ( ) : null } attachmentPopover={ isAttachmentOpen ? ( ) : null } datePopover={ isDateOpen ? ( ) : null } timePopover={isTimeOpen ? : null} />
); }; export default TaskDetailToolbar;