// IndexedDB helper functions class DBHelper { constructor() { this.dbName = 'ThaiDessertShopDB'; this.version = 1; this.db = null; } // Ensure DB is opened before using it async _ensureDB() { if (!this.db) { await this.init(); } } // Initialize the database async init() { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, this.version); request.onerror = (event) => { console.error('Database error:', event.target.error); reject(event.target.error); }; request.onsuccess = (event) => { this.db = event.target.result; console.log('Database opened successfully'); resolve(this.db); }; request.onupgradeneeded = (event) => { const db = event.target.result; // Create products store if (!db.objectStoreNames.contains('products')) { const productsStore = db.createObjectStore('products', {keyPath: 'id'}); productsStore.createIndex('category', 'category', {unique: false}); } // Create categories store if (!db.objectStoreNames.contains('categories')) { db.createObjectStore('categories', {keyPath: 'id'}); } // Create orders store if (!db.objectStoreNames.contains('orders')) { const ordersStore = db.createObjectStore('orders', {keyPath: 'id'}); ordersStore.createIndex('status', 'status', {unique: false}); ordersStore.createIndex('date', 'date', {unique: false}); } // Create cart store if (!db.objectStoreNames.contains('cart')) { db.createObjectStore('cart', {keyPath: 'id'}); } // Create settings store if (!db.objectStoreNames.contains('settings')) { db.createObjectStore('settings', {keyPath: 'key'}); } // Create sales store if (!db.objectStoreNames.contains('sales')) { const salesStore = db.createObjectStore('sales', {keyPath: 'id', autoIncrement: true}); salesStore.createIndex('date', 'date', {unique: false}); } }; }); } // Generic function to get all items from a store async getAll(storeName) { await this._ensureDB(); return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readonly'); const store = transaction.objectStore(storeName); const request = store.getAll(); request.onsuccess = () => { resolve(request.result); }; request.onerror = (event) => { reject(event.target.error); }; }); } // Generic function to get an item by ID from a store async getById(storeName, id) { await this._ensureDB(); return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readonly'); const store = transaction.objectStore(storeName); const request = store.get(id); request.onsuccess = () => { resolve(request.result); }; request.onerror = (event) => { reject(event.target.error); }; }); } // Generic function to add an item to a store async add(storeName, item) { await this._ensureDB(); return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const request = store.add(item); request.onsuccess = () => { resolve(request.result); }; request.onerror = async (event) => { const err = event.target.error; // If add failed because the object store's key path didn't yield a value (DataError), // try a put() as a fallback which will insert or update depending on the store. if (err && (err.name === 'DataError' || /key path/i.test(err.message || ''))) { try { const fallbackTx = this.db.transaction([storeName], 'readwrite'); const fallbackStore = fallbackTx.objectStore(storeName); const putReq = fallbackStore.put(item); putReq.onsuccess = () => resolve(putReq.result); putReq.onerror = (e) => reject(e.target.error || e); } catch (e) { reject(e); } return; } reject(err); }; }); } // Generic function to update an item in a store async update(storeName, item) { await this._ensureDB(); return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const request = store.put(item); request.onsuccess = () => { resolve(request.result); }; request.onerror = (event) => { reject(event.target.error); }; }); } // Generic function to delete an item from a store async delete(storeName, id) { await this._ensureDB(); return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const request = store.delete(id); request.onsuccess = () => { resolve(request.result); }; request.onerror = (event) => { reject(event.target.error); }; }); } // Generic function to clear all items from a store async clear(storeName) { await this._ensureDB(); return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const request = store.clear(); request.onsuccess = () => { resolve(request.result); }; request.onerror = (event) => { reject(event.target.error); }; }); } // Function to get items by index async getByIndex(storeName, indexName, value) { await this._ensureDB(); return new Promise((resolve, reject) => { const transaction = this.db.transaction([storeName], 'readonly'); const store = transaction.objectStore(storeName); const index = store.index(indexName); const request = index.getAll(value); request.onsuccess = () => { resolve(request.result); }; request.onerror = (event) => { reject(event.target.error); }; }); } // Load mock data from JSON file async loadMockData(url, storeName) { try { const response = await fetch(url); const data = await response.json(); // Clear existing data await this.clear(storeName); // Add new data for (const item of data) { await this.add(storeName, item); } return data; } catch (error) { console.error('Error loading mock data:', error); throw error; } } // Check if store has data async hasData(storeName) { const data = await this.getAll(storeName); return data.length > 0; } // Initialize with mock data if needed async initializeWithMockData() { try { // Check if products exist const hasProducts = await this.hasData('products'); if (!hasProducts) { await this.loadMockData('mock/products.json', 'products'); } // Check if categories exist const hasCategories = await this.hasData('categories'); if (!hasCategories) { // Try to load mock/categories.json if present try { await this.loadMockData('mock/categories.json', 'categories'); } catch (e) { console.warn('No mock categories available to load', e); } } // Check if orders exist const hasOrders = await this.hasData('orders'); if (!hasOrders) { await this.loadMockData('mock/orders.json', 'orders'); } // Initialize default settings if needed const hasSettings = await this.hasData('settings'); if (!hasSettings) { const defaultSettings = [ {key: 'shopName', value: 'ร้านขนมไทย'}, {key: 'shopAddress', value: '123 ถนนขนมไทย แขวงขนมหวาน เขตหวานกรอบ กรุงเทพฯ 10100'}, {key: 'shopPhone', value: '0868142004'}, {key: 'shopEmail', value: 'info@thaidessertshop.com'}, {key: 'shippingCost', value: 50}, {key: 'enablePromptpay', value: true}, {key: 'enableBankTransfer', value: true}, {key: 'enableCOD', value: true}, {key: 'enableHomeDelivery', value: true}, {key: 'enableStorePickup', value: true} ]; for (const setting of defaultSettings) { await this.add('settings', setting); } } return true; } catch (error) { console.error('Error initializing with mock data:', error); return false; } } } // Create a singleton instance const dbHelper = new DBHelper();