feat(roles): add roles list page

- Complete table view with role information
- CRUD operations (view, edit, delete, permissions)
- Delete confirmation modal
- Responsive design with proper Persian RTL
- Loading states and error handling
This commit is contained in:
hosseintaromi 2025-07-18 14:02:55 +03:30
parent efb92ac136
commit 0ee448d9be
1 changed files with 193 additions and 0 deletions

View File

@ -0,0 +1,193 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useRoles, useDeleteRole } from "../core/_hooks";
import { Button } from "@/components/ui/Button";
import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
import { Role } from "@/types/auth";
import { Trash2, Edit, Plus, Eye, Users } from "lucide-react";
import { Modal } from "@/components/ui/Modal";
const RolesListPage = () => {
const navigate = useNavigate();
const [deleteRoleId, setDeleteRoleId] = useState<string | null>(null);
const { data: roles, isLoading, error } = useRoles();
const { mutate: deleteRole, isPending: isDeleting } = useDeleteRole();
const handleEdit = (roleId: number) => {
navigate(`/roles/${roleId}/edit`);
};
const handleView = (roleId: number) => {
navigate(`/roles/${roleId}`);
};
const handlePermissions = (roleId: number) => {
navigate(`/roles/${roleId}/permissions`);
};
const handleDelete = (roleId: number) => {
setDeleteRoleId(roleId.toString());
};
const confirmDelete = () => {
if (deleteRoleId) {
deleteRole(deleteRoleId, {
onSuccess: () => {
setDeleteRoleId(null);
}
});
}
};
const cancelDelete = () => {
setDeleteRoleId(null);
};
if (isLoading) return <LoadingSpinner />;
if (error) return <div className="text-red-600">خطا در بارگذاری نقشها</div>;
return (
<div className="p-6">
<div className="mb-6">
<div className="flex justify-between items-center mb-4">
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
مدیریت نقشها
</h1>
<Button
variant="primary"
onClick={() => navigate('/roles/create')}
className="flex items-center gap-2"
>
<Plus className="h-4 w-4" />
افزودن نقش جدید
</Button>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-700">
<tr>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
نام نقش
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
توضیحات
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
تعداد دسترسیها
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
تاریخ ایجاد
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
عملیات
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{roles?.map((role: Role) => (
<tr key={role.id} className="hover:bg-gray-50 dark:hover:bg-gray-700">
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
{role.title}
</div>
</td>
<td className="px-6 py-4">
<div className="text-sm text-gray-600 dark:text-gray-300 max-w-xs truncate">
{role.description}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className="px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded-full">
{role.permissions?.length || 0} دسترسی
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-300">
{new Date(role.created_at).toLocaleDateString('fa-IR')}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">
<div className="flex items-center gap-2">
<Button
size="sm"
variant="secondary"
onClick={() => handleView(role.id)}
className="flex items-center gap-1"
>
<Eye className="h-3 w-3" />
مشاهده
</Button>
<Button
size="sm"
variant="primary"
onClick={() => handlePermissions(role.id)}
className="flex items-center gap-1"
>
<Users className="h-3 w-3" />
دسترسیها
</Button>
<Button
size="sm"
variant="secondary"
onClick={() => handleEdit(role.id)}
className="flex items-center gap-1"
>
<Edit className="h-3 w-3" />
ویرایش
</Button>
<Button
size="sm"
variant="danger"
onClick={() => handleDelete(role.id)}
className="flex items-center gap-1"
>
<Trash2 className="h-3 w-3" />
حذف
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{roles?.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500 dark:text-gray-400">هیچ نقشی یافت نشد</p>
</div>
)}
</div>
{/* Delete Confirmation Modal */}
<Modal
isOpen={!!deleteRoleId}
onClose={cancelDelete}
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={cancelDelete}>
انصراف
</Button>
<Button
variant="danger"
onClick={confirmDelete}
loading={isDeleting}
>
حذف
</Button>
</div>
</div>
</Modal>
</div>
);
};
export default RolesListPage;