From b4196c340decaf78ee7d1fd83ba9bfe0b6187ab1 Mon Sep 17 00:00:00 2001 From: mouazkh Date: Sat, 30 May 2026 13:24:22 +0300 Subject: [PATCH] removed the contact information --- app/property/[id]/PropertyDetail.js | 1288 ++++++++++++++++++++------- 1 file changed, 982 insertions(+), 306 deletions(-) diff --git a/app/property/[id]/PropertyDetail.js b/app/property/[id]/PropertyDetail.js index 599d82d..cf96cfd 100644 --- a/app/property/[id]/PropertyDetail.js +++ b/app/property/[id]/PropertyDetail.js @@ -1,91 +1,213 @@ -'use client'; +"use client"; -import { useState, useEffect, useMemo } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import toast, { Toaster } from 'react-hot-toast'; -import Link from 'next/link'; -import { useParams, useRouter } from 'next/navigation'; +import { useState, useEffect, useMemo } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import toast, { Toaster } from "react-hot-toast"; +import Link from "next/link"; +import { useParams, useRouter } from "next/navigation"; import { - MapPin, Bed, Bath, Square, DollarSign, Heart, Share2, - Phone, Mail, MessageCircle, Calendar, Shield, Star, - ChevronLeft, ChevronRight, Check, X, Wifi, Car, Wind, - Camera, Home, Building2, Users, Clock, FileText, - LogIn, Loader2, ArrowLeft, ImageIcon, ChevronDown, - Layers, Sofa, DoorOpen, School, Hospital, Store, - TreePine, Building, GraduationCap, ExternalLink, - Smile, Ban, Wine, Dog, CassetteTape, Info -} from 'lucide-react'; -import { getRentProperty, getSaleProperty, getSalePropertyById, bookReservation, getAvailableDateRanges, getOwnerContactInformation, getMyRentListings, getMySaleListings } from '../../utils/api'; -import AuthService from '../../services/AuthService'; -import { useFavorites } from '@/app/contexts/FavoritesContext'; -import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums'; -import PropertyRatingList from '@/app/components/ratings/PropertyRatingList'; -import { getPropertyAverageRating } from '../../utils/ratings'; -import dynamic from 'next/dynamic'; -import 'leaflet/dist/leaflet.css'; + MapPin, + Bed, + Bath, + Square, + DollarSign, + Heart, + Share2, + Phone, + Mail, + MessageCircle, + Calendar, + Shield, + Star, + ChevronLeft, + ChevronRight, + Check, + X, + Wifi, + Car, + Wind, + Camera, + Home, + Building2, + Users, + Clock, + FileText, + LogIn, + Loader2, + ArrowLeft, + ImageIcon, + ChevronDown, + Layers, + Sofa, + DoorOpen, + School, + Hospital, + Store, + TreePine, + Building, + GraduationCap, + ExternalLink, + Smile, + Ban, + Wine, + Dog, + CassetteTape, + Info, +} from "lucide-react"; +import { + getRentProperty, + getSaleProperty, + getSalePropertyById, + bookReservation, + getAvailableDateRanges, + getOwnerContactInformation, + getMyRentListings, + getMySaleListings, +} from "../../utils/api"; +import AuthService from "../../services/AuthService"; +import { useFavorites } from "@/app/contexts/FavoritesContext"; +import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from "../../enums"; +import PropertyRatingList from "@/app/components/ratings/PropertyRatingList"; +import { getPropertyAverageRating } from "../../utils/ratings"; +import dynamic from "next/dynamic"; +import "leaflet/dist/leaflet.css"; -const MapContainer = dynamic(() => import('react-leaflet').then(m => m.MapContainer), { ssr: false }); -const TileLayer = dynamic(() => import('react-leaflet').then(m => m.TileLayer), { ssr: false }); -const Marker = dynamic(() => import('react-leaflet').then(m => m.Marker), { ssr: false }); -const Popup = dynamic(() => import('react-leaflet').then(m => m.Popup), { ssr: false }); +const MapContainer = dynamic( + () => import("react-leaflet").then((m) => m.MapContainer), + { ssr: false }, +); +const TileLayer = dynamic( + () => import("react-leaflet").then((m) => m.TileLayer), + { ssr: false }, +); +const Marker = dynamic(() => import("react-leaflet").then((m) => m.Marker), { + ssr: false, +}); +const Popup = dynamic(() => import("react-leaflet").then((m) => m.Popup), { + ssr: false, +}); function formatCurrency(amount) { - if (!amount || isNaN(amount)) return '0'; - return Number(amount).toLocaleString('ar-SA'); + if (!amount || isNaN(amount)) return "0"; + return Number(amount).toLocaleString("ar-SA"); } function buildImageUrl(img) { - if (!img) return ''; - const apiBase = typeof window !== 'undefined' - ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') - : ''; - if (img.startsWith('http')) return img; - return `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`; + if (!img) return ""; + const apiBase = + typeof window !== "undefined" + ? process.env.NEXT_PUBLIC_API_URL || "https://45.93.137.91.nip.io/api" + : ""; + if (img.startsWith("http")) return img; + return `${apiBase}${img.startsWith("/") ? "" : "/Pictures/"}${img}`; } const serviceLabels = { - Electricity: 'كهرباء', Internet: 'إنترنت', Heating: 'تدفئة', Water: 'ماء', - Pool: 'مسبح', PrivateGarden: 'حديقة خاصة', Parking: 'موقف سيارات', - Security247: 'حراسة 24 س', CentralHeating: 'تدفئة مركزية', - CentralAirConditioning: 'تكييف مركزي', EquippedKitchen: 'مطبخ مجهز', - MaidsRoom: 'غرفة خادمة', Elevator: 'مصعد', Gym: 'نادي رياضي', - Sauna: 'ساونا', Jacuzzi: 'جاكوزي', Balcony: 'بلكونة', - Rooftop: 'سطح', Furnished: 'مفروش', AirConditioning: 'تكييف', - SatelliteTV: 'تلفاز', Fireplace: 'مدفأة', StudyRoom: 'غرفة دراسة', - Storage: 'مستودع', Laundry: 'غرفة غسيل', SmartHome: 'منزل ذكي', + Electricity: "كهرباء", + Internet: "إنترنت", + Heating: "تدفئة", + Water: "ماء", + Pool: "مسبح", + PrivateGarden: "حديقة خاصة", + Parking: "موقف سيارات", + Security247: "حراسة 24 س", + CentralHeating: "تدفئة مركزية", + CentralAirConditioning: "تكييف مركزي", + EquippedKitchen: "مطبخ مجهز", + MaidsRoom: "غرفة خادمة", + Elevator: "مصعد", + Gym: "نادي رياضي", + Sauna: "ساونا", + Jacuzzi: "جاكوزي", + Balcony: "بلكونة", + Rooftop: "سطح", + Furnished: "مفروش", + AirConditioning: "تكييف", + SatelliteTV: "تلفاز", + Fireplace: "مدفأة", + StudyRoom: "غرفة دراسة", + Storage: "مستودع", + Laundry: "غرفة غسيل", + SmartHome: "منزل ذكي", }; const termLabels = { - NoSmoking: 'ممنوع التدخين', NoAnimals: 'ممنوع الحيوانات الأليفة', - NoParties: 'ممنوع الحفلات', NoAlcohol: 'ممنوع الكحول', - SuitableForChildren: 'مناسب للأطفال', SuitableForFamilies: 'مناسب للعائلات', - SuitableForStudents: 'مناسب للطلاب', SuitableForElderly: 'مناسب لكبار السن', - OnlyFemales: 'إناث فقط', OnlyMales: 'ذكور فقط', + NoSmoking: "ممنوع التدخين", + NoAnimals: "ممنوع الحيوانات الأليفة", + NoParties: "ممنوع الحفلات", + NoAlcohol: "ممنوع الكحول", + SuitableForChildren: "مناسب للأطفال", + SuitableForFamilies: "مناسب للعائلات", + SuitableForStudents: "مناسب للطلاب", + SuitableForElderly: "مناسب لكبار السن", + OnlyFemales: "إناث فقط", + OnlyMales: "ذكور فقط", }; const proximityLabels = { - School: 'مدرسة', Hospital: 'مستشفى', Restaurant: 'مطعم', - University: 'جامعة', Park: 'حديقة', Mall: 'مركز تسوق', - Supermarket: 'سوبر ماركت', Pharmacy: 'صيدلية', Mosque: 'مسجد', - Bank: 'بنك', Airport: 'مطار', BusStation: 'موقف باص', + School: "مدرسة", + Hospital: "مستشفى", + Restaurant: "مطعم", + University: "جامعة", + Park: "حديقة", + Mall: "مركز تسوق", + Supermarket: "سوبر ماركت", + Pharmacy: "صيدلية", + Mosque: "مسجد", + Bank: "بنك", + Airport: "مطار", + BusStation: "موقف باص", }; function mapApiDetail(item) { if (!item) return null; const info = item.propertyInformation || {}; - const details = typeof info.detailsJSON === 'object' && info.detailsJSON ? info.detailsJSON : (() => { try { return JSON.parse(info.detailsJSON || '{}'); } catch { return {}; } })(); + const details = + typeof info.detailsJSON === "object" && info.detailsJSON + ? info.detailsJSON + : (() => { + try { + return JSON.parse(info.detailsJSON || "{}"); + } catch { + return {}; + } + })(); const isRent = item.dailyRent != null || item.monthlyRent != null; const dailyPrice = item.dailyRent || 0; const monthlyPrice = item.monthlyRent || 0; const salePrice = item.price || 0; - const price = isRent ? (monthlyPrice || dailyPrice) : salePrice; - const priceUnit = isRent ? (monthlyPrice ? 'monthly' : 'daily') : 'sale'; + const price = isRent ? monthlyPrice || dailyPrice : salePrice; + const priceUnit = isRent ? (monthlyPrice ? "monthly" : "daily") : "sale"; - const propType = BuildingTypeKeys[info.buildingType] ?? BuildingTypeKeys[item.type] ?? 'apartment'; - const typeLabel = { apartment: 'شقة', villa: 'فيلا', house: 'بيت', sweet: 'سويت', studio: 'استوديو', office: 'مكتب', shop: 'متجر', warehouse: 'مستودع', farms: 'مزرعة', room: 'غرفة ضمن شقة' }[propType] || 'عقار'; - const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available'; - const statusLabel = { available: 'متاح', booked: 'محجوز', notAvailable: 'غير متاح', maintenance: 'صيانة' }[status] || 'متاح'; + const propType = + BuildingTypeKeys[info.buildingType] ?? + BuildingTypeKeys[item.type] ?? + "apartment"; + const typeLabel = + { + apartment: "شقة", + villa: "فيلا", + house: "بيت", + sweet: "سويت", + studio: "استوديو", + office: "مكتب", + shop: "متجر", + warehouse: "مستودع", + farms: "مزرعة", + room: "غرفة ضمن شقة", + }[propType] || "عقار"; + const status = + PropertyStatusKeys[info.status] ?? + PropertyStatusKeys[item.status] ?? + "available"; + const statusLabel = + { + available: "متاح", + booked: "محجوز", + notAvailable: "غير متاح", + maintenance: "صيانة", + }[status] || "متاح"; const rawImages = Array.isArray(info.images) ? info.images : []; const photosFallback = Array.isArray(details.photos) ? details.photos : []; @@ -94,18 +216,29 @@ function mapApiDetail(item) { // Normalize services: Flutter sends list of strings or object with boolean values const rawServices = details.services || {}; - const serviceList = Array.isArray(rawServices) ? rawServices : Object.keys(rawServices).filter(k => rawServices[k]); + const serviceList = Array.isArray(rawServices) + ? rawServices + : Object.keys(rawServices).filter((k) => rawServices[k]); const serviceDetails = details.serviceDetails || {}; const services = {}; - serviceList.forEach(s => { services[s] = serviceDetails[s] || true; }); + serviceList.forEach((s) => { + services[s] = serviceDetails[s] || true; + }); const rawTerms = details.terms || {}; - const terms = Array.isArray(rawTerms) ? rawTerms.reduce((acc, t) => ({ ...acc, [t]: true }), {}) : rawTerms; + const terms = Array.isArray(rawTerms) + ? rawTerms.reduce((acc, t) => ({ ...acc, [t]: true }), {}) + : rawTerms; // Try multiple key aliases like Flutter does const floor = details.floorNumber ?? details.floor ?? 0; - const salons = details.numberOfSalons ?? details.salonsCount ?? details.salons ?? 0; - const balconies = details.numberOfBalconies ?? details.balconiesCount ?? details.balconies ?? 0; + const salons = + details.numberOfSalons ?? details.salonsCount ?? details.salons ?? 0; + const balconies = + details.numberOfBalconies ?? + details.balconiesCount ?? + details.balconies ?? + 0; const rawProximity = details.nearbyDistances || details.proximity || {}; const proximity = {}; Object.entries(rawProximity).forEach(([k, v]) => { @@ -115,15 +248,25 @@ function mapApiDetail(item) { }); const roomDetails = details.room || details.roomDetails || {}; - const displayType = details.displayType || (isRent ? (monthlyPrice && dailyPrice ? 'Both' : monthlyPrice ? 'Monthly' : 'Daily') : 'Sale'); - const propertyCondition = details.propertyCondition || ''; - const furnished = propertyCondition.toLowerCase().includes('furniture') ? propertyCondition.toLowerCase() === 'withfurniture' : !!item.isFurnished; + const displayType = + details.displayType || + (isRent + ? monthlyPrice && dailyPrice + ? "Both" + : monthlyPrice + ? "Monthly" + : "Daily" + : "Sale"); + const propertyCondition = details.propertyCondition || ""; + const furnished = propertyCondition.toLowerCase().includes("furniture") + ? propertyCondition.toLowerCase() === "withfurniture" + : !!item.isFurnished; return { id: item.id, propertyInformationId: info.id, title: details.description || info.address || `عقار #${item.id}`, - description: details.description || info.description || '', + description: details.description || info.description || "", type: propType, typeLabel, price, @@ -134,8 +277,8 @@ function mapApiDetail(item) { propertyCondition, furnished, location: { - city: extractCity(info.address) || 'دمشق', - address: info.address || '', + city: extractCity(info.address) || "دمشق", + address: info.address || "", lat: parseFloat(info.cordsX) || 0, lng: parseFloat(info.cordsY) || 0, }, @@ -173,17 +316,21 @@ export default function PropertyDetailsPage() { const [loading, setLoading] = useState(true); const [currentImage, setCurrentImage] = useState(0); const [showLoginDialog, setShowLoginDialog] = useState(false); - const [bookingDates, setBookingDates] = useState({ start: '', end: '' }); + const [bookingDates, setBookingDates] = useState({ start: "", end: "" }); const [bookingLoading, setBookingLoading] = useState(false); const [bookingError, setBookingError] = useState(null); const [bookingSuccess, setBookingSuccess] = useState(false); const [availableRanges, setAvailableRanges] = useState([]); - const [bookingStep, setBookingStep] = useState('entry'); + const [bookingStep, setBookingStep] = useState("entry"); const [selectedStart, setSelectedStart] = useState(null); const [selectedEnd, setSelectedEnd] = useState(null); - const [calendarMonth, setCalendarMonth] = useState(() => new Date().getMonth()); - const [calendarYear, setCalendarYear] = useState(() => new Date().getFullYear()); - const [pricingMode, setPricingMode] = useState('daily'); + const [calendarMonth, setCalendarMonth] = useState(() => + new Date().getMonth(), + ); + const [calendarYear, setCalendarYear] = useState(() => + new Date().getFullYear(), + ); + const [pricingMode, setPricingMode] = useState("daily"); const [isOwnProperty, setIsOwnProperty] = useState(false); const [favLoading, setFavLoading] = useState(false); const [avgRating, setAvgRating] = useState(null); @@ -199,9 +346,20 @@ export default function PropertyDetailsPage() { async function fetchProperty() { try { let data = null; - try { data = await getRentProperty(id); } catch {} - if (!data) { try { data = await getSalePropertyById(id) || await getSaleProperty(id); } catch {} } - if (!data) { try { data = await getSaleProperty(id); } catch {} } + try { + data = await getRentProperty(id); + } catch {} + if (!data) { + try { + data = + (await getSalePropertyById(id)) || (await getSaleProperty(id)); + } catch {} + } + if (!data) { + try { + data = await getSaleProperty(id); + } catch {} + } if (data) { const mapped = mapApiDetail(data); @@ -209,13 +367,14 @@ export default function PropertyDetailsPage() { if (mapped) fetchAvgRating(mapped.id); if (mapped && mapped.isRent) { try { - const propInfoId = mapped._raw?.propertyInformationId || mapped.id; + const propInfoId = + mapped._raw?.propertyInformationId || mapped.id; const ranges = await getAvailableDateRanges(propInfoId); if (ranges && Array.isArray(ranges)) { setAvailableRanges(ranges); } } catch (e) { - console.warn('Failed to fetch date ranges', e); + console.warn("Failed to fetch date ranges", e); } } // Check if current user owns this property via their own listings @@ -227,9 +386,11 @@ export default function PropertyDetailsPage() { ]); const myPropIds = new Set(); const collectIds = (result) => { - if (result.status !== 'fulfilled' || !result.value) return; - const list = Array.isArray(result.value) ? result.value : [result.value]; - list.filter(Boolean).forEach(p => { + if (result.status !== "fulfilled" || !result.value) return; + const list = Array.isArray(result.value) + ? result.value + : [result.value]; + list.filter(Boolean).forEach((p) => { const info = p.propertyInformation || {}; if (info.id) myPropIds.add(Number(info.id)); if (p.id) myPropIds.add(Number(p.id)); @@ -238,16 +399,19 @@ export default function PropertyDetailsPage() { collectIds(myRent); collectIds(mySale); const propInfoId = mapped._raw?.propertyInformation?.id; - if (myPropIds.has(Number(mapped.id)) || (propInfoId && myPropIds.has(Number(propInfoId)))) { + if ( + myPropIds.has(Number(mapped.id)) || + (propInfoId && myPropIds.has(Number(propInfoId))) + ) { setIsOwnProperty(true); } } catch (e) { - console.warn('[OwnerCheck] failed:', e); + console.warn("[OwnerCheck] failed:", e); } } } } catch (err) { - console.error('[PropertyDetail] Failed:', err); + console.error("[PropertyDetail] Failed:", err); } finally { setLoading(false); } @@ -266,16 +430,21 @@ export default function PropertyDetailsPage() { const fetchContactInfo = async () => { if (!property) return; try { - const info = await getOwnerContactInformation(property._raw?.propertyInformationId || property.id); + const info = await getOwnerContactInformation( + property._raw?.propertyInformationId || property.id, + ); setContactInfo(info); setShowContact(true); } catch (err) { - toast.error('فشل تحميل معلومات الاتصال'); + toast.error("فشل تحميل معلومات الاتصال"); } }; const handleFavorite = async () => { - if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; } + if (!AuthService.isAuthenticated()) { + setShowLoginDialog(true); + return; + } if (!property) return; setFavLoading(true); try { @@ -285,42 +454,62 @@ export default function PropertyDetailsPage() { await addFavorite(property.id); } } catch (err) { - toast.error('حدث خطأ'); + toast.error("حدث خطأ"); } finally { setFavLoading(false); } }; const handleBookNow = async () => { - if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; } + if (!AuthService.isAuthenticated()) { + setShowLoginDialog(true); + return; + } if (!bookingDates.start || !bookingDates.end) { - setBookingError('يرجى تحديد تاريخ البداية والنهاية'); + setBookingError("يرجى تحديد تاريخ البداية والنهاية"); return; } setBookingLoading(true); setBookingError(null); try { - await bookReservation(property._raw?.propertyInformationId || property.id, bookingDates.start, bookingDates.end); + await bookReservation( + property._raw?.propertyInformationId || property.id, + bookingDates.start, + bookingDates.end, + ); setBookingSuccess(true); - toast.success('تم إرسال طلب الحجز بنجاح'); + toast.success("تم إرسال طلب الحجز بنجاح"); } catch (err) { - setBookingError(err.message || 'فشل الحجز'); + setBookingError(err.message || "فشل الحجز"); } finally { setBookingLoading(false); } }; - const MONTHS_AR = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر']; - const DAYS_AR = ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س']; + const MONTHS_AR = [ + "يناير", + "فبراير", + "مارس", + "أبريل", + "مايو", + "يونيو", + "يوليو", + "أغسطس", + "سبتمبر", + "أكتوبر", + "نوفمبر", + "ديسمبر", + ]; + const DAYS_AR = ["ح", "ن", "ث", "ر", "خ", "ج", "س"]; const availableDatesSet = useMemo(() => { const dates = new Set(); if (!Array.isArray(availableRanges)) return dates; - availableRanges.forEach(r => { + availableRanges.forEach((r) => { const start = new Date(r.startDate || r.start); const end = new Date(r.endDate || r.end); for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) { - dates.add(d.toISOString().split('T')[0]); + dates.add(d.toISOString().split("T")[0]); } }); return dates; @@ -334,39 +523,42 @@ export default function PropertyDetailsPage() { }; const handleDayClick = (dateStr) => { - if (bookingStep === 'entry') { + if (bookingStep === "entry") { setSelectedStart(dateStr); setSelectedEnd(null); - setBookingStep('exit'); + setBookingStep("exit"); } else { if (new Date(dateStr) <= new Date(selectedStart)) { setSelectedStart(dateStr); setSelectedEnd(null); - setBookingStep('exit'); + setBookingStep("exit"); } else { setSelectedEnd(dateStr); - setBookingStep('entry'); + setBookingStep("entry"); } } }; const handleBookingConfirm = async () => { - if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; } + if (!AuthService.isAuthenticated()) { + setShowLoginDialog(true); + return; + } if (!selectedStart || !selectedEnd) { - setBookingError('يرجى تحديد تاريخ البداية والنهاية'); + setBookingError("يرجى تحديد تاريخ البداية والنهاية"); return; } setBookingLoading(true); setBookingError(null); try { const propInfoId = property._raw?.propertyInformationId || property.id; - const startDate = new Date(selectedStart + 'T00:00:00.000').toISOString(); - const endDate = new Date(selectedEnd + 'T00:00:00.000').toISOString(); + const startDate = new Date(selectedStart + "T00:00:00.000").toISOString(); + const endDate = new Date(selectedEnd + "T00:00:00.000").toISOString(); await bookReservation(propInfoId, startDate, endDate); setBookingSuccess(true); - toast.success('تم إرسال طلب الحجز بنجاح'); + toast.success("تم إرسال طلب الحجز بنجاح"); } catch (err) { - setBookingError(err.message || 'فشل الحجز'); + setBookingError(err.message || "فشل الحجز"); } finally { setBookingLoading(false); } @@ -375,8 +567,14 @@ export default function PropertyDetailsPage() { const navigateMonth = (delta) => { let month = calendarMonth + delta; let year = calendarYear; - if (month < 0) { month = 11; year--; } - if (month > 11) { month = 0; year++; } + if (month < 0) { + month = 11; + year--; + } + if (month > 11) { + month = 0; + year++; + } setCalendarMonth(month); setCalendarYear(year); }; @@ -404,9 +602,14 @@ export default function PropertyDetailsPage() {
-

