diff --git a/app/owner/reservations/page.js b/app/owner/reservations/page.js new file mode 100644 index 0000000..d879743 --- /dev/null +++ b/app/owner/reservations/page.js @@ -0,0 +1,415 @@ +'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'; + +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 ( + + + {config.label} + + ); +} + +function OwnerReservationCard({ reservation, onViewDetails, onConfirm, onReject }) { + const property = reservation.property || reservation.propertyInformation; + const user = reservation.user; + const isPending = reservation.status === 'pending'; + + return ( + +
+
+
+ + {property && ( +
+ + {property.address || 'عقار'} +
+ )} +
+
+
+ {reservation.totalPrice?.toLocaleString() || '—'} +
+
السعر الإجمالي
+
+
+ + {user && ( +
+
+
+ +
+
+

{user.name || user.email || 'مستخدم'}

+
+ {user.phoneNumber && <>{user.phoneNumber}} + {user.email && <>{user.email}} +
+
+
+
+ )} + +
+
+ +
من
+
+ {new Date(reservation.startDate).toLocaleDateString('ar')} +
+
+
+ +
إلى
+
+ {new Date(reservation.endDate).toLocaleDateString('ar')} +
+
+
+ +
+ + + {isPending && ( + <> + + + + )} +
+
+
+ ); +} + +function DetailsModal({ reservation, isOpen, onClose }) { + if (!isOpen || !reservation) return null; + const property = reservation.property || reservation.propertyInformation; + const user = reservation.user; + + return ( + + e.stopPropagation()} + > +
+
+

طلب حجز #{reservation.id}

+ +
+
+ +
+ {user && ( +
+

+ معلومات المستأجر +

+

الاسم: {user.name || '—'}

+

البريد: {user.email || '—'}

+ {user.phoneNumber &&

الهاتف: {user.phoneNumber}

} +
+ )} + + {property && ( +
+

+ معلومات العقار +

+

العنوان: {property.address || '—'}

+ {property.numberOfBedRooms && ( +
+ {property.numberOfBedRooms} غرف + {property.numberOfBathRooms} حمامات +
+ )} +
+ )} + +
+

+ تفاصيل الحجز +

+
+
+

تاريخ البداية

+

{new Date(reservation.startDate).toLocaleDateString('ar')}

+
+
+

تاريخ النهاية

+

{new Date(reservation.endDate).toLocaleDateString('ar')}

+
+
+

الحالة

+ +
+
+

تاريخ الإنشاء

+

{new Date(reservation.createdAt).toLocaleDateString('ar')}

+
+
+
+ +
+

+ المعلومات المالية +

+
+ الإجمالي + {reservation.totalPrice?.toLocaleString() || '—'} +
+
+
+
+
+ ); +} + +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(); + const res = await fetch(`${API_BASE}/Reservations/GetOwnerReservationRequests`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = await res.json(); + + const list = json.data || json || []; + setReservations(Array.isArray(list) ? list : []); + setFiltered(Array.isArray(list) ? list : []); + } 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
; + + return ( +
+ + setSelected(null)} /> + +
+ + +
+
+

طلبات الحجز

+

لديك {reservations.length} طلب

+
+ +
+
+ +
+ {Object.entries(statusCounts).map(([status, count]) => ( + setFilterStatus(status)} + > +
{count}
+
{status === 'all' ? 'الكل' : (statusConfig[status]?.label || status)}
+
+ ))} +
+ +
+ + 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" + /> +
+ + {filtered.length === 0 ? ( +
+ +

لا توجد طلبات

+

لم يتم استلام أي طلبات حجز حتى الآن

+
+ ) : ( +
+ {filtered.map((r) => ( + + ))} +
+ )} +
+
+ ); +} diff --git a/app/reservations/page.js b/app/reservations/page.js new file mode 100644 index 0000000..07d996a --- /dev/null +++ b/app/reservations/page.js @@ -0,0 +1,321 @@ +'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, + Building2 +} from 'lucide-react'; +import toast, { Toaster } from 'react-hot-toast'; +import AuthService from '../services/AuthService'; + +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-blue-100 text-blue-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-green-100 text-green-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 ( + + + {config.label} + + ); +} + +function ReservationCard({ reservation, onViewDetails }) { + const property = reservation.property || reservation.propertyInformation; + + return ( + +
+
+
+
+ +
+ {property && ( +
+ + {property.address || 'عقار'} +
+ )} +
+
+
+ {reservation.totalPrice?.toLocaleString() || '—'} +
+
السعر الإجمالي
+
+
+ + {property?.images?.length > 0 && ( +
+ +
+ )} + +
+
+ +
من
+
+ {new Date(reservation.startDate).toLocaleDateString('ar')} +
+
+
+ +
إلى
+
+ {new Date(reservation.endDate).toLocaleDateString('ar')} +
+
+
+ +
+ +
+
+
+ ); +} + +function DetailsModal({ reservation, isOpen, onClose }) { + if (!isOpen || !reservation) return null; + const property = reservation.property || reservation.propertyInformation; + + return ( + + e.stopPropagation()} + > +
+
+

تفاصيل الحجز

+ +
+

رقم الحجز: #{reservation.id}

+
+ +
+ {property && ( +
+

+ معلومات العقار +

+

العنوان: {property.address || '—'}

+ {property.numberOfBedRooms && ( +
+ {property.numberOfBedRooms} غرف + {property.numberOfBathRooms} حمامات +
+ )} +
+ )} + +
+

+ تفاصيل الحجز +

+
+
+

تاريخ البداية

+

{new Date(reservation.startDate).toLocaleDateString('ar')}

+
+
+

تاريخ النهاية

+

{new Date(reservation.endDate).toLocaleDateString('ar')}

+
+
+

الحالة

+

+
+
+

تاريخ الإنشاء

+

{new Date(reservation.createdAt).toLocaleDateString('ar')}

+
+
+
+ +
+

+ المعلومات المالية +

+
+ الإجمالي + {reservation.totalPrice?.toLocaleString() || '—'} +
+
+
+
+
+ ); +} + +export default function UserReservationsPage() { + 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) { + router.push('/login'); + return; + } + loadReservations(); + }, [router]); + + const loadReservations = async () => { + try { + const token = AuthService.getToken(); + const res = await fetch(`${API_BASE}/Reservations/GetUserReservations`, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = await res.json(); + + const list = json.data || json || []; + setReservations(Array.isArray(list) ? list : []); + setFiltered(Array.isArray(list) ? list : []); + } catch (err) { + console.error('Failed to load reservations:', err); + toast.error('فشل تحميل الحجوزات'); + setReservations([]); + setFiltered([]); + } + 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 sid = String(r.id).toLowerCase(); + return addr.includes(q) || sid.includes(q); + }); + } + setFiltered(result); + }, [reservations, filterStatus, searchTerm]); + + 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
; + + return ( +
+ + setSelected(null)} /> + +
+ + +

حجوزاتي

+

لديك {reservations.length} حجز

+
+ +
+ {Object.entries(statusCounts).map(([status, count]) => ( + setFilterStatus(status)} + > +
{count}
+
{status === 'all' ? 'الكل' : (statusConfig[status]?.label || status)}
+
+ ))} +
+ +
+ + 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" + /> +
+ + {filtered.length === 0 ? ( +
+ +

لا توجد حجوزات

+

لم تقم بأي حجز حتى الآن

+
+ ) : ( +
+ {filtered.map((r) => ( + + ))} +
+ )} +
+
+ ); +}