2026-04-05 14:42:10 +00:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
|
|
|
import { motion } from 'framer-motion';
|
|
|
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
|
import {
|
|
|
|
|
Calendar,
|
|
|
|
|
Clock,
|
|
|
|
|
CheckCircle,
|
|
|
|
|
XCircle,
|
|
|
|
|
Eye,
|
|
|
|
|
Loader2,
|
|
|
|
|
Search,
|
|
|
|
|
MapPin,
|
|
|
|
|
DollarSign,
|
|
|
|
|
Home,
|
|
|
|
|
ArrowLeft,
|
|
|
|
|
User,
|
|
|
|
|
RefreshCw,
|
|
|
|
|
Mail,
|
|
|
|
|
Phone
|
|
|
|
|
} from 'lucide-react';
|
|
|
|
|
import toast, { Toaster } from 'react-hot-toast';
|
|
|
|
|
import AuthService from '../../services/AuthService';
|
2026-04-05 18:40:09 +00:00
|
|
|
import { getRentProperty } from '../../utils/api';
|
2026-04-05 14:42:10 +00:00
|
|
|
|
|
|
|
|
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
|
|
|
|
|
|
|
|
|
const statusConfig = {
|
|
|
|
|
pending: { label: 'قيد الانتظار', color: 'bg-yellow-100 text-yellow-800', icon: Clock },
|
|
|
|
|
ownerConfirmed: { label: 'مؤكد', color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
|
|
|
|
depositPaid: { label: 'تم دفع السلفة', color: 'bg-indigo-100 text-indigo-800', icon: DollarSign },
|
|
|
|
|
depositConfirmed: { label: 'مؤكد نهائياً', color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
|
|
|
|
rejected: { label: 'مرفوض', color: 'bg-red-100 text-red-800', icon: XCircle },
|
|
|
|
|
cancelled: { label: 'ملغي', color: 'bg-gray-100 text-gray-800', icon: XCircle },
|
|
|
|
|
completed: { label: 'منتهي', color: 'bg-blue-100 text-blue-800', icon: CheckCircle },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function StatusBadge({ status }) {
|
|
|
|
|
const config = statusConfig[status] || { label: status, color: 'bg-gray-100 text-gray-700', icon: Clock };
|
|
|
|
|
const Icon = config.icon;
|
|
|
|
|
return (
|
|
|
|
|
<span className={`inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${config.color}`}>
|
|
|
|
|
<Icon className="w-3 h-3" />
|
|
|
|
|
{config.label}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function OwnerReservationCard({ reservation, onViewDetails, onConfirm, onReject }) {
|
|
|
|
|
const property = reservation.property || reservation.propertyInformation;
|
|
|
|
|
const user = reservation.user;
|
|
|
|
|
const isPending = reservation.status === 'pending';
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
className="bg-white rounded-2xl shadow-sm hover:shadow-md transition-all border border-gray-200 overflow-hidden"
|
|
|
|
|
>
|
|
|
|
|
<div className="p-5">
|
|
|
|
|
<div className="flex justify-between items-start mb-4">
|
|
|
|
|
<div>
|
|
|
|
|
<StatusBadge status={reservation.status} />
|
|
|
|
|
{property && (
|
|
|
|
|
<div className="flex items-center gap-1 text-gray-500 text-sm mt-2">
|
|
|
|
|
<MapPin className="w-4 h-4" />
|
|
|
|
|
{property.address || 'عقار'}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-left">
|
|
|
|
|
<div className="text-lg font-bold text-amber-600">
|
|
|
|
|
{reservation.totalPrice?.toLocaleString() || '—'}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs text-gray-500">السعر الإجمالي</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{user && (
|
|
|
|
|
<div className="bg-gray-50 rounded-xl p-3 mb-4">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<div className="w-10 h-10 bg-amber-100 rounded-full flex items-center justify-center">
|
|
|
|
|
<User className="w-5 h-5 text-amber-600" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-medium text-gray-900">{user.name || user.email || 'مستخدم'}</p>
|
|
|
|
|
<div className="flex items-center gap-2 text-xs text-gray-500">
|
|
|
|
|
{user.phoneNumber && <><Phone className="w-3 h-3" />{user.phoneNumber}</>}
|
|
|
|
|
{user.email && <><Mail className="w-3 h-3 mr-1" />{user.email}</>}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-3 mb-4 text-center">
|
|
|
|
|
<div className="bg-gray-50 p-2 rounded-lg">
|
|
|
|
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
|
|
|
|
<div className="text-xs text-gray-500">من</div>
|
|
|
|
|
<div className="text-sm font-medium">
|
|
|
|
|
{new Date(reservation.startDate).toLocaleDateString('ar')}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="bg-gray-50 p-2 rounded-lg">
|
|
|
|
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
|
|
|
|
<div className="text-xs text-gray-500">إلى</div>
|
|
|
|
|
<div className="text-sm font-medium">
|
|
|
|
|
{new Date(reservation.endDate).toLocaleDateString('ar')}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className={`flex gap-3 pt-3 border-t border-gray-100 ${!isPending ? '' : ''}`}>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => onViewDetails(reservation)}
|
|
|
|
|
className="flex-1 bg-gray-100 text-gray-700 py-2 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<Eye className="w-4 h-4" />
|
|
|
|
|
التفاصيل
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
{isPending && (
|
|
|
|
|
<>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => onConfirm(reservation)}
|
|
|
|
|
className="flex-1 bg-green-500 text-white py-2 rounded-xl text-sm font-medium hover:bg-green-600 transition-colors flex items-center justify-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<CheckCircle className="w-4 h-4" />
|
|
|
|
|
قبول
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => onReject(reservation)}
|
|
|
|
|
className="flex-1 bg-red-500 text-white py-2 rounded-xl text-sm font-medium hover:bg-red-600 transition-colors flex items-center justify-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<XCircle className="w-4 h-4" />
|
|
|
|
|
رفض
|
|
|
|
|
</button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function DetailsModal({ reservation, isOpen, onClose }) {
|
|
|
|
|
if (!isOpen || !reservation) return null;
|
|
|
|
|
const property = reservation.property || reservation.propertyInformation;
|
|
|
|
|
const user = reservation.user;
|
|
|
|
|
|
|
|
|
|
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-amber-500 to-amber-600 p-6 text-white">
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
<h2 className="text-xl font-bold">طلب حجز #{reservation.id}</h2>
|
|
|
|
|
<button onClick={onClose} className="p-1 hover:bg-white/20 rounded-full">
|
|
|
|
|
<XCircle className="w-6 h-6" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="p-6 space-y-6">
|
|
|
|
|
{user && (
|
|
|
|
|
<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-5 h-5 text-amber-500" /> معلومات المستأجر
|
|
|
|
|
</h3>
|
|
|
|
|
<p><span className="text-gray-500">الاسم:</span> {user.name || '—'}</p>
|
|
|
|
|
<p><span className="text-gray-500">البريد:</span> {user.email || '—'}</p>
|
|
|
|
|
{user.phoneNumber && <p><span className="text-gray-500">الهاتف:</span> {user.phoneNumber}</p>}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{property && (
|
|
|
|
|
<div className="bg-gray-50 p-4 rounded-xl">
|
|
|
|
|
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
|
|
|
|
<Home className="w-5 h-5 text-amber-500" /> معلومات العقار
|
|
|
|
|
</h3>
|
|
|
|
|
<p><span className="text-gray-500">العنوان:</span> {property.address || '—'}</p>
|
|
|
|
|
{property.numberOfBedRooms && (
|
|
|
|
|
<div className="flex gap-3 mt-2">
|
|
|
|
|
<span className="text-sm bg-white px-2 py-1 rounded-lg">{property.numberOfBedRooms} غرف</span>
|
|
|
|
|
<span className="text-sm bg-white px-2 py-1 rounded-lg">{property.numberOfBathRooms} حمامات</span>
|
|
|
|
|
</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">
|
|
|
|
|
<Calendar className="w-5 h-5 text-amber-500" /> تفاصيل الحجز
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-gray-500">تاريخ البداية</p>
|
|
|
|
|
<p className="font-medium">{new Date(reservation.startDate).toLocaleDateString('ar')}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-gray-500">تاريخ النهاية</p>
|
|
|
|
|
<p className="font-medium">{new Date(reservation.endDate).toLocaleDateString('ar')}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-gray-500">الحالة</p>
|
|
|
|
|
<StatusBadge status={reservation.status} />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-gray-500">تاريخ الإنشاء</p>
|
|
|
|
|
<p className="font-medium">{new Date(reservation.createdAt).toLocaleDateString('ar')}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="bg-amber-50 p-4 rounded-xl">
|
|
|
|
|
<h3 className="font-bold text-amber-700 mb-3 flex items-center gap-2">
|
|
|
|
|
<DollarSign className="w-5 h-5" /> المعلومات المالية
|
|
|
|
|
</h3>
|
|
|
|
|
<div className="flex justify-between font-bold">
|
|
|
|
|
<span className="text-gray-900">الإجمالي</span>
|
|
|
|
|
<span className="text-amber-600 text-lg">{reservation.totalPrice?.toLocaleString() || '—'}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function OwnerReservationRequestsPage() {
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const [reservations, setReservations] = useState([]);
|
|
|
|
|
const [filtered, setFiltered] = useState([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [selected, setSelected] = useState(null);
|
|
|
|
|
const [filterStatus, setFilterStatus] = useState('all');
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const user = AuthService.getUser();
|
|
|
|
|
if (!user || !AuthService.isOwner()) {
|
|
|
|
|
router.push('/auth/choose-role');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
loadReservations();
|
|
|
|
|
}, [router]);
|
|
|
|
|
|
|
|
|
|
const loadReservations = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const token = AuthService.getToken();
|
2026-04-05 17:52:08 +00:00
|
|
|
const res = await fetch(`${API_BASE}/Reservations/GetOwnerResevationRequests`, {
|
2026-04-05 14:42:10 +00:00
|
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
|
|
|
const json = await res.json();
|
|
|
|
|
|
2026-04-05 18:40:09 +00:00
|
|
|
let list = json.data || json || [];
|
|
|
|
|
if (!Array.isArray(list)) list = [];
|
|
|
|
|
|
|
|
|
|
// Enrich each reservation with property details
|
|
|
|
|
const enriched = await Promise.all(
|
|
|
|
|
list.map(async (r) => {
|
|
|
|
|
if (!r.property && r.propertyId) {
|
|
|
|
|
try {
|
|
|
|
|
const propRes = await getRentProperty(r.propertyId);
|
|
|
|
|
r.property = propRes?.data || propRes || null;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn('Failed to load property', r.propertyId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setReservations(enriched);
|
|
|
|
|
setFiltered(enriched);
|
2026-04-05 14:42:10 +00:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Failed to load owner reservations:', err);
|
|
|
|
|
toast.error('فشل تحميل طلبات الحجز');
|
|
|
|
|
}
|
|
|
|
|
setLoading(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let result = reservations;
|
|
|
|
|
if (filterStatus !== 'all') result = result.filter(r => r.status === filterStatus);
|
|
|
|
|
if (searchTerm) {
|
|
|
|
|
const q = searchTerm.toLowerCase();
|
|
|
|
|
result = result.filter(r => {
|
|
|
|
|
const addr = (r.property?.address || '').toLowerCase();
|
|
|
|
|
const userName = ((r.user?.name) || (r.user?.email) || '').toLowerCase();
|
|
|
|
|
const sid = String(r.id).toLowerCase();
|
|
|
|
|
return addr.includes(q) || userName.includes(q) || sid.includes(q);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
setFiltered(result);
|
|
|
|
|
}, [reservations, filterStatus, searchTerm]);
|
|
|
|
|
|
|
|
|
|
const handleConfirm = async (reservation) => {
|
|
|
|
|
try {
|
|
|
|
|
const token = AuthService.getToken();
|
|
|
|
|
const id = typeof reservation.id === 'number' ? reservation.id : parseInt(reservation.id);
|
|
|
|
|
const res = await fetch(`${API_BASE}/Reservations/owner-confirm/${id}`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
|
|
|
|
|
|
|
|
toast.success('تم قبول الحجز بنجاح');
|
|
|
|
|
await loadReservations();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Failed to confirm:', err);
|
|
|
|
|
toast.error('فشل قبول الحجز');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleReject = async (reservation) => {
|
|
|
|
|
if (!confirm('هل أنت متأكد من رفض هذا الحجز؟')) return;
|
|
|
|
|
try {
|
|
|
|
|
const token = AuthService.getToken();
|
|
|
|
|
const id = typeof reservation.id === 'number' ? reservation.id : parseInt(reservation.id);
|
|
|
|
|
const res = await fetch(`${API_BASE}/Reservations/reject/${id}`, {
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
|
|
|
|
|
|
|
|
toast.success('تم رفض الحجز');
|
|
|
|
|
await loadReservations();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Failed to reject:', err);
|
|
|
|
|
toast.error('فشل رفض الحجز');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const statuses = ['all', ...new Set(reservations.map(r => r.status))];
|
|
|
|
|
const statusCounts = {
|
|
|
|
|
all: reservations.length,
|
|
|
|
|
...Object.fromEntries(
|
|
|
|
|
[...new Set(reservations.map(r => r.status))].map(s => [s, reservations.filter(r => r.status === s).length])
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (loading) return <div className="min-h-screen bg-gray-50 flex items-center justify-center"><Loader2 className="w-12 h-12 text-amber-500 animate-spin" /></div>;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="min-h-screen bg-gray-50 py-8" dir="rtl">
|
|
|
|
|
<Toaster position="top-center" reverseOrder={false} />
|
|
|
|
|
<DetailsModal reservation={selected} isOpen={!!selected} onClose={() => setSelected(null)} />
|
|
|
|
|
|
|
|
|
|
<div className="container mx-auto px-4">
|
|
|
|
|
<motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} className="mb-8">
|
|
|
|
|
<button onClick={() => router.back()} className="flex items-center gap-2 text-gray-600 hover:text-amber-600 mb-4">
|
|
|
|
|
<ArrowLeft className="w-5 h-5" /> الرجوع
|
|
|
|
|
</button>
|
|
|
|
|
<div className="flex items-center justify-between mb-2">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-3xl font-bold text-gray-900">طلبات الحجز</h1>
|
|
|
|
|
<p className="text-gray-600">لديك {reservations.length} طلب</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={loadReservations}
|
|
|
|
|
className="p-2 bg-white shadow rounded-xl hover:shadow-md transition-all"
|
|
|
|
|
>
|
|
|
|
|
<RefreshCw className="w-5 h-5 text-gray-600" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
|
|
|
|
{Object.entries(statusCounts).map(([status, count]) => (
|
|
|
|
|
<motion.div
|
|
|
|
|
key={status}
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
className={`bg-white rounded-xl shadow-sm p-4 text-center border cursor-pointer hover:shadow-md transition-all ${
|
|
|
|
|
filterStatus === status ? 'border-amber-500 bg-amber-50' : 'border-gray-200'
|
|
|
|
|
}`}
|
|
|
|
|
onClick={() => setFilterStatus(status)}
|
|
|
|
|
>
|
|
|
|
|
<div className="text-2xl font-bold text-amber-600">{count}</div>
|
|
|
|
|
<div className="text-sm text-gray-600">{status === 'all' ? 'الكل' : (statusConfig[status]?.label || status)}</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mb-6 relative">
|
|
|
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
|
|
|
|
|
<input
|
|
|
|
|
type="text" placeholder="ابحث بعنوان العقار أو المستأجر أو رقم الحجز..."
|
|
|
|
|
value={searchTerm} onChange={e => setSearchTerm(e.target.value)}
|
|
|
|
|
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{filtered.length === 0 ? (
|
|
|
|
|
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
|
|
|
|
<Calendar className="w-12 h-12 text-amber-600 mx-auto mb-4" />
|
|
|
|
|
<h3 className="text-xl font-bold text-gray-900 mb-2">لا توجد طلبات</h3>
|
|
|
|
|
<p className="text-gray-600">لم يتم استلام أي طلبات حجز حتى الآن</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
|
|
|
{filtered.map((r) => (
|
|
|
|
|
<OwnerReservationCard
|
|
|
|
|
key={r.id}
|
|
|
|
|
reservation={r}
|
|
|
|
|
onViewDetails={setSelected}
|
|
|
|
|
onConfirm={handleConfirm}
|
|
|
|
|
onReject={handleReject}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|