// 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();