From 47550dbfac0cacc9551f79e2a4e8c9714322781c Mon Sep 17 00:00:00 2001 From: Beilin-b Date: Wed, 15 Jul 2026 04:38:32 -0700 Subject: [PATCH] =?UTF-8?q?FilterRentProperties=E2=80=8B=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/properties/page.js | 414 +++++++++++++++++++++++++++-------------- 1 file changed, 270 insertions(+), 144 deletions(-) diff --git a/app/properties/page.js b/app/properties/page.js index 28fecbf..70f9a85 100644 --- a/app/properties/page.js +++ b/app/properties/page.js @@ -8,26 +8,16 @@ import { Bed, Bath, Square, - DollarSign, Filter, Grid3x3, List, Heart, - Share2, ChevronDown, Star, - Camera, Home, Building2, - Trees, - Waves, - Warehouse, - Sparkles, - Shield, - Calendar, Phone, - Mail, - MessageCircle + FileText } from 'lucide-react'; import Image from 'next/image'; import Link from 'next/link'; @@ -37,28 +27,51 @@ import AuthService from '@/app/services/AuthService'; import toast, { Toaster } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; -// Map API data to UI format function mapApiProperty(t, item, index) { const info = item.propertyInformation || {}; const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0; - const monthlyPrice = item.monthlyRent ?? 0; - const buildingTypeMap = { 0: 'apartment', 1: 'villa', 2: 'sweet', 3: 'room', 4: 'studio', 5: 'office', 6: 'farms', 7: 'shop', 8: 'warehouse' }; + const buildingTypeMap = { + 0: 'apartment', + 1: 'villa', + 2: 'sweet', + 3: 'room', + 4: 'studio', + 5: 'office', + 6: 'farms', + 7: 'shop', + 8: 'warehouse' + }; const propType = buildingTypeMap[info.buildingType] ?? buildingTypeMap[item.type] ?? 'apartment'; const statusMap = { 0: 'available', 1: 'notAvailable', 2: 'booked' }; const status = statusMap[info.status] ?? statusMap[item.status] ?? 'available'; - const features = []; - if (item.isSmokeAllow) features.push(t('smoke-allowed')); - if (item.isVisitorAllow) features.push(t('visitors-allowed')); - if (item.specializedFor) features.push(t('specialized')); - if (info.numberOfBedRooms) features.push(`${info.numberOfBedRooms} ${t('bedrooms')}`); - if (info.numberOfBathRooms) features.push(`${info.numberOfBathRooms} ${t('bathrooms')}`); + const cityMap = { + 0: 'damascus', + 1: 'aleppo', + 2: 'homs', + 3: 'latakia', + 4: 'daraa', + 5: 'tartous', + 6: 'suweida', + 7: 'deirEzzor', + 8: 'raqqa', + 9: 'idlib', + 10: 'hasakah', + 11: 'qamishli', + 12: 'ruralDamascus' + }; - // Extract images from API and build full URLs - const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : ''; + const features = []; + if (item.isSmokeAllow) features.push(t('smoke-allowed', 'مسموح التدخين')); + if (item.isVisitorAllow) features.push(t('visitors-allowed', 'مسموح الزوار')); + if (item.specializedFor) features.push(t('specialized', 'مخصص')); + if (info.numberOfBedRooms) features.push(`${info.numberOfBedRooms} ${t('bedrooms', 'غرف نوم')}`); + if (info.numberOfBathRooms) features.push(`${info.numberOfBathRooms} ${t('bathrooms', 'حمامات')}`); + + const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'http://45.93.137.91/api') : ''; const rawImages = Array.isArray(info.images) ? info.images : []; const images = rawImages.length > 0 ? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`) @@ -66,13 +79,13 @@ function mapApiProperty(t, item, index) { return { id: item.id ?? index + 1, - title: info.address || `${t('property')} #${item.id || index + 1}`, + title: info.address || `${t('property', 'عقار')} #${item.id || index + 1}`, description: info.description || '', type: propType, price: dailyPrice, priceUnit: 'daily', location: { - city: extractCity(info.address) || 'damascus', + city: cityMap[info.city] || extractCity(info.address) || 'damascus', district: info.address || '', }, bedrooms: info.numberOfBedRooms || 0, @@ -110,8 +123,6 @@ function extractCity(address) { return ''; } -// API-only — no fallback data - const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => { const { t, i18n } = useTranslation(); const { isFavorite: checkFavorite, addFavorite, removeFavorite } = useFavorites(); @@ -134,7 +145,7 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => { }; const formatCurrency = (amount) => { - return amount?.toLocaleString() + ' ' + t('syp-symbol'); + return amount?.toLocaleString() + ' ' + t('syp-symbol', 'ل.س'); }; const getPropertyTypeIcon = (type) => { @@ -149,10 +160,16 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => { const getPropertyTypeLabel = (type) => { switch (type) { - case 'villa': return t('buildingType.villa'); - case 'apartment': return t('buildingType.apartment'); - case 'house': return t('house'); - case 'studio': return t('buildingType.studio'); + case 'villa': return t('buildingType.villa', 'فيلا'); + case 'apartment': return t('buildingType.apartment', 'شقة'); + case 'house': return t('house', 'منزل'); + case 'studio': return t('buildingType.studio', 'ستوديو'); + case 'sweet': return t('buildingType.sweet', 'جناح'); + case 'room': return t('buildingType.room', 'غرفة'); + case 'office': return t('buildingType.office', 'مكتب'); + case 'farms': return t('buildingType.farms', 'مزرعة'); + case 'shop': return t('buildingType.shop', 'محل تجاري'); + case 'warehouse': return t('buildingType.warehouse', 'مستودع'); default: return type; } }; @@ -203,33 +220,33 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => { {getPropertyTypeLabel(property.type)} - {property.status === 'available' ? t('available') : t('booked')} + {property.status === 'available' ? t('available', 'متاح') : t('booked', 'محجوز')}

