edited the edit properrty
All checks were successful
Build frontend / build (push) Successful in 44s

This commit is contained in:
mouazkh
2026-05-26 04:35:03 +03:00
parent 439f69419f
commit 09bbf07d8c
2 changed files with 609 additions and 412 deletions

View File

@ -58,6 +58,7 @@ import {
getMyRentListings, getMyRentListings,
getMySaleListings, getMySaleListings,
editRentProperty, editRentProperty,
editSaleProperty,
} from "../../utils/api"; } from "../../utils/api";
const DeleteConfirmationModal = ({ const DeleteConfirmationModal = ({
@ -521,114 +522,140 @@ const PropertyViewModal = ({ isOpen, onClose, property }) => {
}; };
const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => { const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
const [formData, setFormData] = useState({ ...property }); const [formData, setFormData] = useState({});
const [newCustomTerm, setNewCustomTerm] = useState('');
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [editingField, setEditingField] = useState(null);
const [tempValues, setTempValues] = useState({});
const sections = [ useEffect(() => {
{ if (!property || !isOpen) return;
title: "معلومات أساسية", const raw = property._raw || {};
fields: [ const info = raw.propertyInformation || {};
{ id: "title", label: "عنوان العقار", type: "text" }, let details = {};
{ id: "description", label: "الوصف", type: "textarea" }, try {
{ details =
id: "propertyType", typeof info.detailsJSON === 'object' && info.detailsJSON
label: "نوع العقار", ? info.detailsJSON
type: "select", : JSON.parse(info.detailsJSON || '{}');
options: [ } catch {
{ value: "apartment", label: "شقة" }, details = {};
{ value: "villa", label: "فيلا" }, }
{ value: "suite", label: "سويت" },
{ value: "room", label: "غرفة ضمن شقة" },
],
},
{
id: "furnished",
label: "حالة العقار",
type: "radio",
options: [
{ value: true, label: "مفروش" },
{ value: false, label: "غير مفروش" },
],
},
],
},
{
title: "التفاصيل",
fields: [
{ id: "bedrooms", label: "عدد الغرف", type: "number" },
{ id: "bathrooms", label: "عدد الحمامات", type: "number" },
{ id: "livingRooms", label: "عدد الصالونات", type: "number" },
{ id: "area", label: "المساحة (م²)", type: "number" },
],
},
{
title: "الموقع",
fields: [
{ id: "address", label: "العنوان الكامل", type: "text" },
{ id: "city", label: "المدينة", type: "text" },
{ id: "district", label: "الحي", type: "text" },
],
},
{
title: "السعر",
fields:
formData?.purpose === "rent"
? [
{ id: "dailyPrice", label: "السعر اليومي", type: "number" },
{ id: "monthlyPrice", label: "السعر الشهري", type: "number" },
{
id: "rentType",
label: "نوع الإيجار",
type: "select",
options: [
{ value: "daily", label: "يومي" },
{ value: "monthly", label: "شهري" },
{ value: "both", label: "يومي وشهري" },
],
},
]
: [{ id: "salePrice", label: "سعر البيع", type: "number" }],
},
];
const startEditing = (fieldId) => { const propServices = property.services || {};
setEditingField(fieldId); const services = {};
setTempValues({ ...tempValues, [fieldId]: formData[fieldId] }); const serviceDetails = {};
}; Object.keys(serviceLabels).forEach((key) => {
const val = propServices[key];
const cancelEditing = () => { if (val && typeof val === 'string') {
setEditingField(null); services[key] = true;
setTempValues({}); serviceDetails[key] = val;
}; } else if (val && typeof val === 'object' && val.detail) {
services[key] = true;
const saveField = (fieldId) => { serviceDetails[key] = val.detail;
setFormData({ } else {
...formData, services[key] = !!val;
[fieldId]: tempValues[fieldId], serviceDetails[key] = details.serviceDetails?.[key] || '';
}
}); });
setEditingField(null);
setTempValues({}); const propTerms = property.terms || {};
const fieldLabel = sections const terms = {};
.flatMap((s) => s.fields) Object.keys(termLabels).forEach((key) => {
.find((f) => f.id === fieldId)?.label; terms[key] = !!propTerms[key];
toast.success(`تم تحديث ${fieldLabel}`); });
const prox = property.proximity || details.nearbyDistances || {};
setFormData({
propertyType: property.propertyType || 'apartment',
furnished: property.furnished ?? false,
description: property.description || '',
bedrooms: property.bedrooms || 0,
bathrooms: property.bathrooms || 0,
floor: property.floor ?? details.floorNumber ?? details.floor ?? 0,
salons: property.salons ?? details.numberOfSalons ?? details.salons ?? 0,
balconies:
property.balconies ??
details.numberOfBalconies ??
details.balconies ??
0,
area: property.area || 0,
services,
serviceDetails,
terms,
customTerms: details.customTerms || [],
nearbySchool: prox.School ?? prox.school ?? '',
nearbyHospital: prox.Hospital ?? prox.hospital ?? '',
nearbyRestaurant: prox.Restaurant ?? prox.restaurant ?? '',
nearbyUniversity: prox.University ?? prox.university ?? '',
nearbyPark: prox.Park ?? prox.park ?? '',
nearbyMall: prox.Mall ?? prox.mall ?? '',
purpose: property.purpose || 'rent',
currencyId: property.currencyId || 1,
...(property.purpose === 'rent'
? {
dailyPrice: property.dailyPrice || 0,
monthlyPrice: property.monthlyPrice || 0,
deposit: property.deposit || 0,
rentType: property.rentType || 'monthly',
allowedPaymentPeriod:
property.allowedPaymentPeriod ||
details.allowedPaymentPeriod ||
'',
}
: { salePrice: property.salePrice || 0 }),
});
setNewCustomTerm('');
}, [property, isOpen]);
const handleChange = (field, value) => {
setFormData((prev) => ({ ...prev, [field]: value }));
}; };
const handleTempChange = (fieldId, value) => { const handleServiceToggle = (key, checked) => {
setTempValues({ ...tempValues, [fieldId]: value }); setFormData((prev) => ({
...prev,
services: { ...prev.services, [key]: checked },
}));
}; };
const handleSave = () => { const handleServiceDetail = (key, value) => {
setFormData((prev) => ({
...prev,
serviceDetails: { ...prev.serviceDetails, [key]: value },
}));
};
const handleTermToggle = (key, checked) => {
setFormData((prev) => ({
...prev,
terms: { ...prev.terms, [key]: checked },
}));
};
const addCustomTerm = () => {
const term = newCustomTerm.trim();
if (!term) return;
setFormData((prev) => ({
...prev,
customTerms: [...(prev.customTerms || []), term],
}));
setNewCustomTerm('');
};
const removeCustomTerm = (index) => {
setFormData((prev) => ({
...prev,
customTerms: prev.customTerms.filter((_, i) => i !== index),
}));
};
const handleSave = async () => {
setIsSaving(true); setIsSaving(true);
try {
setTimeout(() => { await onSave(formData);
onSave(formData); } catch {
setIsSaving(false); setIsSaving(false);
onClose(); }
toast.success("تم تحديث العقار بنجاح");
}, 1000);
}; };
if (!isOpen || !property) return null; if (!isOpen || !property) return null;
@ -645,7 +672,7 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
initial={{ scale: 0.9, y: 20 }} initial={{ scale: 0.9, y: 20 }}
animate={{ scale: 1, y: 0 }} animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.9, y: 20 }} exit={{ scale: 0.9, y: 20 }}
className="bg-white rounded-2xl w-full max-w-4xl max-h-[90vh] overflow-y-auto shadow-2xl" className="bg-white rounded-2xl w-full max-w-4xl max-h-[90vh] flex flex-col shadow-2xl"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<div className="sticky top-0 z-10 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white flex justify-between items-center"> <div className="sticky top-0 z-10 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white flex justify-between items-center">
@ -663,236 +690,347 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
</button> </button>
</div> </div>
<div className="p-6 space-y-8"> <div className="p-6 space-y-6 overflow-y-auto flex-1">
{formData.images && formData.images.length > 0 && ( {/* Basic Info */}
<div className="bg-gray-50 p-4 rounded-xl"> <div className="bg-gray-50 p-4 rounded-xl">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-bold text-gray-900">صور العقار</h3>
<button className="text-amber-600 hover:text-amber-700 text-sm font-medium">
+ إضافة صور
</button>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{formData.images.map((image, index) => (
<div key={index} className="relative group aspect-square">
<img
src={image}
alt={`Property ${index + 1}`}
className="w-full h-full object-cover rounded-lg"
/>
<button className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<X className="w-4 h-4 text-white" />
</button>
</div>
))}
</div>
</div>
)}
{sections.map((section, sectionIndex) => (
<div key={sectionIndex} className="bg-gray-50 p-4 rounded-xl">
<h3 className="text-lg font-bold text-gray-900 mb-4"> <h3 className="text-lg font-bold text-gray-900 mb-4">
{section.title} معلومات أساسية
</h3> </h3>
<div className="space-y-4"> <div className="space-y-4">
{section.fields.map((field) => ( <div>
<div <label className="block text-sm font-medium text-gray-700 mb-1">
key={field.id} نوع العقار
className="bg-white p-3 rounded-lg group hover:shadow-sm transition-shadow"
>
<div className="flex justify-between items-start mb-2">
<label className="text-sm font-medium text-gray-600 flex items-center gap-1">
<span>{field.icon}</span>
{field.label}
</label> </label>
{editingField !== field.id && (
<button
onClick={() => startEditing(field.id)}
className="opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 hover:text-amber-500"
>
<Pencil className="w-4 h-4" />
</button>
)}
</div>
{editingField === field.id ? (
<div className="space-y-2">
{field.type === "textarea" ? (
<textarea
value={tempValues[field.id] || ""}
onChange={(e) =>
handleTempChange(field.id, e.target.value)
}
className="w-full px-3 py-2 border border-amber-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
rows="3"
/>
) : field.type === "select" ? (
<select <select
value={tempValues[field.id] || ""} value={formData.propertyType}
onChange={(e) => onChange={(e) =>
handleTempChange(field.id, e.target.value) handleChange('propertyType', e.target.value)
} }
className="w-full px-3 py-2 border border-amber-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
> >
{field.options.map((opt) => ( <option value="apartment">شقة</option>
<option key={opt.value} value={opt.value}> <option value="villa">فيلا</option>
{opt.label} <option value="sweet">سويت</option>
</option> <option value="room">غرفة ضمن شقة</option>
))} <option value="studio">استوديو</option>
<option value="office">مكتب</option>
<option value="farms">مزرعة</option>
<option value="shop">متجر</option>
<option value="warehouse">مستودع</option>
</select> </select>
) : field.type === "radio" ? ( </div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
حالة التأثيث
</label>
<div className="flex gap-4"> <div className="flex gap-4">
{field.options.map((opt) => ( {[
{ value: true, label: 'مفروش' },
{ value: false, label: 'غير مفروش' },
].map((opt) => (
<label <label
key={opt.value} key={String(opt.value)}
className="flex items-center gap-2" className="flex items-center gap-2 cursor-pointer"
> >
<input <input
type="radio" type="radio"
name={field.id} name="furnished"
value={opt.value} checked={formData.furnished === opt.value}
checked={tempValues[field.id] === opt.value} onChange={() => handleChange('furnished', opt.value)}
onChange={(e) =>
handleTempChange(
field.id,
e.target.value === "true",
)
}
className="w-4 h-4 text-amber-500" className="w-4 h-4 text-amber-500"
/> />
<span>{opt.label}</span> <span className="text-sm">{opt.label}</span>
</label> </label>
))} ))}
</div> </div>
) : ( </div>
<input <div>
type={field.type} <label className="block text-sm font-medium text-gray-700 mb-1">
value={tempValues[field.id] || ""} الوصف
</label>
<textarea
value={formData.description}
onChange={(e) => onChange={(e) =>
handleTempChange(field.id, e.target.value) handleChange('description', e.target.value)
} }
className="w-full px-3 py-2 border border-amber-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
rows={3}
/> />
)} </div>
</div>
</div>
<div className="flex gap-2 justify-end"> {/* Details */}
<button <div className="bg-gray-50 p-4 rounded-xl">
onClick={() => saveField(field.id)} <h3 className="text-lg font-bold text-gray-900 mb-4">التفاصيل</h3>
className="px-3 py-1 bg-green-500 text-white rounded-lg text-sm hover:bg-green-600" <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
> {[
حفظ { id: 'bedrooms', label: 'عدد غرف النوم' },
</button> { id: 'bathrooms', label: 'عدد الحمامات' },
<button { id: 'floor', label: 'الطابق' },
onClick={cancelEditing} { id: 'salons', label: 'عدد الصالونات' },
className="px-3 py-1 bg-gray-500 text-white rounded-lg text-sm hover:bg-gray-600" { id: 'balconies', label: 'عدد البلكونات' },
> { id: 'area', label: 'المساحة (م²)' },
إلغاء ].map((field) => (
</button> <div key={field.id}>
</div> <label className="block text-sm font-medium text-gray-700 mb-1">
</div> {field.label}
) : ( </label>
<div className="text-gray-900"> <input
{field.type === "select" type="number"
? field.options.find( min="0"
(opt) => opt.value === formData[field.id], value={formData[field.id]}
)?.label || formData[field.id] onChange={(e) =>
: field.type === "radio" handleChange(field.id, Number(e.target.value))
? formData[field.id] }
? "مفروش" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
: "غير مفروش" />
: field.id.includes("Price")
? formData[field.id]
? `${Number(formData[field.id]).toLocaleString()} ل.س`
: "—"
: formData[field.id] || "—"}
</div>
)}
</div> </div>
))} ))}
</div> </div>
</div> </div>
))}
{/* Services */}
<div className="bg-gray-50 p-4 rounded-xl"> <div className="bg-gray-50 p-4 rounded-xl">
<h3 className="text-lg font-bold text-gray-900 mb-4">الخدمات</h3> <h3 className="text-lg font-bold text-gray-900 mb-4">الخدمات</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{Object.entries(formData.services || {}).map(([key, value]) => { {Object.entries(serviceLabels).map(([key, label]) => (
const serviceLabels = { <div
electricity: "كهرباء",
internet: "انترنت",
heating: "تدفئة",
water: "ماء",
airConditioning: "تكييف",
parking: "موقف سيارات",
elevator: "مصعد",
};
return (
<label
key={key} key={key}
className="flex items-center gap-2 p-2 border rounded-lg cursor-pointer hover:bg-amber-50" className="flex items-start gap-2 p-2 border rounded-lg bg-white"
> >
<input <input
type="checkbox" type="checkbox"
checked={value} checked={formData.services?.[key] || false}
onChange={(e) => { onChange={(e) =>
setFormData({ handleServiceToggle(key, e.target.checked)
...formData, }
services: { className="w-4 h-4 text-amber-500 mt-1"
...formData.services,
[key]: e.target.checked,
},
});
}}
className="w-4 h-4 text-amber-500"
/> />
<span className="text-sm">{serviceLabels[key]}</span> <div className="flex-1">
</label> <span className="text-sm font-medium">{label}</span>
); {formData.services?.[key] && (
})} <input
type="text"
value={formData.serviceDetails?.[key] || ''}
onChange={(e) =>
handleServiceDetail(key, e.target.value)
}
placeholder="تفاصيل الخدمة..."
className="w-full mt-1 px-2 py-1 text-xs border border-gray-200 rounded focus:outline-none focus:ring-1 focus:ring-amber-500"
/>
)}
</div>
</div>
))}
</div> </div>
</div> </div>
{/* Terms - rent only */}
{formData.purpose === 'rent' && (
<div className="bg-gray-50 p-4 rounded-xl"> <div className="bg-gray-50 p-4 rounded-xl">
<h3 className="text-lg font-bold text-gray-900 mb-4"> <h3 className="text-lg font-bold text-gray-900 mb-4">
شروط الاستخدام شروط الاستخدام
</h3> </h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{Object.entries(formData.terms || {}).map(([key, value]) => { {Object.entries(termLabels).map(([key, label]) => (
const termLabels = {
noSmoking: " ممنوع التدخين",
noPets: " ممنوع الحيوانات",
noParties: " ممنوع الحفلات",
noAlcohol: " ممنوع الكحول",
suitableForChildren: " مناسب للأطفال",
suitableForElderly: " مناسب لكبار السن",
};
return (
<label <label
key={key} key={key}
className="flex items-center gap-2 p-2 border rounded-lg cursor-pointer hover:bg-amber-50" className="flex items-center gap-2 p-2 border rounded-lg cursor-pointer bg-white hover:bg-amber-50"
> >
<input <input
type="checkbox" type="checkbox"
checked={value} checked={formData.terms?.[key] || false}
onChange={(e) => { onChange={(e) =>
setFormData({ handleTermToggle(key, e.target.checked)
...formData, }
terms: {
...formData.terms,
[key]: e.target.checked,
},
});
}}
className="w-4 h-4 text-amber-500" className="w-4 h-4 text-amber-500"
/> />
<span className="text-sm">{termLabels[key]}</span> <span className="text-sm">{label}</span>
</label> </label>
); ))}
})} </div>
<div className="mt-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
شروط مخصصة
</label>
<div className="flex gap-2 mb-2">
<input
type="text"
value={newCustomTerm}
onChange={(e) => setNewCustomTerm(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addCustomTerm()}
placeholder="أضف شرطاً مخصصاً..."
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500 text-sm"
/>
<button
onClick={addCustomTerm}
className="px-3 py-2 bg-amber-500 text-white rounded-lg text-sm hover:bg-amber-600"
>
إضافة
</button>
</div>
{formData.customTerms?.length > 0 && (
<div className="flex flex-wrap gap-2">
{formData.customTerms.map((term, i) => (
<span
key={i}
className="inline-flex items-center gap-1 px-3 py-1 bg-amber-100 text-amber-800 rounded-full text-sm"
>
{term}
<button
onClick={() => removeCustomTerm(i)}
className="hover:text-red-600"
>
<X className="w-3 h-3" />
</button>
</span>
))}
</div>
)}
</div>
</div>
)}
{/* Nearby Distances */}
<div className="bg-gray-50 p-4 rounded-xl">
<h3 className="text-lg font-bold text-gray-900 mb-4">
القرب من الخدمات
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{[
{ id: 'nearbySchool', label: 'المدرسة' },
{ id: 'nearbyHospital', label: 'المستشفى' },
{ id: 'nearbyRestaurant', label: 'المطعم' },
{ id: 'nearbyUniversity', label: 'الجامعة' },
{ id: 'nearbyPark', label: 'الحديقة' },
{ id: 'nearbyMall', label: 'مركز التسوق' },
].map((field) => (
<div key={field.id}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{field.label}
</label>
<input
type="text"
value={formData[field.id] || ''}
onChange={(e) => handleChange(field.id, e.target.value)}
placeholder="المسافة (كم)"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
/>
</div>
))}
</div> </div>
</div> </div>
<div className="flex gap-3 pt-6 border-t"> {/* Pricing */}
<div className="bg-gray-50 p-4 rounded-xl">
<h3 className="text-lg font-bold text-gray-900 mb-4">
{formData.purpose === 'rent'
? 'معلومات السعر (إيجار)'
: 'سعر البيع'}
</h3>
{formData.purpose === 'rent' ? (
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
السعر اليومي
</label>
<input
type="number"
min="0"
value={formData.dailyPrice}
onChange={(e) =>
handleChange('dailyPrice', Number(e.target.value))
}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
السعر الشهري
</label>
<input
type="number"
min="0"
value={formData.monthlyPrice}
onChange={(e) =>
handleChange('monthlyPrice', Number(e.target.value))
}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
التأمين
</label>
<input
type="number"
min="0"
value={formData.deposit}
onChange={(e) =>
handleChange('deposit', Number(e.target.value))
}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
نوع الإيجار
</label>
<select
value={formData.rentType}
onChange={(e) =>
handleChange('rentType', e.target.value)
}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
>
<option value="daily">يومي</option>
<option value="monthly">شهري</option>
<option value="both">يومي وشهري</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
مدة السداد المسموحة
</label>
<select
value={formData.allowedPaymentPeriod}
onChange={(e) =>
handleChange('allowedPaymentPeriod', e.target.value)
}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
>
<option value="">اختر المدة</option>
<option value="1.00:00:00">يومي</option>
<option value="7.00:00:00">أسبوعي</option>
<option value="30.00:00:00">شهري</option>
<option value="90.00:00:00">ربع سنوي</option>
<option value="365.00:00:00">سنوي</option>
</select>
</div>
</div>
) : (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
سعر البيع
</label>
<input
type="number"
min="0"
value={formData.salePrice}
onChange={(e) =>
handleChange('salePrice', Number(e.target.value))
}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
/>
</div>
)}
</div>
</div>
<div className="p-6 border-t flex gap-3">
<button <button
onClick={onClose} onClick={onClose}
className="flex-1 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-colors" className="flex-1 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-colors"
@ -912,12 +1050,11 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
) : ( ) : (
<> <>
<Save className="w-5 h-5" /> <Save className="w-5 h-5" />
حفظ جميع التغييرات حفظ التغييرات
</> </>
)} )}
</button> </button>
</div> </div>
</div>
</motion.div> </motion.div>
</motion.div> </motion.div>
); );
@ -1239,9 +1376,12 @@ export default function OwnerPropertiesPage() {
} }
}; };
const handleSaveEdit = async (updatedProperty) => { const handleSaveEdit = async (formData) => {
try { try {
if (updatedProperty.purpose === "rent" && updatedProperty._raw) { const property = editModal.property;
const raw = property._raw || {};
const rawInfo = raw.propertyInformation || {};
const buildingTypeMap = { const buildingTypeMap = {
apartment: 0, apartment: 0,
villa: 1, villa: 1,
@ -1253,76 +1393,125 @@ export default function OwnerPropertiesPage() {
shop: 7, shop: 7,
warehouse: 8, warehouse: 8,
}; };
const raw = updatedProperty._raw;
const rentTypeMap = { daily: 1, monthly: 0, both: 0 };
const rawServices = updatedProperty.services || {};
const serviceList = Array.isArray(rawServices)
? rawServices
: Object.keys(rawServices).filter((k) => rawServices[k]);
const details = {
services: serviceList,
serviceDetails: updatedProperty.serviceDetails || {},
terms: normalizeTerms(updatedProperty.terms),
displayType: "Both",
propertyCondition: updatedProperty.furnished
? "WithFurniture"
: "WithoutFurniture",
};
const detailsJSON = JSON.stringify(details);
const payload = { const activeServices = Object.entries(formData.services || {})
propertyInformation: { .filter(([, v]) => v)
cordsX: raw.propertyInformation?.cordsX || "", .map(([k]) => k);
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,
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: 1,
currencyId: updatedProperty.currencyId || raw.currencyId || 1,
rentType: rentTypeMap[updatedProperty.rentType] ?? 0,
type: updatedProperty.furnished ? 0 : 1,
allowedPaymentPeriod: "",
};
await editRentProperty(updatedProperty.id, payload); const activeTerms = Object.entries(formData.terms || {})
.filter(([, v]) => v)
.reduce((acc, [k]) => ({ ...acc, [k]: true }), {});
if (formData.customTerms?.length) {
formData.customTerms.forEach((t) => {
activeTerms[t] = true;
});
} }
const details = {
services: activeServices,
serviceDetails: Object.fromEntries(
Object.entries(formData.serviceDetails || {}).filter(
([k, v]) => activeServices.includes(k) && v,
),
),
...(formData.purpose === 'rent' ? { terms: activeTerms } : {}),
displayType:
formData.purpose === 'rent'
? formData.rentType === 'both'
? 'Both'
: formData.rentType === 'daily'
? 'Daily rent'
: 'Monthly rent'
: 'For sale',
propertyCondition: formData.furnished
? 'WithFurniture'
: 'WithoutFurniture',
floorNumber: parseInt(formData.floor) || 0,
numberOfSalons: parseInt(formData.salons) || 0,
numberOfBalconies: parseInt(formData.balconies) || 0,
nearbyDistances: {
school: formData.nearbySchool || '',
hospital: formData.nearbyHospital || '',
restaurant: formData.nearbyRestaurant || '',
university: formData.nearbyUniversity || '',
park: formData.nearbyPark || '',
mall: formData.nearbyMall || '',
},
};
if (
formData.purpose === 'rent' &&
formData.propertyType === 'room'
) {
const roomDetails = rawInfo.detailsJSON || {};
let parsedDetails = {};
try {
parsedDetails =
typeof roomDetails === 'object'
? roomDetails
: JSON.parse(roomDetails);
} catch {
parsedDetails = {};
}
details.room = parsedDetails.room || {};
}
const detailsJSON = JSON.stringify(details);
const propInfo = {
cordsX: rawInfo.cordsX || '',
cordsY: rawInfo.cordsY || '',
images: rawInfo.images || [],
address: property.address || rawInfo.address || '',
description:
formData.description || rawInfo.description || '',
numberOfBathRooms: parseInt(formData.bathrooms) || 0,
numberOfRooms: parseInt(formData.bedrooms) || 0,
numberOfBedRooms: parseInt(formData.bedrooms) || 0,
space: parseFloat(formData.area) || 0,
detailsJSON,
buildingType: buildingTypeMap[formData.propertyType] ?? 0,
status: 0,
propertyType: formData.furnished ? 0 : 1,
};
if (formData.purpose === 'rent') {
const rentTypeMap = { daily: 1, monthly: 0, both: 0 };
const payload = {
propertyInformation: propInfo,
deposit: parseFloat(formData.deposit) || 0,
monthlyRent: parseFloat(formData.monthlyPrice) || 0,
dailyRent: parseFloat(formData.dailyPrice) || 0,
rating: 1,
currencyId:
formData.currencyId || property.currencyId || 1,
rentType: rentTypeMap[formData.rentType] ?? 0,
type: formData.furnished ? 0 : 1,
allowedPaymentPeriod: formData.allowedPaymentPeriod || '',
};
await editRentProperty(property.id, payload);
} else {
const payload = {
propInfo,
price: parseFloat(formData.salePrice) || 0,
currencyId:
formData.currencyId || property.currencyId || 1,
};
await editSaleProperty(property.id, payload);
}
const updatedProperty = { ...property, ...formData };
const newProperties = properties.map((p) => const newProperties = properties.map((p) =>
p.id === updatedProperty.id ? updatedProperty : p, p.id === property.id ? updatedProperty : p,
); );
updatePropertiesInStorage(newProperties); updatePropertiesInStorage(newProperties);
setEditModal({ isOpen: false, property: null }); setEditModal({ isOpen: false, property: null });
toast.success("تم تحديث العقار بنجاح"); toast.success('تم تحديث العقار بنجاح');
} catch (err) { } catch (err) {
console.error("[OwnerProperties] Edit failed:", err); console.error('[OwnerProperties] Edit failed:', err);
toast.error("فشل تحديث العقار"); toast.error('فشل تحديث العقار');
throw err;
} }
}; };

View File

@ -672,6 +672,14 @@ export async function editRentProperty(id, data) {
}); });
} }
export async function editSaleProperty(id, data) {
console.log('[API] Editing sale property:', id);
return apiFetch(`/SaleProperties/EditSaleProperty/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
export async function addSaleProperty(data) { export async function addSaleProperty(data) {
console.log('[API] Adding sale property'); console.log('[API] Adding sale property');
return apiFetch('/SaleProperties/AddSaleProperty', { return apiFetch('/SaleProperties/AddSaleProperty', {