delete task and update task
This commit is contained in:
@@ -1009,6 +1009,9 @@
|
|||||||
"add_new_task": "اضافه کردن تسک",
|
"add_new_task": "اضافه کردن تسک",
|
||||||
"add_task": "اضافه کردن",
|
"add_task": "اضافه کردن",
|
||||||
"task_title": "عنوان تسک",
|
"task_title": "عنوان تسک",
|
||||||
|
"delete_task": "حذف تسک",
|
||||||
|
"delete_task_confirm": "آیا از حذف این تسک اطمینان دارید؟",
|
||||||
|
"task_deleted": "تسک با موفقیت حذف شد",
|
||||||
"task_detail": {
|
"task_detail": {
|
||||||
"labels": "برچسب ها",
|
"labels": "برچسب ها",
|
||||||
"user_management": "مدیریت کاربران",
|
"user_management": "مدیریت کاربران",
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { useTranslation } from "react-i18next";
|
|||||||
type Props = {
|
type Props = {
|
||||||
value?: string;
|
value?: string;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
|
onBlur?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailDescription: FC<Props> = ({ value = "", onChange }) => {
|
const TaskDetailDescription: FC<Props> = ({ value = "", onChange, onBlur }) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -15,6 +16,7 @@ const TaskDetailDescription: FC<Props> = ({ value = "", onChange }) => {
|
|||||||
<textarea
|
<textarea
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange?.(e.target.value)}
|
onChange={(e) => onChange?.(e.target.value)}
|
||||||
|
onBlur={onBlur}
|
||||||
placeholder={t("taskmanager.task_detail.description_placeholder")}
|
placeholder={t("taskmanager.task_detail.description_placeholder")}
|
||||||
rows={5}
|
rows={5}
|
||||||
className="w-full bg-white/25 rounded-xl px-4 py-3 text-xs outline-none resize-none placeholder:text-description"
|
className="w-full bg-white/25 rounded-xl px-4 py-3 text-xs outline-none resize-none placeholder:text-description"
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { ArrowDown2, CloseCircle } from "iconsax-react";
|
import { ArrowDown2, CloseCircle } from "iconsax-react";
|
||||||
import { type FC } from "react";
|
import { type FC } from "react";
|
||||||
|
import TrashWithConfrim from "../../../../components/TrashWithConfrim";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
statusLabel: string;
|
statusLabel: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
onDelete?: () => void;
|
||||||
|
isDeleting?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailHeader: FC<Props> = ({ statusLabel, onClose }) => {
|
const TaskDetailHeader: FC<Props> = ({ statusLabel, onClose, onDelete, isDeleting }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between border-b border-border pb-4">
|
<div className="flex items-center justify-between border-b border-border pb-4">
|
||||||
<button type="button" className="flex items-center gap-1.5 bg-white/60 rounded-full px-4 py-2 text-xs font-medium cursor-pointer">
|
<button type="button" className="flex items-center gap-1.5 bg-white/60 rounded-full px-4 py-2 text-xs font-medium cursor-pointer">
|
||||||
@@ -15,6 +18,11 @@ const TaskDetailHeader: FC<Props> = ({ statusLabel, onClose }) => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
{onDelete ? (
|
||||||
|
<div className="size-8 rounded-full bg-white/60 flex items-center justify-center">
|
||||||
|
<TrashWithConfrim onDelete={onDelete} isLoading={isDeleting} color="#FF0000" />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<button type="button" onClick={onClose} className="size-8 rounded-full bg-white/60 flex items-center justify-center cursor-pointer" aria-label="بستن">
|
<button type="button" onClick={onClose} className="size-8 rounded-full bg-white/60 flex items-center justify-center cursor-pointer" aria-label="بستن">
|
||||||
<CloseCircle size={20} color="#292D32" />
|
<CloseCircle size={20} color="#292D32" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { type FC, useEffect, useState } from "react";
|
import { type FC, useEffect, useRef, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
import DefaulModal from "../../../../components/DefaulModal";
|
import DefaulModal from "../../../../components/DefaulModal";
|
||||||
|
import PageLoading from "../../../../components/PageLoading";
|
||||||
|
import { ErrorType } from "../../../../helpers/types";
|
||||||
|
import { useDeleteTask, useGetTaskDetail, useUpdateTask } from "../../task/hooks/useTaskData";
|
||||||
|
import type { TaskDetailType } from "../../task/types/TaskTypes";
|
||||||
import type { Task } from "../../types";
|
import type { Task } from "../../types";
|
||||||
import AttachmentList from "./attachment/List";
|
import AttachmentList from "./attachment/List";
|
||||||
import CheckLists from "./checklist/List";
|
import CheckLists from "./checklist/List";
|
||||||
@@ -16,37 +22,126 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailModal: FC<Props> = ({ open, task, statusLabel, onClose }) => {
|
const TaskDetailModal: FC<Props> = ({ open, task, statusLabel, onClose }) => {
|
||||||
|
const { t } = useTranslation("global");
|
||||||
const [activeTab, setActiveTab] = useState<TaskDetailTab | null>(null);
|
const [activeTab, setActiveTab] = useState<TaskDetailTab | null>(null);
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
|
const initialTitleRef = useRef("");
|
||||||
|
const initialDescriptionRef = useRef("");
|
||||||
|
|
||||||
|
const getTaskDetail = useGetTaskDetail(task?.id ?? "", open && !!task?.id);
|
||||||
|
const updateTask = useUpdateTask();
|
||||||
|
const deleteTask = useDeleteTask();
|
||||||
|
|
||||||
|
const taskDetail: TaskDetailType | undefined =
|
||||||
|
getTaskDetail.data?.data?.task ?? getTaskDetail.data?.data;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setActiveTab(null);
|
setActiveTab(null);
|
||||||
|
setTitle("");
|
||||||
setDescription("");
|
setDescription("");
|
||||||
|
initialTitleRef.current = "";
|
||||||
|
initialDescriptionRef.current = "";
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!taskDetail) return;
|
||||||
|
|
||||||
|
setTitle(taskDetail.title);
|
||||||
|
setDescription(taskDetail.description ?? "");
|
||||||
|
initialTitleRef.current = taskDetail.title;
|
||||||
|
initialDescriptionRef.current = taskDetail.description ?? "";
|
||||||
|
}, [taskDetail]);
|
||||||
|
|
||||||
const handleTabChange = (tab: TaskDetailTab) => {
|
const handleTabChange = (tab: TaskDetailTab) => {
|
||||||
setActiveTab((prev) => (prev === tab ? null : tab));
|
setActiveTab((prev) => (prev === tab ? null : tab));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (!task?.id) return;
|
||||||
|
|
||||||
|
deleteTask.mutate(
|
||||||
|
{ id: task.id, projectId: taskDetail?.projectId ?? "" },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("taskmanager.task_deleted"));
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error.response?.data?.error.message[0]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveField = (field: "title" | "description", value: string) => {
|
||||||
|
if (!task?.id) return;
|
||||||
|
|
||||||
|
const initialValue = field === "title" ? initialTitleRef.current : initialDescriptionRef.current;
|
||||||
|
if (value === initialValue) return;
|
||||||
|
|
||||||
|
updateTask.mutate(
|
||||||
|
{ id: task.id, params: { [field]: value } },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
if (field === "title") {
|
||||||
|
initialTitleRef.current = value;
|
||||||
|
} else {
|
||||||
|
initialDescriptionRef.current = value;
|
||||||
|
}
|
||||||
|
toast.success(t("success"));
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error.response?.data?.error.message[0]);
|
||||||
|
if (field === "title") {
|
||||||
|
setTitle(initialTitleRef.current);
|
||||||
|
} else {
|
||||||
|
setDescription(initialDescriptionRef.current);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (!task) return null;
|
if (!task) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaulModal open={open} close={onClose} width={680}>
|
<DefaulModal open={open} close={onClose} width={680}>
|
||||||
<TaskDetailHeader statusLabel={statusLabel} onClose={onClose} />
|
{getTaskDetail.isPending ? (
|
||||||
|
<PageLoading />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TaskDetailHeader
|
||||||
|
statusLabel={statusLabel}
|
||||||
|
onClose={onClose}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
isDeleting={deleteTask.isPending}
|
||||||
|
/>
|
||||||
|
|
||||||
<h2 className="mt-6 text-sm font-bold">{task.title}</h2>
|
<input
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
onBlur={() => saveField("title", title)}
|
||||||
|
className="mt-6 w-full text-sm font-bold bg-transparent outline-none"
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<TaskDetailToolbar activeTab={activeTab} onTabChange={handleTabChange} />
|
<TaskDetailToolbar activeTab={activeTab} onTabChange={handleTabChange} taskDetail={taskDetail} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TaskDetailDescription value={description} onChange={setDescription} />
|
<TaskDetailDescription
|
||||||
|
value={description}
|
||||||
|
onChange={setDescription}
|
||||||
|
onBlur={() => saveField("description", description)}
|
||||||
|
/>
|
||||||
|
|
||||||
<AttachmentList />
|
<AttachmentList attachments={taskDetail?.attachments} />
|
||||||
|
|
||||||
<CheckLists />
|
<CheckLists checklists={taskDetail?.checklists} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</DefaulModal>
|
</DefaulModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { type FC, useState } from "react";
|
import { type FC, useEffect, useState } from "react";
|
||||||
import TaskDetailAttachmentPopover from "./attachment/TaskDetailAttachmentPopover";
|
import TaskDetailAttachmentPopover from "./attachment/TaskDetailAttachmentPopover";
|
||||||
import { DEFAULT_DATE_RANGE } from "./date/constants";
|
import { DEFAULT_DATE_RANGE } from "./date/constants";
|
||||||
import TaskDetailDatePopover from "./date/TaskDetailDatePopover";
|
import TaskDetailDatePopover from "./date/TaskDetailDatePopover";
|
||||||
import type DateObject from "react-date-object";
|
import type DateObject from "react-date-object";
|
||||||
|
import DateObjectClass from "react-date-object";
|
||||||
|
import persian from "react-date-object/calendars/persian";
|
||||||
|
import persian_fa from "react-date-object/locales/persian_fa";
|
||||||
import { DEFAULT_CHECKLISTS } from "./checklist/constants";
|
import { DEFAULT_CHECKLISTS } from "./checklist/constants";
|
||||||
import TaskDetailChecklistPopover from "./checklist/TaskDetailChecklistPopover";
|
import TaskDetailChecklistPopover from "./checklist/TaskDetailChecklistPopover";
|
||||||
import type { TaskChecklist } from "./checklist/types";
|
import type { TaskChecklist } from "./checklist/types";
|
||||||
@@ -14,13 +17,21 @@ import TaskDetailMetadataPreview from "./TaskDetailMetadataPreview";
|
|||||||
import type { TaskDetailTab } from "./types";
|
import type { TaskDetailTab } from "./types";
|
||||||
import { DEFAULT_USERS } from "./users/constants";
|
import { DEFAULT_USERS } from "./users/constants";
|
||||||
import TaskDetailUsersPopover from "./users/TaskDetailUsersPopover";
|
import TaskDetailUsersPopover from "./users/TaskDetailUsersPopover";
|
||||||
|
import type { TaskDetailType } from "../../task/types/TaskTypes";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
activeTab: TaskDetailTab | null;
|
activeTab: TaskDetailTab | null;
|
||||||
onTabChange: (tab: TaskDetailTab) => void;
|
onTabChange: (tab: TaskDetailTab) => void;
|
||||||
|
taskDetail?: TaskDetailType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
const parseDate = (value?: string): DateObject | null => {
|
||||||
|
if (!value) return null;
|
||||||
|
const date = new DateObjectClass({ date: value, calendar: persian, locale: persian_fa });
|
||||||
|
return date.isValid ? date : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail }) => {
|
||||||
const [labelsView, setLabelsView] = useState<LabelsView>("list");
|
const [labelsView, setLabelsView] = useState<LabelsView>("list");
|
||||||
const [labels, setLabels] = useState<TaskLabel[]>(DEFAULT_LABELS);
|
const [labels, setLabels] = useState<TaskLabel[]>(DEFAULT_LABELS);
|
||||||
const [selectedLabelIds, setSelectedLabelIds] = useState<Set<string>>(new Set(["1"]));
|
const [selectedLabelIds, setSelectedLabelIds] = useState<Set<string>>(new Set(["1"]));
|
||||||
@@ -29,8 +40,36 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
|||||||
const [startDate, setStartDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.startDate);
|
const [startDate, setStartDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.startDate);
|
||||||
const [endDate, setEndDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.endDate);
|
const [endDate, setEndDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.endDate);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!taskDetail) return;
|
||||||
|
|
||||||
|
if (taskDetail.labels?.length) {
|
||||||
|
setLabels(taskDetail.labels);
|
||||||
|
setSelectedLabelIds(new Set(taskDetail.labels.map((label) => label.id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskDetail.users?.length) {
|
||||||
|
setSelectedUserIds(new Set(taskDetail.users.map((user) => user.id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskDetail.checklists?.length) {
|
||||||
|
setChecklists(taskDetail.checklists.map((checklist) => ({ id: checklist.id, title: checklist.title })));
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedStartDate = parseDate(taskDetail.startDate);
|
||||||
|
const parsedEndDate = parseDate(taskDetail.endDate);
|
||||||
|
if (parsedStartDate) setStartDate(parsedStartDate);
|
||||||
|
if (parsedEndDate) setEndDate(parsedEndDate);
|
||||||
|
}, [taskDetail]);
|
||||||
|
|
||||||
const selectedLabels = labels.filter((label) => selectedLabelIds.has(label.id));
|
const selectedLabels = labels.filter((label) => selectedLabelIds.has(label.id));
|
||||||
const selectedUsers = DEFAULT_USERS.filter((user) => selectedUserIds.has(user.id));
|
const users = taskDetail?.users?.length
|
||||||
|
? taskDetail.users.map((user) => ({
|
||||||
|
id: user.id,
|
||||||
|
name: `${user.firstName} ${user.lastName}`.trim(),
|
||||||
|
}))
|
||||||
|
: DEFAULT_USERS;
|
||||||
|
const selectedUsers = users.filter((user) => selectedUserIds.has(user.id));
|
||||||
const isLabelsOpen = activeTab === "labels";
|
const isLabelsOpen = activeTab === "labels";
|
||||||
const isUsersOpen = activeTab === "users";
|
const isUsersOpen = activeTab === "users";
|
||||||
const isChecklistOpen = activeTab === "checklist";
|
const isChecklistOpen = activeTab === "checklist";
|
||||||
@@ -129,7 +168,7 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
|||||||
}
|
}
|
||||||
usersPopover={
|
usersPopover={
|
||||||
isUsersOpen ? (
|
isUsersOpen ? (
|
||||||
<TaskDetailUsersPopover users={DEFAULT_USERS} selectedIds={selectedUserIds} onToggle={handleUserToggle} />
|
<TaskDetailUsersPopover users={users} selectedIds={selectedUserIds} onToggle={handleUserToggle} />
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
checklistPopover={
|
checklistPopover={
|
||||||
|
|||||||
@@ -2,29 +2,27 @@ import { type FC } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import AttachmentItem from "./AttachmentItem";
|
import AttachmentItem from "./AttachmentItem";
|
||||||
import type { TaskAttachment } from "./types";
|
import type { TaskAttachment } from "./types";
|
||||||
|
import type { TaskAttachmentType } from "../../../task/types/TaskTypes";
|
||||||
|
|
||||||
const AttachmentList: FC = () => {
|
type Props = {
|
||||||
|
attachments?: TaskAttachmentType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const AttachmentList: FC<Props> = ({ attachments = [] }) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
|
|
||||||
const attachments: TaskAttachment[] = [
|
const mappedAttachments: TaskAttachment[] = attachments.map((item) => ({
|
||||||
{
|
id: item.id,
|
||||||
id: "1",
|
title: item.title,
|
||||||
title: "file.pdf",
|
fileName: item.fileName,
|
||||||
fileName: "file.pdf",
|
url: item.url,
|
||||||
createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
createdAt: item.createdAt,
|
||||||
},
|
}));
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
title: "لینک طرح",
|
|
||||||
url: "https://example.com",
|
|
||||||
createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const files = attachments.filter((item) => item.fileName);
|
const files = mappedAttachments.filter((item) => item.fileName);
|
||||||
const links = attachments.filter((item) => item.url);
|
const links = mappedAttachments.filter((item) => item.url);
|
||||||
|
|
||||||
if (attachments.length === 0) return null;
|
if (mappedAttachments.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
|
|||||||
@@ -1,14 +1,26 @@
|
|||||||
import { Edit } from "iconsax-react";
|
import { Edit } from "iconsax-react";
|
||||||
import ProgressBar from "../../../../../components/ProgressBar";
|
import ProgressBar from "../../../../../components/ProgressBar";
|
||||||
import TrashWithConfrim from "../../../../../components/TrashWithConfrim";
|
import TrashWithConfrim from "../../../../../components/TrashWithConfrim";
|
||||||
|
import type { TaskChecklistType } from "../../../task/types/TaskTypes";
|
||||||
import ChecklistItem from "./ChecklistItem";
|
import ChecklistItem from "./ChecklistItem";
|
||||||
|
|
||||||
const CheckLists = () => {
|
type Props = {
|
||||||
const items = [
|
checklists?: TaskChecklistType[];
|
||||||
{ id: "1", title: "آیتم ۱", checked: true },
|
};
|
||||||
{ id: "2", title: "آیتم ۱", checked: true },
|
|
||||||
{ id: "3", title: "آیتم ۱", checked: true },
|
const CheckLists = ({ checklists = [] }: Props) => {
|
||||||
];
|
const items = checklists.flatMap((checklist) =>
|
||||||
|
checklist.items.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
checked: item.isDone,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const doneCount = items.filter((item) => item.checked).length;
|
||||||
|
const progress = items.length > 0 ? Math.round((doneCount / items.length) * 100) : 0;
|
||||||
|
|
||||||
|
if (checklists.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
@@ -19,8 +31,8 @@ const CheckLists = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 flex gap-2 items-center">
|
<div className="mt-4 flex gap-2 items-center">
|
||||||
<div className="text-xs shrink-0">۲۵٪</div>
|
<div className="text-xs shrink-0">{progress}٪</div>
|
||||||
<ProgressBar value={25} />
|
<ProgressBar value={progress} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 flex flex-col gap-2">
|
<div className="mt-4 flex flex-col gap-2">
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import * as api from "../service/TaskService";
|
import * as api from "../service/TaskService";
|
||||||
import { CreateTaskType } from "../types/TaskTypes";
|
import { CreateTaskType, UpdateTaskType } from "../types/TaskTypes";
|
||||||
|
|
||||||
|
export const useGetTaskDetail = (id: string, enabled = true) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["task-detail", id],
|
||||||
|
queryFn: () => api.getTaskDetail(id),
|
||||||
|
enabled: !!id && enabled,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useCreateTask = () => {
|
export const useCreateTask = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -12,3 +20,28 @@ export const useCreateTask = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUpdateTask = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({ id, params }: { id: string; params: UpdateTaskType }) => api.updateTask(id, params),
|
||||||
|
onSuccess: (_data, variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["task-detail", variables.id] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["project"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteTask = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({ id }: { id: string; projectId: string }) => api.deleteTask(id),
|
||||||
|
onSuccess: (_data, variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["task-detail", variables.id] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["project", variables.projectId] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["project"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
import axios from "../../../../config/axios";
|
import axios from "../../../../config/axios";
|
||||||
import { CreateTaskType } from "../types/TaskTypes";
|
import { CreateTaskType, GetTaskDetailResponse, UpdateTaskType } from "../types/TaskTypes";
|
||||||
|
|
||||||
export const createTask = async (params: CreateTaskType) => {
|
export const createTask = async (params: CreateTaskType) => {
|
||||||
const { data } = await axios.post(`/task-manager/tasks`, params);
|
const { data } = await axios.post(`/task-manager/tasks`, params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTaskDetail = async (id: string): Promise<GetTaskDetailResponse> => {
|
||||||
|
const { data } = await axios.get(`/task-manager/tasks/detail/${id}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateTask = async (id: string, params: UpdateTaskType) => {
|
||||||
|
const { data } = await axios.patch(`/task-manager/tasks/${id}`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteTask = async (id: string) => {
|
||||||
|
const { data } = await axios.delete(`/task-manager/tasks/${id}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { IResponse } from "../../../../types/response.types";
|
||||||
|
|
||||||
export type CreateTaskType = {
|
export type CreateTaskType = {
|
||||||
title: string;
|
title: string;
|
||||||
taskPhaseId: string;
|
taskPhaseId: string;
|
||||||
@@ -16,3 +18,58 @@ export type TaskItemType = {
|
|||||||
checklistDone?: number;
|
checklistDone?: number;
|
||||||
checklistTotal?: number;
|
checklistTotal?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TaskLabelType = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TaskUserType = {
|
||||||
|
id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TaskChecklistItemType = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
isDone: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TaskChecklistType = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
items: TaskChecklistItemType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TaskAttachmentType = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
fileName?: string;
|
||||||
|
url?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TaskDetailType = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
taskPhaseId: string;
|
||||||
|
order: number;
|
||||||
|
projectId: string;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
labels?: TaskLabelType[];
|
||||||
|
users?: TaskUserType[];
|
||||||
|
checklists?: TaskChecklistType[];
|
||||||
|
attachments?: TaskAttachmentType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateTaskType = Partial<
|
||||||
|
Pick<TaskDetailType, "title" | "description" | "taskPhaseId" | "order" | "startDate" | "endDate">
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface GetTaskDetailResponse extends IResponse<TaskDetailType> {
|
||||||
|
statusCode: number;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user