713 lines
27 KiB
JavaScript
713 lines
27 KiB
JavaScript
|
|
'use client';
|
|||
|
|
|
|||
|
|
import { useState } from 'react';
|
|||
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|||
|
|
import { useTranslation } from 'react-i18next';
|
|||
|
|
import {
|
|||
|
|
Search,
|
|||
|
|
MapPin,
|
|||
|
|
Bed,
|
|||
|
|
Bath,
|
|||
|
|
Square,
|
|||
|
|
DollarSign,
|
|||
|
|
Filter,
|
|||
|
|
Grid3x3,
|
|||
|
|
List,
|
|||
|
|
Heart,
|
|||
|
|
Share2,
|
|||
|
|
ChevronDown,
|
|||
|
|
Star,
|
|||
|
|
Camera,
|
|||
|
|
Home,
|
|||
|
|
Building2,
|
|||
|
|
Trees,
|
|||
|
|
Waves,
|
|||
|
|
Warehouse,
|
|||
|
|
Sparkles,
|
|||
|
|
Shield,
|
|||
|
|
Calendar,
|
|||
|
|
Phone,
|
|||
|
|
Mail,
|
|||
|
|
MessageCircle
|
|||
|
|
} from 'lucide-react';
|
|||
|
|
import Image from 'next/image';
|
|||
|
|
import Link from 'next/link';
|
|||
|
|
|
|||
|
|
const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
|||
|
|
const [isFavorite, setIsFavorite] = useState(false);
|
|||
|
|
const [currentImage, setCurrentImage] = useState(0);
|
|||
|
|
|
|||
|
|
const formatCurrency = (amount) => {
|
|||
|
|
return amount?.toLocaleString() + ' ل.س';
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getPropertyTypeIcon = (type) => {
|
|||
|
|
switch(type) {
|
|||
|
|
case 'villa': return <Home className="w-4 h-4" />;
|
|||
|
|
case 'apartment': return <Building2 className="w-4 h-4" />;
|
|||
|
|
case 'house': return <Home className="w-4 h-4" />;
|
|||
|
|
case 'studio': return <Building2 className="w-4 h-4" />;
|
|||
|
|
default: return <Home className="w-4 h-4" />;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getPropertyTypeLabel = (type) => {
|
|||
|
|
switch(type) {
|
|||
|
|
case 'villa': return 'فيلا';
|
|||
|
|
case 'apartment': return 'شقة';
|
|||
|
|
case 'house': return 'بيت';
|
|||
|
|
case 'studio': return 'استوديو';
|
|||
|
|
default: return type;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (viewMode === 'list') {
|
|||
|
|
return (
|
|||
|
|
<motion.div
|
|||
|
|
initial={{ opacity: 0, y: 20 }}
|
|||
|
|
animate={{ opacity: 1, y: 0 }}
|
|||
|
|
className="bg-white rounded-2xl shadow-sm hover:shadow-md transition-all duration-300 overflow-hidden border border-gray-100"
|
|||
|
|
>
|
|||
|
|
<div className="flex flex-col md:flex-row">
|
|||
|
|
<div className="md:w-1/3 relative h-64 md:h-auto bg-gray-100">
|
|||
|
|
<Image
|
|||
|
|
src={property.images[currentImage] || '/property-placeholder.jpg'}
|
|||
|
|
alt={property.title}
|
|||
|
|
fill
|
|||
|
|
className="object-cover"
|
|||
|
|
/>
|
|||
|
|
{property.images.length > 1 && (
|
|||
|
|
<div className="absolute bottom-2 left-2 right-2 flex justify-center gap-1">
|
|||
|
|
{property.images.map((_, idx) => (
|
|||
|
|
<button
|
|||
|
|
key={idx}
|
|||
|
|
onClick={() => setCurrentImage(idx)}
|
|||
|
|
className={`w-1.5 h-1.5 rounded-full transition-all ${
|
|||
|
|
idx === currentImage ? 'bg-gray-800 w-3' : 'bg-white/70'
|
|||
|
|
}`}
|
|||
|
|
/>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
<div className="absolute top-2 right-2 flex gap-2">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setIsFavorite(!isFavorite)}
|
|||
|
|
className="w-8 h-8 bg-white/90 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white transition-colors shadow-sm"
|
|||
|
|
>
|
|||
|
|
<Heart className={`w-4 h-4 ${isFavorite ? 'fill-red-500 text-red-500' : 'text-gray-600'}`} />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
{property.isNew && (
|
|||
|
|
<div className="absolute top-2 left-2 bg-gray-800 text-white px-2 py-1 rounded-lg text-xs font-medium">
|
|||
|
|
جديد
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="md:w-2/3 p-6">
|
|||
|
|
<div className="flex justify-between items-start mb-3">
|
|||
|
|
<div>
|
|||
|
|
<div className="flex items-center gap-2 mb-2">
|
|||
|
|
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs font-medium flex items-center gap-1">
|
|||
|
|
{getPropertyTypeIcon(property.type)}
|
|||
|
|
{getPropertyTypeLabel(property.type)}
|
|||
|
|
</span>
|
|||
|
|
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${
|
|||
|
|
property.status === 'available'
|
|||
|
|
? 'bg-gray-800 text-white'
|
|||
|
|
: 'bg-gray-200 text-gray-600'
|
|||
|
|
}`}>
|
|||
|
|
{property.status === 'available' ? 'متاح' : 'محجوز'}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<h3 className="text-xl font-bold text-gray-900 mb-1">{property.title}</h3>
|
|||
|
|
<div className="flex items-center gap-1 text-gray-500 text-sm mb-3">
|
|||
|
|
<MapPin className="w-4 h-4" />
|
|||
|
|
{property.location.city}، {property.location.district}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-left">
|
|||
|
|
<div className="text-2xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
|||
|
|
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex flex-wrap gap-4 mb-4">
|
|||
|
|
<div className="flex items-center gap-1 text-gray-600">
|
|||
|
|
<Bed className="w-4 h-4" />
|
|||
|
|
<span>{property.bedrooms} غرف</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center gap-1 text-gray-600">
|
|||
|
|
<Bath className="w-4 h-4" />
|
|||
|
|
<span>{property.bathrooms} حمامات</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center gap-1 text-gray-600">
|
|||
|
|
<Square className="w-4 h-4" />
|
|||
|
|
<span>{property.area} م²</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<p className="text-gray-600 text-sm mb-4 line-clamp-2">
|
|||
|
|
{property.description}
|
|||
|
|
</p>
|
|||
|
|
|
|||
|
|
<div className="flex flex-wrap gap-2 mb-4">
|
|||
|
|
{property.features.slice(0, 4).map((feature, idx) => (
|
|||
|
|
<span key={idx} className="px-2 py-1 bg-gray-100 text-gray-600 rounded-lg text-xs">
|
|||
|
|
{feature}
|
|||
|
|
</span>
|
|||
|
|
))}
|
|||
|
|
{property.features.length > 4 && (
|
|||
|
|
<span className="px-2 py-1 bg-gray-100 text-gray-600 rounded-lg text-xs">
|
|||
|
|
+{property.features.length - 4}
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex gap-3">
|
|||
|
|
<Link
|
|||
|
|
href={`/property/${property.id}`}
|
|||
|
|
className="flex-1 bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center"
|
|||
|
|
>
|
|||
|
|
عرض التفاصيل
|
|||
|
|
</Link>
|
|||
|
|
<button className="px-4 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center gap-2">
|
|||
|
|
<Phone className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</motion.div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<motion.div
|
|||
|
|
initial={{ opacity: 0, y: 20 }}
|
|||
|
|
animate={{ opacity: 1, y: 0 }}
|
|||
|
|
className="bg-white rounded-2xl shadow-sm hover:shadow-md transition-all duration-300 overflow-hidden border border-gray-100"
|
|||
|
|
>
|
|||
|
|
<div className="relative h-56 bg-gray-100">
|
|||
|
|
<Image
|
|||
|
|
src={property.images[currentImage] || '/property-placeholder.jpg'}
|
|||
|
|
alt={property.title}
|
|||
|
|
fill
|
|||
|
|
className="object-cover"
|
|||
|
|
/>
|
|||
|
|
{property.images.length > 1 && (
|
|||
|
|
<div className="absolute bottom-2 left-2 right-2 flex justify-center gap-1">
|
|||
|
|
{property.images.map((_, idx) => (
|
|||
|
|
<button
|
|||
|
|
key={idx}
|
|||
|
|
onClick={() => setCurrentImage(idx)}
|
|||
|
|
className={`w-1.5 h-1.5 rounded-full transition-all ${
|
|||
|
|
idx === currentImage ? 'bg-gray-800 w-3' : 'bg-white/70'
|
|||
|
|
}`}
|
|||
|
|
/>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
<div className="absolute top-2 right-2 flex gap-2">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setIsFavorite(!isFavorite)}
|
|||
|
|
className="w-8 h-8 bg-white/90 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white transition-colors shadow-sm"
|
|||
|
|
>
|
|||
|
|
<Heart className={`w-4 h-4 ${isFavorite ? 'fill-red-500 text-red-500' : 'text-gray-600'}`} />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
{property.isNew && (
|
|||
|
|
<div className="absolute top-2 left-2 bg-gray-800 text-white px-2 py-1 rounded-lg text-xs font-medium">
|
|||
|
|
جديد
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="p-5">
|
|||
|
|
<div className="flex justify-between items-start mb-3">
|
|||
|
|
<div>
|
|||
|
|
<div className="flex items-center gap-2 mb-2">
|
|||
|
|
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs font-medium flex items-center gap-1">
|
|||
|
|
{getPropertyTypeIcon(property.type)}
|
|||
|
|
{getPropertyTypeLabel(property.type)}
|
|||
|
|
</span>
|
|||
|
|
{property.status === 'available' && (
|
|||
|
|
<span className="px-2 py-1 bg-gray-800 text-white rounded-lg text-xs font-medium">
|
|||
|
|
متاح
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<h3 className="font-bold text-gray-900 mb-1 line-clamp-1">{property.title}</h3>
|
|||
|
|
<div className="flex items-center gap-1 text-gray-500 text-xs mb-2">
|
|||
|
|
<MapPin className="w-3 h-3" />
|
|||
|
|
<span className="line-clamp-1">{property.location.city}، {property.location.district}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="text-left">
|
|||
|
|
<div className="text-xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
|||
|
|
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex justify-between items-center mb-4">
|
|||
|
|
<div className="flex items-center gap-3 text-gray-600 text-sm">
|
|||
|
|
<div className="flex items-center gap-1">
|
|||
|
|
<Bed className="w-4 h-4" />
|
|||
|
|
<span>{property.bedrooms}</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center gap-1">
|
|||
|
|
<Bath className="w-4 h-4" />
|
|||
|
|
<span>{property.bathrooms}</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center gap-1">
|
|||
|
|
<Square className="w-4 h-4" />
|
|||
|
|
<span>{property.area}م²</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-center gap-1">
|
|||
|
|
<Star className="w-4 h-4 fill-gray-400 text-gray-400" />
|
|||
|
|
<span className="text-sm font-medium text-gray-700">{property.rating || 4.5}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex flex-wrap gap-2 mb-4">
|
|||
|
|
{property.features.slice(0, 3).map((feature, idx) => (
|
|||
|
|
<span key={idx} className="px-2 py-1 bg-gray-100 text-gray-600 rounded-lg text-xs">
|
|||
|
|
{feature}
|
|||
|
|
</span>
|
|||
|
|
))}
|
|||
|
|
{property.features.length > 3 && (
|
|||
|
|
<span className="px-2 py-1 bg-gray-100 text-gray-600 rounded-lg text-xs">
|
|||
|
|
+{property.features.length - 3}
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<Link
|
|||
|
|
href={`/property/${property.id}`}
|
|||
|
|
className="block w-full bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center"
|
|||
|
|
>
|
|||
|
|
عرض التفاصيل
|
|||
|
|
</Link>
|
|||
|
|
</div>
|
|||
|
|
</motion.div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const FilterBar = ({ filters, onFilterChange }) => {
|
|||
|
|
const [showFilters, setShowFilters] = useState(false);
|
|||
|
|
|
|||
|
|
const propertyTypes = [
|
|||
|
|
{ id: 'all', label: 'الكل' },
|
|||
|
|
{ id: 'apartment', label: 'شقة', icon: Building2 },
|
|||
|
|
{ id: 'villa', label: 'فيلا', icon: Home },
|
|||
|
|
{ id: 'house', label: 'بيت', icon: Home },
|
|||
|
|
{ id: 'studio', label: 'استوديو', icon: Building2 }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const priceRanges = [
|
|||
|
|
{ id: 'all', label: 'جميع الأسعار' },
|
|||
|
|
{ id: '0-500000', label: 'أقل من 500,000' },
|
|||
|
|
{ id: '500000-1000000', label: '500,000 - 1,000,000' },
|
|||
|
|
{ id: '1000000-2000000', label: '1,000,000 - 2,000,000' },
|
|||
|
|
{ id: '2000000-5000000', label: '2,000,000 - 5,000,000' },
|
|||
|
|
{ id: '5000000+', label: 'أكثر من 5,000,000' }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const cities = [
|
|||
|
|
{ id: 'all', label: 'جميع المدن' },
|
|||
|
|
{ id: 'damascus', label: 'دمشق' },
|
|||
|
|
{ id: 'aleppo', label: 'حلب' },
|
|||
|
|
{ id: 'homs', label: 'حمص' },
|
|||
|
|
{ id: 'latakia', label: 'اللاذقية' },
|
|||
|
|
{ id: 'daraa', label: 'درعا' }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
|||
|
|
<div className="flex flex-col md:flex-row gap-3 mb-4">
|
|||
|
|
<div className="flex-1 relative">
|
|||
|
|
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
placeholder="ابحث عن عقار..."
|
|||
|
|
className="w-full pr-12 px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 transition-all"
|
|||
|
|
value={filters.search}
|
|||
|
|
onChange={(e) => onFilterChange({ ...filters, search: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<button
|
|||
|
|
onClick={() => setShowFilters(!showFilters)}
|
|||
|
|
className="px-6 py-3 bg-gray-100 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center gap-2 text-gray-700"
|
|||
|
|
>
|
|||
|
|
<Filter className="w-5 h-5" />
|
|||
|
|
فلاتر متقدمة
|
|||
|
|
<ChevronDown className={`w-4 h-4 transition-transform ${showFilters ? 'rotate-180' : ''}`} />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<AnimatePresence>
|
|||
|
|
{showFilters && (
|
|||
|
|
<motion.div
|
|||
|
|
initial={{ height: 0, opacity: 0 }}
|
|||
|
|
animate={{ height: 'auto', opacity: 1 }}
|
|||
|
|
exit={{ height: 0, opacity: 0 }}
|
|||
|
|
className="overflow-hidden"
|
|||
|
|
>
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4 border-t border-gray-100">
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">نوع العقار</label>
|
|||
|
|
<div className="flex flex-wrap gap-2">
|
|||
|
|
{propertyTypes.map((type) => {
|
|||
|
|
const Icon = type.icon;
|
|||
|
|
return (
|
|||
|
|
<button
|
|||
|
|
key={type.id}
|
|||
|
|
onClick={() => onFilterChange({ ...filters, propertyType: type.id })}
|
|||
|
|
className={`px-3 py-2 rounded-xl text-sm font-medium transition-all flex items-center gap-1 ${
|
|||
|
|
filters.propertyType === type.id
|
|||
|
|
? 'bg-gray-800 text-white'
|
|||
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{Icon && <Icon className="w-4 h-4" />}
|
|||
|
|
{type.label}
|
|||
|
|
</button>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">المدينة</label>
|
|||
|
|
<select
|
|||
|
|
value={filters.city}
|
|||
|
|
onChange={(e) => onFilterChange({ ...filters, city: e.target.value })}
|
|||
|
|
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
|||
|
|
>
|
|||
|
|
{cities.map((city) => (
|
|||
|
|
<option key={city.id} value={city.id}>{city.label}</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">نطاق السعر</label>
|
|||
|
|
<select
|
|||
|
|
value={filters.priceRange}
|
|||
|
|
onChange={(e) => onFilterChange({ ...filters, priceRange: e.target.value })}
|
|||
|
|
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
|||
|
|
>
|
|||
|
|
{priceRanges.map((range) => (
|
|||
|
|
<option key={range.id} value={range.id}>{range.label}</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">غرف النوم</label>
|
|||
|
|
<select
|
|||
|
|
value={filters.bedrooms}
|
|||
|
|
onChange={(e) => onFilterChange({ ...filters, bedrooms: e.target.value })}
|
|||
|
|
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
|||
|
|
>
|
|||
|
|
<option value="all">جميع الأعداد</option>
|
|||
|
|
<option value="1">1+</option>
|
|||
|
|
<option value="2">2+</option>
|
|||
|
|
<option value="3">3+</option>
|
|||
|
|
<option value="4">4+</option>
|
|||
|
|
<option value="5">5+</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">المساحة (م²)</label>
|
|||
|
|
<div className="flex gap-2">
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
placeholder="من"
|
|||
|
|
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
|||
|
|
value={filters.minArea}
|
|||
|
|
onChange={(e) => onFilterChange({ ...filters, minArea: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
placeholder="إلى"
|
|||
|
|
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
|||
|
|
value={filters.maxArea}
|
|||
|
|
onChange={(e) => onFilterChange({ ...filters, maxArea: e.target.value })}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">المميزات</label>
|
|||
|
|
<div className="flex flex-wrap gap-2">
|
|||
|
|
{['مسبح', 'حديقة', 'موقف سيارات', 'أمن', 'مصعد', 'تكييف'].map((feature) => (
|
|||
|
|
<button
|
|||
|
|
key={feature}
|
|||
|
|
onClick={() => {
|
|||
|
|
const newFeatures = filters.features.includes(feature)
|
|||
|
|
? filters.features.filter(f => f !== feature)
|
|||
|
|
: [...filters.features, feature];
|
|||
|
|
onFilterChange({ ...filters, features: newFeatures });
|
|||
|
|
}}
|
|||
|
|
className={`px-3 py-2 rounded-xl text-sm font-medium transition-all ${
|
|||
|
|
filters.features.includes(feature)
|
|||
|
|
? 'bg-gray-800 text-white'
|
|||
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{feature}
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex gap-3 mt-4 pt-4 border-t border-gray-100">
|
|||
|
|
<button
|
|||
|
|
onClick={() => onFilterChange({
|
|||
|
|
search: '',
|
|||
|
|
propertyType: 'all',
|
|||
|
|
city: 'all',
|
|||
|
|
priceRange: 'all',
|
|||
|
|
bedrooms: 'all',
|
|||
|
|
minArea: '',
|
|||
|
|
maxArea: '',
|
|||
|
|
features: []
|
|||
|
|
})}
|
|||
|
|
className="px-6 py-2 bg-gray-100 rounded-xl font-medium hover:bg-gray-200 transition-colors text-gray-700"
|
|||
|
|
>
|
|||
|
|
إعادة تعيين
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={() => setShowFilters(false)}
|
|||
|
|
className="px-6 py-2 bg-gray-800 text-white rounded-xl font-medium hover:bg-gray-900 transition-colors"
|
|||
|
|
>
|
|||
|
|
تطبيق الفلاتر
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</motion.div>
|
|||
|
|
)}
|
|||
|
|
</AnimatePresence>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default function PropertiesPage() {
|
|||
|
|
const [viewMode, setViewMode] = useState('grid');
|
|||
|
|
const [sortBy, setSortBy] = useState('newest');
|
|||
|
|
const [filters, setFilters] = useState({
|
|||
|
|
search: '',
|
|||
|
|
propertyType: 'all',
|
|||
|
|
city: 'all',
|
|||
|
|
priceRange: 'all',
|
|||
|
|
bedrooms: 'all',
|
|||
|
|
minArea: '',
|
|||
|
|
maxArea: '',
|
|||
|
|
features: []
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [properties] = useState([
|
|||
|
|
{
|
|||
|
|
id: 1,
|
|||
|
|
title: 'فيلا فاخرة في المزة',
|
|||
|
|
description: 'فيلا فاخرة مع حديقة خاصة ومسبح في أفضل أحياء دمشق.',
|
|||
|
|
type: 'villa',
|
|||
|
|
price: 500000,
|
|||
|
|
priceUnit: 'daily',
|
|||
|
|
location: { city: 'دمشق', district: 'المزة' },
|
|||
|
|
bedrooms: 5,
|
|||
|
|
bathrooms: 4,
|
|||
|
|
area: 450,
|
|||
|
|
features: ['مسبح', 'حديقة خاصة', 'موقف سيارات', 'أمن'],
|
|||
|
|
images: ['/villa1.jpg'],
|
|||
|
|
status: 'available',
|
|||
|
|
rating: 4.8,
|
|||
|
|
isNew: true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 2,
|
|||
|
|
title: 'شقة حديثة في الشهباء',
|
|||
|
|
description: 'شقة عصرية في حي الشهباء الراقي بحلب.',
|
|||
|
|
type: 'apartment',
|
|||
|
|
price: 250000,
|
|||
|
|
priceUnit: 'daily',
|
|||
|
|
location: { city: 'حلب', district: 'الشهباء' },
|
|||
|
|
bedrooms: 3,
|
|||
|
|
bathrooms: 2,
|
|||
|
|
area: 180,
|
|||
|
|
features: ['مطبخ مجهز', 'بلكونة', 'موقف سيارات', 'مصعد'],
|
|||
|
|
images: ['/apartment1.jpg'],
|
|||
|
|
status: 'available',
|
|||
|
|
rating: 4.5,
|
|||
|
|
isNew: false
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 3,
|
|||
|
|
title: 'بيت عائلي في بابا عمرو',
|
|||
|
|
description: 'بيت واسع مناسب للعائلات في حمص.',
|
|||
|
|
type: 'house',
|
|||
|
|
price: 350000,
|
|||
|
|
priceUnit: 'daily',
|
|||
|
|
location: { city: 'حمص', district: 'بابا عمرو' },
|
|||
|
|
bedrooms: 4,
|
|||
|
|
bathrooms: 3,
|
|||
|
|
area: 300,
|
|||
|
|
features: ['حديقة كبيرة', 'موقف سيارات', 'مدفأة'],
|
|||
|
|
images: ['/house1.jpg'],
|
|||
|
|
status: 'booked',
|
|||
|
|
rating: 4.3,
|
|||
|
|
isNew: false
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 4,
|
|||
|
|
title: 'شقة بجانب البحر',
|
|||
|
|
description: 'شقة رائعة مع إطلالة بحرية في اللاذقية.',
|
|||
|
|
type: 'apartment',
|
|||
|
|
price: 300000,
|
|||
|
|
priceUnit: 'daily',
|
|||
|
|
location: { city: 'اللاذقية', district: 'الشاطئ الأزرق' },
|
|||
|
|
bedrooms: 3,
|
|||
|
|
bathrooms: 2,
|
|||
|
|
area: 200,
|
|||
|
|
features: ['إطلالة بحرية', 'شرفة', 'تكييف'],
|
|||
|
|
images: ['/seaside1.jpg'],
|
|||
|
|
status: 'available',
|
|||
|
|
rating: 4.9,
|
|||
|
|
isNew: true
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 5,
|
|||
|
|
title: 'فيلا في درعا',
|
|||
|
|
description: 'فيلا فاخرة في حي الأطباء بدرعا.',
|
|||
|
|
type: 'villa',
|
|||
|
|
price: 400000,
|
|||
|
|
priceUnit: 'daily',
|
|||
|
|
location: { city: 'درعا', district: 'حي الأطباء' },
|
|||
|
|
bedrooms: 4,
|
|||
|
|
bathrooms: 3,
|
|||
|
|
area: 350,
|
|||
|
|
features: ['حديقة مثمرة', 'أنظمة أمن', 'مسبح'],
|
|||
|
|
images: ['/villa4.jpg'],
|
|||
|
|
status: 'available',
|
|||
|
|
rating: 4.6,
|
|||
|
|
isNew: false
|
|||
|
|
}
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
const filteredProperties = properties
|
|||
|
|
.filter(property => {
|
|||
|
|
if (filters.search && !property.title.includes(filters.search) && !property.description.includes(filters.search)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (filters.propertyType !== 'all' && property.type !== filters.propertyType) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (filters.city !== 'all' && property.location.city !== filters.city) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (filters.priceRange !== 'all') {
|
|||
|
|
const [min, max] = filters.priceRange.split('-');
|
|||
|
|
if (max) {
|
|||
|
|
if (property.price < parseInt(min) || property.price > parseInt(max)) return false;
|
|||
|
|
} else if (filters.priceRange.endsWith('+')) {
|
|||
|
|
const min = parseInt(filters.priceRange.replace('+', ''));
|
|||
|
|
if (property.price < min) return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (filters.bedrooms !== 'all' && property.bedrooms < parseInt(filters.bedrooms)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (filters.minArea && property.area < parseInt(filters.minArea)) return false;
|
|||
|
|
if (filters.maxArea && property.area > parseInt(filters.maxArea)) return false;
|
|||
|
|
if (filters.features.length > 0) {
|
|||
|
|
if (!filters.features.every(f => property.features.includes(f))) return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
})
|
|||
|
|
.sort((a, b) => {
|
|||
|
|
switch(sortBy) {
|
|||
|
|
case 'price_asc': return a.price - b.price;
|
|||
|
|
case 'price_desc': return b.price - a.price;
|
|||
|
|
case 'rating': return b.rating - a.rating;
|
|||
|
|
default: return b.isNew ? 1 : -1;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="min-h-screen bg-gray-50 py-8">
|
|||
|
|
<div className="container mx-auto px-4">
|
|||
|
|
<motion.div
|
|||
|
|
initial={{ opacity: 0, y: -20 }}
|
|||
|
|
animate={{ opacity: 1, y: 0 }}
|
|||
|
|
className="text-center mb-8"
|
|||
|
|
>
|
|||
|
|
<h1 className="text-4xl font-bold text-gray-900 mb-2">عقارات للإيجار</h1>
|
|||
|
|
<p className="text-gray-500">أفضل العقارات في سوريا</p>
|
|||
|
|
</motion.div>
|
|||
|
|
|
|||
|
|
<FilterBar filters={filters} onFilterChange={setFilters} />
|
|||
|
|
|
|||
|
|
<div className="flex justify-between items-center my-6">
|
|||
|
|
<div className="text-gray-600">
|
|||
|
|
<span className="font-bold text-gray-900">{filteredProperties.length}</span> عقار متاح
|
|||
|
|
</div>
|
|||
|
|
<div className="flex gap-3">
|
|||
|
|
<select
|
|||
|
|
value={sortBy}
|
|||
|
|
onChange={(e) => setSortBy(e.target.value)}
|
|||
|
|
className="px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-gray-700"
|
|||
|
|
>
|
|||
|
|
<option value="newest">الأحدث</option>
|
|||
|
|
<option value="price_asc">السعر: من الأقل</option>
|
|||
|
|
<option value="price_desc">السعر: من الأعلى</option>
|
|||
|
|
<option value="rating">التقييم</option>
|
|||
|
|
</select>
|
|||
|
|
<div className="flex gap-2">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setViewMode('grid')}
|
|||
|
|
className={`p-2 rounded-xl transition-colors ${
|
|||
|
|
viewMode === 'grid' ? 'bg-gray-800 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<Grid3x3 className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={() => setViewMode('list')}
|
|||
|
|
className={`p-2 rounded-xl transition-colors ${
|
|||
|
|
viewMode === 'list' ? 'bg-gray-800 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<List className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className={viewMode === 'grid'
|
|||
|
|
? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'
|
|||
|
|
: 'space-y-4'
|
|||
|
|
}>
|
|||
|
|
{filteredProperties.map((property) => (
|
|||
|
|
<PropertyCard key={property.id} property={property} viewMode={viewMode} />
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{filteredProperties.length === 0 && (
|
|||
|
|
<motion.div
|
|||
|
|
initial={{ opacity: 0 }}
|
|||
|
|
animate={{ opacity: 1 }}
|
|||
|
|
className="text-center py-16"
|
|||
|
|
>
|
|||
|
|
<div className="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|||
|
|
<Home className="w-12 h-12 text-gray-400" />
|
|||
|
|
</div>
|
|||
|
|
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد عقارات</h3>
|
|||
|
|
<p className="text-gray-500">جرب تغيير معايير البحث</p>
|
|||
|
|
</motion.div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|