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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user