// ───────────────────────────────────────────────────────────────────────────── // src/components/admin/UserManagement.tsx // Fixed: Added null checks for all optional fields // ───────────────────────────────────────────────────────────────────────────── import { useState, useEffect, useCallback } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '../ui/card'; import { Input } from '../ui/input'; import { Button } from '../ui/button'; import { Badge } from '../ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'; import { Search, Users, GraduationCap, Shield, MoreVertical, Eye, Ban, Trash2, Mail, Calendar, TrendingUp, Loader2, RefreshCw, } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '../ui/dropdown-menu'; import { useNavigate } from 'react-router-dom'; import { useLanguage } from '../../lib/languageContext'; import { translations } from '../../lib/translations'; // Import from the User Management API import adminUserManagementApi, { type StudentDtoAdmin, type SheikhDtoAdmin, type UserManagementStatsDto, type UserManagementApiError, } from '../../lib/api/adminUserManagementApi'; import { toast } from 'sonner'; // ─── Types ─────────────────────────────────────────────────────────────────── // Combined user type for display interface DisplayUser { id: number; name: string; email: string; registrationDate: string; type: 'student' | 'sheikh' | 'admin'; status: string; // Student fields streak?: number | null; totalSessions?: number | null; // Sheikh fields numberOfSessions?: number | null; totalRevenue?: number | null; averageRating?: number | null; } // ─── Helpers ────────────────────────────────────────────────────────────────── function formatDate(dateString: string, locale: string): string { const d = new Date(dateString); return d.toLocaleDateString(locale === 'ar' ? 'ar-EG' : 'en-US', { year: 'numeric', month: 'short', day: 'numeric', }); } function formatNumber(value: number | null | undefined, locale: string): string { if (value === null || value === undefined) return '—'; return value.toLocaleString(locale === 'ar' ? 'ar-EG' : 'en-US'); } function formatCurrency(value: number | null | undefined, locale: string): string { if (value === null || value === undefined) return '—'; return `${value.toLocaleString(locale === 'ar' ? 'ar-EG' : 'en-US')} ${locale === 'ar' ? 'ج.م' : 'EGP'}`; } function formatRating(value: number | null | undefined): string { if (value === null || value === undefined) return '—'; return value.toFixed(1); } // ─── Main Component ─────────────────────────────────────────────────────────── export function UserManagement() { const { language } = useLanguage(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const t = (translations[language] as any).userManagement ?? {}; const isAr = language === 'ar'; const navigate = useNavigate(); // ── State ───────────────────────────────────────────────────────────────── const [students, setStudents] = useState([]); const [sheikhs, setSheikhs] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState(null); const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [activeTab, setActiveTab] = useState('all'); // ── Fetch all data ──────────────────────────────────────────────────────── const loadData = useCallback(async () => { setLoading(true); setError(null); try { // Fetch students, sheikhs, and stats in parallel const [studentsData, sheikhsData, statsData] = await Promise.all([ adminUserManagementApi.fetchAllStudents(), adminUserManagementApi.fetchAllSheikhs(), adminUserManagementApi.fetchUserManagementStats(), ]); setStudents(studentsData); setSheikhs(sheikhsData); setStats(statsData); } catch (err) { console.error('Failed to load data:', err); // Handle different error types if (err instanceof UserManagementApiError) { if (err.status === 403) { setError(isAr ? 'صلاحية المسؤول مطلوبة' : 'Admin access required'); } else if (err.status === 401) { setError(isAr ? 'انتهت الجلسة، يرجى تسجيل الدخول مرة أخرى' : 'Session expired'); } else { setError(isAr ? 'فشل تحميل البيانات' : 'Failed to load data'); } } else { setError(isAr ? 'خطأ في الشبكة' : 'Network error'); } setStudents([]); setSheikhs([]); setStats(null); } finally { setLoading(false); } }, [isAr]); useEffect(() => { loadData(); }, [loadData]); // ── Combine users for display ───────────────────────────────────────────── const getAllUsers = useCallback((): DisplayUser[] => { const allUsers: DisplayUser[] = [ ...students.map(s => ({ id: s.id, name: s.name, email: s.email, registrationDate: s.registrationDate, type: 'student' as const, status: s.status, streak: s.streak, totalSessions: s.totalSessions, })), ...sheikhs.map(sh => ({ id: sh.id, name: sh.name, email: sh.email, registrationDate: sh.registrationDate, type: 'sheikh' as const, status: sh.status, numberOfSessions: sh.numberOfSessions, totalRevenue: sh.totalRevenue, averageRating: sh.averageRating, })), ]; // Add mock admins (since admin list not available from API) allUsers.push({ id: 999, name: isAr ? 'مدير النظام' : 'System Admin', email: 'admin@example.com', registrationDate: new Date().toISOString(), type: 'admin', status: 'ACTIVE', }); return allUsers; }, [students, sheikhs, isAr]); // ── Filter ──────────────────────────────────────────────────────────────── const filterUsers = (type?: 'student' | 'sheikh' | 'admin') => { let list = getAllUsers(); if (type) { list = list.filter(u => u.type === type); } if (searchQuery.trim()) { const q = searchQuery.toLowerCase(); list = list.filter( u => (u.name ?? '').toLowerCase().includes(q) || (u.email ?? '').toLowerCase().includes(q), ); } return list; }; const allUsers = filterUsers(); const studentList = filterUsers('student'); const sheikhList = filterUsers('sheikh'); const adminList = filterUsers('admin'); // ── Actions ─────────────────────────────────────────────────────────────── const handleBlockUser = async (user: DisplayUser) => { setActionLoading(user.id); try { await adminUserManagementApi.blockUser(user.id); // Refresh data after blocking await loadData(); } catch (err) { console.error('Block failed:', err); toast.error(isAr ? 'فشلت العملية، حاول مرة أخرى' : 'Operation failed, please try again'); } finally { setActionLoading(null); } }; const handleUnblockUser = async (user: DisplayUser) => { setActionLoading(user.id); try { await adminUserManagementApi.unblockUser(user.id); // Refresh data after unblocking await loadData(); } catch (err) { console.error('Unblock failed:', err); toast.error(isAr ? 'فشلت العملية، حاول مرة أخرى' : 'Operation failed, please try again'); } finally { setActionLoading(null); } }; // const handleDeleteUser = async (user: DisplayUser) => { // const msg = isAr // ? 'هل أنت متأكد من حذف هذا المستخدم؟ لا يمكن التراجع عن هذا الإجراء.' // : 'Are you sure you want to delete this user? This action cannot be undone.'; // if (!confirm(msg)) return; // setActionLoading(user.id); // try { // // Note: Delete user API not in docs // alert(isAr ? 'حذف المستخدمين غير متاح حالياً' : 'Delete user not available yet'); // } catch (err) { // console.error('Delete failed:', err); // alert(isAr ? 'فشل الحذف، حاول مرة أخرى' : 'Delete failed, please try again'); // } finally { // setActionLoading(null); // } // }; const handleViewProfile = (user: DisplayUser) => { if (user.type === 'student') { navigate(`/admin/student/${user.id}`); } else if (user.type === 'sheikh') { navigate(`/admin/sheikh/${user.id}`); } else { navigate(`/admin/user/${user.id}`); } }; // ── Role helpers ────────────────────────────────────────────────────────── const getRoleIcon = (type: string) => { if (type === 'student') return ; if (type === 'sheikh') return ; return ; }; const getRoleBadgeVariant = (type: string) => { if (type === 'student') return 'default' as const; if (type === 'sheikh') return 'secondary' as const; return 'destructive' as const; }; const getRoleLabel = (type: string) => { if (!isAr) { if (type === 'student') return 'Student'; if (type === 'sheikh') return 'Sheikh'; return 'Admin'; } if (type === 'student') return 'طالب'; if (type === 'sheikh') return 'شيخ'; return 'مسؤول'; }; const getStatusBadge = (status: string) => { if (status === 'BLOCKED') { return ( {isAr ? 'محظور' : 'Blocked'} ); } if (status === 'PENDING' || status === 'INACTIVE') { return ( {isAr ? 'قيد المراجعة' : 'Pending'} ); } return null; }; // ── Stats ───────────────────────────────────────────────────────────────── const totalUsers = stats?.totalUsers ?? (students.length + sheikhs.length + 1); // +1 for mock admin const totalStudents = stats?.totalStudents ?? students.length; const totalSheikhs = stats?.totalSheikhs ?? sheikhs.length; // const totalBlocked = stats?.blockedUsers ?? users.filter(u => u.status === 'BLOCKED').length; const totalBlocked = stats?.blockedUsers ?? [...students, ...sheikhs].filter(u => u.status === 'BLOCKED').length; // ── User Table ──────────────────────────────────────────────────────────── const UserTable = ({ list }: { list: DisplayUser[] }) => { if (loading) { return (
{isAr ? 'جاري التحميل…' : 'Loading…'}
); } if (list.length === 0) { return (

{t.noUsersFound ?? (isAr ? 'لا يوجد مستخدمون' : 'No users found')}

); } return (
{list.map((user) => { const isBlocked = user.status === 'BLOCKED'; const isActioning = actionLoading === user.id; return (
{(user.name ?? '?').charAt(0).toUpperCase()}
{user.name ?? '—'} {getRoleIcon(user.type)} {getRoleLabel(user.type)} {getStatusBadge(user.status)}
{user.email} {t.joined ?? (isAr ? 'انضم' : 'Joined')}{' '} {formatDate(user.registrationDate, language)}
{/* Mobile stats row */}
{user.type === 'student' && ( <> {user.totalSessions !== null && user.totalSessions !== undefined && ( {formatNumber(user.totalSessions, language)} {t.sessions ?? (isAr ? 'ج' : 'Sess')} )} {user.streak !== null && user.streak !== undefined && ( {formatNumber(user.streak, language)} 🔥 )} )} {user.type === 'sheikh' && ( <> {user.numberOfSessions !== null && user.numberOfSessions !== undefined && ( {formatNumber(user.numberOfSessions, language)} {t.sessions ?? (isAr ? 'ج' : 'Sess')} )} {user.averageRating !== null && user.averageRating !== undefined && ( {formatRating(user.averageRating)} )} )} {user.status === 'BLOCKED' && ( {isAr ? 'محظور' : 'Blocked'} )}
{/* ── Left: avatar + info ── */} {/* ── Right: stats + actions ── */}
{user.type === 'student' && ( <> {user.totalSessions !== null && user.totalSessions !== undefined && (
{formatNumber(user.totalSessions, language)}
{t.sessions ?? (isAr ? 'جلسة' : 'Sessions')}
)} {user.streak !== null && user.streak !== undefined && (
{formatNumber(user.streak, language)} 🔥
{t.streak ?? (isAr ? 'سلسلة' : 'Streak')}
)} )} {user.type === 'sheikh' && ( <> {user.numberOfSessions !== null && user.numberOfSessions !== undefined && (
{formatNumber(user.numberOfSessions, language)}
{t.sessions ?? (isAr ? 'جلسة' : 'Sessions')}
)} {user.averageRating !== null && user.averageRating !== undefined && (
{formatRating(user.averageRating)} ⭐
{t.rating ?? (isAr ? 'تقييم' : 'Rating')}
)} {user.totalRevenue !== null && user.totalRevenue !== undefined && (
{formatCurrency(user.totalRevenue, language)}
{t.earnings ?? (isAr ? 'أرباح' : 'Earnings')}
)} )}
handleViewProfile(user)}> {t.viewProfile ?? (isAr ? 'عرض الملف' : 'View Profile')} {isBlocked ? ( handleUnblockUser(user)}> {t.unblockUser ?? (isAr ? 'إلغاء الحظر' : 'Unblock User')} ) : ( handleBlockUser(user)}> {t.blockUser ?? (isAr ? 'حظر المستخدم' : 'Block User')} )} {/* handleDeleteUser(user)} className="text-red-600" > {t.deleteUser ?? (isAr ? 'حذف المستخدم' : 'Delete User')} */}
); })}
); }; // ───────────────────────────────────────────────────────────────────────── return (
{/* ── Page Title ── */}

