// 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 = '<div class="loading"><div class="spinner"></div></div>';
setTimeout(() => {
if (!galleryItems || galleryItems.length === 0) {
container.innerHTML = `<div style="text-align:center; padding:2rem; color:var(--muted);">No gallery items</div>`;
return;
}
this.lastGalleryItems = galleryItems;
// Create main slideshow
this.createMainGallerySlideshow(galleryItems);
// Create thumbnail grid
container.innerHTML = galleryItems.map((item, i) => `
<div class="gallery-item fade-in" data-index="${i}" onclick="app.showMainSlide(${i})">
<img src="${item.image}" alt="${item.title}">
<div class="gallery-overlay">
<div class="gallery-title">${item.title}</div>
</div>
</div>
`).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) => `
<div class="gallery-slide ${i === 0 ? 'active' : ''}" data-index="${i}">
<img src="${item.image}" alt="${item.title}">
<div class="gallery-slide-caption">
<h3>${item.title}</h3>
<p>หมวดหมู่: ${this.getCategoryName(item.category)}</p>
</div>
</div>
`).join('');
// Create dots
dotsContainer.innerHTML = items.map((_, i) => `
<button class="gallery-dot ${i === 0 ? 'active' : ''}" data-index="${i}"></button>
`).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 = `
<div class="slideshow">
<div class="slides">
${items.map((it, i) => `
<div class="slide ${i === startIndex ? 'active' : ''}" data-index="${i}">
<img src="${it.image}" alt="${it.title}">
<div class="slide-caption">${it.title}</div>
</div>
`).join('')}
</div>
<button class="sl-prev" aria-label="Previous">❮</button>
<button class="sl-next" aria-label="Next">❯</button>
<div class="sl-dots">
${items.map((_, i) => `<button class="sl-dot ${i === startIndex ? 'active' : ''}" data-index="${i}"></button>`).join('')}
</div>
</div>
`;
// 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 = '<div class="loading"><div class="spinner"></div></div>';
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 `
<div class="room-card">
<div class="room-image" style="background-image: url('${room.images[0]}')">
<div class="room-price">฿${formatter.format(room.price)}/คืน</div>
</div>
<div class="room-content">
<h3 class="room-title">${room.name}</h3>
<p>${room.description}</p>
<div class="room-features">
<div class="feature">
<i class="fas fa-users"></i>
<span>สูงสุด ${room.maxGuests} คน</span>
</div>
<div class="feature">
<i class="fas fa-expand-arrows-alt"></i>
<span>${room.size} ตร.ม.</span>
</div>
<div class="feature">
<i class="fas fa-wifi"></i>
<span>WiFi ฟรี</span>
</div>
</div>
<div style="display: flex; gap: 1rem; margin-top: 1rem;">
<button class="btn" style="flex: 1;" onclick="app.showRoomDetails(${room.id})">
<i class="fas fa-info-circle"></i> รายละเอียด
</button>
<button class="btn" style="flex: 1;" onclick="app.bookRoom(${room.id})">
<i class="fas fa-calendar-check"></i> จองเลย
</button>
</div>
</div>
</div>
`;
}
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 = `
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 2rem; margin-bottom: 2rem;">
<div>
<img src="${room.images[0]}" alt="${room.name}"
style="width: 100%; height: 300px; object-fit: cover; border-radius: 10px;">
<div style="display: flex; gap: 0.5rem; margin-top: 1rem;">
${room.images.slice(1).map(img =>
`<img src="${img}" alt="${room.name}"
style="width: 80px; height: 60px; object-fit: cover; border-radius: 5px; cursor: pointer;"
onclick="this.parentElement.previousElementSibling.src = this.src">`
).join('')}
</div>
</div>
<div>
<div class="booking-summary">
<div class="summary-item">
<span>ราคา:</span>
<span style="font-size: 1.5rem; color: var(--primary-color); font-weight: 700;">
฿${formatter.format(room.price)}/คืน
</span>
</div>
<div class="summary-item">
<span>ขนาดห้อง:</span>
<span>${room.size} ตร.ม.</span>
</div>
<div class="summary-item">
<span>จำนวนผู้เข้าพัก:</span>
<span>สูงสุด ${room.maxGuests} คน</span>
</div>
</div>
<button class="btn" style="width: 100%;" onclick="app.bookRoom(${room.id}); app.closeModal();">
<i class="fas fa-calendar-check"></i> จองห้องนี้
</button>
</div>
</div>
<div style="margin-bottom: 2rem;">
<h4 style="margin-bottom: 1rem; color: var(--secondary-color);">คำอธิบาย</h4>
<p style="line-height: 1.8;">${room.description}</p>
</div>
<div style="margin-bottom: 2rem;">
<h4 style="margin-bottom: 1rem; color: var(--secondary-color);">สิ่งอำนวยความสะดวก</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
${room.amenities.map(amenity =>
`<div class="feature">
<i class="fas fa-check" style="color: var(--success-color);"></i>
<span>${amenity}</span>
</div>`
).join('')}
</div>
</div>
<div>
<h4 style="margin-bottom: 1rem; color: var(--secondary-color);">คุณสมบัติพิเศษ</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
${room.features.map(feature =>
`<div class="feature">
<i class="fas fa-star" style="color: var(--primary-color);"></i>
<span>${feature}</span>
</div>`
).join('')}
</div>
</div>
`;
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 = `
<form id="bookingForm">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 2rem;">
<div>
<h4 style="margin-bottom: 1rem; color: var(--secondary-color);">รายละเอียดการจอง</h4>
<div class="form-group">
<label for="bookingCheckin">วันที่เข้าพัก</label>
<input type="date" id="bookingCheckin" class="form-control" required>
</div>
<div class="form-group">
<label for="bookingCheckout">วันที่ออก</label>
<input type="date" id="bookingCheckout" class="form-control" required>
</div>
<div class="form-group">
<label for="bookingGuests">จำนวนผู้เข้าพัก</label>
<select id="bookingGuests" class="form-control" required>
${Array.from({length: room.maxGuests}, (_, i) => i + 1)
.map(num => `<option value="${num}">${num} คน</option>`).join('')}
</select>
</div>
<h4 style="margin: 2rem 0 1rem; color: var(--secondary-color);">ข้อมูลผู้จอง</h4>
<div class="form-group">
<label for="guestName">ชื่อ-นามสกุล</label>
<input type="text" id="guestName" class="form-control" required>
</div>
<div class="form-group">
<label for="guestEmail">อีเมล</label>
<input type="email" id="guestEmail" class="form-control" required>
</div>
<div class="form-group">
<label for="guestPhone">เบอร์โทรศัพท์</label>
<input type="tel" id="guestPhone" class="form-control" required>
</div>
<div class="form-group">
<label for="specialRequests">ความต้องการพิเศษ</label>
<textarea id="specialRequests" class="form-control" rows="3" placeholder="เช่น เตียงเสริม, อาหารเช้า, etc."></textarea>
</div>
</div>
<div>
<h4 style="margin-bottom: 1rem; color: var(--secondary-color);">สรุปการจอง</h4>
<div class="booking-summary">
<div style="text-align: center; margin-bottom: 1rem;">
<img src="${room.images[0]}" alt="${room.name}"
style="width: 100%; height: 150px; object-fit: cover; border-radius: 8px;">
<h5 style="margin: 1rem 0; color: var(--secondary-color);">${room.name}</h5>
</div>
<div class="summary-item">
<span>ราคาต่อคืน:</span>
<span>฿${formatter.format(room.price)}</span>
</div>
<div class="summary-item">
<span>จำนวนคืน:</span>
<span id="nightCount">0</span>
</div>
<div class="summary-item">
<span>รวมราคาห้อง:</span>
<span id="roomTotal">฿0</span>
</div>
<div class="summary-item">
<span>ภาษีและค่าบริการ (7%):</span>
<span id="taxAmount">฿0</span>
</div>
<div class="summary-item total">
<span>ราคารวมทั้งสิ้น:</span>
<span id="grandTotal">฿0</span>
</div>
</div>
<h4 style="margin: 2rem 0 1rem; color: var(--secondary-color);">การชำระเงิน</h4>
<div class="form-group">
<label for="paymentMethod">วิธีการชำระเงิน</label>
<select id="paymentMethod" class="form-control" required>
<option value="">เลือกวิธีการชำระเงิน</option>
<option value="credit">บัตรเครดิต/เดบิต</option>
<option value="bank">โอนเงินผ่านธนาคาร</option>
<option value="promptpay">พร้อมเพย์</option>
<option value="cash">จ่ายที่โรงแรม</option>
</select>
</div>
<div id="creditCardSection" style="display: none;">
<div class="form-group">
<label for="cardNumber">หมายเลขบัตร</label>
<input type="text" id="cardNumber" class="form-control" placeholder="1234 5678 9012 3456">
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
<div class="form-group">
<label for="expiry">วันหมดอายุ</label>
<input type="text" id="expiry" class="form-control" placeholder="MM/YY">
</div>
<div class="form-group">
<label for="cvv">CVV</label>
<input type="text" id="cvv" class="form-control" placeholder="123">
</div>
</div>
<div class="form-group">
<label for="cardHolder">ชื่อผู้ถือบัตร</label>
<input type="text" id="cardHolder" class="form-control">
</div>
</div>
<button type="submit" class="btn" style="width: 100%; margin-top: 1rem;">
<i class="fas fa-credit-card"></i> ยืนยันการจอง
</button>
</div>
</div>
</form>
`;
// 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 = `
<div style="text-align: center;">
<div style="color: var(--success-color); font-size: 4rem; margin-bottom: 1rem;">
<i class="fas fa-check-circle"></i>
</div>
<h2 style="color: var(--success-color); margin-bottom: 2rem;">การจองสำเร็จ!</h2>
<div class="booking-summary" style="text-align: left;">
<div class="summary-item">
<span>หมายเลขการจอง:</span>
<span style="font-weight: 700; color: var(--primary-color);">${booking.bookingId}</span>
</div>
<div class="summary-item">
<span>ห้องพัก:</span>
<span>${booking.roomName}</span>
</div>
<div class="summary-item">
<span>วันที่เข้าพัก:</span>
<span>${checkinDate.toLocaleDateString('th-TH')}</span>
</div>
<div class="summary-item">
<span>วันที่ออก:</span>
<span>${checkoutDate.toLocaleDateString('th-TH')}</span>
</div>
<div class="summary-item">
<span>จำนวนคืน:</span>
<span>${booking.nights} คืน</span>
</div>
<div class="summary-item">
<span>ผู้เข้าพัก:</span>
<span>${booking.guests} คน</span>
</div>
<div class="summary-item total">
<span>ราคารวม:</span>
<span>฿${formatter.format(booking.grandTotal)}</span>
</div>
</div>
<p style="margin: 2rem 0; color: #666;">
เราได้ส่งอีเมลยืนยันการจองไปยัง ${booking.guestEmail} แล้ว<br>
กรุณาเก็บหมายเลขการจองไว้สำหรับการเช็คอิน
</p>
<div style="display: flex; gap: 1rem; justify-content: center;">
<button class="btn btn-outline" onclick="app.closeModal()">ปิด</button>
<button class="btn" onclick="app.showPage('bookings'); app.closeModal();">
ดูการจองของฉัน
</button>
</div>
</div>
`;
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 = `
<img src="${imageUrl}" alt="${title}"
style="max-width: 100%; max-height: 70vh; border-radius: 10px;">
`;
modal.style.display = 'block';
}
async loadBookings() {
try {
const bookings = await this.db.getData('bookings');
const container = document.getElementById('bookingsContainer');
container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
setTimeout(() => {
if (bookings.length > 0) {
const formatter = new Intl.NumberFormat('th-TH');
container.innerHTML = `
<div style="display: grid; gap: 2rem;">
${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 `
<div class="booking-summary fade-in" style="position: relative;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="color: var(--secondary-color);">
การจอง #${booking.bookingId}
</h3>
<span class="status ${statusClass}">${statusText}</span>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
<div>
<div class="summary-item">
<span>ห้องพัก:</span>
<span>${booking.roomName}</span>
</div>
<div class="summary-item">
<span>วันที่เข้าพัก:</span>
<span>${checkinDate.toLocaleDateString('th-TH')}</span>
</div>
<div class="summary-item">
<span>วันที่ออก:</span>
<span>${checkoutDate.toLocaleDateString('th-TH')}</span>
</div>
<div class="summary-item">
<span>จำนวนคืน:</span>
<span>${booking.nights} คืน</span>
</div>
</div>
<div>
<div class="summary-item">
<span>ผู้เข้าพัก:</span>
<span>${booking.guests} คน</span>
</div>
<div class="summary-item">
<span>ชื่อผู้จอง:</span>
<span>${booking.guestName}</span>
</div>
<div class="summary-item">
<span>วันที่จอง:</span>
<span>${new Date(booking.bookingDate).toLocaleDateString('th-TH')}</span>
</div>
<div class="summary-item total">
<span>ราคารวม:</span>
<span>฿${formatter.format(booking.grandTotal)}</span>
</div>
</div>
</div>
${booking.status === 'confirmed' ? `
<div style="margin-top: 1rem; display: flex; gap: 1rem;">
<button class="btn btn-outline" onclick="app.editBooking(${booking.id})" style="flex: 1;">
<i class="fas fa-edit"></i> แก้ไข
</button>
<button class="btn" onclick="app.cancelBooking(${booking.id})" style="flex: 1; background: var(--danger-color);">
<i class="fas fa-times"></i> ยกเลิก
</button>
</div>
` : ''}
</div>
`;
}).join('')}
</div>
`;
// Add animations
container.querySelectorAll('.booking-summary').forEach((card, index) => {
setTimeout(() => {
card.classList.add('visible');
}, index * 200);
});
} else {
container.innerHTML = `
<div style="text-align: center; padding: 3rem; color: #666;">
<i class="fas fa-calendar-times" style="font-size: 4rem; margin-bottom: 1rem; opacity: 0.5;"></i>
<h3>ยังไม่มีการจอง</h3>
<p>เริ่มต้นการเดินทางของคุณกับเรา</p>
<button class="btn" onclick="app.showPage('rooms')" style="margin-top: 1rem;">
<i class="fas fa-bed"></i> เลือกห้องพัก
</button>
</div>
`;
}
}, 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 = '<div class="loading"><div class="spinner"></div></div>';
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 = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<h2>จัดการการจอง (${bookings.length} รายการ)</h2>
<div style="display: flex; gap: 1rem;">
<button class="btn btn-outline" onclick="app.exportBookings()">
<i class="fas fa-download"></i> ส่งออก
</button>
</div>
</div>
<div style="overflow-x: auto;">
<table class="data-table">
<thead>
<tr>
<th>หมายเลขการจอง</th>
<th>ห้องพัก</th>
<th>ผู้จอง</th>
<th>เข้าพัก</th>
<th>ออก</th>
<th>ราคา</th>
<th>สถานะ</th>
<th>การจัดการ</th>
</tr>
</thead>
<tbody>
${bookings.map(booking => {
const statusClass = booking.status === 'confirmed' ? 'confirmed' :
booking.status === 'pending' ? 'pending' : 'cancelled';
const statusText = booking.status === 'confirmed' ? 'ยืนยันแล้ว' :
booking.status === 'pending' ? 'รอดำเนินการ' : 'ยกเลิก';
return `
<tr>
<td style="font-weight: 600;">${booking.bookingId}</td>
<td>${booking.roomName}</td>
<td>
${booking.guestName}<br>
<small style="color: #666;">${booking.guestEmail}</small>
</td>
<td>${new Date(booking.checkIn).toLocaleDateString('th-TH')}</td>
<td>${new Date(booking.checkOut).toLocaleDateString('th-TH')}</td>
<td>฿${formatter.format(booking.grandTotal)}</td>
<td><span class="status ${statusClass}">${statusText}</span></td>
<td>
<div style="display: flex; gap: 0.5rem;">
<button class="btn btn-outline" onclick="app.viewBookingDetails(${booking.id})"
style="padding: 0.3rem 0.8rem; font-size: 0.9rem;">
<i class="fas fa-eye"></i>
</button>
${booking.status !== 'cancelled' ? `
<button class="btn" onclick="app.updateBookingStatus(${booking.id})"
style="padding: 0.3rem 0.8rem; font-size: 0.9rem; background: var(--accent-color);">
<i class="fas fa-edit"></i>
</button>
` : ''}
</div>
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
`;
}, 500);
}
}
// Initialize app when DOM ready
let app = null;
document.addEventListener('DOMContentLoaded', () => {
app = new HotelBookingApp();
});