233 lines
6.8 KiB
TypeScript
233 lines
6.8 KiB
TypeScript
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'}
|
|
/>
|
|
</>
|
|
);
|
|
}); |