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