@@ -332,12 +756,12 @@ const RequestCard = ({ request, onAction, onViewDetails }) => {
};
const labels = {
- pending: ' بانتظار الموافقة',
- owner_approved: ' موافقة المالك',
- admin_approved: ' موافقة الإدارة',
- active: ' إيجار نشط',
- completed: ' منتهي',
- rejected: ' مرفوض'
+ pending: 'بانتظار الموافقة',
+ owner_approved: 'موافقة المالك',
+ admin_approved: 'موافقة الإدارة',
+ active: 'إيجار نشط',
+ completed: 'منتهي',
+ rejected: 'مرفوض'
};
return (
@@ -840,6 +1264,9 @@ export default function BookingRequests() {
return (
+
+
+ {/* إحصائيات سريعة */}
setDetailsDialog({ isOpen: true, request })}
/>
))}
diff --git a/app/components/admin/LedgerBook.js b/app/components/admin/LedgerBook.js
index 65bef51..fe86650 100644
--- a/app/components/admin/LedgerBook.js
+++ b/app/components/admin/LedgerBook.js
@@ -13,9 +13,15 @@ import {
TrendingUp,
TrendingDown,
Wallet,
- Shield
+ Shield,
+ FileText,
+ Printer,
+ X,
+ CheckCircle
} from 'lucide-react';
import { formatCurrency } from '@/app/utils/calculations';
+import toast, { Toaster } from 'react-hot-toast';
+import * as XLSX from 'xlsx';
export default function LedgerBook({ userType = 'admin' }) {
const [transactions, setTransactions] = useState([]);
@@ -28,6 +34,7 @@ export default function LedgerBook({ userType = 'admin' }) {
securityDeposits: 0,
commissionEarned: 0
});
+ const [isExporting, setIsExporting] = useState(false);
useEffect(() => {
loadTransactions();
@@ -144,30 +151,239 @@ export default function LedgerBook({ userType = 'admin' }) {
}
};
- const exportToExcel = () => {
- const csvContent = [
- ['التاريخ', 'الوصف', 'من', 'إلى', 'المبلغ', 'العمولة', 'الحالة'],
- ...filteredTransactions.map(t => [
- t.date,
- t.description,
- t.fromUser,
- t.toUser,
- t.amount,
- t.commission,
- t.status
- ])
- ].map(row => row.join(',')).join('\n');
+ const exportToExcel = async () => {
+ if (filteredTransactions.length === 0) {
+ toast.error('لا توجد معاملات للتصدير');
+ return;
+ }
- const blob = new Blob([csvContent], { type: 'text/csv' });
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `ledger_${new Date().toISOString()}.csv`;
- a.click();
+ setIsExporting(true);
+ toast.loading('جاري تصدير البيانات...', { id: 'export' });
+
+ try {
+ const exportData = filteredTransactions.map(t => ({
+ 'رقم العملية': t.id,
+ 'التاريخ': t.date,
+ 'نوع العملية': t.type === 'rent_payment' ? 'دفعة إيجار' :
+ t.type === 'security_deposit' ? 'سلفة ضمان' :
+ t.type === 'commission' ? 'عمولة' : 'أخرى',
+ 'الوصف': t.description,
+ 'من': t.fromUser,
+ 'إلى': t.toUser,
+ 'المبلغ (ل.س)': t.amount,
+ 'العمولة (ل.س)': t.commission || 0,
+ 'الحالة': t.status === 'completed' ? 'مكتمل' :
+ t.status === 'pending' ? 'معلق' :
+ t.status === 'pending_refund' ? 'بإنتظار الاسترداد' : 'مؤكد',
+ }));
+
+ const summaryRow = {
+ 'رقم العملية': '',
+ 'التاريخ': '',
+ 'نوع العملية': '',
+ 'الوصف': '',
+ 'من': '',
+ 'إلى': '',
+ 'المبلغ (ل.س)': summary.totalRevenue,
+ 'العمولة (ل.س)': summary.commissionEarned,
+ 'الحالة': ''
+ };
+
+ exportData.push(summaryRow);
+
+ const worksheet = XLSX.utils.json_to_sheet(exportData);
+
+ const columnWidths = [
+ { wch: 12 }, // رقم العملية
+ { wch: 12 }, // التاريخ
+ { wch: 12 }, // نوع العملية
+ { wch: 30 }, // الوصف
+ { wch: 20 }, // من
+ { wch: 20 }, // إلى
+ { wch: 15 }, // المبلغ
+ { wch: 15 }, // العمولة
+ { wch: 12 }, // الحالة
+ ];
+ worksheet['!cols'] = columnWidths;
+
+ const range = XLSX.utils.decode_range(worksheet['!ref']);
+ for (let C = range.s.c; C <= range.e.c; ++C) {
+ const address = XLSX.utils.encode_col(C) + '1';
+ if (!worksheet[address]) continue;
+ worksheet[address].s = {
+ font: { bold: true, sz: 12 },
+ fill: { fgColor: { rgb: "F59E0B" } },
+ alignment: { horizontal: "center", vertical: "center" }
+ };
+ }
+
+
+ const workbook = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'دفتر الحسابات');
+
+ const fileName = `دفتر_الحسابات_${new Date().toISOString().split('T')[0]}.xlsx`;
+
+ XLSX.writeFile(workbook, fileName);
+
+ toast.success(`تم تصدير ${filteredTransactions.length} معاملة بنجاح!`, { id: 'export' });
+
+ } catch (error) {
+ console.error('Error exporting to Excel:', error);
+ toast.error('حدث خطأ أثناء تصدير البيانات', { id: 'export' });
+ } finally {
+ setIsExporting(false);
+ }
+ };
+
+ const printReport = () => {
+ const printWindow = window.open('', '_blank');
+ printWindow.document.write(`
+
+
+
+
+
تقرير دفتر الحسابات
+
+
+
+
+
+
+
+
إجمالي الإيرادات
+
${formatCurrency(summary.totalRevenue)}
+
+
+
أرباح المنصة
+
${formatCurrency(summary.commissionEarned)}
+
+
+
سلف الضمان
+
${formatCurrency(summary.securityDeposits)}
+
+
+
المدفوعات المعلقة
+
${formatCurrency(summary.pendingPayments)}
+
+
+
+
+
+
+ | التاريخ |
+ الوصف |
+ من |
+ إلى |
+ المبلغ |
+ العمولة |
+ الحالة |
+
+
+
+ ${filteredTransactions.map(t => `
+
+ | ${t.date} |
+ ${t.description} |
+ ${t.fromUser} |
+ ${t.toUser} |
+ ${formatCurrency(t.amount)} |
+ ${t.commission ? formatCurrency(t.commission) : '-'} |
+ ${t.status === 'completed' ? 'مكتمل' : t.status === 'pending' ? 'معلق' : 'بإنتظار الرد'} |
+
+ `).join('')}
+
+
+
+
+
+
+
+
+
+
+ `);
+ printWindow.document.close();
};
return (
+
+
-
+
+
+
+
+
+ {(dateRange.start || dateRange.end || searchTerm) && (
+
+
+ {filteredTransactions.length} معاملة من إجمالي {transactions.length}
+
+
+
+ )}
@@ -307,14 +561,14 @@ export default function LedgerBook({ userType = 'admin' }) {
{transaction.toUser}
-
+ |
{formatCurrency(transaction.amount)}
|
{transaction.commission ? formatCurrency(transaction.commission) : '-'}
|
-
)}
diff --git a/app/components/admin/PropertiesTable.js b/app/components/admin/PropertiesTable.js
index de975a2..d35f71b 100644
--- a/app/components/admin/PropertiesTable.js
+++ b/app/components/admin/PropertiesTable.js
@@ -1,7 +1,7 @@
'use client';
import { useState } from 'react';
-import { motion } from 'framer-motion';
+import { motion, AnimatePresence } from 'framer-motion';
import {
Edit,
Trash2,
@@ -12,14 +12,404 @@ import {
Square,
DollarSign,
Percent,
- MoreVertical
+ MoreVertical,
+ X,
+ CheckCircle,
+ AlertCircle,
+ Calendar,
+ User,
+ Home,
+ Building,
+ Clock
} from 'lucide-react';
+import toast, { Toaster } from 'react-hot-toast';
+
+const DeleteConfirmationModal = ({ isOpen, onClose, onConfirm, propertyTitle }) => {
+ if (!isOpen) return null;
+
+ return (
+
+ e.stopPropagation()}
+ >
+
+
+ تأكيد الحذف
+
+ هل أنت متأكد من حذف العقار: "{propertyTitle}"؟
+
+ هذا الإجراء لا يمكن التراجع عنه
+
+
+
+
+
+
+
+
+ );
+};
+
+const PropertyViewModal = ({ property, isOpen, onClose }) => {
+ if (!isOpen || !property) return null;
+
+ const formatCurrency = (amount) => {
+ return amount?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' ل.س';
+ };
+
+ return (
+
+ e.stopPropagation()}
+ >
+
+
+
+ {property.title}
+ {property.location}
+
+
+
+
+
+
+
+
+
+ {property.type === 'villa' ? 'فيلا' : property.type === 'apartment' ? 'شقة' : 'بيت'}
+ نوع العقار
+
+
+
+ {formatCurrency(property.price)}
+ السعر اليومي
+
+
+
+ {property.commission}%
+ نسبة العمولة
+
+
+
+ {property.bookings || 0}
+ عدد الحجوزات
+
+
+
+
+
+
+ الموقع
+
+ {property.location}
+
+
+
+ المواصفات
+
+
+
+ {property.bedrooms}
+ غرف نوم
+
+
+
+ {property.bathrooms}
+ حمامات
+
+
+
+ {property.area}
+ م²
+
+
+
+
+
+
+
+ معلومات العمولة
+
+
+
+
+ {property.commission}%
+
+
+
+ {property.commissionType}
+
+
+
+
+ {formatCurrency((property.price * property.commission) / 100)}
+
+
+
+
+
+ {property.status === 'available' ? 'متاح' : 'محجوز'}
+
+
+
+
+
+
+
+ );
+};
+
+const PropertyEditModal = ({ property, isOpen, onClose, onSave }) => {
+ const [formData, setFormData] = useState({ ...property });
+ const [isSaving, setIsSaving] = useState(false);
+
+ const formatCurrency = (amount) => {
+ return amount?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+ };
+
+ const handleSave = () => {
+ setIsSaving(true);
+ setTimeout(() => {
+ onSave(formData);
+ setIsSaving(false);
+ onClose();
+ toast.success('تم تحديث العقار بنجاح');
+ }, 1000);
+ };
+
+ if (!isOpen || !property) return null;
+
+ return (
+
+ e.stopPropagation()}
+ >
+
+
+ تعديل العقار
+
+
+ يمكنك تعديل معلومات العقار
+
+
+
+
+
+
+ setFormData({...formData, title: e.target.value})}
+ className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
+ />
+
+
+
+
+
+
+
+ setFormData({...formData, location: e.target.value})}
+ className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
+ />
+
+
+
+ setFormData({...formData, price: parseInt(e.target.value)})}
+ className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
+ />
+
+
+
+ setFormData({...formData, commission: parseFloat(e.target.value)})}
+ className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
+ />
+
+
+
+
+
+
+
+ setFormData({...formData, bedrooms: parseInt(e.target.value)})}
+ className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
+ />
+
+
+
+ setFormData({...formData, bathrooms: parseInt(e.target.value)})}
+ className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
+ />
+
+
+
+ setFormData({...formData, area: parseInt(e.target.value)})}
+ className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const MoreActionsMenu = ({ property, isOpen, onClose, onViewBookings, onViewReports }) => {
+ if (!isOpen) return null;
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
export default function PropertiesTable() {
const [properties, setProperties] = useState([
{
id: 1,
- title: 'luxuryVillaDamascus',
+ title: 'فيلا فاخرة في المزة',
type: 'villa',
location: 'دمشق, المزة',
price: 500000,
@@ -33,7 +423,7 @@ export default function PropertiesTable() {
},
{
id: 2,
- title: 'modernApartmentAleppo',
+ title: 'شقة حديثة في الشهباء',
type: 'apartment',
location: 'حلب, الشهباء',
price: 250000,
@@ -47,6 +437,11 @@ export default function PropertiesTable() {
}
]);
+ const [viewModal, setViewModal] = useState({ isOpen: false, property: null });
+ const [editModal, setEditModal] = useState({ isOpen: false, property: null });
+ const [deleteModal, setDeleteModal] = useState({ isOpen: false, property: null });
+ const [moreMenu, setMoreMenu] = useState({ isOpen: false, property: null, anchorEl: null });
+
const formatCurrency = (amount) => {
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' ل.س';
};
@@ -71,8 +466,50 @@ export default function PropertiesTable() {
);
};
+ const handleView = (property) => {
+ setViewModal({ isOpen: true, property });
+ };
+
+ const handleEdit = (property) => {
+ setEditModal({ isOpen: true, property });
+ };
+
+ const handleDelete = (property) => {
+ setDeleteModal({ isOpen: true, property });
+ };
+
+ const confirmDelete = () => {
+ if (deleteModal.property) {
+ setProperties(prev => prev.filter(p => p.id !== deleteModal.property.id));
+ setDeleteModal({ isOpen: false, property: null });
+ toast.success('تم حذف العقار بنجاح');
+ }
+ };
+
+ const handleSaveEdit = (updatedProperty) => {
+ setProperties(prev => prev.map(p =>
+ p.id === updatedProperty.id ? updatedProperty : p
+ ));
+ toast.success('تم تحديث العقار بنجاح');
+ };
+
+ const handleMoreClick = (event, property) => {
+ event.stopPropagation();
+ setMoreMenu({ isOpen: true, property, anchorEl: event.currentTarget });
+ };
+
+ const handleViewBookings = (property) => {
+ toast.success(`جاري عرض حجوزات ${property.title}`);
+ };
+
+ const handleViewReports = (property) => {
+ toast.success(`جاري عرض تقرير أرباح ${property.title}`);
+ };
+
return (
+
+
@@ -97,7 +534,11 @@ export default function PropertiesTable() {
>
|
{property.title}
- {property.type}
+
+ {property.type === 'villa' ? 'فيلا' :
+ property.type === 'apartment' ? 'شقة' :
+ property.type === 'house' ? 'بيت' : 'استوديو'}
+
|
@@ -125,20 +566,38 @@ export default function PropertiesTable() {
{getStatusBadge(property.status)}
|
-
+ |
-
|
@@ -152,6 +611,26 @@ export default function PropertiesTable() {
لا توجد عقارات مضافة بعد
)}
+
+ setViewModal({ isOpen: false, property: null })}
+ />
+
+ setEditModal({ isOpen: false, property: null })}
+ onSave={handleSaveEdit}
+ />
+
+ setDeleteModal({ isOpen: false, property: null })}
+ onConfirm={confirmDelete}
+ propertyTitle={deleteModal.property?.title}
+ />
);
}
\ No newline at end of file
diff --git a/app/components/admin/UsersList.js b/app/components/admin/UsersList.js
index ef7713e..8085bc2 100644
--- a/app/components/admin/UsersList.js
+++ b/app/components/admin/UsersList.js
@@ -1,7 +1,7 @@
'use client';
import { useState } from 'react';
-import { motion } from 'framer-motion';
+import { motion, AnimatePresence } from 'framer-motion';
import {
User,
Mail,
@@ -11,8 +11,445 @@ import {
DollarSign,
Search,
Filter,
- Eye
+ Eye,
+ X,
+ CheckCircle,
+ XCircle,
+ ChevronDown,
+ Users,
+ Award,
+ Clock,
+ TrendingUp,
+ CalendarDays,
+ Shield
} from 'lucide-react';
+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 (
+
+ e.stopPropagation()}
+ >
+
+
+
+
+
+ تصفية متقدمة
+
+ حدد معايير التصفية المطلوبة
+
+
+
+
+
+
+
+
+
+
+
+ {identityTypes.map((type) => (
+ 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}
+
+ ))}
+
+
+
+
+
+
+ setLocalFilters({...localFilters, minBookings: e.target.value})}
+ className="px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500"
+ />
+ setLocalFilters({...localFilters, maxBookings: e.target.value})}
+ className="px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500"
+ />
+
+
+ {bookingRanges.slice(1).map((range) => (
+ {
+ 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}
+
+ ))}
+
+
+
+
+
+
+ setLocalFilters({...localFilters, minSpending: e.target.value})}
+ className="px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500"
+ />
+ setLocalFilters({...localFilters, maxSpending: e.target.value})}
+ className="px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500"
+ />
+
+
+ {spendingRanges.slice(1).map((range) => (
+ {
+ 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}
+
+ ))}
+
+
+
+
+
+
+ {dateRanges.map((range) => (
+ 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}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ إعادة تعيين
+
+
+ تطبيق الفلاتر
+
+
+
+
+ );
+};
+
+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 (
+
+ e.stopPropagation()}
+ >
+
+
+
+
+
+ تفاصيل المستخدم
+
+ {user.name}
+
+
+
+
+
+
+
+
+
+
+
+
+ معلومات شخصية
+
+
+
+ الاسم الكامل:
+ {user.name}
+
+
+ البريد الإلكتروني:
+ {user.email}
+
+
+ رقم الهاتف:
+ {user.phone}
+
+
+ تاريخ التسجيل:
+ {user.joinDate}
+
+
+
+
+
+
+
+ معلومات الهوية
+
+
+
+ نوع الهوية:
+
+ {user.identityType === 'syrian' ? 'هوية سورية' : 'جواز سفر'}
+
+
+
+ رقم الهوية:
+ {user.identityNumber}
+
+
+
+
+
+
+
+ {user.totalBookings}
+ إجمالي الحجوزات
+
+
+ {user.activeBookings}
+ حجوزات نشطة
+
+
+ {formatCurrency(user.totalSpent)}
+ إجمالي المنصرف
+
+
+
+
+
+
+ سجل الحجوزات
+
+
+ {userBookings.map((booking) => (
+
+
+ {booking.property}
+
+
+ {booking.startDate} - {booking.endDate}
+
+
+
+
+ {formatCurrency(booking.amount)}
+ المبلغ الإجمالي
+
+
+ {booking.status === 'completed' ? 'مكتمل' : 'مؤكد'}
+
+
+
+ ))}
+
+ {userBookings.length === 0 && (
+
+
+ لا توجد حجوزات سابقة
+
+ )}
+
+
+
+
+
+
+
+
+ );
+};
export default function UsersList() {
const [users, setUsers] = useState([
@@ -44,30 +481,194 @@ export default function UsersList() {
const [searchTerm, setSearchTerm] = useState('');
const [selectedUser, setSelectedUser] = useState(null);
+ const [showFilterDialog, setShowFilterDialog] = useState(false);
+ const [filters, setFilters] = useState({
+ identityType: 'all',
+ minBookings: '',
+ maxBookings: '',
+ minSpending: '',
+ maxSpending: '',
+ dateRange: 'all',
+ activeOnly: false,
+ inactiveOnly: false
+ });
- const filteredUsers = users.filter(user =>
- user.name.includes(searchTerm) ||
- user.email.includes(searchTerm) ||
- user.phone.includes(searchTerm)
- );
+ 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;
+ };
return (
-
+
+
+
-
+
setSearchTerm(e.target.value)}
- className="w-full pr-10 px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
+ 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"
/>
-
-
- تصفية
-
+
+ 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'
+ }`}
+ >
+
+ تصفية متقدمة
+ {getActiveFiltersCount() > 0 && (
+
+ {getActiveFiltersCount()}
+
+ )}
+
+ {filterStats.filtered && (
+
+
+ إعادة تعيين
+
+ )}
+
+
+
+ {getActiveFiltersCount() > 0 && (
+
+ الفلاتر النشطة:
+ {filters.identityType !== 'all' && (
+
+ {filters.identityType === 'syrian' ? 'هوية سورية' : 'جواز سفر'}
+
+ )}
+ {(filters.minBookings || filters.maxBookings) && (
+
+ الحجوزات: {filters.minBookings || '0'} - {filters.maxBookings || '∞'}
+
+ )}
+ {(filters.minSpending || filters.maxSpending) && (
+
+ الإنفاق: {parseInt(filters.minSpending || 0).toLocaleString()} - {parseInt(filters.maxSpending || '∞').toLocaleString()} ل.س
+
+ )}
+ {filters.dateRange !== 'all' && (
+
+ {filters.dateRange === 'today' ? 'اليوم' :
+ filters.dateRange === 'week' ? 'آخر 7 أيام' :
+ filters.dateRange === 'month' ? 'آخر 30 يوم' : 'آخر 12 شهر'}
+
+ )}
+ {filters.activeOnly && (
+
+ لديهم حجوزات نشطة
+
+ )}
+ {filters.inactiveOnly && (
+
+ بدون حجوزات نشطة
+
+ )}
+
+ )}
+
+
+
+ عرض {filteredUsers.length} مستخدم
+ {filterStats.filtered && (
+ (من {users.length})
+ )}
+
@@ -76,40 +677,46 @@ export default function UsersList() {
key={user.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
- transition={{ delay: index * 0.1 }}
- className="bg-white border rounded-lg p-4 hover:shadow-md transition-shadow"
+ transition={{ delay: index * 0.05 }}
+ className="bg-white border border-gray-200 rounded-xl p-5 hover:shadow-md transition-all"
>
-
-
+
+ {user.name.charAt(0).toUpperCase()}
- {user.name}
-
+ {user.name}
+
-
+
{user.email}
+
+
+ {user.identityType === 'syrian' ? 'هوية سورية' : 'جواز سفر'}
+
-
-
- {user.totalBookings}
+
+
+ {user.totalBookings}
إجمالي الحجوزات
-
- {user.activeBookings}
+
+ 0 ? 'text-green-600' : 'text-gray-400'}`}>
+ {user.activeBookings}
+
حجوزات نشطة
-
-
+
+
{user.totalSpent.toLocaleString()}
إجمالي المنصرف
@@ -118,7 +725,7 @@ export default function UsersList() {
setSelectedUser(user)}
- className="px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm flex items-center gap-1 hover:bg-blue-700"
+ 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"
>
عرض التفاصيل
@@ -128,63 +735,39 @@ export default function UsersList() {
))}
- {selectedUser && (
-
-
-
- تفاصيل المستخدم
- setSelectedUser(null)}
- className="p-1 hover:bg-gray-100 rounded"
- >
- ✕
-
-
-
-
-
-
-
- {selectedUser.name}
-
-
-
- {selectedUser.email}
-
-
-
- {selectedUser.phone}
-
-
-
-
- {selectedUser.identityType === 'syrian' ? 'هوية سورية' : 'جواز سفر'}
-
-
-
-
- {selectedUser.identityNumber}
-
-
-
- {selectedUser.joinDate}
-
-
-
-
- سجل الحجوزات
-
- لا توجد حجوزات سابقة
-
-
-
-
-
+ {filteredUsers.length === 0 && (
+
+
+ لا توجد نتائج
+ لا يوجد مستخدمون يطابقون معايير البحث
+ {(searchTerm || getActiveFiltersCount() > 0) && (
+
+ إعادة تعيين الفلاتر
+
+ )}
+
)}
+
+ setShowFilterDialog(false)}
+ filters={filters}
+ onApplyFilters={applyFilters}
+ onResetFilters={resetFilters}
+ />
+
+ setSelectedUser(null)}
+ />
);
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index e94ee17..c8319d8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,8 +12,10 @@
"flowbite": "^4.0.1",
"flowbite-react": "^0.12.16",
"framer-motion": "^12.29.2",
+ "html2canvas": "^1.4.1",
"i18next": "^25.8.0",
"i18next-browser-languagedetector": "^8.2.0",
+ "jspdf": "^4.2.1",
"leaflet": "^1.9.4",
"lucide-react": "^0.563.0",
"next": "16.1.6",
@@ -22,7 +24,8 @@
"react-hot-toast": "^2.6.0",
"react-i18next": "^16.5.4",
"react-intersection-observer": "^10.0.3",
- "react-leaflet": "^4.2.1"
+ "react-leaflet": "^4.2.1",
+ "xlsx": "^0.18.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -1199,12 +1202,32 @@
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT"
},
+ "node_modules/@types/pako": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
+ "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/raf": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+ "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"license": "MIT"
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/yandex-maps": {
"version": "2.1.29",
"resolved": "https://registry.npmjs.org/@types/yandex-maps/-/yandex-maps-2.1.29.tgz",
@@ -1306,6 +1329,15 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/adler-32": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
+ "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/array-timsort": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz",
@@ -1377,6 +1409,15 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/baseline-browser-mapping": {
"version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
@@ -1461,6 +1502,39 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/canvg": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+ "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/cfb": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
+ "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "crc-32": "~1.2.0"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -1488,6 +1562,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/codepage": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
+ "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/comment-json": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.4.1.tgz",
@@ -1502,12 +1585,45 @@
"node": ">= 6"
}
},
+ "node_modules/core-js": {
+ "version": "3.49.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz",
+ "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "license": "MIT",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -1580,6 +1696,16 @@
"node": ">=8"
}
},
+ "node_modules/dompurify": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
+ "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optional": true,
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.279",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz",
@@ -1657,6 +1783,17 @@
"node": ">=8.6.0"
}
},
+ "node_modules/fast-png": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
+ "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/pako": "^2.0.3",
+ "iobuffer": "^5.3.2",
+ "pako": "^2.1.0"
+ }
+ },
"node_modules/fastq": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
@@ -1666,6 +1803,12 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -1731,6 +1874,15 @@
"tailwindcss": "^3 || ^4"
}
},
+ "node_modules/frac": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
+ "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/fraction.js": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
@@ -1829,6 +1981,19 @@
"void-elements": "3.1.0"
}
},
+ "node_modules/html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "license": "MIT",
+ "dependencies": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/i18next": {
"version": "25.8.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.0.tgz",
@@ -1869,6 +2034,12 @@
"@babel/runtime": "^7.23.2"
}
},
+ "node_modules/iobuffer": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
+ "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
+ "license": "MIT"
+ },
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -1935,6 +2106,23 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
+ "node_modules/jspdf": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz",
+ "integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "fast-png": "^6.2.0",
+ "fflate": "^0.8.1"
+ },
+ "optionalDependencies": {
+ "canvg": "^3.0.11",
+ "core-js": "^3.6.0",
+ "dompurify": "^3.3.1",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
"node_modules/klona": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
@@ -2420,12 +2608,25 @@
"integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==",
"license": "MIT"
},
+ "node_modules/pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -2499,6 +2700,16 @@
],
"license": "MIT"
},
+ "node_modules/raf": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "performance-now": "^2.1.0"
+ }
+ },
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
@@ -2626,6 +2837,13 @@
"node": ">= 4"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -2656,6 +2874,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.8.15"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -2763,6 +2991,28 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ssf": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
+ "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "frac": "~1.1.2"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/stackblur-canvas": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+ "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.14"
+ }
+ },
"node_modules/styled-jsx": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
@@ -2798,6 +3048,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/tabbable": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
@@ -2845,6 +3105,15 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "license": "MIT",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
@@ -2935,6 +3204,15 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
@@ -2943,6 +3221,45 @@
"engines": {
"node": ">=0.10.0"
}
+ },
+ "node_modules/wmf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
+ "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/word": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
+ "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/xlsx": {
+ "version": "0.18.5",
+ "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
+ "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "cfb": "~1.2.1",
+ "codepage": "~1.15.0",
+ "crc-32": "~1.2.1",
+ "ssf": "~0.11.2",
+ "wmf": "~1.0.1",
+ "word": "~0.3.0"
+ },
+ "bin": {
+ "xlsx": "bin/xlsx.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
}
}
}
diff --git a/package.json b/package.json
index 0c0f2c0..a662f8b 100644
--- a/package.json
+++ b/package.json
@@ -12,8 +12,10 @@
"flowbite": "^4.0.1",
"flowbite-react": "^0.12.16",
"framer-motion": "^12.29.2",
+ "html2canvas": "^1.4.1",
"i18next": "^25.8.0",
"i18next-browser-languagedetector": "^8.2.0",
+ "jspdf": "^4.2.1",
"leaflet": "^1.9.4",
"lucide-react": "^0.563.0",
"next": "16.1.6",
@@ -22,7 +24,8 @@
"react-hot-toast": "^2.6.0",
"react-i18next": "^16.5.4",
"react-intersection-observer": "^10.0.3",
- "react-leaflet": "^4.2.1"
+ "react-leaflet": "^4.2.1",
+ "xlsx": "^0.18.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
| |