Files

905 lines
33 KiB
JavaScript

// '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';
// import { getRentProperty } from '../../utils/api';
// const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
// const STATUS_MAP = ['pending','ownerConfirmed','depositPaid','depositConfirmed','completed','cancelled'];
// const STATUS_UI = {
// 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 },
// completed: { label: 'منتهي', color: 'bg-blue-100 text-blue-800', icon: CheckCircle },
// cancelled: { label: 'ملغي', color: 'bg-gray-100 text-gray-800', icon: XCircle },
// };
// const sLabel = c => STATUS_UI[STATUS_MAP[c]]?.label ?? String(c);
// const sColor = c => STATUS_UI[STATUS_MAP[c]]?.color ?? 'bg-gray-100 text-gray-700';
// const sIcon = c => STATUS_UI[STATUS_MAP[c]]?.icon ?? Clock;
// function StatusBadge({ code }) {
// const Icon = sIcon(code);
// return <span className={`inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${sColor(code)}`}><Icon className="w-3 h-3"/> {sLabel(code)}</span>;
// }
// async function enrich(r) {
// if (!r.propertyId) return r;
// try {
// const prop = await getRentProperty(r.propertyId);
// r._prop = prop?.propertyInformation ?? prop ?? null;
// } catch { /* skip */ }
// return r;
// }
// const pAddr = p => p?.address ?? '';
// const pImgs = p => Array.isArray(p?.images) ? p.images : [];
// const pBeds = p => p?.numberOfBedRooms ?? 0;
// const pBaths = p => p?.numberOfBathRooms ?? 0;
// const API = (token, method, path, body) => fetch(`${API_BASE}${path}`, {
// method: method || 'GET',
// headers: { 'Content-Type': 'application/json', ...(token && { Authorization: `Bearer ${token}` }) },
// ...(body && { body: JSON.stringify(body) }),
// });
// function OwnerCard({ r, onViewDetails, onConfirm, onReject }) {
// const p = r._prop;
// const imgs = pImgs(p);
// const img = imgs.length > 0 ? `${API_BASE}${imgs[0]}` : null;
// const addr = pAddr(p);
// const isPending = r.status === 0; // 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">
// {img && <div className="mb-4 w-full h-40 rounded-xl overflow-hidden"><img src={img} alt="" className="w-full h-full object-cover"/></div>}
// <div className="flex justify-between items-start mb-3">
// <div>
// <StatusBadge code={r.status}/>
// {addr && <div className="flex items-center gap-1 text-gray-500 text-sm mt-1"><MapPin className="w-4 h-4"/>{addr}</div>}
// </div>
// <div className="text-left">
// <div className="text-lg font-bold text-amber-600">{r.totalPrice?.toLocaleString()??'—'}</div>
// <div className="text-xs text-gray-500">السعر الإجمالي</div>
// </div>
// </div>
// {(pBeds(p)||pBaths(p)) && <div className="flex gap-3 mb-3 text-sm text-gray-600">{pBeds(p)>0&&<span>{pBeds(p)} غرف</span>}{pBaths(p)>0&&<span>{pBaths(p)} حمامات</span>}</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(r.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(r.endDate).toLocaleDateString('ar')}</div>
// </div>
// </div>
// <div className={`flex gap-3 pt-3 border-t border-gray-100 ${!isPending?'justify-center':''}`}>
// <button onClick={()=>onViewDetails(r)}
// 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(r)}
// 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(r)}
// 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({ r, isOpen, onClose }) {
// if (!isOpen || !r) return null;
// const p = r._prop;
// 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">طلب حجز #{r.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">
// {p && <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> {pAddr(p)||'—'}</p>
// {(pBeds(p)||pBaths(p)) && <div className="flex gap-3 mt-2">
// {pBeds(p)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{pBeds(p)} غرف</span>}
// {pBaths(p)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{pBaths(p)} حمامات</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(r.startDate).toLocaleDateString('ar')}</p></div>
// <div><p className="text-gray-500">تاريخ النهاية</p><p className="font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</p></div>
// <div><p className="text-gray-500">الحالة</p><StatusBadge code={r.status}/></div>
// <div><p className="text-gray-500">تاريخ الإنشاء</p><p className="font-medium">{new Date(r.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">{r.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(() => {
// if (!AuthService.getUser() || !AuthService.isOwner()) { router.push('/auth/choose-role'); return; }
// loadReservations();
// }, [router]);
// const loadReservations = useCallback(async () => {
// try {
// const token = AuthService.getToken();
// const res = await fetch(`${API_BASE}/Reservations/GetOwnerResevationRequests`, {
// headers: { Authorization: `Bearer ${token}` },
// });
// if (!res.ok) throw new Error(`HTTP ${res.status}`);
// const json = await res.json();
// let list = json.data || json || [];
// if (!Array.isArray(list)) list = [];
// const enriched = await Promise.all(list.map(enrich));
// setReservations(enriched);
// setFiltered(enriched);
// } catch (err) {
// console.error(err);
// toast.error('فشل تحميل طلبات الحجز');
// }
// setLoading(false);
// }, []);
// useEffect(() => {
// let r = reservations;
// if (filterStatus !== 'all') r = r.filter(x => STATUS_MAP[x.status] === filterStatus);
// if (searchTerm) {
// const q = searchTerm.toLowerCase();
// r = r.filter(x => pAddr(x._prop).toLowerCase().includes(q) || String(x.id).includes(q));
// }
// setFiltered(r);
// }, [reservations, filterStatus, searchTerm]);
// const handleConfirm = async (r) => {
// try {
// const res = await API(AuthService.getToken(), 'PUT', `/Reservations/owner-confirm/${r.id}`);
// if (!res.ok) throw new Error(`HTTP ${res.status}`);
// toast.success('تم قبول الحجز بنجاح');
// await loadReservations();
// } catch (err) { console.error(err); toast.error('فشل قبول الحجز'); }
// };
// const handleReject = async (r) => {
// if (!confirm('هل أنت متأكد من رفض هذا الحجز؟')) return;
// try {
// const res = await API(AuthService.getToken(), 'PUT', `/Reservations/reject/${r.id}`);
// if (!res.ok) throw new Error(`HTTP ${res.status}`);
// toast.success('تم رفض الحجز');
// await loadReservations();
// } catch (err) { console.error(err); toast.error('فشل رفض الحجز'); }
// };
// const allStatuses = [...new Set(reservations.map(r => STATUS_MAP[r.status]))];
// const counts = { all: reservations.length, ...Object.fromEntries(allStatuses.map(s => [s, reservations.filter(r => STATUS_MAP[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 r={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(counts).map(([s, c]) => (
// <motion.div key={s} 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===s?'border-amber-500 bg-amber-50':'border-gray-200'}`}
// onClick={() => setFilterStatus(s)}>
// <div className="text-2xl font-bold text-amber-600">{c}</div>
// <div className="text-sm text-gray-600">{s==='all'?'الكل':(STATUS_UI[s]?.label||s)}</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 => <OwnerCard key={r.id} r={r} onViewDetails={setSelected} onConfirm={handleConfirm} onReject={handleReject} />)}
// </div>
// )}
// </div>
// </div>
// );
// }
'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,
RefreshCw,
} from 'lucide-react';
import toast, { Toaster } from 'react-hot-toast';
import AuthService from '../../services/AuthService';
import { getRentProperty } from '../../utils/api';
const API_BASE =
process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
const STATUS_MAP = [
'pending',
'ownerConfirmed',
'depositPaid',
'depositConfirmed',
'completed',
'cancelled',
];
const STATUS_UI = {
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,
},
completed: {
label: 'منتهي',
color: 'bg-blue-100 text-blue-800',
icon: CheckCircle,
},
cancelled: {
label: 'ملغي',
color: 'bg-gray-100 text-gray-800',
icon: XCircle,
},
};
const getStatusKey = (code) => STATUS_MAP[Number(code)] || 'pending';
const sLabel = (code) => STATUS_UI[getStatusKey(code)]?.label ?? String(code);
const sColor = (code) =>
STATUS_UI[getStatusKey(code)]?.color ?? 'bg-gray-100 text-gray-700';
const sIcon = (code) => STATUS_UI[getStatusKey(code)]?.icon ?? Clock;
function StatusBadge({ code }) {
const Icon = sIcon(code);
return (
<span
className={`inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${sColor(
code
)}`}
>
<Icon className="w-3 h-3" /> {sLabel(code)}
</span>
);
}
async function enrich(r) {
if (!r?.propertyId) return r;
try {
const prop = await getRentProperty(r.propertyId);
return {
...r,
_prop: prop?.propertyInformation ?? prop ?? null,
};
} catch {
return {
...r,
_prop: null,
};
}
}
const pAddr = (p) => p?.address ?? '';
const pImgs = (p) => (Array.isArray(p?.images) ? p.images : []);
const pBeds = (p) => p?.numberOfBedRooms ?? 0;
const pBaths = (p) => p?.numberOfBathRooms ?? 0;
const API = (token, method, path, body) => {
return fetch(`${API_BASE}${path}`, {
method: method || 'GET',
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
},
...(body && { body: JSON.stringify(body) }),
});
};
function buildImageUrl(path) {
if (!path) return null;
if (path.startsWith('http://') || path.startsWith('https://')) {
return path;
}
return `${API_BASE}${path}`;
}
function OwnerCard({
r,
onViewDetails,
onConfirm,
onReject,
actionLoadingId,
}) {
const p = r._prop;
const imgs = pImgs(p);
const img = imgs.length > 0 ? buildImageUrl(imgs[0]) : null;
const addr = pAddr(p);
const isPending = Number(r.status) === 0;
const isActionLoading = actionLoadingId === r.id;
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">
{img && (
<div className="mb-4 w-full h-40 rounded-xl overflow-hidden">
<img src={img} alt="" className="w-full h-full object-cover" />
</div>
)}
<div className="flex justify-between items-start mb-3">
<div>
<StatusBadge code={r.status} />
{addr && (
<div className="flex items-center gap-1 text-gray-500 text-sm mt-1">
<MapPin className="w-4 h-4" />
{addr}
</div>
)}
</div>
<div className="text-left">
<div className="text-lg font-bold text-amber-600">
{r.totalPrice?.toLocaleString() ?? '—'}
</div>
<div className="text-xs text-gray-500">السعر الإجمالي</div>
</div>
</div>
{(pBeds(p) || pBaths(p)) && (
<div className="flex gap-3 mb-3 text-sm text-gray-600">
{pBeds(p) > 0 && <span>{pBeds(p)} غرف</span>}
{pBaths(p) > 0 && <span>{pBaths(p)} حمامات</span>}
</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">
{r.startDate
? new Date(r.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">
{r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'}
</div>
</div>
</div>
<div
className={`flex gap-3 pt-3 border-t border-gray-100 ${
!isPending ? 'justify-center' : ''
}`}
>
<button
type="button"
onClick={() => onViewDetails(r)}
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
type="button"
disabled={isActionLoading}
onClick={() => onConfirm(r)}
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 disabled:opacity-60 disabled:cursor-not-allowed"
>
{isActionLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<CheckCircle className="w-4 h-4" />
)}
قبول
</button>
<button
type="button"
disabled={isActionLoading}
onClick={() => onReject(r)}
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 disabled:opacity-60 disabled:cursor-not-allowed"
>
{isActionLoading ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<XCircle className="w-4 h-4" />
)}
رفض
</button>
</>
)}
</div>
</div>
</motion.div>
);
}
function DetailsModal({ r, isOpen, onClose }) {
if (!isOpen || !r) return null;
const p = r._prop;
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">طلب حجز #{r.id}</h2>
<button
type="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">
{p && (
<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>{' '}
{pAddr(p) || '—'}
</p>
{(pBeds(p) || pBaths(p)) && (
<div className="flex gap-3 mt-2">
{pBeds(p) > 0 && (
<span className="text-sm bg-white px-2 py-1 rounded-lg">
{pBeds(p)} غرف
</span>
)}
{pBaths(p) > 0 && (
<span className="text-sm bg-white px-2 py-1 rounded-lg">
{pBaths(p)} حمامات
</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">
{r.startDate
? new Date(r.startDate).toLocaleDateString('ar')
: '—'}
</p>
</div>
<div>
<p className="text-gray-500">تاريخ النهاية</p>
<p className="font-medium">
{r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'}
</p>
</div>
<div>
<p className="text-gray-500">الحالة</p>
<StatusBadge code={r.status} />
</div>
<div>
<p className="text-gray-500">تاريخ الإنشاء</p>
<p className="font-medium">
{r.createdAt
? new Date(r.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">
{r.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('');
const [actionLoadingId, setActionLoadingId] = useState(null);
const loadReservations = useCallback(async () => {
try {
setLoading(true);
const token = AuthService.getToken();
const res = await fetch(
`${API_BASE}/Reservations/GetOwnerResevationRequests`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
if (!res.ok) {
const errorText = await res.text();
throw new Error(errorText || `HTTP ${res.status}`);
}
const json = await res.json();
let list = json.data || json || [];
if (!Array.isArray(list)) list = [];
const enriched = await Promise.all(list.map(enrich));
setReservations(enriched);
setFiltered(enriched);
} catch (err) {
console.error(err);
toast.error('فشل تحميل طلبات الحجز');
setReservations([]);
setFiltered([]);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
if (!AuthService.getUser() || !AuthService.isOwner()) {
router.push('/auth/choose-role');
return;
}
loadReservations();
}, [router, loadReservations]);
useEffect(() => {
let result = reservations;
if (filterStatus !== 'all') {
result = result.filter((x) => getStatusKey(x.status) === filterStatus);
}
if (searchTerm.trim()) {
const q = searchTerm.trim().toLowerCase();
result = result.filter(
(x) =>
pAddr(x._prop).toLowerCase().includes(q) || String(x.id).includes(q)
);
}
setFiltered(result);
}, [reservations, filterStatus, searchTerm]);
const handleConfirm = async (r) => {
try {
setActionLoadingId(r.id);
const res = await API(
AuthService.getToken(),
'PUT',
`/Reservations/OwnerConfirmReservation/owner-confirm/${r.id}`
);
if (!res.ok) {
const errorText = await res.text();
throw new Error(errorText || `HTTP ${res.status}`);
}
toast.success('تم قبول الحجز بنجاح');
await loadReservations();
} catch (err) {
console.error(err);
toast.error('فشل قبول الحجز');
} finally {
setActionLoadingId(null);
}
};
const handleReject = async (r) => {
if (!confirm('هل أنت متأكد من رفض هذا الحجز؟')) return;
try {
setActionLoadingId(r.id);
const res = await API(
AuthService.getToken(),
'POST',
`/Reservations/ChangeReservationStatus?id=${r.id}&newStatus=5`
);
if (!res.ok) {
const errorText = await res.text();
throw new Error(errorText || `HTTP ${res.status}`);
}
toast.success('تم رفض الحجز');
await loadReservations();
} catch (err) {
console.error(err);
toast.error('فشل رفض الحجز');
} finally {
setActionLoadingId(null);
}
};
const allStatuses = [
...new Set(reservations.map((r) => getStatusKey(r.status)).filter(Boolean)),
];
const counts = {
all: reservations.length,
...Object.fromEntries(
allStatuses.map((s) => [
s,
reservations.filter((r) => getStatusKey(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
r={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
type="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
type="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(counts).map(([s, c]) => (
<motion.div
key={s}
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 === s
? 'border-amber-500 bg-amber-50'
: 'border-gray-200'
}`}
onClick={() => setFilterStatus(s)}
>
<div className="text-2xl font-bold text-amber-600">{c}</div>
<div className="text-sm text-gray-600">
{s === 'all' ? 'الكل' : STATUS_UI[s]?.label || s}
</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) => (
<OwnerCard
key={r.id}
r={r}
onViewDetails={setSelected}
onConfirm={handleConfirm}
onReject={handleReject}
actionLoadingId={actionLoadingId}
/>
))}
</div>
)}
</div>
</div>
);
}