// Database Management class DatabaseManager { constructor() { this.dbName = 'HotelBookingDB'; this.version = 1; this.db = null; } initDB() { return new Promise((resolve, reject) => { if (this.db) return resolve(this.db); const request = indexedDB.open(this.dbName, this.version); request.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains('rooms')) { db.createObjectStore('rooms', {keyPath: 'id'}); } if (!db.objectStoreNames.contains('bookings')) { db.createObjectStore('bookings', {keyPath: 'id', autoIncrement: true}); } if (!db.objectStoreNames.contains('gallery')) { db.createObjectStore('gallery', {keyPath: 'id'}); } }; request.onsuccess = (e) => { this.db = e.target.result; resolve(this.db); }; request.onerror = (e) => reject(e.target.error); }); } addData(storeName, data) { return new Promise((resolve, reject) => { const tx = this.db.transaction(storeName, 'readwrite'); const store = tx.objectStore(storeName); const req = store.put(data); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } getData(storeName, id) { return new Promise((resolve, reject) => { const tx = this.db.transaction(storeName, 'readonly'); const store = tx.objectStore(storeName); if (typeof id !== 'undefined') { const req = store.get(id); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); } else { const req = store.getAll(); req.onsuccess = () => resolve(req.result || []); req.onerror = () => reject(req.error); } }); } updateData(storeName, data) { return this.addData(storeName, data); } deleteData(storeName, id) { return new Promise((resolve, reject) => { const tx = this.db.transaction(storeName, 'readwrite'); const store = tx.objectStore(storeName); const req = store.delete(id); req.onsuccess = () => resolve(); req.onerror = () => reject(req.error); }); } } class HotelBookingApp { constructor() { this.db = new DatabaseManager(); this.currentBooking = null; this.currentRoom = null; this.init(); } async init() { try { await this.db.initDB(); await this.seedData(); this.setupEventListeners(); this.loadFeaturedRooms(); this.setupSEO(); this.setupAnimations(); this.setDefaultDates(); this.setFooterYear(); } catch (error) { console.error('Error initializing app:', error); this.showNotification('เกิดข้อผิดพลาดในการเริ่มต้นระบบ', 'error'); } } showNotification(message, type = 'info') { // Minimal notification: console + small DOM toast console.log('NOTIFY', type, message); try { let toast = document.getElementById('app-toast'); if (!toast) { toast = document.createElement('div'); toast.id = 'app-toast'; toast.style.position = 'fixed'; toast.style.right = '1rem'; toast.style.bottom = '1rem'; toast.style.padding = '0.8rem 1rem'; toast.style.borderRadius = '6px'; toast.style.boxShadow = '0 6px 18px rgba(0,0,0,0.12)'; toast.style.zIndex = 99999; document.body.appendChild(toast); } toast.textContent = message; toast.style.background = type === 'error' ? 'rgba(220,50,50,0.95)' : (type === 'success' ? 'rgba(40,160,90,0.95)' : 'rgba(30,30,30,0.95)'); toast.style.color = '#fff'; toast.style.opacity = '1'; setTimeout(() => { try {toast.style.opacity = '0';} catch (e) {} }, 3000); } catch (e) { // noop } } setFooterYear() { try { const el = document.getElementById('year'); if (el) el.textContent = new Date().getFullYear(); } catch (e) { // ignore if DOM not ready or element missing } } async seedData() { const existingRooms = await this.db.getData('rooms'); if (existingRooms.length === 0) { const rooms = [ { id: 1, name: 'ห้องสวีท เอ็กเซกคิวทีฟ', type: 'suite', price: 8500, maxGuests: 4, size: 85, description: 'ห้องสวีทหรูหราพร้อมวิวทะเลที่สวยงาม ตกแต่งด้วยเฟอร์นิเจอร์คลาสสิกและสิ่งอำนวยความสะดวกครบครัน', features: ['เตียงคิงไซซ์', 'วิวทะเล', 'บาร์มินิ', 'อ่างอาบน้ำจากุซซี่', 'ระเบียงส่วนตัว'], images: [ 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80', 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1590490360182-c33d57733427?ixlib=rb-4.0.3' ], amenities: ['WiFi ฟรี', 'เครื่องปรับอากาศ', 'ทีวี 55 นิ้ว', 'ตู้เซฟ', 'โต๊ะทำงาน'] }, { id: 2, name: 'ห้องดีลักซ์', type: 'deluxe', price: 4500, maxGuests: 2, size: 45, description: 'ห้องพักสะดวกสบายพร้อมการตกแต่งที่ทันสมัย เหมาะสำหรับคู่รักหรือนักธุรกิจ', features: ['เตียงควีนไซซ์', 'วิวเมือง', 'โซฟาพักผ่อน', 'ฝักบัวเรนฟอลล์', 'ระเบียงเล็ก'], images: [ 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1563298723-dcfebaa392e3?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1522771739844-6a9f6d5f14af?ixlib=rb-4.0.3' ], amenities: ['WiFi ฟรี', 'เครื่องปรับอากาศ', 'ทีวี 43 นิ้ว', 'ตู้เซฟ', 'โต๊ะทำงาน'] }, { id: 3, name: 'ห้องสแตนดาร์ด', type: 'standard', price: 2800, maxGuests: 2, size: 32, description: 'ห้องพักประหยัดที่ครบครันด้วยสิ่งอำนวยความสะดวกที่จำเป็น เหมาะสำหรับการเดินทางทั่วไป', features: ['เตียงคู่', 'วิวสวน', 'โต๊ะเล็ก', 'ห้องน้ำส่วนตัว'], images: [ 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1551632436-cbf8dd35adfa?ixlib=rb-4.0.3' ], amenities: ['WiFi ฟรี', 'เครื่องปรับอากาศ', 'ทีวี 32 นิ้ว', 'ตู้เซฟ'] }, { id: 4, name: 'ห้องแฟมิลี่', type: 'family', price: 6200, maxGuests: 6, size: 65, description: 'ห้องพักขนาดใหญ่เหมาะสำหรับครอบครัว มีพื้นที่นั่งเล่นและเตียงเสริม', features: ['เตียงคิงไซซ์ + เตียงเสริม', 'ห้องนั่งเล่น', 'ครัวเล็ก', 'ระเบียงกว้าง'], images: [ 'https://images.unsplash.com/photo-1566195992011-5f6b21e539aa?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1598928506311-c55ded91a20c?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1595576508898-0ad5c879a061?ixlib=rb-4.0.3' ], amenities: ['WiFi ฟรี', 'เครื่องปรับอากาศ', 'ทีวี 50 นิ้ว', 'ตู้เซฟ', 'ไมโครเวฟ', 'ตู้เย็นเล็ก'] }, { id: 5, name: 'ห้องพรีเมียม', type: 'premium', price: 5800, maxGuests: 3, size: 55, description: 'ห้องพักคุณภาพสูงพร้อมการตกแต่งสไตล์โมเดิร์น และสิ่งอำนวยความสะดวกระดับพรีเมียม', features: ['เตียงคิงไซซ์', 'วิวสระว่ายน้ำ', 'โซฟาเบด', 'ห้องน้ำแยก', 'ระเบียงส่วนตัว'], images: [ 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1584132967334-10e028bd69f7?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1582719508461-905c673771fd?ixlib=rb-4.0.3' ], amenities: ['WiFi ฟรี', 'เครื่องปรับอากาศ', 'ทีวี 48 นิ้ว', 'ตู้เซฟ', 'เครื่องชงกาแฟ', 'โต๊ะทำงาน'] }, { id: 6, name: 'ห้องเพนท์เฮาส์', type: 'penthouse', price: 12000, maxGuests: 8, size: 120, description: 'ห้องสวีทหรูหราสุดพรีเมียมบนชั้นสูงสุด พร้อมวิวพาโนราม่า 360 องศา', features: ['เตียงคิงไซซ์ 2 ห้อง', 'ห้องรับแขก', 'ครัวเต็ม', 'ดาดฟ้าส่วนตัว', 'จากุซซี่กลางแจ้ง'], images: [ 'https://images.unsplash.com/photo-1542314831-068cd1dbfeeb?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1445019980597-93fa8acb246c?ixlib=rb-4.0.3', 'https://images.unsplash.com/photo-1520637836862-4d197d17c886?ixlib=rb-4.0.3' ], amenities: ['WiFi ฟรี', 'เครื่องปรับอากาศ', 'ทีวี 65 นิ้ว 2 เครื่อง', 'ตู้เซฟ', 'บาร์เต็ม', 'เครื่องซักผ้า'] } ]; for (const room of rooms) { await this.db.addData('rooms', room); } } // Seed gallery data const existingGallery = await this.db.getData('gallery'); if (existingGallery.length === 0) { const galleryItems = [ { id: 1, title: 'ห้องสวีทเอ็กเซกคิวทีฟ', category: 'rooms', image: 'https://images.unsplash.com/photo-1586023492125-27b2c045efd7?ixlib=rb-4.0.3' }, { id: 2, title: 'สระว่ายน้ำบนดาดฟ้า', category: 'facilities', image: 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80' }, { id: 3, title: 'ห้องอาหารหรู', category: 'restaurant', image: 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80' }, { id: 4, title: 'สปาและเวลเนส', category: 'spa', image: 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80' }, { id: 5, title: 'ล็อบบี้โรงแรม', category: 'lobby', image: 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80' }, { id: 6, title: 'ห้องออกกำลังกาย', category: 'fitness', image: 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80' }, { id: 7, title: 'วิวทะเลจากห้องพัก', category: 'view', image: 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80' }, { id: 8, title: 'บาร์และเลานจ์', category: 'bar', image: 'https://images.unsplash.com/photo-1514933651103-005eec06c04b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1000&q=80' } ]; for (const item of galleryItems) { await this.db.addData('gallery', item); } } } setupEventListeners() { // Navigation document.getElementById('mobileMenu').addEventListener('click', this.toggleMobileMenu); document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const page = link.dataset.page; this.showPage(page); }); }); // Search form document.getElementById('searchForm').addEventListener('submit', (e) => { e.preventDefault(); this.searchRooms(); }); // Modal event listeners document.querySelectorAll('.close').forEach(closeBtn => { // bind to ensure `this` refers to the HotelBookingApp instance when handler runs closeBtn.addEventListener('click', this.closeModal.bind(this)); }); window.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { this.closeModal(); } }); // Admin navigation document.addEventListener('click', (e) => { if (e.target.classList.contains('admin-link')) { e.preventDefault(); document.querySelectorAll('.admin-link').forEach(link => { link.classList.remove('active'); }); e.target.classList.add('active'); this.loadAdminSection(e.target.dataset.section); } }); } setupSEO() { // Update page title and meta description based on current page const observer = new MutationObserver(() => { const activePage = document.querySelector('.page.active'); if (activePage) { this.updateSEOTags(activePage.id); } }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); } updateSEOTags(pageId) { const seoData = { home: { title: 'Luxury Hotel - จองห้องพักออนไลน์', description: 'จองห้องพักออนไลน์ที่โรงแรมหรู พร้อมสิ่งอำนวยความสะดวกครบครัน ราคาพิเศษ' }, rooms: { title: 'ห้องพัก - Luxury Hotel', description: 'เลือกห้องพักจากหลากหลายประเภท สวีท ดีลักซ์ แฟมิลี่ ราคาเริ่มต้น 2,800 บาท' }, gallery: { title: 'แกลเลอรี่ - Luxury Hotel', description: 'ชมภาพบรรยากาศโรงแรม ห้องพัก สระว่ายน้ำ สปา และสิ่งอำนวยความสะดวกต่างๆ' }, bookings: { title: 'การจองของฉัน - Luxury Hotel', description: 'ตรวจสอบสถานะการจอง แก้ไขการจอง และจัดการการเดินทางของคุณ' }, admin: { title: 'จัดการระบบ - Luxury Hotel', description: 'ระบบจัดการโรงแรม การจอง ห้องพัก และรายงานต่างๆ' } }; if (seoData[pageId]) { document.title = seoData[pageId].title; document.querySelector('meta[name="description"]').setAttribute('content', seoData[pageId].description); } } setupAnimations() { const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }, observerOptions); document.querySelectorAll('.fade-in').forEach(el => { observer.observe(el); }); } setDefaultDates() { const today = new Date(); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); document.getElementById('checkin').value = today.toISOString().split('T')[0]; document.getElementById('checkout').value = tomorrow.toISOString().split('T')[0]; } toggleMobileMenu() { document.getElementById('navMenu').classList.toggle('active'); } showPage(pageId) { console.log('showPage called:', pageId); // Update navigation document.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('active'); }); const targetNavLink = document.querySelector(`.nav-link[data-page="${pageId}"]`); if (targetNavLink) { targetNavLink.classList.add('active'); } else { console.warn('Nav link for page not found:', pageId); } // Update pages document.querySelectorAll('.page').forEach(page => { page.classList.remove('active'); }); const targetPage = document.getElementById(pageId); if (targetPage) { targetPage.classList.add('active'); } else { console.error('Page element not found for id:', pageId); return; } // Load page specific content switch (pageId) { case 'rooms': this.loadRooms(); break; case 'gallery': this.loadGallery(); break; case 'bookings': this.loadBookings(); break; case 'admin': this.loadAdminSection('bookings'); break; } // Stop main gallery autoplay when leaving gallery page if (pageId !== 'gallery') { this.stopMainGalleryAutoplay(); } // Close mobile menu document.getElementById('navMenu').classList.remove('active'); // Update SEO this.updateSEOTags(pageId); // Scroll to top window.scrollTo(0, 0); } async loadFeaturedRooms() { try { const rooms = await this.db.getData('rooms'); const featuredRooms = rooms.slice(0, 3); // Show first 3 rooms as featured const container = document.getElementById('featuredRooms'); container.innerHTML = featuredRooms.map(room => this.createRoomCard(room)).join(''); // Add fade-in animation to cards setTimeout(() => { container.querySelectorAll('.room-card').forEach((card, index) => { card.classList.add('fade-in'); setTimeout(() => { card.classList.add('visible'); }, index * 200); }); }, 100); } catch (error) { console.error('Error loading featured rooms:', error); this.showNotification('เกิดข้อผิดพลาดในการโหลดข้อมูลห้องพัก', 'error'); } } async loadGallery() { try { let galleryItems = await this.db.getData('gallery'); // If DB was cleared, seed defaults and re-fetch so gallery shows immediately if (!galleryItems || galleryItems.length === 0) { console.log('loadGallery: no items found; seeding default gallery data'); await this.seedData(); galleryItems = await this.db.getData('gallery'); } console.log('loadGallery: got', galleryItems ? galleryItems.length : 0, 'items'); const container = document.getElementById('galleryContainer'); if (!container) { console.error('Gallery container #galleryContainer not found in DOM'); return; } container.innerHTML = '
'; setTimeout(() => { if (!galleryItems || galleryItems.length === 0) { container.innerHTML = `
No gallery items
`; return; } this.lastGalleryItems = galleryItems; // Create main slideshow this.createMainGallerySlideshow(galleryItems); // Create thumbnail grid container.innerHTML = galleryItems.map((item, i) => ` `).join(''); // add visible class staggered container.querySelectorAll('.gallery-item').forEach((item, index) => { setTimeout(() => item.classList.add('visible'), index * 80); }); }, 400); } catch (error) { console.error('Error loading gallery:', error); this.showNotification('เกิดข้อผิดพลาดในการโหลดแกลเลอรี่', 'error'); } } createMainGallerySlideshow(items) { if (!items || items.length === 0) return; const slidesContainer = document.getElementById('gallerySlides'); const dotsContainer = document.getElementById('galleryDots'); const titleElement = document.getElementById('currentImageTitle'); const categoryElement = document.getElementById('currentImageCategory'); if (!slidesContainer || !dotsContainer) return; this.currentSlideIndex = 0; // Create slides slidesContainer.innerHTML = items.map((item, i) => ` `).join(''); // Create dots dotsContainer.innerHTML = items.map((_, i) => ` `).join(''); // Update info this.updateGalleryInfo(items[0]); // Wire controls const prevBtn = document.getElementById('galleryPrev'); const nextBtn = document.getElementById('galleryNext'); const dots = dotsContainer.querySelectorAll('.gallery-dot'); prevBtn.addEventListener('click', () => this.showMainSlide(this.currentSlideIndex - 1)); nextBtn.addEventListener('click', () => this.showMainSlide(this.currentSlideIndex + 1)); dots.forEach(dot => { dot.addEventListener('click', () => this.showMainSlide(parseInt(dot.dataset.index))); }); // Add hover pause/resume for main slideshow const mainSlideshow = document.getElementById('mainGallerySlideshow'); if (mainSlideshow) { mainSlideshow.addEventListener('mouseenter', () => this.stopMainGalleryAutoplay()); mainSlideshow.addEventListener('mouseleave', () => this.startMainGalleryAutoplay()); } // Add keyboard navigation for main gallery document.addEventListener('keydown', (e) => { const galleryPage = document.getElementById('gallery'); if (galleryPage && galleryPage.classList.contains('active')) { if (e.key === 'ArrowLeft') { e.preventDefault(); this.showMainSlide(this.currentSlideIndex - 1); } else if (e.key === 'ArrowRight') { e.preventDefault(); this.showMainSlide(this.currentSlideIndex + 1); } } }); // Auto-play this.startMainGalleryAutoplay(); } showMainSlide(index) { if (!this.lastGalleryItems || this.lastGalleryItems.length === 0) return; const items = this.lastGalleryItems; const totalSlides = items.length; // Normalize index if (index < 0) index = totalSlides - 1; if (index >= totalSlides) index = 0; this.currentSlideIndex = index; // Update slides const slides = document.querySelectorAll('.gallery-slide'); const dots = document.querySelectorAll('.gallery-dot'); slides.forEach((slide, i) => { slide.classList.remove('active', 'prev', 'next'); if (i === index) slide.classList.add('active'); else if (i === index - 1 || (index === 0 && i === totalSlides - 1)) slide.classList.add('prev'); else if (i === index + 1 || (index === totalSlides - 1 && i === 0)) slide.classList.add('next'); }); dots.forEach((dot, i) => { dot.classList.toggle('active', i === index); }); // Update info this.updateGalleryInfo(items[index]); // Restart autoplay this.startMainGalleryAutoplay(); } updateGalleryInfo(item) { const titleElement = document.getElementById('currentImageTitle'); const categoryElement = document.getElementById('currentImageCategory'); if (titleElement) titleElement.textContent = item.title; if (categoryElement) categoryElement.textContent = `หมวดหมู่: ${this.getCategoryName(item.category)}`; } getCategoryName(category) { const categoryNames = { 'rooms': 'ห้องพัก', 'facilities': 'สิ่งอำนวยความสะดวก', 'restaurant': 'ห้องอาหาร', 'spa': 'สปา', 'lobby': 'ล็อบบี้', 'fitness': 'ฟิตเนส', 'view': 'วิวทิวทัศน์', 'bar': 'บาร์' }; return categoryNames[category] || category; } startMainGalleryAutoplay() { // Clear existing interval if (this.mainGalleryInterval) { clearInterval(this.mainGalleryInterval); } // Start new interval this.mainGalleryInterval = setInterval(() => { this.showMainSlide(this.currentSlideIndex + 1); }, 4000); } stopMainGalleryAutoplay() { if (this.mainGalleryInterval) { clearInterval(this.mainGalleryInterval); this.mainGalleryInterval = null; } } openGallerySlideshow(startIndex = 0) { const items = this.lastGalleryItems || []; if (!items.length) return; const modal = document.getElementById('galleryModal'); const content = document.getElementById('galleryModalContent'); content.innerHTML = `
${items.map((it, i) => `
${it.title}
${it.title}
`).join('')}
${items.map((_, i) => ``).join('')}
`; // show modal as fullscreen gallery modal.classList.add('gallery-open'); modal.style.display = 'block'; // wire modal controls const slides = Array.from(content.querySelectorAll('.slide')); const dots = Array.from(content.querySelectorAll('.sl-dot')); const prevBtn = content.querySelector('.sl-prev'); const nextBtn = content.querySelector('.sl-next'); let idx = startIndex; const show = (i) => { i = (i + slides.length) % slides.length; slides.forEach((s, j) => s.classList.toggle('active', j === i)); dots.forEach((d, j) => d.classList.toggle('active', j === i)); idx = i; }; const next = () => show(idx + 1); const prev = () => show(idx - 1); nextBtn.addEventListener('click', next); prevBtn.addEventListener('click', prev); dots.forEach(d => d.addEventListener('click', () => show(parseInt(d.dataset.index)))); // autoplay in modal if (this.modalGalleryInterval) clearInterval(this.modalGalleryInterval); this.modalGalleryInterval = setInterval(() => next(), 3500); // pause on hover const slideshowEl = content.querySelector('.slideshow'); const onMouseEnter = () => {if (this.modalGalleryInterval) {clearInterval(this.modalGalleryInterval); this.modalGalleryInterval = null;} }; const onMouseLeave = () => {if (!this.modalGalleryInterval) this.modalGalleryInterval = setInterval(() => next(), 3500);}; slideshowEl.addEventListener('mouseenter', onMouseEnter); slideshowEl.addEventListener('mouseleave', onMouseLeave); // keyboard navigation const onKeyDown = (ev) => { if (!modal.classList.contains('gallery-open')) return; if (ev.key === 'ArrowRight') next(); if (ev.key === 'ArrowLeft') prev(); if (ev.key === 'Escape') this.closeModal(); }; document.addEventListener('keydown', onKeyDown); // store handlers so closeModal can remove them this._galleryHandlers = {onMouseEnter, onMouseLeave, onKeyDown, prevEl: prevBtn, nextEl: nextBtn, dotEls: dots}; } async loadRooms() { try { const rooms = await this.db.getData('rooms'); const container = document.getElementById('roomsContainer'); container.innerHTML = '
'; setTimeout(() => { container.innerHTML = rooms.map(room => this.createRoomCard(room)).join(''); // Add animations container.querySelectorAll('.room-card').forEach((card, index) => { card.classList.add('fade-in'); setTimeout(() => { card.classList.add('visible'); }, index * 100); }); }, 500); } catch (error) { console.error('Error loading rooms:', error); this.showNotification('เกิดข้อผิดพลาดในการโหลดข้อมูลห้องพัก', 'error'); } } createRoomCard(room) { const formatter = new Intl.NumberFormat('th-TH'); return `
฿${formatter.format(room.price)}/คืน

