diff --git a/src/pages/admin/Create.tsx b/src/pages/admin/Create.tsx new file mode 100644 index 0000000..1a7df48 --- /dev/null +++ b/src/pages/admin/Create.tsx @@ -0,0 +1,145 @@ +import { useFormik } from 'formik' +import { type FC } from 'react' +import { useGetPermissions, useCreateAdmin } from './hooks/useAdminData' +import type { CreateAdminType } from './types/Types' +import * as Yup from 'yup' +import { useNavigate } from 'react-router-dom' +import Input from '../../components/Input' +import MultiSelectSearchable from '../../components/MultiSelectSearchable' +import Button from '../../components/Button' +import { TickCircle } from 'iconsax-react' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' +import { Pages } from '../../config/Pages' + +const Create: FC = () => { + const navigate = useNavigate() + const { data: permissionsData } = useGetPermissions() + const createAdminMutation = useCreateAdmin() + + const formik = useFormik({ + initialValues: { + fullName: "", + userName: "", + email: "", + phoneNumber: "", + permissions: [], + password: "", + }, + validationSchema: Yup.object({ + fullName: Yup.string().required("نام کامل الزامی است"), + userName: Yup.string().required("نام کاربری الزامی است"), + email: Yup.string().email("ایمیل معتبر نیست").required("ایمیل الزامی است"), + phoneNumber: Yup.string().required("شماره تلفن الزامی است"), + permissions: Yup.array().min(1, "دسترسی های الزامی است").required("دسترسی های الزامی است"), + password: Yup.string().min(6, "رمز عبور باید حداقل ۶ کاراکتر باشد").required("رمز عبور الزامی است"), + }), + onSubmit: async (values) => { + createAdminMutation.mutate(values, { + onSuccess: () => { + toast.success('ادمین با موفقیت ایجاد شد') + navigate(Pages.admin.list) + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + }) + + const permissionsOptions = permissionsData?.results?.permissions?.map((permission) => ({ + value: permission._id, + label: permission.name + })) || [] + + return ( +
+
+
+

ساخت ادمین جدید

+
+
+ +
+
+ +
+ {/* اطلاعات اصلی */} +
+

اطلاعات اصلی

+ +
+
+ + +
+ +
+ + +
+ + +
+
+ + {/* دسترسی‌ها */} +
+
+

دسترسی‌ها

