refactor: migrate room photos from rooms.photos to images table
- Photos now stored exclusively in images table (no duplication) - rooms.photos array cleared, freed 17 MB storage - Updated room APIs to fetch images by room_id - Added PUT endpoint for room updates - Updated RoomEditor to load/delete images from images table - Fixed images API to return 'data' key consistently
This commit is contained in:
+31
-13
@@ -921,17 +921,25 @@ function RoomsSection({ rooms, setRooms, images, onToast }: any) {
|
||||
|
||||
function RoomEditor({ room, setRoom, images, onSave, onCancel }: any) {
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
const [localPhotos, setLocalPhotos] = useState<string[]>(room.photos || []);
|
||||
const [roomImages, setRoomImages] = useState<{id: number; data: string; thumbnail: string}[]>([]);
|
||||
const [allAmenities, setAllAmenities] = useState<Amenity[]>([]);
|
||||
const [selectedAmenities, setSelectedAmenities] = useState<number[]>(room.amenity_ids || []);
|
||||
const r = { ...room, photos: localPhotos, amenity_ids: selectedAmenities };
|
||||
const r = { ...room, photos: roomImages.map(img => img.thumbnail || img.data), amenity_ids: selectedAmenities };
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/amenities')
|
||||
.then(res => res.json())
|
||||
.then(d => setAllAmenities(d.data || []))
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
// Load existing images for this room from images table
|
||||
if (room.id) {
|
||||
fetch(`/api/admin/images?room_id=${room.id}`)
|
||||
.then(res => res.json())
|
||||
.then(d => setRoomImages(d.data || []))
|
||||
.catch(() => {});
|
||||
}
|
||||
}, [room.id]);
|
||||
|
||||
const toggleAmenity = (id: number) => {
|
||||
setSelectedAmenities(prev =>
|
||||
@@ -940,7 +948,7 @@ function RoomEditor({ room, setRoom, images, onSave, onCancel }: any) {
|
||||
};
|
||||
|
||||
const addPhotos = async (files: FileList) => {
|
||||
const newPhotos: string[] = [];
|
||||
const newImages: {id: number; data: string; thumbnail: string}[] = [];
|
||||
for (const f of Array.from(files)) {
|
||||
const b64 = await readFileBase64(f);
|
||||
try {
|
||||
@@ -948,10 +956,20 @@ function RoomEditor({ room, setRoom, images, onSave, onCancel }: any) {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ filename: f.name, data: b64, category: 'rooms', room_id: r.id || null, location_target: `room-${r.id || 'new'}` }),
|
||||
});
|
||||
if (res.ok) { const d = await res.json(); newPhotos.push(d.data.data); }
|
||||
} catch { const b = await readFileBase64(f); newPhotos.push(b); }
|
||||
if (res.ok) {
|
||||
const d = await res.json();
|
||||
newImages.push({ id: d.data.id, data: d.data.data, thumbnail: d.data.thumbnail || d.data.data });
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
setLocalPhotos(prev => [...prev, ...newPhotos]);
|
||||
setRoomImages(prev => [...prev, ...newImages]);
|
||||
};
|
||||
|
||||
const removeImage = async (imageId: number) => {
|
||||
try {
|
||||
await fetch(`/api/admin/images?id=${imageId}`, { method: 'DELETE' });
|
||||
setRoomImages(prev => prev.filter(img => img.id !== imageId));
|
||||
} catch { /* ignore */ }
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -965,15 +983,15 @@ function RoomEditor({ room, setRoom, images, onSave, onCancel }: any) {
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
<label style={{ fontSize: '13px', fontWeight: 600, color: C.text }}>Photos</label>
|
||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||
<Btn onClick={() => document.getElementById(`room-photos-${r.id}`)?.click()} size="sm" variant="secondary">+ Add Photos</Btn>
|
||||
<input id={`room-photos-${r.id}`} type="file" accept="image/*" multiple style={{ display: 'none' }}
|
||||
<Btn onClick={() => document.getElementById(`room-photos-${r.id || 'new'}`)?.click()} size="sm" variant="secondary">+ Add Photos</Btn>
|
||||
<input id={`room-photos-${r.id || 'new'}`} type="file" accept="image/*" multiple style={{ display: 'none' }}
|
||||
onChange={e => { addPhotos(e.target.files!); e.target.value = ''; }} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap' }}>
|
||||
{localPhotos.map((p, i) => (
|
||||
<div key={i} style={{ position: 'relative' }}>
|
||||
<img src={p} alt="" style={{ width: 64, height: 48, objectFit: 'cover', borderRadius: 6, border: '1px solid #eee' }} />
|
||||
<button onClick={() => setLocalPhotos(prev => prev.filter((_, j) => j !== i))}
|
||||
{roomImages.map((img, i) => (
|
||||
<div key={img.id || i} style={{ position: 'relative' }}>
|
||||
<img src={img.thumbnail || img.data} alt="" style={{ width: 64, height: 48, objectFit: 'cover', borderRadius: 6, border: '1px solid #eee' }} />
|
||||
<button onClick={() => removeImage(img.id)}
|
||||
style={{ position: 'absolute', top: -4, right: -4, width: 18, height: 18, borderRadius: '50%', background: C.danger, color: '#fff', border: 'none', cursor: 'pointer', fontSize: '11px', lineHeight: 18, padding: 0 }}>×</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user