2026-02-15 01:53:37 +03:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useState } from 'react';
|
2026-03-27 00:34:59 +03:00
|
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
2026-02-15 01:53:37 +03:00
|
|
|
import {
|
|
|
|
|
User,
|
|
|
|
|
Mail,
|
|
|
|
|
Phone,
|
|
|
|
|
Calendar,
|
|
|
|
|
Home,
|
|
|
|
|
DollarSign,
|
|
|
|
|
Search,
|
|
|
|
|
Filter,
|
2026-03-27 00:34:59 +03:00
|
|
|
Eye,
|
|
|
|
|
X,
|
|
|
|
|
CheckCircle,
|
|
|
|
|
XCircle,
|
|
|
|
|
ChevronDown,
|
|
|
|
|
Users,
|
|
|
|
|
Award,
|
|
|
|
|
Clock,
|
|
|
|
|
TrendingUp,
|
|
|
|
|
CalendarDays,
|
|
|
|
|
Shield
|
2026-02-15 01:53:37 +03:00
|
|
|
} from 'lucide-react';
|
2026-03-27 00:34:59 +03:00
|
|
|
import toast, { Toaster } from 'react-hot-toast';
|
|
|
|
|
|
|
|
|
|
const FilterDialog = ({ isOpen, onClose, filters, onApplyFilters, onResetFilters }) => {
|
|
|
|
|
const [localFilters, setLocalFilters] = useState({ ...filters });
|
|
|
|
|
|
|
|
|
|
const identityTypes = [
|
|
|
|
|
{ id: 'all', label: 'الكل' },
|
|
|
|
|
{ id: 'syrian', label: 'هوية سورية' },
|
|
|
|
|
{ id: 'passport', label: 'جواز سفر' }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const bookingRanges = [
|
|
|
|
|
{ id: 'all', label: 'الكل' },
|
|
|
|
|
{ id: '0-5', label: '0 - 5 حجوزات' },
|
|
|
|
|
{ id: '5-10', label: '5 - 10 حجوزات' },
|
|
|
|
|
{ id: '10-20', label: '10 - 20 حجوزات' },
|
|
|
|
|
{ id: '20+', label: 'أكثر من 20 حجز' }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const spendingRanges = [
|
|
|
|
|
{ id: 'all', label: 'الكل' },
|
|
|
|
|
{ id: '0-500000', label: 'أقل من 500,000 ل.س' },
|
|
|
|
|
{ id: '500000-1000000', label: '500,000 - 1,000,000 ل.س' },
|
|
|
|
|
{ id: '1000000-5000000', label: '1,000,000 - 5,000,000 ل.س' },
|
|
|
|
|
{ id: '5000000+', label: 'أكثر من 5,000,000 ل.س' }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const dateRanges = [
|
|
|
|
|
{ id: 'all', label: 'الكل' },
|
|
|
|
|
{ id: 'today', label: 'اليوم' },
|
|
|
|
|
{ id: 'week', label: 'آخر 7 أيام' },
|
|
|
|
|
{ id: 'month', label: 'آخر 30 يوم' },
|
|
|
|
|
{ id: 'year', label: 'آخر 12 شهر' }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const applyFilters = () => {
|
|
|
|
|
onApplyFilters(localFilters);
|
|
|
|
|
onClose();
|
|
|
|
|
toast.success('تم تطبيق الفلاتر بنجاح');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resetFilters = () => {
|
|
|
|
|
const resetData = {
|
|
|
|
|
identityType: 'all',
|
|
|
|
|
minBookings: '',
|
|
|
|
|
maxBookings: '',
|
|
|
|
|
minSpending: '',
|
|
|
|
|
maxSpending: '',
|
|
|
|
|
dateRange: 'all',
|
|
|
|
|
activeOnly: false,
|
|
|
|
|
inactiveOnly: false
|
|
|
|
|
};
|
|
|
|
|
setLocalFilters(resetData);
|
|
|
|
|
onResetFilters();
|
|
|
|
|
onClose();
|
|
|
|
|
toast.success('تم إعادة تعيين الفلاتر');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!isOpen) return null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
exit={{ opacity: 0 }}
|
|
|
|
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50"
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
>
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ scale: 0.9, y: 20 }}
|
|
|
|
|
animate={{ scale: 1, y: 0 }}
|
|
|
|
|
exit={{ scale: 0.9, y: 20 }}
|
|
|
|
|
className="bg-white rounded-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto shadow-2xl"
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
>
|
|
|
|
|
<div className="sticky top-0 bg-gradient-to-r from-blue-600 to-blue-700 p-6 text-white">
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-xl font-bold flex items-center gap-2">
|
|
|
|
|
<Filter className="w-5 h-5" />
|
|
|
|
|
تصفية متقدمة
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-blue-100 text-sm mt-1">حدد معايير التصفية المطلوبة</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button onClick={onClose} className="p-1 hover:bg-white/20 rounded-full">
|
|
|
|
|
<X className="w-6 h-6" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="p-6 space-y-6">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
نوع الهوية
|
|
|
|
|
</label>
|
|
|
|
|
<div className="grid grid-cols-3 gap-2">
|
|
|
|
|
{identityTypes.map((type) => (
|
|
|
|
|
<button
|
|
|
|
|
key={type.id}
|
|
|
|
|
onClick={() => setLocalFilters({...localFilters, identityType: type.id})}
|
|
|
|
|
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all ${
|
|
|
|
|
localFilters.identityType === type.id
|
|
|
|
|
? 'bg-blue-600 text-white shadow-md'
|
|
|
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{type.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
عدد الحجوزات
|
|
|
|
|
</label>
|
|
|
|
|
<div className="grid grid-cols-2 gap-2 mb-3">
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
placeholder="من"
|
|
|
|
|
value={localFilters.minBookings}
|
|
|
|
|
onChange={(e) => setLocalFilters({...localFilters, minBookings: e.target.value})}
|
|
|
|
|
className="px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
placeholder="إلى"
|
|
|
|
|
value={localFilters.maxBookings}
|
|
|
|
|
onChange={(e) => setLocalFilters({...localFilters, maxBookings: e.target.value})}
|
|
|
|
|
className="px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
{bookingRanges.slice(1).map((range) => (
|
|
|
|
|
<button
|
|
|
|
|
key={range.id}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
const [min, max] = range.id.split('-');
|
|
|
|
|
setLocalFilters({
|
|
|
|
|
...localFilters,
|
|
|
|
|
minBookings: min,
|
|
|
|
|
maxBookings: max === '5' ? '5' : max === '10' ? '10' : max === '20' ? '20' : '1000'
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
className="px-3 py-1 text-xs bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200"
|
|
|
|
|
>
|
|
|
|
|
{range.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
إجمالي الإنفاق (ل.س)
|
|
|
|
|
</label>
|
|
|
|
|
<div className="grid grid-cols-2 gap-2 mb-3">
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
placeholder="من"
|
|
|
|
|
value={localFilters.minSpending}
|
|
|
|
|
onChange={(e) => setLocalFilters({...localFilters, minSpending: e.target.value})}
|
|
|
|
|
className="px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
placeholder="إلى"
|
|
|
|
|
value={localFilters.maxSpending}
|
|
|
|
|
onChange={(e) => setLocalFilters({...localFilters, maxSpending: e.target.value})}
|
|
|
|
|
className="px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
{spendingRanges.slice(1).map((range) => (
|
|
|
|
|
<button
|
|
|
|
|
key={range.id}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
const [min, max] = range.id.split('-');
|
|
|
|
|
setLocalFilters({
|
|
|
|
|
...localFilters,
|
|
|
|
|
minSpending: min,
|
|
|
|
|
maxSpending: max === '500000' ? '500000' : max === '1000000' ? '1000000' : max === '5000000' ? '5000000' : '999999999'
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
className="px-3 py-1 text-xs bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200"
|
|
|
|
|
>
|
|
|
|
|
{range.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
فترة التسجيل
|
|
|
|
|
</label>
|
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
|
|
|
|
|
{dateRanges.map((range) => (
|
|
|
|
|
<button
|
|
|
|
|
key={range.id}
|
|
|
|
|
onClick={() => setLocalFilters({...localFilters, dateRange: range.id})}
|
|
|
|
|
className={`px-3 py-2 rounded-xl text-sm font-medium transition-all ${
|
|
|
|
|
localFilters.dateRange === range.id
|
|
|
|
|
? 'bg-blue-600 text-white'
|
|
|
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{range.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-4">
|
|
|
|
|
<label className="flex items-center gap-2 cursor-pointer">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={localFilters.activeOnly}
|
|
|
|
|
onChange={(e) => setLocalFilters({...localFilters, activeOnly: e.target.checked, inactiveOnly: false})}
|
|
|
|
|
className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
<span className="text-sm text-gray-700">مستخدمون لديهم حجوزات نشطة فقط</span>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="flex items-center gap-2 cursor-pointer">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={localFilters.inactiveOnly}
|
|
|
|
|
onChange={(e) => setLocalFilters({...localFilters, inactiveOnly: e.target.checked, activeOnly: false})}
|
|
|
|
|
className="w-4 h-4 text-blue-600 rounded focus:ring-blue-500"
|
|
|
|
|
/>
|
|
|
|
|
<span className="text-sm text-gray-700">مستخدمون بدون حجوزات نشطة</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="sticky bottom-0 bg-gray-50 border-t p-4 flex gap-3">
|
|
|
|
|
<button
|
|
|
|
|
onClick={resetFilters}
|
|
|
|
|
className="flex-1 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
إعادة تعيين
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={applyFilters}
|
|
|
|
|
className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-medium hover:bg-blue-700 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
تطبيق الفلاتر
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const UserDetailsModal = ({ user, isOpen, onClose }) => {
|
|
|
|
|
if (!isOpen || !user) return null;
|
|
|
|
|
|
|
|
|
|
const formatCurrency = (amount) => {
|
|
|
|
|
return amount?.toLocaleString() + ' ل.س';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const userBookings = [
|
|
|
|
|
{
|
|
|
|
|
id: 'BK001',
|
|
|
|
|
property: 'فيلا فاخرة في المزة',
|
|
|
|
|
startDate: '2024-03-10',
|
|
|
|
|
endDate: '2024-03-15',
|
|
|
|
|
amount: 2500000,
|
|
|
|
|
status: 'completed'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'BK002',
|
|
|
|
|
property: 'شقة حديثة في الشهباء',
|
|
|
|
|
startDate: '2024-02-20',
|
|
|
|
|
endDate: '2024-02-25',
|
|
|
|
|
amount: 1250000,
|
|
|
|
|
status: 'completed'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'BK003',
|
|
|
|
|
property: 'بيت عائلي في بابا عمرو',
|
|
|
|
|
startDate: '2024-04-01',
|
|
|
|
|
endDate: '2024-04-10',
|
|
|
|
|
amount: 3500000,
|
|
|
|
|
status: 'confirmed'
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
exit={{ opacity: 0 }}
|
|
|
|
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4 z-50"
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
>
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ scale: 0.9, y: 20 }}
|
|
|
|
|
animate={{ scale: 1, y: 0 }}
|
|
|
|
|
exit={{ scale: 0.9, y: 20 }}
|
|
|
|
|
className="bg-white rounded-2xl w-full max-w-3xl max-h-[90vh] overflow-y-auto shadow-2xl"
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
>
|
|
|
|
|
<div className="sticky top-0 bg-gradient-to-r from-blue-600 to-blue-700 p-6 text-white">
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-xl font-bold flex items-center gap-2">
|
|
|
|
|
<User className="w-5 h-5" />
|
|
|
|
|
تفاصيل المستخدم
|
|
|
|
|
</h2>
|
|
|
|
|
<p className="text-blue-100 text-sm mt-1">{user.name}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button onClick={onClose} className="p-1 hover:bg-white/20 rounded-full">
|
|
|
|
|
<X className="w-6 h-6" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="p-6 space-y-6">
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
|
<div className="bg-gray-50 p-4 rounded-xl">
|
|
|
|
|
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
|
|
|
|
<User className="w-4 h-4 text-blue-500" />
|
|
|
|
|
معلومات شخصية
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">الاسم الكامل:</span>
|
|
|
|
|
<span className="font-medium">{user.name}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">البريد الإلكتروني:</span>
|
|
|
|
|
<span className="font-medium">{user.email}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">رقم الهاتف:</span>
|
|
|
|
|
<span className="font-medium">{user.phone}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">تاريخ التسجيل:</span>
|
|
|
|
|
<span className="font-medium">{user.joinDate}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="bg-gray-50 p-4 rounded-xl">
|
|
|
|
|
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
|
|
|
|
<Shield className="w-4 h-4 text-blue-500" />
|
|
|
|
|
معلومات الهوية
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">نوع الهوية:</span>
|
|
|
|
|
<span className="font-medium">
|
|
|
|
|
{user.identityType === 'syrian' ? 'هوية سورية' : 'جواز سفر'}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
|
<span className="text-gray-500">رقم الهوية:</span>
|
|
|
|
|
<span className="font-medium">{user.identityNumber}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
|
|
<div className="bg-blue-50 p-4 rounded-xl text-center">
|
|
|
|
|
<div className="text-2xl font-bold text-blue-600">{user.totalBookings}</div>
|
|
|
|
|
<div className="text-sm text-gray-600">إجمالي الحجوزات</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="bg-green-50 p-4 rounded-xl text-center">
|
|
|
|
|
<div className="text-2xl font-bold text-green-600">{user.activeBookings}</div>
|
|
|
|
|
<div className="text-sm text-gray-600">حجوزات نشطة</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="bg-amber-50 p-4 rounded-xl text-center">
|
|
|
|
|
<div className="text-2xl font-bold text-amber-600">{formatCurrency(user.totalSpent)}</div>
|
|
|
|
|
<div className="text-sm text-gray-600">إجمالي المنصرف</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
|
|
|
|
<Calendar className="w-4 h-4 text-blue-500" />
|
|
|
|
|
سجل الحجوزات
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
{userBookings.map((booking) => (
|
|
|
|
|
<div key={booking.id} className="bg-gray-50 p-4 rounded-xl flex flex-col md:flex-row justify-between items-start md:items-center gap-3">
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-medium text-gray-900">{booking.property}</p>
|
|
|
|
|
<div className="flex items-center gap-2 text-sm text-gray-500 mt-1">
|
|
|
|
|
<CalendarDays className="w-3 h-3" />
|
|
|
|
|
{booking.startDate} - {booking.endDate}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
<div className="text-right">
|
|
|
|
|
<div className="text-lg font-bold text-amber-600">{formatCurrency(booking.amount)}</div>
|
|
|
|
|
<div className="text-xs text-gray-500">المبلغ الإجمالي</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${
|
|
|
|
|
booking.status === 'completed'
|
|
|
|
|
? 'bg-green-100 text-green-800'
|
|
|
|
|
: 'bg-blue-100 text-blue-800'
|
|
|
|
|
}`}>
|
|
|
|
|
{booking.status === 'completed' ? 'مكتمل' : 'مؤكد'}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
{userBookings.length === 0 && (
|
|
|
|
|
<div className="text-center py-8 text-gray-500">
|
|
|
|
|
<Calendar className="w-12 h-12 text-gray-300 mx-auto mb-2" />
|
|
|
|
|
<p>لا توجد حجوزات سابقة</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="sticky bottom-0 bg-gray-50 border-t p-4 flex gap-3">
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
);
|
|
|
|
|
};
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
|
|
|
export default function UsersList() {
|
|
|
|
|
const [users, setUsers] = useState([
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
|
|
|
|
name: 'أحمد محمد',
|
|
|
|
|
email: 'ahmed@example.com',
|
|
|
|
|
phone: '0938123456',
|
|
|
|
|
identityType: 'syrian',
|
|
|
|
|
identityNumber: '123456789',
|
|
|
|
|
joinDate: '2024-01-15',
|
|
|
|
|
totalBookings: 3,
|
|
|
|
|
activeBookings: 1,
|
|
|
|
|
totalSpent: 1500000
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 2,
|
|
|
|
|
name: 'سارة أحمد',
|
|
|
|
|
email: 'sara@example.com',
|
|
|
|
|
phone: '0945123789',
|
|
|
|
|
identityType: 'passport',
|
|
|
|
|
identityNumber: 'AB123456',
|
|
|
|
|
joinDate: '2024-02-10',
|
|
|
|
|
totalBookings: 2,
|
|
|
|
|
activeBookings: 0,
|
|
|
|
|
totalSpent: 500000
|
|
|
|
|
}
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
|
const [selectedUser, setSelectedUser] = useState(null);
|
2026-03-27 00:34:59 +03:00
|
|
|
const [showFilterDialog, setShowFilterDialog] = useState(false);
|
|
|
|
|
const [filters, setFilters] = useState({
|
|
|
|
|
identityType: 'all',
|
|
|
|
|
minBookings: '',
|
|
|
|
|
maxBookings: '',
|
|
|
|
|
minSpending: '',
|
|
|
|
|
maxSpending: '',
|
|
|
|
|
dateRange: 'all',
|
|
|
|
|
activeOnly: false,
|
|
|
|
|
inactiveOnly: false
|
|
|
|
|
});
|
2026-02-15 01:53:37 +03:00
|
|
|
|
2026-03-27 00:34:59 +03:00
|
|
|
const applyFilters = (newFilters) => {
|
|
|
|
|
setFilters(newFilters);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resetFilters = () => {
|
|
|
|
|
setFilters({
|
|
|
|
|
identityType: 'all',
|
|
|
|
|
minBookings: '',
|
|
|
|
|
maxBookings: '',
|
|
|
|
|
minSpending: '',
|
|
|
|
|
maxSpending: '',
|
|
|
|
|
dateRange: 'all',
|
|
|
|
|
activeOnly: false,
|
|
|
|
|
inactiveOnly: false
|
|
|
|
|
});
|
|
|
|
|
setSearchTerm('');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const filteredUsers = users.filter(user => {
|
|
|
|
|
if (searchTerm && !user.name.includes(searchTerm) && !user.email.includes(searchTerm) && !user.phone.includes(searchTerm)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filters.identityType !== 'all' && user.identityType !== filters.identityType) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filters.minBookings && user.totalBookings < parseInt(filters.minBookings)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (filters.maxBookings && user.totalBookings > parseInt(filters.maxBookings)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filters.minSpending && user.totalSpent < parseInt(filters.minSpending)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (filters.maxSpending && user.totalSpent > parseInt(filters.maxSpending)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filters.activeOnly && user.activeBookings === 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (filters.inactiveOnly && user.activeBookings > 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filters.dateRange !== 'all') {
|
|
|
|
|
const joinDate = new Date(user.joinDate);
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const diffDays = Math.floor((today - joinDate) / (1000 * 60 * 60 * 24));
|
|
|
|
|
|
|
|
|
|
switch(filters.dateRange) {
|
|
|
|
|
case 'today':
|
|
|
|
|
if (joinDate.toDateString() !== today.toDateString()) return false;
|
|
|
|
|
break;
|
|
|
|
|
case 'week':
|
|
|
|
|
if (diffDays > 7) return false;
|
|
|
|
|
break;
|
|
|
|
|
case 'month':
|
|
|
|
|
if (diffDays > 30) return false;
|
|
|
|
|
break;
|
|
|
|
|
case 'year':
|
|
|
|
|
if (diffDays > 365) return false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const filterStats = {
|
|
|
|
|
total: filteredUsers.length,
|
|
|
|
|
filtered: filteredUsers.length !== users.length
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getActiveFiltersCount = () => {
|
|
|
|
|
let count = 0;
|
|
|
|
|
if (filters.identityType !== 'all') count++;
|
|
|
|
|
if (filters.minBookings || filters.maxBookings) count++;
|
|
|
|
|
if (filters.minSpending || filters.maxSpending) count++;
|
|
|
|
|
if (filters.dateRange !== 'all') count++;
|
|
|
|
|
if (filters.activeOnly || filters.inactiveOnly) count++;
|
|
|
|
|
return count;
|
|
|
|
|
};
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-4">
|
2026-03-27 00:34:59 +03:00
|
|
|
<Toaster position="top-center" reverseOrder={false} />
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col md:flex-row gap-4">
|
2026-02-15 01:53:37 +03:00
|
|
|
<div className="flex-1 relative">
|
2026-03-27 00:34:59 +03:00
|
|
|
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
2026-02-15 01:53:37 +03:00
|
|
|
<input
|
|
|
|
|
type="text"
|
2026-03-27 00:34:59 +03:00
|
|
|
placeholder="بحث عن مستخدم بالاسم أو البريد أو الهاتف..."
|
2026-02-15 01:53:37 +03:00
|
|
|
value={searchTerm}
|
|
|
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
2026-03-27 00:34:59 +03:00
|
|
|
className="w-full pr-12 px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500"
|
2026-02-15 01:53:37 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-03-27 00:34:59 +03:00
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setShowFilterDialog(true)}
|
|
|
|
|
className={`px-5 py-3 rounded-xl font-medium flex items-center gap-2 transition-all ${
|
|
|
|
|
getActiveFiltersCount() > 0
|
|
|
|
|
? 'bg-blue-600 text-white hover:bg-blue-700'
|
|
|
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<Filter className="w-5 h-5" />
|
|
|
|
|
تصفية متقدمة
|
|
|
|
|
{getActiveFiltersCount() > 0 && (
|
|
|
|
|
<span className="ml-1 bg-white text-blue-600 rounded-full w-5 h-5 text-xs flex items-center justify-center">
|
|
|
|
|
{getActiveFiltersCount()}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
{filterStats.filtered && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={resetFilters}
|
|
|
|
|
className="px-5 py-3 bg-gray-100 text-gray-700 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<X className="w-4 h-4" />
|
|
|
|
|
إعادة تعيين
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{getActiveFiltersCount() > 0 && (
|
|
|
|
|
<div className="flex flex-wrap gap-2 p-3 bg-blue-50 rounded-xl">
|
|
|
|
|
<span className="text-sm text-blue-800 font-medium">الفلاتر النشطة:</span>
|
|
|
|
|
{filters.identityType !== 'all' && (
|
|
|
|
|
<span className="px-2 py-1 bg-blue-100 text-blue-700 rounded-lg text-xs">
|
|
|
|
|
{filters.identityType === 'syrian' ? 'هوية سورية' : 'جواز سفر'}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{(filters.minBookings || filters.maxBookings) && (
|
|
|
|
|
<span className="px-2 py-1 bg-blue-100 text-blue-700 rounded-lg text-xs">
|
|
|
|
|
الحجوزات: {filters.minBookings || '0'} - {filters.maxBookings || '∞'}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{(filters.minSpending || filters.maxSpending) && (
|
|
|
|
|
<span className="px-2 py-1 bg-blue-100 text-blue-700 rounded-lg text-xs">
|
|
|
|
|
الإنفاق: {parseInt(filters.minSpending || 0).toLocaleString()} - {parseInt(filters.maxSpending || '∞').toLocaleString()} ل.س
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{filters.dateRange !== 'all' && (
|
|
|
|
|
<span className="px-2 py-1 bg-blue-100 text-blue-700 rounded-lg text-xs">
|
|
|
|
|
{filters.dateRange === 'today' ? 'اليوم' :
|
|
|
|
|
filters.dateRange === 'week' ? 'آخر 7 أيام' :
|
|
|
|
|
filters.dateRange === 'month' ? 'آخر 30 يوم' : 'آخر 12 شهر'}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{filters.activeOnly && (
|
|
|
|
|
<span className="px-2 py-1 bg-green-100 text-green-700 rounded-lg text-xs">
|
|
|
|
|
لديهم حجوزات نشطة
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{filters.inactiveOnly && (
|
|
|
|
|
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs">
|
|
|
|
|
بدون حجوزات نشطة
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
<div className="text-sm text-gray-600">
|
|
|
|
|
عرض <span className="font-bold text-gray-900">{filteredUsers.length}</span> مستخدم
|
|
|
|
|
{filterStats.filtered && (
|
|
|
|
|
<span className="text-gray-500 mr-1">(من {users.length})</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-02-15 01:53:37 +03:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
{filteredUsers.map((user, index) => (
|
|
|
|
|
<motion.div
|
|
|
|
|
key={user.id}
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
2026-03-27 00:34:59 +03:00
|
|
|
transition={{ delay: index * 0.05 }}
|
|
|
|
|
className="bg-white border border-gray-200 rounded-xl p-5 hover:shadow-md transition-all"
|
2026-02-15 01:53:37 +03:00
|
|
|
>
|
|
|
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
|
|
|
<div className="flex items-center gap-4">
|
2026-03-27 00:34:59 +03:00
|
|
|
<div className="w-14 h-14 bg-gradient-to-br from-blue-500 to-blue-600 rounded-full flex items-center justify-center text-white text-xl font-bold shadow-lg">
|
|
|
|
|
{user.name.charAt(0).toUpperCase()}
|
2026-02-15 01:53:37 +03:00
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-03-27 00:34:59 +03:00
|
|
|
<h3 className="font-bold text-gray-900 text-lg">{user.name}</h3>
|
|
|
|
|
<div className="flex flex-wrap gap-3 mt-1 text-sm text-gray-500">
|
2026-02-15 01:53:37 +03:00
|
|
|
<div className="flex items-center gap-1">
|
2026-03-27 00:34:59 +03:00
|
|
|
<Mail className="w-4 h-4" />
|
2026-02-15 01:53:37 +03:00
|
|
|
{user.email}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-1">
|
2026-03-27 00:34:59 +03:00
|
|
|
<Phone className="w-4 h-4" />
|
2026-02-15 01:53:37 +03:00
|
|
|
{user.phone}
|
|
|
|
|
</div>
|
2026-03-27 00:34:59 +03:00
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
<Shield className="w-4 h-4" />
|
|
|
|
|
{user.identityType === 'syrian' ? 'هوية سورية' : 'جواز سفر'}
|
|
|
|
|
</div>
|
2026-02-15 01:53:37 +03:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-27 00:34:59 +03:00
|
|
|
<div className="flex gap-6">
|
|
|
|
|
<div className="text-center min-w-[80px]">
|
|
|
|
|
<div className="text-xl font-bold text-blue-600">{user.totalBookings}</div>
|
2026-02-15 01:53:37 +03:00
|
|
|
<div className="text-xs text-gray-500">إجمالي الحجوزات</div>
|
|
|
|
|
</div>
|
2026-03-27 00:34:59 +03:00
|
|
|
<div className="text-center min-w-[80px]">
|
|
|
|
|
<div className={`text-xl font-bold ${user.activeBookings > 0 ? 'text-green-600' : 'text-gray-400'}`}>
|
|
|
|
|
{user.activeBookings}
|
|
|
|
|
</div>
|
2026-02-15 01:53:37 +03:00
|
|
|
<div className="text-xs text-gray-500">حجوزات نشطة</div>
|
|
|
|
|
</div>
|
2026-03-27 00:34:59 +03:00
|
|
|
<div className="text-center min-w-[100px]">
|
|
|
|
|
<div className="text-xl font-bold text-amber-600">
|
2026-02-15 01:53:37 +03:00
|
|
|
{user.totalSpent.toLocaleString()}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs text-gray-500">إجمالي المنصرف</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setSelectedUser(user)}
|
2026-03-27 00:34:59 +03:00
|
|
|
className="px-4 py-2 bg-blue-600 text-white rounded-xl text-sm font-medium hover:bg-blue-700 transition-colors flex items-center gap-2"
|
2026-02-15 01:53:37 +03:00
|
|
|
>
|
|
|
|
|
<Eye className="w-4 h-4" />
|
|
|
|
|
عرض التفاصيل
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-27 00:34:59 +03:00
|
|
|
{filteredUsers.length === 0 && (
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
|
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
|
|
|
className="text-center py-16 bg-white rounded-2xl border-2 border-dashed border-gray-300"
|
|
|
|
|
>
|
|
|
|
|
<Users className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
|
|
|
|
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد نتائج</h3>
|
|
|
|
|
<p className="text-gray-500">لا يوجد مستخدمون يطابقون معايير البحث</p>
|
|
|
|
|
{(searchTerm || getActiveFiltersCount() > 0) && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={resetFilters}
|
|
|
|
|
className="mt-4 px-6 py-2 bg-blue-600 text-white rounded-xl text-sm font-medium hover:bg-blue-700"
|
|
|
|
|
>
|
|
|
|
|
إعادة تعيين الفلاتر
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
2026-03-27 00:34:59 +03:00
|
|
|
<FilterDialog
|
|
|
|
|
isOpen={showFilterDialog}
|
|
|
|
|
onClose={() => setShowFilterDialog(false)}
|
|
|
|
|
filters={filters}
|
|
|
|
|
onApplyFilters={applyFilters}
|
|
|
|
|
onResetFilters={resetFilters}
|
|
|
|
|
/>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
2026-03-27 00:34:59 +03:00
|
|
|
<UserDetailsModal
|
|
|
|
|
user={selectedUser}
|
|
|
|
|
isOpen={!!selectedUser}
|
|
|
|
|
onClose={() => setSelectedUser(null)}
|
|
|
|
|
/>
|
2026-02-15 01:53:37 +03:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|