{t.title ?? (isAr ? 'إدارة المستخدمين' : 'User Management')}

{t.description ?? (isAr ? 'بحث وإدارة جميع مستخدمي المنصة' : 'Search and manage all platform users')}

{/* */}
{/* ── Error Banner ── */} {error && (
{error}
)} {/* ── Statistics Cards ── */}
{t.totalUsers ?? (isAr ? 'إجمالي المستخدمين' : 'Total Users')}
{loading ? : totalUsers}
{t.students ?? (isAr ? 'الطلاب' : 'Students')}
{loading ? : totalStudents}
{t.sheikhs ?? (isAr ? 'الشيوخ' : 'Sheikhs')}
{loading ? : totalSheikhs}
{t.blockedUsers ?? (isAr ? 'المحظورون' : 'Blocked Users')}
{loading ? : totalBlocked}
{/* ── Search ── */} {t.searchUsers ?? (isAr ? 'بحث عن مستخدم' : 'Search Users')} {t.searchDescription ?? (isAr ? 'ابحث بالاسم أو البريد الإلكتروني' : 'Find users by name or email address')}
setSearchQuery(e.target.value)} className="pl-10" />
{/* ── Users Table with Tabs ── */} {/* ── Users Table with Tabs ── */} {t.allUsers ?? (isAr ? 'الكل' : 'All')} {isAr ? 'الكل' : 'All'} ({allUsers.length}) {t.students ?? (isAr ? 'طلاب' : 'Students')} {isAr ? 'طلاب' : 'Stu'} ({studentList.length}) {t.sheikhs ?? (isAr ? 'شيوخ' : 'Sheikhs')} {isAr ? 'شيوخ' : 'Sh'} ({sheikhList.length}) {t.admins ?? (isAr ? 'مسؤولون' : 'Admins')} {isAr ? 'مسؤول' : 'Adm'} ({adminList.length})
); }