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' + />