255 lines
9.5 KiB
TypeScript
255 lines
9.5 KiB
TypeScript
import { useState } from 'react';
|
||
import { Plus, Search, Filter } from 'lucide-react';
|
||
import { Table } from '../components/ui/Table';
|
||
import { Button } from '../components/ui/Button';
|
||
import { Modal } from '../components/ui/Modal';
|
||
import { Pagination } from '../components/ui/Pagination';
|
||
import { UserForm } from '../components/forms/UserForm';
|
||
import { PermissionWrapper } from '../components/common/PermissionWrapper';
|
||
import { TableColumn } from '../types';
|
||
import { UserFormData } from '../utils/validationSchemas';
|
||
import { formatDate } from '../utils/formatters';
|
||
import { useUsers, useCreateUser, useUpdateUser, useDeleteUser } from '../hooks/useUsers';
|
||
|
||
import { useFilters } from '../stores/useAppStore';
|
||
|
||
const Users = () => {
|
||
const [showUserModal, setShowUserModal] = useState(false);
|
||
const [editingUser, setEditingUser] = useState<any>(null);
|
||
const [currentPage, setCurrentPage] = useState(1);
|
||
const itemsPerPage = 5;
|
||
|
||
const { filters, setFilters } = useFilters();
|
||
|
||
const queryParams = {
|
||
page: currentPage,
|
||
limit: itemsPerPage,
|
||
search: filters.search,
|
||
sortBy: 'createdAt',
|
||
sortOrder: 'desc' as const,
|
||
};
|
||
|
||
const { data: usersResponse, isLoading, error } = useUsers(queryParams);
|
||
const createUserMutation = useCreateUser();
|
||
const updateUserMutation = useUpdateUser();
|
||
const deleteUserMutation = useDeleteUser();
|
||
|
||
const users = usersResponse?.data || [];
|
||
const totalItems = usersResponse?.total || 0;
|
||
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
||
|
||
const columns: TableColumn[] = [
|
||
{ key: 'name', label: 'نام', sortable: true },
|
||
{ key: 'email', label: 'ایمیل', sortable: true },
|
||
{ key: 'phone', label: 'تلفن' },
|
||
{ key: 'role', label: 'نقش' },
|
||
{
|
||
key: 'status',
|
||
label: 'وضعیت',
|
||
render: (value) => (
|
||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${value === 'active'
|
||
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
|
||
: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
||
}`}>
|
||
{value === 'active' ? 'فعال' : 'غیرفعال'}
|
||
</span>
|
||
)
|
||
},
|
||
{
|
||
key: 'createdAt',
|
||
label: 'تاریخ عضویت',
|
||
sortable: true,
|
||
render: (value) => formatDate(value)
|
||
},
|
||
{
|
||
key: 'actions',
|
||
label: 'عملیات',
|
||
render: (_, row) => (
|
||
<div className="flex space-x-2">
|
||
<Button
|
||
size="sm"
|
||
variant="secondary"
|
||
onClick={() => handleEditUser(row)}
|
||
disabled={updateUserMutation.isPending}
|
||
>
|
||
ویرایش
|
||
</Button>
|
||
<PermissionWrapper permission={22}>
|
||
<Button
|
||
size="sm"
|
||
variant="danger"
|
||
onClick={() => handleDeleteUser(row.id)}
|
||
disabled={deleteUserMutation.isPending}
|
||
>
|
||
حذف
|
||
</Button>
|
||
</PermissionWrapper>
|
||
</div>
|
||
)
|
||
}
|
||
];
|
||
|
||
const handleAddUser = () => {
|
||
setEditingUser(null);
|
||
setShowUserModal(true);
|
||
};
|
||
|
||
const handleEditUser = (user: any) => {
|
||
setEditingUser(user);
|
||
setShowUserModal(true);
|
||
};
|
||
|
||
const handleDeleteUser = async (userId: string) => {
|
||
if (confirm('آیا از حذف این کاربر اطمینان دارید؟')) {
|
||
try {
|
||
await deleteUserMutation.mutateAsync(userId);
|
||
} catch (error) {
|
||
console.error('Delete error:', error);
|
||
}
|
||
}
|
||
};
|
||
|
||
const handleSubmitUser = async (data: UserFormData) => {
|
||
try {
|
||
if (editingUser) {
|
||
await updateUserMutation.mutateAsync({
|
||
id: editingUser.id,
|
||
data: {
|
||
name: data.name,
|
||
email: data.email,
|
||
phone: data.phone,
|
||
role: data.role,
|
||
}
|
||
});
|
||
} else {
|
||
await createUserMutation.mutateAsync({
|
||
name: data.name,
|
||
email: data.email,
|
||
phone: data.phone,
|
||
role: data.role,
|
||
password: data.password || '123456',
|
||
});
|
||
}
|
||
setShowUserModal(false);
|
||
} catch (error) {
|
||
console.error('Submit error:', error);
|
||
}
|
||
};
|
||
|
||
const handleCloseModal = () => {
|
||
setShowUserModal(false);
|
||
setEditingUser(null);
|
||
};
|
||
|
||
const handleSearchChange = (value: string) => {
|
||
setFilters({ search: value });
|
||
setCurrentPage(1);
|
||
};
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="p-6">
|
||
<div className="text-center py-12">
|
||
<p className="text-red-600 dark:text-red-400">
|
||
خطا در بارگذاری کاربران: {error.message}
|
||
</p>
|
||
<Button
|
||
onClick={() => window.location.reload()}
|
||
className="mt-4"
|
||
>
|
||
تلاش دوباره
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="p-6 space-y-6">
|
||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||
مدیریت کاربران
|
||
</h1>
|
||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||
{totalItems} کاربر یافت شد
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-3 space-x-reverse">
|
||
<Button variant="secondary">
|
||
<Filter className="h-4 w-4 ml-2" />
|
||
فیلتر
|
||
</Button>
|
||
<PermissionWrapper permission={25}>
|
||
<button
|
||
onClick={handleAddUser}
|
||
disabled={createUserMutation.isPending}
|
||
className="flex items-center justify-center w-12 h-12 bg-primary-600 hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-full transition-colors duration-200 text-white shadow-lg hover:shadow-xl"
|
||
title="افزودن کاربر"
|
||
>
|
||
<Plus className="h-5 w-5" />
|
||
</button>
|
||
</PermissionWrapper>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="card p-6">
|
||
<div className="mb-6">
|
||
<div className="relative">
|
||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||
<Search className="h-5 w-5 text-gray-400" />
|
||
</div>
|
||
<input
|
||
type="text"
|
||
placeholder="جستجو در کاربران..."
|
||
value={filters.search}
|
||
onChange={(e) => handleSearchChange(e.target.value)}
|
||
className="input pr-10 max-w-md"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{isLoading ? (
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||
<Table columns={columns} data={[]} loading={true} />
|
||
</div>
|
||
) : (
|
||
<>
|
||
<div className="bg-white dark:bg-gray-800 rounded-lg overflow-hidden">
|
||
<Table
|
||
columns={columns}
|
||
data={users}
|
||
loading={isLoading}
|
||
/>
|
||
<Pagination
|
||
currentPage={currentPage}
|
||
totalPages={totalPages}
|
||
onPageChange={setCurrentPage}
|
||
itemsPerPage={itemsPerPage}
|
||
totalItems={totalItems}
|
||
/>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
|
||
<Modal
|
||
title={editingUser ? "ویرایش کاربر" : "افزودن کاربر"}
|
||
isOpen={showUserModal}
|
||
onClose={handleCloseModal}
|
||
size="lg"
|
||
>
|
||
<UserForm
|
||
initialData={editingUser}
|
||
onSubmit={handleSubmitUser}
|
||
onCancel={handleCloseModal}
|
||
loading={createUserMutation.isPending || updateUserMutation.isPending}
|
||
isEdit={!!editingUser}
|
||
/>
|
||
</Modal>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default Users;
|