2026-08-11 16:04:09 +03:00
|
|
|
|
"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";
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
2026-07-01 15:43:58 +03:00
|
|
|
|
function mapApiProperty(t, item, index) {
|
2026-03-26 22:59:08 +00:00
|
|
|
|
const info = item.propertyInformation || {};
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
|
|
|
|
|
const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const buildingTypeMap = {
|
|
|
|
|
|
0: "apartment",
|
|
|
|
|
|
1: "villa",
|
|
|
|
|
|
2: "sweet",
|
|
|
|
|
|
3: "room",
|
|
|
|
|
|
4: "studio",
|
|
|
|
|
|
5: "office",
|
|
|
|
|
|
6: "farms",
|
|
|
|
|
|
7: "shop",
|
|
|
|
|
|
8: "warehouse",
|
2026-07-15 04:38:32 -07:00
|
|
|
|
};
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const propType = buildingTypeMap[info.buildingType] ?? buildingTypeMap[item.type] ?? "apartment";
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const statusMap = { 0: "available", 1: "notAvailable", 2: "booked" };
|
|
|
|
|
|
const status = statusMap[info.status] ?? statusMap[item.status] ?? "available";
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
2026-07-15 04:38:32 -07:00
|
|
|
|
const cityMap = {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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",
|
2026-07-15 04:38:32 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-26 22:20:33 +00:00
|
|
|
|
const features = [];
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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", "حمامات")}`);
|
2026-07-15 04:38:32 -07:00
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const apiBase = typeof window !== "undefined" ? process.env.NEXT_PUBLIC_API_URL || "http://45.93.137.91/api" : "";
|
2026-03-30 01:01:42 +00:00
|
|
|
|
const rawImages = Array.isArray(info.images) ? info.images : [];
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const images = rawImages.length > 0 ? rawImages.map((img) => (img.startsWith("http") ? img : `${apiBase}${img.startsWith("/") ? "" : "/Pictures/"}${img}`)) : ["/property-placeholder.jpg"];
|
2026-03-30 01:01:42 +00:00
|
|
|
|
|
2026-03-26 22:20:33 +00:00
|
|
|
|
return {
|
|
|
|
|
|
id: item.id ?? index + 1,
|
2026-08-11 16:04:09 +03:00
|
|
|
|
title: info.address || `${t("property", "عقار")} #${item.id || index + 1}`,
|
|
|
|
|
|
description: info.description || "",
|
2026-03-26 22:20:33 +00:00
|
|
|
|
type: propType,
|
|
|
|
|
|
price: dailyPrice,
|
2026-08-11 16:04:09 +03:00
|
|
|
|
priceUnit: "daily",
|
2026-03-26 22:20:33 +00:00
|
|
|
|
location: {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
city: cityMap[info.city] || extractCity(info.address) || "damascus",
|
|
|
|
|
|
district: info.address || "",
|
2026-03-26 22:20:33 +00:00
|
|
|
|
},
|
2026-03-26 22:59:08 +00:00
|
|
|
|
bedrooms: info.numberOfBedRooms || 0,
|
2026-03-26 22:20:33 +00:00
|
|
|
|
bathrooms: info.numberOfBathRooms || 0,
|
|
|
|
|
|
area: info.space || 0,
|
|
|
|
|
|
features,
|
2026-03-30 01:01:42 +00:00
|
|
|
|
images,
|
2026-03-26 22:20:33 +00:00
|
|
|
|
status,
|
2026-05-26 20:24:37 +03:00
|
|
|
|
rating: item.rating || 0,
|
2026-03-26 22:20:33 +00:00
|
|
|
|
isNew: false,
|
|
|
|
|
|
_raw: item,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
2026-03-26 22:20:33 +00:00
|
|
|
|
function extractCity(address) {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (!address) return "";
|
2026-07-01 15:43:58 +03:00
|
|
|
|
const cityMap = {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
دمشق: "damascus",
|
|
|
|
|
|
حلب: "aleppo",
|
|
|
|
|
|
حمص: "homs",
|
|
|
|
|
|
اللاذقية: "latakia",
|
|
|
|
|
|
درعا: "daraa",
|
|
|
|
|
|
طرطوس: "tartous",
|
|
|
|
|
|
السويداء: "suweida",
|
|
|
|
|
|
"دير الزور": "deirEzzor",
|
|
|
|
|
|
الرقة: "raqqa",
|
|
|
|
|
|
إدلب: "idlib",
|
|
|
|
|
|
الحسكة: "hasakah",
|
|
|
|
|
|
القامشلي: "qamishli",
|
|
|
|
|
|
"ريف دمشق": "ruralDamascus",
|
2026-07-01 15:43:58 +03:00
|
|
|
|
};
|
|
|
|
|
|
for (const [arabic, key] of Object.entries(cityMap)) {
|
|
|
|
|
|
if (address.includes(arabic)) return key;
|
2026-03-26 22:20:33 +00:00
|
|
|
|
}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
return "";
|
2026-03-26 22:20:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const PropertyCard = ({ property, viewMode = "grid", onLoginRequired, type = "rent" }) => {
|
2026-07-01 15:43:58 +03:00
|
|
|
|
const { t, i18n } = useTranslation();
|
2026-03-30 17:54:42 +00:00
|
|
|
|
const { isFavorite: checkFavorite, addFavorite, removeFavorite } = useFavorites();
|
|
|
|
|
|
const [favLoading, setFavLoading] = useState(false);
|
2026-02-15 01:53:37 +03:00
|
|
|
|
const [currentImage, setCurrentImage] = useState(0);
|
2026-03-30 17:54:42 +00:00
|
|
|
|
const isFav = checkFavorite(property.id);
|
|
|
|
|
|
const toggleFavorite = async (e) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
e.stopPropagation();
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (!AuthService.isAuthenticated()) {
|
|
|
|
|
|
onLoginRequired?.();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-30 17:54:42 +00:00
|
|
|
|
setFavLoading(true);
|
|
|
|
|
|
if (isFav) {
|
|
|
|
|
|
await removeFavorite(property.id);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await addFavorite(property.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
setFavLoading(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-15 01:53:37 +03:00
|
|
|
|
const formatCurrency = (amount) => {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
return amount?.toLocaleString() + " " + t("syp-symbol", "ل.س");
|
2026-02-15 01:53:37 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getPropertyTypeIcon = (type) => {
|
2026-03-26 22:20:33 +00:00
|
|
|
|
switch (type) {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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" />;
|
2026-02-15 01:53:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getPropertyTypeLabel = (type) => {
|
2026-03-26 22:20:33 +00:00
|
|
|
|
switch (type) {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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;
|
2026-02-15 01:53:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (viewMode === "list") {
|
2026-02-15 01:53:37 +03:00
|
|
|
|
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">
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<Image src={property.images[currentImage] || "/property-placeholder.jpg"} alt={property.title} fill loading="lazy" className="object-cover" />
|
2026-02-15 01:53:37 +03:00
|
|
|
|
{property.images.length > 1 && (
|
|
|
|
|
|
<div className="absolute bottom-2 left-2 right-2 flex justify-center gap-1">
|
|
|
|
|
|
{property.images.map((_, idx) => (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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"}`} />
|
2026-02-15 01:53:37 +03:00
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<div className="absolute top-2 right-2 flex gap-2">
|
|
|
|
|
|
<button
|
2026-03-30 17:54:42 +00:00
|
|
|
|
onClick={toggleFavorite}
|
|
|
|
|
|
disabled={favLoading}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
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"
|
|
|
|
|
|
>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<Heart className={`w-4 h-4 ${isFav ? "fill-red-500 text-red-500" : "text-gray-600"}`} />
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</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>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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", "محجوز")}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</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" />
|
2026-08-11 16:04:09 +03:00
|
|
|
|
{t("city." + property.location.city, property.location.city)}، {property.location.district}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-left">
|
|
|
|
|
|
<div className="text-2xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<div className="text-xs text-gray-500">/{property.priceUnit === "daily" ? t("day", "يوم") : t("month", "شهر")}</div>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</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" />
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<span>
|
|
|
|
|
|
{property.bedrooms} {t("rooms", "غرف")}
|
|
|
|
|
|
</span>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center gap-1 text-gray-600">
|
|
|
|
|
|
<Bath className="w-4 h-4" />
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<span>
|
|
|
|
|
|
{property.bathrooms} {t("bathrooms", "حمامات")}
|
|
|
|
|
|
</span>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center gap-1 text-gray-600">
|
|
|
|
|
|
<Square className="w-4 h-4" />
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<span>
|
|
|
|
|
|
{property.area} {t("sqm", "م²")}
|
|
|
|
|
|
</span>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-26 22:20:33 +00:00
|
|
|
|
<p className="text-gray-600 text-sm mb-4 line-clamp-2">{property.description}</p>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-3">
|
2026-08-11 16:04:09 +03:00
|
|
|
|
//
|
|
|
|
|
|
<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", "عرض التفاصيل")}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</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">
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<Image src={property.images[currentImage] || "/property-placeholder.jpg"} alt={property.title} fill className="object-cover" />
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<div className="absolute top-2 right-2 flex gap-2">
|
|
|
|
|
|
<button
|
2026-03-30 17:54:42 +00:00
|
|
|
|
onClick={toggleFavorite}
|
|
|
|
|
|
disabled={favLoading}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
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"
|
|
|
|
|
|
>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<Heart className={`w-4 h-4 ${isFav ? "fill-red-500 text-red-500" : "text-gray-600"}`} />
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</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>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
{property.status === "available" && <span className="px-2 py-1 bg-gray-800 text-white rounded-lg text-xs font-medium">{t("available", "متاح")}</span>}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</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" />
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<span className="line-clamp-1">
|
|
|
|
|
|
{t("city." + property.location.city, property.location.city)}، {property.location.district}
|
|
|
|
|
|
</span>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-left">
|
|
|
|
|
|
<div className="text-xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<div className="text-xs text-gray-500">/{property.priceUnit === "daily" ? t("day", "يوم") : t("month", "شهر")}</div>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</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" />
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<span>
|
|
|
|
|
|
{property.area}
|
|
|
|
|
|
{t("sqm", "م²")}
|
|
|
|
|
|
</span>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-26 20:24:37 +03:00
|
|
|
|
{property.rating > 0 && (
|
2026-07-15 04:38:32 -07:00
|
|
|
|
<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>
|
2026-05-26 20:24:37 +03:00
|
|
|
|
)}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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", "عرض التفاصيل")}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</Link>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const FilterBar = ({ filters, onFilterChange }) => {
|
2026-07-15 04:38:32 -07:00
|
|
|
|
const { t } = useTranslation();
|
2026-02-15 01:53:37 +03:00
|
|
|
|
const [showFilters, setShowFilters] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
const propertyTypes = [
|
2026-08-11 16:04:09 +03:00
|
|
|
|
{ id: "all", label: t("all", "الكل") },
|
|
|
|
|
|
{ id: "residential", label: t("propertyType.residential", "سكني") },
|
|
|
|
|
|
{ id: "commercial", label: t("propertyType.commercial", "تجاري") },
|
|
|
|
|
|
{ id: "agricultural", label: t("propertyType.agricultural", "زراعي") },
|
2026-07-15 04:38:32 -07:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const buildingTypes = [
|
2026-08-11 16:04:09 +03:00
|
|
|
|
{ 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 },
|
2026-02-15 01:53:37 +03:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const priceRanges = [
|
2026-08-11 16:04:09 +03:00
|
|
|
|
{ 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 مليون") },
|
2026-02-15 01:53:37 +03:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const cities = [
|
2026-08-11 16:04:09 +03:00
|
|
|
|
{ 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", "ريف دمشق") },
|
2026-07-15 04:38:32 -07:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const certificates = [
|
2026-08-11 16:04:09 +03:00
|
|
|
|
{ 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", "أسهم عقارية") },
|
2026-02-15 01:53:37 +03:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
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"
|
2026-08-11 16:04:09 +03:00
|
|
|
|
placeholder={t("search-property-placeholder", "ابحث عن عقار...")}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
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>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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">
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<Filter className="w-5 h-5" />
|
2026-08-11 16:04:09 +03:00
|
|
|
|
{t("advanced-filters", "فلاتر متقدمة")}
|
|
|
|
|
|
<ChevronDown className={`w-4 h-4 transition-transform ${showFilters ? "rotate-180" : ""}`} />
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<AnimatePresence>
|
|
|
|
|
|
{showFilters && (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} className="overflow-hidden">
|
2026-07-15 04:38:32 -07:00
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 pt-4 border-t border-gray-100">
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<div>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("property-type", "نوع الاستثمار")}</label>
|
2026-07-15 04:38:32 -07:00
|
|
|
|
<select
|
|
|
|
|
|
value={filters.propertyType}
|
|
|
|
|
|
onChange={(e) => onFilterChange({ ...filters, propertyType: e.target.value })}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"
|
2026-07-15 04:38:32 -07:00
|
|
|
|
>
|
|
|
|
|
|
{propertyTypes.map((type) => (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<option key={type.id} value={type.id}>
|
|
|
|
|
|
{type.label}
|
|
|
|
|
|
</option>
|
2026-07-15 04:38:32 -07:00
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("building-type", "نوع البناء")}</label>
|
2026-07-15 04:38:32 -07:00
|
|
|
|
<select
|
|
|
|
|
|
value={filters.buildingType}
|
|
|
|
|
|
onChange={(e) => onFilterChange({ ...filters, buildingType: e.target.value })}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"
|
2026-07-15 04:38:32 -07:00
|
|
|
|
>
|
|
|
|
|
|
{buildingTypes.map((type) => (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<option key={type.id} value={type.id}>
|
|
|
|
|
|
{type.label}
|
|
|
|
|
|
</option>
|
2026-07-15 04:38:32 -07:00
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("city", "المدينة")}</label>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<select
|
|
|
|
|
|
value={filters.city}
|
|
|
|
|
|
onChange={(e) => onFilterChange({ ...filters, city: e.target.value })}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
>
|
|
|
|
|
|
{cities.map((city) => (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<option key={city.id} value={city.id}>
|
|
|
|
|
|
{city.label}
|
|
|
|
|
|
</option>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("price-range", "مجال السعر")}</label>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<select
|
|
|
|
|
|
value={filters.priceRange}
|
|
|
|
|
|
onChange={(e) => onFilterChange({ ...filters, priceRange: e.target.value })}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
>
|
|
|
|
|
|
{priceRanges.map((range) => (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<option key={range.id} value={range.id}>
|
|
|
|
|
|
{range.label}
|
|
|
|
|
|
</option>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("bedrooms", "عدد الغرف")}</label>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<select
|
|
|
|
|
|
value={filters.bedrooms}
|
|
|
|
|
|
onChange={(e) => onFilterChange({ ...filters, bedrooms: e.target.value })}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<option value="all">{t("all-numbers", "الكل")}</option>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<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>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("bathrooms-label", "عدد الحمامات")}</label>
|
2026-07-15 04:38:32 -07:00
|
|
|
|
<select
|
|
|
|
|
|
value={filters.bathrooms}
|
|
|
|
|
|
onChange={(e) => onFilterChange({ ...filters, bathrooms: e.target.value })}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"
|
2026-07-15 04:38:32 -07:00
|
|
|
|
>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<option value="all">{t("all-numbers", "الكل")}</option>
|
2026-07-15 04:38:32 -07:00
|
|
|
|
<option value="1">1+</option>
|
|
|
|
|
|
<option value="2">2+</option>
|
|
|
|
|
|
<option value="3">3+</option>
|
|
|
|
|
|
<option value="4">4+</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("certificate-label", "الشهادة / السند العقاري")}</label>
|
2026-07-15 04:38:32 -07:00
|
|
|
|
<select
|
|
|
|
|
|
value={filters.certificate}
|
|
|
|
|
|
onChange={(e) => onFilterChange({ ...filters, certificate: e.target.value })}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"
|
2026-07-15 04:38:32 -07:00
|
|
|
|
>
|
|
|
|
|
|
{certificates.map((cert) => (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<option key={cert.id} value={cert.id}>
|
|
|
|
|
|
{cert.label}
|
|
|
|
|
|
</option>
|
2026-07-15 04:38:32 -07:00
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("area-sqm", "المساحة (م²)")}</label>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
2026-08-11 16:04:09 +03:00
|
|
|
|
placeholder={t("from", "من")}
|
2026-07-15 04:38:32 -07:00
|
|
|
|
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"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
value={filters.minArea}
|
|
|
|
|
|
onChange={(e) => onFilterChange({ ...filters, minArea: e.target.value })}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
2026-08-11 16:04:09 +03:00
|
|
|
|
placeholder={t("to", "إلى")}
|
2026-07-15 04:38:32 -07:00
|
|
|
|
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"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
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
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
{t("reset", "إعادة تعيين")}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</button>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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", "تطبيق")}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</AnimatePresence>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default function PropertiesPage() {
|
2026-07-01 15:43:58 +03:00
|
|
|
|
const { t, i18n } = useTranslation();
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const [purposeTab, setPurposeTab] = useState("rent");
|
|
|
|
|
|
const [viewMode, setViewMode] = useState("grid");
|
|
|
|
|
|
const [sortBy, setSortBy] = useState("newest");
|
2026-03-28 17:48:00 +00:00
|
|
|
|
const [properties, setProperties] = useState([]);
|
2026-03-26 22:20:33 +00:00
|
|
|
|
const [loading, setLoading] = useState(true);
|
2026-03-30 18:34:19 +00:00
|
|
|
|
const [showLoginDialog, setShowLoginDialog] = useState(false);
|
2026-02-15 01:53:37 +03:00
|
|
|
|
const [filters, setFilters] = useState({
|
2026-08-11 16:04:09 +03:00
|
|
|
|
search: "",
|
|
|
|
|
|
propertyType: "all",
|
|
|
|
|
|
buildingType: "all",
|
|
|
|
|
|
city: "all",
|
|
|
|
|
|
priceRange: "all",
|
|
|
|
|
|
bedrooms: "all",
|
|
|
|
|
|
bathrooms: "all",
|
|
|
|
|
|
certificate: "all",
|
|
|
|
|
|
minArea: "",
|
|
|
|
|
|
maxArea: "",
|
|
|
|
|
|
features: [],
|
2026-02-15 01:53:37 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-03-26 22:20:33 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
async function fetchProperties() {
|
2026-07-15 04:38:32 -07:00
|
|
|
|
setLoading(true);
|
2026-03-26 22:20:33 +00:00
|
|
|
|
try {
|
2026-07-15 04:38:32 -07:00
|
|
|
|
const queryParams = new URLSearchParams();
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (filters.bedrooms !== "all") {
|
|
|
|
|
|
queryParams.append("numberOfBedRooms", filters.bedrooms);
|
2026-07-15 04:38:32 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (filters.bathrooms !== "all") {
|
|
|
|
|
|
queryParams.append("numberOfBathRooms", filters.bathrooms);
|
2026-07-15 04:38:32 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (filters.minArea) {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
queryParams.append("space", filters.minArea);
|
2026-07-15 04:38:32 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const buildingTypeReverseMap = {
|
|
|
|
|
|
apartment: "Apartment",
|
|
|
|
|
|
villa: "Villa",
|
|
|
|
|
|
sweet: "Sweet",
|
|
|
|
|
|
room: "Room",
|
|
|
|
|
|
studio: "Studio",
|
|
|
|
|
|
office: "Office",
|
|
|
|
|
|
farms: "Farms",
|
|
|
|
|
|
shop: "Shop",
|
|
|
|
|
|
warehouse: "Warehouse",
|
2026-07-15 04:38:32 -07:00
|
|
|
|
};
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (filters.buildingType !== "all" && buildingTypeReverseMap[filters.buildingType] !== undefined) {
|
|
|
|
|
|
queryParams.append("buildingType", buildingTypeReverseMap[filters.buildingType]);
|
2026-07-15 04:38:32 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const propertyTypeReverseMap = {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
residential: "Residential",
|
|
|
|
|
|
commercial: "Commercial",
|
|
|
|
|
|
agricultural: "Agricultural",
|
2026-07-15 04:38:32 -07:00
|
|
|
|
};
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (filters.propertyType !== "all" && propertyTypeReverseMap[filters.propertyType] !== undefined) {
|
|
|
|
|
|
queryParams.append("propertyType", propertyTypeReverseMap[filters.propertyType]);
|
2026-07-15 04:38:32 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const certificateReverseMap = {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
realEstateTitle: "RealEstateTitle",
|
|
|
|
|
|
courtRuling: "CourtRuling",
|
|
|
|
|
|
notary: "Notary",
|
|
|
|
|
|
shares: "Shares",
|
2026-07-15 04:38:32 -07:00
|
|
|
|
};
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (filters.certificate !== "all" && certificateReverseMap[filters.certificate] !== undefined) {
|
|
|
|
|
|
queryParams.append("certificate", certificateReverseMap[filters.certificate]);
|
2026-07-15 04:38:32 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (filters.city !== "all") {
|
2026-07-15 04:38:32 -07:00
|
|
|
|
const cityFormatted = filters.city.charAt(0).toUpperCase() + filters.city.slice(1);
|
2026-08-11 16:04:09 +03:00
|
|
|
|
queryParams.append("city", cityFormatted);
|
2026-07-15 04:38:32 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://45.93.137.91/api";
|
2026-07-15 04:38:32 -07:00
|
|
|
|
const rentEndpoint = `${baseUrl}/RentProperties/FilterRentProperties?${queryParams.toString()}`;
|
|
|
|
|
|
|
|
|
|
|
|
const [rentRes, saleData] = await Promise.all([
|
|
|
|
|
|
fetch(rentEndpoint, {
|
|
|
|
|
|
headers: {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
Accept: "application/json",
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((res) => res.json())
|
|
|
|
|
|
.catch(() => ({ data: [], isSuccess: false })),
|
2026-03-26 22:20:33 +00:00
|
|
|
|
getSaleProperties().catch(() => []),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
2026-07-15 04:38:32 -07:00
|
|
|
|
const rentList = rentRes.isSuccess && Array.isArray(rentRes.data) ? rentRes.data : [];
|
2026-03-26 22:20:33 +00:00
|
|
|
|
const saleList = Array.isArray(saleData) ? saleData : [];
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
const mapped = [...rentList.map((p, i) => ({ ...mapApiProperty(t, p, i), purpose: "rent" })), ...saleList.map((p, i) => ({ ...mapApiProperty(t, p, rentList.length + i), purpose: "sale" }))];
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
2026-07-15 04:38:32 -07:00
|
|
|
|
setProperties(mapped);
|
2026-03-26 22:20:33 +00:00
|
|
|
|
} catch (err) {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
console.error("[Properties] Failed to fetch properties:", err);
|
2026-03-26 22:20:33 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
}
|
2026-03-26 22:20:33 +00:00
|
|
|
|
|
|
|
|
|
|
fetchProperties();
|
2026-07-15 04:38:32 -07:00
|
|
|
|
}, [filters, t]);
|
2026-02-15 01:53:37 +03:00
|
|
|
|
|
|
|
|
|
|
const filteredProperties = properties
|
2026-08-11 16:04:09 +03:00
|
|
|
|
.filter((p) => p.purpose === purposeTab)
|
|
|
|
|
|
.filter((property) => {
|
2026-02-15 01:53:37 +03:00
|
|
|
|
if (filters.search && !property.title.includes(filters.search) && !property.description.includes(filters.search)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
if (filters.priceRange !== "all") {
|
|
|
|
|
|
const [min, max] = filters.priceRange.split("-");
|
2026-02-15 01:53:37 +03:00
|
|
|
|
if (max) {
|
|
|
|
|
|
if (property.price < parseInt(min) || property.price > parseInt(max)) return false;
|
2026-08-11 16:04:09 +03:00
|
|
|
|
} else if (filters.priceRange.endsWith("+")) {
|
|
|
|
|
|
const minVal = parseInt(filters.priceRange.replace("+", ""));
|
2026-03-26 22:20:33 +00:00
|
|
|
|
if (property.price < minVal) return false;
|
2026-02-15 01:53:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
})
|
|
|
|
|
|
.sort((a, b) => {
|
2026-03-26 22:20:33 +00:00
|
|
|
|
switch (sortBy) {
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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;
|
2026-02-15 01:53:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<div dir={i18n.language === "ar" ? "rtl" : "ltr"} className="min-h-screen bg-gray-50 py-8">
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<div className="container mx-auto px-4">
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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>
|
2026-05-26 19:53:28 +03:00
|
|
|
|
|
|
|
|
|
|
<div className="flex justify-center mt-4">
|
|
|
|
|
|
<div className="inline-flex bg-gray-100 rounded-xl p-1">
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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", "للإيجار")}
|
2026-05-26 19:53:28 +03:00
|
|
|
|
</button>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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", "للبيع")}
|
2026-05-26 19:53:28 +03:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-26 22:20:33 +00:00
|
|
|
|
{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>
|
|
|
|
|
|
)}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
<FilterBar filters={filters} onFilterChange={setFilters} />
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex justify-between items-center my-6">
|
|
|
|
|
|
<div className="text-gray-600">
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<span className="font-bold text-gray-900">{filteredProperties.length}</span> {t("property-available", "عقار متاح")}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex gap-3">
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={sortBy}
|
|
|
|
|
|
onChange={(e) => setSortBy(e.target.value)}
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"
|
2026-02-15 01:53:37 +03:00
|
|
|
|
>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</select>
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<button
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"}`}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
>
|
|
|
|
|
|
<Grid3x3 className="w-5 h-5" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
2026-08-11 16:04:09 +03:00
|
|
|
|
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"}`}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
>
|
|
|
|
|
|
<List className="w-5 h-5" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<div className={viewMode === "grid" ? "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" : "space-y-4"}>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
{filteredProperties.map((property) => (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<PropertyCard key={property.id} property={property} viewMode={viewMode} type={property.purpose} onLoginRequired={() => setShowLoginDialog(true)} />
|
2026-02-15 01:53:37 +03:00
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-07-15 04:38:32 -07:00
|
|
|
|
{!loading && filteredProperties.length === 0 && (
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="text-center py-16">
|
2026-02-15 01:53:37 +03:00
|
|
|
|
<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>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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>
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</motion.div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-03-30 17:54:42 +00:00
|
|
|
|
<Toaster position="top-center" />
|
2026-03-30 18:34:19 +00:00
|
|
|
|
{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>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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>
|
2026-03-30 18:34:19 +00:00
|
|
|
|
<div className="flex gap-3">
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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", "إلغاء")}
|
2026-03-30 18:34:19 +00:00
|
|
|
|
</button>
|
2026-08-11 16:04:09 +03:00
|
|
|
|
<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", "دخول")}
|
2026-03-30 18:34:19 +00:00
|
|
|
|
</Link>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-02-15 01:53:37 +03:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
2026-08-11 16:04:09 +03:00
|
|
|
|
}
|