From 30969723fa26039fe150431135800e58651d5160 Mon Sep 17 00:00:00 2001 From: hosseintaromi Date: Tue, 22 Jul 2025 08:48:44 +0330 Subject: [PATCH] permissions: implement complete permissions management - Add permissions CRUD operations (list, create, edit, delete) - Implement proper API request/response handling - Create hooks and request functions following established pattern - Add TypeScript interfaces and form validation - Include proper error handling and user feedback --- src/pages/permissions/core/_hooks.ts | 103 ++++++++ src/pages/permissions/core/_models.ts | 46 ++++ src/pages/permissions/core/_requests.ts | 124 +++++++++ .../permission-form/PermissionFormPage.tsx | 159 ++++++++++++ .../permissions-list/PermissionsListPage.tsx | 237 ++++++++++++++++++ 5 files changed, 669 insertions(+) create mode 100644 src/pages/permissions/core/_hooks.ts create mode 100644 src/pages/permissions/core/_models.ts create mode 100644 src/pages/permissions/core/_requests.ts create mode 100644 src/pages/permissions/permission-form/PermissionFormPage.tsx create mode 100644 src/pages/permissions/permissions-list/PermissionsListPage.tsx diff --git a/src/pages/permissions/core/_hooks.ts b/src/pages/permissions/core/_hooks.ts new file mode 100644 index 0000000..923d361 --- /dev/null +++ b/src/pages/permissions/core/_hooks.ts @@ -0,0 +1,103 @@ +import { QUERY_KEYS } from "@/utils/query-key"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { + getPermissions, + getPermission, + createPermission, + updatePermission, + deletePermission, +} from "./_requests"; +import { + CreatePermissionRequest, + UpdatePermissionRequest, + PermissionFilters, +} from "./_models"; +import toast from "react-hot-toast"; + +export const usePermissions = (filters?: PermissionFilters) => { + return useQuery({ + queryKey: [QUERY_KEYS.GET_PERMISSIONS, filters], + queryFn: () => getPermissions(filters), + }); +}; + +export const usePermission = (id: string, enabled: boolean = true) => { + return useQuery({ + queryKey: [QUERY_KEYS.GET_PERMISSION, id], + queryFn: () => getPermission(id), + enabled: enabled && !!id, + }); +}; + +export const useCreatePermission = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: [QUERY_KEYS.CREATE_PERMISSION], + mutationFn: (permissionData: CreatePermissionRequest) => + createPermission(permissionData), + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_PERMISSIONS] }); + // Also invalidate roles permissions as they might be affected + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.GET_ROLE_PERMISSIONS], + }); + toast.success("دسترسی با موفقیت ایجاد شد"); + }, + onError: (error: any) => { + console.error("Create permission error:", error); + toast.error(error?.message || "خطا در ایجاد دسترسی"); + }, + }); +}; + +export const useUpdatePermission = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: [QUERY_KEYS.UPDATE_PERMISSION], + mutationFn: ({ + id, + permissionData, + }: { + id: string; + permissionData: UpdatePermissionRequest; + }) => updatePermission(id, permissionData), + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_PERMISSIONS] }); + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.GET_PERMISSION, variables.id], + }); + // Also invalidate roles permissions as they might be affected + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.GET_ROLE_PERMISSIONS], + }); + toast.success("دسترسی با موفقیت به‌روزرسانی شد"); + }, + onError: (error: any) => { + console.error("Update permission error:", error); + toast.error(error?.message || "خطا در به‌روزرسانی دسترسی"); + }, + }); +}; + +export const useDeletePermission = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: [QUERY_KEYS.DELETE_PERMISSION], + mutationFn: (id: string) => deletePermission(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_PERMISSIONS] }); + // Also invalidate roles permissions as they might be affected + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.GET_ROLE_PERMISSIONS], + }); + toast.success("دسترسی با موفقیت حذف شد"); + }, + onError: (error: any) => { + console.error("Delete permission error:", error); + toast.error(error?.message || "خطا در حذف دسترسی"); + }, + }); +}; diff --git a/src/pages/permissions/core/_models.ts b/src/pages/permissions/core/_models.ts new file mode 100644 index 0000000..64385ee --- /dev/null +++ b/src/pages/permissions/core/_models.ts @@ -0,0 +1,46 @@ +import { Permission } from "@/types/auth"; + +export interface PermissionFormData { + title: string; + description: string; +} + +export interface PermissionFilters { + search?: string; + page?: number; + limit?: number; +} + +export interface CreatePermissionRequest { + title: string; + description: string; +} + +export interface UpdatePermissionRequest { + id: number; + title: string; + description: string; +} + +export interface PermissionsResponse { + permissions: Permission[] | null; +} + +export interface PermissionResponse { + permission: Permission; +} + +export interface CreatePermissionResponse { + permission: Permission; +} + +export interface UpdatePermissionResponse { + permission: Permission; +} + +export interface DeletePermissionResponse { + message: string; +} + +// Export Permission type for easier access +export type { Permission } from "@/types/auth"; diff --git a/src/pages/permissions/core/_requests.ts b/src/pages/permissions/core/_requests.ts new file mode 100644 index 0000000..446b332 --- /dev/null +++ b/src/pages/permissions/core/_requests.ts @@ -0,0 +1,124 @@ +import { + httpGetRequest, + httpPostRequest, + httpPutRequest, + httpDeleteRequest, + APIUrlGenerator, +} from "@/utils/baseHttpService"; +import { API_ROUTES } from "@/constant/routes"; +import { + Permission, + CreatePermissionRequest, + UpdatePermissionRequest, + PermissionsResponse, + PermissionResponse, + CreatePermissionResponse, + UpdatePermissionResponse, + DeletePermissionResponse, + PermissionFilters, +} from "./_models"; + +export const getPermissions = async (filters?: PermissionFilters) => { + try { + const queryParams: Record = {}; + + if (filters?.search) queryParams.search = filters.search; + if (filters?.page) queryParams.page = filters.page; + if (filters?.limit) queryParams.limit = filters.limit; + + const response = await httpGetRequest( + APIUrlGenerator(API_ROUTES.GET_PERMISSIONS, queryParams) + ); + + console.log("Permissions API Response:", response); + console.log("Permissions data:", response.data); + + // Handle API response structure: {data: {permissions: Permission[] | null}} + if ( + response.data && + response.data.permissions && + Array.isArray(response.data.permissions) + ) { + return response.data.permissions; + } + + // If permissions is null or not an array, return empty array + console.warn("Permissions is null or not an array:", response.data); + return []; + } catch (error) { + console.error("Error fetching permissions:", error); + return []; + } +}; + +export const getPermission = async (id: string) => { + try { + const response = await httpGetRequest( + APIUrlGenerator(API_ROUTES.GET_PERMISSION(id)) + ); + + // Handle API response structure + if (response.data && response.data.permission) { + return response.data.permission; + } + + throw new Error("Permission not found"); + } catch (error) { + console.error("Error fetching permission:", error); + throw error; + } +}; + +export const createPermission = async ( + permissionData: CreatePermissionRequest +) => { + try { + const response = await httpPostRequest( + APIUrlGenerator(API_ROUTES.CREATE_PERMISSION), + permissionData + ); + + if (response.data && response.data.permission) { + return response.data.permission; + } + + throw new Error("Failed to create permission"); + } catch (error) { + console.error("Error creating permission:", error); + throw error; + } +}; + +export const updatePermission = async ( + id: string, + permissionData: UpdatePermissionRequest +) => { + try { + const response = await httpPutRequest( + APIUrlGenerator(API_ROUTES.UPDATE_PERMISSION(id)), + permissionData + ); + + if (response.data && response.data.permission) { + return response.data.permission; + } + + throw new Error("Failed to update permission"); + } catch (error) { + console.error("Error updating permission:", error); + throw error; + } +}; + +export const deletePermission = async (id: string) => { + try { + const response = await httpDeleteRequest( + APIUrlGenerator(API_ROUTES.DELETE_PERMISSION(id)) + ); + + return response.data; + } catch (error) { + console.error("Error deleting permission:", error); + throw error; + } +}; diff --git a/src/pages/permissions/permission-form/PermissionFormPage.tsx b/src/pages/permissions/permission-form/PermissionFormPage.tsx new file mode 100644 index 0000000..8192a4a --- /dev/null +++ b/src/pages/permissions/permission-form/PermissionFormPage.tsx @@ -0,0 +1,159 @@ +import React, { useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as yup from 'yup'; +import { usePermission, useCreatePermission, useUpdatePermission } from '../core/_hooks'; +import { PermissionFormData } from '../core/_models'; +import { Button } from "@/components/ui/Button"; +import { Input } from "@/components/ui/Input"; +import { LoadingSpinner } from "@/components/ui/LoadingSpinner"; +import { ArrowRight } from "lucide-react"; + +const permissionSchema = yup.object({ + title: yup.string().required('عنوان الزامی است').min(3, 'عنوان باید حداقل 3 کاراکتر باشد'), + description: yup.string().required('توضیحات الزامی است').min(10, 'توضیحات باید حداقل 10 کاراکتر باشد'), +}); + +const PermissionFormPage = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const isEdit = !!id; + + const { data: permission, isLoading: isLoadingPermission } = usePermission(id || '', isEdit); + const { mutate: createPermission, isPending: isCreating } = useCreatePermission(); + const { mutate: updatePermission, isPending: isUpdating } = useUpdatePermission(); + + const isLoading = isCreating || isUpdating; + + const { + register, + handleSubmit, + formState: { errors, isValid }, + setValue + } = useForm({ + resolver: yupResolver(permissionSchema), + mode: 'onChange', + defaultValues: { + title: '', + description: '' + } + }); + + // Populate form when editing + useEffect(() => { + if (isEdit && permission) { + setValue('title', permission.title); + setValue('description', permission.description); + } + }, [isEdit, permission, setValue]); + + const onSubmit = (data: PermissionFormData) => { + if (isEdit && id) { + updatePermission({ + id, + permissionData: { + id: parseInt(id), + title: data.title, + description: data.description + } + }, { + onSuccess: () => { + navigate('/permissions'); + } + }); + } else { + createPermission({ + title: data.title, + description: data.description + }, { + onSuccess: () => { + navigate('/permissions'); + } + }); + } + }; + + const handleBack = () => { + navigate('/permissions'); + }; + + if (isEdit && isLoadingPermission) { + return ( +
+ +
+ ); + } + + return ( +
+ {/* Header */} +
+ +
+

+ {isEdit ? 'ویرایش دسترسی' : 'ایجاد دسترسی جدید'} +

+

+ {isEdit ? 'ویرایش اطلاعات دسترسی' : 'اطلاعات دسترسی جدید را وارد کنید'} +

+
+
+ + {/* Form */} +
+
+ + +
+ +