'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 } 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 PropertyRatingForm from '@/app/components/ratings/PropertyRatingForm'; import StarRating from '@/app/components/ratings/StarRating'; 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 = (() => { 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 terms = details.terms || {}; // 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 proximity = details.nearbyDistances || details.proximity || {}; const roomDetails = details.room || details.roomDetails || {}; 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, 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, deposit: item.deposit || 0, currencyId: item.currencyId, isSmokeAllow: item.isSmokeAllow, isVisitorAllow: item.isVisitorAllow, specializedFor: item.specializedFor, _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 [favLoading, setFavLoading] = useState(false); const [avgRating, setAvgRating] = useState(null); const [showRatingForm, setShowRatingForm] = useState(false); 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); } } 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 handleRatingSuccess = () => { setShowRatingForm(false); if (property) fetchAvgRating(property.id); }; if (loading) { return (
جاري تحميل العقار...
لم يتم العثور على العقار المطلوب
لا توجد صور
{property.description}
شارك تجربتك مع المستأجرين الآخرين
سيتم مراجعة طلبك من قبل المالك
للحجز أو إضافة المفضلة، سجل دخولك.
تسجيل الدخول إنشاء حساب