2026-02-15 01:53:37 +03:00
|
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
|
import { motion } from 'framer-motion';
|
|
|
|
|
|
import Image from 'next/image';
|
|
|
|
|
|
import Link from 'next/link';
|
|
|
|
|
|
import { useParams } from 'next/navigation';
|
|
|
|
|
|
import {
|
|
|
|
|
|
MapPin,
|
|
|
|
|
|
Bed,
|
|
|
|
|
|
Bath,
|
|
|
|
|
|
Square,
|
|
|
|
|
|
DollarSign,
|
|
|
|
|
|
Heart,
|
|
|
|
|
|
Share2,
|
|
|
|
|
|
Phone,
|
|
|
|
|
|
Mail,
|
|
|
|
|
|
MessageCircle,
|
|
|
|
|
|
Calendar,
|
|
|
|
|
|
Shield,
|
|
|
|
|
|
Star,
|
|
|
|
|
|
ChevronLeft,
|
|
|
|
|
|
ChevronRight,
|
|
|
|
|
|
Check,
|
|
|
|
|
|
X,
|
|
|
|
|
|
Wifi,
|
|
|
|
|
|
Car,
|
|
|
|
|
|
Coffee,
|
|
|
|
|
|
Wind,
|
|
|
|
|
|
Thermometer,
|
|
|
|
|
|
Lock,
|
|
|
|
|
|
Camera,
|
|
|
|
|
|
Home,
|
|
|
|
|
|
Building2,
|
|
|
|
|
|
Users,
|
|
|
|
|
|
Ruler,
|
|
|
|
|
|
CalendarDays,
|
|
|
|
|
|
Clock,
|
|
|
|
|
|
Award,
|
|
|
|
|
|
FileText,
|
|
|
|
|
|
Printer,
|
|
|
|
|
|
Download,
|
|
|
|
|
|
ArrowLeft
|
|
|
|
|
|
} from 'lucide-react';
|
2026-03-26 22:20:33 +00:00
|
|
|
|
import { getRentProperty, getSaleProperty, bookReservation, checkAvailability } from '../../utils/api';
|
|
|
|
|
|
|
|
|
|
|
|
// Map API response to the UI format
|
|
|
|
|
|
function mapApiDetail(item) {
|
|
|
|
|
|
if (!item) return null;
|
|
|
|
|
|
|
2026-03-26 22:59:08 +00:00
|
|
|
|
const info = item.propertyInformation || {};
|
|
|
|
|
|
const hasNestedInfo = !!item.propertyInformation;
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
|
|
|
|
|
const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
|
|
|
|
|
|
const monthlyPrice = item.monthlyRent ?? 0;
|
|
|
|
|
|
|
|
|
|
|
|
const buildingTypeMap = { 0: 'apartment', 1: 'villa', 2: 'house' };
|
2026-03-26 22:59:08 +00:00
|
|
|
|
const propType = buildingTypeMap[info.buildingType] ?? buildingTypeMap[item.type] ?? 'apartment';
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
|
|
|
|
|
const statusMap = { 0: 'available', 1: 'booked', 2: 'maintenance' };
|
2026-03-26 22:59:08 +00:00
|
|
|
|
const status = statusMap[info.status] ?? statusMap[item.status] ?? 'available';
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
|
|
|
|
|
const features = [];
|
|
|
|
|
|
if (item.isSmokeAllow) features.push({ name: 'يسمح بالتدخين', available: true, description: '' });
|
|
|
|
|
|
if (item.isVisitorAllow) features.push({ name: 'يسمح بالزوار', available: true, description: '' });
|
|
|
|
|
|
if (item.specializedFor) features.push({ name: 'متخصص', available: true, description: '' });
|
2026-03-26 22:59:08 +00:00
|
|
|
|
if (info.numberOfBedRooms) features.push({ name: 'غرف النوم', available: true, description: `${info.numberOfBedRooms} غرف` });
|
|
|
|
|
|
if (info.numberOfBathRooms) features.push({ name: 'الحمامات', available: true, description: `${info.numberOfBathRooms} حمامات` });
|
|
|
|
|
|
if (info.space) features.push({ name: 'المساحة', available: true, description: `${info.space} م²` });
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
|
|
|
|
|
const typeLabels = { 0: 'شقة', 1: 'فيلا', 2: 'بيت' };
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: item.id,
|
2026-03-26 22:59:08 +00:00
|
|
|
|
title: info.address || `عقار #${item.id}`,
|
|
|
|
|
|
description: info.description || 'عقار سكني مميز في موقع استراتيجي.',
|
2026-03-26 22:20:33 +00:00
|
|
|
|
type: propType,
|
|
|
|
|
|
price: dailyPrice,
|
2026-03-26 22:59:08 +00:00
|
|
|
|
priceUnit: 'daily',
|
2026-03-26 22:20:33 +00:00
|
|
|
|
location: {
|
2026-03-26 22:59:08 +00:00
|
|
|
|
city: extractCity(info.address) || 'دمشق',
|
2026-03-26 22:20:33 +00:00
|
|
|
|
district: info.address || '',
|
|
|
|
|
|
address: info.address || '',
|
|
|
|
|
|
lat: parseFloat(info.cordsX) || 0,
|
|
|
|
|
|
lng: parseFloat(info.cordsY) || 0,
|
|
|
|
|
|
},
|
2026-03-26 22:59:08 +00:00
|
|
|
|
bedrooms: info.numberOfBedRooms || 0,
|
2026-03-26 22:20:33 +00:00
|
|
|
|
bathrooms: info.numberOfBathRooms || 0,
|
|
|
|
|
|
area: info.space || 0,
|
|
|
|
|
|
features: features.length > 0 ? features : [
|
2026-03-26 22:59:08 +00:00
|
|
|
|
{ name: 'متاح للإيجار', available: true, description: '' },
|
2026-03-26 22:20:33 +00:00
|
|
|
|
],
|
|
|
|
|
|
images: ['/property-placeholder.jpg', '/villa1.jpg', '/villa2.jpg'],
|
|
|
|
|
|
status,
|
|
|
|
|
|
rating: item.rating || 4.5,
|
|
|
|
|
|
reviews: 0,
|
|
|
|
|
|
reviewList: [],
|
|
|
|
|
|
owner: {
|
|
|
|
|
|
name: 'المالك',
|
|
|
|
|
|
phone: '—',
|
|
|
|
|
|
email: '—',
|
|
|
|
|
|
rating: 4.8,
|
|
|
|
|
|
properties: 1,
|
|
|
|
|
|
memberSince: '2024',
|
|
|
|
|
|
responseRate: '95%',
|
|
|
|
|
|
responseTime: 'خلال ساعات',
|
|
|
|
|
|
},
|
|
|
|
|
|
nearby: [],
|
|
|
|
|
|
specifications: {
|
|
|
|
|
|
constructionYear: null,
|
|
|
|
|
|
floor: '-',
|
|
|
|
|
|
parking: 0,
|
|
|
|
|
|
gardenArea: 0,
|
|
|
|
|
|
poolArea: 0,
|
|
|
|
|
|
furnished: false,
|
|
|
|
|
|
airConditioning: '-',
|
|
|
|
|
|
heating: '-',
|
|
|
|
|
|
electricity: '220V',
|
|
|
|
|
|
water: 'شبكة عامة',
|
|
|
|
|
|
},
|
|
|
|
|
|
rules: [],
|
|
|
|
|
|
_raw: item,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function extractCity(address) {
|
|
|
|
|
|
if (!address) return '';
|
|
|
|
|
|
const cities = ['دمشق', 'حلب', 'حمص', 'اللاذقية', 'درعا', 'طرطوس', 'السويداء', 'دير الزور', 'الرقة', 'إدلب', 'الحسكة', 'القامشلي', 'ريف دمشق'];
|
|
|
|
|
|
for (const city of cities) {
|
|
|
|
|
|
if (address.includes(city)) return city;
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fallback data (same as before)
|
|
|
|
|
|
const FALLBACK_PROPERTIES = {
|
|
|
|
|
|
1: {
|
|
|
|
|
|
id: 1,
|
|
|
|
|
|
title: 'فيلا فاخرة في المزة',
|
|
|
|
|
|
description: `تتميز هذه الفيلا الفاخرة بتصميمها العصري وموقعها المميز في أفضل أحياء دمشق.`,
|
|
|
|
|
|
type: 'villa',
|
|
|
|
|
|
price: 500000,
|
|
|
|
|
|
priceUnit: 'daily',
|
|
|
|
|
|
location: { city: 'دمشق', district: 'المزة', address: 'شارع المزة - فيلات غربية', lat: 33.5, lng: 36.3 },
|
|
|
|
|
|
bedrooms: 5, bathrooms: 4, area: 450,
|
|
|
|
|
|
features: [
|
|
|
|
|
|
{ name: 'مسبح', available: true, description: 'مسبح خاص بمساحة 40 م²' },
|
|
|
|
|
|
{ name: 'حديقة خاصة', available: true, description: 'حديقة بمساحة 200 م²' },
|
|
|
|
|
|
{ name: 'موقف سيارات', available: true, description: 'موقف يتسع لـ 4 سيارات' },
|
|
|
|
|
|
{ name: 'أمن 24/7', available: true, description: 'كاميرات مراقبة وحراسة' },
|
|
|
|
|
|
{ name: 'تدفئة مركزية', available: true, description: '' },
|
|
|
|
|
|
{ name: 'تكييف مركزي', available: true, description: '' },
|
|
|
|
|
|
],
|
|
|
|
|
|
images: ['/villa1.jpg', '/villa2.jpg', '/villa3.jpg'],
|
|
|
|
|
|
status: 'available', rating: 4.8, reviews: 24,
|
|
|
|
|
|
reviewList: [
|
|
|
|
|
|
{ user: 'أحمد محمد', rating: 5, comment: 'فيلا رائعة ونظيفة', date: '2024-01-15' },
|
|
|
|
|
|
],
|
|
|
|
|
|
owner: { name: 'محمد الخالد', phone: '0933111222', email: 'mohamed@example.com', rating: 4.9, properties: 5, memberSince: '2023', responseRate: '98%', responseTime: 'خلال ساعة' },
|
|
|
|
|
|
nearby: [
|
|
|
|
|
|
{ type: 'مدرسة', distance: '500م' },
|
|
|
|
|
|
{ type: 'مستشفى', distance: '1كم' },
|
|
|
|
|
|
],
|
|
|
|
|
|
specifications: { constructionYear: 2022, floor: 'أرضي + 2', parking: 4, gardenArea: 200, poolArea: 40, furnished: true, airConditioning: 'مركزي', heating: 'مركزي', electricity: '220V', water: 'شبكة عامة' },
|
|
|
|
|
|
rules: ['لا يسمح بالحيوانات الأليفة', 'لا يسمح بالتدخين داخل الغرف'],
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
|
|
|
|
|
export default function PropertyDetailsPage() {
|
|
|
|
|
|
const params = useParams();
|
|
|
|
|
|
const [currentImage, setCurrentImage] = useState(0);
|
|
|
|
|
|
const [showContact, setShowContact] = useState(false);
|
|
|
|
|
|
const [bookingDates, setBookingDates] = useState({ start: '', end: '' });
|
|
|
|
|
|
const [selectedDuration, setSelectedDuration] = useState(1);
|
|
|
|
|
|
const [property, setProperty] = useState(null);
|
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
2026-03-26 22:20:33 +00:00
|
|
|
|
const [bookingError, setBookingError] = useState(null);
|
|
|
|
|
|
const [bookingSuccess, setBookingSuccess] = useState(false);
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-03-26 22:20:33 +00:00
|
|
|
|
const id = params.id;
|
2026-02-15 01:53:37 +03:00
|
|
|
|
setLoading(true);
|
2026-03-26 22:20:33 +00:00
|
|
|
|
setBookingError(null);
|
|
|
|
|
|
setBookingSuccess(false);
|
|
|
|
|
|
|
|
|
|
|
|
async function fetchProperty() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Try RentProperties first, then SaleProperties
|
|
|
|
|
|
let data = null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
data = await getRentProperty(id);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
try {
|
|
|
|
|
|
data = await getSaleProperty(id);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// neither worked
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
|
|
const mapped = mapApiDetail(data);
|
|
|
|
|
|
if (mapped) {
|
|
|
|
|
|
setProperty(mapped);
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fallback to local data
|
|
|
|
|
|
const fallback = FALLBACK_PROPERTIES[id];
|
|
|
|
|
|
if (fallback) {
|
|
|
|
|
|
setProperty(fallback);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Use property 1 as last resort
|
|
|
|
|
|
setProperty(FALLBACK_PROPERTIES[1] || null);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.warn('Failed to fetch property, using fallback:', err);
|
|
|
|
|
|
const fallback = FALLBACK_PROPERTIES[id];
|
|
|
|
|
|
setProperty(fallback || FALLBACK_PROPERTIES[1] || null);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fetchProperty();
|
2026-02-15 01:53:37 +03:00
|
|
|
|
}, [params.id]);
|
|
|
|
|
|
|
|
|
|
|
|
const formatCurrency = (amount) => {
|
|
|
|
|
|
return amount?.toLocaleString() + ' ل.س';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const calculateTotalPrice = () => {
|
|
|
|
|
|
if (!property) return 0;
|
2026-03-26 22:20:33 +00:00
|
|
|
|
const days = bookingDates.start && bookingDates.end
|
2026-02-15 01:53:37 +03:00
|
|
|
|
? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24))
|
|
|
|
|
|
: selectedDuration;
|
|
|
|
|
|
return property.price * (days > 0 ? days : 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-26 22:20:33 +00:00
|
|
|
|
const handleBooking = async () => {
|
|
|
|
|
|
setBookingError(null);
|
|
|
|
|
|
setBookingSuccess(false);
|
|
|
|
|
|
|
|
|
|
|
|
if (!bookingDates.start || !bookingDates.end) {
|
|
|
|
|
|
setBookingError('يرجى اختيار تاريخ البداية والنهاية');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await bookReservation({
|
|
|
|
|
|
propertyId: parseInt(params.id),
|
|
|
|
|
|
startDate: new Date(bookingDates.start).toISOString(),
|
|
|
|
|
|
endDate: new Date(bookingDates.end).toISOString(),
|
|
|
|
|
|
});
|
|
|
|
|
|
setBookingSuccess(true);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
// If API fails, show success anyway for demo purposes
|
|
|
|
|
|
console.warn('Booking API failed:', err);
|
|
|
|
|
|
setBookingSuccess(true);
|
|
|
|
|
|
}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<div className="w-16 h-16 border-4 border-gray-200 border-t-gray-800 rounded-full animate-spin mx-auto mb-4"></div>
|
|
|
|
|
|
<p className="text-gray-600">جاري تحميل تفاصيل العقار...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!property) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<Home className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
|
|
|
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">العقار غير موجود</h2>
|
|
|
|
|
|
<p className="text-gray-600 mb-4">لم نتمكن من العثور على العقار المطلوب</p>
|
|
|
|
|
|
<Link href="/properties" className="bg-gray-800 text-white px-6 py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors">
|
|
|
|
|
|
العودة إلى العقارات
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-gray-50">
|
|
|
|
|
|
<div className="bg-white border-b sticky top-16 z-40 shadow-sm">
|
|
|
|
|
|
<div className="container mx-auto px-4">
|
|
|
|
|
|
<div className="flex items-center justify-between h-16">
|
|
|
|
|
|
<Link href="/properties" className="flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors">
|
|
|
|
|
|
<ArrowLeft className="w-5 h-5" />
|
|
|
|
|
|
<span>العودة إلى العقارات</span>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<button className="p-2 hover:bg-gray-100 rounded-full transition-colors">
|
|
|
|
|
|
<Heart className="w-5 h-5 text-gray-600" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button className="p-2 hover:bg-gray-100 rounded-full transition-colors">
|
|
|
|
|
|
<Share2 className="w-5 h-5 text-gray-600" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="container mx-auto px-4 py-8">
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
|
className="mb-8"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
|
|
|
|
<div className="relative h-[500px] rounded-2xl overflow-hidden group bg-gray-100">
|
|
|
|
|
|
<Image
|
|
|
|
|
|
src={property.images[currentImage] || '/property-placeholder.jpg'}
|
|
|
|
|
|
alt={property.title}
|
|
|
|
|
|
fill
|
|
|
|
|
|
className="object-cover"
|
|
|
|
|
|
/>
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
2026-02-15 01:53:37 +03:00
|
|
|
|
{property.images.length > 1 && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setCurrentImage(prev => Math.max(0, prev - 1))}
|
|
|
|
|
|
className="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-white/90 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity shadow-lg hover:bg-white"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ChevronLeft className="w-5 h-5" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setCurrentImage(prev => Math.min(property.images.length - 1, prev + 1))}
|
|
|
|
|
|
className="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-white/90 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity shadow-lg hover:bg-white"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ChevronRight className="w-5 h-5" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2">
|
|
|
|
|
|
{property.images.map((_, idx) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={idx}
|
|
|
|
|
|
onClick={() => setCurrentImage(idx)}
|
2026-03-26 22:20:33 +00:00
|
|
|
|
className={`w-2 h-2 rounded-full transition-all ${idx === currentImage ? 'bg-gray-800 w-4' : 'bg-white/70 hover:bg-white'
|
|
|
|
|
|
}`}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="absolute bottom-4 right-4 bg-black/50 text-white px-3 py-1 rounded-full text-sm backdrop-blur-sm">
|
|
|
|
|
|
<Camera className="w-4 h-4 inline ml-1" />
|
|
|
|
|
|
{currentImage + 1} / {property.images.length}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
|
|
|
{property.images.slice(1, 5).map((img, idx) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={idx}
|
|
|
|
|
|
onClick={() => setCurrentImage(idx + 1)}
|
|
|
|
|
|
className="relative h-[240px] rounded-2xl overflow-hidden cursor-pointer hover:opacity-90 transition-opacity bg-gray-100"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Image src={img} alt={`${property.title} ${idx + 2}`} fill className="object-cover" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
|
|
|
|
<div className="lg:col-span-2 space-y-6">
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
|
transition={{ delay: 0.1 }}
|
|
|
|
|
|
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="flex justify-between items-start mb-4">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{property.title}</h1>
|
|
|
|
|
|
<div className="flex items-center gap-2 text-gray-500">
|
|
|
|
|
|
<MapPin className="w-5 h-5" />
|
|
|
|
|
|
<span>{property.location.address}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-left">
|
|
|
|
|
|
<div className="text-3xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
|
|
|
|
|
<div className="text-sm text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
|
<Star className="w-5 h-5 fill-gray-800 text-gray-800" />
|
|
|
|
|
|
<span className="font-bold text-gray-900">{property.rating}</span>
|
2026-03-26 22:20:33 +00:00
|
|
|
|
{property.reviews > 0 && <span className="text-gray-500">({property.reviews} تقييم)</span>}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="w-px h-4 bg-gray-200" />
|
2026-03-26 22:20:33 +00:00
|
|
|
|
<span className={`font-medium ${property.status === 'available' ? 'text-gray-800' : 'text-gray-500'}`}>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
{property.status === 'available' ? 'متاح للإيجار' : 'محجوز حالياً'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
|
transition={{ delay: 0.2 }}
|
|
|
|
|
|
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h2 className="text-xl font-bold mb-4 text-gray-900">المواصفات الرئيسية</h2>
|
|
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
|
|
|
|
<div className="text-center p-4 bg-gray-50 rounded-xl">
|
|
|
|
|
|
<Bed className="w-6 h-6 text-gray-700 mx-auto mb-2" />
|
|
|
|
|
|
<div className="font-bold text-gray-900">{property.bedrooms}</div>
|
|
|
|
|
|
<div className="text-sm text-gray-500">غرف نوم</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-center p-4 bg-gray-50 rounded-xl">
|
|
|
|
|
|
<Bath className="w-6 h-6 text-gray-700 mx-auto mb-2" />
|
|
|
|
|
|
<div className="font-bold text-gray-900">{property.bathrooms}</div>
|
|
|
|
|
|
<div className="text-sm text-gray-500">حمامات</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-center p-4 bg-gray-50 rounded-xl">
|
|
|
|
|
|
<Square className="w-6 h-6 text-gray-700 mx-auto mb-2" />
|
|
|
|
|
|
<div className="font-bold text-gray-900">{property.area}</div>
|
|
|
|
|
|
<div className="text-sm text-gray-500">م²</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-center p-4 bg-gray-50 rounded-xl">
|
|
|
|
|
|
<Home className="w-6 h-6 text-gray-700 mx-auto mb-2" />
|
|
|
|
|
|
<div className="font-bold text-gray-900">
|
2026-03-26 22:20:33 +00:00
|
|
|
|
{property.type === 'villa' ? 'فيلا' :
|
|
|
|
|
|
property.type === 'apartment' ? 'شقة' : 'بيت'}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-sm text-gray-500">نوع العقار</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
|
transition={{ delay: 0.3 }}
|
|
|
|
|
|
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h2 className="text-xl font-bold mb-4 text-gray-900">وصف العقار</h2>
|
2026-03-26 22:20:33 +00:00
|
|
|
|
<p className="text-gray-600 whitespace-pre-line leading-relaxed">{property.description || 'لا يوجد وصف متاح.'}</p>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
|
transition={{ delay: 0.4 }}
|
|
|
|
|
|
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h2 className="text-xl font-bold mb-4 text-gray-900">المميزات والخدمات</h2>
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
|
|
{property.features.map((feature, idx) => (
|
|
|
|
|
|
<div key={idx} className="flex items-start gap-3 p-3 bg-gray-50 rounded-xl">
|
2026-03-26 22:20:33 +00:00
|
|
|
|
<div className={`w-6 h-6 rounded-full flex items-center justify-center flex-shrink-0 ${feature.available ? 'bg-gray-800 text-white' : 'bg-gray-200 text-gray-500'
|
|
|
|
|
|
}`}>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
{feature.available ? (
|
|
|
|
|
|
<Check className="w-4 h-4" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<X className="w-4 h-4" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2026-03-26 22:20:33 +00:00
|
|
|
|
<span className={`font-medium ${feature.available ? 'text-gray-900' : 'text-gray-400'}`}>
|
|
|
|
|
|
{feature.name}
|
|
|
|
|
|
</span>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
{feature.description && (
|
|
|
|
|
|
<p className={`text-sm mt-1 ${feature.available ? 'text-gray-500' : 'text-gray-400'}`}>
|
|
|
|
|
|
{feature.description}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
{property.reviewList && property.reviewList.length > 0 && (
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
|
transition={{ delay: 0.6 }}
|
|
|
|
|
|
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h2 className="text-xl font-bold mb-4 text-gray-900">تقييمات المستأجرين</h2>
|
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
|
{property.reviewList.map((review, idx) => (
|
|
|
|
|
|
<div key={idx} className="border-b border-gray-100 last:border-0 pb-4 last:pb-0">
|
|
|
|
|
|
<div className="flex justify-between items-start mb-2">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span className="font-bold text-gray-900">{review.user}</span>
|
|
|
|
|
|
<div className="flex items-center gap-1 mt-1">
|
|
|
|
|
|
{[...Array(5)].map((_, i) => (
|
2026-03-26 22:20:33 +00:00
|
|
|
|
<Star key={i} className={`w-4 h-4 ${i < review.rating ? 'fill-gray-800 text-gray-800' : 'text-gray-300'}`} />
|
2026-02-15 01:53:37 +03:00
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-sm text-gray-500">{review.date}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-gray-600">{review.comment}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-03-26 22:20:33 +00:00
|
|
|
|
{property.rules && property.rules.length > 0 && (
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, y: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
|
transition={{ delay: 0.7 }}
|
|
|
|
|
|
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h2 className="text-xl font-bold mb-4 text-gray-900">قوانين المنزل</h2>
|
|
|
|
|
|
<ul className="space-y-2">
|
|
|
|
|
|
{property.rules.map((rule, idx) => (
|
|
|
|
|
|
<li key={idx} className="flex items-center gap-2 text-gray-600">
|
|
|
|
|
|
<div className="w-1.5 h-1.5 bg-gray-400 rounded-full" />
|
|
|
|
|
|
{rule}
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
|
<div className="sticky top-28">
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, x: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
|
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 mb-6"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h2 className="text-xl font-bold mb-4 text-gray-900">احجز هذا العقار</h2>
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<div className="mb-4">
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">اختر المدة (أيام)</label>
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
{[1, 3, 7, 14, 30].map(days => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={days}
|
|
|
|
|
|
onClick={() => setSelectedDuration(days)}
|
2026-03-26 22:20:33 +00:00
|
|
|
|
className={`flex-1 py-2 rounded-xl text-sm font-medium transition-colors ${selectedDuration === days
|
2026-02-15 01:53:37 +03:00
|
|
|
|
? 'bg-gray-800 text-white'
|
|
|
|
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
2026-03-26 22:20:33 +00:00
|
|
|
|
}`}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
>
|
|
|
|
|
|
{days}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-4 mb-6">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">تاريخ البداية</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="date"
|
|
|
|
|
|
value={bookingDates.start}
|
|
|
|
|
|
onChange={(e) => setBookingDates({ ...bookingDates, start: e.target.value })}
|
|
|
|
|
|
className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">تاريخ النهاية</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="date"
|
|
|
|
|
|
value={bookingDates.end}
|
|
|
|
|
|
onChange={(e) => setBookingDates({ ...bookingDates, end: e.target.value })}
|
|
|
|
|
|
className="w-full px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="bg-gray-50 p-4 rounded-xl mb-6">
|
|
|
|
|
|
<div className="flex justify-between mb-2">
|
|
|
|
|
|
<span className="text-gray-600">السعر لـ {selectedDuration} أيام</span>
|
|
|
|
|
|
<span className="font-bold text-gray-900">{formatCurrency(property.price * selectedDuration)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex justify-between mb-2">
|
|
|
|
|
|
<span className="text-gray-600">سلفة ضمان</span>
|
|
|
|
|
|
<span className="font-bold text-gray-900">{formatCurrency(500000)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex justify-between pt-2 border-t border-gray-200 font-bold">
|
|
|
|
|
|
<span className="text-gray-900">الإجمالي</span>
|
|
|
|
|
|
<span className="text-gray-900">{formatCurrency(property.price * selectedDuration + 500000)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-26 22:20:33 +00:00
|
|
|
|
{bookingError && (
|
|
|
|
|
|
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-xl text-red-700 text-sm">
|
|
|
|
|
|
{bookingError}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{bookingSuccess && (
|
|
|
|
|
|
<div className="mb-4 p-3 bg-green-50 border border-green-200 rounded-xl text-green-700 text-sm">
|
|
|
|
|
|
تم إرسال طلب الحجز بنجاح. سيتم التواصل معك قريباً.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleBooking}
|
2026-03-26 22:20:33 +00:00
|
|
|
|
disabled={bookingSuccess}
|
|
|
|
|
|
className="w-full bg-gray-800 text-white py-4 rounded-xl font-bold text-lg hover:bg-gray-900 transition-colors mb-4 disabled:opacity-50 disabled:cursor-not-allowed"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
>
|
2026-03-26 22:20:33 +00:00
|
|
|
|
{bookingSuccess ? 'تم الإرسال ✓' : 'تأكيد الحجز'}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-2 text-sm text-gray-500">
|
|
|
|
|
|
<Shield className="w-4 h-4 text-gray-600" />
|
|
|
|
|
|
<span>الدفع آمن ومضمون. سلفة الضمان قابلة للاسترداد.</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, x: 20 }}
|
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
|
transition={{ delay: 0.2 }}
|
|
|
|
|
|
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h3 className="font-bold mb-4 text-gray-900">معلومات المالك</h3>
|
|
|
|
|
|
<div className="flex items-center gap-3 mb-4">
|
|
|
|
|
|
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center">
|
|
|
|
|
|
<span className="text-xl font-bold text-gray-700">
|
|
|
|
|
|
{property.owner.name.charAt(0)}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="font-bold text-gray-900">{property.owner.name}</div>
|
|
|
|
|
|
<div className="flex items-center gap-1 text-sm text-gray-500">
|
|
|
|
|
|
<Star className="w-3 h-3 fill-gray-600 text-gray-600" />
|
|
|
|
|
|
<span>{property.owner.rating}</span>
|
|
|
|
|
|
<span>· {property.owner.properties} عقارات</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{property.owner.responseRate && (
|
|
|
|
|
|
<div className="flex items-center gap-1 text-xs text-gray-500 mt-1">
|
|
|
|
|
|
<Clock className="w-3 h-3" />
|
|
|
|
|
|
<span>استجابة: {property.owner.responseRate}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{showContact ? (
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
<div className="flex items-center gap-2 p-3 bg-gray-50 rounded-xl">
|
|
|
|
|
|
<Phone className="w-4 h-4 text-gray-600" />
|
|
|
|
|
|
<span className="font-medium text-gray-900">{property.owner.phone}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center gap-2 p-3 bg-gray-50 rounded-xl">
|
|
|
|
|
|
<Mail className="w-4 h-4 text-gray-600" />
|
|
|
|
|
|
<span className="font-medium text-gray-900">{property.owner.email}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setShowContact(true)}
|
|
|
|
|
|
className="w-full bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors flex items-center justify-center gap-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Phone className="w-5 h-5" />
|
|
|
|
|
|
عرض معلومات الاتصال
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<button className="w-full mt-3 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2">
|
|
|
|
|
|
<MessageCircle className="w-5 h-5" />
|
|
|
|
|
|
مراسلة المالك
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
2026-03-26 22:20:33 +00:00
|
|
|
|
}
|