Spaces:
Sleeping
Sleeping
| // src/components/admin/UserProfileView.tsx | |
| import { useState, useEffect } from 'react'; | |
| import { useParams, useNavigate } from 'react-router-dom'; | |
| import { useLanguage } from '../../lib/languageContext'; | |
| import { translations } from '../../lib/translations'; | |
| import { | |
| Card, | |
| CardContent, | |
| CardDescription, | |
| CardHeader, | |
| CardTitle, | |
| } from '../ui/card'; | |
| import { Button } from '../ui/button'; | |
| import { Badge } from '../ui/badge'; | |
| import { | |
| AlertDialog, | |
| AlertDialogAction, | |
| AlertDialogCancel, | |
| AlertDialogContent, | |
| AlertDialogDescription, | |
| AlertDialogFooter, | |
| AlertDialogHeader, | |
| AlertDialogTitle, | |
| AlertDialogTrigger, | |
| } from '../ui/alert-dialog'; | |
| import { Progress } from '../ui/progress'; | |
| import { Separator } from '../ui/separator'; | |
| import { toast } from 'sonner'; | |
| import { | |
| ArrowLeft, | |
| Mail, | |
| Calendar, | |
| MapPin, | |
| Award, | |
| Clock, | |
| DollarSign, | |
| Star, | |
| Users, | |
| Ban, | |
| Trash2, | |
| CheckCircle, | |
| AlertTriangle, | |
| Video, | |
| BookOpen, | |
| Target, | |
| Loader2, | |
| } from 'lucide-react'; | |
| // Import API | |
| import adminUserManagementApi, { | |
| type StudentDtoAdmin, | |
| type SheikhDtoAdmin, | |
| type UserManagementApiError, | |
| type StudentProfileResponse, | |
| type SheikhProfileResponse, | |
| type SessionHistoryDto, | |
| } from '../../lib/api/adminUserManagementApi'; | |
| // ─── Types & Interfaces ────────────────────────────────────────────────────── | |
| type UserType = 'student' | 'sheikh' | 'admin'; | |
| interface DisplayUser { | |
| id: number; | |
| name: string; | |
| email: string; | |
| registrationDate: string; | |
| type: UserType; | |
| status: string; | |
| // Student fields | |
| streak?: number | null; | |
| totalSessions?: number | null; | |
| // Sheikh fields | |
| numberOfSessions?: number | null; | |
| totalRevenue?: number | null; | |
| averageRating?: number | null; | |
| } | |
| interface StudentProfileData { | |
| profile: StudentProfileResponse['studentProfile']; | |
| sessions: SessionHistoryDto[]; | |
| } | |
| interface SheikhProfileData { | |
| profile: SheikhProfileResponse['sheikhProfile']; | |
| sessions: SessionHistoryDto[]; | |
| } | |
| // ─── 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: 'long', | |
| 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); | |
| } | |
| function formatDuration(minutes: number): string { | |
| const hours = Math.floor(minutes / 60); | |
| const mins = minutes % 60; | |
| if (hours === 0) return `${mins} ${mins === 1 ? 'min' : 'mins'}`; | |
| if (mins === 0) return `${hours} ${hours === 1 ? 'hr' : 'hrs'}`; | |
| return `${hours}h ${mins}m`; | |
| } | |
| // ─── Main Component ─────────────────────────────────────────────────────────── | |
| export function UserProfileView() { | |
| const { userId } = useParams<{ userId: string }>(); | |
| const navigate = useNavigate(); | |
| const { language } = useLanguage(); | |
| const t = translations[language]; | |
| const isArabic = language === 'ar'; | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| const [userType, setUserType] = useState<UserType | null>(null); | |
| const [studentData, setStudentData] = useState<StudentProfileData | null>(null); | |
| const [sheikhData, setSheikhData] = useState<SheikhProfileData | null>(null); | |
| const [actionLoading, setActionLoading] = useState(false); | |
| // ── Load user profile ───────────────────────────────────────────────────── | |
| useEffect(() => { | |
| loadUserProfile(); | |
| }, [userId]); | |
| const loadUserProfile = async () => { | |
| if (!userId) { | |
| setError('User ID not provided'); | |
| setLoading(false); | |
| return; | |
| } | |
| setLoading(true); | |
| setError(null); | |
| try { | |
| // Try to load as student first | |
| try { | |
| const studentProfile = await adminUserManagementApi.fetchStudentProfile(parseInt(userId)); | |
| setUserType('student'); | |
| setStudentData({ | |
| profile: studentProfile.studentProfile, | |
| sessions: studentProfile.sessionHistories, | |
| }); | |
| setLoading(false); | |
| return; | |
| } catch (err) { | |
| // Not a student, continue to try sheikh | |
| } | |
| // Try to load as sheikh | |
| try { | |
| const sheikhProfile = await adminUserManagementApi.fetchSheikhProfile(parseInt(userId)); | |
| setUserType('sheikh'); | |
| setSheikhData({ | |
| profile: sheikhProfile.sheikhProfile, | |
| sessions: sheikhProfile.sessionHistories, | |
| }); | |
| setLoading(false); | |
| return; | |
| } catch (err) { | |
| // Not a sheikh either | |
| if (err instanceof UserManagementApiError) { | |
| if (err.status === 404) { | |
| setError(isArabic ? 'المستخدم غير موجود' : 'User not found'); | |
| } else if (err.status === 403) { | |
| setError(isArabic ? 'صلاحية المسؤول مطلوبة' : 'Admin access required'); | |
| } else { | |
| setError(err.message); | |
| } | |
| } else { | |
| setError(isArabic ? 'فشل تحميل بيانات المستخدم' : 'Failed to load user data'); | |
| } | |
| } | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| // ── Actions ─────────────────────────────────────────────────────────────── | |
| const handleBlock = async () => { | |
| if (!userId) return; | |
| setActionLoading(true); | |
| try { | |
| await adminUserManagementApi.blockUser(parseInt(userId)); | |
| toast.success( | |
| isArabic | |
| ? 'تم حظر المستخدم بنجاح' | |
| : 'User has been blocked' | |
| ); | |
| // Refresh data | |
| await loadUserProfile(); | |
| } catch (err) { | |
| console.error('Block failed:', err); | |
| toast.error( | |
| isArabic | |
| ? 'فشل حظر المستخدم' | |
| : 'Failed to block user' | |
| ); | |
| } finally { | |
| setActionLoading(false); | |
| } | |
| }; | |
| const handleUnblock = async () => { | |
| if (!userId) return; | |
| setActionLoading(true); | |
| try { | |
| await adminUserManagementApi.unblockUser(parseInt(userId)); | |
| toast.success( | |
| isArabic | |
| ? 'تم إلغاء حظر المستخدم بنجاح' | |
| : 'User has been unblocked' | |
| ); | |
| // Refresh data | |
| await loadUserProfile(); | |
| } catch (err) { | |
| console.error('Unblock failed:', err); | |
| toast.error( | |
| isArabic | |
| ? 'فشل إلغاء حظر المستخدم' | |
| : 'Failed to unblock user' | |
| ); | |
| } finally { | |
| setActionLoading(false); | |
| } | |
| }; | |
| const handleDelete = async () => { | |
| if (!userId) return; | |
| setActionLoading(true); | |
| try { | |
| // Note: Delete API not available yet | |
| toast.error( | |
| isArabic | |
| ? 'حذف المستخدمين غير متاح حالياً' | |
| : 'Delete user not available yet' | |
| ); | |
| } catch (err) { | |
| console.error('Delete failed:', err); | |
| } finally { | |
| setActionLoading(false); | |
| } | |
| }; | |
| const handleBack = () => { | |
| navigate('/admin/users'); | |
| }; | |
| // ── Loading State ───────────────────────────────────────────────────────── | |
| if (loading) { | |
| return ( | |
| <div className="container mx-auto py-16 px-4 flex justify-center items-center"> | |
| <div className="text-center space-y-4"> | |
| <Loader2 className="h-12 w-12 animate-spin text-emerald-600 mx-auto" /> | |
| <p className="text-muted-foreground"> | |
| {isArabic ? 'جاري تحميل الملف الشخصي...' : 'Loading profile...'} | |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // ── Error State ─────────────────────────────────────────────────────────── | |
| if (error || !userType) { | |
| return ( | |
| <div className="container mx-auto py-16 px-4"> | |
| <Card className="max-w-md mx-auto"> | |
| <CardHeader> | |
| <CardTitle className="text-center text-destructive"> | |
| <AlertTriangle className="h-12 w-12 mx-auto mb-4 text-destructive" /> | |
| {isArabic ? 'خطأ' : 'Error'} | |
| </CardTitle> | |
| <CardDescription className="text-center"> | |
| {error || (isArabic ? 'المستخدم غير موجود' : 'User not found')} | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="text-center"> | |
| <Button onClick={handleBack}> | |
| <ArrowLeft className={`h-4 w-4 ${isArabic ? 'ml-2' : 'mr-2'}`} /> | |
| {isArabic ? 'العودة' : 'Go Back'} | |
| </Button> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ); | |
| } | |
| const isStudent = userType === 'student'; | |
| const isSheikh = userType === 'sheikh'; | |
| const currentStatus = isStudent ? studentData?.profile.status : sheikhData?.profile.status; | |
| const isBlocked = currentStatus === 'BLOCKED'; | |
| const student = studentData?.profile; | |
| const sheikh = sheikhData?.profile; | |
| const sessions = studentData?.sessions || sheikhData?.sessions || []; | |
| return ( | |
| <div dir={isArabic ? 'rtl' : 'ltr'} className="container mx-auto py-8 px-4 max-w-7xl space-y-8"> | |
| {/* Header + Actions */} | |
| <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-6"> | |
| <div className="flex items-center gap-4"> | |
| <Button variant="outline" size="sm" onClick={handleBack}> | |
| <ArrowLeft className={`h-4 w-4 ${isArabic ? 'ml-2' : 'mr-2'}`} /> | |
| {isArabic ? 'العودة' : 'Back'} | |
| </Button> | |
| <div> | |
| <h1 className="text-2xl font-bold tracking-tight"> | |
| {isArabic ? 'الملف الشخصي' : 'User Profile'} | |
| </h1> | |
| <p className="text-muted-foreground mt-1"> | |
| {isStudent ? student?.name : sheikh?.name} | |
| </p> | |
| </div> | |
| </div> | |
| <div className="flex flex-wrap gap-3"> | |
| <AlertDialog> | |
| <AlertDialogTrigger asChild> | |
| <Button variant="outline" size="sm" disabled={actionLoading}> | |
| <Ban className={`h-4 w-4 ${isArabic ? 'ml-2' : 'mr-2'}`} /> | |
| {isBlocked | |
| ? (isArabic ? 'إلغاء الحظر' : 'Unblock') | |
| : (isArabic ? 'حظر' : 'Block')} | |
| </Button> | |
| </AlertDialogTrigger> | |
| <AlertDialogContent> | |
| <AlertDialogHeader> | |
| <AlertDialogTitle> | |
| {isBlocked | |
| ? (isArabic ? 'إلغاء حظر المستخدم؟' : 'Unblock User?') | |
| : (isArabic ? 'حظر المستخدم؟' : 'Block User?')} | |
| </AlertDialogTitle> | |
| <AlertDialogDescription> | |
| {isBlocked | |
| ? (isArabic | |
| ? 'سيُعاد للمستخدم الوصول إلى المنصة.' | |
| : "This will restore the user's access to the platform.") | |
| : (isArabic | |
| ? 'سيمنع هذا الإجراء المستخدم من الوصول إلى المنصة. يمكن إلغاء الحظر لاحقًا.' | |
| : 'This will prevent the user from accessing the platform. They can be unblocked later.')} | |
| </AlertDialogDescription> | |
| </AlertDialogHeader> | |
| <AlertDialogFooter> | |
| <AlertDialogCancel>{isArabic ? 'إلغاء' : 'Cancel'}</AlertDialogCancel> | |
| <AlertDialogAction onClick={isBlocked ? handleUnblock : handleBlock}> | |
| {isBlocked | |
| ? (isArabic ? 'إلغاء الحظر' : 'Unblock') | |
| : (isArabic ? 'حظر' : 'Block')} | |
| </AlertDialogAction> | |
| </AlertDialogFooter> | |
| </AlertDialogContent> | |
| </AlertDialog> | |
| <AlertDialog> | |
| <AlertDialogTrigger asChild> | |
| <Button variant="destructive" size="sm" disabled={actionLoading}> | |
| <Trash2 className={`h-4 w-4 ${isArabic ? 'ml-2' : 'mr-2'}`} /> | |
| {isArabic ? 'حذف' : 'Delete'} | |
| </Button> | |
| </AlertDialogTrigger> | |
| <AlertDialogContent> | |
| <AlertDialogHeader> | |
| <AlertDialogTitle> | |
| {isArabic ? 'حذف المستخدم نهائيًا؟' : 'Delete User Permanently?'} | |
| </AlertDialogTitle> | |
| <AlertDialogDescription> | |
| {isArabic | |
| ? 'هذا الإجراء لا يمكن التراجع عنه. سيتم حذف الحساب وجميع البيانات المرتبطة به نهائيًا.' | |
| : 'This action cannot be undone. This will permanently delete the user account and all associated data.'} | |
| </AlertDialogDescription> | |
| </AlertDialogHeader> | |
| <AlertDialogFooter> | |
| <AlertDialogCancel>{isArabic ? 'إلغاء' : 'Cancel'}</AlertDialogCancel> | |
| <AlertDialogAction | |
| onClick={handleDelete} | |
| className="bg-destructive text-destructive-foreground hover:bg-destructive/90" | |
| > | |
| {isArabic ? 'حذف نهائي' : 'Delete Permanently'} | |
| </AlertDialogAction> | |
| </AlertDialogFooter> | |
| </AlertDialogContent> | |
| </AlertDialog> | |
| </div> | |
| </div> | |
| {/* Main User Card */} | |
| <Card className="border shadow-sm"> | |
| <CardContent className="pt-6"> | |
| <div className="flex flex-col sm:flex-row gap-6"> | |
| {/* Avatar */} | |
| <div className="shrink-0"> | |
| <div className="w-24 h-24 rounded-full bg-gradient-to-br from-emerald-500 to-teal-600 flex items-center justify-center text-white text-4xl font-bold shadow-md"> | |
| {(isStudent ? student?.name : sheikh?.name)?.charAt(0).toUpperCase() || '?'} | |
| </div> | |
| </div> | |
| {/* Info */} | |
| <div className="flex-1 space-y-4"> | |
| <div className="flex flex-wrap items-center gap-3"> | |
| <h2 className="text-2xl font-bold">{isStudent ? student?.name : sheikh?.name}</h2> | |
| <Badge variant={isStudent ? 'default' : isSheikh ? 'secondary' : 'destructive'} className="px-3 py-1"> | |
| {userType.toUpperCase()} | |
| </Badge> | |
| <Badge | |
| variant={!isBlocked ? 'outline' : 'destructive'} | |
| className={!isBlocked ? 'text-emerald-600 border-emerald-600' : ''} | |
| > | |
| {!isBlocked ? ( | |
| <> | |
| <CheckCircle className="h-3 w-3 mr-1" /> | |
| {isArabic ? 'نشط' : 'Active'} | |
| </> | |
| ) : ( | |
| <> | |
| <Ban className="h-3 w-3 mr-1" /> | |
| {isArabic ? 'محظور' : 'Blocked'} | |
| </> | |
| )} | |
| </Badge> | |
| </div> | |
| <div className="grid gap-2 text-sm text-muted-foreground"> | |
| <div className="flex items-center gap-2"> | |
| <Mail className="h-4 w-4" /> | |
| {isStudent ? student?.email : sheikh?.email} | |
| </div> | |
| {isSheikh && sheikh?.country && ( | |
| <div className="flex items-center gap-2"> | |
| <MapPin className="h-4 w-4" /> | |
| {sheikh.country} | |
| </div> | |
| )} | |
| <div className="flex items-center gap-2"> | |
| <Calendar className="h-4 w-4" /> | |
| {isArabic ? 'انضم في ' : 'Joined '} | |
| {formatDate( | |
| isStudent ? student?.registrationDate || '' : sheikh?.registrationDate || '', | |
| language | |
| )} | |
| </div> | |
| {isSheikh && sheikh?.bio && ( | |
| <div className="mt-2 text-sm border-t pt-2"> | |
| <p className="text-muted-foreground">{sheikh.bio}</p> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| {/* Right side stats */} | |
| {isSheikh && sheikh && ( | |
| <div className="text-right space-y-2 min-w-[180px]"> | |
| <div className="text-3xl font-bold text-emerald-600"> | |
| {formatCurrency(sheikh.totalEarnings, language)} | |
| </div> | |
| <div className="text-sm text-muted-foreground">{isArabic ? 'الأرباح' : 'Earnings'}</div> | |
| <div className="flex items-center justify-end gap-1 text-yellow-600"> | |
| <Star className="h-5 w-5 fill-yellow-500 text-yellow-500" /> | |
| <span className="font-bold text-xl">{formatRating(sheikh.averageRating)}</span> | |
| </div> | |
| </div> | |
| )} | |
| {isStudent && student && ( | |
| <div className="text-right space-y-2 min-w-[180px]"> | |
| <div className="text-3xl font-bold">{formatNumber(student.sessionCount, language)}</div> | |
| <div className="text-sm text-muted-foreground">{isArabic ? 'الجلسات' : 'Sessions'}</div> | |
| <div className="text-2xl font-bold text-orange-600"> | |
| {student.streak ?? 0} <span className="text-xl">🔥</span> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| {/* Statistics Cards Grid */} | |
| {isStudent && student && ( | |
| <div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4"> | |
| <Card className="hover:shadow-md transition-shadow"> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0"> | |
| <CardTitle className="text-sm font-medium">{isArabic ? 'إجمالي الجلسات' : 'Total Sessions'}</CardTitle> | |
| <BookOpen className="w-4 h-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-bold">{formatNumber(student.sessionCount, language)}</div> | |
| </CardContent> | |
| </Card> | |
| <Card className="hover:shadow-md transition-shadow"> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0"> | |
| <CardTitle className="text-sm font-medium">{isArabic ? 'الجلسات المكتملة' : 'Completed Sessions'}</CardTitle> | |
| <CheckCircle className="w-4 h-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-bold">{formatNumber(student.numberOfCompletedSessions, language)}</div> | |
| </CardContent> | |
| </Card> | |
| <Card className="hover:shadow-md transition-shadow"> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0"> | |
| <CardTitle className="text-sm font-medium">{isArabic ? 'وقت التعلم' : 'Learning Time'}</CardTitle> | |
| <Clock className="w-4 h-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-bold">{formatDuration(student.totalSpentTime)}</div> | |
| </CardContent> | |
| </Card> | |
| <Card className="hover:shadow-md transition-shadow"> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0"> | |
| <CardTitle className="text-sm font-medium">{isArabic ? 'السلسلة' : 'Streak'}</CardTitle> | |
| <Target className="w-4 h-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-bold text-orange-600"> | |
| {student.streak ?? 0} <span className="text-xl">🔥</span> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| )} | |
| {isSheikh && sheikh && ( | |
| <div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4"> | |
| <Card className="hover:shadow-md transition-shadow"> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0"> | |
| <CardTitle className="text-sm font-medium">{isArabic ? 'إجمالي الجلسات' : 'Total Sessions'}</CardTitle> | |
| <Video className="w-4 h-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-bold">{formatNumber(sheikh.numberOfCompletedSessions, language)}</div> | |
| </CardContent> | |
| </Card> | |
| <Card className="hover:shadow-md transition-shadow"> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0"> | |
| <CardTitle className="text-sm font-medium">{isArabic ? 'عدد الطلاب' : 'Students'}</CardTitle> | |
| <Users className="w-4 h-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-bold">{formatNumber(sheikh.numberOfStudents, language)}</div> | |
| </CardContent> | |
| </Card> | |
| <Card className="hover:shadow-md transition-shadow"> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0"> | |
| <CardTitle className="text-sm font-medium">{isArabic ? 'التقييم' : 'Rating'}</CardTitle> | |
| <Star className="w-4 h-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-bold text-yellow-600">{formatRating(sheikh.averageRating)}</div> | |
| </CardContent> | |
| </Card> | |
| <Card className="hover:shadow-md transition-shadow"> | |
| <CardHeader className="flex flex-row items-center justify-between pb-2 space-y-0"> | |
| <CardTitle className="text-sm font-medium">{isArabic ? 'سعر الساعة' : 'Hourly Rate'}</CardTitle> | |
| <DollarSign className="w-4 h-4 text-muted-foreground" /> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-bold">{formatCurrency(sheikh.hourlyRate, language)}</div> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| )} | |
| {/* Session History */} | |
| <Card> | |
| <CardHeader> | |
| <CardTitle>{isArabic ? 'سجل الجلسات' : 'Session History'}</CardTitle> | |
| <CardDescription> | |
| {isArabic ? 'آخر الجلسات والأنشطة' : 'Recent sessions and activities'} | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| {sessions.length === 0 ? ( | |
| <div className="text-center py-8 text-muted-foreground"> | |
| {isArabic ? 'لا توجد جلسات سابقة' : 'No session history'} | |
| </div> | |
| ) : ( | |
| <div className="space-y-4"> | |
| {sessions.map((session) => ( | |
| <div | |
| key={session.sessionId} | |
| className="flex items-center justify-between p-4 border rounded-lg hover:bg-muted/50 transition-colors" | |
| > | |
| <div className="flex items-center gap-4"> | |
| <div | |
| className={`w-10 h-10 rounded-full flex items-center justify-center ${ | |
| session.status === 'COMPLETED' ? 'bg-emerald-100' : 'bg-red-100' | |
| }`} | |
| > | |
| <Video | |
| className={`h-5 w-5 ${ | |
| session.status === 'COMPLETED' ? 'text-emerald-600' : 'text-red-600' | |
| }`} | |
| /> | |
| </div> | |
| <div> | |
| <p className="font-medium"> | |
| {isStudent ? session.sheikhName : session.studentName} | |
| </p> | |
| <p className="text-sm text-muted-foreground mt-0.5"> | |
| {formatDate(session.date, language)} | |
| {session.durationInMinutes && ( | |
| <span className="ml-2">· {formatDuration(session.durationInMinutes)}</span> | |
| )} | |
| </p> | |
| </div> | |
| </div> | |
| <div className="text-right"> | |
| <p className="font-semibold">{formatCurrency(session.price, language)}</p> | |
| <Badge | |
| variant={session.status === 'COMPLETED' ? 'default' : 'destructive'} | |
| className="mt-1" | |
| > | |
| {session.status === 'COMPLETED' | |
| ? (isArabic ? 'مكتملة' : 'Completed') | |
| : session.status === 'CANCELLED' | |
| ? (isArabic ? 'ملغية' : 'Cancelled') | |
| : (isArabic ? 'معلقة' : 'Pending')} | |
| </Badge> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ); | |
| } |