+ +
+ formik.setFieldValue('permissions', value)} + error_text={formik.touched.permissions && formik.errors.permissions ? String(formik.errors.permissions) : ''} + /> +
+
+
+
+
+ ) +} + +export default Create \ No newline at end of file diff --git a/src/pages/admin/List.tsx b/src/pages/admin/List.tsx new file mode 100644 index 0000000..e9f4f84 --- /dev/null +++ b/src/pages/admin/List.tsx @@ -0,0 +1,136 @@ +import { type FC, useState } from 'react' +import { useDeleteAdmin, useGetAdmins } from './hooks/useAdminData' +import { useQueryClient } from '@tanstack/react-query' +import { type Admin } from './types/Types' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import PaginationUi from '../../components/PaginationUi' +import Td from '../../components/Td' +import { Calendar, Call, Card, User as UserIcon } from 'iconsax-react' +import Button from '@/components/Button' +import { useNavigate } from 'react-router-dom' +import { Pages } from '@/config/Pages' +import TrashWithConfrim from '../../components/TrashWithConfrim' + +const List: FC = () => { + + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [currentPage, setCurrentPage] = useState(1); + const { data: adminsData, isLoading, error } = useGetAdmins(currentPage); + const deleteAdminMutation = useDeleteAdmin(); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + const admins = adminsData?.results?.admins || []; + const pager = adminsData?.results?.pager; + + const formatDate = (dateString: string) => { + if (!dateString) return '-'; + return new Date(dateString).toLocaleDateString('fa-IR'); + }; + + return ( +
+
+
+
+ + + + + + + {admins.length === 0 ? ( + + + + ) : ( + admins.map((admin: Admin) => ( + + + + + + + + )) + )} + +
+ + + + + + +
+ هیچ ادمینی یافت نشد +
+
+ + {admin.fullName || '-'} +
+
+ +
+ + {admin.email} +
+
+
+ + {admin.phoneNumber} +
+
+ +
+ + {formatDate(admin.createdAt)} +
+
+
+ + { + deleteAdminMutation.mutate(admin._id, { + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['admins'] }) + } + }) + }} + /> +
+
+
+ + { + setCurrentPage(page); + }} + /> +
+ ) +} + +export default List \ No newline at end of file diff --git a/src/pages/admin/Update.tsx b/src/pages/admin/Update.tsx new file mode 100644 index 0000000..8a4a997 --- /dev/null +++ b/src/pages/admin/Update.tsx @@ -0,0 +1,159 @@ +import { useFormik } from 'formik' +import { type FC, useEffect } from 'react' +import { useParams } from 'react-router-dom' +import { useGetPermissions, useGetAdminDetail, useUpdateAdmin } from './hooks/useAdminData' +import type { UpdateAdminType } from './types/Types' +import * as Yup from 'yup' +import { useNavigate } from 'react-router-dom' +import Input from '../../components/Input' +import MultiSelectSearchable from '../../components/MultiSelectSearchable' +import Button from '../../components/Button' +import { TickCircle } from 'iconsax-react' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' +import { Pages } from '../../config/Pages' + +const Update: FC = () => { + const navigate = useNavigate() + const { id } = useParams<{ id: string }>() + const { data: permissionsData } = useGetPermissions() + const { data: adminDetail, isLoading: adminDetailLoading, error: adminDetailError } = useGetAdminDetail(id!) + const updateAdminMutation = useUpdateAdmin() + + const formik = useFormik({ + initialValues: { + fullName: "", + userName: "", + email: "", + phoneNumber: "", + permissions: [], + }, + validationSchema: Yup.object({ + fullName: Yup.string().required("نام کامل الزامی است"), + userName: Yup.string().required("نام کاربری الزامی است"), + email: Yup.string().email("ایمیل معتبر نیست").required("ایمیل الزامی است"), + phoneNumber: Yup.string().required("شماره تلفن الزامی است"), + permissions: Yup.array().min(1, "دسترسی های الزامی است").required("دسترسی های الزامی است"), + }), + onSubmit: async (values) => { + updateAdminMutation.mutate({ id: id!, ...values }, { + onSuccess: () => { + toast.success('ادمین با موفقیت ویرایش شد') + navigate(Pages.admin.list) + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + }) + + useEffect(() => { + if (adminDetail) { + formik.setValues({ + fullName: adminDetail.results.admin.fullName, + userName: adminDetail.results.admin.userName, + email: adminDetail.results.admin.email, + phoneNumber: adminDetail.results.admin.phoneNumber, + permissions: adminDetail.results.admin.permissions, + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [adminDetail]) + + const permissionsOptions = permissionsData?.results?.permissions?.map((permission) => ({ + value: permission._id, + label: permission.name + })) || [] + + if (adminDetailLoading) { + return
در حال بارگذاری...
+ } + + if (adminDetailError) { + return
خطا در بارگذاری داده‌ها
+ } + + return ( +
+
+
+

ویرایش ادمین

+
+
+ +
+
+ +
+ {/* اطلاعات اصلی */} +
+

اطلاعات اصلی

+ +
+
+ + +
+ +
+ + +
+
+
+ + {/* دسترسی‌ها */} +
+
+

دسترسی‌ها

+ +
+ formik.setFieldValue('permissions', value)} + error_text={formik.touched.permissions && formik.errors.permissions ? String(formik.errors.permissions) : ''} + /> +
+
+
+
+
+ ) +} + +export default Update diff --git a/src/pages/admin/hooks/useAdminData.ts b/src/pages/admin/hooks/useAdminData.ts new file mode 100644 index 0000000..a9c8866 --- /dev/null +++ b/src/pages/admin/hooks/useAdminData.ts @@ -0,0 +1,42 @@ +import * as api from "../service/AdminService"; +import { useMutation, useQuery } from "@tanstack/react-query"; + +export const useGetAdmins = (page: number = 1, limit: number = 10) => { + return useQuery({ + queryKey: ["admins", page, limit], + queryFn: () => api.getAdmins(page, limit), + }); +}; + +export const useGetPermissions = () => { + return useQuery({ + queryKey: ["permissions"], + queryFn: () => api.getPermissions(), + }); +}; + +export const useCreateAdmin = () => { + return useMutation({ + mutationFn: api.createAdmin, + }); +}; + +export const useDeleteAdmin = () => { + return useMutation({ + mutationFn: api.deleteAdmin, + }); +}; + +export const useGetAdminDetail = (id: string) => { + return useQuery({ + queryKey: ["admin", id], + queryFn: () => api.getAdminDetail(id), + enabled: !!id, + }); +}; + +export const useUpdateAdmin = () => { + return useMutation({ + mutationFn: api.updateAdmin, + }); +}; diff --git a/src/pages/admin/service/AdminService.ts b/src/pages/admin/service/AdminService.ts new file mode 100644 index 0000000..a545a3a --- /dev/null +++ b/src/pages/admin/service/AdminService.ts @@ -0,0 +1,48 @@ +import axios from "../../../config/axios"; +import type { + GetPermissionsResponse, + CreateAdminType, + UpdateAdminType, + GetAdminsResponse, + Admin, +} from "../types/Types"; + +export const getAdmins = async ( + page: number = 1, + limit: number = 10 +): Promise => { + const { data } = await axios.get(`/admin/admins`, { + params: { page, limit }, + }); + return data; +}; + +export const getPermissions = async (): Promise => { + const { data } = await axios.get(`/admin/permissions`); + return data; +}; + +export const createAdmin = async (admin: CreateAdminType) => { + const { data } = await axios.post(`/admin/create`, admin); + return data; +}; + +export const deleteAdmin = async (id: string) => { + const { data } = await axios.delete(`/admin/${id}/delete`); + return data; +}; + +export const getAdminDetail = async ( + id: string +): Promise<{ status: number; success: boolean; results: { admin: Admin } }> => { + const { data } = await axios.get(`/admin/${id}`); + return data; +}; + +export const updateAdmin = async ({ + id, + ...admin +}: { id: string } & UpdateAdminType) => { + const { data } = await axios.put(`/admin/${id}/update`, admin); + return data; +}; diff --git a/src/pages/admin/types/Types.ts b/src/pages/admin/types/Types.ts new file mode 100644 index 0000000..1a57d09 --- /dev/null +++ b/src/pages/admin/types/Types.ts @@ -0,0 +1,76 @@ +export interface Permission { + _id: string; + name: string; + createdAt: string; + updatedAt: string; +} + +export interface GetPermissionsResponse { + status: number; + success: boolean; + results: { + permissions: Permission[]; + }; +} + +export type CreateAdminType = { + fullName: string; + userName: string; + email: string; + phoneNumber: string; + permissions: string[]; + password: string; +}; + +export type UpdateAdminType = { + fullName: string; + userName: string; + email: string; + phoneNumber: string; + permissions: string[]; +}; + +export interface AdminRole { + _id: string; + name: string; + createdAt: string; + updatedAt: string; +} + +export interface CreatedBy { + _id: string; + fullName: string; + userName: string; + email: string; +} + +export interface Admin { + _id: string; + fullName: string; + userName: string; + email: string; + phoneNumber: string; + role: AdminRole; + createdBy: CreatedBy; + createdAt: string; + updatedAt: string; + permissions: string[]; +} + +export interface Pager { + page: number; + limit: number; + totalItems: number; + totalPages: number; + prevPage: boolean; + nextPage: string | null; +} + +export interface GetAdminsResponse { + status: number; + success: boolean; + results: { + admins: Admin[]; + pager: Pager; + }; +} diff --git a/src/router/Main.tsx b/src/router/Main.tsx index a57a64d..d0effd8 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -33,6 +33,8 @@ import CouponList from '@/pages/Coupon/List' import CreateCoupon from '@/pages/Coupon/Create' import UpdateCoupon from '@/pages/Coupon/Update' import UsersList from '@/pages/user/List' +import AdminList from '@/pages/admin/List' +import AdminCreate from '@/pages/admin/Create' const MainRouter: FC = () => { @@ -86,6 +88,10 @@ const MainRouter: FC = () => { } /> } /> + + } /> + } /> +