diff --git a/app/components/ratings/PropertyRatingForm.js b/app/components/ratings/PropertyRatingForm.js
new file mode 100644
index 0000000..c2384a3
--- /dev/null
+++ b/app/components/ratings/PropertyRatingForm.js
@@ -0,0 +1,317 @@
+// import { useState } from 'react';
+// import { motion } from 'framer-motion';
+// import { Star, Edit2, X, Check, Clock } from 'lucide-react';
+// import StarRating from './StarRating.js';
+// import toast, { Toaster } from 'react-hot-toast';
+// import { rateProperty, rateCustomer, getUserPropertyRating, canRateProperty } from '../../utils/ratings.js';
+
+// const RatingForm = ({
+// propertyId,
+// userId,
+// propertyOwner = false,
+// initialRating = 0,
+// initialComment = '',
+// onSubmitSuccess
+// }) => {
+// const [rating, setRating] = useState(initialRating);
+// const [comment, setComment] = useState(initialComment);
+// const [loading, setLoading] = useState(false);
+// const [showForm, setShowForm] = useState(false);
+// const [userRating, setUserRating] = useState(null);
+
+// // Check if user has already rated
+// useState(() => {
+// async function fetchUserRating() {
+// try {
+// const rating = await getUserPropertyRating(propertyId, userId);
+// if (rating) {
+// setUserRating(rating);
+// setRating(rating.rating);
+// setComment(rating.comment || '');
+// }
+// } catch (error) {
+// console.error('[RatingForm] Failed to fetch user rating:', error);
+// }
+// }
+
+// if (propertyId && userId) {
+// fetchUserRating();
+// }
+// }, [propertyId, userId]);
+
+// const handleSubmit = async (e) => {
+// e.preventDefault();
+
+// if (!rating) {
+// toast.error('يرجى إعطاء تقييم من 1 إلى 5 نجوم');
+// return;
+// }
+
+// setLoading(true);
+
+// try {
+// const ratingData = {
+// propertyId,
+// customerId: userId,
+// rating,
+// comment: comment.trim() || null
+// };
+
+// await rateProperty(ratingData);
+
+// toast.success('تم إرسال التقييم بنجاح!');
+
+// // Reset form
+// setRating(0);
+// setComment('');
+// setShowForm(false);
+
+// if (onSubmitSuccess) {
+// onSubmitSuccess();
+// }
+// } catch (error) {
+// console.error('[RatingForm] Failed to submit rating:', error);
+// toast.error('حدث خطأ أثناء إرسال التقييم. يرجى المحاولة مرة أخرى.');
+// } finally {
+// setLoading(false);
+// }
+// };
+
+// const handleEdit = () => {
+// setShowForm(true);
+// setRating(userRating?.rating || 0);
+// setComment(userRating?.comment || '');
+// };
+
+// const handleCancel = () => {
+// setShowForm(false);
+// setRating(userRating?.rating || 0);
+// setComment(userRating?.comment || '');
+// };
+
+// if (!propertyId || !userId) {
+// return null;
+// }
+
+// return (
+//
- {[...Array(maxStars)].map((_, index) => (
-
handleClick(index + 1)}
- onMouseEnter={() => handleMouseEnter(index + 1)}
- >
- {getStarIcon(index)}
-
- ))}
+
!readOnly && setHoverRating(0)}
+ >
+ {[1, 2, 3, 4, 5].map((star) => {
+ const filled = (hoverRating || rating) >= star;
+ return (
+
+ );
+ })}
);
};
-export default StarRating;
-
-// Helper functions
-export function getStarCount(rating, maxStars = 5) {
- return Math.round(rating * maxStars) / maxStars;
-}
-
-export function formatRating(rating) {
- if (rating === 0) return 'لا يوجد تقييم';
- return `${rating.toFixed(1)}`; // Show 1 decimal place
-}
-
-export function getRatingColor(rating) {
- if (rating >= 4.5) return 'text-green-600';
- if (rating >= 3.5) return 'text-yellow-600';
- if (rating >= 2.5) return 'text-orange-600';
- return 'text-red-600';
-}
-
-export function getRatingText(rating) {
- if (rating >= 4.5) return 'ممتاز';
- if (rating >= 3.5) return 'جيد جداً';
- if (rating >= 2.5) return 'جيد';
- if (rating >= 1.5) return 'مقبول';
- return 'ضعيف';
-}
\ No newline at end of file
+export default StarRating;
\ No newline at end of file
diff --git a/app/property/[id]/PropertyDetail.js b/app/property/[id]/PropertyDetail.js
index 82c0145..417658b 100644
--- a/app/property/[id]/PropertyDetail.js
+++ b/app/property/[id]/PropertyDetail.js
@@ -1,1083 +1,1373 @@
+// 'use client';
+
+// import { useState, useEffect } from 'react';
+// import { motion } from 'framer-motion';
+// import toast, { Toaster } from 'react-hot-toast';
+// import Image from 'next/image';
+// import Link from 'next/link';
+// import { useParams } from 'next/navigation';
+// import {
+// MapPin,
+// Bed,
+// Bath,
+// Square,
+// DollarSign,
+// Heart,
+// Share2,
+// Phone,
+// Mail,
+// MessageCircle,
+// Calendar,
+// Shield,
+// Star,
+// ChevronLeft,
+// ChevronRight,
+// Check,
+// X,
+// Wifi,
+// Car,
+// Coffee,
+// Wind,
+// Thermometer,
+// Lock,
+// Camera,
+// Home,
+// Building2,
+// Users,
+// Ruler,
+// CalendarDays,
+// Clock,
+// Award,
+// FileText,
+// Printer,
+// Download,
+// ArrowLeft,
+// LogIn,
+// Copy
+// } from 'lucide-react';
+// import { getRentProperty, getSaleProperty, bookReservation, checkAvailability, getAvailableDateRanges } from '../../utils/api';
+// import AuthService from '../../services/AuthService';
+// import { useFavorites } from '@/app/contexts/FavoritesContext';
+// import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums';
+// import RatingForm from '@/app/components/ratings/RatingForm.js';
+// import RatingList from '@/app/components/ratings/RatingList.js';
+// import StarRating from '@/app/components/ratings/StarRating.js';
+
+// // Copy to clipboard that works on HTTP too
+// async function copyToClipboard(text) {
+// try {
+// await navigator.clipboard.writeText(text);
+// return true;
+// } catch {
+// // Fallback for HTTP / older browsers
+// const textarea = document.createElement('textarea');
+// textarea.value = text;
+// textarea.style.position = 'fixed';
+// textarea.style.opacity = '0';
+// document.body.appendChild(textarea);
+// textarea.select();
+// try {
+// document.execCommand('copy');
+// return true;
+// } catch {
+// return false;
+// } finally {
+// document.body.removeChild(textarea);
+// }
+// }
+// }
+
+// // Map API response to the UI format
+// function mapApiDetail(item) {
+// if (!item) return null;
+
+// const info = item.propertyInformation || {};
+
+// const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
+// const monthlyPrice = item.monthlyRent ?? 0;
+
+// const propType = BuildingTypeKeys[info.buildingType] ?? BuildingTypeKeys[item.type] ?? 'apartment';
+// const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available';
+
+// const features = [];
+// if (item.isSmokeAllow) features.push({ name: 'يسمح بالتدخين', available: true, description: '' });
+// if (item.isVisitorAllow) features.push({ name: 'يسمح بالزوار', available: true, description: '' });
+// if (item.specializedFor) features.push({ name: 'متخصص', available: true, description: '' });
+// if (info.numberOfBedRooms) features.push({ name: 'غرف النوم', available: true, description: `${info.numberOfBedRooms} غرف` });
+// if (info.numberOfBathRooms) features.push({ name: 'الحمامات', available: true, description: `${info.numberOfBathRooms} حمامات` });
+// if (info.space) features.push({ name: 'المساحة', available: true, description: `${info.space} م²` });
+
+// const typeLabels = { 0: 'شقة', 1: 'فيلا', 2: 'بيت' };
+
+// // Extract images from API and build full URLs
+// const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : '';
+// const rawImages = Array.isArray(info.images) ? info.images : [];
+// const images = rawImages.length > 0
+// ? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`)
+// : ['/property-placeholder.jpg', '/villa1.jpg', '/villa2.jpg'];
+
+// return {
+// id: item.id,
+// title: info.address || `عقار #${item.id}`,
+// description: info.description || 'عقار سكني مميز في موقع استراتيجي.',
+// type: propType,
+// price: dailyPrice,
+// priceUnit: 'daily',
+// location: {
+// city: extractCity(info.address) || 'دمشق',
+// district: info.address || '',
+// address: info.address || '',
+// lat: parseFloat(info.cordsX) || 0,
+// lng: parseFloat(info.cordsY) || 0,
+// },
+// bedrooms: info.numberOfBedRooms || 0,
+// bathrooms: info.numberOfBathRooms || 0,
+// area: info.space || 0,
+// features: features.length > 0 ? features : [
+// { name: 'متاح للإيجار', available: true, description: '' },
+// ],
+// images,
+// status,
+// rating: item.rating || 4.5,
+// reviews: 0,
+// reviewList: [],
+// owner: {
+// name: 'المالك',
+// phone: '—',
+// email: '—',
+// rating: 4.8,
+// properties: 1,
+// memberSince: '2024',
+// responseRate: '95%',
+// responseTime: 'خلال ساعات',
+// },
+// nearby: [],
+// specifications: {
+// constructionYear: null,
+// floor: '-',
+// parking: 0,
+// gardenArea: 0,
+// poolArea: 0,
+// furnished: false,
+// airConditioning: '-',
+// heating: '-',
+// electricity: '220V',
+// water: 'شبكة عامة',
+// },
+// rules: [],
+// _raw: item,
+// };
+// }
+
+// // extractCity is now imported from @/app/enums
+
+// // API-only — no fallback data
+
+// export default function PropertyDetailsPage() {
+// const params = useParams();
+// const { isFavorite, addFavorite, removeFavorite } = useFavorites();
+// const [currentImage, setCurrentImage] = useState(0);
+// const [showContact, setShowContact] = useState(false);
+// const [showShareMenu, setShowShareMenu] = useState(false);
+// const [bookingDates, setBookingDates] = useState({ start: '', end: '' });
+// const [selectedDuration, setSelectedDuration] = useState(1);
+// const [property, setProperty] = useState(null);
+// const [loading, setLoading] = useState(true);
+// const [bookingError, setBookingError] = useState(null);
+// const [bookingSuccess, setBookingSuccess] = useState(false);
+// const [availableRanges, setAvailableRanges] = useState([]);
+// const [calendarMonth, setCalendarMonth] = useState(new Date());
+// const [favLoading, setFavLoading] = useState(false);
+// const [selectingEnd, setSelectingEnd] = useState(false);
+// const [showLoginDialog, setShowLoginDialog] = useState(false);
+// const [showRatingForm, setShowRatingForm] = useState(false);
+// const [currentRating, setCurrentRating] = useState(0);
+// const [currentComment, setCurrentComment] = useState('');
+// const [userRating, setUserRating] = useState(null);
+// const [canRate, setCanRate] = useState(false);
+
+// useEffect(() => {
+// const id = params.id;
+// setLoading(true);
+// setBookingError(null);
+// setBookingSuccess(false);
+
+// async function fetchProperty() {
+// try {
+// // Try RentProperties first, then SaleProperties
+// let data = null;
+// try {
+// data = await getRentProperty(id);
+// } catch {
+// try {
+// data = await getSaleProperty(id);
+// } catch {
+// // neither worked
+// }
+// }
+
+// if (data) {
+// const mapped = mapApiDetail(data);
+// if (mapped) {
+// setProperty(mapped);
+// setLoading(false);
+// return;
+// }
+// }
+// setProperty(null);
+// } catch (err) {
+// console.error('[Property] Failed to fetch property:', err);
+// setProperty(null);
+// } finally {
+// setLoading(false);
+// }
+// }
+
+// fetchProperty();
+// }, [params.id]);
+
+// // Fetch user rating and check if they can rate
+// useEffect(() => {
+// async function fetchUserRatingAndCheck() {
+// if (!property || !AuthService.isAuthenticated()) return;
+
+// try {
+// // Check if user has already rated
+// const rating = await getUserPropertyRating(property._raw?.id || parseInt(params.id), AuthService.getUserId());
+// if (rating) {
+// setUserRating(rating);
+// setCurrentRating(rating.rating);
+// setCurrentComment(rating.comment || '');
+// }
+
+// // Check if user can rate (e.g., after renting)
+// const canRateProperty = await canRateProperty(property._raw?.id || parseInt(params.id), AuthService.getUserId());
+// setCanRate(canRateProperty);
+// } catch (error) {
+// console.error('[Property] Failed to fetch user rating:', error);
+// }
+// }
+
+// fetchUserRatingAndCheck();
+// }, [property, params.id]);
+
+// // Set Open Graph meta tags dynamically for Facebook/Twitter sharing
+// useEffect(() => {
+// if (!property) return;
+
+// const typeLabel = property.type === 'villa' ? 'فيلا' : property.type === 'apartment' ? 'شقة' : 'بيت';
+// const priceLabel = `${formatCurrency(property.price)} / ${property.priceUnit === 'daily' ? 'يوم' : 'شهر'}`;
+// const desc = `${typeLabel} في ${property.location?.address || ''} · ${property.bedrooms} غرف نوم · ${property.bathrooms} حمامات · ${property.area} م²`;
+// const imageUrl = property.images?.[0]
+// ? (property.images[0].startsWith('http') ? property.images[0] : `http://45.93.137.91${property.images[0]}`)
+// : '';
+
+// const setMeta = (prop, content) => {
+// let tag = document.querySelector(`meta[property="${prop}"]`);
+// if (!tag) {
+// tag = document.createElement('meta');
+// tag.setAttribute('property', prop);
+// document.head.appendChild(tag);
+// }
+// tag.setAttribute('content', content);
+// };
+
+// const setMetaName = (name, content) => {
+// let tag = document.querySelector(`meta[name="${name}"]`);
+// if (!tag) {
+// tag = document.createElement('meta');
+// tag.setAttribute('name', name);
+// document.head.appendChild(tag);
+// }
+// tag.setAttribute('content', content);
+// };
+
+// setMeta('og:title', `${property.title} - ${priceLabel}`);
+// setMeta('og:description', desc);
+// if (imageUrl) setMeta('og:image', imageUrl);
+// setMeta('og:url', window.location.href);
+// setMeta('og:type', 'website');
+// setMeta('og:site_name', 'SweetHome');
+
+// // Twitter cards
+// setMetaName('twitter:card', 'summary_large_image');
+// setMetaName('twitter:title', `${property.title} - ${priceLabel}`);
+// setMetaName('twitter:description', desc);
+// if (imageUrl) setMetaName('twitter:image', imageUrl);
+// }, [property]);
+
+// const formatCurrency = (amount) => {
+// return amount?.toLocaleString() + ' ل.س';
+// };
+
+// const calculateTotalPrice = () => {
+// if (!property) return 0;
+// const days = bookingDates.start && bookingDates.end
+// ? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24))
+// : selectedDuration;
+// return property.price * (days > 0 ? days : 1);
+// };
+
+// // Calendar helpers
+// const isDateAvailable = (dateStr) => {
+// const d = new Date(dateStr + 'T00:00:00');
+// return availableRanges.some((range) => {
+// const start = new Date(range.startDate);
+// const end = new Date(range.endDate);
+// return d >= start && d <= end;
+// });
+// };
+
+// const isInRange = (dateStr) => {
+// if (!bookingDates.start) return false;
+// const d = new Date(dateStr + 'T00:00:00');
+// const start = new Date(bookingDates.start + 'T00:00:00');
+// const end = bookingDates.end ? new Date(bookingDates.end + 'T00:00:00') : start;
+// return d >= start && d <= end;
+// };
+
+// const isRangeFullyAvailable = (startStr, endStr) => {
+// const start = new Date(startStr + 'T00:00:00');
+// const end = new Date(endStr + 'T00:00:00');
+// for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
+// if (!isDateAvailable(d.toISOString().split('T')[0])) return false;
+// }
+// return true;
+// };
+
+// const handleCalendarClick = (dateStr) => {
+// if (!isDateAvailable(dateStr)) return;
+
+// if (!bookingDates.start || selectingEnd) {
+// if (!bookingDates.start) {
+// setBookingDates({ start: dateStr, end: '' });
+// setSelectingEnd(true);
+// } else {
+// const start = bookingDates.start;
+// const end = dateStr;
+// const [s, e] = end > start ? [start, end] : [end, start];
+// if (isRangeFullyAvailable(s, e)) {
+// setBookingDates({ start: s, end: e });
+// setSelectingEnd(false);
+// } else {
+// toast.error('بعض التواريخ في هذه الفترة غير متاحة');
+// }
+// }
+// } else {
+// setBookingDates({ start: dateStr, end: '' });
+// setSelectingEnd(true);
+// }
+// };
+
+// const getDaysInMonth = (year, month) => new Date(year, month + 1, 0).getDate();
+// const getFirstDayOfMonth = (year, month) => new Date(year, month, 1).getDay();
+
+// const formatDateStr = (year, month, day) => {
+// return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
+// };
+
+// const monthNames = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'];
+// const dayNames = ['أح', 'إث', 'ثل', 'أر', 'خم', 'جم', 'سب'];
+
+// const handleBooking = async () => {
+// if (!AuthService.isAuthenticated()) {
+// setShowLoginDialog(true);
+// return;
+// }
+
+// setBookingError(null);
+// setBookingSuccess(false);
+
+// if (!bookingDates.start || !bookingDates.end) {
+// setBookingError('يرجى اختيار تاريخ البداية والنهاية');
+// return;
+// }
+
+// const propId = property?._raw?.id || parseInt(params.id);
+// const startDate = new Date(bookingDates.start).toISOString();
+// const endDate = new Date(bookingDates.end).toISOString();
+
+// console.log('[Booking] Reserving:', { propertyId: propId, startDate, endDate });
+
+// try {
+// const res = await bookReservation(propId, startDate, endDate);
+// console.log('[Booking] Success:', res);
+// setBookingSuccess(true);
+// toast.success('تم إرسال طلب الحجز بنجاح!');
+// } catch (err) {
+// console.error('[Booking] Failed:', err);
+// setBookingError(err.message || 'فشل في إرسال طلب الحجز');
+// }
+// };
+
+// if (loading) {
+// return (
+//
+//
+//
+//
جاري تحميل تفاصيل العقار...
+//
+//
+// );
+// }
+
+// if (!property) {
+// return (
+//
+//
+//
+//
العقار غير موجود
+//
لم نتمكن من العثور على العقار المطلوب
+//
+// العودة إلى العقارات
+//
+//
+//
+// );
+// }
+
+// return (
+//
+//
+//
+//
+//
+//
+//
+//
العودة إلى العقارات
+//
+//
+//
+// {/* Share Dropdown */}
+//
+//
+
+// {showShareMenu && (
+// <>
+//
setShowShareMenu(false)} />
+//
+//
+// {/* Facebook */}
+//
+
+// {/* WhatsApp */}
+//
+
+// {/* Telegram */}
+//
+
+// {/* Instagram (copy link) */}
+//
+
+// {/* Copy Link */}
+//
+//
+//
+// >
+// )}
+//
+//
+//
+//
+//
+
+//
+//
+//
+//
+//
+
+// {property.images.length > 1 && (
+// <>
+//
+//
+// >
+// )}
+
+//
+// {property.images.map((_, idx) => (
+//
+
+//
+//
+// {currentImage + 1} / {property.images.length}
+//
+//
+
+//
+// {property.images.slice(1, 5).map((img, idx) => (
+//
setCurrentImage(idx + 1)}
+// className="relative h-[240px] rounded-2xl overflow-hidden cursor-pointer hover:opacity-90 transition-opacity bg-gray-100"
+// >
+//
+//
+// ))}
+//
+//
+//
+
+//
+//
+//
+//
+//
+//
{property.title}
+//
+//
+// {property.location.address}
+//
+//
+//
+//
{formatCurrency(property.price)}
+//
/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}
+//
+//
+
+//
+//
+//
+// {property.rating}
+// {property.reviews > 0 && ({property.reviews} تقييم)}
+//
+//
+//
+// {property.status === 'available' ? 'متاح للإيجار' : 'محجوز حالياً'}
+//
+//
+//
+
+//
+// المواصفات الرئيسية
+//
+//
+//
+//
{property.bedrooms}
+//
غرف نوم
+//
+//
+//
+//
{property.bathrooms}
+//
حمامات
+//
+//
+//
+//
{property.area}
+//
م²
+//
+//
+//
+//
+// {property.type === 'villa' ? 'فيلا' :
+// property.type === 'apartment' ? 'شقة' : 'بيت'}
+//
+//
نوع العقار
+//
+//
+//
+
+//
+// وصف العقار
+// {property.description || 'لا يوجد وصف متاح.'}
+//
+
+//
+// المميزات والخدمات
+//
+// {property.features.map((feature, idx) => (
+//
+//
+// {feature.available ? (
+//
+// ) : (
+//
+// )}
+//
+//
+//
+// {feature.name}
+//
+// {feature.description && (
+//
+// {feature.description}
+//
+// )}
+//
+//
+// ))}
+//
+//
+
+// {property.reviewList && property.reviewList.length > 0 && (
+//
+// تقييمات المستأجرين
+//
+// {property.reviewList.map((review, idx) => (
+//
+//
+//
+//
+//
+//
+//
+//
{review.user}
+//
+// {[...Array(5)].map((_, i) => (
+//
+// ))}
+//
+//
+//
+//
{review.date}
+//
+//
{review.comment}
+//
+// ))}
+//
+//
+// )}
+
+// {/* New Rating Components */}
+// {AuthService.isAuthenticated() && canRate && !userRating && (
+//
setShowRatingForm(true)}
+// >
+//
+// قيّم هذا العقار
+// شارك تجربتك مع المستأجرين الآخرين
+//
+// )}
+
+//
+//
+//
+//
+
+//
+//
+//
+// احجز هذا العقار
+
+// {/* Selected dates display */}
+//
+//
+// من
+// {bookingDates.start || '—'}
+//
+//
+// إلى
+// {bookingDates.end || '—'}
+//
+//
+
+// {bookingDates.start && bookingDates.end && (() => {
+// const days = Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24));
+// return days > 0 ? (
+//
+// {days} يوم{days > 1 ? 'اً' : 'اً'} {selectingEnd ? '— اضغط على تاريخ النهاية' : '✓'}
+//
+// ) : null;
+// })()}
+
+// {/* Calendar */}
+//
+//
+//
+// {monthNames[calendarMonth.getMonth()]} {calendarMonth.getFullYear()}
+//
+//
+
+//
+// {dayNames.map((d) => (
+//
{d}
+// ))}
+//
+
+//
+// {(() => {
+// const year = calendarMonth.getFullYear();
+// const month = calendarMonth.getMonth();
+// const daysInMonth = getDaysInMonth(year, month);
+// const firstDay = getFirstDayOfMonth(year, month);
+// const today = new Date().toISOString().split('T')[0];
+// const cells = [];
+
+// // Empty cells before first day
+// for (let i = 0; i < firstDay; i++) {
+// cells.push(
);
+// }
+
+// for (let day = 1; day <= daysInMonth; day++) {
+// const dateStr = formatDateStr(year, month, day);
+// const available = isDateAvailable(dateStr);
+// const isStart = bookingDates.start === dateStr;
+// const isEnd = bookingDates.end === dateStr;
+// const inRange = isInRange(dateStr);
+// const isPast = dateStr < today;
+
+// cells.push(
+//
+// );
+// }
+// return cells;
+// })()}
+//
+
+//
+//
+
+//
+// {(() => {
+// const days = bookingDates.start && bookingDates.end
+// ? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24))
+// : 0;
+// const effectiveDays = days > 0 ? days : 1;
+// return (
+// <>
+//
+// السعر لـ {effectiveDays} يوم{effectiveDays > 1 ? 'اً' : 'اً'}
+// {formatCurrency(property.price * effectiveDays)}
+//
+//
+// سلفة ضمان
+// {formatCurrency(property._raw?.deposit || 0)}
+//
+//
+// الإجمالي
+// {formatCurrency(property.price * effectiveDays + (property._raw?.deposit || 0))}
+//
+// >
+// );
+// })()}
+//
+
+// {bookingError && (
+//
+// {bookingError}
+//
+// )}
+
+// {bookingSuccess && (
+//
+// تم إرسال طلب الحجز بنجاح. سيتم التواصل معك قريباً.
+//
+// )}
+
+//
+
+//
+//
+// الدفع آمن ومضمون. سلفة الضمان قابلة للاسترداد.
+//
+//
+
+//
+// معلومات المالك
+//
+//
+//
+// {property.owner.name.charAt(0)}
+//
+//
+//
+//
{property.owner.name}
+//
+//
+// {property.owner.rating}
+// · {property.owner.properties} عقارات
+//
+// {property.owner.responseRate && (
+//
+//
+// استجابة: {property.owner.responseRate}
+//
+// )}
+//
+//
+
+// {showContact ? (
+//
+//
+//
+//
{property.owner.phone}
+//
+//
+//
+// {property.owner.email}
+//
+//
+// ) : (
+//
+// )}
+
+//
+//
+//
+//
+//
+//
+
+// {/* Login/Register Dialog */}
+// {showLoginDialog && (
+//
setShowLoginDialog(false)}>
+//
e.stopPropagation()}
+// className="bg-white rounded-2xl p-8 max-w-md w-full mx-4 shadow-2xl text-center"
+// >
+//
+//
+//
+// سجّل الدخول للمتابعة
+// يجب عليك إنشاء حساب أو تسجيل الدخول لحجز هذا العقار
+//
+//
+// إنشاء حساب جديد
+//
+//
+// تسجيل الدخول
+//
+//
+//
+//
+//
+// )}
+//
+// );
+// }
+
'use client';
import { useState, useEffect } from 'react';
-import { motion } from 'framer-motion';
+import { motion, AnimatePresence } from 'framer-motion';
import toast, { Toaster } from 'react-hot-toast';
import Image from 'next/image';
import Link from 'next/link';
import { useParams } from 'next/navigation';
import {
- MapPin,
- Bed,
- Bath,
- Square,
- DollarSign,
- Heart,
- Share2,
- Phone,
- Mail,
- MessageCircle,
- Calendar,
- Shield,
- Star,
- ChevronLeft,
- ChevronRight,
- Check,
- X,
- Wifi,
- Car,
- Coffee,
- Wind,
- Thermometer,
- Lock,
- Camera,
- Home,
- Building2,
- Users,
- Ruler,
- CalendarDays,
- Clock,
- Award,
- FileText,
- Printer,
- Download,
- ArrowLeft,
- LogIn,
- Copy
+ MapPin, Bed, Bath, Square, Heart, Share2, Phone, Mail, MessageCircle,
+ Calendar, Shield, Star, ChevronLeft, ChevronRight, Check, X,
+ Camera, Home, Building2, ArrowLeft, LogIn, Copy, Clock, User, Loader2
} from 'lucide-react';
-import { getRentProperty, getSaleProperty, bookReservation, checkAvailability, getAvailableDateRanges } from '../../utils/api';
import AuthService from '../../services/AuthService';
import { useFavorites } from '@/app/contexts/FavoritesContext';
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums';
-import RatingForm from '@/app/components/ratings/RatingForm.js';
-import RatingList from '@/app/components/ratings/RatingList.js';
-import StarRating from '@/app/components/ratings/StarRating.js';
+import PropertyRatingList from '@/app/components/ratings/PropertyRatingList';
+import PropertyRatingForm from '@/app/components/ratings/PropertyRatingForm';
+import { getPropertyAverageRating } from '@/app/utils/ratings';
+import { getRentProperty, getSaleProperty, bookReservation } from '../../utils/api';
-// Copy to clipboard that works on HTTP too
-async function copyToClipboard(text) {
- try {
- await navigator.clipboard.writeText(text);
- return true;
- } catch {
- // Fallback for HTTP / older browsers
- const textarea = document.createElement('textarea');
- textarea.value = text;
- textarea.style.position = 'fixed';
- textarea.style.opacity = '0';
- document.body.appendChild(textarea);
- textarea.select();
- try {
- document.execCommand('copy');
- return true;
- } catch {
- return false;
- } finally {
- document.body.removeChild(textarea);
- }
- }
+function formatCurrency(amount) {
+ return amount?.toLocaleString() + ' ل.س';
}
-// Map API response to the UI format
function mapApiDetail(item) {
if (!item) return null;
-
const info = item.propertyInformation || {};
-
+ const buildingType = info.buildingType ?? 0;
+ const propType = BuildingTypeKeys[buildingType] ?? 'apartment';
+ const status = PropertyStatusKeys[info.status] ?? 'available';
const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
- const monthlyPrice = item.monthlyRent ?? 0;
+ const priceUnit = item.monthlyRent ? 'monthly' : 'daily';
- const propType = BuildingTypeKeys[info.buildingType] ?? BuildingTypeKeys[item.type] ?? 'apartment';
- const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available';
-
- const features = [];
- if (item.isSmokeAllow) features.push({ name: 'يسمح بالتدخين', available: true, description: '' });
- if (item.isVisitorAllow) features.push({ name: 'يسمح بالزوار', available: true, description: '' });
- if (item.specializedFor) features.push({ name: 'متخصص', available: true, description: '' });
- if (info.numberOfBedRooms) features.push({ name: 'غرف النوم', available: true, description: `${info.numberOfBedRooms} غرف` });
- if (info.numberOfBathRooms) features.push({ name: 'الحمامات', available: true, description: `${info.numberOfBathRooms} حمامات` });
- if (info.space) features.push({ name: 'المساحة', available: true, description: `${info.space} م²` });
-
- const typeLabels = { 0: 'شقة', 1: 'فيلا', 2: 'بيت' };
-
- // Extract images from API and build full URLs
- const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : '';
+ const apiBase = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
const rawImages = Array.isArray(info.images) ? info.images : [];
const images = rawImages.length > 0
? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`)
- : ['/property-placeholder.jpg', '/villa1.jpg', '/villa2.jpg'];
+ : ['/property-placeholder.jpg'];
return {
id: item.id,
title: info.address || `عقار #${item.id}`,
- description: info.description || 'عقار سكني مميز في موقع استراتيجي.',
+ description: info.description || '',
type: propType,
price: dailyPrice,
- priceUnit: 'daily',
- location: {
- city: extractCity(info.address) || 'دمشق',
- district: info.address || '',
- address: info.address || '',
- lat: parseFloat(info.cordsX) || 0,
- lng: parseFloat(info.cordsY) || 0,
- },
+ priceUnit,
+ location: { city: extractCity(info.address) || 'دمشق', address: info.address || '' },
bedrooms: info.numberOfBedRooms || 0,
bathrooms: info.numberOfBathRooms || 0,
area: info.space || 0,
- features: features.length > 0 ? features : [
- { name: 'متاح للإيجار', available: true, description: '' },
- ],
images,
status,
- rating: item.rating || 4.5,
- reviews: 0,
- reviewList: [],
- owner: {
- name: 'المالك',
- phone: '—',
- email: '—',
- rating: 4.8,
- properties: 1,
- memberSince: '2024',
- responseRate: '95%',
- responseTime: 'خلال ساعات',
- },
- nearby: [],
- specifications: {
- constructionYear: null,
- floor: '-',
- parking: 0,
- gardenArea: 0,
- poolArea: 0,
- furnished: false,
- airConditioning: '-',
- heating: '-',
- electricity: '220V',
- water: 'شبكة عامة',
- },
- rules: [],
+ deposit: item.deposit || 0,
_raw: item,
};
}
-// extractCity is now imported from @/app/enums
-
-// API-only — no fallback data
-
export default function PropertyDetailsPage() {
const params = useParams();
+ const propertyId = parseInt(params.id);
const { isFavorite, addFavorite, removeFavorite } = useFavorites();
+
+ const [property, setProperty] = useState(null);
+ const [loading, setLoading] = useState(true);
const [currentImage, setCurrentImage] = useState(0);
const [showContact, setShowContact] = useState(false);
const [showShareMenu, setShowShareMenu] = useState(false);
+ const [favLoading, setFavLoading] = useState(false);
+ const [showLoginDialog, setShowLoginDialog] = useState(false);
const [bookingDates, setBookingDates] = useState({ start: '', end: '' });
- const [selectedDuration, setSelectedDuration] = useState(1);
- const [property, setProperty] = useState(null);
- const [loading, setLoading] = useState(true);
const [bookingError, setBookingError] = useState(null);
const [bookingSuccess, setBookingSuccess] = useState(false);
- const [availableRanges, setAvailableRanges] = useState([]);
- const [calendarMonth, setCalendarMonth] = useState(new Date());
- const [favLoading, setFavLoading] = useState(false);
- const [selectingEnd, setSelectingEnd] = useState(false);
- const [showLoginDialog, setShowLoginDialog] = useState(false);
+ const [averageRating, setAverageRating] = useState(null);
+ const [eligibleReservationId, setEligibleReservationId] = useState(null);
const [showRatingForm, setShowRatingForm] = useState(false);
- const [currentRating, setCurrentRating] = useState(0);
- const [currentComment, setCurrentComment] = useState('');
- const [userRating, setUserRating] = useState(null);
- const [canRate, setCanRate] = useState(false);
useEffect(() => {
- const id = params.id;
- setLoading(true);
- setBookingError(null);
- setBookingSuccess(false);
-
async function fetchProperty() {
try {
- // Try RentProperties first, then SaleProperties
let data = null;
- try {
- data = await getRentProperty(id);
- } catch {
- try {
- data = await getSaleProperty(id);
- } catch {
- // neither worked
- }
- }
-
- if (data) {
- const mapped = mapApiDetail(data);
- if (mapped) {
- setProperty(mapped);
- setLoading(false);
- return;
- }
- }
- setProperty(null);
+ try { data = await getRentProperty(propertyId); } catch {}
+ if (!data) try { data = await getSaleProperty(propertyId); } catch {}
+ if (data) setProperty(mapApiDetail(data));
+ else throw new Error('Property not found');
} catch (err) {
- console.error('[Property] Failed to fetch property:', err);
- setProperty(null);
+ console.error(err);
+ toast.error('فشل تحميل العقار');
} finally {
setLoading(false);
}
}
-
fetchProperty();
- }, [params.id]);
+ }, [propertyId]);
- // Fetch user rating and check if they can rate
useEffect(() => {
- async function fetchUserRatingAndCheck() {
- if (!property || !AuthService.isAuthenticated()) return;
-
+ async function fetchAvg() {
+ if (!propertyId) return;
try {
- // Check if user has already rated
- const rating = await getUserPropertyRating(property._raw?.id || parseInt(params.id), AuthService.getUserId());
- if (rating) {
- setUserRating(rating);
- setCurrentRating(rating.rating);
- setCurrentComment(rating.comment || '');
- }
-
- // Check if user can rate (e.g., after renting)
- const canRateProperty = await canRateProperty(property._raw?.id || parseInt(params.id), AuthService.getUserId());
- setCanRate(canRateProperty);
- } catch (error) {
- console.error('[Property] Failed to fetch user rating:', error);
- }
+ const avg = await getPropertyAverageRating(propertyId);
+ setAverageRating(avg);
+ } catch (err) { console.error(err); }
}
-
- fetchUserRatingAndCheck();
- }, [property, params.id]);
+ fetchAvg();
+ }, [propertyId]);
- // Set Open Graph meta tags dynamically for Facebook/Twitter sharing
useEffect(() => {
- if (!property) return;
-
- const typeLabel = property.type === 'villa' ? 'فيلا' : property.type === 'apartment' ? 'شقة' : 'بيت';
- const priceLabel = `${formatCurrency(property.price)} / ${property.priceUnit === 'daily' ? 'يوم' : 'شهر'}`;
- const desc = `${typeLabel} في ${property.location?.address || ''} · ${property.bedrooms} غرف نوم · ${property.bathrooms} حمامات · ${property.area} م²`;
- const imageUrl = property.images?.[0]
- ? (property.images[0].startsWith('http') ? property.images[0] : `http://45.93.137.91${property.images[0]}`)
- : '';
-
- const setMeta = (prop, content) => {
- let tag = document.querySelector(`meta[property="${prop}"]`);
- if (!tag) {
- tag = document.createElement('meta');
- tag.setAttribute('property', prop);
- document.head.appendChild(tag);
+ async function checkEligibility() {
+ if (!propertyId || !AuthService.isAuthenticated()) return;
+ try {
+ const res = await fetch(`http://45.93.137.91/api/Reservations/GetUserReservations?userId=${AuthService.getUserId()}`);
+ const data = await res.json();
+ const reservations = data?.data || data || [];
+ const completed = reservations.find(r =>
+ r.propertyId == propertyId &&
+ (r.status === 'Completed' || r.status === 'Confirmed') &&
+ !r.hasRatedProperty
+ );
+ setEligibleReservationId(completed?.id || null);
+ } catch (err) {
+ console.error('[Eligibility]', err);
}
- tag.setAttribute('content', content);
- };
-
- const setMetaName = (name, content) => {
- let tag = document.querySelector(`meta[name="${name}"]`);
- if (!tag) {
- tag = document.createElement('meta');
- tag.setAttribute('name', name);
- document.head.appendChild(tag);
- }
- tag.setAttribute('content', content);
- };
-
- setMeta('og:title', `${property.title} - ${priceLabel}`);
- setMeta('og:description', desc);
- if (imageUrl) setMeta('og:image', imageUrl);
- setMeta('og:url', window.location.href);
- setMeta('og:type', 'website');
- setMeta('og:site_name', 'SweetHome');
-
- // Twitter cards
- setMetaName('twitter:card', 'summary_large_image');
- setMetaName('twitter:title', `${property.title} - ${priceLabel}`);
- setMetaName('twitter:description', desc);
- if (imageUrl) setMetaName('twitter:image', imageUrl);
- }, [property]);
-
- const formatCurrency = (amount) => {
- return amount?.toLocaleString() + ' ل.س';
- };
-
- const calculateTotalPrice = () => {
- if (!property) return 0;
- const days = bookingDates.start && bookingDates.end
- ? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24))
- : selectedDuration;
- return property.price * (days > 0 ? days : 1);
- };
-
- // Calendar helpers
- const isDateAvailable = (dateStr) => {
- const d = new Date(dateStr + 'T00:00:00');
- return availableRanges.some((range) => {
- const start = new Date(range.startDate);
- const end = new Date(range.endDate);
- return d >= start && d <= end;
- });
- };
-
- const isInRange = (dateStr) => {
- if (!bookingDates.start) return false;
- const d = new Date(dateStr + 'T00:00:00');
- const start = new Date(bookingDates.start + 'T00:00:00');
- const end = bookingDates.end ? new Date(bookingDates.end + 'T00:00:00') : start;
- return d >= start && d <= end;
- };
-
- const isRangeFullyAvailable = (startStr, endStr) => {
- const start = new Date(startStr + 'T00:00:00');
- const end = new Date(endStr + 'T00:00:00');
- for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
- if (!isDateAvailable(d.toISOString().split('T')[0])) return false;
}
- return true;
- };
-
- const handleCalendarClick = (dateStr) => {
- if (!isDateAvailable(dateStr)) return;
-
- if (!bookingDates.start || selectingEnd) {
- if (!bookingDates.start) {
- setBookingDates({ start: dateStr, end: '' });
- setSelectingEnd(true);
- } else {
- const start = bookingDates.start;
- const end = dateStr;
- const [s, e] = end > start ? [start, end] : [end, start];
- if (isRangeFullyAvailable(s, e)) {
- setBookingDates({ start: s, end: e });
- setSelectingEnd(false);
- } else {
- toast.error('بعض التواريخ في هذه الفترة غير متاحة');
- }
- }
- } else {
- setBookingDates({ start: dateStr, end: '' });
- setSelectingEnd(true);
- }
- };
-
- const getDaysInMonth = (year, month) => new Date(year, month + 1, 0).getDate();
- const getFirstDayOfMonth = (year, month) => new Date(year, month, 1).getDay();
-
- const formatDateStr = (year, month, day) => {
- return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
- };
-
- const monthNames = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'];
- const dayNames = ['أح', 'إث', 'ثل', 'أر', 'خم', 'جم', 'سب'];
+ checkEligibility();
+ }, [propertyId]);
const handleBooking = async () => {
- if (!AuthService.isAuthenticated()) {
- setShowLoginDialog(true);
- return;
- }
-
+ if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; }
setBookingError(null);
- setBookingSuccess(false);
-
if (!bookingDates.start || !bookingDates.end) {
setBookingError('يرجى اختيار تاريخ البداية والنهاية');
return;
}
-
- const propId = property?._raw?.id || parseInt(params.id);
- const startDate = new Date(bookingDates.start).toISOString();
- const endDate = new Date(bookingDates.end).toISOString();
-
- console.log('[Booking] Reserving:', { propertyId: propId, startDate, endDate });
-
try {
- const res = await bookReservation(propId, startDate, endDate);
- console.log('[Booking] Success:', res);
+ await bookReservation(propertyId, bookingDates.start, bookingDates.end);
setBookingSuccess(true);
- toast.success('تم إرسال طلب الحجز بنجاح!');
+ toast.success('تم إرسال طلب الحجز بنجاح');
} catch (err) {
- console.error('[Booking] Failed:', err);
- setBookingError(err.message || 'فشل في إرسال طلب الحجز');
+ setBookingError(err.message || 'فشل الحجز');
}
};
if (loading) {
return (
-
-
-
-
جاري تحميل تفاصيل العقار...
-
+
+
);
}
-
if (!property) {
- return (
-
-
-
-
العقار غير موجود
-
لم نتمكن من العثور على العقار المطلوب
-
- العودة إلى العقارات
-
-
-
- );
+ return
العقار غير موجود
;
}
return (
-
+
-
-
-
العودة إلى العقارات
+
+
العودة
+ {/* زر المشاركة (اختصار) */}
+
- {/* Share Dropdown */}
-
-
-
- {showShareMenu && (
- <>
-
setShowShareMenu(false)} />
-
-
- {/* Facebook */}
-
-
- {/* WhatsApp */}
-
-
- {/* Telegram */}
-
-
- {/* Instagram (copy link) */}
-
-
- {/* Copy Link */}
-
-
-
- >
- )}
-
-
-
-
-
-
- {property.images.length > 1 && (
- <>
-
-
- >
- )}
-
-
- {property.images.map((_, idx) => (
-
-
-
-
- {currentImage + 1} / {property.images.length}
-
-
-
-
- {property.images.slice(1, 5).map((img, idx) => (
-
setCurrentImage(idx + 1)}
- className="relative h-[240px] rounded-2xl overflow-hidden cursor-pointer hover:opacity-90 transition-opacity bg-gray-100"
- >
-
-
+
+
+ {property.images.length > 1 && (
+
+ {property.images.map((_, idx) => (
+
-
-
+ )}
+
-
+
-
-
-
-
{property.title}
-
-
- {property.location.address}
-
-
-
-
{formatCurrency(property.price)}
-
/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}
-
-
-
-
+
+
{property.title}
+
{property.location.address}
+
-
- {property.rating}
- {property.reviews > 0 && ({property.reviews} تقييم)}
+
+ {averageRating > 0 ? averageRating.toFixed(1) : property.rating}
-
-
- {property.status === 'available' ? 'متاح للإيجار' : 'محجوز حالياً'}
+
+ {property.status === 'available' ? 'متاح' : 'محجوز'}
-
+
-
- المواصفات الرئيسية
+
+
المواصفات
-
-
-
{property.bedrooms}
-
غرف نوم
-
-
-
-
{property.bathrooms}
-
حمامات
-
-
-
-
{property.area}
-
م²
-
-
-
-
- {property.type === 'villa' ? 'فيلا' :
- property.type === 'apartment' ? 'شقة' : 'بيت'}
-
-
نوع العقار
-
+
{property.bedrooms} غرف نوم
+
{property.bathrooms} حمامات
+
-
+
-
- وصف العقار
- {property.description || 'لا يوجد وصف متاح.'}
-
+
+
الوصف
+
{property.description}
+
-
- المميزات والخدمات
-
- {property.features.map((feature, idx) => (
-
-
- {feature.available ? (
-
- ) : (
-
- )}
-
-
-
- {feature.name}
-
- {feature.description && (
-
- {feature.description}
-
- )}
-
-
- ))}
-
-
+
- {property.reviewList && property.reviewList.length > 0 && (
-
- تقييمات المستأجرين
-
- {property.reviewList.map((review, idx) => (
-
-
-
-
-
-
-
-
{review.user}
-
- {[...Array(5)].map((_, i) => (
-
- ))}
-
-
-
-
{review.date}
-
-
{review.comment}
-
- ))}
-
-
- )}
-
- {/* New Rating Components */}
- {AuthService.isAuthenticated() && canRate && !userRating && (
- setShowRatingForm(true)}
- >
+ {eligibleReservationId && !showRatingForm && (
+ setShowRatingForm(true)}>
-
قيّم هذا العقار
+
قيّم هذا العقار
شارك تجربتك مع المستأجرين الآخرين
-
+
)}
-
-
- {
+ setShowRatingForm(false);
+ setEligibleReservationId(null);
+ getPropertyAverageRating(propertyId).then(setAverageRating);
+ }}
+ onCancel={() => setShowRatingForm(false)}
/>
-
+ )}
-
-
- احجز هذا العقار
-
- {/* Selected dates display */}
-
-
- من
- {bookingDates.start || '—'}
-
-
- إلى
- {bookingDates.end || '—'}
-
-
-
- {bookingDates.start && bookingDates.end && (() => {
- const days = Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24));
- return days > 0 ? (
-
- {days} يوم{days > 1 ? 'اً' : 'اً'} {selectingEnd ? '— اضغط على تاريخ النهاية' : '✓'}
-
- ) : null;
- })()}
-
- {/* Calendar */}
-
-
-
- {monthNames[calendarMonth.getMonth()]} {calendarMonth.getFullYear()}
-
-
-
-
- {dayNames.map((d) => (
-
{d}
- ))}
-
-
-
- {(() => {
- const year = calendarMonth.getFullYear();
- const month = calendarMonth.getMonth();
- const daysInMonth = getDaysInMonth(year, month);
- const firstDay = getFirstDayOfMonth(year, month);
- const today = new Date().toISOString().split('T')[0];
- const cells = [];
-
- // Empty cells before first day
- for (let i = 0; i < firstDay; i++) {
- cells.push(
);
- }
-
- for (let day = 1; day <= daysInMonth; day++) {
- const dateStr = formatDateStr(year, month, day);
- const available = isDateAvailable(dateStr);
- const isStart = bookingDates.start === dateStr;
- const isEnd = bookingDates.end === dateStr;
- const inRange = isInRange(dateStr);
- const isPast = dateStr < today;
-
- cells.push(
-
- );
- }
- return cells;
- })()}
-
-
-
-
-
-
- {(() => {
- const days = bookingDates.start && bookingDates.end
- ? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24))
- : 0;
- const effectiveDays = days > 0 ? days : 1;
- return (
- <>
-
- السعر لـ {effectiveDays} يوم{effectiveDays > 1 ? 'اً' : 'اً'}
- {formatCurrency(property.price * effectiveDays)}
-
-
- سلفة ضمان
- {formatCurrency(property._raw?.deposit || 0)}
-
-
- الإجمالي
- {formatCurrency(property.price * effectiveDays + (property._raw?.deposit || 0))}
-
- >
- );
- })()}
-
-
- {bookingError && (
-
- {bookingError}
-
- )}
-
- {bookingSuccess && (
-
- تم إرسال طلب الحجز بنجاح. سيتم التواصل معك قريباً.
-
- )}
-
-
-
-
-
- الدفع آمن ومضمون. سلفة الضمان قابلة للاسترداد.
-
-
-
-
- معلومات المالك
-
-
-
- {property.owner.name.charAt(0)}
-
-
-
-
{property.owner.name}
-
-
- {property.owner.rating}
- · {property.owner.properties} عقارات
-
- {property.owner.responseRate && (
-
-
- استجابة: {property.owner.responseRate}
-
- )}
-
-
-
- {showContact ? (
-
-
-
-
{property.owner.phone}
-
-
-
- {property.owner.email}
-
-
- ) : (
-
- )}
-
-
-
+
+
احجز هذا العقار
+
+ setBookingDates({ ...bookingDates, start: e.target.value })} />
+ setBookingDates({ ...bookingDates, end: e.target.value })} />
+
+
+
السعر اليومي{formatCurrency(property.price)}
+
سلفة ضمان{formatCurrency(property.deposit)}
+
الإجمالي{formatCurrency(property.price + property.deposit)}
+
+
+ {bookingError &&
{bookingError}
}
+
+
+
معلومات المالك
+ {showContact ? (
+
{property._raw?.phoneNumber || 'غير متوفر'}
+ ) : (
+
+ )}
- {/* Login/Register Dialog */}
{showLoginDialog && (
-
setShowLoginDialog(false)}>
-
e.stopPropagation()}
- className="bg-white rounded-2xl p-8 max-w-md w-full mx-4 shadow-2xl text-center"
- >
-
-
-
- سجّل الدخول للمتابعة
- يجب عليك إنشاء حساب أو تسجيل الدخول لحجز هذا العقار
-
-
- إنشاء حساب جديد
-
-
- تسجيل الدخول
-
-
-
-
+
setShowLoginDialog(false)}>
+
e.stopPropagation()}>
+
تسجيل الدخول مطلوب
+
للحجز أو إضافة المفضلة، سجل دخولك.
+
تسجيل الدخول
+
إنشاء حساب
+
+
)}
);
-}
+}
\ No newline at end of file
diff --git a/app/utils/ratings.js b/app/utils/ratings.js
index 27c968d..50b3e4a 100644
--- a/app/utils/ratings.js
+++ b/app/utils/ratings.js
@@ -1,193 +1,292 @@
-// Rating API endpoints for SweetHome
-// Handles both customer ratings and property ratings
+// // Rating API endpoints for SweetHome
+// // Handles both customer ratings and property ratings
+// import AuthService from '../services/AuthService';
+
+// const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
+
+// /**
+// * Rate a property as a customer
+// * @param {Object} data - Rating data
+// * @param {number} data.propertyId - ID of the property being rated
+// * @param {number} data.customerId - ID of the customer doing the rating
+// * @param {number} data.rating - Rating value (1-5)
+// * @param {string} data.comment - Optional comment
+// * @returns {Promise} - API response
+// */
+// export async function rateProperty(data) {
+// console.log('[Rating] Customer rating property:', data);
+// return apiFetch('/Ratings/CustomerRateProperty', {
+// method: 'POST',
+// body: JSON.stringify(data),
+// });
+// }
+
+// /**
+// * Rate a customer as a property owner
+// * @param {Object} data - Rating data
+// * @param {number} data.propertyId - ID of the property
+// * @param {number} data.customerId - ID of the customer being rated
+// * @param {number} data.rating - Rating value (1-5)
+// * @param {string} data.comment - Optional comment
+// * @returns {Promise} - API response
+// */
+// export async function rateCustomer(data) {
+// console.log('[Rating] Property owner rating customer:', data);
+// return apiFetch('/Ratings/PropertyRateCustomer', {
+// method: 'POST',
+// body: JSON.stringify(data),
+// });
+// }
+
+// /**
+// * Get all ratings for a property
+// * @param {number} propertyId - ID of the property
+// * @returns {Promise} - Array of ratings
+// */
+// export async function getPropertyRatings(propertyId) {
+// console.log('[Rating] Fetching property ratings for:', propertyId);
+// return apiFetch(`/Ratings/GetPropertyRatings?propertyId=${propertyId}`);
+// }
+
+// /**
+// * Get all ratings for a customer
+// * @param {number} customerId - ID of the customer
+// * @returns {Promise} - Array of ratings
+// */
+// export async function getCustomerRatings(customerId) {
+// console.log('[Rating] Fetching customer ratings for:', customerId);
+// return apiFetch(`/Ratings/GetCustomerRatings?customerId=${customerId}`);
+// }
+
+// /**
+// * Get average rating for a property
+// * @param {number} propertyId - ID of the property
+// * @returns {Promise} - Average rating
+// */
+// export async function getPropertyAverageRating(propertyId) {
+// console.log('[Rating] Fetching average rating for property:', propertyId);
+// const ratings = await getPropertyRatings(propertyId);
+// if (!Array.isArray(ratings) || ratings.length === 0) return 0;
+
+// const total = ratings.reduce((sum, rating) => sum + rating.rating, 0);
+// return Math.round((total / ratings.length) * 10) / 10; // Round to 1 decimal
+// }
+
+// /**
+// * Get average rating for a customer
+// * @param {number} customerId - ID of the customer
+// * @returns {Promise} - Average rating
+// */
+// export async function getCustomerAverageRating(customerId) {
+// console.log('[Rating] Fetching average rating for customer:', customerId);
+// const ratings = await getCustomerRatings(customerId);
+// if (!Array.isArray(ratings) || ratings.length === 0) return 0;
+
+// const total = ratings.reduce((sum, rating) => sum + rating.rating, 0);
+// return Math.round((total / ratings.length) * 10) / 10; // Round to 1 decimal
+// }
+
+// /**
+// * Get user's rating for a specific property (if any)
+// * @param {number} propertyId - ID of the property
+// * @param {number} userId - ID of the user
+// * @returns {Promise} - User's rating or null
+// */
+// export async function getUserPropertyRating(propertyId, userId) {
+// console.log('[Rating] Fetching user rating for property:', propertyId, 'user:', userId);
+// const allRatings = await getPropertyRatings(propertyId);
+// if (!Array.isArray(allRatings)) return null;
+
+// return allRatings.find(r => r.userId === userId) || null;
+// }
+
+// /**
+// * Get user's rating for a specific customer (if any)
+// * @param {number} customerId - ID of the customer
+// * @param {number} userId - ID of the user
+// * @returns {Promise} - User's rating or null
+// */
+// export async function getUserCustomerRating(customerId, userId) {
+// console.log('[Rating] Fetching user rating for customer:', customerId, 'user:', userId);
+// const allRatings = await getCustomerRatings(customerId);
+// if (!Array.isArray(allRatings)) return null;
+
+// return allRatings.find(r => r.userId === userId) || null;
+// }
+
+// /**
+// * Check if user can rate a property (after renting)
+// * @param {number} propertyId - ID of the property
+// * @param {number} userId - ID of the user
+// * @returns {Promise} - Boolean indicating if rating is allowed
+// */
+// export async function canRateProperty(propertyId, userId) {
+// console.log('[Rating] Checking if user can rate property:', propertyId, 'user:', userId);
+
+// // Logic: User can rate if they have completed a rental in the past
+// // This would typically check reservation history
+// // For now, we'll simulate this with a simple check
+
+// // In a real implementation, this would check:
+// // 1. User's reservation history for this property
+// // 2. Whether the rental period has ended
+// // 3. Whether they've already rated
+
+// const userRating = await getUserPropertyRating(propertyId, userId);
+// return !userRating; // Can rate if no existing rating
+// }
+
+// /**
+// * Check if user can rate a customer (after renting to them)
+// * @param {number} customerId - ID of the customer
+// * @param {number} userId - ID of the user (owner)
+// * @returns {Promise} - Boolean indicating if rating is allowed
+// */
+// export async function canRateCustomer(customerId, userId) {
+// console.log('[Rating] Checking if user can rate customer:', customerId, 'user:', userId);
+
+// // Logic: Owner can rate if they have rented to this customer
+// // This would typically check reservation history
+
+// const userRating = await getUserCustomerRating(customerId, userId);
+// return !userRating; // Can rate if no existing rating
+// }
+
+// // Helper function for API calls
+// async function apiFetch(endpoint, options = {}) {
+// const token = AuthService.getToken();
+
+// const headers = {
+// 'Content-Type': 'application/json',
+// ...(token && { Authorization: `Bearer ${token}` }),
+// ...options.headers,
+// };
+
+// console.log('[Rating API] Request:', options.method || 'GET', `${API_BASE}${endpoint}`);
+
+// const res = await fetch(`${API_BASE}${endpoint}`, {
+// ...options,
+// headers,
+// });
+
+// console.log('[Rating API] Response:', res.status, endpoint);
+
+// if (!res.ok && res.status !== 206) {
+// const text = await res.text().catch(() => '');
+// console.error('[Rating API] Error:', res.status, text);
+// throw new Error(`Rating API ${res.status}: ${text || res.statusText}`);
+// }
+
+// const text = await res.text();
+// if (!text) return null;
+
+// try {
+// const json = JSON.parse(text);
+// if (json && typeof json === 'object' && 'data' in json) {
+// return json.data;
+// }
+// return json;
+// } catch {
+// return text;
+// }
+// }
+
+
+// utils/ratings.js
import AuthService from '../services/AuthService';
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
-
-/**
- * Rate a property as a customer
- * @param {Object} data - Rating data
- * @param {number} data.propertyId - ID of the property being rated
- * @param {number} data.customerId - ID of the customer doing the rating
- * @param {number} data.rating - Rating value (1-5)
- * @param {string} data.comment - Optional comment
- * @returns {Promise} - API response
- */
-export async function rateProperty(data) {
- console.log('[Rating] Customer rating property:', data);
- return apiFetch('/Ratings/CustomerRateProperty', {
- method: 'POST',
- body: JSON.stringify(data),
- });
-}
-
-/**
- * Rate a customer as a property owner
- * @param {Object} data - Rating data
- * @param {number} data.propertyId - ID of the property
- * @param {number} data.customerId - ID of the customer being rated
- * @param {number} data.rating - Rating value (1-5)
- * @param {string} data.comment - Optional comment
- * @returns {Promise} - API response
- */
-export async function rateCustomer(data) {
- console.log('[Rating] Property owner rating customer:', data);
- return apiFetch('/Ratings/PropertyRateCustomer', {
- method: 'POST',
- body: JSON.stringify(data),
- });
-}
-
-/**
- * Get all ratings for a property
- * @param {number} propertyId - ID of the property
- * @returns {Promise} - Array of ratings
- */
-export async function getPropertyRatings(propertyId) {
- console.log('[Rating] Fetching property ratings for:', propertyId);
- return apiFetch(`/Ratings/GetPropertyRatings?propertyId=${propertyId}`);
-}
-
-/**
- * Get all ratings for a customer
- * @param {number} customerId - ID of the customer
- * @returns {Promise} - Array of ratings
- */
-export async function getCustomerRatings(customerId) {
- console.log('[Rating] Fetching customer ratings for:', customerId);
- return apiFetch(`/Ratings/GetCustomerRatings?customerId=${customerId}`);
-}
-
-/**
- * Get average rating for a property
- * @param {number} propertyId - ID of the property
- * @returns {Promise} - Average rating
- */
-export async function getPropertyAverageRating(propertyId) {
- console.log('[Rating] Fetching average rating for property:', propertyId);
- const ratings = await getPropertyRatings(propertyId);
- if (!Array.isArray(ratings) || ratings.length === 0) return 0;
-
- const total = ratings.reduce((sum, rating) => sum + rating.rating, 0);
- return Math.round((total / ratings.length) * 10) / 10; // Round to 1 decimal
-}
-
-/**
- * Get average rating for a customer
- * @param {number} customerId - ID of the customer
- * @returns {Promise} - Average rating
- */
-export async function getCustomerAverageRating(customerId) {
- console.log('[Rating] Fetching average rating for customer:', customerId);
- const ratings = await getCustomerRatings(customerId);
- if (!Array.isArray(ratings) || ratings.length === 0) return 0;
-
- const total = ratings.reduce((sum, rating) => sum + rating.rating, 0);
- return Math.round((total / ratings.length) * 10) / 10; // Round to 1 decimal
-}
-
-/**
- * Get user's rating for a specific property (if any)
- * @param {number} propertyId - ID of the property
- * @param {number} userId - ID of the user
- * @returns {Promise} - User's rating or null
- */
-export async function getUserPropertyRating(propertyId, userId) {
- console.log('[Rating] Fetching user rating for property:', propertyId, 'user:', userId);
- const allRatings = await getPropertyRatings(propertyId);
- if (!Array.isArray(allRatings)) return null;
-
- return allRatings.find(r => r.userId === userId) || null;
-}
-
-/**
- * Get user's rating for a specific customer (if any)
- * @param {number} customerId - ID of the customer
- * @param {number} userId - ID of the user
- * @returns {Promise} - User's rating or null
- */
-export async function getUserCustomerRating(customerId, userId) {
- console.log('[Rating] Fetching user rating for customer:', customerId, 'user:', userId);
- const allRatings = await getCustomerRatings(customerId);
- if (!Array.isArray(allRatings)) return null;
-
- return allRatings.find(r => r.userId === userId) || null;
-}
-
-/**
- * Check if user can rate a property (after renting)
- * @param {number} propertyId - ID of the property
- * @param {number} userId - ID of the user
- * @returns {Promise} - Boolean indicating if rating is allowed
- */
-export async function canRateProperty(propertyId, userId) {
- console.log('[Rating] Checking if user can rate property:', propertyId, 'user:', userId);
-
- // Logic: User can rate if they have completed a rental in the past
- // This would typically check reservation history
- // For now, we'll simulate this with a simple check
-
- // In a real implementation, this would check:
- // 1. User's reservation history for this property
- // 2. Whether the rental period has ended
- // 3. Whether they've already rated
-
- const userRating = await getUserPropertyRating(propertyId, userId);
- return !userRating; // Can rate if no existing rating
-}
-
-/**
- * Check if user can rate a customer (after renting to them)
- * @param {number} customerId - ID of the customer
- * @param {number} userId - ID of the user (owner)
- * @returns {Promise} - Boolean indicating if rating is allowed
- */
-export async function canRateCustomer(customerId, userId) {
- console.log('[Rating] Checking if user can rate customer:', customerId, 'user:', userId);
-
- // Logic: Owner can rate if they have rented to this customer
- // This would typically check reservation history
-
- const userRating = await getUserCustomerRating(customerId, userId);
- return !userRating; // Can rate if no existing rating
-}
-
-// Helper function for API calls
async function apiFetch(endpoint, options = {}) {
const token = AuthService.getToken();
-
const headers = {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
...options.headers,
};
- console.log('[Rating API] Request:', options.method || 'GET', `${API_BASE}${endpoint}`);
-
- const res = await fetch(`${API_BASE}${endpoint}`, {
+ const response = await fetch(`${API_BASE}${endpoint}`, {
...options,
headers,
});
- console.log('[Rating API] Response:', res.status, endpoint);
-
- if (!res.ok && res.status !== 206) {
- const text = await res.text().catch(() => '');
- console.error('[Rating API] Error:', res.status, text);
- throw new Error(`Rating API ${res.status}: ${text || res.statusText}`);
+ if (!response.ok && response.status !== 206) {
+ const errorText = await response.text().catch(() => '');
+ throw new Error(`API Error ${response.status}: ${errorText}`);
}
- const text = await res.text();
+ const text = await response.text();
if (!text) return null;
try {
const json = JSON.parse(text);
- if (json && typeof json === 'object' && 'data' in json) {
- return json.data;
- }
- return json;
+ return json && typeof json === 'object' && 'data' in json ? json.data : json;
} catch {
return text;
}
+}
+
+/**
+ * POST /Rating/AddPropertyRating
+ * @param {Object} data - { reservationId, cleanRating, servicesRating, ownerBehaviorRating, experienceRating, comment? }
+ */
+export async function addPropertyRating(data) {
+ return apiFetch('/Rating/AddPropertyRating', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+}
+
+/**
+ * POST /Rating/AddCustomerRating
+ * @param {Object} data - { reservationId, furnitureIntegrityRating, termsComplianceRating, renterBehaviorRating, comment? }
+ */
+export async function addCustomerRating(data) {
+ return apiFetch('/Rating/AddCustomerRating', {
+ method: 'POST',
+ body: JSON.stringify(data),
+ });
+}
+
+/**
+ * GET /Rating/GetPropertyRatings
+ * @param {number} propertyId
+ * @param {number} page - default 1
+ * @param {number} pageSize - default 10
+ * @returns {Promise<{ items: Array, totalPages: number, currentPage: number }>}
+ */
+export async function getPropertyRatings(propertyId, page = 1, pageSize = 10) {
+ const query = new URLSearchParams({
+ propertyId: String(propertyId),
+ page: String(page),
+ pageSize: String(pageSize),
+ }).toString();
+ return apiFetch(`/Rating/GetPropertyRatings?${query}`);
+}
+
+/**
+ * GET /Rating/GetCustomerRatings
+ * @param {number} renterId
+ * @param {number} page
+ * @param {number} pageSize
+ */
+export async function getCustomerRatings(renterId, page = 1, pageSize = 10) {
+ const query = new URLSearchParams({
+ renterId: String(renterId),
+ page: String(page),
+ pageSize: String(pageSize),
+ }).toString();
+ return apiFetch(`/Rating/GetCustomerRatings?${query}`);
+}
+
+/**
+ * GET /Rating/GetPropertyAverage
+ * @param {number} propertyId
+ * @returns {Promise
} average rating (0 if none)
+ */
+export async function getPropertyAverageRating(propertyId) {
+ const result = await apiFetch(`/Rating/GetPropertyAverage?propertyId=${propertyId}`);
+ if (typeof result === 'number') return result;
+ if (result && typeof result.average === 'number') return result.average;
+ return 0;
}
\ No newline at end of file