Files
SweetHome/app/property/[id]/PropertyDetail.js

1028 lines
57 KiB
JavaScript
Raw Normal View History

'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';
2026-05-25 21:27:39 +03:00
import { useParams, useRouter } from 'next/navigation';
import {
2026-05-25 21:27:39 +03:00
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';
2026-05-26 20:24:37 +03:00
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';
2026-04-26 13:46:30 +03:00
import PropertyRatingList from '@/app/components/ratings/PropertyRatingList';
2026-05-25 21:27:39 +03:00
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 });
2026-04-26 13:46:30 +03:00
function formatCurrency(amount) {
2026-05-25 21:27:39 +03:00
if (!amount || isNaN(amount)) return '0';
return Number(amount).toLocaleString('ar-SA');
}
2026-05-25 21:27:39 +03:00
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 || {};
2026-05-26 00:20:20 +03:00
const details = typeof info.detailsJSON === 'object' && info.detailsJSON ? info.detailsJSON : (() => { try { return JSON.parse(info.detailsJSON || '{}'); } catch { return {}; } })();
2026-05-25 21:27:39 +03:00
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';
2026-05-25 21:27:39 +03:00
const propType = BuildingTypeKeys[info.buildingType] ?? BuildingTypeKeys[item.type] ?? 'apartment';
const typeLabel = { apartment: 'شقة', villa: 'فيلا', house: 'بيت', sweet: 'سويت', studio: 'استوديو', office: 'مكتب', shop: 'متجر', warehouse: 'مستودع', farms: 'مزرعة', room: 'غرفة ضمن شقة' }[propType] || 'عقار';
2026-05-25 21:27:39 +03:00
const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available';
const statusLabel = { available: 'متاح', booked: 'محجوز', notAvailable: 'غير متاح', maintenance: 'صيانة' }[status] || 'متاح';
2026-05-25 21:27:39 +03:00
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 || {};
2026-05-26 00:20:20 +03:00
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,
2026-05-25 21:27:39 +03:00
typeLabel,
price,
2026-04-26 13:46:30 +03:00
priceUnit,
2026-05-25 21:27:39 +03:00
priceDisplay: { daily: dailyPrice, monthly: monthlyPrice, sale: salePrice },
isRent,
2026-05-26 00:20:20 +03:00
displayType,
propertyCondition,
furnished,
location: {
city: extractCity(info.address) || 'دمشق',
address: info.address || '',
2026-05-25 21:27:39 +03:00
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,
2026-05-25 21:27:39 +03:00
statusLabel,
services,
terms,
proximity,
roomDetails,
2026-05-25 21:27:39 +03:00
details,
bookedCount: details.bookedCount || 0,
2026-04-26 13:46:30 +03:00
deposit: item.deposit || 0,
2026-05-25 21:27:39 +03:00
currencyId: item.currencyId,
isSmokeAllow: item.isSmokeAllow,
isVisitorAllow: item.isVisitorAllow,
specializedFor: item.specializedFor,
2026-05-26 18:43:47 +03:00
ownerId: info.ownerId ?? info.userId ?? item.ownerId ?? item.userId ?? null,
_raw: item,
};
}
export default function PropertyDetailsPage() {
const params = useParams();
2026-05-25 21:27:39 +03:00
const router = useRouter();
const { isFavorite, addFavorite, removeFavorite } = useFavorites();
2026-04-26 13:46:30 +03:00
const [property, setProperty] = useState(null);
const [loading, setLoading] = useState(true);
const [currentImage, setCurrentImage] = useState(0);
2026-04-26 13:46:30 +03:00
const [showLoginDialog, setShowLoginDialog] = useState(false);
const [bookingDates, setBookingDates] = useState({ start: '', end: '' });
2026-05-25 21:27:39 +03:00
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');
2026-05-26 17:31:01 +03:00
const [isOwnProperty, setIsOwnProperty] = useState(false);
2026-05-25 21:27:39 +03:00
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(() => {
2026-05-25 21:27:39 +03:00
const id = params.id;
if (!id) return;
setLoading(true);
2026-05-25 21:27:39 +03:00
async function fetchProperty() {
try {
let data = null;
2026-05-25 21:27:39 +03:00
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) {
2026-05-25 21:27:39 +03:00
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);
}
}
2026-05-26 19:18:49 +03:00
// Check if current user owns this property via their own listings
2026-05-26 19:04:57 +03:00
if (AuthService.isAuthenticated() && AuthService.isOwner()) {
try {
2026-05-26 20:24:37 +03:00
const [myRent, mySale] = await Promise.allSettled([
getMyRentListings(),
getMySaleListings(),
]);
2026-05-26 19:25:10 +03:00
const myPropIds = new Set();
2026-05-26 20:24:37 +03:00
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);
2026-05-26 19:25:10 +03:00
const propInfoId = mapped._raw?.propertyInformation?.id;
if (myPropIds.has(Number(mapped.id)) || (propInfoId && myPropIds.has(Number(propInfoId)))) {
2026-05-26 19:18:49 +03:00
setIsOwnProperty(true);
2026-05-26 19:04:57 +03:00
}
} catch (e) {
2026-05-26 19:18:49 +03:00
console.warn('[OwnerCheck] failed:', e);
2026-05-26 18:22:21 +03:00
}
2026-05-26 17:31:01 +03:00
}
}
} catch (err) {
2026-05-25 21:27:39 +03:00
console.error('[PropertyDetail] Failed:', err);
} finally {
setLoading(false);
}
}
2026-05-25 21:27:39 +03:00
fetchProperty();
}, [params.id]);
2026-05-25 21:27:39 +03:00
const fetchAvgRating = async (propId) => {
try {
const avg = await getPropertyAverageRating(propId);
setAvgRating(avg);
} catch {}
2026-05-26 23:00:19 +03:00
};
2026-05-25 21:27:39 +03:00
const fetchContactInfo = async () => {
if (!property) return;
try {
const info = await getOwnerContactInformation(property._raw?.propertyInformationId || property.id);
setContactInfo(info);
setShowContact(true);
} catch (err) {
toast.error('فشل تحميل معلومات الاتصال');
}
};
2026-05-25 21:27:39 +03:00
const handleFavorite = async () => {
if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; }
if (!property) return;
setFavLoading(true);
try {
if (isFavorite(property.id)) {
await removeFavorite(property.id);
} else {
2026-05-25 21:27:39 +03:00
await addFavorite(property.id);
}
2026-05-25 21:27:39 +03:00
} catch (err) {
toast.error('حدث خطأ');
} finally {
setFavLoading(false);
}
};
2026-05-25 21:27:39 +03:00
const handleBookNow = async () => {
if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; }
if (!bookingDates.start || !bookingDates.end) {
2026-05-25 21:27:39 +03:00
setBookingError('يرجى تحديد تاريخ البداية والنهاية');
return;
}
2026-05-25 21:27:39 +03:00
setBookingLoading(true);
setBookingError(null);
try {
2026-05-25 21:27:39 +03:00
await bookReservation(property._raw?.propertyInformationId || property.id, bookingDates.start, bookingDates.end);
setBookingSuccess(true);
2026-04-26 13:46:30 +03:00
toast.success('تم إرسال طلب الحجز بنجاح');
} catch (err) {
2026-04-26 13:46:30 +03:00
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);
};
2026-05-25 21:27:39 +03:00
const handleRatingSuccess = () => {
setShowRatingForm(false);
if (property) fetchAvgRating(property.id);
};
if (loading) {
return (
2026-05-25 21:27:39 +03:00
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
<p className="text-gray-600">جاري تحميل العقار...</p>
</div>
</div>
);
}
if (!property) {
return (
2026-05-25 21:27:39 +03:00
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="text-center max-w-md">
<div className="w-24 h-24 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Home className="w-12 h-12 text-amber-600" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">العقار غير موجود</h2>
<p className="text-gray-600 mb-6">لم يتم العثور على العقار المطلوب</p>
<Link href="/properties" className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600">
<ArrowLeft className="w-5 h-5" />
العودة إلى العقارات
</Link>
</div>
</div>
);
}
2026-05-25 21:27:39 +03:00
const isFav = isFavorite(property.id);
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');
return (
2026-05-25 21:27:39 +03:00
<div className="min-h-screen bg-gray-50" dir="rtl">
<Toaster position="top-center" reverseOrder={false} />
<div className="container mx-auto px-4 py-6">
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} className="mb-6">
<Link href="/properties" className="inline-flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors">
<ArrowLeft className="w-5 h-5" />
العودة إلى العقارات
</Link>
</motion.div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-5">
2026-05-25 21:27:39 +03:00
{/* 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 bg-gray-900" style={{ minHeight: '380px', maxHeight: '460px' }}>
2026-05-25 21:27:39 +03:00
{property.images.length > 0 ? (
<img src={property.images[currentImage]} alt={property.title}
className="w-full h-full object-contain mx-auto"
style={{ minHeight: '380px', maxHeight: '460px' }} />
2026-05-25 21:27:39 +03:00
) : (
<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>
2026-05-25 21:27:39 +03:00
</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>
)}
2026-05-25 21:27:39 +03:00
{property.images.length > 1 && (
<>
2026-05-25 21:27:39 +03:00
<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/60 text-white p-3 rounded-full hover:bg-black/80 transition-all shadow-lg">
2026-05-25 21:27:39 +03:00
<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/60 text-white p-3 rounded-full hover:bg-black/80 transition-all shadow-lg">
2026-05-25 21:27:39 +03:00
<ChevronLeft className="w-5 h-5" />
</button>
</>
)}
<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>
2026-05-25 21:27:39 +03:00
{property.images.length > 1 && (
<div className="flex gap-2 p-3 bg-gray-50 overflow-x-auto" style={{ scrollBehavior: 'smooth' }}>
2026-05-25 21:27:39 +03:00
{property.images.map((img, idx) => (
<button key={idx} onClick={() => setCurrentImage(idx)}
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'}`}>
2026-05-25 21:27:39 +03:00
<img src={img} alt="" className="w-full h-full object-cover" />
</button>
))}
</div>
2026-05-25 21:27:39 +03:00
)}
</motion.div>
2026-05-25 21:27:39 +03:00
{/* Property Info */}
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} className="bg-white rounded-2xl p-5 shadow-sm border border-gray-200">
<div className="flex justify-between items-start mb-3">
<div>
2026-05-26 00:20:20 +03:00
<div className="flex items-center gap-1.5 mb-1 flex-wrap">
<span className="px-2 py-0.5 bg-amber-100 text-amber-800 rounded-full text-xs">{property.typeLabel}</span>
<span className={`px-2 py-0.5 rounded-full text-xs ${property.status === 'available' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'}`}>{property.statusLabel}</span>
2026-05-26 00:20:20 +03:00
{property.isRent && property.displayType && (
<span className="px-2 py-0.5 bg-blue-100 text-blue-800 rounded-full text-xs">
{(() => {
const dt = property.displayType.toLowerCase();
if (dt === 'both' || dt.includes('both')) return 'يومي وشهري';
if (dt.includes('daily')) return 'يومي';
if (dt.includes('monthly')) return 'شهري';
return dt;
})()}
</span>
)}
<span className={`px-2 py-0.5 rounded-full text-xs ${property.furnished ? 'bg-purple-100 text-purple-800' : 'bg-gray-100 text-gray-600'}`}>
{property.furnished ? 'مفروش' : 'غير مفروش'}
</span>
2026-05-25 21:27:39 +03:00
</div>
<h1 className="text-xl font-bold text-gray-900">{property.title}</h1>
<div className="flex items-center gap-1 text-gray-500 text-xs mt-0.5">
<MapPin className="w-3 h-3" />
2026-05-25 21:27:39 +03:00
<span>{property.location.address || property.location.city}</span>
</div>
</div>
<button onClick={handleFavorite} disabled={favLoading}
className="p-1.5 rounded-full hover:bg-gray-100 transition-colors">
<Heart className={`w-5 h-5 ${isFav ? 'fill-red-500 text-red-500' : 'text-gray-400'}`} />
</button>
2026-05-25 21:27:39 +03:00
</div>
2026-05-25 21:27:39 +03:00
{/* Price */}
<div className="bg-amber-50 rounded-xl p-3 mb-4">
2026-05-25 21:27:39 +03:00
{property.isRent ? (
<div className="flex flex-wrap gap-6 items-end">
2026-05-25 21:27:39 +03:00
{property.priceDisplay.monthly > 0 && (
<div>
<span className="text-2xl font-bold text-amber-600">{formatCurrency(property.priceDisplay.monthly)}</span>
<span className="text-gray-500 mr-1">ل.س / شهرياً</span>
</div>
)}
{property.priceDisplay.daily > 0 && (
<div>
<span className="text-2xl font-bold text-amber-600">{formatCurrency(property.priceDisplay.daily)}</span>
<span className="text-gray-500 mr-1">ل.س / يومياً</span>
</div>
)}
{property.deposit > 0 && (
<div className="text-sm text-gray-500">
<span className="font-medium">تأمين:</span> {formatCurrency(property.deposit)} ل.س
</div>
2026-05-25 21:27:39 +03:00
)}
</div>
) : (
<div>
2026-05-25 21:27:39 +03:00
<span className="text-2xl font-bold text-blue-600">{formatCurrency(property.price)}</span>
<span className="text-gray-500 mr-1">ل.س</span>
<span className="text-sm text-gray-400 mr-2">للبيع</span>
</div>
2026-05-25 21:27:39 +03:00
)}
</div>
{/* Specs Tiles */}
<div className="grid grid-cols-3 md:grid-cols-6 gap-1.5 mb-4">
2026-05-25 21:27:39 +03:00
{property.bedrooms > 0 && (
<div className="bg-gray-50 rounded-lg p-2 text-center">
<Bed className="w-4 h-4 text-amber-500 mx-auto mb-0.5" />
<div className="font-bold text-gray-900 text-sm">{property.bedrooms}</div>
<div className="text-[10px] text-gray-500">غرف نوم</div>
2026-05-25 21:27:39 +03:00
</div>
)}
{property.bathrooms > 0 && (
<div className="bg-gray-50 rounded-lg p-2 text-center">
<Bath className="w-4 h-4 text-amber-500 mx-auto mb-0.5" />
<div className="font-bold text-gray-900 text-sm">{property.bathrooms}</div>
<div className="text-[10px] text-gray-500">حمامات</div>
</div>
2026-05-25 21:27:39 +03:00
)}
{property.area > 0 && (
<div className="bg-gray-50 rounded-lg p-2 text-center">
<Square className="w-4 h-4 text-amber-500 mx-auto mb-0.5" />
<div className="font-bold text-gray-900 text-sm">{property.area}</div>
<div className="text-[10px] text-gray-500">م²</div>
2026-05-25 21:27:39 +03:00
</div>
)}
{property.floor > 0 && (
<div className="bg-gray-50 rounded-lg p-2 text-center">
<Layers className="w-4 h-4 text-amber-500 mx-auto mb-0.5" />
<div className="font-bold text-gray-900 text-sm">{property.floor}</div>
<div className="text-[10px] text-gray-500">طابق</div>
</div>
)}
{property.salons > 0 && (
<div className="bg-gray-50 rounded-lg p-2 text-center">
<Sofa className="w-4 h-4 text-amber-500 mx-auto mb-0.5" />
<div className="font-bold text-gray-900 text-sm">{property.salons}</div>
<div className="text-[10px] text-gray-500">صالونات</div>
</div>
)}
{property.balconies > 0 && (
<div className="bg-gray-50 rounded-lg p-2 text-center">
<DoorOpen className="w-4 h-4 text-amber-500 mx-auto mb-0.5" />
<div className="font-bold text-gray-900 text-sm">{property.balconies}</div>
<div className="text-[10px] text-gray-500">بلكونات</div>
</div>
)}
2026-05-26 22:53:18 +03:00
{avgRating !== null && avgRating > 0 && (
<div className="bg-gray-50 rounded-lg p-2 text-center">
<Star className="w-4 h-4 text-amber-500 mx-auto mb-0.5 fill-amber-500" />
<div className="font-bold text-gray-900 text-sm">{avgRating.toFixed(1)}</div>
<div className="text-[10px] text-gray-500">التقييم</div>
2026-05-25 21:27:39 +03:00
</div>
)}
{property.bookedCount > 0 && (
<div className="bg-gray-50 rounded-lg p-2 text-center">
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-0.5" />
<div className="font-bold text-gray-900 text-sm">{property.bookedCount}</div>
<div className="text-[10px] text-gray-500">حجوزات</div>
</div>
)}
</div>
2026-05-25 21:27:39 +03:00
{/* Description */}
{property.description && (
<div className="mb-4">
<h3 className="font-bold text-gray-900 mb-1 text-sm">الوصف</h3>
<p className="text-gray-600 text-sm leading-relaxed">{property.description}</p>
2026-05-25 21:27:39 +03:00
</div>
)}
2026-05-25 21:27:39 +03:00
{/* Features */}
<div className="flex flex-wrap gap-1.5 mb-4">
{property.isSmokeAllow && <span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1"><Wind className="w-3 h-3" /> يسمح بالتدخين</span>}
{!property.isSmokeAllow && <span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1"><Ban className="w-3 h-3" /> ممنوع التدخين</span>}
{property.isVisitorAllow && <span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1"><Users className="w-3 h-3" /> يسمح بالزوار</span>}
{property.specializedFor && <span className="px-2 py-0.5 bg-amber-50 text-amber-700 rounded-full text-xs border border-amber-200 flex items-center gap-1"><Users className="w-3 h-3" /> {property.specializedFor}</span>}
2026-04-26 13:46:30 +03:00
</div>
2026-05-25 21:27:39 +03:00
{/* Services with detail text */}
{property.services && (Array.isArray(property.services) ? property.services.length > 0 : Object.keys(property.services).length > 0) && (
<div className="mb-4">
<h3 className="font-bold text-gray-900 mb-2 text-sm">الخدمات</h3>
<div className="flex flex-wrap gap-1.5">
{Array.isArray(property.services) ? (
property.services.map((svc, i) => (
<span key={i} 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[svc] || svc}
</span>
))
) : (
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 (
<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>
)}
{/* Room Details (only for room type) */}
{isRoomType && Object.keys(property.roomDetails).length > 0 && (
<div className="mb-4 bg-blue-50 rounded-xl p-3">
<h3 className="font-bold text-gray-900 mb-2 text-sm flex items-center gap-2">
<Info className="w-4 h-4 text-blue-500" />
تفاصيل الغرفة
</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
{(() => {
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 ? 'مفعلة' : '') });
return items.map((item, i) => (
<div key={i} className="bg-white rounded-lg p-2 text-center">
<div className="text-xs text-gray-500">{item.label}</div>
<div className="font-medium text-sm">{item.value}</div>
</div>
));
})()}
</div>
</div>
)}
{/* Proximity */}
{Object.keys(property.proximity).length > 0 && (
<div className="mb-4">
<h3 className="font-bold text-gray-900 mb-2 text-sm">القرب من الخدمات</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-1.5">
{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-2 flex items-center gap-1.5">
{key === 'School' && <School className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />}
{key === 'Hospital' && <Hospital className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />}
{key === 'Restaurant' && <Store className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />}
{key === 'University' && <GraduationCap className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />}
{key === 'Park' && <TreePine className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />}
{key === 'Mall' && <Building className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />}
{!['School','Hospital','Restaurant','University','Park','Mall'].includes(key) && <MapPin className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />}
<div>
<div className="text-xs font-medium text-gray-900">{proximityLabels[key] || key}</div>
<div className="text-[10px] text-gray-500">{dist} {typeof dist === 'number' ? 'كم' : ''}</div>
</div>
</div>
);
2026-05-25 21:27:39 +03:00
})}
</div>
</div>
)}
{/* Terms as checklist */}
2026-05-25 21:27:39 +03:00
{Object.keys(property.terms).length > 0 && (
<div className="mb-4">
<h3 className="font-bold text-gray-900 mb-2 text-sm">الشروط</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-1.5">
2026-05-25 21:27:39 +03:00
{Object.entries(property.terms).map(([key, val]) => {
if (!val) return null;
return (
<div key={key} className="flex items-center gap-1.5 p-1.5 bg-gray-50 rounded-lg">
{key.startsWith('No') || key.startsWith('Only') ? (
<Ban className="w-3.5 h-3.5 text-red-500 flex-shrink-0" />
) : (
<Check className="w-3.5 h-3.5 text-green-500 flex-shrink-0" />
)}
<span className="text-xs text-gray-700">{termLabels[key] || key}</span>
</div>
);
2026-05-25 21:27:39 +03:00
})}
</div>
</div>
)}
</motion.div>
{/* 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">
<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>
2026-05-25 21:27:39 +03:00
</motion.div>
)}
{/* Ratings Section */}
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}>
<PropertyRatingList propertyId={property._raw?.propertyInformationId || property.id} />
</motion.div>
</div>
2026-05-25 21:27:39 +03:00
{/* Sidebar */}
<div className="space-y-4">
2026-05-25 21:27:39 +03:00
{/* Booking Card */}
{property.isRent && (
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="bg-white rounded-2xl p-5 shadow-sm border border-gray-200 sticky top-6">
2026-05-26 17:31:01 +03:00
{isOwnProperty ? (
<div className="text-center py-3">
<div className="w-14 h-14 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-2">
<Home className="w-7 h-7 text-gray-400" />
</div>
<h4 className="font-bold text-gray-700 text-sm mb-1">هذا عقارك</h4>
<p className="text-xs text-gray-500">لا يمكنك حجز عقارك الخاص</p>
</div>
) : bookingSuccess ? (
<div className="text-center py-3">
<div className="w-14 h-14 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-2">
<Check className="w-7 h-7 text-green-600" />
2026-05-25 21:27:39 +03:00
</div>
<h4 className="font-bold text-green-700 text-sm mb-1">تم إرسال طلب الحجز</h4>
<p className="text-xs text-gray-500">سيتم مراجعة طلبك من قبل المالك</p>
</div>
2026-05-25 21:27:39 +03:00
) : (
<>
{/* Pricing Mode Toggle */}
{showPricingToggle && (
<div className="grid grid-cols-2 gap-2 mb-3">
<button onClick={() => setPricingMode('daily')}
className={`p-2.5 rounded-xl text-center border-2 transition-all ${effectivePricingMode === 'daily' ? 'border-amber-500 bg-amber-50' : 'border-gray-200 hover:border-gray-300'}`}>
<div className="text-xs font-bold text-gray-900">إيجار يومي</div>
<div className="text-sm font-bold text-amber-600">{formatCurrency(property.priceDisplay.daily)} ل.س</div>
</button>
<button onClick={() => setPricingMode('monthly')}
className={`p-2.5 rounded-xl text-center border-2 transition-all ${effectivePricingMode === 'monthly' ? 'border-amber-500 bg-amber-50' : 'border-gray-200 hover:border-gray-300'}`}>
<div className="text-xs font-bold text-gray-900">إيجار شهري</div>
<div className="text-sm font-bold text-amber-600">{formatCurrency(property.priceDisplay.monthly)} ل.س</div>
</button>
2026-05-25 21:27:39 +03:00
</div>
)}
{/* Step Indicator */}
<div className="flex items-center justify-center gap-2 mb-3">
<div className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${bookingStep === 'entry' ? 'bg-amber-100 text-amber-800' : 'bg-gray-100 text-gray-500'}`}>
<div className={`w-1.5 h-1.5 rounded-full ${bookingStep === 'entry' ? 'bg-amber-500' : 'bg-gray-400'}`} />
تحديد تاريخ البداية
</div>
<div className="text-gray-300 text-xs"></div>
<div className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${bookingStep === 'exit' ? 'bg-amber-100 text-amber-800' : 'bg-gray-100 text-gray-500'}`}>
<div className={`w-1.5 h-1.5 rounded-full ${bookingStep === 'exit' ? 'bg-amber-500' : 'bg-gray-400'}`} />
تحديد تاريخ النهاية
</div>
</div>
{/* Calendar */}
{effectivePricingMode === 'daily' ? (
<div className="mb-3">
<div className="flex items-center justify-between mb-2">
<button onClick={() => navigateMonth(-1)} className="p-1 rounded-lg hover:bg-gray-100 transition-colors">
<ChevronRight className="w-4 h-4 text-gray-600" />
</button>
<span className="text-sm font-bold text-gray-900">{MONTHS_AR[calendarMonth]} {calendarYear}</span>
<button onClick={() => navigateMonth(1)} className="p-1 rounded-lg hover:bg-gray-100 transition-colors">
<ChevronLeft className="w-4 h-4 text-gray-600" />
</button>
</div>
<div className="grid grid-cols-7 mb-1">
{DAYS_AR.map((d, i) => (
<div key={i} className="text-center text-[10px] text-gray-400 font-medium py-1">{d}</div>
))}
</div>
{(() => {
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(<div key={`e-${i}`} />);
}
for (let day = 1; day <= daysInMonth; day++) {
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 disabled = past || !available;
cells.push(
<button key={dateStr} onClick={() => !disabled && handleDayClick(dateStr)} disabled={disabled}
className={`text-center py-1.5 text-xs rounded-lg transition-all ${disabled ? 'text-gray-300 cursor-not-allowed' : ''} ${isSelStart || isSelEnd ? 'bg-amber-500 text-white font-bold shadow-sm' : ''} ${inRange ? 'bg-amber-100 text-amber-800' : ''} ${!disabled && !isSelStart && !isSelEnd && !inRange && available ? 'hover:bg-amber-50 text-gray-700' : ''} ${!disabled && !isSelStart && !isSelEnd && !inRange && !available ? 'text-red-300' : ''}`}>
{day}
</button>
);
}
return <div className="grid grid-cols-7 gap-0.5">{cells}</div>;
})()}
</div>
) : (
<div className="mb-3">
<div className="flex items-center justify-between mb-2">
<button onClick={() => setCalendarYear(prev => prev - 1)} className="p-1 rounded-lg hover:bg-gray-100 transition-colors">
<ChevronRight className="w-4 h-4 text-gray-600" />
</button>
<span className="text-sm font-bold text-gray-900">{calendarYear}</span>
<button onClick={() => setCalendarYear(prev => prev + 1)} className="p-1 rounded-lg hover:bg-gray-100 transition-colors">
<ChevronLeft className="w-4 h-4 text-gray-600" />
</button>
</div>
<div className="grid grid-cols-3 gap-2">
{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);
return (
<button key={idx} onClick={() => {
const firstDay = `${monthStr}-01`;
if (bookingStep === 'entry' || (selectedStart && monthStr <= selectedStart.substring(0, 7))) {
setSelectedStart(firstDay);
setSelectedEnd(null);
setBookingStep('exit');
} else {
const lastDay = new Date(calendarYear, idx + 1, 0).getDate();
setSelectedEnd(`${monthStr}-${String(lastDay).padStart(2, '0')}`);
setBookingStep('entry');
}
}}
className={`p-2.5 rounded-xl text-center text-xs font-medium transition-all border ${isSelStart || isSelEnd ? 'bg-amber-500 text-white border-amber-500 shadow-sm' : inRange ? 'bg-amber-100 text-amber-800 border-amber-200' : 'bg-white text-gray-700 border-gray-200 hover:border-amber-300'}`}>
{name}
</button>
);
})}
</div>
</div>
)}
{/* Summary */}
{selectedStart && (
<div className="bg-gray-50 rounded-xl p-3 mb-3 space-y-1.5">
<div className="flex justify-between text-xs">
<span className="text-gray-500">تاريخ البداية</span>
<span className="font-medium text-gray-900">{selectedStart}</span>
</div>
{selectedEnd && (
<>
<div className="flex justify-between text-xs">
<span className="text-gray-500">تاريخ النهاية</span>
<span className="font-medium text-gray-900">{selectedEnd}</span>
</div>
<div className="border-t border-gray-200 my-1" />
<div className="flex justify-between text-xs">
<span className="text-gray-500">{effectivePricingMode === 'daily' ? 'عدد الأيام' : 'عدد الأشهر'}</span>
<span className="font-medium text-gray-900">
{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}
</span>
</div>
<div className="flex justify-between text-xs font-bold">
<span className="text-gray-700">المجموع</span>
<span className="text-amber-600">
{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)} ل.س
</span>
</div>
{property.deposit > 0 && (
<div className="flex justify-between text-xs">
<span className="text-gray-500">تأمين</span>
<span className="font-medium text-gray-900">{formatCurrency(property.deposit)} ل.س</span>
</div>
)}
</>
)}
</div>
)}
2026-05-25 21:27:39 +03:00
{bookingError && (
<div className="bg-red-50 text-red-600 p-2.5 rounded-xl text-xs mb-3">{bookingError}</div>
2026-05-25 21:27:39 +03:00
)}
<button onClick={handleBookingConfirm} disabled={bookingLoading || !selectedStart || !selectedEnd}
className="w-full bg-amber-500 hover:bg-amber-600 text-white py-2.5 rounded-xl font-bold text-sm transition-all disabled:opacity-50 flex items-center justify-center gap-2">
{bookingLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Calendar className="w-4 h-4" />}
{bookingLoading ? 'جاري الحجز...' : 'تأكيد الحجز'}
2026-05-25 21:27:39 +03:00
</button>
</>
)}
</motion.div>
2026-05-25 21:27:39 +03:00
)}
2026-05-25 21:27:39 +03:00
{/* Contact Card */}
2026-05-26 18:01:18 +03:00
{!isOwnProperty && (
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="bg-white rounded-2xl p-5 shadow-sm border border-gray-200">
<div className="flex items-center gap-2 mb-3">
<Phone className="w-4 h-4 text-amber-500" />
<h3 className="font-bold text-gray-900">معلومات المالك</h3>
</div>
2026-05-25 21:27:39 +03:00
{showContact && contactInfo ? (
<div className="space-y-2">
<div className="flex items-center gap-2.5 p-2.5 bg-gray-50 rounded-xl">
<Phone className="w-4 h-4 text-gray-600 flex-shrink-0" />
<span className="font-medium text-gray-900 text-sm" dir="ltr">{contactInfo.phone || contactInfo.phoneNumber || '—'}</span>
</div>
2026-05-25 21:27:39 +03:00
{contactInfo.whatsAppNumber && (
<a href={`https://wa.me/${contactInfo.whatsAppNumber.replace(/[^0-9]/g, '')}`} target="_blank" rel="noopener noreferrer"
className="flex items-center gap-2.5 p-2.5 bg-green-50 rounded-xl hover:bg-green-100 transition-colors">
<MessageCircle className="w-4 h-4 text-green-600 flex-shrink-0" />
<span className="font-medium text-gray-900 text-sm" dir="ltr">{contactInfo.whatsAppNumber}</span>
</a>
2026-05-25 21:27:39 +03:00
)}
</div>
2026-05-25 21:27:39 +03:00
) : (
<button onClick={fetchContactInfo}
className="w-full bg-gray-800 hover:bg-gray-900 text-white py-2.5 rounded-xl font-medium text-sm transition-colors flex items-center justify-center gap-2">
<Phone className="w-4 h-4" />
2026-05-25 21:27:39 +03:00
عرض معلومات الاتصال
</button>
2026-05-25 21:27:39 +03:00
)}
</motion.div>
2026-05-26 18:01:18 +03:00
)}
</div>
</div>
</div>
2026-05-25 21:27:39 +03:00
{/* Login Dialog */}
{showLoginDialog && (
2026-05-25 21:27:39 +03:00
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={() => setShowLoginDialog(false)}>
<div className="bg-white rounded-2xl p-6 max-w-sm text-center mx-4" onClick={e => e.stopPropagation()}>
<div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
<LogIn className="w-8 h-8 text-amber-600" />
</div>
2026-05-25 21:27:39 +03:00
<h3 className="text-xl font-bold mb-2">تسجيل الدخول مطلوب</h3>
<p className="text-gray-500 mb-4">للحجز أو إضافة المفضلة، سجل دخولك.</p>
<Link href="/login" className="block w-full bg-amber-500 text-white py-3 rounded-xl font-medium mb-2 hover:bg-amber-600">تسجيل الدخول</Link>
<Link href="/auth/choose-role" className="block w-full bg-gray-100 py-3 rounded-xl font-medium hover:bg-gray-200">إنشاء حساب</Link>
<button onClick={() => setShowLoginDialog(false)} className="mt-3 text-gray-400 hover:text-gray-600">إلغاء</button>
2026-04-26 13:46:30 +03:00
</div>
</div>
)}
</div>
);
2026-05-25 21:27:39 +03:00
}