confrim before delete a workspace or a project
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-07-20 11:37:43 +03:30
parent d3fd21177b
commit a3ce347f02
8 changed files with 156 additions and 21 deletions
+74
View File
@@ -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<Props> = ({
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 (
<DefaulModal
open={isOpen}
close={close}
title_header={t("confrim.subject")}
isHeader
>
<div className="mt-6">
<div className="text-sm text-center">
{message ?? t("taskmanager.delete_confirm_message", { name: itemName })}
</div>
<div className="mt-5">
<Input
value={typedName}
onChange={(e) => setTypedName(e.target.value)}
placeholder={t("taskmanager.delete_confirm_placeholder")}
className="text-center"
/>
</div>
<div className="flex gap-4 justify-center mt-10">
<Button
label={t("confrim.yes")}
onClick={onConfirm}
isLoading={isLoading}
disabled={!isNameMatched}
/>
<Button
label={t("confrim.cancel")}
className="bg-transparent text-black border border-primary"
onClick={close}
/>
</div>
</div>
</DefaulModal>
);
};
export default DeleteNameConfirmModal;
+3
View File
@@ -1020,6 +1020,9 @@
"select_date": "انتخاب تاریخ", "select_date": "انتخاب تاریخ",
"projects": "پروژه‌ها", "projects": "پروژه‌ها",
"delete_project": "پاک کردن", "delete_project": "پاک کردن",
"project_deleted": "پروژه با موفقیت حذف شد",
"delete_confirm_message": "برای حذف «{{name}}»، نام آن را در کادر زیر وارد کنید.",
"delete_confirm_placeholder": "نام را وارد کنید",
"add_new_column": "اضافه کردن ستون جدید", "add_new_column": "اضافه کردن ستون جدید",
"add_column": "اضافه کردن", "add_column": "اضافه کردن",
"column_name": "نام ستون", "column_name": "نام ستون",
+33 -7
View File
@@ -1,30 +1,48 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import { useParams } from "react-router-dom"; 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 PageLoading from "../../../components/PageLoading";
import { ErrorType } from "../../../helpers/types";
import { useGetWorkspaceDetail } from "../workspace/hooks/useWorkspaceData"; import { useGetWorkspaceDetail } from "../workspace/hooks/useWorkspaceData";
import { WorkspaceDetailType } from "../workspace/types/WorkspaceTypes"; import { WorkspaceDetailType } from "../workspace/types/WorkspaceTypes";
import ProjectGrid from "./components/ProjectGrid"; import ProjectGrid from "./components/ProjectGrid";
import ProjectListHeader from "./components/ProjectListHeader"; import ProjectListHeader from "./components/ProjectListHeader";
import WorkspaceInfoBanner from "./components/WorkspaceInfoBanner"; import WorkspaceInfoBanner from "./components/WorkspaceInfoBanner";
import { useGetProjectsByWorkspace } from "./hooks/useProjectData"; import { useDeleteProject, useGetProjectsByWorkspace } from "./hooks/useProjectData";
import type { ProjectItem } from "./types/ProjectTypes"; import type { ProjectItem } from "./types/ProjectTypes";
const List: FC = () => { const List: FC = () => {
const { t } = useTranslation("global");
const { workspaceId = "" } = useParams(); const { workspaceId = "" } = useParams();
const getProjects = useGetProjectsByWorkspace(workspaceId); const getProjects = useGetProjectsByWorkspace(workspaceId);
const getWorkspaceDetail = useGetWorkspaceDetail(workspaceId); const getWorkspaceDetail = useGetWorkspaceDetail(workspaceId);
const deleteProject = useDeleteProject();
const workspace: WorkspaceDetailType | undefined = const workspace: WorkspaceDetailType | undefined =
getWorkspaceDetail.data?.data?.workspace ?? getWorkspaceDetail.data?.data; getWorkspaceDetail.data?.data?.workspace ?? getWorkspaceDetail.data?.data;
const apiProjects: ProjectItem[] = const projects: ProjectItem[] =
getProjects.data?.data?.projects ?? getProjects.data?.data ?? []; getProjects.data?.data?.projects ?? getProjects.data?.data ?? [];
const [deletedIds, setDeletedIds] = useState<string[]>([]); const [deleteTarget, setDeleteTarget] = useState<ProjectItem | null>(null);
const projects = apiProjects.filter((project) => !deletedIds.includes(project.id));
const handleDelete = (id: string) => { const handleConfirmDelete = () => {
setDeletedIds((prev) => [...prev, id]); 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; const isLoading = getProjects.isPending || getWorkspaceDetail.isPending;
@@ -40,8 +58,16 @@ const List: FC = () => {
{isLoading ? ( {isLoading ? (
<PageLoading /> <PageLoading />
) : ( ) : (
<ProjectGrid projects={projects} onDelete={handleDelete} /> <ProjectGrid projects={projects} onDelete={setDeleteTarget} />
)} )}
<DeleteNameConfirmModal
isOpen={!!deleteTarget}
close={() => setDeleteTarget(null)}
onConfirm={handleConfirmDelete}
isLoading={deleteProject.isPending}
itemName={deleteTarget?.name ?? ""}
/>
</div> </div>
); );
}; };
@@ -7,7 +7,7 @@ import ProjectCardMenu from "./ProjectCardMenu";
type Props = { type Props = {
project: ProjectItem; project: ProjectItem;
onDelete: (id: string) => void; onDelete: (project: ProjectItem) => void;
}; };
const ProjectCard: FC<Props> = ({ project, onDelete }) => { const ProjectCard: FC<Props> = ({ project, onDelete }) => {
@@ -28,7 +28,7 @@ const ProjectCard: FC<Props> = ({ project, onDelete }) => {
{project.name} {project.name}
</Link> </Link>
<ProjectCardMenu onEdit={handleEdit} onDelete={() => onDelete(project.id)} /> <ProjectCardMenu onEdit={handleEdit} onDelete={() => onDelete(project)} />
</div> </div>
</div> </div>
); );
@@ -5,7 +5,7 @@ import ProjectCard from "./ProjectCard";
type Props = { type Props = {
projects: ProjectItem[]; projects: ProjectItem[];
onDelete: (id: string) => void; onDelete: (project: ProjectItem) => void;
}; };
const ProjectGrid: FC<Props> = ({ projects, onDelete }) => { const ProjectGrid: FC<Props> = ({ projects, onDelete }) => {
@@ -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],
});
},
});
};
@@ -25,3 +25,8 @@ export const updateProject = async (id: string, params: UpdateProjectType) => {
const { data } = await axios.patch(`/task-manager/projects/${id}`, params); const { data } = await axios.patch(`/task-manager/projects/${id}`, params);
return data; return data;
}; };
export const deleteProject = async (id: string) => {
const { data } = await axios.delete(`/task-manager/projects/${id}`);
return data;
};
+25 -11
View File
@@ -1,12 +1,12 @@
import { Add, Edit } from "iconsax-react"; import { Add, Edit, Trash } from "iconsax-react";
import { FC } from "react"; import { FC, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { toast } from '../../../components/Toast'; import { toast } from "../../../components/Toast";
import Button from "../../../components/Button"; import Button from "../../../components/Button";
import DeleteNameConfirmModal from "../../../components/DeleteNameConfirmModal";
import PageLoading from "../../../components/PageLoading"; import PageLoading from "../../../components/PageLoading";
import Td from "../../../components/Td"; import Td from "../../../components/Td";
import TrashWithConfrim from "../../../components/TrashWithConfrim";
import { Pages } from "../../../config/Pages"; import { Pages } from "../../../config/Pages";
import { ErrorType } from "../../../helpers/types"; import { ErrorType } from "../../../helpers/types";
import { useDeleteWorkspace, useGetWorkspaces } from "./hooks/useWorkspaceData"; import { useDeleteWorkspace, useGetWorkspaces } from "./hooks/useWorkspaceData";
@@ -16,16 +16,20 @@ const WorkspaceList: FC = () => {
const { t } = useTranslation("global"); const { t } = useTranslation("global");
const getWorkspaces = useGetWorkspaces(); const getWorkspaces = useGetWorkspaces();
const deleteWorkspace = useDeleteWorkspace(); const deleteWorkspace = useDeleteWorkspace();
const [deleteTarget, setDeleteTarget] = useState<WorkspaceItemType | null>(null);
const workspaces: WorkspaceItemType[] = getWorkspaces.data?.data?.workspaces ?? getWorkspaces.data?.data ?? []; const workspaces: WorkspaceItemType[] = getWorkspaces.data?.data?.workspaces ?? getWorkspaces.data?.data ?? [];
const handleDelete = (id: string) => { const handleConfirmDelete = () => {
deleteWorkspace.mutate(id, { if (!deleteTarget) return;
deleteWorkspace.mutate(deleteTarget.id, {
onSuccess: () => { onSuccess: () => {
toast(t("taskmanager.workspace_deleted"), 'success'); toast(t("taskmanager.workspace_deleted"), "success");
setDeleteTarget(null);
}, },
onError: (error: ErrorType) => { 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" color="#8C90A3"
/> />
</Link> </Link>
<TrashWithConfrim <Trash
onDelete={() => handleDelete(item.id)} onClick={() => setDeleteTarget(item)}
isLoading={deleteWorkspace.isPending} color="#888"
size={20}
className="cursor-pointer"
/> />
</div> </div>
</Td> </Td>
@@ -106,6 +112,14 @@ const WorkspaceList: FC = () => {
</table> </table>
</div> </div>
)} )}
<DeleteNameConfirmModal
isOpen={!!deleteTarget}
close={() => setDeleteTarget(null)}
onConfirm={handleConfirmDelete}
isLoading={deleteWorkspace.isPending}
itemName={deleteTarget?.name ?? ""}
/>
</div> </div>
); );
}; };