edited the edit properrty
All checks were successful
Build frontend / build (push) Successful in 44s
All checks were successful
Build frontend / build (push) Successful in 44s
This commit is contained in:
@ -58,6 +58,7 @@ import {
|
||||
getMyRentListings,
|
||||
getMySaleListings,
|
||||
editRentProperty,
|
||||
editSaleProperty,
|
||||
} from "../../utils/api";
|
||||
|
||||
const DeleteConfirmationModal = ({
|
||||
@ -521,114 +522,140 @@ const PropertyViewModal = ({ isOpen, onClose, property }) => {
|
||||
};
|
||||
|
||||
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 [editingField, setEditingField] = useState(null);
|
||||
const [tempValues, setTempValues] = useState({});
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: "معلومات أساسية",
|
||||
fields: [
|
||||
{ id: "title", label: "عنوان العقار", type: "text" },
|
||||
{ id: "description", label: "الوصف", type: "textarea" },
|
||||
{
|
||||
id: "propertyType",
|
||||
label: "نوع العقار",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "apartment", label: "شقة" },
|
||||
{ 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" }],
|
||||
},
|
||||
];
|
||||
useEffect(() => {
|
||||
if (!property || !isOpen) return;
|
||||
const raw = property._raw || {};
|
||||
const info = raw.propertyInformation || {};
|
||||
let details = {};
|
||||
try {
|
||||
details =
|
||||
typeof info.detailsJSON === 'object' && info.detailsJSON
|
||||
? info.detailsJSON
|
||||
: JSON.parse(info.detailsJSON || '{}');
|
||||
} catch {
|
||||
details = {};
|
||||
}
|
||||
|
||||
const startEditing = (fieldId) => {
|
||||
setEditingField(fieldId);
|
||||
setTempValues({ ...tempValues, [fieldId]: formData[fieldId] });
|
||||
};
|
||||
|
||||
const cancelEditing = () => {
|
||||
setEditingField(null);
|
||||
setTempValues({});
|
||||
};
|
||||
|
||||
const saveField = (fieldId) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[fieldId]: tempValues[fieldId],
|
||||
const propServices = property.services || {};
|
||||
const services = {};
|
||||
const serviceDetails = {};
|
||||
Object.keys(serviceLabels).forEach((key) => {
|
||||
const val = propServices[key];
|
||||
if (val && typeof val === 'string') {
|
||||
services[key] = true;
|
||||
serviceDetails[key] = val;
|
||||
} else if (val && typeof val === 'object' && val.detail) {
|
||||
services[key] = true;
|
||||
serviceDetails[key] = val.detail;
|
||||
} else {
|
||||
services[key] = !!val;
|
||||
serviceDetails[key] = details.serviceDetails?.[key] || '';
|
||||
}
|
||||
});
|
||||
setEditingField(null);
|
||||
setTempValues({});
|
||||
const fieldLabel = sections
|
||||
.flatMap((s) => s.fields)
|
||||
.find((f) => f.id === fieldId)?.label;
|
||||
toast.success(`تم تحديث ${fieldLabel}`);
|
||||
|
||||
const propTerms = property.terms || {};
|
||||
const terms = {};
|
||||
Object.keys(termLabels).forEach((key) => {
|
||||
terms[key] = !!propTerms[key];
|
||||
});
|
||||
|
||||
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) => {
|
||||
setTempValues({ ...tempValues, [fieldId]: value });
|
||||
const handleServiceToggle = (key, checked) => {
|
||||
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);
|
||||
|
||||
setTimeout(() => {
|
||||
onSave(formData);
|
||||
try {
|
||||
await onSave(formData);
|
||||
} catch {
|
||||
setIsSaving(false);
|
||||
onClose();
|
||||
toast.success("تم تحديث العقار بنجاح");
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isOpen || !property) return null;
|
||||
@ -645,7 +672,7 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
||||
initial={{ scale: 0.9, y: 20 }}
|
||||
animate={{ scale: 1, y: 0 }}
|
||||
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()}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-8">
|
||||
{formData.images && formData.images.length > 0 && (
|
||||
<div className="p-6 space-y-6 overflow-y-auto flex-1">
|
||||
{/* Basic Info */}
|
||||
<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">
|
||||
{section.title}
|
||||
معلومات أساسية
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{section.fields.map((field) => (
|
||||
<div
|
||||
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}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
نوع العقار
|
||||
</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
|
||||
value={tempValues[field.id] || ""}
|
||||
value={formData.propertyType}
|
||||
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 key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
<option value="apartment">شقة</option>
|
||||
<option value="villa">فيلا</option>
|
||||
<option value="sweet">سويت</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>
|
||||
) : field.type === "radio" ? (
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
حالة التأثيث
|
||||
</label>
|
||||
<div className="flex gap-4">
|
||||
{field.options.map((opt) => (
|
||||
{[
|
||||
{ value: true, label: 'مفروش' },
|
||||
{ value: false, label: 'غير مفروش' },
|
||||
].map((opt) => (
|
||||
<label
|
||||
key={opt.value}
|
||||
className="flex items-center gap-2"
|
||||
key={String(opt.value)}
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={field.id}
|
||||
value={opt.value}
|
||||
checked={tempValues[field.id] === opt.value}
|
||||
onChange={(e) =>
|
||||
handleTempChange(
|
||||
field.id,
|
||||
e.target.value === "true",
|
||||
)
|
||||
}
|
||||
name="furnished"
|
||||
checked={formData.furnished === opt.value}
|
||||
onChange={() => handleChange('furnished', opt.value)}
|
||||
className="w-4 h-4 text-amber-500"
|
||||
/>
|
||||
<span>{opt.label}</span>
|
||||
<span className="text-sm">{opt.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<input
|
||||
type={field.type}
|
||||
value={tempValues[field.id] || ""}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
الوصف
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
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">
|
||||
<button
|
||||
onClick={() => saveField(field.id)}
|
||||
className="px-3 py-1 bg-green-500 text-white rounded-lg text-sm hover:bg-green-600"
|
||||
>
|
||||
حفظ
|
||||
</button>
|
||||
<button
|
||||
onClick={cancelEditing}
|
||||
className="px-3 py-1 bg-gray-500 text-white rounded-lg text-sm hover:bg-gray-600"
|
||||
>
|
||||
إلغاء
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-gray-900">
|
||||
{field.type === "select"
|
||||
? field.options.find(
|
||||
(opt) => opt.value === formData[field.id],
|
||||
)?.label || formData[field.id]
|
||||
: field.type === "radio"
|
||||
? formData[field.id]
|
||||
? "مفروش"
|
||||
: "غير مفروش"
|
||||
: field.id.includes("Price")
|
||||
? formData[field.id]
|
||||
? `${Number(formData[field.id]).toLocaleString()} ل.س`
|
||||
: "—"
|
||||
: formData[field.id] || "—"}
|
||||
</div>
|
||||
)}
|
||||
{/* Details */}
|
||||
<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-2 md:grid-cols-3 gap-4">
|
||||
{[
|
||||
{ id: 'bedrooms', label: 'عدد غرف النوم' },
|
||||
{ id: 'bathrooms', label: 'عدد الحمامات' },
|
||||
{ id: 'floor', label: 'الطابق' },
|
||||
{ id: 'salons', label: 'عدد الصالونات' },
|
||||
{ id: 'balconies', label: 'عدد البلكونات' },
|
||||
{ id: 'area', label: 'المساحة (م²)' },
|
||||
].map((field) => (
|
||||
<div key={field.id}>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{field.label}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={formData[field.id]}
|
||||
onChange={(e) =>
|
||||
handleChange(field.id, 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>
|
||||
))}
|
||||
|
||||
{/* Services */}
|
||||
<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-2 md:grid-cols-4 gap-3">
|
||||
{Object.entries(formData.services || {}).map(([key, value]) => {
|
||||
const serviceLabels = {
|
||||
electricity: "كهرباء",
|
||||
internet: "انترنت",
|
||||
heating: "تدفئة",
|
||||
water: "ماء",
|
||||
airConditioning: "تكييف",
|
||||
parking: "موقف سيارات",
|
||||
elevator: "مصعد",
|
||||
};
|
||||
return (
|
||||
<label
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{Object.entries(serviceLabels).map(([key, label]) => (
|
||||
<div
|
||||
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
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={(e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
services: {
|
||||
...formData.services,
|
||||
[key]: e.target.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="w-4 h-4 text-amber-500"
|
||||
checked={formData.services?.[key] || false}
|
||||
onChange={(e) =>
|
||||
handleServiceToggle(key, e.target.checked)
|
||||
}
|
||||
className="w-4 h-4 text-amber-500 mt-1"
|
||||
/>
|
||||
<span className="text-sm">{serviceLabels[key]}</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
<div className="flex-1">
|
||||
<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>
|
||||
|
||||
{/* Terms - rent only */}
|
||||
{formData.purpose === 'rent' && (
|
||||
<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-2 md:grid-cols-3 gap-3">
|
||||
{Object.entries(formData.terms || {}).map(([key, value]) => {
|
||||
const termLabels = {
|
||||
noSmoking: " ممنوع التدخين",
|
||||
noPets: " ممنوع الحيوانات",
|
||||
noParties: " ممنوع الحفلات",
|
||||
noAlcohol: " ممنوع الكحول",
|
||||
suitableForChildren: " مناسب للأطفال",
|
||||
suitableForElderly: " مناسب لكبار السن",
|
||||
};
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{Object.entries(termLabels).map(([key, label]) => (
|
||||
<label
|
||||
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
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={(e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
terms: {
|
||||
...formData.terms,
|
||||
[key]: e.target.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
checked={formData.terms?.[key] || false}
|
||||
onChange={(e) =>
|
||||
handleTermToggle(key, e.target.checked)
|
||||
}
|
||||
className="w-4 h-4 text-amber-500"
|
||||
/>
|
||||
<span className="text-sm">{termLabels[key]}</span>
|
||||
<span className="text-sm">{label}</span>
|
||||
</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 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
|
||||
onClick={onClose}
|
||||
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" />
|
||||
حفظ جميع التغييرات
|
||||
حفظ التغييرات
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
@ -1239,9 +1376,12 @@ export default function OwnerPropertiesPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveEdit = async (updatedProperty) => {
|
||||
const handleSaveEdit = async (formData) => {
|
||||
try {
|
||||
if (updatedProperty.purpose === "rent" && updatedProperty._raw) {
|
||||
const property = editModal.property;
|
||||
const raw = property._raw || {};
|
||||
const rawInfo = raw.propertyInformation || {};
|
||||
|
||||
const buildingTypeMap = {
|
||||
apartment: 0,
|
||||
villa: 1,
|
||||
@ -1253,76 +1393,125 @@ export default function OwnerPropertiesPage() {
|
||||
shop: 7,
|
||||
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 = {
|
||||
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,
|
||||
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: "",
|
||||
};
|
||||
const activeServices = Object.entries(formData.services || {})
|
||||
.filter(([, v]) => v)
|
||||
.map(([k]) => k);
|
||||
|
||||
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) =>
|
||||
p.id === updatedProperty.id ? updatedProperty : p,
|
||||
p.id === property.id ? updatedProperty : p,
|
||||
);
|
||||
updatePropertiesInStorage(newProperties);
|
||||
setEditModal({ isOpen: false, property: null });
|
||||
toast.success("تم تحديث العقار بنجاح");
|
||||
toast.success('تم تحديث العقار بنجاح');
|
||||
} catch (err) {
|
||||
console.error("[OwnerProperties] Edit failed:", err);
|
||||
toast.error("فشل تحديث العقار");
|
||||
console.error('[OwnerProperties] Edit failed:', err);
|
||||
toast.error('فشل تحديث العقار');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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) {
|
||||
console.log('[API] Adding sale property');
|
||||
return apiFetch('/SaleProperties/AddSaleProperty', {
|
||||
|
||||
Reference in New Issue
Block a user