"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 { 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, }); function formatCurrency(amount) { 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}`; } 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: "منزل ذكي", }; const termLabels = { NoSmoking: "ممنوع التدخين", NoAnimals: "ممنوع الحيوانات الأليفة", NoParties: "ممنوع الحفلات", NoAlcohol: "ممنوع الكحول", SuitableForChildren: "مناسب للأطفال", SuitableForFamilies: "مناسب للعائلات", SuitableForStudents: "مناسب للطلاب", SuitableForElderly: "مناسب لكبار السن", OnlyFemales: "إناث فقط", OnlyMales: "ذكور فقط", }; const proximityLabels = { 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 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 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 : []; const allRaw = rawImages.length > 0 ? rawImages : photosFallback; const images = allRaw.length > 0 ? allRaw.map(buildImageUrl) : []; // 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 serviceDetails = details.serviceDetails || {}; const services = {}; 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; // 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 rawProximity = details.nearbyDistances || details.proximity || {}; const proximity = {}; Object.entries(rawProximity).forEach(([k, v]) => { if (!v) return; const normalizedKey = k.charAt(0).toUpperCase() + k.slice(1); proximity[normalizedKey] = v; }); 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; return { id: item.id, propertyInformationId: info.id, title: details.description || info.address || `عقار #${item.id}`, description: details.description || info.description || "", type: propType, typeLabel, price, priceUnit, priceDisplay: { daily: dailyPrice, monthly: monthlyPrice, sale: salePrice }, isRent, displayType, propertyCondition, furnished, location: { city: extractCity(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, floor, salons, balconies, images, status, statusLabel, services, terms, proximity, roomDetails, details, bookedCount: details.bookedCount || 0, deposit: item.deposit || 0, currencyId: item.currencyId, isSmokeAllow: item.isSmokeAllow, isVisitorAllow: item.isVisitorAllow, specializedFor: item.specializedFor, ownerId: info.ownerId ?? info.userId ?? item.ownerId ?? item.userId ?? null, _raw: item, }; } export default function PropertyDetailsPage() { const params = useParams(); const router = useRouter(); const { isFavorite, addFavorite, removeFavorite } = useFavorites(); const [property, setProperty] = useState(null); const [loading, setLoading] = useState(true); const [currentImage, setCurrentImage] = useState(0); const [showLoginDialog, setShowLoginDialog] = useState(false); 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 [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 [isOwnProperty, setIsOwnProperty] = useState(false); const [favLoading, setFavLoading] = useState(false); const [avgRating, setAvgRating] = useState(null); const [showContact, setShowContact] = useState(false); const [contactInfo, setContactInfo] = useState(null); const [ownerData, setOwnerData] = useState(null); useEffect(() => { const id = params.id; if (!id) return; setLoading(true); 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 {} } if (data) { const mapped = mapApiDetail(data); setProperty(mapped); if (mapped) fetchAvgRating(mapped.id); if (mapped && mapped.isRent) { try { 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); } } // Check if current user owns this property via their own listings if (AuthService.isAuthenticated() && AuthService.isOwner()) { try { const [myRent, mySale] = await Promise.allSettled([ getMyRentListings(), getMySaleListings(), ]); 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) => { const info = p.propertyInformation || {}; if (info.id) myPropIds.add(Number(info.id)); if (p.id) myPropIds.add(Number(p.id)); }); }; collectIds(myRent); collectIds(mySale); const propInfoId = mapped._raw?.propertyInformation?.id; if ( myPropIds.has(Number(mapped.id)) || (propInfoId && myPropIds.has(Number(propInfoId))) ) { setIsOwnProperty(true); } } catch (e) { console.warn("[OwnerCheck] failed:", e); } } } } catch (err) { console.error("[PropertyDetail] Failed:", err); } finally { setLoading(false); } } fetchProperty(); }, [params.id]); const fetchAvgRating = async (propId) => { try { const avg = await getPropertyAverageRating(propId); setAvgRating(avg); } catch {} }; const fetchContactInfo = async () => { if (!property) return; try { const info = await getOwnerContactInformation( property._raw?.propertyInformationId || property.id, ); setContactInfo(info); setShowContact(true); } catch (err) { toast.error("فشل تحميل معلومات الاتصال"); } }; const handleFavorite = async () => { if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; } if (!property) return; setFavLoading(true); try { if (isFavorite(property.id)) { await removeFavorite(property.id); } else { await addFavorite(property.id); } } catch (err) { toast.error("حدث خطأ"); } finally { setFavLoading(false); } }; const handleBookNow = async () => { if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; } if (!bookingDates.start || !bookingDates.end) { setBookingError("يرجى تحديد تاريخ البداية والنهاية"); return; } setBookingLoading(true); setBookingError(null); try { await bookReservation( property._raw?.propertyInformationId || property.id, bookingDates.start, bookingDates.end, ); setBookingSuccess(true); toast.success("تم إرسال طلب الحجز بنجاح"); } catch (err) { setBookingError(err.message || "فشل الحجز"); } finally { setBookingLoading(false); } }; const MONTHS_AR = [ "يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر", ]; const DAYS_AR = ["ح", "ن", "ث", "ر", "خ", "ج", "س"]; const availableDatesSet = useMemo(() => { const dates = new Set(); if (!Array.isArray(availableRanges)) return dates; 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]); } }); return dates; }, [availableRanges]); const isDateAvailable = (dateStr) => availableDatesSet.has(dateStr); const isPastDate = (dateStr) => { const today = new Date(); today.setHours(0, 0, 0, 0); return new Date(dateStr) < today; }; const handleDayClick = (dateStr) => { if (bookingStep === "entry") { setSelectedStart(dateStr); setSelectedEnd(null); setBookingStep("exit"); } else { if (new Date(dateStr) <= new Date(selectedStart)) { setSelectedStart(dateStr); setSelectedEnd(null); setBookingStep("exit"); } else { setSelectedEnd(dateStr); setBookingStep("entry"); } } }; const handleBookingConfirm = async () => { if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; } if (!selectedStart || !selectedEnd) { 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(); await bookReservation(propInfoId, startDate, endDate); setBookingSuccess(true); toast.success("تم إرسال طلب الحجز بنجاح"); } catch (err) { setBookingError(err.message || "فشل الحجز"); } finally { setBookingLoading(false); } }; const navigateMonth = (delta) => { let month = calendarMonth + delta; let year = calendarYear; if (month < 0) { month = 11; year--; } if (month > 11) { month = 0; year++; } setCalendarMonth(month); setCalendarYear(year); }; const handleRatingSuccess = () => { setShowRatingForm(false); if (property) fetchAvgRating(property.id); }; if (loading) { return (
جاري تحميل العقار...
لم يتم العثور على العقار المطلوب
لا توجد صور
{property.description}
لا يمكنك حجز عقارك الخاص
سيتم مراجعة طلبك من قبل المالك
للحجز أو إضافة المفضلة، سجل دخولك.
تسجيل الدخول إنشاء حساب