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 43875f0..bbf319d 100644
--- a/app/property/[id]/PropertyDetail.js
+++ b/app/property/[id]/PropertyDetail.js
@@ -1083,21 +1083,6 @@
// }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
'use client';
import { useState, useEffect } from 'react';
@@ -1111,61 +1096,56 @@ import {
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
+ Copy,
+ Clock,
+ Users,
+ 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,
+ getAvailableDateRanges,
+} from '../../utils/api';
+
+function formatCurrency(amount) {
+ return amount?.toLocaleString() + ' ل.س';
+}
-// 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;
@@ -1177,160 +1157,174 @@ async function copyToClipboard(text) {
}
}
-// 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 buildingType = info.buildingType ?? 0;
+ const propType = BuildingTypeKeys[buildingType] ?? BuildingTypeKeys[item.type] ?? 'apartment';
const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available';
+ const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
+ const priceUnit = item.monthlyRent ? 'monthly' : 'daily';
+
+ const apiBase = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
+ const rawImages = Array.isArray(info.images) ? info.images.filter(Boolean) : [];
+
+ const images = rawImages.length > 0
+ ? rawImages.map((img) =>
+ img.startsWith('http')
+ ? img
+ : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`
+ )
+ : ['/property-placeholder.jpg'];
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: 'بيت' };
+ if (item.isSmokeAllow) {
+ features.push({ name: 'يسمح بالتدخين', available: true, description: '' });
+ }
- // 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.filter(Boolean) : [];
- const images = rawImages.length > 0
- ? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`)
- : ['/property-placeholder.jpg', '/villa1.jpg', '/villa2.jpg'];
+ 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} م²`,
+ });
+ }
return {
id: item.id,
title: info.address || `عقار #${item.id}`,
- description: info.description || 'عقار سكني مميز في موقع استراتيجي.',
+ description: info.description || '',
type: propType,
price: dailyPrice,
- priceUnit: 'daily',
+ priceUnit,
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: [],
+ deposit: item.deposit || 0,
+ rating: item.rating || 0,
+ features: features.length > 0
+ ? features
+ : [{ name: 'متاح للإيجار', available: true, description: '' }],
owner: {
- name: 'المالك',
- phone: '—',
- email: '—',
- rating: 4.8,
- properties: 1,
- memberSince: '2024',
- responseRate: '95%',
- responseTime: 'خلال ساعات',
+ name: item.ownerName || info.ownerName || 'المالك',
+ phone: item.phoneNumber || info.phoneNumber || 'غير متوفر',
+ email: item.email || info.email || 'غير متوفر',
+ rating: item.ownerRating || 0,
+ properties: item.ownerPropertiesCount || 1,
+ responseRate: item.responseRate || '',
},
- 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 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 [bookingLoading, setBookingLoading] = 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() {
+ setLoading(true);
+ setBookingError(null);
+ setBookingSuccess(false);
+
try {
- // Try RentProperties first, then SaleProperties
let data = null;
+
try {
- data = await getRentProperty(id);
+ data = await getRentProperty(propertyId);
} catch {
+ // Try sale property below
+ }
+
+ if (!data) {
try {
- data = await getSaleProperty(id);
+ data = await getSaleProperty(propertyId);
} catch {
- // neither worked
+ // No property found
}
}
if (data) {
- const mapped = mapApiDetail(data);
- if (mapped) {
- setProperty(mapped);
- setLoading(false);
- return;
- }
+ setProperty(mapApiDetail(data));
+ } else {
+ throw new Error('Property not found');
}
- setProperty(null);
} catch (err) {
console.error('[Property] Failed to fetch property:', err);
+ toast.error('فشل تحميل العقار');
setProperty(null);
} finally {
setLoading(false);
}
}
- fetchProperty();
- }, [params.id]);
+ if (propertyId) {
+ fetchProperty();
+ }
+ }, [propertyId]);
useEffect(() => {
async function fetchAvailableRanges() {
if (!property) return;
- const propId = property?._raw?.id || parseInt(params.id);
+ const rentPropertyId = property?._raw?.id || propertyId;
const year = calendarMonth.getFullYear();
const month = calendarMonth.getMonth();
@@ -1339,12 +1333,11 @@ export default function PropertyDetailsPage() {
const toDate = new Date(year, month + 1, 0, 23, 59, 59).toISOString();
try {
- const result = await getAvailableDateRanges(propId, fromDate, toDate);
-
+ const result = await getAvailableDateRanges(rentPropertyId, fromDate, toDate);
const ranges = Array.isArray(result) ? result : [];
console.log('[Availability] Loaded ranges:', {
- propertyId: propId,
+ propertyId: rentPropertyId,
fromDate,
toDate,
ranges,
@@ -1359,65 +1352,94 @@ export default function PropertyDetailsPage() {
}
fetchAvailableRanges();
- }, [property, params.id, calendarMonth]);
+ }, [property, propertyId, calendarMonth]);
- // Fetch user rating and check if they can rate
useEffect(() => {
- async function fetchUserRatingAndCheck() {
- if (!property || !AuthService.isAuthenticated()) return;
-
+ async function fetchAverageRating() {
+ if (!propertyId) return;
+
try {
- // Check if user has already rated
- if (typeof getUserPropertyRating === 'function') {
- 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)
- if (typeof canRateProperty === 'function') {
- const canRateResult = await canRateProperty(property._raw?.id || parseInt(params.id), AuthService.getUserId());
- setCanRate(canRateResult);
- }
- } catch (error) {
- console.error('[Property] Failed to fetch user rating:', error);
+ const avg = await getPropertyAverageRating(propertyId);
+ setAverageRating(avg);
+ } catch (err) {
+ console.error('[Rating] Failed to fetch average rating:', err);
}
}
-
- fetchUserRatingAndCheck();
- }, [property, params.id]);
- // Set Open Graph meta tags dynamically for Facebook/Twitter sharing
+ fetchAverageRating();
+ }, [propertyId]);
+
+ useEffect(() => {
+ 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(
+ (reservation) =>
+ reservation.propertyId == propertyId &&
+ (reservation.status === 'Completed' || reservation.status === 'Confirmed') &&
+ !reservation.hasRatedProperty
+ );
+
+ setEligibleReservationId(completed?.id || null);
+ } catch (err) {
+ console.error('[Eligibility] Failed to check rating eligibility:', err);
+ }
+ }
+
+ checkEligibility();
+ }, [propertyId]);
+
useEffect(() => {
if (!property) return;
- const typeLabel = property.type === 'villa' ? 'فيلا' : property.type === 'apartment' ? 'شقة' : 'بيت';
- const priceLabel = `${formatCurrency(property.price)} / ${property.priceUnit === 'daily' ? 'يوم' : 'شهر'}`;
+ 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]}`)
+ ? 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);
};
@@ -1428,26 +1450,12 @@ export default function PropertyDetailsPage() {
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) => {
return availableRanges.some((range) => {
if (!range?.startDate || !range?.endDate) return false;
@@ -1461,12 +1469,28 @@ export default function PropertyDetailsPage() {
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;
+ const end = bookingDates.end
+ ? new Date(bookingDates.end + 'T00:00:00')
+ : start;
+
return d >= start && d <= end;
};
+ const getDaysInMonth = (year, month) => {
+ return new Date(year, month + 1, 0).getDate();
+ };
+
+ const getFirstDayOfMonth = (year, month) => {
+ return 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 isRangeFullyAvailable = (startStr, endStr) => {
const start = new Date(startStr + 'T00:00:00');
const end = new Date(endStr + 'T00:00:00');
@@ -1509,14 +1533,21 @@ export default function PropertyDetailsPage() {
}
};
- const getDaysInMonth = (year, month) => new Date(year, month + 1, 0).getDate();
- const getFirstDayOfMonth = (year, month) => new Date(year, month, 1).getDay();
+ const monthNames = [
+ 'يناير',
+ 'فبراير',
+ 'مارس',
+ 'أبريل',
+ 'مايو',
+ 'يونيو',
+ 'يوليو',
+ 'أغسطس',
+ 'سبتمبر',
+ 'أكتوبر',
+ 'نوفمبر',
+ 'ديسمبر',
+ ];
- const formatDateStr = (year, month, day) => {
- return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
- };
-
- const monthNames = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'];
const dayNames = ['أح', 'إث', 'ثل', 'أر', 'خم', 'جم', 'سب'];
const toApiStartDate = (dateStr) => {
@@ -1527,12 +1558,23 @@ export default function PropertyDetailsPage() {
return `${dateStr}T23:59:59`;
};
+ const calculateBookingDays = () => {
+ if (!bookingDates.start || !bookingDates.end) return 0;
+
+ return Math.ceil(
+ (new Date(bookingDates.end) - new Date(bookingDates.start)) /
+ (1000 * 60 * 60 * 24)
+ );
+ };
+
const handleBooking = async () => {
if (!AuthService.isAuthenticated()) {
setShowLoginDialog(true);
return;
}
+ if (bookingLoading) return;
+
setBookingError(null);
setBookingSuccess(false);
@@ -1547,11 +1589,11 @@ export default function PropertyDetailsPage() {
}
const propertyInfoId =
+ property?._raw?.propertyInformationId ||
property?._raw?.propertyInformation?.id ||
property?._raw?.propertyInfoId ||
- property?._raw?.propertyInformationId ||
property?._raw?.id ||
- parseInt(params.id);
+ propertyId;
const startDate = toApiStartDate(bookingDates.start);
const endDate = toApiEndDate(bookingDates.end);
@@ -1560,199 +1602,169 @@ export default function PropertyDetailsPage() {
bookingDates,
propertyInfoId,
rawId: property?._raw?.id,
- propertyInfoFromRaw: property?._raw?.propertyInformation,
propertyRaw: property?._raw,
startDate,
endDate,
- payload: {
- dto: {
- propertyInfoId,
- startDate,
- endDate,
- },
- },
});
try {
- const res = await bookReservation(propertyInfoId, startDate, endDate);
- console.log('[Booking] Success:', res);
+ setBookingLoading(true);
+
+ await bookReservation(propertyInfoId, startDate, endDate);
+
setBookingSuccess(true);
- toast.success('تم إرسال طلب الحجز بنجاح!');
+ toast.success('تم إرسال طلب الحجز بنجاح');
} catch (err) {
console.error('[Booking] Failed:', err);
- setBookingError(err.message || 'فشل في إرسال طلب الحجز');
+ setBookingError(err.message || 'فشل الحجز');
+ } finally {
+ setBookingLoading(false);
}
};
if (loading) {
return (
-
-
-
-
جاري تحميل تفاصيل العقار...
-
+
+
);
}
if (!property) {
return (
-
-
-
-
العقار غير موجود
-
لم نتمكن من العثور على العقار المطلوب
-
- العودة إلى العقارات
-
-
+
+ العقار غير موجود
);
}
+ const bookingDays = calculateBookingDays();
+ const effectiveDays = bookingDays > 0 ? bookingDays : 1;
+ const totalPrice = property.price * effectiveDays + property.deposit;
+
return (
-
+
+
-
+
-
العودة إلى العقارات
+
العودة
+
- {/* Share Dropdown */}
{showShareMenu && (
<>
-
setShowShareMenu(false)} />
+
setShowShareMenu(false)}
+ />
+
-
-
-
@@ -1765,171 +1777,167 @@ export default function PropertyDetailsPage() {
-
-
-
-
+
+
- {property.images.length > 1 && (
- <>
-
-
- >
- )}
+ {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"
- >
-
-
- ))}
-
+
+
+ {currentImage + 1} / {property.images.length}
-
+
-
+
-
-
-
-
{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.bedrooms} غرف نوم
-
-
-
{property.bathrooms}
-
حمامات
+
+
+
+
{property.bathrooms} حمامات
-
-
-
{property.area}
-
م²
+
+
-
-
-
- {property.type === 'villa' ? 'فيلا' :
- property.type === 'apartment' ? 'شقة' : 'بيت'}
+
+
+
+
+ {property.type === 'villa'
+ ? 'فيلا'
+ : property.type === 'apartment'
+ ? 'شقة'
+ : 'بيت'}
-
نوع العقار
-
+
-
- وصف العقار
- {property.description || 'لا يوجد وصف متاح.'}
-
+
+
الوصف
+
+ {property.description || 'لا يوجد وصف متاح.'}
+
+
+
+
+
المميزات والخدمات
-
- المميزات والخدمات
{property.features.map((feature, idx) => (
-
-
+
+
{feature.available ? (
) : (
)}
+
-
+
{feature.name}
+
{feature.description && (
-
+
{feature.description}
)}
@@ -1937,67 +1945,34 @@ export default function PropertyDetailsPage() {
))}
-
+
- {property.reviewList && property.reviewList.length > 0 && (
-
- تقييمات المستأجرين
-
- {property.reviewList.map((review, idx) => (
-
-
-
-
-
-
-
-
{review.user}
-
- {[...Array(5)].map((_, i) => (
-
- ))}
-
-
-
-
{review.date}
-
-
{review.comment}
-
- ))}
-
-
- )}
+
- {AuthService.isAuthenticated() && canRate && !userRating && (
-
setShowRatingForm(true)}
>
- قيّم هذا العقار
- شارك تجربتك مع المستأجرين الآخرين
-
+
قيّم هذا العقار
+
+ شارك تجربتك مع المستأجرين الآخرين
+
+
)}
-
- {
+ setShowRatingForm(false);
+ setEligibleReservationId(null);
+ getPropertyAverageRating(propertyId).then(setAverageRating);
+ }}
+ onCancel={() => setShowRatingForm(false)}
/>
-
+ )}
@@ -2007,42 +1982,76 @@ export default function PropertyDetailsPage() {
animate={{ opacity: 1, x: 0 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 mb-6"
>
-
احجز هذا العقار
+
+ احجز هذا العقار
+
من
- {bookingDates.start || '—'}
+
+ {bookingDates.start || '—'}
+
+
إلى
- {bookingDates.end || '—'}
+
+ {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;
- })()}
+ {bookingDates.start && bookingDates.end && bookingDays > 0 && (
+
+ {bookingDays} يوماً {selectingEnd ? '— اضغط على تاريخ النهاية' : '✓'}
+
+ )}
-
- {dayNames.map((d) => (
-
{d}
+ {dayNames.map((day) => (
+
+ {day}
+
))}
@@ -2055,11 +2064,11 @@ export default function PropertyDetailsPage() {
const today = new Date().toISOString().split('T')[0];
const cells = [];
- for (let i = 0; i < firstDay; i++) {
+ for (let i = 0; i < firstDay; i += 1) {
cells.push(
);
}
- for (let day = 1; day <= daysInMonth; day++) {
+ for (let day = 1; day <= daysInMonth; day += 1) {
const dateStr = formatDateStr(year, month, day);
const available = isDateAvailable(dateStr);
const isStart = bookingDates.start === dateStr;
@@ -2086,6 +2095,7 @@ export default function PropertyDetailsPage() {
);
}
+
return cells;
})()}
@@ -2095,10 +2105,12 @@ export default function PropertyDetailsPage() {
متاح
+
+
غير متاح
@@ -2107,28 +2119,28 @@ export default function PropertyDetailsPage() {
- {(() => {
- 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))}
-
- >
- );
- })()}
+
+
+ السعر لـ {effectiveDays} يوم
+
+
+ {formatCurrency(property.price * effectiveDays)}
+
+
+
+
+ سلفة ضمان
+
+ {formatCurrency(property.deposit)}
+
+
+
+
+ الإجمالي
+
+ {formatCurrency(totalPrice)}
+
+
{bookingError && (
@@ -2145,10 +2157,14 @@ export default function PropertyDetailsPage() {
- {bookingSuccess ? 'تم الإرسال ✓' : 'تأكيد الحجز'}
+ {bookingLoading
+ ? 'جاري الإرسال...'
+ : bookingSuccess
+ ? 'تم الإرسال ✓'
+ : 'تأكيد الحجز'}
@@ -2163,20 +2179,22 @@ export default function PropertyDetailsPage() {
transition={{ delay: 0.2 }}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
>
-
معلومات المالك
+
+ معلومات المالك
+
+
{property.owner.name.charAt(0)}
+
-
{property.owner.name}
-
-
-
{property.owner.rating}
-
· {property.owner.properties} عقارات
+
+ {property.owner.name}
+
{property.owner.responseRate && (
@@ -2190,11 +2208,16 @@ export default function PropertyDetailsPage() {
-
{property.owner.phone}
+
+ {property.owner.phone}
+
+
- {property.owner.email}
+
+ {property.owner.email}
+
) : (
@@ -2218,39 +2241,45 @@ export default function PropertyDetailsPage() {
{showLoginDialog && (
-
setShowLoginDialog(false)}>
-
setShowLoginDialog(false)}
+ >
+ e.stopPropagation()}
- className="bg-white rounded-2xl p-8 max-w-md w-full mx-4 shadow-2xl text-center"
>
-
سجّل الدخول للمتابعة
-
يجب عليك إنشاء حساب أو تسجيل الدخول لحجز هذا العقار
-
-
- إنشاء حساب جديد
-
-
- تسجيل الدخول
-
- setShowLoginDialog(false)}
- className="block w-full text-gray-400 py-2 text-sm hover:text-gray-600 transition-colors"
- >
- إلغاء
-
-
-
+
+
تسجيل الدخول مطلوب
+
+
+ للحجز أو إضافة المفضلة، سجل دخولك.
+
+
+
+ تسجيل الدخول
+
+
+
+ إنشاء حساب
+
+
+
setShowLoginDialog(false)}
+ className="mt-3 text-gray-400"
+ >
+ إلغاء
+
+
)}
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