feat(roles): add role permissions management page

- Two-column layout for assigned vs available permissions
- Real-time permission assignment and removal
- Confirmation modals for destructive actions
- Complete permissions list with descriptions
- Visual distinction between assigned and available permissions
This commit is contained in:
hosseintaromi 2025-07-18 14:03:04 +03:30
parent cdf0eb29f3
commit ebd2c3c8f7
1 changed files with 275 additions and 0 deletions

View File

@ -0,0 +1,275 @@
import { useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useRole, useRolePermissions, usePermissions, useAssignPermission, useRemovePermission } from "../core/_hooks";
import { Button } from "@/components/ui/Button";
import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
import { Permission } from "@/types/auth";
import { ArrowRight, Plus, Trash2, Check } from "lucide-react";
import { Modal } from "@/components/ui/Modal";
const RolePermissionsPage = () => {
const navigate = useNavigate();
const { id = "" } = useParams();
const [showAssignModal, setShowAssignModal] = useState(false);
const [removePermissionId, setRemovePermissionId] = useState<string | null>(null);
const { data: role, isLoading: roleLoading } = useRole(id);
const { data: rolePermissions, isLoading: permissionsLoading } = useRolePermissions(id);
const { data: allPermissions, isLoading: allPermissionsLoading } = usePermissions();
const { mutate: assignPermission, isPending: assigning } = useAssignPermission();
const { mutate: removePermission, isPending: removing } = useRemovePermission();
const handleAssignPermission = (permissionId: number) => {
assignPermission({
roleId: id,
permissionId: permissionId.toString(),
});
};
const handleRemovePermission = (permissionId: number) => {
setRemovePermissionId(permissionId.toString());
};
const confirmRemovePermission = () => {
if (removePermissionId) {
removePermission({
roleId: id,
permissionId: removePermissionId,
}, {
onSuccess: () => {
setRemovePermissionId(null);
}
});
}
};
const cancelRemovePermission = () => {
setRemovePermissionId(null);
};
const isLoading = roleLoading || permissionsLoading;
if (isLoading) return <LoadingSpinner />;
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 (
<div className="p-6">
<div className="mb-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-4">
<Button
variant="secondary"
onClick={() => navigate('/roles')}
className="flex items-center gap-2"
>
<ArrowRight className="h-4 w-4" />
بازگشت
</Button>
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
مدیریت دسترسیهای نقش
</h1>
<p className="text-gray-600 dark:text-gray-400">
{role.title}
</p>
</div>
</div>
<Button
variant="primary"
onClick={() => setShowAssignModal(true)}
className="flex items-center gap-2"
disabled={availablePermissions.length === 0}
>
<Plus className="h-4 w-4" />
اختصاص دسترسی جدید
</Button>
</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">
دسترسیهای تخصیص یافته ({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>
<Button
size="sm"
variant="primary"
onClick={() => {
handleAssignPermission(permission.id);
setShowAssignModal(false);
}}
className="flex items-center gap-1"
>
<Check className="h-3 w-3" />
انتخاب
</Button>
</div>
))}
</div>
<div className="flex justify-end gap-3 pt-4">
<Button variant="secondary" onClick={() => setShowAssignModal(false)}>
انصراف
</Button>
</div>
</div>
</Modal>
{/* Remove Permission Confirmation Modal */}
<Modal
isOpen={!!removePermissionId}
onClose={cancelRemovePermission}
title="تأیید حذف دسترسی"
size="sm"
>
<div className="space-y-4">
<p className="text-gray-600 dark:text-gray-300">
آیا از حذف این دسترسی از نقش اطمینان دارید؟
</p>
<div className="flex justify-end gap-3">
<Button variant="secondary" onClick={cancelRemovePermission}>
انصراف
</Button>
<Button
variant="danger"
onClick={confirmRemovePermission}
loading={removing}
>
حذف دسترسی
</Button>
</div>
</div>
</Modal>
</div>
);
};
export default RolePermissionsPage;