feat(admin-users): enhance admin user management with permissions and roles
- Add permissions and roles multi-select to admin user form - Update admin user models to include permissions and roles arrays - Make permissions list page read-only by removing CRUD actions - Integrate MultiSelectAutocomplete for better UX
This commit is contained in:
parent
2e9fa5460e
commit
dce0f918ef
|
|
@ -5,9 +5,12 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
|||
import * as yup from 'yup';
|
||||
import { useAdminUser, useCreateAdminUser, useUpdateAdminUser } from '../core/_hooks';
|
||||
import { AdminUserFormData } from '../core/_models';
|
||||
import { usePermissions } from '../../permissions/core/_hooks';
|
||||
import { useRoles } from '../../roles/core/_hooks';
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { Input } from "@/components/ui/Input";
|
||||
import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
|
||||
import { MultiSelectAutocomplete, Option } from "@/components/ui/MultiSelectAutocomplete";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
|
||||
const adminUserSchema = yup.object({
|
||||
|
|
@ -22,6 +25,8 @@ const adminUserSchema = yup.object({
|
|||
})
|
||||
}),
|
||||
status: yup.string().required('وضعیت الزامی است').oneOf(['active', 'deactive'], 'وضعیت نامعتبر است'),
|
||||
permissions: yup.array().of(yup.number()).default([]),
|
||||
roles: yup.array().of(yup.number()).default([]),
|
||||
isEdit: yup.boolean().default(false)
|
||||
});
|
||||
|
||||
|
|
@ -34,6 +39,9 @@ const AdminUserFormPage = () => {
|
|||
const { mutate: createUser, isPending: isCreating } = useCreateAdminUser();
|
||||
const { mutate: updateUser, isPending: isUpdating } = useUpdateAdminUser();
|
||||
|
||||
const { data: permissions, isLoading: isLoadingPermissions } = usePermissions();
|
||||
const { data: roles, isLoading: isLoadingRoles } = useRoles();
|
||||
|
||||
const isLoading = isCreating || isUpdating;
|
||||
|
||||
const {
|
||||
|
|
@ -51,6 +59,8 @@ const AdminUserFormPage = () => {
|
|||
username: '',
|
||||
password: '',
|
||||
status: 'active' as 'active' | 'deactive',
|
||||
permissions: [],
|
||||
roles: [],
|
||||
isEdit: isEdit
|
||||
}
|
||||
});
|
||||
|
|
@ -69,6 +79,8 @@ const AdminUserFormPage = () => {
|
|||
setValue('last_name', user.last_name, { shouldValidate: true });
|
||||
setValue('username', user.username, { shouldValidate: true });
|
||||
setValue('status', user.status, { shouldValidate: true });
|
||||
setValue('permissions', user.permissions?.map(p => p.id) || [], { shouldValidate: true });
|
||||
setValue('roles', user.roles?.map(r => r.id) || [], { shouldValidate: true });
|
||||
setValue('isEdit', true, { shouldValidate: true });
|
||||
}
|
||||
}, [isEdit, user, setValue]);
|
||||
|
|
@ -83,7 +95,9 @@ const AdminUserFormPage = () => {
|
|||
last_name: data.last_name,
|
||||
username: data.username,
|
||||
password: data.password && data.password.trim() ? data.password : undefined,
|
||||
status: data.status
|
||||
status: data.status,
|
||||
permissions: data.permissions,
|
||||
roles: data.roles
|
||||
}
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
|
|
@ -97,7 +111,9 @@ const AdminUserFormPage = () => {
|
|||
last_name: data.last_name,
|
||||
username: data.username,
|
||||
password: data.password || '',
|
||||
status: data.status
|
||||
status: data.status,
|
||||
permissions: data.permissions,
|
||||
roles: data.roles
|
||||
}, {
|
||||
onSuccess: (result) => {
|
||||
console.log('✅ Admin user created successfully:', result);
|
||||
|
|
@ -178,6 +194,36 @@ const AdminUserFormPage = () => {
|
|||
placeholder={isEdit ? "رمز عبور جدید (در صورت تمایل به تغییر)" : "رمز عبور"}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<MultiSelectAutocomplete
|
||||
label="دسترسیها"
|
||||
options={(permissions || []).map((permission): Option => ({
|
||||
id: permission.id,
|
||||
title: permission.title,
|
||||
description: permission.description
|
||||
}))}
|
||||
selectedValues={watch('permissions') || []}
|
||||
onChange={(values) => setValue('permissions', values, { shouldValidate: true })}
|
||||
placeholder="انتخاب دسترسیها..."
|
||||
isLoading={isLoadingPermissions}
|
||||
error={errors.permissions?.message}
|
||||
/>
|
||||
|
||||
<MultiSelectAutocomplete
|
||||
label="نقشها"
|
||||
options={(roles || []).map((role): Option => ({
|
||||
id: role.id,
|
||||
title: role.title,
|
||||
description: role.description
|
||||
}))}
|
||||
selectedValues={watch('roles') || []}
|
||||
onChange={(values) => setValue('roles', values, { shouldValidate: true })}
|
||||
placeholder="انتخاب نقشها..."
|
||||
isLoading={isLoadingRoles}
|
||||
error={errors.roles?.message}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
وضعیت
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ export interface AdminUserFormData {
|
|||
username: string;
|
||||
password?: string;
|
||||
status: "active" | "deactive";
|
||||
permissions: number[];
|
||||
roles: number[];
|
||||
isEdit: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { usePermissions, useDeletePermission } from '../core/_hooks';
|
||||
import { usePermissions } from '../core/_hooks';
|
||||
import { Permission } from '../core/_models';
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
|
||||
import { Trash2, Edit3, Plus, Shield, Eye } from "lucide-react";
|
||||
import { Modal } from "@/components/ui/Modal";
|
||||
import { Shield } from "lucide-react";
|
||||
|
||||
// Skeleton Loading Component
|
||||
const PermissionsTableSkeleton = () => (
|
||||
|
|
@ -25,9 +22,6 @@ const PermissionsTableSkeleton = () => (
|
|||
<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">
|
||||
|
|
@ -75,36 +69,11 @@ const PermissionsTableSkeleton = () => (
|
|||
);
|
||||
|
||||
const PermissionsListPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [deletePermissionId, setDeletePermissionId] = useState<string | null>(null);
|
||||
const [filters, setFilters] = useState({
|
||||
search: ''
|
||||
});
|
||||
|
||||
const { data: permissions, isLoading, error } = usePermissions(filters);
|
||||
const { mutate: deletePermission, isPending: isDeleting } = useDeletePermission();
|
||||
|
||||
const handleCreate = () => {
|
||||
navigate('/permissions/create');
|
||||
};
|
||||
|
||||
const handleView = (permissionId: number) => {
|
||||
navigate(`/permissions/${permissionId}`);
|
||||
};
|
||||
|
||||
const handleEdit = (permissionId: number) => {
|
||||
navigate(`/permissions/${permissionId}/edit`);
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = () => {
|
||||
if (deletePermissionId) {
|
||||
deletePermission(deletePermissionId, {
|
||||
onSuccess: () => {
|
||||
setDeletePermissionId(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFilters(prev => ({ ...prev, search: e.target.value }));
|
||||
|
|
@ -127,16 +96,12 @@ const PermissionsListPage = () => {
|
|||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 flex items-center gap-2">
|
||||
<Shield className="h-6 w-6" />
|
||||
مدیریت دسترسیها
|
||||
لیست دسترسیها
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
مدیریت دسترسیهای سیستم
|
||||
نمایش دسترسیهای سیستم
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={handleCreate} className="flex items-center gap-2">
|
||||
<Plus className="h-4 w-4" />
|
||||
دسترسی جدید
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
|
|
@ -213,24 +178,6 @@ const PermissionsListPage = () => {
|
|||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
||||
{new Date(permission.created_at).toLocaleDateString('fa-IR')}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleEdit(permission.id)}
|
||||
className="text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||
title="ویرایش"
|
||||
>
|
||||
<Edit3 className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeletePermissionId(permission.id.toString())}
|
||||
className="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
|
||||
title="حذف"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
@ -252,59 +199,16 @@ const PermissionsListPage = () => {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
تاریخ ایجاد: {new Date(permission.created_at).toLocaleDateString('fa-IR')}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => handleEdit(permission.id)}
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs text-indigo-600 hover:text-indigo-900 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||
>
|
||||
<Edit3 className="h-3 w-3" />
|
||||
ویرایش
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeletePermissionId(permission.id.toString())}
|
||||
className="flex items-center gap-1 px-2 py-1 text-xs text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
حذف
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
<Modal
|
||||
isOpen={!!deletePermissionId}
|
||||
onClose={() => setDeletePermissionId(null)}
|
||||
title="حذف دسترسی"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
آیا از حذف این دسترسی اطمینان دارید؟ این عمل قابل بازگشت نیست و ممکن است بر نقشهایی که از این دسترسی استفاده میکنند تأثیر بگذارد.
|
||||
</p>
|
||||
<div className="flex justify-end space-x-2 space-x-reverse">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => setDeletePermissionId(null)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
انصراف
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={handleDeleteConfirm}
|
||||
loading={isDeleting}
|
||||
>
|
||||
حذف
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ export interface CreateAdminUserRequest {
|
|||
username: string;
|
||||
password: string;
|
||||
status: string;
|
||||
permissions?: number[];
|
||||
roles?: number[];
|
||||
}
|
||||
|
||||
export interface UpdateAdminUserRequest {
|
||||
|
|
@ -54,6 +56,8 @@ export interface UpdateAdminUserRequest {
|
|||
username: string;
|
||||
password?: string;
|
||||
status: string;
|
||||
permissions?: number[];
|
||||
roles?: number[];
|
||||
}
|
||||
|
||||
export interface AdminUsersListResponse {
|
||||
|
|
|
|||
Loading…
Reference in New Issue