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:
parent
1074d590a0
commit
174452d65d
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue