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,
|
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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', {
|
||||||
|
|||||||
Reference in New Issue
Block a user