${room.name}

${room.description}

สูงสุด ${room.maxGuests} คน
${room.size} ตร.ม.
WiFi ฟรี
`; } async showRoomDetails(roomId) { try { const room = await this.db.getData('rooms', roomId); const modal = document.getElementById('roomModal'); const title = document.getElementById('roomModalTitle'); const content = document.getElementById('roomModalContent'); title.textContent = room.name; const formatter = new Intl.NumberFormat('th-TH'); content.innerHTML = `
${room.name}
${room.images.slice(1).map(img => `${room.name}` ).join('')}
ราคา: ฿${formatter.format(room.price)}/คืน
ขนาดห้อง: ${room.size} ตร.ม.
จำนวนผู้เข้าพัก: สูงสุด ${room.maxGuests} คน

คำอธิบาย

${room.description}

สิ่งอำนวยความสะดวก

${room.amenities.map(amenity => `
${amenity}
` ).join('')}

คุณสมบัติพิเศษ

${room.features.map(feature => `
${feature}
` ).join('')}
`; modal.style.display = 'block'; } catch (error) { console.error('Error showing room details:', error); this.showNotification('เกิดข้อผิดพลาดในการแสดงรายละเอียดห้องพัก', 'error'); } } async bookRoom(roomId) { try { const room = await this.db.getData('rooms', roomId); const modal = document.getElementById('bookingModal'); const content = document.getElementById('bookingContent'); this.currentRoom = room; const formatter = new Intl.NumberFormat('th-TH'); content.innerHTML = `