{property.title}

- {t('city.' + property.location.city)}، {property.location.district} + {t('city.' + property.location.city, property.location.city)}، {property.location.district}
{formatCurrency(property.price)}
-
/{property.priceUnit === 'daily' ? t('day') : t('month')}
+
/{property.priceUnit === 'daily' ? t('day', 'يوم') : t('month', 'شهر')}
- {property.bedrooms} {t('rooms')} + {property.bedrooms} {t('rooms', 'غرف')}
- {property.bathrooms} {t('bathrooms')} + {property.bathrooms} {t('bathrooms', 'حمامات')}
- {property.area} {t('sqm')} + {property.area} {t('sqm', 'م²')}
@@ -240,7 +257,7 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => { 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" > - {t('viewDetails')} + {t('viewDetails', 'عرض التفاصيل')} @@ -402,32 +440,39 @@ const FilterBar = ({ filters, onFilterChange }) => { exit={{ height: 0, opacity: 0 }} className="overflow-hidden" > -
+
- -
- {propertyTypes.map((type) => { - const Icon = type.icon; - return ( - - ); - })} -
+ +
- + + +
+ +
+ 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" + className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-sm" > {priceRanges.map((range) => ( @@ -449,13 +494,13 @@ const FilterBar = ({ filters, onFilterChange }) => {
- + onFilterChange({ ...filters, bathrooms: 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 text-sm" + > + + + + + + +
+ +
+ + +
+ +
+
onFilterChange({ ...filters, minArea: e.target.value })} /> onFilterChange({ ...filters, maxArea: e.target.value })} /> @@ -490,22 +563,25 @@ const FilterBar = ({ filters, onFilterChange }) => { onClick={() => onFilterChange({ search: '', propertyType: 'all', + buildingType: 'all', city: 'all', priceRange: 'all', bedrooms: 'all', + bathrooms: 'all', + certificate: '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" + className="px-6 py-2 bg-gray-100 rounded-xl font-medium hover:bg-gray-200 transition-colors text-gray-700 text-sm" > - {t('reset')} + {t('reset', 'إعادة تعيين')}
@@ -526,9 +602,12 @@ export default function PropertiesPage() { const [filters, setFilters] = useState({ search: '', propertyType: 'all', + buildingType: 'all', city: 'all', priceRange: 'all', bedrooms: 'all', + bathrooms: 'all', + certificate: 'all', minArea: '', maxArea: '', features: [] @@ -536,13 +615,74 @@ export default function PropertiesPage() { useEffect(() => { async function fetchProperties() { + setLoading(true); try { - const [rentData, saleData] = await Promise.all([ - getRentProperties().catch(() => []), + const queryParams = new URLSearchParams(); + + if (filters.bedrooms !== 'all') { + queryParams.append('numberOfBedRooms', filters.bedrooms); + } + + if (filters.bathrooms !== 'all') { + queryParams.append('numberOfBathRooms', filters.bathrooms); + } + + if (filters.minArea) { + queryParams.append('space', filters.minArea); + } + + const buildingTypeReverseMap = { + 'apartment': 'Apartment', + 'villa': 'Villa', + 'sweet': 'Sweet', + 'room': 'Room', + 'studio': 'Studio', + 'office': 'Office', + 'farms': 'Farms', + 'shop': 'Shop', + 'warehouse': 'Warehouse' + }; + if (filters.buildingType !== 'all' && buildingTypeReverseMap[filters.buildingType] !== undefined) { + queryParams.append('buildingType', buildingTypeReverseMap[filters.buildingType]); + } + + const propertyTypeReverseMap = { + 'residential': 'Residential', + 'commercial': 'Commercial', + 'agricultural': 'Agricultural' + }; + if (filters.propertyType !== 'all' && propertyTypeReverseMap[filters.propertyType] !== undefined) { + queryParams.append('propertyType', propertyTypeReverseMap[filters.propertyType]); + } + + const certificateReverseMap = { + 'realEstateTitle': 'RealEstateTitle', + 'courtRuling': 'CourtRuling', + 'notary': 'Notary', + 'shares': 'Shares' + }; + if (filters.certificate !== 'all' && certificateReverseMap[filters.certificate] !== undefined) { + queryParams.append('certificate', certificateReverseMap[filters.certificate]); + } + + if (filters.city !== 'all') { + const cityFormatted = filters.city.charAt(0).toUpperCase() + filters.city.slice(1); + queryParams.append('city', cityFormatted); + } + + const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://45.93.137.91/api'; + const rentEndpoint = `${baseUrl}/RentProperties/FilterRentProperties?${queryParams.toString()}`; + + const [rentRes, saleData] = await Promise.all([ + fetch(rentEndpoint, { + headers: { + 'Accept': 'application/json', + } + }).then(res => res.json()).catch(() => ({ data: [], isSuccess: false })), getSaleProperties().catch(() => []), ]); - const rentList = Array.isArray(rentData) ? rentData : []; + const rentList = rentRes.isSuccess && Array.isArray(rentRes.data) ? rentRes.data : []; const saleList = Array.isArray(saleData) ? saleData : []; const mapped = [ @@ -550,9 +690,7 @@ export default function PropertiesPage() { ...saleList.map((p, i) => ({ ...mapApiProperty(t, p, rentList.length + i), purpose: 'sale' })), ]; - if (mapped.length > 0) { - setProperties(mapped); - } + setProperties(mapped); } catch (err) { console.error('[Properties] Failed to fetch properties:', err); } finally { @@ -561,7 +699,7 @@ export default function PropertiesPage() { } fetchProperties(); - }, []); + }, [filters, t]); const filteredProperties = properties .filter(p => p.purpose === purposeTab) @@ -569,12 +707,6 @@ export default function PropertiesPage() { 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) { @@ -584,11 +716,6 @@ export default function PropertiesPage() { if (property.price < minVal) 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; return true; }) .sort((a, b) => { @@ -608,19 +735,18 @@ export default function PropertiesPage() { animate={{ opacity: 1, y: 0 }} className="text-center mb-8" > -

{purposeTab === 'rent' ? t('properties-for-rent') : t('properties-for-sale')}

-

{t('best-properties-in-syria')}

+

{purposeTab === 'rent' ? t('properties-for-rent', 'عقارات للإيجار') : t('properties-for-sale', 'عقارات للبيع')}

+

{t('best-properties-in-syria', 'أفضل العقارات المتاحة في سوريا مباشرة وبسهولة')}

- {/* Purpose Toggle */}
@@ -635,18 +761,18 @@ export default function PropertiesPage() {
- {filteredProperties.length} {t('property-available')} + {filteredProperties.length} {t('property-available', 'عقار متاح')}
-

{t('no-properties')}

-

{t('try-changing-search-criteria')}

+

{t('no-properties', 'لا يوجد نتائج تطابق بحثك')}

+

{t('try-changing-search-criteria', 'يرجى تجربة تعديل خيارات الفلترة والبحث')}

)}
@@ -700,20 +826,20 @@ export default function PropertiesPage() {
-

{t('login-required-title')}

-

{t('login-required-favorites-desc')}

+

{t('login-required-title', 'تسجيل الدخول مطلوب')}

+

{t('login-required-favorites-desc', 'يجب تسجيل الدخول لتتمكن من إضافة العقار للمفضلة')}

- {t('login')} + {t('login', 'دخول')}
@@ -721,4 +847,4 @@ export default function PropertiesPage() { )}
); -} +} \ No newline at end of file