العقار غير موجود

+

+ العقار غير موجود +

لم يتم العثور على العقار المطلوب

- + العودة إلى العقارات @@ -416,18 +619,32 @@ export default function PropertyDetailsPage() { } const isFav = isFavorite(property.id); - const isRoomType = property.type === 'room'; + const isRoomType = property.type === "room"; const isMostRequested = avgRating !== null && avgRating >= 4.5; - const showPricingToggle = property.isRent && property.priceDisplay?.daily > 0 && property.priceDisplay?.monthly > 0; - const effectivePricingMode = showPricingToggle ? pricingMode : (property.isRent && property.priceDisplay?.monthly > 0 ? 'monthly' : 'daily'); + const showPricingToggle = + property.isRent && + property.priceDisplay?.daily > 0 && + property.priceDisplay?.monthly > 0; + const effectivePricingMode = showPricingToggle + ? pricingMode + : property.isRent && property.priceDisplay?.monthly > 0 + ? "monthly" + : "daily"; return (
- - + + العودة إلى العقارات @@ -436,14 +653,27 @@ export default function PropertyDetailsPage() {
{/* Image Gallery */} - -
+ +
{property.images.length > 0 ? ( - {property.title} + style={{ minHeight: "380px", maxHeight: "460px" }} + /> ) : ( -
+

لا توجد صور

@@ -461,20 +691,38 @@ export default function PropertyDetailsPage() { {property.images.length > 1 && ( <> - - )}
- {property.statusLabel} - {property.typeLabel} + + {property.statusLabel} + + + {property.typeLabel} +
{currentImage + 1} / {property.images.length || 1} @@ -482,11 +730,21 @@ export default function PropertyDetailsPage() {
{property.images.length > 1 && ( -
+
{property.images.map((img, idx) => ( - ))}
@@ -494,36 +752,58 @@ export default function PropertyDetailsPage() { {/* Property Info */} - +
- {property.typeLabel} - {property.statusLabel} + + {property.typeLabel} + + + {property.statusLabel} + {property.isRent && property.displayType && ( {(() => { const dt = property.displayType.toLowerCase(); - if (dt === 'both' || dt.includes('both')) return 'يومي وشهري'; - if (dt.includes('daily')) return 'يومي'; - if (dt.includes('monthly')) return 'شهري'; + if (dt === "both" || dt.includes("both")) + return "يومي وشهري"; + if (dt.includes("daily")) return "يومي"; + if (dt.includes("monthly")) return "شهري"; return dt; })()} )} - - {property.furnished ? 'مفروش' : 'غير مفروش'} + + {property.furnished ? "مفروش" : "غير مفروش"}
-

{property.title}

+

+ {property.title} +

- {property.location.address || property.location.city} + + {property.location.address || property.location.city} +
-
@@ -533,25 +813,32 @@ export default function PropertyDetailsPage() {
{property.priceDisplay.monthly > 0 && (
- {formatCurrency(property.priceDisplay.monthly)} + + {formatCurrency(property.priceDisplay.monthly)} + ل.س / شهرياً
)} {property.priceDisplay.daily > 0 && (
- {formatCurrency(property.priceDisplay.daily)} + + {formatCurrency(property.priceDisplay.daily)} + ل.س / يومياً
)} {property.deposit > 0 && (
- تأمين: {formatCurrency(property.deposit)} ل.س + تأمين:{" "} + {formatCurrency(property.deposit)} ل.س
)}
) : (
- {formatCurrency(property.price)} + + {formatCurrency(property.price)} + ل.س للبيع
@@ -563,56 +850,72 @@ export default function PropertyDetailsPage() { {property.bedrooms > 0 && (
-
{property.bedrooms}
+
+ {property.bedrooms} +
غرف نوم
)} {property.bathrooms > 0 && (
-
{property.bathrooms}
+
+ {property.bathrooms} +
حمامات
)} {property.area > 0 && (
-
{property.area}
+
+ {property.area} +
م²
)} {property.floor > 0 && (
-
{property.floor}
+
+ {property.floor} +
طابق
)} {property.salons > 0 && (
-
{property.salons}
+
+ {property.salons} +
صالونات
)} {property.balconies > 0 && (
-
{property.balconies}
+
+ {property.balconies} +
بلكونات
)} - {avgRating !== null && avgRating > 0 && ( + {avgRating !== null && avgRating > 0 && (
-
{avgRating.toFixed(1)}
+
+ {avgRating.toFixed(1)} +
التقييم
)} {property.bookedCount > 0 && (
-
{property.bookedCount}
+
+ {property.bookedCount} +
حجوزات
)} @@ -621,45 +924,85 @@ export default function PropertyDetailsPage() { {/* Description */} {property.description && (
-

الوصف

-

{property.description}

+

+ الوصف +

+

+ {property.description} +

)} {/* Features */}
- {property.isSmokeAllow && يسمح بالتدخين} - {!property.isSmokeAllow && ممنوع التدخين} - {property.isVisitorAllow && يسمح بالزوار} - {property.specializedFor && {property.specializedFor}} + {property.isSmokeAllow && ( + + يسمح بالتدخين + + )} + {!property.isSmokeAllow && ( + + ممنوع التدخين + + )} + {property.isVisitorAllow && ( + + يسمح بالزوار + + )} + {property.specializedFor && ( + + {property.specializedFor} + + )}
{/* Services with detail text */} - {property.services && (Array.isArray(property.services) ? property.services.length > 0 : Object.keys(property.services).length > 0) && ( -
-

الخدمات

-
- {Array.isArray(property.services) ? ( - property.services.map((svc, i) => ( - - {serviceLabels[svc] || svc} - - )) - ) : ( - Object.entries(property.services).map(([key, val]) => { - if (!val) return null; - const detail = typeof val === 'object' && val.detail ? val.detail : (typeof val === 'string' ? val : null); - return ( - - {serviceLabels[key] || key} - {detail && · {detail}} - - ); - }) - )} + {property.services && + (Array.isArray(property.services) + ? property.services.length > 0 + : Object.keys(property.services).length > 0) && ( +
+

+ الخدمات +

+
+ {Array.isArray(property.services) + ? property.services.map((svc, i) => ( + + {serviceLabels[svc] || svc} + + )) + : Object.entries(property.services).map( + ([key, val]) => { + if (!val) return null; + const detail = + typeof val === "object" && val.detail + ? val.detail + : typeof val === "string" + ? val + : null; + return ( + + {serviceLabels[key] || key} + {detail && ( + + · {detail} + + )} + + ); + }, + )} +
-
- )} + )} {/* Room Details (only for room type) */} {isRoomType && Object.keys(property.roomDetails).length > 0 && ( @@ -672,25 +1015,131 @@ export default function PropertyDetailsPage() { {(() => { const rd = property.roomDetails; const items = []; - if (rd.areaType) items.push({ label: 'نوع المساحة', value: rd.areaType === 'private room' ? 'غرفة خاصة' : rd.areaType === 'shared room' ? 'غرفة مشتركة' : rd.areaType }); - if (rd.peopleAllowed) items.push({ label: 'عدد الأشخاص', value: rd.peopleAllowed }); - if (rd.furnitureDetails || rd.furniture) items.push({ label: 'الأثاث', value: rd.furnitureDetails || rd.furniture }); - if (rd.entranceType) items.push({ label: 'نوع المدخل', value: rd.entranceType === 'shared entrance' ? 'مدخل مشترك' : rd.entranceType === 'independent entrance' ? 'مدخل مستقل' : rd.entranceType }); - if (rd.bathroomType) items.push({ label: 'الحمام', value: rd.bathroomType === 'room specific' ? 'خاص بالغرفة' : rd.bathroomType === 'shared' ? 'مشترك' : rd.bathroomType }); - if (rd.kitchenType) items.push({ label: 'المطبخ', value: rd.kitchenType === 'shared' ? 'مشترك' : rd.kitchenType === 'not available' ? 'غير متوفر' : rd.kitchenType }); - if (rd.homeResidentsCount ?? rd.residents) items.push({ label: 'عدد السكان', value: rd.homeResidentsCount ?? rd.residents }); - if (rd.currentPopulationGender) items.push({ label: 'جنس السكان', value: rd.currentPopulationGender === 'men' ? 'رجال' : rd.currentPopulationGender === 'women' ? 'نساء' : rd.currentPopulationGender === 'family' ? 'عائلة' : rd.currentPopulationGender }); - if (rd.dedicatedTo) items.push({ label: 'مخصص لـ', value: rd.dedicatedTo === 'men only' ? 'رجال فقط' : rd.dedicatedTo === 'women only' ? 'نساء فقط' : rd.dedicatedTo === 'families only' ? 'عائلات فقط' : rd.dedicatedTo === 'everyone' ? 'الجميع' : rd.dedicatedTo }); - if (rd.hasRestrictedOwnerAreas !== undefined) items.push({ label: 'مناطق ممنوعة', value: rd.hasRestrictedOwnerAreas ? 'نعم' : 'لا' }); - if (rd.hasChildren !== undefined) items.push({ label: 'أطفال', value: rd.hasChildren ? 'مسموح' : 'غير مسموح' }); - if (rd.hasPets !== undefined) items.push({ label: 'حيوانات أليفة', value: rd.hasPets ? 'مسموح' : 'غير مسموح' }); - if (rd.languageDialect) items.push({ label: 'اللغة', value: rd.languageDialect }); - if (rd.visitorsAllowed !== undefined) items.push({ label: 'الزوار', value: rd.visitorsAllowed ? 'مسموح' : 'ممنوع' }); - if (rd.quietTimesEnabled ?? rd.quietTimes) items.push({ label: 'أوقات الهدوء', value: rd.quietTimesDetails || rd.quietTimes || (rd.quietTimesEnabled ? 'مفعلة' : '') }); + if (rd.areaType) + items.push({ + label: "نوع المساحة", + value: + rd.areaType === "private room" + ? "غرفة خاصة" + : rd.areaType === "shared room" + ? "غرفة مشتركة" + : rd.areaType, + }); + if (rd.peopleAllowed) + items.push({ + label: "عدد الأشخاص", + value: rd.peopleAllowed, + }); + if (rd.furnitureDetails || rd.furniture) + items.push({ + label: "الأثاث", + value: rd.furnitureDetails || rd.furniture, + }); + if (rd.entranceType) + items.push({ + label: "نوع المدخل", + value: + rd.entranceType === "shared entrance" + ? "مدخل مشترك" + : rd.entranceType === "independent entrance" + ? "مدخل مستقل" + : rd.entranceType, + }); + if (rd.bathroomType) + items.push({ + label: "الحمام", + value: + rd.bathroomType === "room specific" + ? "خاص بالغرفة" + : rd.bathroomType === "shared" + ? "مشترك" + : rd.bathroomType, + }); + if (rd.kitchenType) + items.push({ + label: "المطبخ", + value: + rd.kitchenType === "shared" + ? "مشترك" + : rd.kitchenType === "not available" + ? "غير متوفر" + : rd.kitchenType, + }); + if (rd.homeResidentsCount ?? rd.residents) + items.push({ + label: "عدد السكان", + value: rd.homeResidentsCount ?? rd.residents, + }); + if (rd.currentPopulationGender) + items.push({ + label: "جنس السكان", + value: + rd.currentPopulationGender === "men" + ? "رجال" + : rd.currentPopulationGender === "women" + ? "نساء" + : rd.currentPopulationGender === "family" + ? "عائلة" + : rd.currentPopulationGender, + }); + if (rd.dedicatedTo) + items.push({ + label: "مخصص لـ", + value: + rd.dedicatedTo === "men only" + ? "رجال فقط" + : rd.dedicatedTo === "women only" + ? "نساء فقط" + : rd.dedicatedTo === "families only" + ? "عائلات فقط" + : rd.dedicatedTo === "everyone" + ? "الجميع" + : rd.dedicatedTo, + }); + if (rd.hasRestrictedOwnerAreas !== undefined) + items.push({ + label: "مناطق ممنوعة", + value: rd.hasRestrictedOwnerAreas ? "نعم" : "لا", + }); + if (rd.hasChildren !== undefined) + items.push({ + label: "أطفال", + value: rd.hasChildren ? "مسموح" : "غير مسموح", + }); + if (rd.hasPets !== undefined) + items.push({ + label: "حيوانات أليفة", + value: rd.hasPets ? "مسموح" : "غير مسموح", + }); + if (rd.languageDialect) + items.push({ + label: "اللغة", + value: rd.languageDialect, + }); + if (rd.visitorsAllowed !== undefined) + items.push({ + label: "الزوار", + value: rd.visitorsAllowed ? "مسموح" : "ممنوع", + }); + if (rd.quietTimesEnabled ?? rd.quietTimes) + items.push({ + label: "أوقات الهدوء", + value: + rd.quietTimesDetails || + rd.quietTimes || + (rd.quietTimesEnabled ? "مفعلة" : ""), + }); return items.map((item, i) => ( -
-
{item.label}
-
{item.value}
+
+
+ {item.label} +
+
+ {item.value} +
)); })()} @@ -701,23 +1150,53 @@ export default function PropertyDetailsPage() { {/* Proximity */} {Object.keys(property.proximity).length > 0 && (
-

القرب من الخدمات

+

+ القرب من الخدمات +

{Object.entries(property.proximity).map(([key, val]) => { if (!val) return null; - const dist = typeof val === 'object' ? val.distance : val; + const dist = typeof val === "object" ? val.distance : val; return ( -
- {key === 'School' && } - {key === 'Hospital' && } - {key === 'Restaurant' && } - {key === 'University' && } - {key === 'Park' && } - {key === 'Mall' && } - {!['School','Hospital','Restaurant','University','Park','Mall'].includes(key) && } +
+ {key === "School" && ( + + )} + {key === "Hospital" && ( + + )} + {key === "Restaurant" && ( + + )} + {key === "University" && ( + + )} + {key === "Park" && ( + + )} + {key === "Mall" && ( + + )} + {![ + "School", + "Hospital", + "Restaurant", + "University", + "Park", + "Mall", + ].includes(key) && ( + + )}
-
{proximityLabels[key] || key}
-
{dist} {typeof dist === 'number' ? 'كم' : ''}
+
+ {proximityLabels[key] || key} +
+
+ {dist} {typeof dist === "number" ? "كم" : ""} +
); @@ -729,18 +1208,25 @@ export default function PropertyDetailsPage() { {/* Terms as checklist */} {Object.keys(property.terms).length > 0 && (
-

الشروط

+

+ الشروط +

{Object.entries(property.terms).map(([key, val]) => { if (!val) return null; return ( -
- {key.startsWith('No') || key.startsWith('Only') ? ( +
+ {key.startsWith("No") || key.startsWith("Only") ? ( ) : ( )} - {termLabels[key] || key} + + {termLabels[key] || key} +
); })} @@ -751,11 +1237,22 @@ export default function PropertyDetailsPage() { {/* Map */} {property.location.lat && property.location.lng && ( - +
- + - + {property.title} @@ -765,8 +1262,12 @@ export default function PropertyDetailsPage() { موقع تقريبي — يظهر الموقع الدقيق بعد تأكيد الحجز
- + فتح في Google Maps @@ -775,140 +1276,239 @@ export default function PropertyDetailsPage() { )} {/* Ratings Section */} - - + + -
{/* Sidebar */}
{/* Booking Card */} {property.isRent && ( - + {isOwnProperty ? (
-

هذا عقارك

-

لا يمكنك حجز عقارك الخاص

+

+ هذا عقارك +

+

+ لا يمكنك حجز عقارك الخاص +

) : bookingSuccess ? (
-

تم إرسال طلب الحجز

-

سيتم مراجعة طلبك من قبل المالك

+

+ تم إرسال طلب الحجز +

+

+ سيتم مراجعة طلبك من قبل المالك +

) : ( <> {/* Pricing Mode Toggle */} {showPricingToggle && (
- -
)} {/* Step Indicator */}
-
-
+
+
تحديد تاريخ البداية
-
-
+
+
تحديد تاريخ النهاية
{/* Calendar */} - {effectivePricingMode === 'daily' ? ( + {effectivePricingMode === "daily" ? (
- - {MONTHS_AR[calendarMonth]} {calendarYear} -
{DAYS_AR.map((d, i) => ( -
{d}
+
+ {d} +
))}
{(() => { - const firstDay = new Date(calendarYear, calendarMonth, 1).getDay(); - const daysInMonth = new Date(calendarYear, calendarMonth + 1, 0).getDate(); + const firstDay = new Date( + calendarYear, + calendarMonth, + 1, + ).getDay(); + const daysInMonth = new Date( + calendarYear, + calendarMonth + 1, + 0, + ).getDate(); const adjustedFirstDay = (firstDay + 1) % 7; const cells = []; for (let i = 0; i < adjustedFirstDay; i++) { cells.push(
); } for (let day = 1; day <= daysInMonth; day++) { - const dateStr = `${calendarYear}-${String(calendarMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + const dateStr = `${calendarYear}-${String(calendarMonth + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`; const past = isPastDate(dateStr); const available = isDateAvailable(dateStr); const isSelStart = dateStr === selectedStart; const isSelEnd = dateStr === selectedEnd; - const inRange = selectedStart && selectedEnd && new Date(dateStr) > new Date(selectedStart) && new Date(dateStr) < new Date(selectedEnd); + const inRange = + selectedStart && + selectedEnd && + new Date(dateStr) > new Date(selectedStart) && + new Date(dateStr) < new Date(selectedEnd); const disabled = past || !available; cells.push( - + , ); } - return
{cells}
; + return ( +
+ {cells} +
+ ); })()}
) : (
- - {calendarYear} -
{MONTHS_AR.map((name, idx) => { - const monthStr = `${calendarYear}-${String(idx + 1).padStart(2, '0')}`; - const isSelStart = selectedStart && selectedStart.startsWith(monthStr); - const isSelEnd = selectedEnd && selectedEnd.startsWith(monthStr); - const inRange = selectedStart && selectedEnd && monthStr > selectedStart.substring(0, 7) && monthStr < selectedEnd.substring(0, 7); + const monthStr = `${calendarYear}-${String(idx + 1).padStart(2, "0")}`; + const isSelStart = + selectedStart && + selectedStart.startsWith(monthStr); + const isSelEnd = + selectedEnd && selectedEnd.startsWith(monthStr); + const inRange = + selectedStart && + selectedEnd && + monthStr > selectedStart.substring(0, 7) && + monthStr < selectedEnd.substring(0, 7); return ( - ); @@ -922,35 +1522,77 @@ export default function PropertyDetailsPage() {
تاريخ البداية - {selectedStart} + + {selectedStart} +
{selectedEnd && ( <>
- تاريخ النهاية - {selectedEnd} + + تاريخ النهاية + + + {selectedEnd} +
- {effectivePricingMode === 'daily' ? 'عدد الأيام' : 'عدد الأشهر'} + + {effectivePricingMode === "daily" + ? "عدد الأيام" + : "عدد الأشهر"} + - {effectivePricingMode === 'daily' - ? Math.max(1, Math.round((new Date(selectedEnd) - new Date(selectedStart)) / (1000 * 60 * 60 * 24)) + 1) - : (new Date(selectedEnd).getMonth() - new Date(selectedStart).getMonth() + (new Date(selectedEnd).getFullYear() - new Date(selectedStart).getFullYear()) * 12) + 1} + {effectivePricingMode === "daily" + ? Math.max( + 1, + Math.round( + (new Date(selectedEnd) - + new Date(selectedStart)) / + (1000 * 60 * 60 * 24), + ) + 1, + ) + : new Date(selectedEnd).getMonth() - + new Date(selectedStart).getMonth() + + (new Date(selectedEnd).getFullYear() - + new Date(selectedStart).getFullYear()) * + 12 + + 1}
المجموع - {formatCurrency(effectivePricingMode === 'daily' - ? Math.max(1, Math.round((new Date(selectedEnd) - new Date(selectedStart)) / (1000 * 60 * 60 * 24)) + 1) * property.priceDisplay.daily - : ((new Date(selectedEnd).getMonth() - new Date(selectedStart).getMonth() + (new Date(selectedEnd).getFullYear() - new Date(selectedStart).getFullYear()) * 12) + 1) * property.priceDisplay.monthly)} ل.س + {formatCurrency( + effectivePricingMode === "daily" + ? Math.max( + 1, + Math.round( + (new Date(selectedEnd) - + new Date(selectedStart)) / + (1000 * 60 * 60 * 24), + ) + 1, + ) * property.priceDisplay.daily + : (new Date(selectedEnd).getMonth() - + new Date(selectedStart).getMonth() + + (new Date(selectedEnd).getFullYear() - + new Date( + selectedStart, + ).getFullYear()) * + 12 + + 1) * + property.priceDisplay.monthly, + )}{" "} + ل.س
{property.deposit > 0 && (
تأمين - {formatCurrency(property.deposit)} ل.س + + {formatCurrency(property.deposit)} ل.س +
)} @@ -959,13 +1601,24 @@ export default function PropertyDetailsPage() { )} {bookingError && ( -
{bookingError}
+
+ {bookingError} +
)} - )} @@ -973,7 +1626,7 @@ export default function PropertyDetailsPage() { )} {/* Contact Card */} - {!isOwnProperty && ( + {/* {!isOwnProperty && (
@@ -1002,23 +1655,46 @@ export default function PropertyDetailsPage() { )} - )} + )} */}
{/* Login Dialog */} {showLoginDialog && ( -
setShowLoginDialog(false)}> -
e.stopPropagation()}> +
setShowLoginDialog(false)} + > +
e.stopPropagation()} + >

تسجيل الدخول مطلوب

-

للحجز أو إضافة المفضلة، سجل دخولك.

- تسجيل الدخول - إنشاء حساب - +

+ للحجز أو إضافة المفضلة، سجل دخولك. +

+ + تسجيل الدخول + + + إنشاء حساب + +
)}