the best in the west is mouaz
All checks were successful
Build frontend / build (push) Successful in 55s

This commit is contained in:
mouazkh
2026-05-25 21:27:39 +03:00
parent a5577765ed
commit 00ccf5f262
35 changed files with 4876 additions and 2433 deletions

View File

@ -0,0 +1,238 @@
'use client';
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { useRouter } from 'next/navigation';
import {
DollarSign,
TrendingUp,
Calendar,
Users,
Building,
Download,
Loader2,
ArrowLeft,
} from 'lucide-react';
import toast, { Toaster } from 'react-hot-toast';
import AuthService from '@/app/services/AuthService';
import { getOwnerStatistics } from '@/app/utils/api';
const StatCard = ({ title, value, icon: Icon, color, subtitle }) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-all"
>
<div className="flex items-center justify-between mb-4">
<div className={`w-12 h-12 ${color} rounded-xl flex items-center justify-center`}>
<Icon className="w-6 h-6 text-white" />
</div>
</div>
<h3 className="text-sm text-gray-500 mb-1">{title}</h3>
<div className="text-2xl font-bold text-gray-900">{value}</div>
{subtitle && <p className="text-xs text-gray-400 mt-1">{subtitle}</p>}
</motion.div>
);
export default function OwnerAccountBookPage() {
const router = useRouter();
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [stats, setStats] = useState({
totalRevenue: 0,
totalReservations: 0,
activeProperties: 0,
});
const [transactions, setTransactions] = useState([]);
useEffect(() => {
if (AuthService.isGuest()) {
router.push('/auth/choose-role');
return;
}
if (!AuthService.isOwner()) {
router.push('/');
return;
}
const authUser = AuthService.getUser();
if (authUser) {
setUser({
name: authUser.name || authUser.email,
email: authUser.email,
});
}
async function fetchData() {
try {
const data = await getOwnerStatistics();
if (data) {
setStats({
totalRevenue: data.totalRevenue ?? 0,
totalReservations: data.totalReservations ?? 0,
activeProperties: data.activeProperties ?? 0,
});
if (data.transactions) {
setTransactions(data.transactions);
}
}
} catch {
toast.error('تعذر تحميل إحصائيات الحساب');
} finally {
setIsLoading(false);
}
}
fetchData();
}, [router]);
const formatCurrency = (amount) => {
const num = Number(amount) || 0;
return num.toLocaleString() + ' ل.س';
};
const handleExport = () => {
toast.success('جاري تصدير البيانات...');
};
if (isLoading) {
return (
<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>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50 py-8" dir="rtl">
<Toaster position="top-center" reverseOrder={false} />
<div className="container mx-auto px-4 max-w-7xl">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
>
<div className="flex items-center gap-4">
<button
onClick={() => router.back()}
className="p-2 hover:bg-gray-200 rounded-xl transition-colors"
>
<ArrowLeft className="w-5 h-5 text-gray-600" />
</button>
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">دفتر الحسابات</h1>
<p className="text-gray-600">
مرحباً {user?.name}، نظرة عامة على حساباتك
</p>
</div>
</div>
<button
onClick={handleExport}
className="px-5 py-2.5 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors flex items-center gap-2 shadow-sm"
>
<Download className="w-5 h-5" />
تصدير
</button>
</motion.div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
<StatCard
title="إجمالي الإيرادات"
value={formatCurrency(stats.totalRevenue)}
icon={DollarSign}
color="bg-green-500"
/>
<StatCard
title="إجمالي الحجوزات"
value={stats.totalReservations}
icon={Calendar}
color="bg-blue-500"
/>
<StatCard
title="العقارات النشطة"
value={stats.activeProperties}
icon={Building}
color="bg-amber-500"
/>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden"
>
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-lg font-bold text-gray-900">المعاملات المالية</h2>
</div>
{transactions.length > 0 ? (
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 text-sm">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">
التاريخ
</th>
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">
البيان
</th>
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">
المبلغ
</th>
<th className="px-4 py-3 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">
الحالة
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-100">
{transactions.map((tx, idx) => (
<tr
key={tx.id || idx}
className={`hover:bg-amber-50/40 transition-colors ${
idx % 2 === 0 ? 'bg-white' : 'bg-gray-50'
}`}
>
<td className="px-4 py-3 whitespace-nowrap text-center text-gray-700">
{tx.date}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center text-gray-800 font-medium">
{tx.description}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center font-mono font-semibold text-gray-800">
{formatCurrency(tx.amount)}
</td>
<td className="px-4 py-3 whitespace-nowrap text-center">
<span
className={`inline-flex items-center px-2 py-1 rounded-lg text-xs font-medium ${
tx.status === 'completed'
? 'bg-green-100 text-green-800'
: tx.status === 'pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'
}`}
>
{tx.status === 'completed'
? 'مكتمل'
: tx.status === 'pending'
? 'قيد الانتظار'
: 'ملغي'}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="p-12 text-center">
<DollarSign className="w-12 h-12 text-gray-300 mx-auto mb-4" />
<p className="text-gray-500">لا توجد معاملات مالية بعد</p>
</div>
)}
</motion.div>
</div>
</div>
);
}

View File

@ -2,7 +2,7 @@
import { useState, useRef, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useRouter } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import Image from 'next/image';
import dynamic from 'next/dynamic';
@ -48,10 +48,11 @@ import {
Minus,
Save,
Wind,
Move
Move,
Trees
} from 'lucide-react';
import toast, { Toaster } from 'react-hot-toast';
import { addRentProperty, getCurrencies, uploadPicture } from '../../../utils/api';
import { addRentProperty, addSaleProperty, getCurrencies, uploadPicture } from '../../../utils/api';
import {
BuildingType,
RentPropertyCondition,
@ -85,12 +86,14 @@ function MapClickHandler({ onMapClick }) {
export default function AddPropertyPage() {
const router = useRouter();
const searchParams = useSearchParams();
const purpose = searchParams.get('purpose') || 'rent';
const [step, setStep] = useState(1);
const totalSteps = 4;
const totalSteps = purpose === 'sale' ? 4 : 4;
const [formData, setFormData] = useState({
propertyType: 'apartment', // apartment, villa, suite, room
propertyType: 'apartment',
furnished: false,
@ -152,8 +155,13 @@ export default function AddPropertyPage() {
const propertyTypes = [
{ id: 'apartment', label: 'شقة', icon: Building },
{ id: 'villa', label: 'فيلا', icon: Home },
{ id: 'suite', label: 'سويت', icon: Sofa },
{ id: 'room', label: 'غرفة ضمن شقة', icon: DoorOpen }
{ id: 'sweet', label: 'سويت', icon: Sofa },
{ id: 'room', label: 'غرفة', icon: DoorOpen },
{ id: 'studio', label: 'استوديو', icon: Sofa },
{ id: 'office', label: 'مكتب', icon: Building },
{ id: 'farms', label: 'مزرعة', icon: Trees },
{ id: 'shop', label: 'متجر', icon: Warehouse },
{ id: 'warehouse', label: 'مستودع', icon: Warehouse },
];
const serviceList = [
@ -493,15 +501,19 @@ const handleMapClick = async (coords) => {
break;
case 3:
if (formData.offerType === 'daily' && !formData.dailyPrice) {
newErrors.dailyPrice = 'السعر اليومي مطلوب';
}
if (formData.offerType === 'monthly' && !formData.monthlyPrice) {
newErrors.monthlyPrice = 'السعر الشهري مطلوب';
}
if (formData.offerType === 'both') {
if (!formData.dailyPrice) newErrors.dailyPrice = 'السعر اليومي مطلوب';
if (!formData.monthlyPrice) newErrors.monthlyPrice = 'السعر الشهري مطلوب';
if (purpose === 'sale') {
if (!formData.salePrice) newErrors.salePrice = 'سعر البيع مطلوب';
} else {
if (formData.offerType === 'daily' && !formData.dailyPrice) {
newErrors.dailyPrice = 'السعر اليومي مطلوب';
}
if (formData.offerType === 'monthly' && !formData.monthlyPrice) {
newErrors.monthlyPrice = 'السعر الشهري مطلوب';
}
if (formData.offerType === 'both') {
if (!formData.dailyPrice) newErrors.dailyPrice = 'السعر اليومي مطلوب';
if (!formData.monthlyPrice) newErrors.monthlyPrice = 'السعر الشهري مطلوب';
}
}
break;
@ -535,25 +547,18 @@ const handleMapClick = async (coords) => {
if (!validateStep()) return;
setIsLoading(true);
console.log('[AddProperty] Building RentPropertyDto payload...');
// Map UI property type to API BuildingType enum
const buildingTypeMap = { apartment: BuildingType.APARTMENT, villa: BuildingType.VILLA, suite: BuildingType.APARTMENT, room: BuildingType.APARTMENT };
const buildingTypeMap = { apartment: BuildingType.APARTMENT, villa: BuildingType.VILLA, sweet: BuildingType.SWEET, suite: BuildingType.SWEET, room: BuildingType.ROOM, studio: BuildingType.STUDIO, office: BuildingType.OFFICE, farms: BuildingType.FARMS, shop: BuildingType.SHOP, warehouse: BuildingType.WAREHOUSE };
// Map offer type to RentType enum: 0=Monthly, 1=Daily
const rentTypeMap = { daily: RentType.DAILY, monthly: RentType.MONTHLY, both: RentType.MONTHLY };
// Services: collect selected service enum names into array
const selectedServices = Object.entries(formData.services)
.filter(([, v]) => v)
.map(([k]) => k); // k is already the enum value (e.g. "Electricity")
.map(([k]) => k);
// Terms: collect selected term enum names into array
const selectedTerms = Object.entries(formData.terms)
.filter(([, v]) => v)
.map(([k]) => k); // k is already the enum value (e.g. "NoSmoking")
.map(([k]) => k);
// Build detailsJSON matching Flutter structure
const detailsJSON = JSON.stringify({
services: selectedServices,
serviceDetails: selectedServices.reduce((acc, s) => ({ ...acc, [s]: 'in general' }), {}),
@ -578,40 +583,53 @@ const handleMapClick = async (coords) => {
}
});
const payload = {
propertyInformation: {
cordsX: formData.lat ? String(formData.lat) : '',
cordsY: formData.lng ? String(formData.lng) : '',
address: `${formData.city} - ${formData.district} - ${formData.address}`.trim(),
description: formData.description || '',
numberOfBathRooms: formData.bathrooms || 0,
numberOfRooms: (formData.bedrooms || 0) + (formData.livingRooms || 0),
numberOfBedRooms: formData.bedrooms || 0,
space: parseFloat(formData.space) || 0,
detailsJSON,
buildingType: buildingTypeMap[formData.propertyType] ?? BuildingType.APARTMENT,
status: 0,
propertyType: formData.furnished ? RentPropertyCondition.WITH_FURNITURE : RentPropertyCondition.WITHOUT_FURNITURE,
images: uploadedImagePaths,
},
deposit: parseFloat(formData.deposit) || 0,
monthlyRent: parseFloat(formData.monthlyPrice) || 0,
dailyRent: parseFloat(formData.dailyPrice) || 0,
rating: 0,
currencyId: selectedCurrencyId,
rentType: rentTypeMap[formData.offerType] ?? RentType.MONTHLY,
isSmokeAllow: !formData.terms[PropertyTerm.NO_SMOKING],
specializedFor: false,
isVisitorAllow: !formData.terms[PropertyTerm.NO_PARTIES],
type: formData.furnished ? RentPropertyType.FURNISHED : RentPropertyType.UNFURNISHED,
const propInfo = {
cordsX: formData.lat ? String(formData.lat) : '',
cordsY: formData.lng ? String(formData.lng) : '',
address: `${formData.city} - ${formData.district} - ${formData.address}`.trim(),
description: formData.description || '',
numberOfBathRooms: formData.bathrooms || 0,
numberOfRooms: (formData.bedrooms || 0) + (formData.livingRooms || 0),
numberOfBedRooms: formData.bedrooms || 0,
space: parseFloat(formData.space) || 0,
detailsJSON,
buildingType: buildingTypeMap[formData.propertyType] ?? BuildingType.APARTMENT,
status: 0,
propertyType: formData.furnished ? RentPropertyCondition.WITH_FURNITURE : RentPropertyCondition.WITHOUT_FURNITURE,
images: uploadedImagePaths,
};
console.log('[AddProperty] Payload:', JSON.stringify(payload, null, 2));
try {
const res = await addRentProperty(payload);
console.log('[AddProperty] API response:', res);
toast.success('تم إضافة العقار بنجاح!');
if (purpose === 'sale') {
const payload = {
propInfo,
price: parseFloat(formData.salePrice) || 0,
currencyId: selectedCurrencyId,
};
console.log('[AddProperty] Sale payload:', JSON.stringify(payload, null, 2));
const res = await addSaleProperty(payload);
console.log('[AddProperty] Sale API response:', res);
toast.success('تم إضافة عقار للبيع بنجاح!');
} else {
const rentTypeMap = { daily: RentType.DAILY, monthly: RentType.MONTHLY, both: RentType.MONTHLY };
const payload = {
propertyInformation: propInfo,
deposit: parseFloat(formData.deposit) || 0,
monthlyRent: parseFloat(formData.monthlyPrice) || 0,
dailyRent: parseFloat(formData.dailyPrice) || 0,
rating: 0,
currencyId: selectedCurrencyId,
rentType: rentTypeMap[formData.offerType] ?? RentType.MONTHLY,
isSmokeAllow: !formData.terms[PropertyTerm.NO_SMOKING],
specializedFor: false,
isVisitorAllow: !formData.terms[PropertyTerm.NO_PARTIES],
type: formData.furnished ? RentPropertyType.FURNISHED : RentPropertyType.UNFURNISHED,
};
console.log('[AddProperty] Rent payload:', JSON.stringify(payload, null, 2));
const res = await addRentProperty(payload);
console.log('[AddProperty] Rent API response:', res);
toast.success('تم إضافة عقار للإيجار بنجاح!');
}
setTimeout(() => {
router.push('/owner/properties');
}, 1500);
@ -663,7 +681,7 @@ const handleMapClick = async (coords) => {
<div className="flex justify-between mt-2 text-xs text-gray-500">
<span>معلومات العقار</span>
<span>التفاصيل والخدمات</span>
<span>السعر</span>
<span>{purpose === 'sale' ? 'سعر البيع' : 'السعر'}</span>
<span>الموقع والصور</span>
</div>
</div>
@ -927,7 +945,53 @@ const handleMapClick = async (coords) => {
</motion.div>
)}
{step === 3 && (
{step === 3 && purpose === 'sale' && (
<motion.div variants={fadeInUp} className="space-y-8">
<div className="text-center mb-6">
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
<DollarSign className="w-10 h-10 text-amber-600" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">سعر البيع</h2>
<p className="text-gray-600">حدد سعر البيع والعملة</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
سعر البيع (ل.س) <span className="text-red-500">*</span>
</label>
<div className="relative">
<DollarSign className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="number"
value={formData.salePrice || ''}
onChange={(e) => setFormData({...formData, salePrice: e.target.value})}
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
errors.salePrice ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="مثال: 50000000"
/>
</div>
{errors.salePrice && <p className="text-red-500 text-sm mt-1">{errors.salePrice}</p>}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
العملة <span className="text-red-500">*</span>
</label>
<select
value={selectedCurrencyId}
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
>
{Object.entries(CurrencyLabels).map(([id, label]) => (
<option key={id} value={id}>{label}</option>
))}
</select>
</div>
</motion.div>
)}
{step === 3 && purpose === 'rent' && (
<motion.div variants={fadeInUp} className="space-y-8">
<div className="text-center mb-6">
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">

View File

@ -46,7 +46,7 @@ import {
} from 'lucide-react';
import toast, { Toaster } from 'react-hot-toast';
import AuthService from '../../services/AuthService';
import { getMyRentListings } from '../../utils/api';
import { getMyRentListings, getMySaleListings, editRentProperty } from '../../utils/api';
const DeleteConfirmationModal = ({ isOpen, onClose, onConfirm, propertyTitle }) => {
if (!isOpen) return null;
@ -721,24 +721,31 @@ export default function OwnerPropertiesPage() {
try {
console.log('[OwnerProperties] Fetching listings for user:', userId);
const data = await getMyRentListings();
const list = Array.isArray(data) ? data : (data ? [data] : []);
console.log('[OwnerProperties] API returned:', list.length, 'properties');
const mapped = list.map((item) => {
const [rentData, saleData] = await Promise.allSettled([
getMyRentListings(),
getMySaleListings(),
]);
const rentList = rentData.status === 'fulfilled' ? (Array.isArray(rentData.value) ? rentData.value : (rentData.value ? [rentData.value] : [])) : [];
const saleList = saleData.status === 'fulfilled' ? (Array.isArray(saleData.value) ? saleData.value : (saleData.value ? [saleData.value] : [])) : [];
console.log('[OwnerProperties] Rent:', rentList.length, 'Sale:', saleList.length);
const mappedRent = rentList.map((item) => {
const info = item.propertyInformation || {};
const details = (() => {
try { return JSON.parse(info.detailsJSON || '{}'); } catch { return {}; }
})();
const details = (() => { try { return JSON.parse(info.detailsJSON || '{}'); } catch { return {}; } })();
const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : '';
const raw = Array.isArray(info.images) ? info.images : [];
return {
id: item.id,
title: info.address || `عقار #${item.id}`,
propertyType: { 0: 'apartment', 1: 'villa', 2: 'house' }[info.buildingType] || 'apartment',
purpose: 'rent',
rentType: { 0: 'daily', 1: 'weekly', 2: 'monthly' }[item.rentType] || 'daily',
rentType: { 0: 'daily', 1: 'monthly' }[item.rentType] || 'daily',
dailyPrice: item.dailyRent || 0,
monthlyPrice: item.monthlyRent || 0,
salePrice: item.price || 0,
deposit: item.deposit || 0,
location: info.address || '',
bedrooms: info.numberOfBedRooms || 0,
@ -746,11 +753,7 @@ export default function OwnerPropertiesPage() {
area: info.space || 0,
livingRooms: details.livingRooms || 0,
status: { 0: 'available', 1: 'booked', 2: 'maintenance' }[info.status] || 'available',
images: (() => {
const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : '';
const raw = Array.isArray(info.images) ? info.images : [];
return raw.length > 0 ? raw.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`) : ['/property-placeholder.jpg'];
})(),
images: raw.length > 0 ? raw.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`) : ['/property-placeholder.jpg'],
createdAt: item.createdAt || new Date().toISOString(),
furnished: details.furnished || false,
description: info.description || '',
@ -765,7 +768,42 @@ export default function OwnerPropertiesPage() {
};
});
setProperties(mapped);
const mappedSale = saleList.map((item) => {
const info = item.propertyInformation || {};
const details = (() => { try { return JSON.parse(info.detailsJSON || '{}'); } catch { return {}; } })();
const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : '';
const raw = Array.isArray(info.images) ? info.images : [];
return {
id: item.id,
title: info.address || `عقار للبيع #${item.id}`,
propertyType: { 0: 'apartment', 1: 'villa', 2: 'house' }[info.buildingType] || 'apartment',
purpose: 'sale',
dailyPrice: 0,
monthlyPrice: 0,
salePrice: item.price || 0,
deposit: 0,
location: info.address || '',
bedrooms: info.numberOfBedRooms || 0,
bathrooms: info.numberOfBathRooms || 0,
area: info.space || 0,
livingRooms: details.livingRooms || 0,
status: 'available',
images: raw.length > 0 ? raw.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`) : ['/property-placeholder.jpg'],
createdAt: item.createdAt || new Date().toISOString(),
furnished: details.furnished || false,
description: info.description || '',
address: info.address || '',
city: '',
district: '',
services: details.services || {},
terms: details.terms || {},
rating: item.rating || 0,
currencyId: item.currencyId,
_raw: item,
};
});
setProperties([...mappedRent, ...mappedSale]);
} catch (err) {
console.error('[OwnerProperties] Failed to load properties:', err);
toast.error('فشل في تحميل العقارات');
@ -788,12 +826,58 @@ export default function OwnerPropertiesPage() {
}
};
const handleSaveEdit = (updatedProperty) => {
const newProperties = properties.map(p =>
p.id === updatedProperty.id ? updatedProperty : p
);
updatePropertiesInStorage(newProperties);
setEditModal({ isOpen: false, property: null });
const handleSaveEdit = async (updatedProperty) => {
try {
if (updatedProperty.purpose === 'rent' && updatedProperty._raw) {
const buildingTypeMap = { apartment: 0, villa: 1, sweet: 2, room: 3, studio: 4, office: 5, farms: 6, shop: 7, warehouse: 8 };
const raw = updatedProperty._raw;
const rentTypeMap = { daily: 1, monthly: 0, both: 0 };
const detailsJSON = JSON.stringify({
services: updatedProperty.services || {},
terms: updatedProperty.terms || {},
furnished: updatedProperty.furnished,
});
const payload = {
propertyInformation: {
cordsX: raw.propertyInformation?.cordsX || '',
cordsY: raw.propertyInformation?.cordsY || '',
address: updatedProperty.address || raw.propertyInformation?.address || '',
description: updatedProperty.description || raw.propertyInformation?.description || '',
numberOfBathRooms: updatedProperty.bathrooms || raw.propertyInformation?.numberOfBathRooms || 0,
numberOfRooms: (updatedProperty.bedrooms || 0) + (updatedProperty.livingRooms || 0),
numberOfBedRooms: updatedProperty.bedrooms || raw.propertyInformation?.numberOfBedRooms || 0,
space: parseFloat(updatedProperty.area) || raw.propertyInformation?.space || 0,
detailsJSON,
buildingType: buildingTypeMap[updatedProperty.propertyType] ?? 0,
status: updatedProperty.status === 'available' ? 0 : 1,
propertyType: updatedProperty.furnished ? 0 : 1,
images: raw.propertyInformation?.images || [],
},
deposit: parseFloat(updatedProperty.deposit) || raw.deposit || 0,
monthlyRent: parseFloat(updatedProperty.monthlyPrice) || raw.monthlyRent || 0,
dailyRent: parseFloat(updatedProperty.dailyPrice) || raw.dailyRent || 0,
rating: updatedProperty.rating || 0,
currencyId: updatedProperty.currencyId || raw.currencyId || 1,
rentType: rentTypeMap[updatedProperty.rentType] ?? 0,
isSmokeAllow: !updatedProperty.terms?.NoSmoking,
isVisitorAllow: !updatedProperty.terms?.NoParties,
type: updatedProperty.furnished ? 0 : 1,
};
await editRentProperty(updatedProperty.id, payload);
}
const newProperties = properties.map(p =>
p.id === updatedProperty.id ? updatedProperty : p
);
updatePropertiesInStorage(newProperties);
setEditModal({ isOpen: false, property: null });
toast.success('تم تحديث العقار بنجاح');
} catch (err) {
console.error('[OwnerProperties] Edit failed:', err);
toast.error('فشل تحديث العقار');
}
};
const fadeInUp = {