crud role
This commit is contained in:
@@ -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/',
|
||||
|
||||
@@ -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<CreateRoleType>({
|
||||
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 (
|
||||
<div className="mt-5">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-lg font-light">ایجاد نقش جدید</h1>
|
||||
<Button
|
||||
className="w-fit px-6"
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={isPending}
|
||||
>
|
||||
<div className="flex gap-1.5">
|
||||
<AddSquare size={18} color="black" />
|
||||
<div className="text-[13px] font-light">ایجاد نقش</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 bg-white p-6 rounded-3xl flex-1">
|
||||
<div className="rowTwoInput">
|
||||
<Input
|
||||
label="نام"
|
||||
placeholder="نام را وارد کنید"
|
||||
{...formik.getFieldProps("name")}
|
||||
error_text={
|
||||
formik.touched.name && formik.errors.name ? formik.errors.name : ""
|
||||
}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="عنوان"
|
||||
placeholder="عنوان را وارد کنید"
|
||||
{...formik.getFieldProps("title")}
|
||||
error_text={
|
||||
formik.touched.title && formik.errors.title
|
||||
? formik.errors.title
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
دسترسیها
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={selectAll}
|
||||
className="text-xs text-primary hover:underline"
|
||||
>
|
||||
{formik.values.permissionIds.length === permissions.length
|
||||
? "حذف همه"
|
||||
: "انتخاب همه"}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className={clx(
|
||||
"border border-gray-200 rounded-xl p-4 max-h-64 overflow-y-auto",
|
||||
"flex gap-6 flex-wrap"
|
||||
)}
|
||||
>
|
||||
{permissions.map((p) => (
|
||||
<label
|
||||
key={p.id}
|
||||
className="flex items-center gap-2 cursor-pointer hover:bg-gray-50 p-2 rounded-lg"
|
||||
>
|
||||
<Checkbox
|
||||
checked={formik.values.permissionIds.includes(p.id)}
|
||||
onCheckedChange={() => togglePermission(p.id)}
|
||||
/>
|
||||
<span className="text-sm">
|
||||
{p.title || p.name}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateRole;
|
||||
@@ -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 (
|
||||
<div className="mt-5">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-lg font-light">نقشها</h1>
|
||||
<Link to={Paths.role.create}>
|
||||
<Button className="w-fit px-6">
|
||||
<div className="flex gap-1.5">
|
||||
<AddSquare size={18} color="black" />
|
||||
<div className="text-[13px] font-light">نقش جدید</div>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Table<RoleItemType>
|
||||
columns={[
|
||||
{
|
||||
key: "name",
|
||||
title: "نام",
|
||||
},
|
||||
{
|
||||
key: "title",
|
||||
title: "عنوان",
|
||||
},
|
||||
{
|
||||
key: "isSystem",
|
||||
title: "سیستمی",
|
||||
render: (item) => (
|
||||
<div>{item.isSystem ? "بله" : "خیر"}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "permissionsCount",
|
||||
title: "تعداد دسترسیها",
|
||||
render: (item) => (
|
||||
<div>{item.permissions?.length ?? 0}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "createdAt",
|
||||
title: "تاریخ ثبت",
|
||||
render: (item) => (
|
||||
<div>{moment(item.createdAt).format("jYYYY-jMM-jDD")}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
title: "عملیات",
|
||||
render: (item) => (
|
||||
<div className="flex gap-2">
|
||||
<Link to={`${Paths.role.update}${item.id}`}>
|
||||
<Eye size={20} color="black" />
|
||||
</Link>
|
||||
{!item.isSystem && (
|
||||
<TrashWithConfrim
|
||||
onDelete={() => deleteRoleMutation.mutate(item.id)}
|
||||
isloading={deleteRoleMutation.isPending}
|
||||
colorIcon="#ef4444"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
data={data?.data}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoleList;
|
||||
@@ -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<CreateRoleType>({
|
||||
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 (
|
||||
<div className="mt-5">
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="text-lg">در حال بارگذاری...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!roleData?.data) {
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="text-lg text-red-500">نقش یافت نشد</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-lg font-light">ویرایش نقش</h1>
|
||||
<Button
|
||||
className="w-fit px-6"
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={isUpdating}
|
||||
>
|
||||
<div className="flex gap-1.5">
|
||||
<Edit size={18} color="black" />
|
||||
<div className="text-[13px] font-light">ویرایش نقش</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 bg-white p-6 rounded-3xl flex-1">
|
||||
<div className="rowTwoInput">
|
||||
<Input
|
||||
label="نام"
|
||||
placeholder="نام را وارد کنید"
|
||||
{...formik.getFieldProps("name")}
|
||||
error_text={
|
||||
formik.touched.name && formik.errors.name ? formik.errors.name : ""
|
||||
}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="عنوان"
|
||||
placeholder="عنوان را وارد کنید"
|
||||
{...formik.getFieldProps("title")}
|
||||
error_text={
|
||||
formik.touched.title && formik.errors.title
|
||||
? formik.errors.title
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
دسترسیها
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={selectAll}
|
||||
className="text-xs text-primary hover:underline"
|
||||
>
|
||||
{formik.values.permissionIds.length === permissions.length
|
||||
? "حذف همه"
|
||||
: "انتخاب همه"}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className={clx(
|
||||
"border border-gray-200 rounded-xl p-4 max-h-64 overflow-y-auto",
|
||||
"flex gap-6 flex-wrap"
|
||||
)}
|
||||
>
|
||||
{permissions.map((p) => (
|
||||
<label
|
||||
key={p.id}
|
||||
className="flex items-center gap-2 cursor-pointer hover:bg-gray-50 p-2 rounded-lg"
|
||||
>
|
||||
<Checkbox
|
||||
checked={formik.values.permissionIds.includes(p.id)}
|
||||
onCheckedChange={() => togglePermission(p.id)}
|
||||
/>
|
||||
<span className="text-sm">{p.title || p.name}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateRole;
|
||||
@@ -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"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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<RolesResponseType>("/admin/roles");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getPermissions = async () => {
|
||||
const { data } = await axios.get<PermissionsResponseType>(
|
||||
"/admin/roles/permissions"
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getRoleById = async (roleId: string) => {
|
||||
const { data } = await axios.get<RoleDetailResponseType>(
|
||||
`/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;
|
||||
};
|
||||
@@ -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<RoleItemType[]>;
|
||||
export type PermissionsResponseType = BaseResponse<PermissionItemType[]>;
|
||||
export type RoleDetailResponseType = BaseResponse<RoleItemType>;
|
||||
|
||||
export type CreateRoleType = {
|
||||
name: string;
|
||||
title: string;
|
||||
permissionIds: string[];
|
||||
};
|
||||
@@ -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 = () => {
|
||||
<Route path={Paths.admin.create} element={<CreateAdmin />} />
|
||||
<Route path={Paths.admin.update + ":id"} element={<EditAdmin />} />
|
||||
|
||||
<Route path={Paths.role.list} element={<RoleList />} />
|
||||
<Route path={Paths.role.create} element={<CreateRole />} />
|
||||
<Route path={Paths.role.update + ":id"} element={<UpdateRole />} />
|
||||
|
||||
<Route path={Paths.features.list} element={<FeaturesList />} />
|
||||
<Route path={Paths.features.create} element={<CreateFeature />} />
|
||||
<Route path={Paths.service.print} element={<PrintService />} />
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
<SideBarItem
|
||||
icon={<ShieldSecurity size={iconSizeSideBar} color="#4F5260" />}
|
||||
title={'نقشها'}
|
||||
isActive={isActive('role')}
|
||||
link={Paths.role.list}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-10 px-6 text-description text-sm font-bold">{t('sidebar.other')}</div>
|
||||
|
||||
Reference in New Issue
Block a user