Files
SweetHome/app/properties/page.js
Rahaf 6d81ff56a8 Edit admin page
Edit home image
Added properties page
2026-02-15 01:53:37 +03:00

713 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
);
}