uup
This commit is contained in:
233
app/components/users/UserList.tsx
Normal file
233
app/components/users/UserList.tsx
Normal file
@@ -0,0 +1,233 @@
|
||||
import { useState, memo } from 'react';
|
||||
import { Form } from '@remix-run/react';
|
||||
import { DataTable, Pagination, Button, Text, ConfirmModal } from '~/components/ui';
|
||||
import { getAuthLevelName, getStatusName } from '~/lib/user-utils';
|
||||
import { useSettings } from '~/contexts/SettingsContext';
|
||||
import { AUTH_LEVELS } from '~/types/auth';
|
||||
import type { UserWithoutPassword } from '~/types/database';
|
||||
|
||||
interface UserListProps {
|
||||
users: UserWithoutPassword[];
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
onEdit: (user: UserWithoutPassword) => void;
|
||||
onDelete: (userId: number) => void;
|
||||
onToggleStatus: (userId: number) => void;
|
||||
currentUserAuthLevel: number;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export const UserList = memo(function UserList({
|
||||
users,
|
||||
currentPage,
|
||||
totalPages,
|
||||
onPageChange,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onToggleStatus,
|
||||
currentUserAuthLevel,
|
||||
loading = false,
|
||||
}: UserListProps) {
|
||||
const { formatDate } = useSettings();
|
||||
const [deleteModal, setDeleteModal] = useState<{
|
||||
isOpen: boolean;
|
||||
user: UserWithoutPassword | null;
|
||||
}>({ isOpen: false, user: null });
|
||||
|
||||
const [statusModal, setStatusModal] = useState<{
|
||||
isOpen: boolean;
|
||||
user: UserWithoutPassword | null;
|
||||
}>({ isOpen: false, user: null });
|
||||
|
||||
const handleDeleteClick = (user: UserWithoutPassword) => {
|
||||
setDeleteModal({ isOpen: true, user });
|
||||
};
|
||||
|
||||
const handleStatusClick = (user: UserWithoutPassword) => {
|
||||
setStatusModal({ isOpen: true, user });
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = () => {
|
||||
if (deleteModal.user) {
|
||||
onDelete(deleteModal.user.id);
|
||||
}
|
||||
setDeleteModal({ isOpen: false, user: null });
|
||||
};
|
||||
|
||||
const handleStatusConfirm = () => {
|
||||
if (statusModal.user) {
|
||||
onToggleStatus(statusModal.user.id);
|
||||
}
|
||||
setStatusModal({ isOpen: false, user: null });
|
||||
};
|
||||
|
||||
const canEditUser = (user: UserWithoutPassword) => {
|
||||
// Superadmin can edit anyone
|
||||
if (currentUserAuthLevel === AUTH_LEVELS.SUPERADMIN) return true;
|
||||
|
||||
// Admin cannot edit superadmin
|
||||
if (currentUserAuthLevel === AUTH_LEVELS.ADMIN && user.authLevel === AUTH_LEVELS.SUPERADMIN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const canDeleteUser = (user: UserWithoutPassword) => {
|
||||
// Same rules as edit
|
||||
return canEditUser(user);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'name',
|
||||
header: 'الاسم',
|
||||
sortable: true,
|
||||
render: (user: UserWithoutPassword) => (
|
||||
<div>
|
||||
<Text weight="medium">{user.name}</Text>
|
||||
<Text size="sm" color="secondary">@{user.username}</Text>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
header: 'البريد الإلكتروني',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
key: 'authLevel',
|
||||
header: 'مستوى الصلاحية',
|
||||
render: (user: UserWithoutPassword) => {
|
||||
const levelName = getAuthLevelName(user.authLevel);
|
||||
const colorClass = user.authLevel === AUTH_LEVELS.SUPERADMIN
|
||||
? 'text-purple-600 bg-purple-100'
|
||||
: user.authLevel === AUTH_LEVELS.ADMIN
|
||||
? 'text-blue-600 bg-blue-100'
|
||||
: 'text-gray-600 bg-gray-100';
|
||||
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${colorClass}`}>
|
||||
{levelName}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
header: 'الحالة',
|
||||
render: (user: UserWithoutPassword) => {
|
||||
const statusName = getStatusName(user.status);
|
||||
const colorClass = user.status === 'active'
|
||||
? 'text-green-600 bg-green-100'
|
||||
: 'text-red-600 bg-red-100';
|
||||
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${colorClass}`}>
|
||||
{statusName}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'createdDate',
|
||||
header: 'تاريخ الإنشاء',
|
||||
sortable: true,
|
||||
render: (user: UserWithoutPassword) => (
|
||||
<Text size="sm" color="secondary">
|
||||
{formatDate(user.createdDate)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
header: 'الإجراءات',
|
||||
render: (user: UserWithoutPassword) => (
|
||||
<div className="flex items-center space-x-2 space-x-reverse">
|
||||
{canEditUser(user) && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onEdit(user)}
|
||||
className='w-24'
|
||||
>
|
||||
تعديل
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{canEditUser(user) && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleStatusClick(user)}
|
||||
className='w-24'
|
||||
>
|
||||
{user.status === 'active' ? 'إلغاء تفعيل' : 'تفعيل'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{canDeleteUser(user) && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
onClick={() => handleDeleteClick(user)}
|
||||
className='w-24'
|
||||
>
|
||||
حذف
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable
|
||||
data={users}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
emptyMessage="لا توجد مستخدمين"
|
||||
/>
|
||||
|
||||
{totalPages > 1 && (
|
||||
<div className="mt-6">
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
<ConfirmModal
|
||||
isOpen={deleteModal.isOpen}
|
||||
onClose={() => setDeleteModal({ isOpen: false, user: null })}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
title="تأكيد الحذف"
|
||||
message={`هل أنت متأكد من حذف المستخدم "${deleteModal.user?.name}"؟ هذا الإجراء لا يمكن التراجع عنه.`}
|
||||
confirmText="حذف"
|
||||
cancelText="إلغاء"
|
||||
variant="danger"
|
||||
/>
|
||||
|
||||
{/* Status Toggle Confirmation Modal */}
|
||||
<ConfirmModal
|
||||
isOpen={statusModal.isOpen}
|
||||
onClose={() => setStatusModal({ isOpen: false, user: null })}
|
||||
onConfirm={handleStatusConfirm}
|
||||
title={statusModal.user?.status === 'active' ? 'إلغاء تفعيل المستخدم' : 'تفعيل المستخدم'}
|
||||
message={
|
||||
statusModal.user?.status === 'active'
|
||||
? `هل أنت متأكد من إلغاء تفعيل المستخدم "${statusModal.user?.name}"؟`
|
||||
: `هل أنت متأكد من تفعيل المستخدم "${statusModal.user?.name}"؟`
|
||||
}
|
||||
confirmText={statusModal.user?.status === 'active' ? 'إلغاء تفعيل' : 'تفعيل'}
|
||||
cancelText="إلغاء"
|
||||
variant={statusModal.user?.status === 'active' ? 'warning' : 'info'}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user