db.js

8.80 KB
14/10/2025 16:35
JS
db.js
// 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();