Files
hotell777_260507/public/js/rooms-public.js
kalugin66 22df4e3c48
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 39s
фото
2026-07-18 12:21:03 +05:00

236 lines
11 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_ICONS = {
has_ac: { icon: 'fa-snowflake', label: 'Кондиционер' },
has_tv: { icon: 'fa-tv', label: 'ТВ' },
has_fridge: { icon: 'fa-sink', label: 'Холодильник' },
has_wifi: { icon: 'fa-wifi', label: 'WiFi' },
has_kettle: { icon: 'fa-mug-hot', label: 'Чайник' },
has_hairdryer: { icon: 'fa-wind', label: 'Фен' },
has_shower: { icon: 'fa-shower', label: 'Душ в номере' },
has_sea_view: { icon: 'fa-water', label: 'Вид на море' }
};
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}"
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"
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_ICONS[a];
if (!info) return '';
return `<span class="room-feature-tag"><i class="fas ${info.icon}"></i> ${info.label}</span>`;
}).join('');
const floorsStr = floors.length > 0 ? 'Этажи ' + floors.join(', ') : '';
const extraBedsHtml = room.extra_beds > 0
? `<div class="room-extra-hint">Возможно +${room.extra_beds} доп. мест (${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> Душ в номере</span>' : ''}
</div>
<div class="room-features">
${amenitiesHtml}
</div>
${extraBedsHtml}
<div class="room-footer">
<div class="room-price">
<span class="amount">от ${room.price_per_night || 0} ₽</span>
<span class="period"> / ночь</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}">Забронировать</button>
</div>
</div>
</div>
</div>
`;
}).join('');
initRoomBookingHandlers();
}
let roomCarouselState = {};
let roomCarouselTimers = {};
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++) {
let text = 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 = 'Нет мест';
el.style.background = 'rgba(239,68,68,0.85)';
} else if (avail.available <= 2) {
el.textContent = 'Мест: ' + avail.available;
el.style.background = 'rgba(245,158,11,0.85)';
} else {
el.textContent = 'Свободно: ' + 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();
});