fixed some changes in the ccode
All checks were successful
Build frontend / build (push) Successful in 2m13s
All checks were successful
Build frontend / build (push) Successful in 2m13s
This commit is contained in:
@ -63,6 +63,7 @@ import {
|
||||
editSaleProperty,
|
||||
updateRentPropertyStatus,
|
||||
updateSalePropertyStatus,
|
||||
uploadPicture,
|
||||
} from "../../utils/api";
|
||||
import { PropertyService } from "../../enums/PropertyService";
|
||||
import { PropertyTerm } from "../../enums/PropertyTerm";
|
||||
@ -621,6 +622,16 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
||||
});
|
||||
const [newCustomTerm, setNewCustomTerm] = useState('');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [existingImages, setExistingImages] = useState([]);
|
||||
const [newImages, setNewImages] = useState([]);
|
||||
const editApiBase =
|
||||
process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
||||
|
||||
const editImgUrl = (path) => {
|
||||
if (!path) return null;
|
||||
if (path.startsWith('http://') || path.startsWith('https://')) return path;
|
||||
return `${editApiBase}${path}`;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!property || !isOpen) return;
|
||||
@ -718,6 +729,13 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
||||
: { salePrice: property.salePrice || 0 }),
|
||||
});
|
||||
setNewCustomTerm('');
|
||||
setNewImages([]);
|
||||
const rawImages = Array.isArray(property.images)
|
||||
? property.images
|
||||
: Array.isArray(info.images)
|
||||
? info.images
|
||||
: [];
|
||||
setExistingImages(rawImages.filter((p) => p && p.trim()));
|
||||
}, [property, isOpen]);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
@ -762,6 +780,76 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRemoveExistingImage = (index) => {
|
||||
setExistingImages((prev) => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleMoveExistingImage = (index, direction) => {
|
||||
setExistingImages((prev) => {
|
||||
const next = [...prev];
|
||||
const target = index + direction;
|
||||
if (target < 0 || target >= next.length) return prev;
|
||||
[next[index], next[target]] = [next[target], next[index]];
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddImages = async (files) => {
|
||||
const validFiles = Array.from(files).filter((f) => {
|
||||
if (!f.type.startsWith('image/')) return false;
|
||||
if (f.size > 5 * 1024 * 1024) {
|
||||
toast.error(t('fileTooLarge') || 'الصورة كبيرة جداً');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (validFiles.length === 0) return;
|
||||
const maxTotal = 10;
|
||||
const currentCount = existingImages.length + newImages.length;
|
||||
const slots = maxTotal - currentCount;
|
||||
if (slots <= 0) {
|
||||
toast.error(t('maxImagesReached') || 'الحد الأقصى 10 صور');
|
||||
return;
|
||||
}
|
||||
const toAdd = validFiles.slice(0, slots);
|
||||
const entries = toAdd.map((file) => ({
|
||||
file,
|
||||
preview: URL.createObjectURL(file),
|
||||
path: null,
|
||||
uploading: true,
|
||||
}));
|
||||
setNewImages((prev) => [...prev, ...entries]);
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
try {
|
||||
const result = await uploadPicture(entry.file);
|
||||
const path = result?.value || result?.path || result?.url || result;
|
||||
entry.path = path;
|
||||
} catch {
|
||||
toast.error(t('uploadFailed') || 'فشل رفع الصورة');
|
||||
} finally {
|
||||
entry.uploading = false;
|
||||
setNewImages((prev) => [...prev]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveNewImage = (index) => {
|
||||
const entry = newImages[index];
|
||||
if (entry?.preview) URL.revokeObjectURL(entry.preview);
|
||||
setNewImages((prev) => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleMoveNewImage = (index, direction) => {
|
||||
setNewImages((prev) => {
|
||||
const next = [...prev];
|
||||
const target = index + direction;
|
||||
if (target < 0 || target >= next.length) return prev;
|
||||
[next[index], next[target]] = [next[target], next[index]];
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const errors = [];
|
||||
if (!formData.description?.trim()) errors.push(t('descriptionRequired') || 'الوصف مطلوب');
|
||||
@ -781,7 +869,11 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
||||
}
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await onSave(formData);
|
||||
const uploadedPaths = newImages
|
||||
.filter((e) => e.path && !e.uploading)
|
||||
.map((e) => e.path);
|
||||
const allImages = [...existingImages, ...uploadedPaths];
|
||||
await onSave({ ...formData, _images: allImages });
|
||||
} catch {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@ -920,6 +1012,76 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Images */}
|
||||
<div className="bg-gray-50 p-4 rounded-xl">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('images')}</h3>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
|
||||
{existingImages.map((path, i) => (
|
||||
<div key={`e-${i}`} className="relative group aspect-square rounded-xl overflow-hidden bg-white border border-gray-200">
|
||||
<img src={editImgUrl(path)} alt="" className="w-full h-full object-cover" />
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2">
|
||||
<button type="button" onClick={() => handleRemoveExistingImage(i)} className="w-8 h-8 bg-red-500 text-white rounded-full flex items-center justify-center hover:bg-red-600 shadow-md">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
{i > 0 && (
|
||||
<button type="button" onClick={() => handleMoveExistingImage(i, -1)} className="w-8 h-8 bg-white text-gray-700 rounded-full flex items-center justify-center hover:bg-gray-100 shadow-md">
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
{i < existingImages.length - 1 && (
|
||||
<button type="button" onClick={() => handleMoveExistingImage(i, 1)} className="w-8 h-8 bg-white text-gray-700 rounded-full flex items-center justify-center hover:bg-gray-100 shadow-md">
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{newImages.map((entry, i) => (
|
||||
<div key={`n-${i}`} className="relative group aspect-square rounded-xl overflow-hidden bg-white border border-gray-200">
|
||||
{entry.uploading ? (
|
||||
<div className="w-full h-full flex items-center justify-center bg-gray-100">
|
||||
<Loader2 className="w-8 h-8 text-amber-500 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<img src={entry.preview} alt="" className="w-full h-full object-cover" />
|
||||
)}
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2">
|
||||
<button type="button" onClick={() => handleRemoveNewImage(i)} className="w-8 h-8 bg-red-500 text-white rounded-full flex items-center justify-center hover:bg-red-600 shadow-md">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
{i > 0 && (
|
||||
<button type="button" onClick={() => handleMoveNewImage(i, -1)} className="w-8 h-8 bg-white text-gray-700 rounded-full flex items-center justify-center hover:bg-gray-100 shadow-md">
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
{i < newImages.length - 1 && (
|
||||
<button type="button" onClick={() => handleMoveNewImage(i, 1)} className="w-8 h-8 bg-white text-gray-700 rounded-full flex items-center justify-center hover:bg-gray-100 shadow-md">
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{existingImages.length + newImages.length < 10 && (
|
||||
<label className="aspect-square rounded-xl border-2 border-dashed border-gray-300 bg-white flex flex-col items-center justify-center gap-1 cursor-pointer hover:border-amber-300 hover:bg-amber-50 transition-all">
|
||||
<ImageIcon className="w-8 h-8 text-gray-400" />
|
||||
<span className="text-xs text-gray-500">{t('addImage')}</span>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
multiple
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
handleAddImages(e.target.files);
|
||||
e.target.value = '';
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-400 mt-2">{existingImages.length + newImages.length}/10</p>
|
||||
</div>
|
||||
|
||||
{/* Services */}
|
||||
<div className="bg-gray-50 p-4 rounded-xl">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('services')}</h3>
|
||||
@ -1591,7 +1753,7 @@ export default function OwnerPropertiesPage() {
|
||||
const propInfo = {
|
||||
cordsX: rawInfo.cordsX || '',
|
||||
cordsY: rawInfo.cordsY || '',
|
||||
images: rawInfo.images || [],
|
||||
images: formData._images || rawInfo.images || [],
|
||||
address: property.address || rawInfo.address || '',
|
||||
description:
|
||||
formData.description || rawInfo.description || '',
|
||||
|
||||
@ -369,7 +369,7 @@ export async function editRentProperty(id, data) {
|
||||
|
||||
return apiFetch(`/RentProperties/EditRentProperty/${id}`, {
|
||||
method: "PUT",
|
||||
body: { rentPropertyDto: body },
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user