รายละเอียดการจอง

ข้อมูลผู้จอง

สรุปการจอง

${room.name}
${room.name}
ราคาต่อคืน: ฿${formatter.format(room.price)}
จำนวนคืน: 0
รวมราคาห้อง: ฿0
ภาษีและค่าบริการ (7%): ฿0
ราคารวมทั้งสิ้น: ฿0

การชำระเงิน

`; // Set default dates const today = new Date(); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); document.getElementById('bookingCheckin').value = today.toISOString().split('T')[0]; document.getElementById('bookingCheckout').value = tomorrow.toISOString().split('T')[0]; // Add event listeners document.getElementById('bookingCheckin').addEventListener('change', this.calculateTotal.bind(this)); document.getElementById('bookingCheckout').addEventListener('change', this.calculateTotal.bind(this)); document.getElementById('paymentMethod').addEventListener('change', this.togglePaymentSection); document.getElementById('bookingForm').addEventListener('submit', this.submitBooking.bind(this)); // Calculate initial total setTimeout(() => this.calculateTotal(), 100); modal.style.display = 'block'; } catch (error) { console.error('Error booking room:', error); this.showNotification('เกิดข้อผิดพลาดในการจองห้องพัก', 'error'); } } calculateTotal() { const checkinDate = new Date(document.getElementById('bookingCheckin').value); const checkoutDate = new Date(document.getElementById('bookingCheckout').value); if (checkinDate && checkoutDate && checkoutDate > checkinDate) { const timeDiff = checkoutDate.getTime() - checkinDate.getTime(); const nights = Math.ceil(timeDiff / (1000 * 3600 * 24)); const roomPrice = this.currentRoom.price; const roomTotal = roomPrice * nights; const taxAmount = Math.round(roomTotal * 0.07); const grandTotal = roomTotal + taxAmount; const formatter = new Intl.NumberFormat('th-TH'); document.getElementById('nightCount').textContent = `${nights} คืน`; document.getElementById('roomTotal').textContent = `฿${formatter.format(roomTotal)}`; document.getElementById('taxAmount').textContent = `฿${formatter.format(taxAmount)}`; document.getElementById('grandTotal').textContent = `฿${formatter.format(grandTotal)}`; } } togglePaymentSection() { const paymentMethod = document.getElementById('paymentMethod').value; const creditCardSection = document.getElementById('creditCardSection'); if (paymentMethod === 'credit') { creditCardSection.style.display = 'block'; } else { creditCardSection.style.display = 'none'; } } async submitBooking(e) { e.preventDefault(); try { const formData = new FormData(e.target); const checkinDate = new Date(document.getElementById('bookingCheckin').value); const checkoutDate = new Date(document.getElementById('bookingCheckout').value); const nights = Math.ceil((checkoutDate.getTime() - checkinDate.getTime()) / (1000 * 3600 * 24)); const roomTotal = this.currentRoom.price * nights; const taxAmount = Math.round(roomTotal * 0.07); const grandTotal = roomTotal + taxAmount; const booking = { roomId: this.currentRoom.id, roomName: this.currentRoom.name, checkIn: document.getElementById('bookingCheckin').value, checkOut: document.getElementById('bookingCheckout').value, nights: nights, guests: parseInt(document.getElementById('bookingGuests').value), guestName: document.getElementById('guestName').value, guestEmail: document.getElementById('guestEmail').value, guestPhone: document.getElementById('guestPhone').value, specialRequests: document.getElementById('specialRequests').value, paymentMethod: document.getElementById('paymentMethod').value, roomPrice: this.currentRoom.price, roomTotal: roomTotal, taxAmount: taxAmount, grandTotal: grandTotal, status: 'confirmed', bookingDate: new Date().toISOString(), bookingId: this.generateBookingId() }; await this.db.addData('bookings', booking); this.closeModal(); this.showNotification('การจองสำเร็จ! หมายเลขการจอง: ' + booking.bookingId, 'success'); // Show booking confirmation setTimeout(() => { this.showBookingConfirmation(booking); }, 2000); } catch (error) { console.error('Error submitting booking:', error); this.showNotification('เกิดข้อผิดพลาดในการจอง กรุณาลองใหม่อีกครั้ง', 'error'); } } generateBookingId() { const prefix = 'LH'; const timestamp = Date.now().toString().slice(-8); const random = Math.random().toString(36).substr(2, 4).toUpperCase(); return `${prefix}${timestamp}${random}`; } showBookingConfirmation(booking) { const modal = document.getElementById('bookingModal'); const content = document.getElementById('bookingContent'); const formatter = new Intl.NumberFormat('th-TH'); const checkinDate = new Date(booking.checkIn); const checkoutDate = new Date(booking.checkOut); content.innerHTML = `

