Files
SweetHome/app/properties/page.js
hamzaobed7 00dde588cb
All checks were successful
Build frontend / build (push) Successful in 1m46s
fixed getSaleProperiesById and make login type is email
2026-08-11 16:04:09 +03:00

832 lines
36 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, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Search, MapPin, Bed, Bath, Square, Filter, Grid3x3, List, Heart, ChevronDown, Star, Home, Building2, Phone, FileText } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { getRentProperties, getSaleProperties } from "../utils/api";
import { useFavorites } from "@/app/contexts/FavoritesContext";
import AuthService from "@/app/services/AuthService";
import toast, { Toaster } from "react-hot-toast";
import { useTranslation } from "react-i18next";
function mapApiProperty(t, item, index) {
const info = item.propertyInformation || {};
const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
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 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",
};
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}`)) : ["/property-placeholder.jpg"];
return {
id: item.id ?? index + 1,
title: info.address || `${t("property", "عقار")} #${item.id || index + 1}`,
description: info.description || "",
type: propType,
price: dailyPrice,
priceUnit: "daily",
location: {
city: cityMap[info.city] || extractCity(info.address) || "damascus",
district: info.address || "",
},
bedrooms: info.numberOfBedRooms || 0,
bathrooms: info.numberOfBathRooms || 0,
area: info.space || 0,
features,
images,
status,
rating: item.rating || 0,
isNew: false,
_raw: item,
};
}
function extractCity(address) {
if (!address) return "";
const cityMap = {
دمشق: "damascus",
حلب: "aleppo",
حمص: "homs",
اللاذقية: "latakia",
درعا: "daraa",
طرطوس: "tartous",
السويداء: "suweida",
"دير الزور": "deirEzzor",
الرقة: "raqqa",
إدلب: "idlib",
الحسكة: "hasakah",
القامشلي: "qamishli",
"ريف دمشق": "ruralDamascus",
};
for (const [arabic, key] of Object.entries(cityMap)) {
if (address.includes(arabic)) return key;
}
return "";
}
const PropertyCard = ({ property, viewMode = "grid", onLoginRequired, type = "rent" }) => {
const { t, i18n } = useTranslation();
const { isFavorite: checkFavorite, addFavorite, removeFavorite } = useFavorites();
const [favLoading, setFavLoading] = useState(false);
const [currentImage, setCurrentImage] = useState(0);
const isFav = checkFavorite(property.id);
const toggleFavorite = async (e) => {
e.preventDefault();
e.stopPropagation();
if (!AuthService.isAuthenticated()) {
onLoginRequired?.();
return;
}
setFavLoading(true);
if (isFav) {
await removeFavorite(property.id);
} else {
await addFavorite(property.id);
}
setFavLoading(false);
};
const formatCurrency = (amount) => {
return amount?.toLocaleString() + " " + t("syp-symbol", "ل.س");
};
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 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;
}
};
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 loading="lazy" 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={toggleFavorite}
disabled={favLoading}
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 ${isFav ? "fill-red-500 text-red-500" : "text-gray-600"}`} />
</button>
</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" ? t("available", "متاح") : t("booked", "محجوز")}
</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" />
{t("city." + property.location.city, 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" ? t("day", "يوم") : t("month", "شهر")}</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} {t("rooms", "غرف")}
</span>
</div>
<div className="flex items-center gap-1 text-gray-600">
<Bath className="w-4 h-4" />
<span>
{property.bathrooms} {t("bathrooms", "حمامات")}
</span>
</div>
<div className="flex items-center gap-1 text-gray-600">
<Square className="w-4 h-4" />
<span>
{property.area} {t("sqm", "م²")}
</span>
</div>
</div>
<p className="text-gray-600 text-sm mb-4 line-clamp-2">{property.description}</p>
<div className="flex gap-3">
//
<Link href={`/property/${property.id}?type=${type}`} className="flex-1 bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center">
{t("viewDetails", "عرض التفاصيل")}
</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" />
<div className="absolute top-2 right-2 flex gap-2">
<button
onClick={toggleFavorite}
disabled={favLoading}
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 ${isFav ? "fill-red-500 text-red-500" : "text-gray-600"}`} />
</button>
</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">{t("available", "متاح")}</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">
{t("city." + property.location.city, 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" ? t("day", "يوم") : t("month", "شهر")}</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}
{t("sqm", "م²")}
</span>
</div>
</div>
{property.rating > 0 && (
<div className="flex items-center gap-1">
<Star className="w-4 h-4 fill-amber-500 text-amber-500" />
<span className="text-sm font-medium text-gray-700">{property.rating.toFixed(1)}</span>
</div>
)}
</div>
<Link href={`/property/${property.id}?type=${type}`} className="block w-full bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center">
{t("viewDetails", "عرض التفاصيل")}
</Link>
</div>
</motion.div>
);
};
const FilterBar = ({ filters, onFilterChange }) => {
const { t } = useTranslation();
const [showFilters, setShowFilters] = useState(false);
const propertyTypes = [
{ id: "all", label: t("all", "الكل") },
{ id: "residential", label: t("propertyType.residential", "سكني") },
{ id: "commercial", label: t("propertyType.commercial", "تجاري") },
{ id: "agricultural", label: t("propertyType.agricultural", "زراعي") },
];
const buildingTypes = [
{ id: "all", label: t("all", "الكل") },
{ id: "apartment", label: t("buildingType.apartment", "شقة"), icon: Building2 },
{ id: "villa", label: t("buildingType.villa", "فيلا"), icon: Home },
{ id: "sweet", label: t("buildingType.sweet", "جناح"), icon: Home },
{ id: "room", label: t("buildingType.room", "غرفة"), icon: Home },
{ id: "studio", label: t("buildingType.studio", "ستوديو"), icon: Building2 },
{ id: "office", label: t("buildingType.office", "مكتب"), icon: Building2 },
{ id: "farms", label: t("buildingType.farms", "مزرعة"), icon: Home },
{ id: "shop", label: t("buildingType.shop", "محل تجاري"), icon: Building2 },
{ id: "warehouse", label: t("buildingType.warehouse", "مستودع"), icon: Building2 },
];
const priceRanges = [
{ id: "all", label: t("allPrices", "جميع الأسعار") },
{ id: "0-500000", label: t("price-range-less-than-500k", "أقل من 500 ألف") },
{ id: "500000-1000000", label: t("price-range-500k-to-1m", "500 ألف - 1 مليون") },
{ id: "1000000-2000000", label: t("price-range-1m-to-2m", "1 مليون - 2 مليون") },
{ id: "2000000-5000000", label: t("price-range-2m-to-5m", "2 مليون - 5 مليون") },
{ id: "5000000+", label: t("price-range-more-than-5m", "أكثر من 5 مليون") },
];
const cities = [
{ id: "all", label: t("allCities", "جميع المدن") },
{ id: "damascus", label: t("city.damascus", "دمشق") },
{ id: "aleppo", label: t("city.aleppo", "حلب") },
{ id: "homs", label: t("city.homs", "حمص") },
{ id: "latakia", label: t("city.latakia", "اللاذقية") },
{ id: "daraa", label: t("city.daraa", "درعا") },
{ id: "tartous", label: t("city.tartous", "طرطوس") },
{ id: "suweida", label: t("city.suweida", "السويداء") },
{ id: "deirEzzor", label: t("city.deirEzzor", "دير الزور") },
{ id: "raqqa", label: t("city.raqqa", "الرقة") },
{ id: "idlib", label: t("city.idlib", "إدلب") },
{ id: "hasakah", label: t("city.hasakah", "الحسكة") },
{ id: "qamishli", label: t("city.qamishli", "القامشلي") },
{ id: "ruralDamascus", label: t("city.ruralDamascus", "ريف دمشق") },
];
const certificates = [
{ id: "all", label: t("allCertificates", "جميع السندات") },
{ id: "realEstateTitle", label: t("certificate.realEstateTitle", "طابو أخضر") },
{ id: "courtRuling", label: t("certificate.courtRuling", "حكم محكمة") },
{ id: "notary", label: t("certificate.notary", "كاتب عدل") },
{ id: "shares", label: t("certificate.shares", "أسهم عقارية") },
];
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={t("search-property-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" />
{t("advanced-filters", "فلاتر متقدمة")}
<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 lg:grid-cols-4 gap-4 pt-4 border-t border-gray-100">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">{t("property-type", "نوع الاستثمار")}</label>
<select
value={filters.propertyType}
onChange={(e) => onFilterChange({ ...filters, propertyType: e.target.value })}
className="w-full px-4 py-2 border bg-[#1e293b] border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-sm"
>
{propertyTypes.map((type) => (
<option key={type.id} value={type.id}>
{type.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">{t("building-type", "نوع البناء")}</label>
<select
value={filters.buildingType}
onChange={(e) => onFilterChange({ ...filters, buildingType: e.target.value })}
className="w-full px-4 bg-[#1e293b] py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-sm"
>
{buildingTypes.map((type) => (
<option key={type.id} value={type.id}>
{type.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">{t("city", "المدينة")}</label>
<select
value={filters.city}
onChange={(e) => onFilterChange({ ...filters, city: e.target.value })}
className="w-full px-4 py-2 border bg-[#1e293b] border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-sm"
>
{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">{t("price-range", "مجال السعر")}</label>
<select
value={filters.priceRange}
onChange={(e) => onFilterChange({ ...filters, priceRange: e.target.value })}
className="w-full px-4 py-2 border bg-[#1e293b] border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-sm"
>
{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">{t("bedrooms", "عدد الغرف")}</label>
<select
value={filters.bedrooms}
onChange={(e) => onFilterChange({ ...filters, bedrooms: e.target.value })}
className="w-full px-4 py-2 border bg-[#1e293b] border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-sm"
>
<option value="all">{t("all-numbers", "الكل")}</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">{t("bathrooms-label", "عدد الحمامات")}</label>
<select
value={filters.bathrooms}
onChange={(e) => onFilterChange({ ...filters, bathrooms: e.target.value })}
className="w-full px-4 py-2 border bg-[#1e293b] border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-sm"
>
<option value="all">{t("all-numbers", "الكل")}</option>
<option value="1">1+</option>
<option value="2">2+</option>
<option value="3">3+</option>
<option value="4">4+</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">{t("certificate-label", "الشهادة / السند العقاري")}</label>
<select
value={filters.certificate}
onChange={(e) => onFilterChange({ ...filters, certificate: e.target.value })}
className="w-full px-4 py-2 border bg-[#1e293b] border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-sm"
>
{certificates.map((cert) => (
<option key={cert.id} value={cert.id}>
{cert.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">{t("area-sqm", "المساحة (م²)")}</label>
<div className="flex gap-2">
<input
type="number"
placeholder={t("from", "من")}
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"
value={filters.minArea}
onChange={(e) => onFilterChange({ ...filters, minArea: e.target.value })}
/>
<input
type="number"
placeholder={t("to", "إلى")}
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"
value={filters.maxArea}
onChange={(e) => onFilterChange({ ...filters, maxArea: e.target.value })}
/>
</div>
</div>
</div>
<div className="flex gap-3 mt-4 pt-4 border-t border-gray-100">
<button
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-200 rounded-xl font-medium hover:bg-gray-200 transition-colors text-gray-700 text-sm"
>
{t("reset", "إعادة تعيين")}
</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 text-sm">
{t("apply-filters", "تطبيق")}
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
export default function PropertiesPage() {
const { t, i18n } = useTranslation();
const [purposeTab, setPurposeTab] = useState("rent");
const [viewMode, setViewMode] = useState("grid");
const [sortBy, setSortBy] = useState("newest");
const [properties, setProperties] = useState([]);
const [loading, setLoading] = useState(true);
const [showLoginDialog, setShowLoginDialog] = useState(false);
const [filters, setFilters] = useState({
search: "",
propertyType: "all",
buildingType: "all",
city: "all",
priceRange: "all",
bedrooms: "all",
bathrooms: "all",
certificate: "all",
minArea: "",
maxArea: "",
features: [],
});
useEffect(() => {
async function fetchProperties() {
setLoading(true);
try {
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 = rentRes.isSuccess && Array.isArray(rentRes.data) ? rentRes.data : [];
const saleList = Array.isArray(saleData) ? saleData : [];
const mapped = [...rentList.map((p, i) => ({ ...mapApiProperty(t, p, i), purpose: "rent" })), ...saleList.map((p, i) => ({ ...mapApiProperty(t, p, rentList.length + i), purpose: "sale" }))];
setProperties(mapped);
} catch (err) {
console.error("[Properties] Failed to fetch properties:", err);
} finally {
setLoading(false);
}
}
fetchProperties();
}, [filters, t]);
const filteredProperties = properties
.filter((p) => p.purpose === purposeTab)
.filter((property) => {
if (filters.search && !property.title.includes(filters.search) && !property.description.includes(filters.search)) {
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 minVal = parseInt(filters.priceRange.replace("+", ""));
if (property.price < minVal) 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 0;
}
});
return (
<div dir={i18n.language === "ar" ? "rtl" : "ltr"} 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">{purposeTab === "rent" ? t("properties-for-rent", "عقارات للإيجار") : t("properties-for-sale", "عقارات للبيع")}</h1>
<p className="text-gray-500">{t("best-properties-in-syria", "أفضل العقارات المتاحة في سوريا مباشرة وبسهولة")}</p>
<div className="flex justify-center mt-4">
<div className="inline-flex bg-gray-100 rounded-xl p-1">
<button
onClick={() => setPurposeTab("rent")}
className={`px-6 py-2 rounded-lg text-sm font-medium transition-all ${purposeTab === "rent" ? "bg-white text-amber-600 shadow-sm" : "text-gray-500 hover:text-gray-700"}`}
>
{t("for-rent", "للإيجار")}
</button>
<button
onClick={() => setPurposeTab("sale")}
className={`px-6 py-2 rounded-lg text-sm font-medium transition-all ${purposeTab === "sale" ? "bg-white text-blue-600 shadow-sm" : "text-gray-500 hover:text-gray-700"}`}
>
{t("for-sale", "للبيع")}
</button>
</div>
</div>
{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} />
<div className="flex justify-between items-center my-6">
<div className="text-gray-600">
<span className="font-bold text-gray-900">{filteredProperties.length}</span> {t("property-available", "عقار متاح")}
</div>
<div className="flex gap-3">
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-4 py-2 border bg-[#1e293b] border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-gray-700 text-sm"
>
<option value="newest">{t("newest", "الأحدث")}</option>
<option value="price_asc">{t("price-low-to-high", "السعر من الأقل للأعلى")}</option>
<option value="price_desc">{t("price-high-to-low", "السعر من الأعلى للأقل")}</option>
<option value="rating">{t("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} type={property.purpose} onLoginRequired={() => setShowLoginDialog(true)} />
))}
</div>
{!loading && 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">{t("no-properties", "لا يوجد نتائج تطابق بحثك")}</h3>
<p className="text-gray-500">{t("try-changing-search-criteria", "يرجى تجربة تعديل خيارات الفلترة والبحث")}</p>
</motion.div>
)}
</div>
<Toaster position="top-center" />
{showLoginDialog && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" onClick={() => setShowLoginDialog(false)}>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
onClick={(e) => e.stopPropagation()}
className="bg-white rounded-2xl p-6 max-w-sm w-full mx-4 shadow-xl text-center"
>
<div className="w-14 h-14 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
<Heart className="w-7 h-7 text-amber-600" />
</div>
<h3 className="text-xl font-bold text-gray-900 mb-2">{t("login-required-title", "تسجيل الدخول مطلوب")}</h3>
<p className="text-gray-500 mb-6">{t("login-required-favorites-desc", "يجب تسجيل الدخول لتتمكن من إضافة العقار للمفضلة")}</p>
<div className="flex gap-3">
<button onClick={() => setShowLoginDialog(false)} className="flex-1 py-3 border border-gray-200 rounded-xl font-medium text-gray-600 hover:bg-gray-50 transition-colors">
{t("cancel", "إلغاء")}
</button>
<Link href="/login" className="flex-1 py-3 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors text-center">
{t("login", "دخول")}
</Link>
</div>
</motion.div>
</div>
)}
</div>
);
}