From 01ac4f8d6c186d89756a2032aad8e2f1ecf95ed5 Mon Sep 17 00:00:00 2001 From: Beilin-b Date: Mon, 15 Jun 2026 10:46:18 -0700 Subject: [PATCH] add report on reservation api --- app/owner/reservations/page.js | 850 ++++++++++++++++++++++++++++----- app/reservations/page.js | 644 ++++++++++++++++++++++++- 2 files changed, 1371 insertions(+), 123 deletions(-) diff --git a/app/owner/reservations/page.js b/app/owner/reservations/page.js index 43fd68a..15f2c3b 100644 --- a/app/owner/reservations/page.js +++ b/app/owner/reservations/page.js @@ -1,104 +1,258 @@ + // '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, +// 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 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_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 }, +// 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; +// 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 {sLabel(code)}; + +// return ( +// +// {sLabel(code)} +// +// ); // } // async function enrich(r) { -// if (!r.propertyId) return r; +// if (!r?.propertyId) return r; + // try { // const prop = await getRentProperty(r.propertyId); -// r._prop = prop?.propertyInformation ?? prop ?? null; -// } catch { /* skip */ } -// return r; + +// 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) => fetch(`${API_BASE}${path}`, { -// method: method || 'GET', -// headers: { 'Content-Type': 'application/json', ...(token && { Authorization: `Bearer ${token}` }) }, -// ...(body && { body: JSON.stringify(body) }), -// }); +// 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; -// function OwnerCard({ r, onViewDetails, onConfirm, onReject }) { +// 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 ? `${API_BASE}${imgs[0]}` : null; +// const img = imgs.length > 0 ? buildImageUrl(imgs[0]) : null; // const addr = pAddr(p); -// const isPending = r.status === 0; // Pending +// const isPending = Number(r.status) === 0; +// const isActionLoading = actionLoadingId === r.id; // return ( -// +// //
-// {img &&
} +// {img && ( +//
+// +//
+// )} + //
//
-// -// {addr &&
{addr}
} +// + +// {addr && ( +//
+// +// {addr} +//
+// )} //
+ //
-//
{r.totalPrice?.toLocaleString()??'—'}
+//
+// {r.totalPrice?.toLocaleString() ?? '—'} +//
//
السعر الإجمالي
//
//
-// {(pBeds(p)||pBaths(p)) &&
{pBeds(p)>0&&{pBeds(p)} غرف}{pBaths(p)>0&&{pBaths(p)} حمامات}
} + +// {(pBeds(p) || pBaths(p)) && ( +//
+// {pBeds(p) > 0 && {pBeds(p)} غرف} +// {pBaths(p) > 0 && {pBaths(p)} حمامات} +//
+// )} + //
//
-//
من
-//
{new Date(r.startDate).toLocaleDateString('ar')}
+// +//
من
+//
+// {r.startDate +// ? new Date(r.startDate).toLocaleDateString('ar') +// : '—'} +//
//
+ //
-//
إلى
-//
{new Date(r.endDate).toLocaleDateString('ar')}
+// +//
إلى
+//
+// {r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'} +//
//
//
-//
-// -// {isPending && <> -// -// -// } + +// {isPending && ( +// <> +// + +// +// +// )} //
//
//
@@ -107,39 +261,120 @@ // function DetailsModal({ r, isOpen, onClose }) { // if (!isOpen || !r) return null; + // const p = r._prop; + // return ( -// -// e.stopPropagation()}> +// +// e.stopPropagation()} +// > //
//
//

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

-// + +// //
//
+ //
-// {p &&
-//

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

-//

العنوان: {pAddr(p)||'—'}

-// {(pBeds(p)||pBaths(p)) &&
-// {pBeds(p)>0&&{pBeds(p)} غرف} -// {pBaths(p)>0&&{pBaths(p)} حمامات} -//
} -//
} +// {p && ( +//
+//

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

+ +//

+// العنوان:{' '} +// {pAddr(p) || '—'} +//

+ +// {(pBeds(p) || pBaths(p)) && ( +//
+// {pBeds(p) > 0 && ( +// +// {pBeds(p)} غرف +// +// )} + +// {pBaths(p) > 0 && ( +// +// {pBaths(p)} حمامات +// +// )} +//
+// )} +//
+// )} + //
-//

تفاصيل الحجز

+//

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

+ //
-//

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

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

-//

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

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

-//

الحالة

-//

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

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

+//
+//

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

+//

+// {r.startDate +// ? new Date(r.startDate).toLocaleDateString('ar') +// : '—'} +//

+//
+ +//
+//

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

+//

+// {r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'} +//

+//
+ +//
+//

الحالة

+// +//
+ +//
+//

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

+//

+// {r.createdAt +// ? new Date(r.createdAt).toLocaleDateString('ar') +// : '—'} +//

+//
//
//
+ //
-//

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

-//
الإجمالي{r.totalPrice?.toLocaleString()??'—'}
+//

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

