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,
|
editSaleProperty,
|
||||||
updateRentPropertyStatus,
|
updateRentPropertyStatus,
|
||||||
updateSalePropertyStatus,
|
updateSalePropertyStatus,
|
||||||
|
uploadPicture,
|
||||||
} from "../../utils/api";
|
} from "../../utils/api";
|
||||||
import { PropertyService } from "../../enums/PropertyService";
|
import { PropertyService } from "../../enums/PropertyService";
|
||||||
import { PropertyTerm } from "../../enums/PropertyTerm";
|
import { PropertyTerm } from "../../enums/PropertyTerm";
|
||||||
@ -621,6 +622,16 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
|||||||
});
|
});
|
||||||
const [newCustomTerm, setNewCustomTerm] = useState('');
|
const [newCustomTerm, setNewCustomTerm] = useState('');
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!property || !isOpen) return;
|
if (!property || !isOpen) return;
|
||||||
@ -718,6 +729,13 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
|||||||
: { salePrice: property.salePrice || 0 }),
|
: { salePrice: property.salePrice || 0 }),
|
||||||
});
|
});
|
||||||
setNewCustomTerm('');
|
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]);
|
}, [property, isOpen]);
|
||||||
|
|
||||||
const handleChange = (field, value) => {
|
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 handleSave = async () => {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
if (!formData.description?.trim()) errors.push(t('descriptionRequired') || 'الوصف مطلوب');
|
if (!formData.description?.trim()) errors.push(t('descriptionRequired') || 'الوصف مطلوب');
|
||||||
@ -781,7 +869,11 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
|||||||
}
|
}
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
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 {
|
} catch {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
@ -920,6 +1012,76 @@ const PropertyEditModal = ({ isOpen, onClose, property, onSave }) => {
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* 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">{t('services')}</h3>
|
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('services')}</h3>
|
||||||
@ -1591,7 +1753,7 @@ export default function OwnerPropertiesPage() {
|
|||||||
const propInfo = {
|
const propInfo = {
|
||||||
cordsX: rawInfo.cordsX || '',
|
cordsX: rawInfo.cordsX || '',
|
||||||
cordsY: rawInfo.cordsY || '',
|
cordsY: rawInfo.cordsY || '',
|
||||||
images: rawInfo.images || [],
|
images: formData._images || rawInfo.images || [],
|
||||||
address: property.address || rawInfo.address || '',
|
address: property.address || rawInfo.address || '',
|
||||||
description:
|
description:
|
||||||
formData.description || rawInfo.description || '',
|
formData.description || rawInfo.description || '',
|
||||||
|
|||||||
@ -369,7 +369,7 @@ export async function editRentProperty(id, data) {
|
|||||||
|
|
||||||
return apiFetch(`/RentProperties/EditRentProperty/${id}`, {
|
return apiFetch(`/RentProperties/EditRentProperty/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: { rentPropertyDto: body },
|
body,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
199
edit-property-flow.md
Normal file
199
edit-property-flow.md
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
# Edit Property Flow - Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The edit property flow allows property owners to update their rent property listing, including all fields and images. It shares the same screen as add property (`add_rent_property_screen.dart`) and switches behavior based on `widget.initialListing != null` (edit mode).
|
||||||
|
|
||||||
|
## Screen: `add_rent_property_screen.dart`
|
||||||
|
|
||||||
|
### State Variables for Images
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Existing images loaded from the listing (edit mode)
|
||||||
|
List<String> _existingImagePaths = [];
|
||||||
|
|
||||||
|
// Newly selected images (both add & edit mode)
|
||||||
|
List<XFile> _formImages = [];
|
||||||
|
List<String> _imageSlotIds = [];
|
||||||
|
List<String> _imageFingerprints = [];
|
||||||
|
List<Uint8List> _imagePreviews = [];
|
||||||
|
List<String> _uploadedImagePaths = [];
|
||||||
|
Set<String> _uploadingImageKeys = {};
|
||||||
|
Set<String> _failedImageKeys = {};
|
||||||
|
|
||||||
|
int get _totalImageCount => _existingImagePaths.length + _formImages.length;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Initialization in Edit Mode
|
||||||
|
|
||||||
|
When editing, `_prefillFromInitialListing()` populates `_existingImagePaths` from the API response:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
_existingImagePaths = info.images
|
||||||
|
.map((path) => path.trim())
|
||||||
|
.where((path) => path.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Management UI (Edit Mode)
|
||||||
|
|
||||||
|
| Feature | Implementation |
|
||||||
|
|---------|---------------|
|
||||||
|
| **View existing images** | `Image.network` with `ApiConstants.resolveApiFileUrl()` |
|
||||||
|
| **Delete existing image** | X button → `_removeExistingImageAt(index)` removes from `_existingImagePaths` |
|
||||||
|
| **Replace existing image** | Tap on image → `_replaceExistingImageAt(index)` → file picker → upload via `POST /Files/UploadPicture` → replaces URL at same index |
|
||||||
|
| **Add new image** | Upload button → `_handleImageUpload()` → `_addSelectedImage()` → upload via `POST /Files/UploadPicture` |
|
||||||
|
| **Reorder existing images** | Up/down arrow buttons → `_moveExistingImage(index, direction)` |
|
||||||
|
| **Reorder new images** | Up/down arrow buttons → `_moveSelectedImage(index, direction)` (moves all 6 parallel lists together) |
|
||||||
|
|
||||||
|
### Submit - Image Assembly
|
||||||
|
|
||||||
|
On submit, the final image list is built from:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final allImagePaths = <String>[
|
||||||
|
..._existingImagePaths
|
||||||
|
.map((path) => path.trim())
|
||||||
|
.where((path) => path.isNotEmpty),
|
||||||
|
...uploadedImagePaths,
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
This combined list is passed to `AddRentPropertyDto` → `PropertyInformationDto` as `images`.
|
||||||
|
|
||||||
|
## API Details
|
||||||
|
|
||||||
|
### Image Upload Endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /Files/UploadPicture
|
||||||
|
Headers: Authorization: Bearer <token>, Content-Type: multipart/form-data
|
||||||
|
Body: image=<file>
|
||||||
|
Response: { "value": "/Pictures/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.jpg" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The returned path is used in the edit request.
|
||||||
|
|
||||||
|
### Edit Property Endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
PUT /RentProperties/EditRentProperty/{id}
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Request Body (camelCase contract from swagger)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"propertyInformation": {
|
||||||
|
"activityStatus": 0,
|
||||||
|
"images": [
|
||||||
|
"/Pictures/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.jpg"
|
||||||
|
],
|
||||||
|
"cordsX": "33.51599774641418",
|
||||||
|
"cordsY": "36.281279544066294",
|
||||||
|
"address": "string",
|
||||||
|
"description": "string",
|
||||||
|
"numberOfBathRooms": 2,
|
||||||
|
"numberOfRooms": 3,
|
||||||
|
"numberOfBedRooms": 3,
|
||||||
|
"space": 120.0,
|
||||||
|
"detailsJSON": "{}",
|
||||||
|
"buildingType": 0,
|
||||||
|
"status": 0,
|
||||||
|
"propertyType": 0,
|
||||||
|
"city": 0
|
||||||
|
},
|
||||||
|
"deposit": 5.0,
|
||||||
|
"acceptedCertificate": 0,
|
||||||
|
"monthlyRent": 0.0,
|
||||||
|
"dailyRent": 56.0,
|
||||||
|
"rating": 1.0,
|
||||||
|
"currencyId": 1,
|
||||||
|
"rentType": 1,
|
||||||
|
"isSmokeAllow": false,
|
||||||
|
"specializedFor": false,
|
||||||
|
"isVisitorAllow": false,
|
||||||
|
"allowedPaymentPeriod": "10:02:00",
|
||||||
|
"type": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Payload Attempt Order (Flutter side)
|
||||||
|
|
||||||
|
The app tries multiple payload shapes as fallback:
|
||||||
|
|
||||||
|
| Order | Label | Structure | Example |
|
||||||
|
|-------|-------|-----------|---------|
|
||||||
|
| 1 | `plain_pascal` | PascalCase direct | `{PropertyInformation: {Images: [...]}}` |
|
||||||
|
| 2 | `wrapped_pascal_outer` | PascalCase with DTO wrapper | `{RentPropertyDto: {PropertyInformation: {Images: [...]}}}` |
|
||||||
|
| 3 | `wrapped_pascal_inner` | PascalCase inside camelCase wrapper | `{rentPropertyDto: {PropertyInformation: {Images: [...]}}}` |
|
||||||
|
| 4 | `wrapped_camel` | camelCase wrapped | `{rentPropertyDto: {propertyInformation: {images: [...]}}}` |
|
||||||
|
| 5 | `plain_camel` | camelCase direct | `{propertyInformation: {images: [...]}}` |
|
||||||
|
| 6 | `multipart_form` | multipart form fields | `PropertyInformation.Images[0] = url` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 CONFIRMED BACKEND BUG
|
||||||
|
|
||||||
|
### Symptoms
|
||||||
|
- **All scalar fields** (deposit, dailyRent, address, etc.) update correctly via PUT
|
||||||
|
- **`propertyInformation.images`** is ALWAYS ignored by the PUT endpoint
|
||||||
|
- **`propertyInformation.detailsJSON`** is also ignored by the PUT endpoint
|
||||||
|
- The **POST add endpoint** handles both fields correctly
|
||||||
|
|
||||||
|
### Evidence from Direct API Tests
|
||||||
|
|
||||||
|
**Test 1 - camelCase JSON via curl:**
|
||||||
|
```
|
||||||
|
PUT /RentProperties/EditRentProperty/1
|
||||||
|
→ images sent: ["/Pictures/test1.jpg", "/Pictures/test2.jpg"]
|
||||||
|
→ images response: ["/Pictures/f7062fc863a74306a6a0340982669386.png"] ❌ OLD
|
||||||
|
→ address sent: "test address"
|
||||||
|
→ address response: "test address" ✅ UPDATED
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test 2 - PascalCase JSON via curl:**
|
||||||
|
```
|
||||||
|
PUT /RentProperties/EditRentProperty/1
|
||||||
|
→ Images sent: ["/Pictures/new1.jpg", "/Pictures/new2.jpg"]
|
||||||
|
→ images response: ["/Pictures/f7062fc863a74306a6a0340982669386.png"] ❌ OLD
|
||||||
|
→ Address sent: "test pascal"
|
||||||
|
→ Address response: "test pascal" ✅ UPDATED
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test 3 - POST add endpoint (works correctly):**
|
||||||
|
```
|
||||||
|
POST /RentProperties/AddRentProperty
|
||||||
|
→ images sent: ["/Pictures/test1.jpg", "/Pictures/test2.jpg"]
|
||||||
|
→ images response: ["/Pictures/test1.jpg", "/Pictures/test2.jpg"] ✅ STORED CORRECTLY
|
||||||
|
```
|
||||||
|
|
||||||
|
### Root Cause (Backend Side)
|
||||||
|
|
||||||
|
The `EditRentProperty` PUT handler in the backend likely:
|
||||||
|
1. Creates a new `RentProperty` record (or updates it) - scalar fields work
|
||||||
|
2. Updates or assigns the `PropertyInformation` record - but only scalar fields are copied
|
||||||
|
3. The `Images` and `DetailsJSON` properties of `PropertyInformation` are **not mapped/copied** during update
|
||||||
|
|
||||||
|
Comparison with `AddRentProperty`:
|
||||||
|
- The POST handler correctly maps all `PropertyInformation` fields including `Images` and `DetailsJSON`
|
||||||
|
- The PUT handler appears to skip these two fields specifically
|
||||||
|
|
||||||
|
### Required Backend Fix
|
||||||
|
|
||||||
|
The PUT endpoint handler needs to be updated to persist `propertyInformation.images` and `propertyInformation.detailsJSON` when updating existing property information records.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Aspect | Status |
|
||||||
|
|--------|--------|
|
||||||
|
| Image upload (to server) | ✅ Working via `POST /Files/UploadPicture` |
|
||||||
|
| Image management UI (edit mode) | ✅ Delete, Replace, Add, Reorder all implemented |
|
||||||
|
| Image assembly for submit | ✅ Correctly combines kept + new uploaded paths |
|
||||||
|
| Payload format (camelCase/PascalCase) | ✅ Both formats tried correctly |
|
||||||
|
| **PUT edit endpoint** | **❌ Does NOT update images or detailsJSON** |
|
||||||
|
| POST add endpoint | ✅ Fully functional |
|
||||||
Reference in New Issue
Block a user