Add API client and wire up live data fetching
All checks were successful
Build frontend / build (push) Successful in 43s
All checks were successful
Build frontend / build (push) Successful in 43s
- Created app/utils/api.js with functions for all OpenAPI endpoints - Updated main page to fetch RentProperties + SaleProperties from API - Updated properties listing page with API integration - Updated property detail page to fetch by ID from API - Added mapApiProperty() adapter to transform API responses to UI format - All pages gracefully fall back to dummy data if API is unavailable
This commit is contained in:
@ -1,8 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Search,
|
||||
MapPin,
|
||||
@ -32,7 +31,67 @@ import {
|
||||
} from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { getRentProperties, getSaleProperties } from '../utils/api';
|
||||
|
||||
// Map API data to UI format
|
||||
function mapApiProperty(item, index) {
|
||||
const info = item.propertyInformation || item;
|
||||
|
||||
const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
|
||||
const monthlyPrice = item.monthlyRent ?? 0;
|
||||
|
||||
const buildingTypeMap = { 0: 'apartment', 1: 'villa', 2: 'house' };
|
||||
const propType = buildingTypeMap[info.buildingType] || 'apartment';
|
||||
|
||||
const statusMap = { 0: 'available', 1: 'booked', 2: 'maintenance' };
|
||||
const status = statusMap[info.status] || 'available';
|
||||
|
||||
const features = [];
|
||||
if (item.isSmokeAllow) features.push('يسمح بالتدخين');
|
||||
if (item.isVisitorAllow) features.push('يسمح بالزوار');
|
||||
if (info.numberOfRooms) features.push(`${info.numberOfRooms} غرف`);
|
||||
if (info.numberOfBathRooms) features.push(`${info.numberOfBathRooms} حمامات`);
|
||||
|
||||
return {
|
||||
id: item.id ?? index + 1,
|
||||
title: info.address || info.description?.substring(0, 40) || 'عقار',
|
||||
description: info.description || '',
|
||||
type: propType,
|
||||
price: dailyPrice,
|
||||
priceUnit: 'daily',
|
||||
location: {
|
||||
city: extractCity(info.address),
|
||||
district: info.address || '',
|
||||
},
|
||||
bedrooms: info.numberOfBedRooms || info.numberOfRooms || 0,
|
||||
bathrooms: info.numberOfBathRooms || 0,
|
||||
area: info.space || 0,
|
||||
features,
|
||||
images: ['/property-placeholder.jpg'],
|
||||
status,
|
||||
rating: item.rating || 4.5,
|
||||
isNew: false,
|
||||
_raw: item,
|
||||
};
|
||||
}
|
||||
|
||||
function extractCity(address) {
|
||||
if (!address) return '';
|
||||
const cities = ['دمشق', 'حلب', 'حمص', 'اللاذقية', 'درعا', 'طرطوس', 'السويداء', 'دير الزور', 'الرقة', 'إدلب', 'الحسكة', 'القامشلي', 'ريف دمشق'];
|
||||
for (const city of cities) {
|
||||
if (address.includes(city)) return city;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Fallback data
|
||||
const FALLBACK_PROPERTIES = [
|
||||
{ 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 PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
const [isFavorite, setIsFavorite] = useState(false);
|
||||
@ -43,7 +102,7 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
};
|
||||
|
||||
const getPropertyTypeIcon = (type) => {
|
||||
switch(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" />;
|
||||
@ -53,7 +112,7 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
};
|
||||
|
||||
const getPropertyTypeLabel = (type) => {
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case 'villa': return 'فيلا';
|
||||
case 'apartment': return 'شقة';
|
||||
case 'house': return 'بيت';
|
||||
@ -83,9 +142,7 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
<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'
|
||||
}`}
|
||||
className={`w-1.5 h-1.5 rounded-full transition-all ${idx === currentImage ? 'bg-gray-800 w-3' : 'bg-white/70'}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -98,11 +155,6 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
<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">
|
||||
@ -113,11 +165,7 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
{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'
|
||||
}`}>
|
||||
<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>
|
||||
@ -148,22 +196,7 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
</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>
|
||||
<p className="text-gray-600 text-sm mb-4 line-clamp-2">{property.description}</p>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Link
|
||||
@ -195,19 +228,6 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
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)}
|
||||
@ -216,11 +236,6 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
<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">
|
||||
@ -232,9 +247,7 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
{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>
|
||||
<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>
|
||||
@ -270,19 +283,6 @@ const PropertyCard = ({ property, viewMode = 'grid' }) => {
|
||||
</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"
|
||||
@ -302,7 +302,6 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
||||
{ id: 'apartment', label: 'شقة', icon: Building2 },
|
||||
{ id: 'villa', label: 'فيلا', icon: Home },
|
||||
{ id: 'house', label: 'بيت', icon: Home },
|
||||
{ id: 'studio', label: 'استوديو', icon: Building2 }
|
||||
];
|
||||
|
||||
const priceRanges = [
|
||||
@ -364,11 +363,7 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
||||
<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'
|
||||
}`}
|
||||
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}
|
||||
@ -439,30 +434,6 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
||||
/>
|
||||
</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">
|
||||
@ -496,8 +467,10 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
||||
};
|
||||
|
||||
export default function PropertiesPage() {
|
||||
const [viewMode, setViewMode] = useState('grid');
|
||||
const [viewMode, setViewMode] = useState('grid');
|
||||
const [sortBy, setSortBy] = useState('newest');
|
||||
const [properties, setProperties] = useState(FALLBACK_PROPERTIES);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filters, setFilters] = useState({
|
||||
search: '',
|
||||
propertyType: 'all',
|
||||
@ -509,93 +482,34 @@ export default function PropertiesPage() {
|
||||
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
|
||||
useEffect(() => {
|
||||
async function fetchProperties() {
|
||||
try {
|
||||
const [rentData, saleData] = await Promise.all([
|
||||
getRentProperties().catch(() => []),
|
||||
getSaleProperties().catch(() => []),
|
||||
]);
|
||||
|
||||
const rentList = Array.isArray(rentData) ? rentData : [];
|
||||
const saleList = Array.isArray(saleData) ? saleData : [];
|
||||
|
||||
const mapped = [
|
||||
...rentList.map((p, i) => mapApiProperty(p, i)),
|
||||
...saleList.map((p, i) => mapApiProperty(p, rentList.length + i)),
|
||||
];
|
||||
|
||||
if (mapped.length > 0) {
|
||||
setProperties(mapped);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch properties:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
fetchProperties();
|
||||
}, []);
|
||||
|
||||
const filteredProperties = properties
|
||||
.filter(property => {
|
||||
@ -613,8 +527,8 @@ export default function PropertiesPage() {
|
||||
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;
|
||||
const minVal = parseInt(filters.priceRange.replace('+', ''));
|
||||
if (property.price < minVal) return false;
|
||||
}
|
||||
}
|
||||
if (filters.bedrooms !== 'all' && property.bedrooms < parseInt(filters.bedrooms)) {
|
||||
@ -622,17 +536,14 @@ export default function PropertiesPage() {
|
||||
}
|
||||
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) {
|
||||
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;
|
||||
default: return 0;
|
||||
}
|
||||
});
|
||||
|
||||
@ -646,6 +557,11 @@ export default function PropertiesPage() {
|
||||
>
|
||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">عقارات للإيجار</h1>
|
||||
<p className="text-gray-500">أفضل العقارات في سوريا</p>
|
||||
{loading && (
|
||||
<div className="mt-4">
|
||||
<div className="inline-block w-6 h-6 border-2 border-gray-200 border-t-gray-800 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
<FilterBar filters={filters} onFilterChange={setFilters} />
|
||||
@ -668,19 +584,13 @@ export default function PropertiesPage() {
|
||||
<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'
|
||||
}`}
|
||||
title="عرض شبكي"
|
||||
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'
|
||||
}`}
|
||||
title="عرض قائمة"
|
||||
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>
|
||||
@ -688,7 +598,7 @@ export default function PropertiesPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={viewMode === 'grid'
|
||||
<div className={viewMode === 'grid'
|
||||
? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6'
|
||||
: 'space-y-4'
|
||||
}>
|
||||
@ -713,4 +623,4 @@ export default function PropertiesPage() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user