fix: widget rooms mode — show real photos, hide mock-only meta fields

- displayRooms now maps r.photos (URL strings) to photoUrls[]
- Collapsed thumbnail: shows <img> if real photo URL exists, falls back to emoji
- Expanded gallery: full-width <img> for real photos, gradient for mock
- Thumbnail strip: renders real photo thumbnails when available
- Photo nav buttons use totalPhotos (real or mock count)
- beds/area/rating hidden when value is 0 (real rooms have no beds/area/rating data)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-12 00:26:06 +03:00
parent c153e12543
commit 66cab853f1

View File

@@ -76,6 +76,7 @@ interface MockRoom {
area: number // m²
description: string
photos: { bg: string; emoji: string; label: string }[]
photoUrls: string[] // real photo URLs (empty for mock rooms)
amenities: string[]
has3dTour: boolean
}
@@ -90,7 +91,7 @@ const MOCK_ROOMS: MockRoom[] = [
{ bg: 'from-amber-100 to-amber-200', emoji: '🪟', label: 'Окно' },
],
amenities: ['Wi-Fi', 'TV', 'Кондиционер', 'Фен', 'Мини-холодильник', 'Сейф'],
has3dTour: false,
photoUrls: [], has3dTour: false,
},
{
id: 'r2', name: 'Комфорт', beds: 1, guests: 2, price: 6500, area: 30,
@@ -102,7 +103,7 @@ const MOCK_ROOMS: MockRoom[] = [
{ bg: 'from-lime-100 to-lime-200', emoji: '🛋️', label: 'Зона отдыха' },
],
amenities: ['Wi-Fi', 'TV', 'Кондиционер', 'Мини-бар', 'Халат', 'Фен', 'Сейф', 'Кофемашина'],
has3dTour: false,
photoUrls: [], has3dTour: false,
},
{
id: 'r3', name: 'Делюкс', beds: 2, guests: 3, price: 8900, area: 45,
@@ -115,7 +116,7 @@ const MOCK_ROOMS: MockRoom[] = [
{ bg: 'from-cyan-100 to-cyan-200', emoji: '🍽️', label: 'Мини-кухня' },
],
amenities: ['Wi-Fi', 'Smart TV', 'Кондиционер', 'Джакузи', 'Мини-бар', 'Халат', 'Тапочки', 'Кофемашина', 'Сейф', 'Балкон'],
has3dTour: true,
photoUrls: [], has3dTour: true,
},
{
id: 'r4', name: 'Пентхаус', beds: 2, guests: 4, price: 15000, area: 80,
@@ -129,7 +130,7 @@ const MOCK_ROOMS: MockRoom[] = [
{ bg: 'from-amber-100 to-yellow-100', emoji: '🍸', label: 'Бар' },
],
amenities: ['Wi-Fi 1Гбит', 'Smart TV 75"', 'Кондиционер', 'Сауна', 'Мини-бар', 'Джакузи', 'Терраса', 'Дворецкий', 'Кофемашина', 'Халат', 'Тапочки', 'Сейф'],
has3dTour: true,
photoUrls: [], has3dTour: true,
},
]
@@ -239,9 +240,12 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, realR
// Use real rooms if available, otherwise mock
const displayRooms = realRooms && realRooms.length > 0
? realRooms.map(r => ({
id: r.id, name: r.name || `Номер ${r.number}`, beds: 1,
id: r.id, name: r.name || `Номер ${r.number}`, beds: 0,
guests: r.maxGuests, price: r.baseRate, area: 0,
description: r.description, photos: [], amenities: r.amenities,
description: r.description ?? '',
photos: [] as { bg: string; emoji: string; label: string }[],
photoUrls: r.photos ?? [],
amenities: r.amenities ?? [],
has3dTour: false,
}))
: MOCK_ROOMS
@@ -966,8 +970,11 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, realR
{displayRooms.map(room => {
const isExpanded = expandedRoom === room.id
const isSelected = selected === room.id
const curPhoto = photoIndex[room.id] ?? 0
const hasRealPhotos = room.photoUrls.length > 0
const totalPhotos = hasRealPhotos ? room.photoUrls.length : room.photos.length
const curPhoto = Math.min(photoIndex[room.id] ?? 0, Math.max(0, totalPhotos - 1))
const photo = room.photos[curPhoto] ?? { bg: 'from-slate-200 to-slate-300', emoji: '🛏️', label: 'Фото' }
const curPhotoUrl = hasRealPhotos ? room.photoUrls[curPhoto] : null
return (
<div
key={room.id}
@@ -987,9 +994,13 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, realR
onClick={() => setSelected(room.id === selected ? null : room.id)}
>
{/* Thumbnail */}
<div className={cn('w-14 h-14 rounded-xl bg-gradient-to-br flex items-center justify-center text-2xl shrink-0', photo.bg)}>
{photo.emoji}
</div>
{curPhotoUrl ? (
<img src={curPhotoUrl} alt={room.name} className="w-14 h-14 rounded-xl object-cover shrink-0" />
) : (
<div className={cn('w-14 h-14 rounded-xl bg-gradient-to-br flex items-center justify-center text-2xl shrink-0', photo.bg)}>
{photo.emoji}
</div>
)}
<div className="flex-1 min-w-0">
<p className="font-semibold text-slate-900 text-sm">{room.name}</p>
@@ -997,13 +1008,14 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, realR
<span className="text-xs text-slate-500 flex items-center gap-0.5">
<Users size={10} /> до {room.guests}
</span>
<span className="text-xs text-slate-500 flex items-center gap-0.5">
<BedDouble size={10} /> {room.beds} кровати
</span>
<span className="text-xs text-slate-500">{room.area} м²</span>
<span className="text-xs text-slate-500 flex items-center gap-0.5">
<Star size={10} className="fill-amber-400 text-amber-400" />4.8
</span>
{room.beds > 0 && (
<span className="text-xs text-slate-500 flex items-center gap-0.5">
<BedDouble size={10} /> {room.beds} кр.
</span>
)}
{room.area > 0 && (
<span className="text-xs text-slate-500">{room.area} м²</span>
)}
</div>
</div>
@@ -1033,16 +1045,17 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, realR
<div className="border-t border-slate-100">
{/* Photo gallery */}
<div className="relative">
<div className={cn(
'h-44 bg-gradient-to-br flex items-center justify-center',
photo.bg,
)}>
<span className="text-6xl">{photo.emoji}</span>
<span className="absolute bottom-2 left-3 text-xs text-white/90 font-medium drop-shadow">{photo.label}</span>
</div>
{curPhotoUrl ? (
<img src={curPhotoUrl} alt={room.name} className="w-full h-44 object-cover" />
) : (
<div className={cn('h-44 bg-gradient-to-br flex items-center justify-center', photo.bg)}>
<span className="text-6xl">{photo.emoji}</span>
<span className="absolute bottom-2 left-3 text-xs text-white/90 font-medium drop-shadow">{photo.label}</span>
</div>
)}
{/* Photo nav */}
{room.photos.length > 1 && (
{totalPhotos > 1 && (
<>
<button
onClick={() => setPhotoIndex(prev => ({ ...prev, [room.id]: Math.max(0, (prev[room.id] ?? 0) - 1) }))}
@@ -1052,15 +1065,15 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, realR
<ChevronLeft size={14} />
</button>
<button
onClick={() => setPhotoIndex(prev => ({ ...prev, [room.id]: Math.min(room.photos.length - 1, (prev[room.id] ?? 0) + 1) }))}
disabled={curPhoto === room.photos.length - 1}
onClick={() => setPhotoIndex(prev => ({ ...prev, [room.id]: Math.min(totalPhotos - 1, (prev[room.id] ?? 0) + 1) }))}
disabled={curPhoto === totalPhotos - 1}
className="absolute right-2 top-1/2 -translate-y-1/2 w-7 h-7 rounded-full bg-black/30 hover:bg-black/50 flex items-center justify-center text-white disabled:opacity-0 transition-all"
>
<ChevronRight size={14} />
</button>
{/* Dots */}
<div className="absolute bottom-2 right-3 flex gap-1">
{room.photos.map((_, i) => (
{Array.from({ length: totalPhotos }).map((_, i) => (
<button
key={i}
onClick={() => setPhotoIndex(prev => ({ ...prev, [room.id]: i }))}
@@ -1072,10 +1085,12 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, realR
)}
{/* Photo count */}
<div className="absolute top-2 right-2 flex items-center gap-1 bg-black/30 text-white text-[10px] px-1.5 py-0.5 rounded-full">
<Image size={9} />
{curPhoto + 1}/{room.photos.length}
</div>
{totalPhotos > 0 && (
<div className="absolute top-2 right-2 flex items-center gap-1 bg-black/30 text-white text-[10px] px-1.5 py-0.5 rounded-full">
<Image size={9} />
{curPhoto + 1}/{totalPhotos}
</div>
)}
{/* 3D tour badge */}
{room.has3dTour && (
@@ -1087,22 +1102,32 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, realR
</div>
{/* Thumbnail strip */}
<div className="flex gap-1.5 px-3 py-2 overflow-x-auto">
{room.photos.map((p, i) => (
<button
key={i}
onClick={() => setPhotoIndex(prev => ({ ...prev, [room.id]: i }))}
className={cn(
'w-12 h-9 rounded-lg bg-gradient-to-br flex items-center justify-center text-lg shrink-0 transition-all',
p.bg,
i === curPhoto ? 'ring-2 ring-offset-1' : 'opacity-60 hover:opacity-90',
)}
style={i === curPhoto ? { '--tw-ring-color': settings.primaryColor } as any : {}}
>
{p.emoji}
</button>
))}
</div>
{totalPhotos > 1 && (
<div className="flex gap-1.5 px-3 py-2 overflow-x-auto">
{hasRealPhotos
? room.photoUrls.map((url, i) => (
<button
key={i}
onClick={() => setPhotoIndex(prev => ({ ...prev, [room.id]: i }))}
className={cn('w-12 h-9 rounded-lg overflow-hidden shrink-0 transition-all', i === curPhoto ? 'ring-2 ring-offset-1' : 'opacity-60 hover:opacity-90')}
style={i === curPhoto ? { '--tw-ring-color': settings.primaryColor } as any : {}}
>
<img src={url} alt="" className="w-full h-full object-cover" />
</button>
))
: room.photos.map((p, i) => (
<button
key={i}
onClick={() => setPhotoIndex(prev => ({ ...prev, [room.id]: i }))}
className={cn('w-12 h-9 rounded-lg bg-gradient-to-br flex items-center justify-center text-lg shrink-0 transition-all', p.bg, i === curPhoto ? 'ring-2 ring-offset-1' : 'opacity-60 hover:opacity-90')}
style={i === curPhoto ? { '--tw-ring-color': settings.primaryColor } as any : {}}
>
{p.emoji}
</button>
))
}
</div>
)}
{/* Description */}
<div className="px-3 pb-2">