fixed my propries page and fixed the sidebar again mouaz is the best in the west
All checks were successful
Build frontend / build (push) Successful in 1m1s
All checks were successful
Build frontend / build (push) Successful in 1m1s
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
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';
|
||||
@ -10,7 +10,10 @@ import {
|
||||
Phone, Mail, MessageCircle, Calendar, Shield, Star,
|
||||
ChevronLeft, ChevronRight, Check, X, Wifi, Car, Wind,
|
||||
Camera, Home, Building2, Users, Clock, FileText,
|
||||
LogIn, Loader2, ArrowLeft, ImageIcon, ChevronDown
|
||||
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';
|
||||
@ -42,6 +45,33 @@ function buildImageUrl(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 || {};
|
||||
@ -55,20 +85,24 @@ function mapApiDetail(item) {
|
||||
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 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: 'محجوز', maintenance: 'صيانة', not_available: 'غير متاح' }[status] || 'متاح';
|
||||
const statusLabel = { available: 'متاح', booked: 'محجوز', notAvailable: 'غير متاح', maintenance: 'صيانة' }[status] || 'متاح';
|
||||
|
||||
const rawImages = Array.isArray(info.images) ? info.images : [];
|
||||
const images = rawImages.length > 0 ? rawImages.map(buildImageUrl) : [];
|
||||
const photosFallback = Array.isArray(details.photos) ? details.photos : [];
|
||||
const allRaw = rawImages.length > 0 ? rawImages : photosFallback;
|
||||
const images = allRaw.length > 0 ? allRaw.map(buildImageUrl) : [];
|
||||
|
||||
const services = details.services || {};
|
||||
const terms = details.terms || {};
|
||||
const proximity = details.proximity || {};
|
||||
const roomDetails = details.roomDetails || {};
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
title: info.address || `عقار #${item.id}`,
|
||||
description: info.description || '',
|
||||
title: details.description || info.address || `عقار #${item.id}`,
|
||||
description: details.description || info.description || '',
|
||||
type: propType,
|
||||
typeLabel,
|
||||
price,
|
||||
@ -84,11 +118,16 @@ function mapApiDetail(item) {
|
||||
bedrooms: info.numberOfBedRooms || 0,
|
||||
bathrooms: info.numberOfBathRooms || 0,
|
||||
area: info.space || 0,
|
||||
floor: details.floor || 0,
|
||||
salons: details.salons || 0,
|
||||
balconies: details.balconies || 0,
|
||||
images,
|
||||
status,
|
||||
statusLabel,
|
||||
services,
|
||||
terms,
|
||||
proximity,
|
||||
roomDetails,
|
||||
details,
|
||||
deposit: item.deposit || 0,
|
||||
currencyId: item.currencyId,
|
||||
@ -236,6 +275,8 @@ export default function PropertyDetailsPage() {
|
||||
}
|
||||
|
||||
const isFav = isFavorite(property.id);
|
||||
const isRoomType = property.type === 'room';
|
||||
const isMostRequested = avgRating !== null && avgRating >= 4.5;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50" dir="rtl">
|
||||
@ -253,35 +294,55 @@ export default function PropertyDetailsPage() {
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
{/* Image Gallery */}
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} className="bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-200">
|
||||
<div className="relative aspect-video bg-gray-100">
|
||||
<div className="relative bg-gray-900" style={{ minHeight: '420px', maxHeight: '520px' }}>
|
||||
{property.images.length > 0 ? (
|
||||
<img src={property.images[currentImage]} alt={property.title} className="w-full h-full object-cover" />
|
||||
<img src={property.images[currentImage]} alt={property.title}
|
||||
className="w-full h-full object-contain mx-auto"
|
||||
style={{ minHeight: '420px', maxHeight: '520px' }} />
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<ImageIcon className="w-16 h-16 text-gray-400" />
|
||||
<div className="w-full h-full flex items-center justify-center" style={{ minHeight: '420px' }}>
|
||||
<div className="text-center">
|
||||
<ImageIcon className="w-20 h-20 text-gray-500 mx-auto mb-2" />
|
||||
<p className="text-gray-400 text-sm">لا توجد صور</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isMostRequested && (
|
||||
<div className="absolute top-4 right-4 z-10">
|
||||
<span className="bg-gradient-to-l from-amber-500 to-amber-600 text-white px-3 py-1.5 rounded-full text-xs font-bold shadow-lg flex items-center gap-1">
|
||||
<Star className="w-3 h-3 fill-white" /> الأكثر طلباً
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{property.images.length > 1 && (
|
||||
<>
|
||||
<button onClick={() => setCurrentImage(prev => (prev - 1 + property.images.length) % property.images.length)}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 bg-black/50 text-white p-2 rounded-full hover:bg-black/70">
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 bg-black/60 text-white p-3 rounded-full hover:bg-black/80 transition-all shadow-lg">
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
<button onClick={() => setCurrentImage(prev => (prev + 1) % property.images.length)}
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 bg-black/50 text-white p-2 rounded-full hover:bg-black/70">
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 bg-black/60 text-white p-3 rounded-full hover:bg-black/80 transition-all shadow-lg">
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<div className="absolute bottom-4 right-4">
|
||||
<span className="bg-black/60 text-white px-3 py-1 rounded-full text-sm">{property.statusLabel}</span>
|
||||
|
||||
<div className="absolute bottom-4 right-4 flex gap-2 z-10">
|
||||
<span className="bg-black/70 text-white px-3 py-1 rounded-full text-sm font-medium backdrop-blur-sm">{property.statusLabel}</span>
|
||||
<span className="bg-black/70 text-white px-3 py-1 rounded-full text-sm font-medium backdrop-blur-sm">{property.typeLabel}</span>
|
||||
</div>
|
||||
<div className="absolute bottom-4 left-4 bg-black/70 text-white px-3 py-1 rounded-full text-xs backdrop-blur-sm z-10">
|
||||
{currentImage + 1} / {property.images.length || 1}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{property.images.length > 1 && (
|
||||
<div className="flex gap-2 p-4 overflow-x-auto">
|
||||
<div className="flex gap-2 p-3 bg-gray-50 overflow-x-auto" style={{ scrollBehavior: 'smooth' }}>
|
||||
{property.images.map((img, idx) => (
|
||||
<button key={idx} onClick={() => setCurrentImage(idx)}
|
||||
className={`flex-shrink-0 w-20 h-16 rounded-lg overflow-hidden border-2 transition-colors ${idx === currentImage ? 'border-amber-500' : 'border-transparent opacity-60 hover:opacity-100'}`}>
|
||||
className={`flex-shrink-0 w-24 h-20 rounded-xl overflow-hidden border-2 transition-all duration-200 ${idx === currentImage ? 'border-amber-500 ring-2 ring-amber-200 shadow-md' : 'border-gray-200 opacity-60 hover:opacity-100'}`}>
|
||||
<img src={img} alt="" className="w-full h-full object-cover" />
|
||||
</button>
|
||||
))}
|
||||
@ -314,7 +375,7 @@ export default function PropertyDetailsPage() {
|
||||
{/* Price */}
|
||||
<div className="bg-amber-50 rounded-xl p-4 mb-6">
|
||||
{property.isRent ? (
|
||||
<div className="flex gap-6">
|
||||
<div className="flex flex-wrap gap-6 items-end">
|
||||
{property.priceDisplay.monthly > 0 && (
|
||||
<div>
|
||||
<span className="text-2xl font-bold text-amber-600">{formatCurrency(property.priceDisplay.monthly)}</span>
|
||||
@ -328,7 +389,9 @@ export default function PropertyDetailsPage() {
|
||||
</div>
|
||||
)}
|
||||
{property.deposit > 0 && (
|
||||
<div className="text-sm text-gray-500 mt-1">تأمين: {formatCurrency(property.deposit)} ل.س</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="font-medium">تأمين:</span> {formatCurrency(property.deposit)} ل.س
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
@ -340,33 +403,54 @@ export default function PropertyDetailsPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex gap-6 mb-6">
|
||||
{/* Specs Tiles */}
|
||||
<div className="grid grid-cols-3 md:grid-cols-6 gap-2 mb-6">
|
||||
{property.bedrooms > 0 && (
|
||||
<div className="text-center">
|
||||
<Bed className="w-6 h-6 text-amber-500 mx-auto mb-1" />
|
||||
<div className="font-bold">{property.bedrooms}</div>
|
||||
<div className="bg-gray-50 rounded-xl p-3 text-center">
|
||||
<Bed className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="font-bold text-gray-900">{property.bedrooms}</div>
|
||||
<div className="text-xs text-gray-500">غرف نوم</div>
|
||||
</div>
|
||||
)}
|
||||
{property.bathrooms > 0 && (
|
||||
<div className="text-center">
|
||||
<Bath className="w-6 h-6 text-amber-500 mx-auto mb-1" />
|
||||
<div className="font-bold">{property.bathrooms}</div>
|
||||
<div className="bg-gray-50 rounded-xl p-3 text-center">
|
||||
<Bath className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="font-bold text-gray-900">{property.bathrooms}</div>
|
||||
<div className="text-xs text-gray-500">حمامات</div>
|
||||
</div>
|
||||
)}
|
||||
{property.area > 0 && (
|
||||
<div className="text-center">
|
||||
<Square className="w-6 h-6 text-amber-500 mx-auto mb-1" />
|
||||
<div className="font-bold">{property.area}</div>
|
||||
<div className="bg-gray-50 rounded-xl p-3 text-center">
|
||||
<Square className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="font-bold text-gray-900">{property.area}</div>
|
||||
<div className="text-xs text-gray-500">م²</div>
|
||||
</div>
|
||||
)}
|
||||
{property.floor > 0 && (
|
||||
<div className="bg-gray-50 rounded-xl p-3 text-center">
|
||||
<Layers className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="font-bold text-gray-900">{property.floor}</div>
|
||||
<div className="text-xs text-gray-500">طابق</div>
|
||||
</div>
|
||||
)}
|
||||
{property.salons > 0 && (
|
||||
<div className="bg-gray-50 rounded-xl p-3 text-center">
|
||||
<Sofa className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="font-bold text-gray-900">{property.salons}</div>
|
||||
<div className="text-xs text-gray-500">صالونات</div>
|
||||
</div>
|
||||
)}
|
||||
{property.balconies > 0 && (
|
||||
<div className="bg-gray-50 rounded-xl p-3 text-center">
|
||||
<DoorOpen className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||
<div className="font-bold text-gray-900">{property.balconies}</div>
|
||||
<div className="text-xs text-gray-500">بلكونات</div>
|
||||
</div>
|
||||
)}
|
||||
{avgRating !== null && avgRating > 0 && (
|
||||
<div className="text-center">
|
||||
<Star className="w-6 h-6 text-amber-500 mx-auto mb-1 fill-amber-500" />
|
||||
<div className="font-bold">{avgRating.toFixed(1)}</div>
|
||||
<div className="bg-gray-50 rounded-xl p-3 text-center">
|
||||
<Star className="w-5 h-5 text-amber-500 mx-auto mb-1 fill-amber-500" />
|
||||
<div className="font-bold text-gray-900">{avgRating.toFixed(1)}</div>
|
||||
<div className="text-xs text-gray-500">التقييم</div>
|
||||
</div>
|
||||
)}
|
||||
@ -382,34 +466,148 @@ export default function PropertyDetailsPage() {
|
||||
|
||||
{/* Features */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{property.isSmokeAllow && <span className="px-3 py-1 bg-gray-100 rounded-full text-sm">يسمح بالتدخين</span>}
|
||||
{property.isVisitorAllow && <span className="px-3 py-1 bg-gray-100 rounded-full text-sm">يسمح بالزوار</span>}
|
||||
{property.specializedFor && <span className="px-3 py-1 bg-gray-100 rounded-full text-sm">متخصص</span>}
|
||||
{property.isSmokeAllow && <span className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm border flex items-center gap-1"><Wind className="w-3 h-3" /> يسمح بالتدخين</span>}
|
||||
{!property.isSmokeAllow && <span className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm border flex items-center gap-1"><Ban className="w-3 h-3" /> ممنوع التدخين</span>}
|
||||
{property.isVisitorAllow && <span className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm border flex items-center gap-1"><Users className="w-3 h-3" /> يسمح بالزوار</span>}
|
||||
{property.specializedFor && <span className="px-3 py-1 bg-amber-50 text-amber-700 rounded-full text-sm border border-amber-200 flex items-center gap-1"><Users className="w-3 h-3" /> {property.specializedFor}</span>}
|
||||
</div>
|
||||
|
||||
{/* Services */}
|
||||
{/* Services with detail text */}
|
||||
{Object.keys(property.services).length > 0 && (
|
||||
<div className="mb-6">
|
||||
<h3 className="font-bold text-gray-900 mb-3">الخدمات</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{Object.entries(property.services).map(([key, val]) => {
|
||||
if (!val) return null;
|
||||
const labels = { Electricity: 'كهرباء', Internet: 'إنترنت', Heating: 'تدفئة', Water: 'ماء', Pool: 'مسبح', PrivateGarden: 'حديقة خاصة', Parking: 'موقف سيارات', Security247: 'حراسة 24 س', CentralHeating: 'تدفئة مركزية', CentralAirConditioning: 'تكييف مركزي', EquippedKitchen: 'مطبخ مجهز', MaidsRoom: 'غرفة خادمة', Elevator: 'مصعد' };
|
||||
return <span key={key} className="px-3 py-1 bg-green-50 text-green-700 rounded-full text-sm border border-green-200">{labels[key] || key}</span>;
|
||||
const detail = typeof val === 'object' && val.detail ? val.detail : null;
|
||||
return (
|
||||
<span key={key} className="px-3 py-1 bg-green-50 text-green-700 rounded-full text-sm border border-green-200 flex items-center gap-1">
|
||||
{serviceLabels[key] || key}
|
||||
{detail && <span className="text-green-400">· {detail}</span>}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Terms */}
|
||||
{/* Room Details (only for room type) */}
|
||||
{isRoomType && Object.keys(property.roomDetails).length > 0 && (
|
||||
<div className="mb-6 bg-blue-50 rounded-xl p-4">
|
||||
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
||||
<Info className="w-5 h-5 text-blue-500" />
|
||||
تفاصيل الغرفة
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{property.roomDetails.areaType && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">نوع المساحة</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.areaType}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.roomDetails.peopleAllowed && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">عدد الأشخاص</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.peopleAllowed}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.roomDetails.furniture && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">الأثاث</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.furniture}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.roomDetails.entranceType && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">نوع المدخل</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.entranceType}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.roomDetails.bathroomType && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">الحمام</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.bathroomType}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.roomDetails.kitchenType && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">المطبخ</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.kitchenType}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.roomDetails.residents && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">عدد السكان</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.residents}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.roomDetails.dedicatedTo && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">مخصص لـ</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.dedicatedTo}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.roomDetails.visitors && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">الزوار</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.visitors ? 'مسموح' : 'ممنوع'}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.roomDetails.quietTimes && (
|
||||
<div className="bg-white rounded-lg p-2 text-center">
|
||||
<div className="text-xs text-gray-500">أوقات الهدوء</div>
|
||||
<div className="font-medium text-sm">{property.roomDetails.quietTimes}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Proximity */}
|
||||
{Object.keys(property.proximity).length > 0 && (
|
||||
<div className="mb-6">
|
||||
<h3 className="font-bold text-gray-900 mb-3">القرب من الخدمات</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
||||
{Object.entries(property.proximity).map(([key, val]) => {
|
||||
if (!val) return null;
|
||||
const dist = typeof val === 'object' ? val.distance : val;
|
||||
return (
|
||||
<div key={key} className="bg-gray-50 rounded-lg p-3 flex items-center gap-2">
|
||||
{key === 'School' && <School className="w-4 h-4 text-amber-500 flex-shrink-0" />}
|
||||
{key === 'Hospital' && <Hospital className="w-4 h-4 text-amber-500 flex-shrink-0" />}
|
||||
{key === 'Restaurant' && <Store className="w-4 h-4 text-amber-500 flex-shrink-0" />}
|
||||
{key === 'University' && <GraduationCap className="w-4 h-4 text-amber-500 flex-shrink-0" />}
|
||||
{key === 'Park' && <TreePine className="w-4 h-4 text-amber-500 flex-shrink-0" />}
|
||||
{key === 'Mall' && <Building className="w-4 h-4 text-amber-500 flex-shrink-0" />}
|
||||
{!['School','Hospital','Restaurant','University','Park','Mall'].includes(key) && <MapPin className="w-4 h-4 text-amber-500 flex-shrink-0" />}
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900">{proximityLabels[key] || key}</div>
|
||||
<div className="text-xs text-gray-500">{dist} {typeof dist === 'number' ? 'كم' : ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Terms as checklist */}
|
||||
{Object.keys(property.terms).length > 0 && (
|
||||
<div className="mb-6">
|
||||
<h3 className="font-bold text-gray-900 mb-3">الشروط</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
{Object.entries(property.terms).map(([key, val]) => {
|
||||
if (!val) return null;
|
||||
const labels = { NoSmoking: 'ممنوع التدخين', NoAnimals: 'ممنوع الحيوانات', NoParties: 'ممنوع الحفلات' };
|
||||
return <span key={key} className="px-3 py-1 bg-amber-50 text-amber-700 rounded-full text-sm border border-amber-200">{labels[key] || key}</span>;
|
||||
return (
|
||||
<div key={key} className="flex items-center gap-2 p-2 bg-gray-50 rounded-lg">
|
||||
{key.startsWith('No') || key.startsWith('Only') ? (
|
||||
<Ban className="w-4 h-4 text-red-500 flex-shrink-0" />
|
||||
) : (
|
||||
<Check className="w-4 h-4 text-green-500 flex-shrink-0" />
|
||||
)}
|
||||
<span className="text-sm text-gray-700">{termLabels[key] || key}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
@ -418,13 +616,26 @@ export default function PropertyDetailsPage() {
|
||||
|
||||
{/* Map */}
|
||||
{property.location.lat && property.location.lng && (
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} className="bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-200 h-64">
|
||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} className="bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-200">
|
||||
<div className="h-64">
|
||||
<MapContainer center={[property.location.lat, property.location.lng]} zoom={14} className="h-full w-full" scrollWheelZoom={false}>
|
||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
||||
<Marker position={[property.location.lat, property.location.lng]}>
|
||||
<Popup>{property.title}</Popup>
|
||||
</Marker>
|
||||
</MapContainer>
|
||||
</div>
|
||||
<div className="p-3 bg-amber-50 text-center text-sm text-amber-700 flex items-center justify-center gap-2">
|
||||
<Info className="w-4 h-4" />
|
||||
<span>موقع تقريبي — يظهر الموقع الدقيق بعد تأكيد الحجز</span>
|
||||
</div>
|
||||
<div className="p-3 border-t border-gray-100">
|
||||
<a href={`https://www.google.com/maps?q=${property.location.lat},${property.location.lng}`} target="_blank" rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-2 text-blue-600 hover:text-blue-700 font-medium text-sm">
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
فتح في Google Maps
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@ -457,7 +668,10 @@ export default function PropertyDetailsPage() {
|
||||
{/* Booking Card */}
|
||||
{property.isRent && (
|
||||
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="bg-white rounded-2xl p-6 shadow-sm border border-gray-200 sticky top-6">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">حجز العقار</h3>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Calendar className="w-5 h-5 text-amber-500" />
|
||||
<h3 className="text-lg font-bold text-gray-900">حجز العقار</h3>
|
||||
</div>
|
||||
|
||||
{bookingSuccess ? (
|
||||
<div className="text-center py-4">
|
||||
@ -471,12 +685,12 @@ export default function PropertyDetailsPage() {
|
||||
<>
|
||||
<div className="space-y-3 mb-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">تاريخ البداية</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">من تاريخ</label>
|
||||
<input type="date" value={bookingDates.start} onChange={e => setBookingDates(prev => ({ ...prev, start: e.target.value }))}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">تاريخ النهاية</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">إلى تاريخ</label>
|
||||
<input type="date" value={bookingDates.end} onChange={e => setBookingDates(prev => ({ ...prev, end: e.target.value }))}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500" />
|
||||
</div>
|
||||
@ -498,19 +712,23 @@ export default function PropertyDetailsPage() {
|
||||
|
||||
{/* Contact Card */}
|
||||
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="bg-white rounded-2xl p-6 shadow-sm border border-gray-200">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">معلومات المالك</h3>
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Phone className="w-5 h-5 text-amber-500" />
|
||||
<h3 className="text-lg font-bold text-gray-900">معلومات المالك</h3>
|
||||
</div>
|
||||
|
||||
{showContact && contactInfo ? (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-xl">
|
||||
<Phone className="w-5 h-5 text-gray-600" />
|
||||
<Phone className="w-5 h-5 text-gray-600 flex-shrink-0" />
|
||||
<span className="font-medium text-gray-900" dir="ltr">{contactInfo.phone || contactInfo.phoneNumber || '—'}</span>
|
||||
</div>
|
||||
{contactInfo.whatsAppNumber && (
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-xl">
|
||||
<MessageCircle className="w-5 h-5 text-green-600" />
|
||||
<a href={`https://wa.me/${contactInfo.whatsAppNumber.replace(/[^0-9]/g, '')}`} target="_blank" rel="noopener noreferrer"
|
||||
className="flex items-center gap-3 p-3 bg-green-50 rounded-xl hover:bg-green-100 transition-colors">
|
||||
<MessageCircle className="w-5 h-5 text-green-600 flex-shrink-0" />
|
||||
<span className="font-medium text-gray-900" dir="ltr">{contactInfo.whatsAppNumber}</span>
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user