+ +//
+// الإجمالي +// +// {r.totalPrice?.toLocaleString() ?? '—'} +// +//
//
//
//
@@ -149,111 +384,256 @@ // 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 [actionLoadingId, setActionLoadingId] = useState(null); // const loadReservations = useCallback(async () => { // try { +// setLoading(true); + // 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 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); // } -// 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)); +// if (!AuthService.getUser() || !AuthService.isOwner()) { +// router.push('/auth/choose-role'); +// return; // } -// setFiltered(r); + +// 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 { -// const res = await API(AuthService.getToken(), 'PUT', `/Reservations/owner-confirm/${r.id}`); -// if (!res.ok) throw new Error(`HTTP ${res.status}`); +// 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('فشل قبول الحجز'); } +// } catch (err) { +// console.error(err); +// toast.error('فشل قبول الحجز'); +// } finally { +// setActionLoadingId(null); +// } // }; // 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}`); +// 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('فشل رفض الحجز'); } +// } catch (err) { +// console.error(err); +// toast.error('فشل رفض الحجز'); +// } finally { +// setActionLoadingId(null); +// } // }; -// 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])) }; +// const allStatuses = [ +// ...new Set(reservations.map((r) => getStatusKey(r.status)).filter(Boolean)), +// ]; -// if (loading) return
; +// const counts = { +// all: reservations.length, +// ...Object.fromEntries( +// allStatuses.map((s) => [ +// s, +// reservations.filter((r) => getStatusKey(r.status) === s).length, +// ]) +// ), +// }; + +// if (loading) { +// return ( +//
+// +//
+// ); +// } // return ( //
// -// setSelected(null)} /> + +// setSelected(null)} +// /> + //
-// -// +// +// + //
//
-//

طلبات الحجز

+//

+// طلبات الحجز +//

//

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

//
-// + +// //
//
+ //
// {Object.entries(counts).map(([s, c]) => ( -// setFilterStatus(s)}> +// setFilterStatus(s)} +// > //
{c}
-//
{s==='all'?'الكل':(STATUS_UI[s]?.label||s)}
+//
+// {s === 'all' ? 'الكل' : STATUS_UI[s]?.label || s} +//
//
// ))} //
+ //
-// -// 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"/> +// + +// 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 => )} +// {filtered.map((r) => ( +// +// ))} //
// )} //
@@ -262,6 +642,8 @@ // } + + 'use client'; import { useState, useEffect, useCallback } from 'react'; @@ -280,6 +662,7 @@ import { Home, ArrowLeft, RefreshCw, + Flag, } from 'lucide-react'; import toast, { Toaster } from 'react-hot-toast'; import AuthService from '../../services/AuthService'; @@ -397,12 +780,114 @@ function buildImageUrl(path) { return `${API_BASE}${path}`; } +const getAuthToken = () => { + if (typeof window === 'undefined') return ''; + return ( + AuthService.getToken?.() || + AuthService.getAccessToken?.() || + localStorage.getItem('token') || + localStorage.getItem('accessToken') || + localStorage.getItem('authToken') || + '' + ); +}; + +const readStoredUser = () => { + if (typeof window === 'undefined') return null; + const keys = ['user', 'currentUser', 'authUser', 'profile']; + for (const key of keys) { + const raw = localStorage.getItem(key); + if (!raw) continue; + try { + return JSON.parse(raw); + } catch { + return raw; + } + } + return null; +}; + +const extractNumericUserId = (value) => { + if (!value) return null; + if (typeof value === 'number') return Number.isInteger(value) ? value : null; + if (typeof value === 'string') { + const n = Number(value); + return Number.isInteger(n) ? n : null; + } + if (typeof value === 'object') { + const candidates = [ + value.id, + value.userId, + value.userID, + value.user?.id, + value.user?.userId, + value.profile?.id, + value.profile?.userId, + value.data?.id, + ]; + for (const candidate of candidates) { + const id = extractNumericUserId(candidate); + if (id !== null) return id; + } + } + return null; +}; + +async function reportReservation(reservationId, message) { + const user = AuthService.getUser?.() ?? readStoredUser(); + const reporter = extractNumericUserId(user); + const rid = Number(reservationId); + + if (!Number.isInteger(rid)) { + throw new Error('رقم الحجز غير صالح'); + } + if (!Number.isInteger(reporter)) { + throw new Error('تعذر تحديد المستخدم الحالي'); + } + + const token = getAuthToken(); + const res = await fetch(`${API_BASE}/ReservationReports/ReportReservation`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ + reservationId: rid, + message: message ?? null, + reporter, + }), + }); + + if (!res.ok) { + let errorMessage = 'فشل إرسال البلاغ'; + try { + const data = await res.json(); + errorMessage = data?.message || data?.title || errorMessage; + } catch (_) { + try { + const text = await res.text(); + if (text) errorMessage = text; + } catch (_) {} + } + throw new Error(errorMessage); + } + + try { + return await res.json(); + } catch { + return null; + } +} + function OwnerCard({ r, onViewDetails, onConfirm, onReject, + onReport, actionLoadingId, + reportingId, }) { const p = r._prop; const imgs = pImgs(p); @@ -410,6 +895,7 @@ function OwnerCard({ const addr = pAddr(p); const isPending = Number(r.status) === 0; const isActionLoading = actionLoadingId === r.id; + const isReporting = reportingId === r.id; return ( )}
+ +
); } -function DetailsModal({ r, isOpen, onClose }) { +function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) { if (!isOpen || !r) return null; const p = r._prop; + const isReporting = reportingId === r.id; return ( { + if (isOpen) setMessage(''); + }, [isOpen, reservation?.id]); + + if (!isOpen || !reservation) return null; + + return ( + + e.stopPropagation()} + > +
+
+
+

الإبلاغ عن الحجز

+

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

+
+ +
+
+ +
+

+ اخبر فريق الدعم بما حدث التفاصيل الواضحة تساعدنا على مراجعة هذا الحجز بشكل اسرع +

+ +