การจองสำเร็จ!

หมายเลขการจอง: ${booking.bookingId}
ห้องพัก: ${booking.roomName}
วันที่เข้าพัก: ${checkinDate.toLocaleDateString('th-TH')}
วันที่ออก: ${checkoutDate.toLocaleDateString('th-TH')}
จำนวนคืน: ${booking.nights} คืน
ผู้เข้าพัก: ${booking.guests} คน
ราคารวม: ฿${formatter.format(booking.grandTotal)}

เราได้ส่งอีเมลยืนยันการจองไปยัง ${booking.guestEmail} แล้ว
กรุณาเก็บหมายเลขการจองไว้สำหรับการเช็คอิน

`; modal.style.display = 'block'; } closeModal() { // Hide any open modal elements document.querySelectorAll('.modal').forEach(modal => { modal.style.display = 'none'; }); // Reset transient state this.currentRoom = null; this.currentBooking = null; // clear any gallery slideshow interval if (this.galleryInterval) { clearInterval(this.galleryInterval); this.galleryInterval = null; } // clear modal slideshow interval if present if (this.modalGalleryInterval) { clearInterval(this.modalGalleryInterval); this.modalGalleryInterval = null; } // remove gallery-open class and event listeners if they exist const galleryModal = document.getElementById('galleryModal'); if (galleryModal && galleryModal.classList.contains('gallery-open')) { galleryModal.classList.remove('gallery-open'); } if (this._galleryHandlers) { try { const content = document.getElementById('galleryModalContent'); const slideshowEl = content.querySelector('.slideshow'); if (slideshowEl) { slideshowEl.removeEventListener('mouseenter', this._galleryHandlers.onMouseEnter); slideshowEl.removeEventListener('mouseleave', this._galleryHandlers.onMouseLeave); } document.removeEventListener('keydown', this._galleryHandlers.onKeyDown); } catch (e) { // ignore } this._galleryHandlers = null; } } showGalleryModal(imageUrl, title) { const modal = document.getElementById('galleryModal'); const titleElement = document.getElementById('galleryModalTitle'); const content = document.getElementById('galleryModalContent'); titleElement.textContent = title; content.innerHTML = ` ${title} `; modal.style.display = 'block'; } async loadBookings() { try { const bookings = await this.db.getData('bookings'); const container = document.getElementById('bookingsContainer'); container.innerHTML = '
'; setTimeout(() => { if (bookings.length > 0) { const formatter = new Intl.NumberFormat('th-TH'); container.innerHTML = `
${bookings.map(booking => { const checkinDate = new Date(booking.checkIn); const checkoutDate = new Date(booking.checkOut); const statusClass = booking.status === 'confirmed' ? 'confirmed' : booking.status === 'pending' ? 'pending' : 'cancelled'; const statusText = booking.status === 'confirmed' ? 'ยืนยันแล้ว' : booking.status === 'pending' ? 'รอดำเนินการ' : 'ยกเลิก'; return `

การจอง #${booking.bookingId}

${statusText}
ห้องพัก: ${booking.roomName}
วันที่เข้าพัก: ${checkinDate.toLocaleDateString('th-TH')}
วันที่ออก: ${checkoutDate.toLocaleDateString('th-TH')}
จำนวนคืน: ${booking.nights} คืน
ผู้เข้าพัก: ${booking.guests} คน
ชื่อผู้จอง: ${booking.guestName}
วันที่จอง: ${new Date(booking.bookingDate).toLocaleDateString('th-TH')}
ราคารวม: ฿${formatter.format(booking.grandTotal)}
${booking.status === 'confirmed' ? `
` : ''}
`; }).join('')}
`; // Add animations container.querySelectorAll('.booking-summary').forEach((card, index) => { setTimeout(() => { card.classList.add('visible'); }, index * 200); }); } else { container.innerHTML = `

