roles: fix API response handling

- Add defensive programming for null API responses
- Handle roles array being null in API response
- Fix role permissions array handling
- Add proper fallbacks to prevent 'map is not a function' errors
This commit is contained in:
hosseintaromi 2025-07-22 08:48:28 +03:30
parent 1074d590a0
commit 174452d65d
2 changed files with 186 additions and 230 deletions

View File

@ -16,33 +16,23 @@ import {
export const getRoles = async () => { export const getRoles = async () => {
try { try {
const response = await httpGetRequest<Role[]>( const response = await httpGetRequest<{ roles: Role[] | null }>(
APIUrlGenerator(API_ROUTES.GET_ROLES) APIUrlGenerator(API_ROUTES.GET_ROLES)
); );
console.log("Roles API Response:", response); console.log("Roles API Response:", response);
console.log("Roles data:", response.data); console.log("Roles data:", response.data);
// اگر response.data آرایه نیست، ممکن است در فیلد دیگری باشد // ساختار API: {data: {roles: Role[] | null}}
if (Array.isArray(response.data)) { if (
return response.data; response.data &&
response.data.roles &&
Array.isArray(response.data.roles)
) {
return response.data.roles;
} }
// بررسی اگر در فیلدهای دیگر باشد // اگر roles null باشد یا آرایه نباشد، آرایه خالی برگردان
if (response.data && typeof response.data === "object") { console.warn("Roles is null or not an array:", response.data);
// چک کن آیا در results یا items یا data هست
if (Array.isArray((response.data as any).results)) {
return (response.data as any).results;
}
if (Array.isArray((response.data as any).items)) {
return (response.data as any).items;
}
if (Array.isArray((response.data as any).data)) {
return (response.data as any).data;
}
}
// fallback: آرایه خالی برگردان
console.warn("Roles data is not an array:", response.data);
return []; return [];
} catch (error) { } catch (error) {
console.error("Error fetching roles:", error); console.error("Error fetching roles:", error);
@ -82,10 +72,30 @@ export const deleteRole = async (id: string) => {
}; };
export const getRolePermissions = async (id: string) => { export const getRolePermissions = async (id: string) => {
const response = await httpGetRequest<Permission[]>( try {
APIUrlGenerator(API_ROUTES.GET_ROLE_PERMISSIONS(id)) const response = await httpGetRequest<{ permissions: Permission[] | null }>(
); APIUrlGenerator(API_ROUTES.GET_ROLE_PERMISSIONS(id))
return response.data; );
console.log("Role Permissions API Response:", response);
console.log("Role 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("Role Permissions is null or not an array:", response.data);
return [];
} catch (error) {
console.error("Error fetching role permissions:", error);
return [];
}
}; };
export const assignPermissionToRole = async ( export const assignPermissionToRole = async (

View File

@ -4,7 +4,7 @@ import { useRole, useRolePermissions, usePermissions, useAssignPermission, useRe
import { Button } from "@/components/ui/Button"; import { Button } from "@/components/ui/Button";
import { LoadingSpinner } from "@/components/ui/LoadingSpinner"; import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
import { Permission } from "@/types/auth"; import { Permission } from "@/types/auth";
import { ArrowRight, Plus, Trash2, Check } from "lucide-react"; import { ArrowRight, Plus, Trash2, Check, Shield } from "lucide-react";
import { Modal } from "@/components/ui/Modal"; import { Modal } from "@/components/ui/Modal";
const RolePermissionsPage = () => { const RolePermissionsPage = () => {
@ -16,6 +16,13 @@ const RolePermissionsPage = () => {
const { data: role, isLoading: roleLoading } = useRole(id); const { data: role, isLoading: roleLoading } = useRole(id);
const { data: rolePermissions, isLoading: permissionsLoading } = useRolePermissions(id); const { data: rolePermissions, isLoading: permissionsLoading } = useRolePermissions(id);
const { data: allPermissions, isLoading: allPermissionsLoading } = usePermissions(); const { data: allPermissions, isLoading: allPermissionsLoading } = usePermissions();
const assignedPermissionIds = (rolePermissions || []).map(p => p.id);
// Access the permissions array from the object structure
const safeAllPermissions = Array.isArray((allPermissions as any)?.permissions) ? (allPermissions as any).permissions : [];
const availablePermissions = safeAllPermissions.filter((p: any) => !assignedPermissionIds.includes(p.id));
const { mutate: assignPermission, isPending: assigning } = useAssignPermission(); const { mutate: assignPermission, isPending: assigning } = useAssignPermission();
const { mutate: removePermission, isPending: removing } = useRemovePermission(); const { mutate: removePermission, isPending: removing } = useRemovePermission();
@ -52,222 +59,161 @@ const RolePermissionsPage = () => {
if (isLoading) return <LoadingSpinner />; if (isLoading) return <LoadingSpinner />;
if (!role) return <div className="text-red-600">نقش یافت نشد</div>; if (!role) return <div className="text-red-600">نقش یافت نشد</div>;
const assignedPermissionIds = rolePermissions?.map(p => p.id) || [];
const availablePermissions = allPermissions?.filter(p => !assignedPermissionIds.includes(p.id)) || [];
return ( return (
<div className="p-6"> <div className="p-6">
<div className="mb-6"> <div className="space-y-6">
<div className="flex items-center justify-between mb-4"> {/* Header */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button <Button
variant="secondary" variant="secondary"
onClick={() => navigate('/roles')} onClick={() => navigate('/roles')}
className="flex items-center gap-2" className="flex items-center gap-2"
> >
<ArrowRight className="h-4 w-4" /> <ArrowRight className="h-4 w-4" />
بازگشت بازگشت
</Button> </Button>
<div> <div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100"> <h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
مدیریت دسترسیهای نقش مدیریت دسترسیهای نقش: {role?.title}
</h1> </h1>
<p className="text-gray-600 dark:text-gray-400"> <p className="text-gray-600 dark:text-gray-400 mt-1">
{role.title} تخصیص و حذف دسترسیها برای این نقش
</p> </p>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* دسترسی‌های تخصیص یافته */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
دسترسیهای تخصیص یافته ({(rolePermissions || []).length})
</h2>
</div>
<div className="p-6">
{permissionsLoading ? (
<div className="flex justify-center">
<LoadingSpinner />
</div>
) : (
<div className="space-y-3">
{(rolePermissions || []).length > 0 ? (
(rolePermissions || []).map((permission: Permission) => (
<div
key={permission.id}
className="flex items-center justify-between p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg"
>
<div className="flex-1">
<h4 className="font-medium text-green-900 dark:text-green-100">
{permission.title}
</h4>
<p className="text-sm text-green-700 dark:text-green-300">
{permission.description}
</p>
</div>
<Button
size="sm"
variant="danger"
onClick={() => handleRemovePermission(permission.id)}
className="flex items-center gap-1 ml-3"
>
<Trash2 className="h-3 w-3" />
حذف
</Button>
</div>
))
) : (
<p className="text-center text-gray-500 dark:text-gray-400 py-8">
هیچ دسترسی تخصیص داده نشده است
</p>
)}
</div>
)}
</div> </div>
</div> </div>
<Button {/* دسترسی‌های در دسترس */}
variant="primary" <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md">
onClick={() => setShowAssignModal(true)} <div className="p-6 border-b border-gray-200 dark:border-gray-700">
className="flex items-center gap-2" <h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
disabled={availablePermissions.length === 0} دسترسیهای قابل تخصیص ({availablePermissions.length})
> </h2>
<Plus className="h-4 w-4" /> </div>
اختصاص دسترسی جدید
</Button>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="p-6">
{/* دسترسی‌های تخصیص یافته */} {allPermissionsLoading ? (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md"> <div className="flex justify-center">
<div className="p-6 border-b border-gray-200 dark:border-gray-700"> <LoadingSpinner />
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
دسترسیهای تخصیص یافته ({assignedPermissionIds.length})
</h2>
</div>
<div className="p-6">
{permissionsLoading ? (
<div className="flex justify-center">
<LoadingSpinner />
</div>
) : (
<div className="space-y-3">
{rolePermissions && rolePermissions.length > 0 ? (
rolePermissions.map((permission: Permission) => (
<div
key={permission.id}
className="flex items-center justify-between p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg"
>
<div className="flex-1">
<h4 className="font-medium text-green-900 dark:text-green-100">
{permission.title}
</h4>
<p className="text-sm text-green-700 dark:text-green-300">
{permission.description}
</p>
</div>
<Button
size="sm"
variant="danger"
onClick={() => handleRemovePermission(permission.id)}
className="flex items-center gap-1 ml-3"
>
<Trash2 className="h-3 w-3" />
حذف
</Button>
</div>
))
) : (
<p className="text-center text-gray-500 dark:text-gray-400 py-8">
هیچ دسترسی تخصیص داده نشده است
</p>
)}
</div>
)}
</div>
</div>
{/* دسترسی‌های در دسترس */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
دسترسیهای قابل تخصیص ({availablePermissions.length})
</h2>
</div>
<div className="p-6">
{allPermissionsLoading ? (
<div className="flex justify-center">
<LoadingSpinner />
</div>
) : (
<div className="space-y-3">
{availablePermissions.length > 0 ? (
availablePermissions.map((permission: Permission) => (
<div
key={permission.id}
className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg"
>
<div className="flex-1">
<h4 className="font-medium text-gray-900 dark:text-gray-100">
{permission.title}
</h4>
<p className="text-sm text-gray-600 dark:text-gray-400">
{permission.description}
</p>
</div>
<Button
size="sm"
variant="primary"
onClick={() => handleAssignPermission(permission.id)}
className="flex items-center gap-1 ml-3"
loading={assigning}
>
<Plus className="h-3 w-3" />
اختصاص
</Button>
</div>
))
) : (
<p className="text-center text-gray-500 dark:text-gray-400 py-8">
تمام دسترسیها به این نقش تخصیص داده شده است
</p>
)}
</div>
)}
</div>
</div>
</div>
{/* Assign Permission Modal */}
<Modal
isOpen={showAssignModal}
onClose={() => setShowAssignModal(false)}
title="اختصاص دسترسی جدید"
size="lg"
>
<div className="space-y-4">
<p className="text-gray-600 dark:text-gray-300">
دسترسی مورد نظر را برای تخصیص به نقش "{role.title}" انتخاب کنید:
</p>
<div className="max-h-96 overflow-y-auto space-y-3">
{availablePermissions.map((permission: Permission) => (
<div
key={permission.id}
className="flex items-center justify-between p-3 border border-gray-200 dark:border-gray-600 rounded-lg"
>
<div className="flex-1">
<h4 className="font-medium text-gray-900 dark:text-gray-100">
{permission.title}
</h4>
<p className="text-sm text-gray-600 dark:text-gray-400">
{permission.description}
</p>
</div> </div>
<Button ) : (
size="sm" <div className="space-y-3">
variant="primary" {availablePermissions.length > 0 ? (
onClick={() => { availablePermissions.map((permission: Permission) => (
handleAssignPermission(permission.id); <div
setShowAssignModal(false); key={permission.id}
}} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg"
className="flex items-center gap-1" >
> <div className="flex-1">
<Check className="h-3 w-3" /> <h4 className="font-medium text-gray-900 dark:text-gray-100">
انتخاب {permission.title}
</Button> </h4>
</div> <p className="text-sm text-gray-600 dark:text-gray-400">
))} {permission.description}
</div> </p>
</div>
<div className="flex justify-end gap-3 pt-4"> <Button
<Button variant="secondary" onClick={() => setShowAssignModal(false)}> size="sm"
انصراف variant="primary"
</Button> onClick={() => handleAssignPermission(permission.id)}
className="flex items-center gap-1 ml-3"
loading={assigning}
>
<Plus className="h-3 w-3" />
اختصاص
</Button>
</div>
))
) : (
<p className="text-center text-gray-500 dark:text-gray-400 py-8">
تمام دسترسیها به این نقش تخصیص داده شده است
</p>
)}
</div>
)}
</div>
</div> </div>
</div> </div>
</Modal>
{/* Remove Permission Confirmation Modal */} {/* Modal تأیید حذف دسترسی */}
<Modal <Modal
isOpen={!!removePermissionId} isOpen={!!removePermissionId}
onClose={cancelRemovePermission} onClose={() => setRemovePermissionId(null)}
title="تأیید حذف دسترسی" title="حذف دسترسی"
size="sm" >
> <div className="space-y-4">
<div className="space-y-4"> <p className="text-gray-600 dark:text-gray-400">
<p className="text-gray-600 dark:text-gray-300"> آیا از حذف این دسترسی از نقش اطمینان دارید؟
آیا از حذف این دسترسی از نقش اطمینان دارید؟ </p>
</p> <div className="flex justify-end space-x-2 space-x-reverse">
<div className="flex justify-end gap-3"> <Button
<Button variant="secondary" onClick={cancelRemovePermission}> variant="secondary"
انصراف onClick={() => setRemovePermissionId(null)}
</Button> disabled={removing}
<Button >
variant="danger" انصراف
onClick={confirmRemovePermission} </Button>
loading={removing} <Button
> variant="danger"
حذف دسترسی onClick={confirmRemovePermission}
</Button> loading={removing}
>
حذف
</Button>
</div>
</div> </div>
</div> </Modal>
</Modal> </div>
</div> </div>
); );
}; };