Files
hotell777_260507/public/js/rooms-public.js
kalugin66 100fc38383
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 32s
рус3
2026-07-18 22:33:14 +05:00

260 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const AMENITY_KEYS = {
has_ac: { icon: 'fa-snowflake', key: 'amenity.ac' },
has_tv: { icon: 'fa-tv', key: 'amenity.tv' },
has_fridge: { icon: 'fa-sink', key: 'amenity.fridge' },
has_wifi: { icon: 'fa-wifi', key: 'amenity.wifi' },
has_kettle: { icon: 'fa-mug-hot', key: 'amenity.kettle' },
has_hairdryer: { icon: 'fa-wind', key: 'amenity.hairdryer' },
has_shower: { icon: 'fa-shower', key: 'amenity.shower' },
has_sea_view: { icon: 'fa-water', key: 'amenity.sea_view' }
};
function rt(key, fallback) {
return (window.I18n && typeof window.I18n.t === 'function') ? window.I18n.t(key) : fallback;
}
let cachedRooms = [];
async function loadRoomsPublic() {
try {
const res = await fetch('/api/rooms');
if (!res.ok) throw new Error('Failed to load rooms');
cachedRooms = await res.json();
renderRoomsPublic(cachedRooms);
updateRoomPrices(cachedRooms);
} catch (err) {
console.error('Error loading rooms:', err);
document.getElementById('roomsEmpty').style.display = 'block';
}
}
function renderRoomsPublic(rooms) {
const grid = document.getElementById('roomsGrid');
const empty = document.getElementById('roomsEmpty');
if (!rooms || rooms.length === 0) {
grid.innerHTML = '';
empty.style.display = 'block';
return;
}
empty.style.display = 'none';
grid.innerHTML = rooms.map((room, index) => {
const amenities = Array.isArray(room.amenities) ? room.amenities : [];
const floors = Array.isArray(room.floors) ? room.floors : [];
const images = Array.isArray(room.images) ? room.images : [];
const primaryImg = images.find(i => i.is_primary) || images[0];
let imageSrc = room.image_path || 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 250"%3E%3Crect fill="%23274151" width="400" height="250"/%3E%3Ctext fill="%2364748b" font-family="sans-serif" font-size="16" x="50%25" y="50%25" text-anchor="middle" dy=".3em"%3EФото%3C/text%3E%3C/svg%3E';
if (primaryImg && primaryImg.image_path) {
imageSrc = primaryImg.image_path;
}
const fullImageSrc = imageSrc.startsWith('uploads') || imageSrc.startsWith('data') ? '/' + imageSrc : imageSrc;
const allImages = images.length > 0 ? images : (room.image_path ? [{ image_path: room.image_path, is_primary: 1 }] : []);
let imagesHtml = '';
if (allImages.length > 1) {
const dotsHtml = allImages.map((img, i) => {
const src = img.image_path.startsWith('uploads') || img.image_path.startsWith('data') ? '/' + img.image_path : img.image_path;
return `<span class="room-img-dot ${i === 0 ? 'active' : ''}" data-index="${i}" data-src="${escapeHtml(src)}"></span>`;
}).join('');
imagesHtml = `<div class="room-img-carousel">
<img src="${fullImageSrc}" alt="${room.name}" class="room-carousel-img" id="roomCarouselImg${index}"
onclick="roomImageClick(${index})"
onerror="this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 400 250%22%3E%3Crect fill=%22%23274151%22 width=%22400%22 height=%22250%22/%3E%3Ctext fill=%22%2364748b%22 font-family=%22sans-serif%22 font-size=%2216%22 x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 dy=%22.3em%22%3EФото%3C/text%3E%3C/svg%3E'">
<button class="room-carousel-prev" onclick="roomCarouselPrev(${index})">‹</button>
<button class="room-carousel-next" onclick="roomCarouselNext(${index})">›</button>
<div class="room-img-dots">${dotsHtml}</div>
</div>`;
} else {
imagesHtml = `<img src="${fullImageSrc}" alt="${room.name}" class="room-carousel-img" id="roomCarouselImg${index}"
onclick="roomImageClick(${index})"
onerror="this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 400 250%22%3E%3Crect fill=%22%23274151%22 width=%22400%22 height=%22250%22/%3E%3Ctext fill=%22%2364748b%22 font-family=%22sans-serif%22 font-size=%2216%22 x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 dy=%22.3em%22%3EФото%3C/text%3E%3C/svg%3E'">`;
}
const amenitiesHtml = amenities.map(a => {
const info = AMENITY_KEYS[a];
if (!info) return '';
return `<span class="room-feature-tag"><i class="fas ${info.icon}"></i> ${rt(info.key, a)}</span>`;
}).join('');
const floorsStr = floors.length > 0 ? rt('rooms.floors', 'Этажи') + ' ' + floors.join(', ') : '';
const extraBedsHtml = room.extra_beds > 0
? `<div class="room-extra-hint">${rt('rooms.extra_beds', 'Возможно').replace('{n}', room.extra_beds).replace('{price}', room.extra_bed_price)} +${room.extra_beds} ${rt('rooms.extra_beds_rest', 'доп. мест')} (${room.extra_bed_price} ₽)</div>`
: '';
const featuredClass = room.type === 'Семейный' ? ' featured' : '';
return `
<div class="col-lg-4">
<div class="room-card${featuredClass}">
<div class="room-image">
${imagesHtml}
<div class="room-category">${room.type}</div>
<div class="room-availability" id="roomAvail${index}" style="display:none;position:absolute;bottom:8px;left:8px;background:rgba(0,0,0,0.75);color:#fff;font-size:0.75rem;padding:3px 8px;border-radius:10px;"></div>
</div>
<div class="room-body">
<h3 class="room-name">${escapeHtml(room.name)}</h3>
<p class="room-desc">${escapeHtml(room.description || '')}</p>
<div class="room-meta">
<span><i class="fas fa-door-open"></i> ${room.area_sqm || 20} м²</span>
${floorsStr ? `<span><i class="fas fa-layer-group"></i> ${floorsStr}</span>` : ''}
${amenities.includes('has_shower') ? '<span><i class="fas fa-shower"></i> ' + rt('amenity.shower', 'Душ в номере') + '</span>' : ''}
</div>
<div class="room-features">
${amenitiesHtml}
</div>
${extraBedsHtml}
<div class="room-footer">
<div class="room-price">
<span class="amount">${rt('rooms.from', 'от')} ${room.price_per_night || 0} ₽</span>
<span class="period"> ${rt('rooms.per_night', '/ ночь')}</span>
</div>
<button class="btn-book" data-bs-toggle="modal" data-bs-target="#bookingModal" data-room-id="${room.id}" data-room-type="${room.type}" data-room-name="${escapeHtml(room.name)}" data-max-guests="${room.max_guests}">${rt('rooms.book', 'Забронировать')}</button>
</div>
</div>
</div>
</div>
`;
}).join('');
initRoomBookingHandlers();
Object.values(roomCarouselTimers).forEach(t => clearInterval(t));
roomCarouselTimers = {};
rooms.forEach((room, i) => {
const images = Array.isArray(room.images) ? room.images : [];
if (images.length > 1) {
roomCarouselTimers[i] = setInterval(() => roomCarouselNext(i), 10000);
}
});
}
let roomCarouselState = {};
let roomCarouselTimers = {};
function roomImageClick(index) {
const img = document.getElementById('roomCarouselImg' + index);
const room = cachedRooms[index];
if (img && room) {
openLightbox(img.src, room.name);
}
}
function roomCarouselPrev(index) {
if (!roomCarouselState[index]) roomCarouselState[index] = 0;
const imgs = getRoomImagesForIndex(index);
if (roomCarouselState[index] > 0) roomCarouselState[index]--;
else roomCarouselState[index] = imgs.length - 1;
applyCarouselFrame(index, imgs);
}
function roomCarouselNext(index) {
if (!roomCarouselState[index]) roomCarouselState[index] = 0;
const imgs = getRoomImagesForIndex(index);
if (roomCarouselState[index] < imgs.length - 1) roomCarouselState[index]++;
else roomCarouselState[index] = 0;
applyCarouselFrame(index, imgs);
}
function getRoomImagesForIndex(index) {
const room = cachedRooms[index];
if (!room) return [];
const images = Array.isArray(room.images) && room.images.length > 0 ? room.images : (room.image_path ? [{ image_path: room.image_path, is_primary: 1 }] : []);
return images;
}
function applyCarouselFrame(index, images) {
const img = document.getElementById('roomCarouselImg' + index);
const dots = document.querySelectorAll(`#roomCarouselImg${index} ~ .room-img-dots .room-img-dot`);
if (!img || images.length === 0) return;
const state = roomCarouselState[index];
const src = images[state].image_path.startsWith('uploads') || images[state].image_path.startsWith('data') ? '/' + images[state].image_path : images[state].image_path;
img.src = src;
dots.forEach((d, i) => d.classList.toggle('active', i === state));
}
function updateRoomPrices(rooms) {
const prices = {};
const maxGuests = {};
rooms.forEach(room => {
prices[room.type] = room.price_per_night;
maxGuests[room.type] = room.max_guests;
});
if (window.updateRoomPricesData) {
window.updateRoomPricesData(prices, maxGuests);
}
}
function initRoomBookingHandlers() {
document.querySelectorAll('.btn-book').forEach(btn => {
btn.addEventListener('click', function() {
const roomId = this.getAttribute('data-room-id');
const roomType = this.getAttribute('data-room-type');
const roomName = this.getAttribute('data-room-name');
const maxGuests = parseInt(this.getAttribute('data-max-guests')) || 4;
document.getElementById('selectedRoom').value = roomType;
document.getElementById('selectedRoomId').value = roomId;
updateGuestOptionsDynamic(roomType, maxGuests);
hidePriceInfo();
});
});
}
function updateGuestOptionsDynamic(roomType, maxGuests) {
const guestsSelect = document.querySelector('[name="guests"]');
if (!guestsSelect) return;
let options = [];
for (let i = 1; i <= maxGuests; i++) {
const key = 'booking.guest_' + i;
let text = rt(key, i === 1 ? '1 гость' : (i < 5 ? i + ' гостя' : i + ' гостей'));
options.push(`<option value="${i}">${text}</option>`);
}
guestsSelect.innerHTML = options.join('');
}
async function checkRoomAvailability() {
const checkin = document.querySelector('[name="checkin"]').value;
const checkout = document.querySelector('[name="checkout"]').value;
if (!checkin || !checkout) return;
try {
const res = await fetch(`/api/rooms/availability?checkin=${checkin}&checkout=${checkout}`);
if (!res.ok) return;
const data = await res.json();
cachedRooms.forEach((room, i) => {
const avail = data.find(a => a.id === room.id);
const el = document.getElementById('roomAvail' + i);
if (el && avail) {
el.style.display = 'block';
if (avail.available === 0) {
el.textContent = rt('rooms.no_rooms', 'Нет мест');
el.style.background = 'rgba(239,68,68,0.85)';
} else if (avail.available <= 2) {
el.textContent = rt('rooms.rooms_left', 'Мест:') + ' ' + avail.available;
el.style.background = 'rgba(245,158,11,0.85)';
} else {
el.textContent = rt('rooms.available', 'Свободно:') + ' ' + avail.available;
el.style.background = 'rgba(22,163,74,0.85)';
}
}
});
} catch (e) {}
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
document.addEventListener('DOMContentLoaded', () => {
loadRoomsPublic();
});