diff --git a/src/components/DeleteNameConfirmModal.tsx b/src/components/DeleteNameConfirmModal.tsx new file mode 100644 index 0000000..e758a91 --- /dev/null +++ b/src/components/DeleteNameConfirmModal.tsx @@ -0,0 +1,74 @@ +import { FC, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import Button from "./Button"; +import DefaulModal from "./DefaulModal"; +import Input from "./Input"; + +type Props = { + isOpen: boolean; + close: () => void; + onConfirm: () => void; + isLoading?: boolean; + itemName: string; + message?: string; +}; + +const DeleteNameConfirmModal: FC = ({ + isOpen, + close, + onConfirm, + isLoading = false, + itemName, + message, +}) => { + const { t } = useTranslation("global"); + const [typedName, setTypedName] = useState(""); + + useEffect(() => { + if (!isOpen) { + setTypedName(""); + } + }, [isOpen]); + + const isNameMatched = typedName.trim() === itemName.trim(); + + return ( + +
+
+ {message ?? t("taskmanager.delete_confirm_message", { name: itemName })} +
+ +
+ setTypedName(e.target.value)} + placeholder={t("taskmanager.delete_confirm_placeholder")} + className="text-center" + /> +
+ +
+
+
+
+ ); +}; + +export default DeleteNameConfirmModal; diff --git a/src/langs/fa.json b/src/langs/fa.json index bd99335..9c11fac 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -1020,6 +1020,9 @@ "select_date": "انتخاب تاریخ", "projects": "پروژه‌ها", "delete_project": "پاک کردن", + "project_deleted": "پروژه با موفقیت حذف شد", + "delete_confirm_message": "برای حذف «{{name}}»، نام آن را در کادر زیر وارد کنید.", + "delete_confirm_placeholder": "نام را وارد کنید", "add_new_column": "اضافه کردن ستون جدید", "add_column": "اضافه کردن", "column_name": "نام ستون", diff --git a/src/pages/taskmanager/project/List.tsx b/src/pages/taskmanager/project/List.tsx index 4a83010..3dd1bc8 100644 --- a/src/pages/taskmanager/project/List.tsx +++ b/src/pages/taskmanager/project/List.tsx @@ -1,30 +1,48 @@ import { FC, useState } from "react"; import { useParams } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { toast } from "../../../components/Toast"; +import DeleteNameConfirmModal from "../../../components/DeleteNameConfirmModal"; import PageLoading from "../../../components/PageLoading"; +import { ErrorType } from "../../../helpers/types"; import { useGetWorkspaceDetail } from "../workspace/hooks/useWorkspaceData"; import { WorkspaceDetailType } from "../workspace/types/WorkspaceTypes"; import ProjectGrid from "./components/ProjectGrid"; import ProjectListHeader from "./components/ProjectListHeader"; import WorkspaceInfoBanner from "./components/WorkspaceInfoBanner"; -import { useGetProjectsByWorkspace } from "./hooks/useProjectData"; +import { useDeleteProject, useGetProjectsByWorkspace } from "./hooks/useProjectData"; import type { ProjectItem } from "./types/ProjectTypes"; const List: FC = () => { + const { t } = useTranslation("global"); const { workspaceId = "" } = useParams(); const getProjects = useGetProjectsByWorkspace(workspaceId); const getWorkspaceDetail = useGetWorkspaceDetail(workspaceId); + const deleteProject = useDeleteProject(); const workspace: WorkspaceDetailType | undefined = getWorkspaceDetail.data?.data?.workspace ?? getWorkspaceDetail.data?.data; - const apiProjects: ProjectItem[] = + const projects: ProjectItem[] = getProjects.data?.data?.projects ?? getProjects.data?.data ?? []; - const [deletedIds, setDeletedIds] = useState([]); - const projects = apiProjects.filter((project) => !deletedIds.includes(project.id)); + const [deleteTarget, setDeleteTarget] = useState(null); - const handleDelete = (id: string) => { - setDeletedIds((prev) => [...prev, id]); + const handleConfirmDelete = () => { + if (!deleteTarget) return; + + deleteProject.mutate( + { id: deleteTarget.id, workspaceId }, + { + onSuccess: () => { + toast(t("taskmanager.project_deleted"), "success"); + setDeleteTarget(null); + }, + onError: (error: ErrorType) => { + toast(error.response?.data?.error.message[0], "error"); + }, + }, + ); }; const isLoading = getProjects.isPending || getWorkspaceDetail.isPending; @@ -40,8 +58,16 @@ const List: FC = () => { {isLoading ? ( ) : ( - + )} + + setDeleteTarget(null)} + onConfirm={handleConfirmDelete} + isLoading={deleteProject.isPending} + itemName={deleteTarget?.name ?? ""} + /> ); }; diff --git a/src/pages/taskmanager/project/components/ProjectCard.tsx b/src/pages/taskmanager/project/components/ProjectCard.tsx index e65c133..f770647 100644 --- a/src/pages/taskmanager/project/components/ProjectCard.tsx +++ b/src/pages/taskmanager/project/components/ProjectCard.tsx @@ -7,7 +7,7 @@ import ProjectCardMenu from "./ProjectCardMenu"; type Props = { project: ProjectItem; - onDelete: (id: string) => void; + onDelete: (project: ProjectItem) => void; }; const ProjectCard: FC = ({ project, onDelete }) => { @@ -28,7 +28,7 @@ const ProjectCard: FC = ({ project, onDelete }) => { {project.name} - onDelete(project.id)} /> + onDelete(project)} /> ); diff --git a/src/pages/taskmanager/project/components/ProjectGrid.tsx b/src/pages/taskmanager/project/components/ProjectGrid.tsx index 405208d..239c841 100644 --- a/src/pages/taskmanager/project/components/ProjectGrid.tsx +++ b/src/pages/taskmanager/project/components/ProjectGrid.tsx @@ -5,7 +5,7 @@ import ProjectCard from "./ProjectCard"; type Props = { projects: ProjectItem[]; - onDelete: (id: string) => void; + onDelete: (project: ProjectItem) => void; }; const ProjectGrid: FC = ({ projects, onDelete }) => { diff --git a/src/pages/taskmanager/project/hooks/useProjectData.ts b/src/pages/taskmanager/project/hooks/useProjectData.ts index 2f9aeb4..78fbf19 100644 --- a/src/pages/taskmanager/project/hooks/useProjectData.ts +++ b/src/pages/taskmanager/project/hooks/useProjectData.ts @@ -52,3 +52,16 @@ export const useUpdateProject = () => { }, }); }; + +export const useDeleteProject = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ id }: { id: string; workspaceId: string }) => api.deleteProject(id), + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["projects", variables.workspaceId], + }); + }, + }); +}; diff --git a/src/pages/taskmanager/project/service/ProjectService.ts b/src/pages/taskmanager/project/service/ProjectService.ts index 3df56ea..e216854 100644 --- a/src/pages/taskmanager/project/service/ProjectService.ts +++ b/src/pages/taskmanager/project/service/ProjectService.ts @@ -25,3 +25,8 @@ export const updateProject = async (id: string, params: UpdateProjectType) => { const { data } = await axios.patch(`/task-manager/projects/${id}`, params); return data; }; + +export const deleteProject = async (id: string) => { + const { data } = await axios.delete(`/task-manager/projects/${id}`); + return data; +}; diff --git a/src/pages/taskmanager/workspace/List.tsx b/src/pages/taskmanager/workspace/List.tsx index e90ad81..7dd1943 100644 --- a/src/pages/taskmanager/workspace/List.tsx +++ b/src/pages/taskmanager/workspace/List.tsx @@ -1,12 +1,12 @@ -import { Add, Edit } from "iconsax-react"; -import { FC } from "react"; +import { Add, Edit, Trash } from "iconsax-react"; +import { FC, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; -import { toast } from '../../../components/Toast'; +import { toast } from "../../../components/Toast"; import Button from "../../../components/Button"; +import DeleteNameConfirmModal from "../../../components/DeleteNameConfirmModal"; import PageLoading from "../../../components/PageLoading"; import Td from "../../../components/Td"; -import TrashWithConfrim from "../../../components/TrashWithConfrim"; import { Pages } from "../../../config/Pages"; import { ErrorType } from "../../../helpers/types"; import { useDeleteWorkspace, useGetWorkspaces } from "./hooks/useWorkspaceData"; @@ -16,16 +16,20 @@ const WorkspaceList: FC = () => { const { t } = useTranslation("global"); const getWorkspaces = useGetWorkspaces(); const deleteWorkspace = useDeleteWorkspace(); + const [deleteTarget, setDeleteTarget] = useState(null); const workspaces: WorkspaceItemType[] = getWorkspaces.data?.data?.workspaces ?? getWorkspaces.data?.data ?? []; - const handleDelete = (id: string) => { - deleteWorkspace.mutate(id, { + const handleConfirmDelete = () => { + if (!deleteTarget) return; + + deleteWorkspace.mutate(deleteTarget.id, { onSuccess: () => { - toast(t("taskmanager.workspace_deleted"), 'success'); + toast(t("taskmanager.workspace_deleted"), "success"); + setDeleteTarget(null); }, onError: (error: ErrorType) => { - toast(error.response?.data?.error.message[0], 'error'); + toast(error.response?.data?.error.message[0], "error"); }, }); }; @@ -94,9 +98,11 @@ const WorkspaceList: FC = () => { color="#8C90A3" /> - handleDelete(item.id)} - isLoading={deleteWorkspace.isPending} + setDeleteTarget(item)} + color="#888" + size={20} + className="cursor-pointer" /> @@ -106,6 +112,14 @@ const WorkspaceList: FC = () => { )} + + setDeleteTarget(null)} + onConfirm={handleConfirmDelete} + isLoading={deleteWorkspace.isPending} + itemName={deleteTarget?.name ?? ""} + /> ); };