ยังไม่มีการจอง

เริ่มต้นการเดินทางของคุณกับเรา

`; } }, 500); } catch (error) { console.error('Error loading bookings:', error); this.showNotification('เกิดข้อผิดพลาดในการโหลดข้อมูลการจอง', 'error'); } } async cancelBooking(bookingId) { if (confirm('คุณต้องการยกเลิกการจองนี้หรือไม่?')) { try { const booking = await this.db.getData('bookings', bookingId); booking.status = 'cancelled'; await this.db.updateData('bookings', booking); this.showNotification('ยกเลิกการจองเรียบร้อยแล้ว', 'success'); this.loadBookings(); // Reload bookings } catch (error) { console.error('Error cancelling booking:', error); this.showNotification('เกิดข้อผิดพลาดในการยกเลิกการจอง', 'error'); } } } async loadAdminSection(section) { const content = document.getElementById('adminContent'); content.innerHTML = '
'; try { switch (section) { case 'bookings': await this.loadAdminBookings(content); break; case 'rooms': await this.loadAdminRooms(content); break; case 'reports': await this.loadAdminReports(content); break; } } catch (error) { console.error('Error loading admin section:', error); this.showNotification('เกิดข้อผิดพลาดในการโหลดข้อมูล', 'error'); } } async loadAdminBookings(container) { const bookings = await this.db.getData('bookings'); const formatter = new Intl.NumberFormat('th-TH'); setTimeout(() => { container.innerHTML = `

จัดการการจอง (${bookings.length} รายการ)

${bookings.map(booking => { const statusClass = booking.status === 'confirmed' ? 'confirmed' : booking.status === 'pending' ? 'pending' : 'cancelled'; const statusText = booking.status === 'confirmed' ? 'ยืนยันแล้ว' : booking.status === 'pending' ? 'รอดำเนินการ' : 'ยกเลิก'; return ` `; }).join('')}
หมายเลขการจอง ห้องพัก ผู้จอง เข้าพัก ออก ราคา สถานะ การจัดการ
${booking.bookingId} ${booking.roomName} ${booking.guestName}
${booking.guestEmail}
${new Date(booking.checkIn).toLocaleDateString('th-TH')} ${new Date(booking.checkOut).toLocaleDateString('th-TH')} ฿${formatter.format(booking.grandTotal)} ${statusText}
${booking.status !== 'cancelled' ? ` ` : ''}
`; }, 500); } } // Initialize app when DOM ready let app = null; document.addEventListener('DOMContentLoaded', () => { app = new HotelBookingApp(); });