From f466f03feaebbb90b15ca70ae6b15331300173ed Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Thu, 26 Feb 2026 09:24:41 +0330 Subject: [PATCH] crud role --- src/config/Paths.tsx | 5 + src/pages/role/Create.tsx | 149 +++++++++++++++++++++ src/pages/role/List.tsx | 87 ++++++++++++ src/pages/role/Update.tsx | 186 ++++++++++++++++++++++++++ src/pages/role/hooks/useRoleData.ts | 64 +++++++++ src/pages/role/service/RoleService.ts | 41 ++++++ src/pages/role/types/Types.ts | 30 +++++ src/router/MainRouter.tsx | 7 + src/shared/Sidebar.tsx | 8 ++ 9 files changed, 577 insertions(+) create mode 100644 src/pages/role/Create.tsx create mode 100644 src/pages/role/List.tsx create mode 100644 src/pages/role/Update.tsx create mode 100644 src/pages/role/hooks/useRoleData.ts create mode 100644 src/pages/role/service/RoleService.ts create mode 100644 src/pages/role/types/Types.ts diff --git a/src/config/Paths.tsx b/src/config/Paths.tsx index 9a023de..8db4132 100644 --- a/src/config/Paths.tsx +++ b/src/config/Paths.tsx @@ -31,6 +31,11 @@ export const Paths = { create: '/admin/create', update: '/admin/update/', }, + role: { + list: '/admin/role', + create: '/admin/role/create', + update: '/admin/role/update/', + }, formBuilder: { list: '/form-builder/list/', create: '/form-builder/create/', diff --git a/src/pages/role/Create.tsx b/src/pages/role/Create.tsx new file mode 100644 index 0000000..165ad13 --- /dev/null +++ b/src/pages/role/Create.tsx @@ -0,0 +1,149 @@ +import { useFormik } from "formik"; +import { type FC } from "react"; +import type { CreateRoleType } from "./types/Types"; +import * as Yup from "yup"; +import Button from "@/components/Button"; +import { AddSquare } from "iconsax-react"; +import Input from "@/components/Input"; +import { useCreateRole, useGetPermissions } from "./hooks/useRoleData"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import { extractErrorMessage } from "@/config/func"; +import { Paths } from "@/config/Paths"; +import { Checkbox } from "@/components/ui/checkbox"; +import { clx } from "@/helpers/utils"; + +const CreateRole: FC = () => { + const navigate = useNavigate(); + const { mutate: createRole, isPending } = useCreateRole(); + const { data: permissionsData } = useGetPermissions(); + + const formik = useFormik({ + initialValues: { + name: "", + title: "", + permissionIds: [], + }, + validationSchema: Yup.object({ + name: Yup.string() + .required("نام اجباری می باشد") + .min(2, "نام باید حداقل 2 کاراکتر باشد"), + title: Yup.string(), + permissionIds: Yup.array().of(Yup.string()), + }), + onSubmit: (values) => { + createRole(values, { + onSuccess: () => { + toast.success("نقش با موفقیت ایجاد شد"); + navigate(Paths.role.list); + }, + onError: (error) => { + toast.error(extractErrorMessage(error)); + }, + }); + }, + }); + + const permissions = permissionsData?.data ?? []; + + const togglePermission = (permissionId: string) => { + const current = formik.values.permissionIds; + const next = current.includes(permissionId) + ? current.filter((id) => id !== permissionId) + : [...current, permissionId]; + formik.setFieldValue("permissionIds", next); + }; + + const selectAll = () => { + if (formik.values.permissionIds.length === permissions.length) { + formik.setFieldValue("permissionIds", []); + } else { + formik.setFieldValue( + "permissionIds", + permissions.map((p) => p.id) + ); + } + }; + + return ( +
+
+

ایجاد نقش جدید

+ +
+ +
+
+ + + +
+ +
+
+ + +
+
+ {permissions.map((p) => ( + + ))} +
+
+
+
+ ); +}; + +export default CreateRole; diff --git a/src/pages/role/List.tsx b/src/pages/role/List.tsx new file mode 100644 index 0000000..baab605 --- /dev/null +++ b/src/pages/role/List.tsx @@ -0,0 +1,87 @@ +import { type FC } from "react"; +import { useGetRoles, useDeleteRole } from "./hooks/useRoleData"; +import Table from "@/components/Table"; +import moment from "moment-jalaali"; +import { Link } from "react-router-dom"; +import { Paths } from "@/config/Paths"; +import Button from "@/components/Button"; +import { AddSquare, Eye } from "iconsax-react"; +import TrashWithConfrim from "@/components/TrashWithConfrim"; +import type { RoleItemType } from "./types/Types"; + +const RoleList: FC = () => { + const { data, isLoading } = useGetRoles(); + const deleteRoleMutation = useDeleteRole(); + + return ( +
+
+

نقش‌ها

+ + + +
+ + + columns={[ + { + key: "name", + title: "نام", + }, + { + key: "title", + title: "عنوان", + }, + { + key: "isSystem", + title: "سیستمی", + render: (item) => ( +
{item.isSystem ? "بله" : "خیر"}
+ ), + }, + { + key: "permissionsCount", + title: "تعداد دسترسی‌ها", + render: (item) => ( +
{item.permissions?.length ?? 0}
+ ), + }, + { + key: "createdAt", + title: "تاریخ ثبت", + render: (item) => ( +
{moment(item.createdAt).format("jYYYY-jMM-jDD")}
+ ), + }, + { + key: "actions", + title: "عملیات", + render: (item) => ( +
+ + + + {!item.isSystem && ( + deleteRoleMutation.mutate(item.id)} + isloading={deleteRoleMutation.isPending} + colorIcon="#ef4444" + /> + )} +
+ ), + }, + ]} + data={data?.data} + isLoading={isLoading} + /> +
+ ); +}; + +export default RoleList; diff --git a/src/pages/role/Update.tsx b/src/pages/role/Update.tsx new file mode 100644 index 0000000..ae8985c --- /dev/null +++ b/src/pages/role/Update.tsx @@ -0,0 +1,186 @@ +import { useFormik } from "formik"; +import { type FC, useEffect } from "react"; +import type { CreateRoleType } from "./types/Types"; +import * as Yup from "yup"; +import Button from "@/components/Button"; +import { Edit } from "iconsax-react"; +import Input from "@/components/Input"; +import { useGetRoleById, useGetPermissions, useUpdateRole } from "./hooks/useRoleData"; +import { useNavigate, useParams } from "react-router-dom"; +import { toast } from "react-toastify"; +import { extractErrorMessage } from "@/config/func"; +import { Paths } from "@/config/Paths"; +import { Checkbox } from "@/components/ui/checkbox"; +import { clx } from "@/helpers/utils"; + +const UpdateRole: FC = () => { + const navigate = useNavigate(); + const { id: roleId } = useParams<{ id: string }>(); + const { mutate: update, isPending: isUpdating } = useUpdateRole(); + const { data: permissionsData } = useGetPermissions(); + const { data: roleData, isLoading: isLoadingRole } = useGetRoleById(roleId); + + const formik = useFormik({ + initialValues: { + name: "", + title: "", + permissionIds: [], + }, + validationSchema: Yup.object({ + name: Yup.string() + .required("نام اجباری می باشد") + .min(2, "نام باید حداقل 2 کاراکتر باشد"), + title: Yup.string(), + permissionIds: Yup.array().of(Yup.string()), + }), + onSubmit: (values) => { + if (!roleId) return; + + update( + { roleId, params: values }, + { + onSuccess: () => { + toast.success("نقش با موفقیت ویرایش شد"); + navigate(Paths.role.list); + }, + onError: (error) => { + toast.error(extractErrorMessage(error)); + }, + } + ); + }, + }); + + useEffect(() => { + if (roleData?.data) { + const role = roleData.data; + formik.setValues({ + name: role.name ?? "", + title: role.title ?? "", + permissionIds: role.permissions?.map((p) => p.id) ?? [], + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [roleData]); + + const permissions = permissionsData?.data ?? []; + + const togglePermission = (permissionId: string) => { + const current = formik.values.permissionIds; + const next = current.includes(permissionId) + ? current.filter((id) => id !== permissionId) + : [...current, permissionId]; + formik.setFieldValue("permissionIds", next); + }; + + const selectAll = () => { + if (formik.values.permissionIds.length === permissions.length) { + formik.setFieldValue("permissionIds", []); + } else { + formik.setFieldValue( + "permissionIds", + permissions.map((p) => p.id) + ); + } + }; + + if (isLoadingRole) { + return ( +
+
+
در حال بارگذاری...
+
+
+ ); + } + + if (!roleData?.data) { + return ( +
+
+
نقش یافت نشد
+
+
+ ); + } + + return ( +
+
+

ویرایش نقش

+ +
+ +
+
+ + + +
+ +
+
+ + +
+
+ {permissions.map((p) => ( + + ))} +
+
+
+
+ ); +}; + +export default UpdateRole; diff --git a/src/pages/role/hooks/useRoleData.ts b/src/pages/role/hooks/useRoleData.ts new file mode 100644 index 0000000..acf13a6 --- /dev/null +++ b/src/pages/role/hooks/useRoleData.ts @@ -0,0 +1,64 @@ +import * as api from "../service/RoleService"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import type { CreateRoleType } from "../types/Types"; + +export const useGetRoles = () => { + return useQuery({ + queryKey: ["roles"], + queryFn: api.getRoles, + }); +}; + +export const useGetPermissions = () => { + return useQuery({ + queryKey: ["roles", "permissions"], + queryFn: api.getPermissions, + }); +}; + +export const useGetRoleById = (roleId: string | undefined) => { + return useQuery({ + queryKey: ["role", roleId], + queryFn: () => api.getRoleById(roleId!), + enabled: !!roleId, + }); +}; + +export const useCreateRole = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: api.createRole, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["roles"] }); + }, + }); +}; + +export const useUpdateRole = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + roleId, + params, + }: { + roleId: string; + params: CreateRoleType; + }) => api.updateRole(roleId, params), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["roles"] }); + }, + }); +}; + +export const useDeleteRole = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: api.deleteRole, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["roles"] }); + }, + }); +}; diff --git a/src/pages/role/service/RoleService.ts b/src/pages/role/service/RoleService.ts new file mode 100644 index 0000000..45c684d --- /dev/null +++ b/src/pages/role/service/RoleService.ts @@ -0,0 +1,41 @@ +import axios from "@/config/axios"; +import type { + RolesResponseType, + PermissionsResponseType, + RoleDetailResponseType, + CreateRoleType, +} from "../types/Types"; + +export const getRoles = async () => { + const { data } = await axios.get("/admin/roles"); + return data; +}; + +export const getPermissions = async () => { + const { data } = await axios.get( + "/admin/roles/permissions" + ); + return data; +}; + +export const getRoleById = async (roleId: string) => { + const { data } = await axios.get( + `/admin/roles/${roleId}` + ); + return data; +}; + +export const createRole = async (params: CreateRoleType) => { + const { data } = await axios.post("/admin/role", params); + return data; +}; + +export const updateRole = async (roleId: string, params: CreateRoleType) => { + const { data } = await axios.patch(`/admin/roles/${roleId}`, params); + return data; +}; + +export const deleteRole = async (roleId: string) => { + const { data } = await axios.delete(`/admin/roles/${roleId}`); + return data; +}; diff --git a/src/pages/role/types/Types.ts b/src/pages/role/types/Types.ts new file mode 100644 index 0000000..d130eb4 --- /dev/null +++ b/src/pages/role/types/Types.ts @@ -0,0 +1,30 @@ +import type { RowDataType } from "@/components/types/TableTypes"; +import type { BaseResponse } from "@/shared/types/Types"; + +export interface PermissionItemType { + id: string; + createdAt: string; + deletedAt: string | null; + name: string; + title: string; +} + +export interface RoleItemType extends RowDataType { + id: string; + createdAt: string; + deletedAt: string | null; + name: string; + title: string; + isSystem: boolean; + permissions: PermissionItemType[]; +} + +export type RolesResponseType = BaseResponse; +export type PermissionsResponseType = BaseResponse; +export type RoleDetailResponseType = BaseResponse; + +export type CreateRoleType = { + name: string; + title: string; + permissionIds: string[]; +}; diff --git a/src/router/MainRouter.tsx b/src/router/MainRouter.tsx index bc85e15..9341164 100644 --- a/src/router/MainRouter.tsx +++ b/src/router/MainRouter.tsx @@ -48,6 +48,9 @@ import UpdateUser from "@/pages/user/Update"; import AdminList from "@/pages/admin/List"; import CreateAdmin from "@/pages/admin/Create"; import EditAdmin from "@/pages/admin/Update"; +import RoleList from "@/pages/role/List"; +import CreateRole from "@/pages/role/Create"; +import UpdateRole from "@/pages/role/Update"; import RequestDetail from "@/pages/requests/Detail"; import ConvertToOrders from "@/pages/order/ConvertToOrders"; import OrderPrint from "@/pages/order/Print"; @@ -112,6 +115,10 @@ const MainRouter: FC = () => { } /> } /> + } /> + } /> + } /> + } /> } /> } /> diff --git a/src/shared/Sidebar.tsx b/src/shared/Sidebar.tsx index 713ba96..5bb6b6e 100644 --- a/src/shared/Sidebar.tsx +++ b/src/shared/Sidebar.tsx @@ -13,6 +13,7 @@ import { People, Printer, Receipt21, + ShieldSecurity, Teacher, User, } from 'iconsax-react'; @@ -115,6 +116,13 @@ const SideBar: FC = () => { isActive={isActive('admin')} link={Paths.admin.list} /> + + } + title={'نقش‌ها'} + isActive={isActive('role')} + link={Paths.role.list} + />
{t('sidebar.other')}