forked from amer2004/SweetHome
Added translation to all site with edit style
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useMemo, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import toast, { Toaster } from "react-hot-toast";
|
||||
import Link from "next/link";
|
||||
@ -149,62 +150,9 @@ function buildImageUrl(img) {
|
||||
return `${apiBase}${img.startsWith("/") ? "" : "/Pictures/"}${img}`;
|
||||
}
|
||||
|
||||
const serviceLabels = {
|
||||
Electricity: "كهرباء",
|
||||
Internet: "إنترنت",
|
||||
Heating: "تدفئة",
|
||||
Water: "ماء",
|
||||
Pool: "مسبح",
|
||||
PrivateGarden: "حديقة خاصة",
|
||||
Parking: "موقف سيارات",
|
||||
Security247: "حراسة 24 س",
|
||||
CentralHeating: "تدفئة مركزية",
|
||||
CentralAirConditioning: "تكييف مركزي",
|
||||
EquippedKitchen: "مطبخ مجهز",
|
||||
MaidsRoom: "غرفة خادمة",
|
||||
Elevator: "مصعد",
|
||||
Gym: "نادي رياضي",
|
||||
Sauna: "ساونا",
|
||||
Jacuzzi: "جاكوزي",
|
||||
Balcony: "بلكونة",
|
||||
Rooftop: "سطح",
|
||||
Furnished: "مفروش",
|
||||
AirConditioning: "تكييف",
|
||||
SatelliteTV: "تلفاز",
|
||||
Fireplace: "مدفأة",
|
||||
StudyRoom: "غرفة دراسة",
|
||||
Storage: "مستودع",
|
||||
Laundry: "غرفة غسيل",
|
||||
SmartHome: "منزل ذكي",
|
||||
};
|
||||
|
||||
const termLabels = {
|
||||
NoSmoking: "ممنوع التدخين",
|
||||
NoAnimals: "ممنوع الحيوانات الأليفة",
|
||||
NoParties: "ممنوع الحفلات",
|
||||
NoAlcohol: "ممنوع الكحول",
|
||||
SuitableForChildren: "مناسب للأطفال",
|
||||
SuitableForFamilies: "مناسب للعائلات",
|
||||
SuitableForStudents: "مناسب للطلاب",
|
||||
SuitableForElderly: "مناسب لكبار السن",
|
||||
OnlyFemales: "إناث فقط",
|
||||
OnlyMales: "ذكور فقط",
|
||||
};
|
||||
|
||||
const proximityLabels = {
|
||||
School: "مدرسة",
|
||||
Hospital: "مستشفى",
|
||||
Restaurant: "مطعم",
|
||||
University: "جامعة",
|
||||
Park: "حديقة",
|
||||
Mall: "مركز تسوق",
|
||||
Supermarket: "سوبر ماركت",
|
||||
Pharmacy: "صيدلية",
|
||||
Mosque: "مسجد",
|
||||
Bank: "بنك",
|
||||
Airport: "مطار",
|
||||
BusStation: "موقف باص",
|
||||
};
|
||||
const svcKey = (k) => "propertyService." + k.charAt(0).toLowerCase() + k.slice(1);
|
||||
const termKey = (k) => "propertyTerm." + k.charAt(0).toLowerCase() + k.slice(1);
|
||||
const proxKey = (k) => "proximity." + k.toLowerCase();
|
||||
|
||||
function mapApiDetail(item) {
|
||||
if (!item) return null;
|
||||
@ -231,30 +179,10 @@ function mapApiDetail(item) {
|
||||
BuildingTypeKeys[info.buildingType] ??
|
||||
BuildingTypeKeys[item.type] ??
|
||||
"apartment";
|
||||
const typeLabel =
|
||||
{
|
||||
apartment: "شقة",
|
||||
villa: "فيلا",
|
||||
house: "بيت",
|
||||
sweet: "سويت",
|
||||
studio: "استوديو",
|
||||
office: "مكتب",
|
||||
shop: "متجر",
|
||||
warehouse: "مستودع",
|
||||
farms: "مزرعة",
|
||||
room: "غرفة ضمن شقة",
|
||||
}[propType] || "عقار";
|
||||
const status =
|
||||
PropertyStatusKeys[info.status] ??
|
||||
PropertyStatusKeys[item.status] ??
|
||||
"available";
|
||||
const statusLabel =
|
||||
{
|
||||
available: "متاح",
|
||||
booked: "محجوز",
|
||||
notAvailable: "غير متاح",
|
||||
maintenance: "صيانة",
|
||||
}[status] || "متاح";
|
||||
|
||||
const rawImages = Array.isArray(info.images) ? info.images : [];
|
||||
const photosFallback = Array.isArray(details.photos) ? details.photos : [];
|
||||
@ -312,10 +240,10 @@ function mapApiDetail(item) {
|
||||
return {
|
||||
id: item.id,
|
||||
propertyInformationId: info.id,
|
||||
title: details.description || info.address || `عقار #${item.id}`,
|
||||
title: details.description || info.address || "",
|
||||
description: details.description || info.description || "",
|
||||
type: propType,
|
||||
typeLabel,
|
||||
typeLabel: propType,
|
||||
price,
|
||||
priceUnit,
|
||||
priceDisplay: { daily: dailyPrice, monthly: monthlyPrice, sale: salePrice },
|
||||
@ -324,7 +252,7 @@ function mapApiDetail(item) {
|
||||
propertyCondition,
|
||||
furnished,
|
||||
location: {
|
||||
city: extractCity(info.address) || "دمشق",
|
||||
city: extractCity(info.address) || "damascus",
|
||||
address: info.address || "",
|
||||
lat: parseFloat(info.cordsX) || 0,
|
||||
lng: parseFloat(info.cordsY) || 0,
|
||||
@ -337,7 +265,7 @@ function mapApiDetail(item) {
|
||||
balconies,
|
||||
images,
|
||||
status,
|
||||
statusLabel,
|
||||
statusLabel: status,
|
||||
services,
|
||||
terms,
|
||||
proximity,
|
||||
@ -355,6 +283,7 @@ function mapApiDetail(item) {
|
||||
}
|
||||
|
||||
export default function PropertyDetailsPage() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const { isFavorite, addFavorite, removeFavorite } = useFavorites();
|
||||
@ -483,7 +412,7 @@ export default function PropertyDetailsPage() {
|
||||
setContactInfo(info);
|
||||
setShowContact(true);
|
||||
} catch (err) {
|
||||
toast.error("فشل تحميل معلومات الاتصال");
|
||||
toast.error(t("contactLoadFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
@ -501,7 +430,7 @@ export default function PropertyDetailsPage() {
|
||||
await addFavorite(property.id);
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error("حدث خطأ");
|
||||
toast.error(t("errorOccurred"));
|
||||
} finally {
|
||||
setFavLoading(false);
|
||||
}
|
||||
@ -513,7 +442,7 @@ export default function PropertyDetailsPage() {
|
||||
return;
|
||||
}
|
||||
if (!bookingDates.start || !bookingDates.end) {
|
||||
setBookingError("يرجى تحديد تاريخ البداية والنهاية");
|
||||
setBookingError(t("validationDateRequired"));
|
||||
return;
|
||||
}
|
||||
setBookingLoading(true);
|
||||
@ -525,29 +454,23 @@ export default function PropertyDetailsPage() {
|
||||
bookingDates.end,
|
||||
);
|
||||
setBookingSuccess(true);
|
||||
toast.success("تم إرسال طلب الحجز بنجاح");
|
||||
toast.success(t("bookingSentSuccess"));
|
||||
} catch (err) {
|
||||
setBookingError(err.message || "فشل الحجز");
|
||||
setBookingError(err.message || t("bookingFailed"));
|
||||
} finally {
|
||||
setBookingLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const MONTHS_AR = [
|
||||
"يناير",
|
||||
"فبراير",
|
||||
"مارس",
|
||||
"أبريل",
|
||||
"مايو",
|
||||
"يونيو",
|
||||
"يوليو",
|
||||
"أغسطس",
|
||||
"سبتمبر",
|
||||
"أكتوبر",
|
||||
"نوفمبر",
|
||||
"ديسمبر",
|
||||
const MONTH_KEYS = [
|
||||
"month.january", "month.february", "month.march", "month.april",
|
||||
"month.may", "month.june", "month.july", "month.august",
|
||||
"month.september", "month.october", "month.november", "month.december",
|
||||
];
|
||||
const DAY_KEYS = [
|
||||
"dayAbbr.sun", "dayAbbr.mon", "dayAbbr.tue", "dayAbbr.wed",
|
||||
"dayAbbr.thu", "dayAbbr.fri", "dayAbbr.sat",
|
||||
];
|
||||
const DAYS_AR = ["ح", "ن", "ث", "ر", "خ", "ج", "س"];
|
||||
|
||||
const availableDatesSet = useMemo(() => {
|
||||
const dates = new Set();
|
||||
@ -592,7 +515,7 @@ export default function PropertyDetailsPage() {
|
||||
return;
|
||||
}
|
||||
if (!selectedStart || !selectedEnd) {
|
||||
setBookingError("يرجى تحديد تاريخ البداية والنهاية");
|
||||
setBookingError(t("validationDateRequired"));
|
||||
return;
|
||||
}
|
||||
setBookingLoading(true);
|
||||
@ -603,9 +526,9 @@ export default function PropertyDetailsPage() {
|
||||
const endDate = new Date(selectedEnd + "T00:00:00.000").toISOString();
|
||||
await bookReservation(propInfoId, startDate, endDate);
|
||||
setBookingSuccess(true);
|
||||
toast.success("تم إرسال طلب الحجز بنجاح");
|
||||
toast.success(t("bookingSentSuccess"));
|
||||
} catch (err) {
|
||||
setBookingError(err.message || "فشل الحجز");
|
||||
setBookingError(err.message || t("bookingFailed"));
|
||||
} finally {
|
||||
setBookingLoading(false);
|
||||
}
|
||||
@ -636,7 +559,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||
<p className="text-gray-600">جاري تحميل العقار...</p>
|
||||
<p className="text-gray-600">{t("loadingProperty")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -650,15 +573,15 @@ export default function PropertyDetailsPage() {
|
||||
<Home className="w-12 h-12 text-amber-600" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
العقار غير موجود
|
||||
{t("propertyNotFound")}
|
||||
</h2>
|
||||
<p className="text-gray-600 mb-6">لم يتم العثور على العقار المطلوب</p>
|
||||
<p className="text-gray-600 mb-6">{t("propertyNotFoundDesc")}</p>
|
||||
<Link
|
||||
href="/properties"
|
||||
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
العودة إلى العقارات
|
||||
{t("backToProperties")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@ -679,7 +602,7 @@ export default function PropertyDetailsPage() {
|
||||
: "daily";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50" dir="rtl">
|
||||
<div className="min-h-screen bg-gray-50" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||
<Toaster position="top-center" reverseOrder={false} />
|
||||
|
||||
<div className="container mx-auto px-4 py-6">
|
||||
@ -693,7 +616,7 @@ export default function PropertyDetailsPage() {
|
||||
className="inline-flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
العودة إلى العقارات
|
||||
{t("backToProperties")}
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
@ -723,15 +646,15 @@ export default function PropertyDetailsPage() {
|
||||
>
|
||||
<div className="text-center">
|
||||
<ImageIcon className="w-20 h-20 text-gray-500 mx-auto mb-2" />
|
||||
<p className="text-gray-400 text-sm">لا توجد صور</p>
|
||||
<p className="text-gray-400 text-sm">{t("noImages")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isMostRequested && (
|
||||
<div className="absolute top-4 right-4 z-10">
|
||||
<span className="bg-gradient-to-l from-amber-500 to-amber-600 text-white px-3 py-1.5 rounded-full text-xs font-bold shadow-lg flex items-center gap-1">
|
||||
<Star className="w-3 h-3 fill-white" /> الأكثر طلباً
|
||||
<span className="bg-gradient-to-l from-amber-500 to-amber-600 text-white px-3 py-1.5 rounded-full text-xs font-bold shadow-lg flex items-center gap-1">
|
||||
<Star className="w-3 h-3 fill-white" /> {t("mostRequested")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@ -765,10 +688,10 @@ export default function PropertyDetailsPage() {
|
||||
|
||||
<div className="absolute bottom-4 right-4 flex gap-2 z-10">
|
||||
<span className="bg-black/70 text-white px-3 py-1 rounded-full text-sm font-medium backdrop-blur-sm">
|
||||
{property.statusLabel}
|
||||
{t("propertyStatus." + property.statusLabel)}
|
||||
</span>
|
||||
<span className="bg-black/70 text-white px-3 py-1 rounded-full text-sm font-medium backdrop-blur-sm">
|
||||
{property.typeLabel}
|
||||
{t("buildingType." + property.typeLabel)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="absolute bottom-4 left-4 bg-black/70 text-white px-3 py-1 rounded-full text-xs backdrop-blur-sm z-10">
|
||||
@ -808,21 +731,21 @@ export default function PropertyDetailsPage() {
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 mb-1 flex-wrap">
|
||||
<span className="px-2 py-0.5 bg-amber-100 text-amber-800 rounded-full text-xs">
|
||||
{property.typeLabel}
|
||||
{t("buildingType." + property.typeLabel)}
|
||||
</span>
|
||||
<span
|
||||
className={`px-2 py-0.5 rounded-full text-xs ${property.status === "available" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"}`}
|
||||
>
|
||||
{property.statusLabel}
|
||||
{t("propertyStatus." + property.statusLabel)}
|
||||
</span>
|
||||
{property.isRent && property.displayType && (
|
||||
<span className="px-2 py-0.5 bg-blue-100 text-blue-800 rounded-full text-xs">
|
||||
{(() => {
|
||||
const dt = property.displayType.toLowerCase();
|
||||
if (dt === "both" || dt.includes("both"))
|
||||
return "يومي وشهري";
|
||||
if (dt.includes("daily")) return "يومي";
|
||||
if (dt.includes("monthly")) return "شهري";
|
||||
return t("dailyAndMonthly");
|
||||
if (dt.includes("daily")) return t("rentType.daily");
|
||||
if (dt.includes("monthly")) return t("rentType.monthly");
|
||||
return dt;
|
||||
})()}
|
||||
</span>
|
||||
@ -830,16 +753,16 @@ export default function PropertyDetailsPage() {
|
||||
<span
|
||||
className={`px-2 py-0.5 rounded-full text-xs ${property.furnished ? "bg-purple-100 text-purple-800" : "bg-gray-100 text-gray-600"}`}
|
||||
>
|
||||
{property.furnished ? "مفروش" : "غير مفروش"}
|
||||
{property.furnished ? t("rentCondition.furnished") : t("rentCondition.unfurnished")}
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold text-gray-900">
|
||||
{property.title}
|
||||
{property.title || t("propertyWithId", { id: property.id })}
|
||||
</h1>
|
||||
<div className="flex items-center gap-1 text-gray-500 text-xs mt-0.5">
|
||||
<MapPin className="w-3 h-3" />
|
||||
<span>
|
||||
{property.location.address || property.location.city}
|
||||
{property.location.address || t("city." + property.location.city)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -863,7 +786,7 @@ export default function PropertyDetailsPage() {
|
||||
<span className="text-2xl font-bold text-amber-600">
|
||||
{formatCurrency(property.priceDisplay.monthly)}
|
||||
</span>
|
||||
<span className="text-gray-500 mr-1">ل.س / شهرياً</span>
|
||||
<span className="text-gray-500 mr-1">{t("sypPerMonth")}</span>
|
||||
</div>
|
||||
)}
|
||||
{property.priceDisplay.daily > 0 && (
|
||||
@ -871,13 +794,13 @@ export default function PropertyDetailsPage() {
|
||||
<span className="text-2xl font-bold text-amber-600">
|
||||
{formatCurrency(property.priceDisplay.daily)}
|
||||
</span>
|
||||
<span className="text-gray-500 mr-1">ل.س / يومياً</span>
|
||||
<span className="text-gray-500 mr-1">{t("sypPerDay")}</span>
|
||||
</div>
|
||||
)}
|
||||
{property.deposit > 0 && (
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="font-medium">تأمين:</span>{" "}
|
||||
{formatCurrency(property.deposit)} ل.س
|
||||
<span className="font-medium">{t("depositLabel")}</span>{" "}
|
||||
{formatCurrency(property.deposit)} {t("syp")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -886,8 +809,8 @@ export default function PropertyDetailsPage() {
|
||||
<span className="text-2xl font-bold text-blue-600">
|
||||
{formatCurrency(property.price)}
|
||||
</span>
|
||||
<span className="text-gray-500 mr-1">ل.س</span>
|
||||
<span className="text-sm text-gray-400 mr-2">للبيع</span>
|
||||
<span className="text-gray-500 mr-1">{t("syp")}</span>
|
||||
<span className="text-sm text-gray-400 mr-2">{t("forSale")}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -900,7 +823,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="font-bold text-gray-900 text-sm">
|
||||
{property.bedrooms}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500">غرف نوم</div>
|
||||
<div className="text-[10px] text-gray-500">{t("bedrooms")}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.bathrooms > 0 && (
|
||||
@ -909,7 +832,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="font-bold text-gray-900 text-sm">
|
||||
{property.bathrooms}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500">حمامات</div>
|
||||
<div className="text-[10px] text-gray-500">{t("bathrooms")}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.area > 0 && (
|
||||
@ -918,7 +841,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="font-bold text-gray-900 text-sm">
|
||||
{property.area}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500">م²</div>
|
||||
<div className="text-[10px] text-gray-500">{t("sqm")}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.floor > 0 && (
|
||||
@ -927,7 +850,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="font-bold text-gray-900 text-sm">
|
||||
{property.floor}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500">طابق</div>
|
||||
<div className="text-[10px] text-gray-500">{t("floor")}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.salons > 0 && (
|
||||
@ -936,7 +859,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="font-bold text-gray-900 text-sm">
|
||||
{property.salons}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500">صالونات</div>
|
||||
<div className="text-[10px] text-gray-500">{t("salons")}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.balconies > 0 && (
|
||||
@ -945,7 +868,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="font-bold text-gray-900 text-sm">
|
||||
{property.balconies}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500">بلكونات</div>
|
||||
<div className="text-[10px] text-gray-500">{t("balconies")}</div>
|
||||
</div>
|
||||
)}
|
||||
{avgRating !== null && avgRating > 0 && (
|
||||
@ -954,7 +877,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="font-bold text-gray-900 text-sm">
|
||||
{avgRating.toFixed(1)}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500">التقييم</div>
|
||||
<div className="text-[10px] text-gray-500">{t("rateAndReview")}</div>
|
||||
</div>
|
||||
)}
|
||||
{property.bookedCount > 0 && (
|
||||
@ -963,7 +886,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="font-bold text-gray-900 text-sm">
|
||||
{property.bookedCount}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500">حجوزات</div>
|
||||
<div className="text-[10px] text-gray-500">{t("bookingCount")}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -972,7 +895,7 @@ export default function PropertyDetailsPage() {
|
||||
{property.description && (
|
||||
<div className="mb-4">
|
||||
<h3 className="font-bold text-gray-900 mb-1 text-sm">
|
||||
الوصف
|
||||
{t("description")}
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm leading-relaxed">
|
||||
{property.description}
|
||||
@ -984,17 +907,17 @@ export default function PropertyDetailsPage() {
|
||||
<div className="flex flex-wrap gap-1.5 mb-4">
|
||||
{property.isSmokeAllow && (
|
||||
<span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1">
|
||||
<Wind className="w-3 h-3" /> يسمح بالتدخين
|
||||
<Wind className="w-3 h-3" /> {t("smokingAllowed")}
|
||||
</span>
|
||||
)}
|
||||
{!property.isSmokeAllow && (
|
||||
<span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1">
|
||||
<Ban className="w-3 h-3" /> ممنوع التدخين
|
||||
<Ban className="w-3 h-3" /> {t("propertyTerm.noSmoking")}
|
||||
</span>
|
||||
)}
|
||||
{property.isVisitorAllow && (
|
||||
<span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1">
|
||||
<Users className="w-3 h-3" /> يسمح بالزوار
|
||||
<Users className="w-3 h-3" /> {t("visitorsAllowed")}
|
||||
</span>
|
||||
)}
|
||||
{property.specializedFor && (
|
||||
@ -1011,7 +934,7 @@ export default function PropertyDetailsPage() {
|
||||
: Object.keys(property.services).length > 0) && (
|
||||
<div className="mb-4">
|
||||
<h3 className="font-bold text-gray-900 mb-2 text-sm">
|
||||
الخدمات
|
||||
{t("services")}
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{Array.isArray(property.services)
|
||||
@ -1020,7 +943,7 @@ export default function PropertyDetailsPage() {
|
||||
key={i}
|
||||
className="px-3 py-1 bg-green-50 text-green-700 rounded-full text-sm border border-green-200 flex items-center gap-1"
|
||||
>
|
||||
{serviceLabels[svc] || svc}
|
||||
{t(svcKey(svc))}
|
||||
</span>
|
||||
))
|
||||
: Object.entries(property.services).map(
|
||||
@ -1037,7 +960,7 @@ export default function PropertyDetailsPage() {
|
||||
key={key}
|
||||
className="px-3 py-1 bg-green-50 text-green-700 rounded-full text-sm border border-green-200 flex items-center gap-1"
|
||||
>
|
||||
{serviceLabels[key] || key}
|
||||
{t(svcKey(key))}
|
||||
{detail && (
|
||||
<span className="text-green-400">
|
||||
· {detail}
|
||||
@ -1056,7 +979,7 @@ export default function PropertyDetailsPage() {
|
||||
<div className="mb-4 bg-blue-50 rounded-xl p-3">
|
||||
<h3 className="font-bold text-gray-900 mb-2 text-sm flex items-center gap-2">
|
||||
<Info className="w-4 h-4 text-blue-500" />
|
||||
تفاصيل الغرفة
|
||||
{t("roomDetails")}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
||||
{(() => {
|
||||
@ -1064,117 +987,117 @@ export default function PropertyDetailsPage() {
|
||||
const items = [];
|
||||
if (rd.areaType)
|
||||
items.push({
|
||||
label: "نوع المساحة",
|
||||
label: t("roomDetail.areaType"),
|
||||
value:
|
||||
rd.areaType === "private room"
|
||||
? "غرفة خاصة"
|
||||
? t("roomDetail.privateRoom")
|
||||
: rd.areaType === "shared room"
|
||||
? "غرفة مشتركة"
|
||||
? t("roomDetail.sharedRoom")
|
||||
: rd.areaType,
|
||||
});
|
||||
if (rd.peopleAllowed)
|
||||
items.push({
|
||||
label: "عدد الأشخاص",
|
||||
label: t("roomDetail.numberOfPeople"),
|
||||
value: rd.peopleAllowed,
|
||||
});
|
||||
if (rd.furnitureDetails || rd.furniture)
|
||||
items.push({
|
||||
label: "الأثاث",
|
||||
label: t("roomDetail.furniture"),
|
||||
value: rd.furnitureDetails || rd.furniture,
|
||||
});
|
||||
if (rd.entranceType)
|
||||
items.push({
|
||||
label: "نوع المدخل",
|
||||
label: t("roomDetail.entranceType"),
|
||||
value:
|
||||
rd.entranceType === "shared entrance"
|
||||
? "مدخل مشترك"
|
||||
? t("roomDetail.sharedEntrance")
|
||||
: rd.entranceType === "independent entrance"
|
||||
? "مدخل مستقل"
|
||||
? t("roomDetail.independentEntrance")
|
||||
: rd.entranceType,
|
||||
});
|
||||
if (rd.bathroomType)
|
||||
items.push({
|
||||
label: "الحمام",
|
||||
label: t("roomDetail.bathroom"),
|
||||
value:
|
||||
rd.bathroomType === "room specific"
|
||||
? "خاص بالغرفة"
|
||||
? t("roomDetail.bathroomPrivate")
|
||||
: rd.bathroomType === "shared"
|
||||
? "مشترك"
|
||||
? t("roomDetail.bathroomShared")
|
||||
: rd.bathroomType,
|
||||
});
|
||||
if (rd.kitchenType)
|
||||
items.push({
|
||||
label: "المطبخ",
|
||||
label: t("roomDetail.kitchen"),
|
||||
value:
|
||||
rd.kitchenType === "shared"
|
||||
? "مشترك"
|
||||
? t("roomDetail.kitchenShared")
|
||||
: rd.kitchenType === "not available"
|
||||
? "غير متوفر"
|
||||
? t("roomDetail.kitchenNotAvailable")
|
||||
: rd.kitchenType,
|
||||
});
|
||||
if (rd.homeResidentsCount ?? rd.residents)
|
||||
items.push({
|
||||
label: "عدد السكان",
|
||||
label: t("roomDetail.residents"),
|
||||
value: rd.homeResidentsCount ?? rd.residents,
|
||||
});
|
||||
if (rd.currentPopulationGender)
|
||||
items.push({
|
||||
label: "جنس السكان",
|
||||
label: t("roomDetail.gender"),
|
||||
value:
|
||||
rd.currentPopulationGender === "men"
|
||||
? "رجال"
|
||||
? t("roomDetail.men")
|
||||
: rd.currentPopulationGender === "women"
|
||||
? "نساء"
|
||||
? t("roomDetail.women")
|
||||
: rd.currentPopulationGender === "family"
|
||||
? "عائلة"
|
||||
? t("roomDetail.family")
|
||||
: rd.currentPopulationGender,
|
||||
});
|
||||
if (rd.dedicatedTo)
|
||||
items.push({
|
||||
label: "مخصص لـ",
|
||||
label: t("roomDetail.dedicatedTo"),
|
||||
value:
|
||||
rd.dedicatedTo === "men only"
|
||||
? "رجال فقط"
|
||||
? t("roomDetail.menOnly")
|
||||
: rd.dedicatedTo === "women only"
|
||||
? "نساء فقط"
|
||||
? t("roomDetail.womenOnly")
|
||||
: rd.dedicatedTo === "families only"
|
||||
? "عائلات فقط"
|
||||
? t("roomDetail.familiesOnly")
|
||||
: rd.dedicatedTo === "everyone"
|
||||
? "الجميع"
|
||||
? t("roomDetail.everyone")
|
||||
: rd.dedicatedTo,
|
||||
});
|
||||
if (rd.hasRestrictedOwnerAreas !== undefined)
|
||||
items.push({
|
||||
label: "مناطق ممنوعة",
|
||||
value: rd.hasRestrictedOwnerAreas ? "نعم" : "لا",
|
||||
label: t("roomDetail.restrictedAreas"),
|
||||
value: rd.hasRestrictedOwnerAreas ? t("roomDetail.yes") : t("roomDetail.no"),
|
||||
});
|
||||
if (rd.hasChildren !== undefined)
|
||||
items.push({
|
||||
label: "أطفال",
|
||||
value: rd.hasChildren ? "مسموح" : "غير مسموح",
|
||||
label: t("roomDetail.children"),
|
||||
value: rd.hasChildren ? t("roomDetail.allowed") : t("roomDetail.notAllowed"),
|
||||
});
|
||||
if (rd.hasPets !== undefined)
|
||||
items.push({
|
||||
label: "حيوانات أليفة",
|
||||
value: rd.hasPets ? "مسموح" : "غير مسموح",
|
||||
label: t("roomDetail.pets"),
|
||||
value: rd.hasPets ? t("roomDetail.allowed") : t("roomDetail.notAllowed"),
|
||||
});
|
||||
if (rd.languageDialect)
|
||||
items.push({
|
||||
label: "اللغة",
|
||||
label: t("roomDetail.language"),
|
||||
value: rd.languageDialect,
|
||||
});
|
||||
if (rd.visitorsAllowed !== undefined)
|
||||
items.push({
|
||||
label: "الزوار",
|
||||
value: rd.visitorsAllowed ? "مسموح" : "ممنوع",
|
||||
label: t("roomDetail.visitors"),
|
||||
value: rd.visitorsAllowed ? t("roomDetail.allowed") : t("roomDetail.forbidden"),
|
||||
});
|
||||
if (rd.quietTimesEnabled ?? rd.quietTimes)
|
||||
items.push({
|
||||
label: "أوقات الهدوء",
|
||||
label: t("roomDetail.quietHours"),
|
||||
value:
|
||||
rd.quietTimesDetails ||
|
||||
rd.quietTimes ||
|
||||
(rd.quietTimesEnabled ? "مفعلة" : ""),
|
||||
(rd.quietTimesEnabled ? t("roomDetail.quietHoursEnabled") : ""),
|
||||
});
|
||||
return items.map((item, i) => (
|
||||
<div
|
||||
@ -1198,7 +1121,7 @@ export default function PropertyDetailsPage() {
|
||||
{Object.keys(property.proximity).length > 0 && (
|
||||
<div className="mb-4">
|
||||
<h3 className="font-bold text-gray-900 mb-2 text-sm">
|
||||
القرب من الخدمات
|
||||
{t("proximityToServices")}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-1.5">
|
||||
{Object.entries(property.proximity).map(([key, val]) => {
|
||||
@ -1239,10 +1162,10 @@ export default function PropertyDetailsPage() {
|
||||
)}
|
||||
<div>
|
||||
<div className="text-xs font-medium text-gray-900">
|
||||
{proximityLabels[key] || key}
|
||||
{t(proxKey(key))}
|
||||
</div>
|
||||
<div className="text-[10px] text-gray-500">
|
||||
{dist} {typeof dist === "number" ? "كم" : ""}
|
||||
{dist} {typeof dist === "number" ? t("km") : ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1256,7 +1179,7 @@ export default function PropertyDetailsPage() {
|
||||
{Object.keys(property.terms).length > 0 && (
|
||||
<div className="mb-4">
|
||||
<h3 className="font-bold text-gray-900 mb-2 text-sm">
|
||||
الشروط
|
||||
{t("terms")}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-1.5">
|
||||
{Object.entries(property.terms).map(([key, val]) => {
|
||||
@ -1272,7 +1195,7 @@ export default function PropertyDetailsPage() {
|
||||
<Check className="w-3.5 h-3.5 text-green-500 flex-shrink-0" />
|
||||
)}
|
||||
<span className="text-xs text-gray-700">
|
||||
{termLabels[key] || key}
|
||||
{t(termKey(key))}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@ -1298,7 +1221,7 @@ export default function PropertyDetailsPage() {
|
||||
</div>
|
||||
<div className="p-3 bg-amber-50 text-center text-sm text-amber-700 flex items-center justify-center gap-2">
|
||||
<Info className="w-4 h-4" />
|
||||
<span>موقع تقريبي — يظهر الموقع الدقيق بعد تأكيد الحجز</span>
|
||||
<span>{t("approximateLocation")}</span>
|
||||
</div>
|
||||
<div className="p-3 border-t border-gray-100">
|
||||
<a
|
||||
@ -1308,7 +1231,7 @@ export default function PropertyDetailsPage() {
|
||||
className="flex items-center justify-center gap-2 text-blue-600 hover:text-blue-700 font-medium text-sm"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
فتح في Google Maps
|
||||
{t("openInGoogleMaps")}
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
@ -1340,10 +1263,10 @@ export default function PropertyDetailsPage() {
|
||||
<Home className="w-7 h-7 text-gray-400" />
|
||||
</div>
|
||||
<h4 className="font-bold text-gray-700 text-sm mb-1">
|
||||
هذا عقارك
|
||||
{t("thisIsYourProperty")}
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500">
|
||||
لا يمكنك حجز عقارك الخاص
|
||||
{t("cannotBookOwnProperty")}
|
||||
</p>
|
||||
</div>
|
||||
) : bookingSuccess ? (
|
||||
@ -1352,10 +1275,10 @@ export default function PropertyDetailsPage() {
|
||||
<Check className="w-7 h-7 text-green-600" />
|
||||
</div>
|
||||
<h4 className="font-bold text-green-700 text-sm mb-1">
|
||||
تم إرسال طلب الحجز
|
||||
{t("bookingRequestSent")}
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500">
|
||||
سيتم مراجعة طلبك من قبل المالك
|
||||
{t("bookingRequestWillBeReviewed")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@ -1368,10 +1291,10 @@ export default function PropertyDetailsPage() {
|
||||
className={`p-2.5 rounded-xl text-center border-2 transition-all ${effectivePricingMode === "daily" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300"}`}
|
||||
>
|
||||
<div className="text-xs font-bold text-gray-900">
|
||||
إيجار يومي
|
||||
{t("dailyRentLabel")}
|
||||
</div>
|
||||
<div className="text-sm font-bold text-amber-600">
|
||||
{formatCurrency(property.priceDisplay.daily)} ل.س
|
||||
{formatCurrency(property.priceDisplay.daily)} {t("syp")}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
@ -1379,10 +1302,10 @@ export default function PropertyDetailsPage() {
|
||||
className={`p-2.5 rounded-xl text-center border-2 transition-all ${effectivePricingMode === "monthly" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300"}`}
|
||||
>
|
||||
<div className="text-xs font-bold text-gray-900">
|
||||
إيجار شهري
|
||||
{t("monthlyRentLabel")}
|
||||
</div>
|
||||
<div className="text-sm font-bold text-amber-600">
|
||||
{formatCurrency(property.priceDisplay.monthly)} ل.س
|
||||
{formatCurrency(property.priceDisplay.monthly)} {t("syp")}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@ -1396,7 +1319,7 @@ export default function PropertyDetailsPage() {
|
||||
<div
|
||||
className={`w-1.5 h-1.5 rounded-full ${bookingStep === "entry" ? "bg-amber-500" : "bg-gray-400"}`}
|
||||
/>
|
||||
تحديد تاريخ البداية
|
||||
{t("selectStartDate")}
|
||||
</div>
|
||||
<div className="text-gray-300 text-xs">←</div>
|
||||
<div
|
||||
@ -1405,7 +1328,7 @@ export default function PropertyDetailsPage() {
|
||||
<div
|
||||
className={`w-1.5 h-1.5 rounded-full ${bookingStep === "exit" ? "bg-amber-500" : "bg-gray-400"}`}
|
||||
/>
|
||||
تحديد تاريخ النهاية
|
||||
{t("selectEndDate")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1420,7 +1343,7 @@ export default function PropertyDetailsPage() {
|
||||
<ChevronRight className="w-4 h-4 text-gray-600" />
|
||||
</button>
|
||||
<span className="text-sm font-bold text-gray-900">
|
||||
{MONTHS_AR[calendarMonth]} {calendarYear}
|
||||
{t(MONTH_KEYS[calendarMonth])} {calendarYear}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => navigateMonth(1)}
|
||||
@ -1431,12 +1354,12 @@ export default function PropertyDetailsPage() {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-7 mb-1">
|
||||
{DAYS_AR.map((d, i) => (
|
||||
{DAY_KEYS.map((dk, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-center text-[10px] text-gray-400 font-medium py-1"
|
||||
>
|
||||
{d}
|
||||
{t(dk)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@ -1509,7 +1432,7 @@ export default function PropertyDetailsPage() {
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{MONTHS_AR.map((name, idx) => {
|
||||
{MONTH_KEYS.map((mk, idx) => {
|
||||
const monthStr = `${calendarYear}-${String(idx + 1).padStart(2, "0")}`;
|
||||
const isSelStart =
|
||||
selectedStart &&
|
||||
@ -1548,7 +1471,7 @@ export default function PropertyDetailsPage() {
|
||||
}}
|
||||
className={`p-2.5 rounded-xl text-center text-xs font-medium transition-all border ${isSelStart || isSelEnd ? "bg-amber-500 text-white border-amber-500 shadow-sm" : inRange ? "bg-amber-100 text-amber-800 border-amber-200" : "bg-white text-gray-700 border-gray-200 hover:border-amber-300"}`}
|
||||
>
|
||||
{name}
|
||||
{t(mk)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@ -1560,7 +1483,7 @@ export default function PropertyDetailsPage() {
|
||||
{selectedStart && (
|
||||
<div className="bg-gray-50 rounded-xl p-3 mb-3 space-y-1.5">
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-gray-500">تاريخ البداية</span>
|
||||
<span className="text-gray-500">{t("startDateLabel")}</span>
|
||||
<span className="font-medium text-gray-900">
|
||||
{selectedStart}
|
||||
</span>
|
||||
@ -1569,7 +1492,7 @@ export default function PropertyDetailsPage() {
|
||||
<>
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-gray-500">
|
||||
تاريخ النهاية
|
||||
{t("endDateLabel")}
|
||||
</span>
|
||||
<span className="font-medium text-gray-900">
|
||||
{selectedEnd}
|
||||
@ -1579,8 +1502,8 @@ export default function PropertyDetailsPage() {
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-gray-500">
|
||||
{effectivePricingMode === "daily"
|
||||
? "عدد الأيام"
|
||||
: "عدد الأشهر"}
|
||||
? t("numberOfDays")
|
||||
: t("numberOfMonths")}
|
||||
</span>
|
||||
<span className="font-medium text-gray-900">
|
||||
{effectivePricingMode === "daily"
|
||||
@ -1601,7 +1524,7 @@ export default function PropertyDetailsPage() {
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs font-bold">
|
||||
<span className="text-gray-700">المجموع</span>
|
||||
<span className="text-gray-700">{t("total")}</span>
|
||||
<span className="text-amber-600">
|
||||
{formatCurrency(
|
||||
effectivePricingMode === "daily"
|
||||
@ -1623,14 +1546,14 @@ export default function PropertyDetailsPage() {
|
||||
1) *
|
||||
property.priceDisplay.monthly,
|
||||
)}{" "}
|
||||
ل.س
|
||||
{t("syp")}
|
||||
</span>
|
||||
</div>
|
||||
{property.deposit > 0 && (
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-gray-500">تأمين</span>
|
||||
<span className="text-gray-500">{t("deposit")}</span>
|
||||
<span className="font-medium text-gray-900">
|
||||
{formatCurrency(property.deposit)} ل.س
|
||||
{formatCurrency(property.deposit)} {t("syp")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@ -1657,7 +1580,7 @@ export default function PropertyDetailsPage() {
|
||||
) : (
|
||||
<Calendar className="w-4 h-4" />
|
||||
)}
|
||||
{bookingLoading ? "جاري الحجز..." : "تأكيد الحجز"}
|
||||
{bookingLoading ? t("bookingInProgress") : t("confirmBooking")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@ -1669,7 +1592,7 @@ export default function PropertyDetailsPage() {
|
||||
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="bg-white rounded-2xl p-5 shadow-sm border border-gray-200">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Phone className="w-4 h-4 text-amber-500" />
|
||||
<h3 className="font-bold text-gray-900">معلومات المالك</h3>
|
||||
<h3 className="font-bold text-gray-900">{t("ownerInfo")}</h3>
|
||||
</div>
|
||||
|
||||
{showContact && contactInfo ? (
|
||||
@ -1690,7 +1613,7 @@ export default function PropertyDetailsPage() {
|
||||
<button onClick={fetchContactInfo}
|
||||
className="w-full bg-gray-800 hover:bg-gray-900 text-white py-2.5 rounded-xl font-medium text-sm transition-colors flex items-center justify-center gap-2">
|
||||
<Phone className="w-4 h-4" />
|
||||
عرض معلومات الاتصال
|
||||
{t("showContactInfo")}
|
||||
</button>
|
||||
)}
|
||||
</motion.div>
|
||||
@ -1712,27 +1635,27 @@ export default function PropertyDetailsPage() {
|
||||
<div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<LogIn className="w-8 h-8 text-amber-600" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">تسجيل الدخول مطلوب</h3>
|
||||
<h3 className="text-xl font-bold mb-2">{t("loginRequiredTitle")}</h3>
|
||||
<p className="text-gray-500 mb-4">
|
||||
للحجز أو إضافة المفضلة، سجل دخولك.
|
||||
{t("loginRequiredDesc")}
|
||||
</p>
|
||||
<Link
|
||||
href="/login"
|
||||
className="block w-full bg-amber-500 text-white py-3 rounded-xl font-medium mb-2 hover:bg-amber-600"
|
||||
>
|
||||
تسجيل الدخول
|
||||
{t("login")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/choose-role"
|
||||
className="block w-full bg-gray-100 py-3 rounded-xl font-medium hover:bg-gray-200"
|
||||
>
|
||||
إنشاء حساب
|
||||
{t("createAccount")}
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setShowLoginDialog(false)}
|
||||
className="mt-3 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
إلغاء
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -31,7 +31,7 @@ function mapProperty(item) {
|
||||
const priceUnit = item.monthlyRent || item.MonthlyRent ? 'monthly' : 'daily';
|
||||
const buildingType = info.buildingType ?? info.BuildingType ?? 0;
|
||||
const type = { 0: 'apartment', 1: 'villa', 2: 'house' }[buildingType] || 'apartment';
|
||||
const typeLabel = { 0: 'شقة', 1: 'فيلا', 2: 'بيت' }[buildingType] || 'عقار';
|
||||
const typeLabel = { 0: 'apartment', 1: 'villa', 2: 'house' }[buildingType] || 'apartment';
|
||||
const address = info.address || info.Address || '';
|
||||
const bedrooms = info.numberOfBedRooms || info.NumberOfBedRooms || 0;
|
||||
const bathrooms = info.numberOfBathRooms || info.NumberOfBathRooms || 0;
|
||||
|
||||
Reference in New Issue
Block a user