diff --git a/src/config/Pages.ts b/src/config/Pages.ts
index 96b4785..aa2fbf6 100644
--- a/src/config/Pages.ts
+++ b/src/config/Pages.ts
@@ -46,4 +46,9 @@ export const Pages = {
detail: "/comments/detail/",
},
setting: "/setting",
+ roles: {
+ list: "/roles/list",
+ add: "/roles/add",
+ update: "/roles/update/",
+ },
};
diff --git a/src/langs/fa.json b/src/langs/fa.json
index b234e0d..aa0c716 100644
--- a/src/langs/fa.json
+++ b/src/langs/fa.json
@@ -58,7 +58,8 @@
"reports": "گزارشات",
"comments": "نظرات",
"setting": "تنظیمات",
- "logout": "خروج"
+ "logout": "خروج",
+ "roles": "سطح دسترسی ها"
},
"slider": {
"new_slider": "اسلایدر جدید",
diff --git a/src/pages/roles/List.tsx b/src/pages/roles/List.tsx
new file mode 100644
index 0000000..1545d2c
--- /dev/null
+++ b/src/pages/roles/List.tsx
@@ -0,0 +1,72 @@
+import { type FC } from 'react'
+import Table from '@/components/Table'
+import Filters from '@/components/Filters'
+import { Add } from 'iconsax-react'
+import Button from '@/components/Button'
+import { useGetRoles, useDeleteRole } from './hooks/useRolesData'
+import { useRolesFilters } from './hooks/useRolesFilters'
+import { getRoleTableColumns } from './components/RoleTableColumns'
+import { useRoleFiltersFields } from './components/RoleFiltersFields'
+import { Link } from 'react-router-dom'
+import { Pages } from '@/config/Pages'
+
+const RolesList: FC = () => {
+ const {
+ filters,
+ currentPage,
+ apiParams,
+ handleFiltersChange,
+ handlePageChange,
+ limit,
+ } = useRolesFilters()
+
+ const { data: rolesData, isLoading } = useGetRoles(apiParams)
+ const { mutate: deleteRole, isPending: isDeleting } = useDeleteRole()
+
+ const roles = rolesData?.data || []
+ const columns = getRoleTableColumns({
+ onDelete: (id: string) => deleteRole(id),
+ isDeleting
+ })
+ const filterFields = useRoleFiltersFields()
+
+ const totalPages = Math.ceil(roles.length / limit) || 1
+
+ return (
+
+
+
سطح دسترسی ها
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default RolesList
\ No newline at end of file
diff --git a/src/pages/roles/components/RoleFiltersFields.tsx b/src/pages/roles/components/RoleFiltersFields.tsx
new file mode 100644
index 0000000..767a18c
--- /dev/null
+++ b/src/pages/roles/components/RoleFiltersFields.tsx
@@ -0,0 +1,13 @@
+import { useMemo } from 'react'
+import type { FieldType } from '@/components/Filters'
+
+export const useRoleFiltersFields = (): FieldType[] => {
+ return useMemo(() => [
+ {
+ type: 'input',
+ name: 'search',
+ placeholder: 'جستجو',
+ },
+ ], [])
+}
+
diff --git a/src/pages/roles/components/RoleTableColumns.tsx b/src/pages/roles/components/RoleTableColumns.tsx
new file mode 100644
index 0000000..9615cdc
--- /dev/null
+++ b/src/pages/roles/components/RoleTableColumns.tsx
@@ -0,0 +1,66 @@
+import type { ColumnType } from '@/components/types/TableTypes'
+import type { RoleType } from '../types/Types'
+import Status from '@/components/Status'
+import TrashWithConfrim from '@/components/TrashWithConfrim'
+import { Eye } from 'iconsax-react'
+import { Link } from 'react-router-dom'
+import { Pages } from '@/config/Pages'
+
+interface GetRoleTableColumnsParams {
+ onDelete?: (id: string) => void
+ isDeleting?: boolean
+}
+
+export const getRoleTableColumns = ({ onDelete, isDeleting }: GetRoleTableColumnsParams = {}): ColumnType[] => {
+ return [
+ {
+ key: 'name',
+ title: 'نام نقش',
+ },
+ {
+ key: 'permissions',
+ title: 'تعداد دسترسیها',
+ render: (item: RoleType) => {
+ return item.permissions?.length || 0
+ }
+ },
+ {
+ key: 'restaurant',
+ title: 'رستوران',
+ render: (item: RoleType) => {
+ return item.restaurant ? 'دارد' : '-'
+ }
+ },
+ {
+ key: 'createdAt',
+ title: 'تاریخ ایجاد',
+ render: (item: RoleType) => {
+ return new Date(item.createdAt).toLocaleDateString('fa-IR')
+ }
+ },
+ {
+ key: 'actions',
+ title: '',
+ render: (item: RoleType) => {
+ return (
+
+
+
+
+ {onDelete && (
+ onDelete(item.id)}
+ isLoading={isDeleting}
+ />
+ )}
+
+ )
+ }
+ },
+ ]
+}
+
diff --git a/src/pages/roles/hooks/useRolesData.ts b/src/pages/roles/hooks/useRolesData.ts
new file mode 100644
index 0000000..014cbf8
--- /dev/null
+++ b/src/pages/roles/hooks/useRolesData.ts
@@ -0,0 +1,19 @@
+import * as api from "../service/RolesService";
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+
+export const useGetRoles = (params?: api.GetRolesParams) => {
+ return useQuery({
+ queryKey: ["roles", params],
+ queryFn: () => api.getRoles(params),
+ });
+};
+
+export const useDeleteRole = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: api.deleteRole,
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["roles"] });
+ },
+ });
+};
diff --git a/src/pages/roles/hooks/useRolesFilters.ts b/src/pages/roles/hooks/useRolesFilters.ts
new file mode 100644
index 0000000..912bbc2
--- /dev/null
+++ b/src/pages/roles/hooks/useRolesFilters.ts
@@ -0,0 +1,43 @@
+import { useState, useMemo } from 'react'
+import type { FilterValues } from '@/components/Filters'
+
+const DEFAULT_PAGE = 1
+const DEFAULT_LIMIT = 10
+
+export const useRolesFilters = () => {
+ const [filters, setFilters] = useState({})
+ const [currentPage, setCurrentPage] = useState(DEFAULT_PAGE)
+ const [limit] = useState(DEFAULT_LIMIT)
+
+ const apiParams = useMemo(() => {
+ const params: Record = {
+ page: currentPage,
+ limit: limit,
+ }
+
+ if (filters.search) {
+ params.search = filters.search
+ }
+
+ return params
+ }, [filters, currentPage, limit])
+
+ const handleFiltersChange = (newFilters: FilterValues) => {
+ setFilters(newFilters)
+ setCurrentPage(DEFAULT_PAGE)
+ }
+
+ const handlePageChange = (page: number) => {
+ setCurrentPage(page)
+ }
+
+ return {
+ filters,
+ currentPage,
+ apiParams,
+ handleFiltersChange,
+ handlePageChange,
+ limit,
+ }
+}
+
diff --git a/src/pages/roles/service/RolesService.ts b/src/pages/roles/service/RolesService.ts
new file mode 100644
index 0000000..6a5e814
--- /dev/null
+++ b/src/pages/roles/service/RolesService.ts
@@ -0,0 +1,27 @@
+import axios from "@/config/axios";
+import type { CreateRoleType, GetRolesResponseType } from "../types/Types";
+
+export type GetRolesParams = {
+ page?: number;
+ limit?: number;
+ search?: string;
+};
+
+export const getRoles = async (
+ params?: GetRolesParams
+): Promise => {
+ const { data } = await axios.get("/admin/roles", {
+ params,
+ });
+ return data;
+};
+
+export const createRole = async (params: CreateRoleType) => {
+ const { data } = await axios.post("/admin/roles", params);
+ return data;
+};
+
+export const deleteRole = async (id: string) => {
+ const { data } = await axios.delete(`/admin/roles/${id}`);
+ return data;
+};
diff --git a/src/pages/roles/types/Types.ts b/src/pages/roles/types/Types.ts
new file mode 100644
index 0000000..696d41a
--- /dev/null
+++ b/src/pages/roles/types/Types.ts
@@ -0,0 +1,35 @@
+import type { IResponse } from "@/types/response.types";
+
+export type CreateRoleType = {
+ name: string;
+ permissionIds: string[];
+};
+
+export type PermissionType = {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ name: string;
+ [key: string]: unknown;
+};
+
+export type RestaurantType = {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ [key: string]: unknown;
+};
+
+export type RoleType = {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ deletedAt: string | null;
+ name: string;
+ restaurant: RestaurantType | null;
+ permissions: PermissionType[];
+};
+
+export type GetRolesResponseType = IResponse;
diff --git a/src/router/Main.tsx b/src/router/Main.tsx
index 14c9851..ec367ea 100644
--- a/src/router/Main.tsx
+++ b/src/router/Main.tsx
@@ -19,6 +19,7 @@ import CommentsList from '@/pages/comments/List'
import CommentsDetails from '@/pages/comments/Details'
import ScheduleList from '@/pages/schedule/List'
import CreateSchedule from '@/pages/schedule/Create'
+import RolesList from '@/pages/roles/List'
const MainRouter: FC = () => {
const { hasSubMenu } = useSharedStore()
@@ -49,6 +50,7 @@ const MainRouter: FC = () => {
} />
} />
} />
+ } />
diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx
index a8450dd..d45ab9a 100644
--- a/src/shared/SideBar.tsx
+++ b/src/shared/SideBar.tsx
@@ -2,7 +2,7 @@ import { type FC, useEffect } from 'react'
import LogoImage from '../assets/images/logo.svg'
import LogoSmall from '../assets/images/logo-small.svg'
import { useTranslation } from 'react-i18next'
-import { DocumentText, Home2, Logout, Message, NotificationStatus, People, Setting2, TicketDiscount, Chart, Calendar } from 'iconsax-react'
+import { DocumentText, Home2, Logout, Message, NotificationStatus, People, Setting2, TicketDiscount, Chart, Calendar, Security } from 'iconsax-react'
import SideBarItem from './SideBarItem'
import { useLocation } from 'react-router-dom'
import { Pages } from '../config/Pages'
@@ -109,6 +109,15 @@ const SideBar: FC = () => {
name='discounts'
activeName='discounts'
/>
+
+ }
+ title={t('sidebar.roles')}
+ isActive={isActive('roles')}
+ link={Pages.roles.list}
+ name='roles'
+ activeName='roles'
+ />