/** * @class CustomerStorage * @description จัดการการเก็บข้อมูลลูกค้า รวมถึงข้อมูลส่วนตัวและประวัติการสั่งซื้อ */ class CustomerStorage { /** * สร้างอินสแตนซ์ของ CustomerStorage * กำหนดค่าคีย์สำหรับจัดเก็บข้อมูลและภาษาเริ่มต้น จากนั้นเริ่มต้นระบบจัดเก็บ */ constructor() { this.STORAGE_KEYS = CONFIG.STORAGE_KEYS; this.DEFAULT_LOCALE = CONFIG.LOCALE.DEFAULT; this.init(); } /** * เริ่มต้นระบบจัดเก็บข้อมูลลูกค้าโดยโหลดข้อมูลที่มีอยู่ * หากเกิดข้อผิดพลาด จะรีเซ็ตข้อมูลเป็นค่าเริ่มต้น */ init() { try { this.loadCustomerData(); } catch (error) { console.error('เกิดข้อผิดพลาดในการเริ่มต้น CustomerStorage:', error); this.resetToDefaults(); } } /** * รีเซ็ตข้อมูลลูกค้าทั้งหมดเป็นค่าเริ่มต้น */ resetToDefaults() { this.customerInfo = this.getDefaultCustomerInfo(); this.addresses = []; this.orderHistory = []; } /** * สร้างโครงสร้างข้อมูลลูกค้าเริ่มต้น * @returns {Object} ออบเจ็กต์ข้อมูลลูกค้าเริ่มต้น */ getDefaultCustomerInfo() { return { name: '', phone: '', email: '', addressLine1: '', addressLine2: '', district: '', province: '', postalCode: '', deliveryNotes: '', preferences: { preferredPayment: Object.keys(CONFIG.PAYMENT.METHODS)[0], newsletter: false, language: this.DEFAULT_LOCALE, theme: CONFIG.THEME.DEFAULT }, verification: { isVerified: false, verifiedEmail: false, verifiedPhone: false, verificationDate: null, lastVerificationAttempt: null }, metadata: { createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), lastLogin: null, lastOrder: null } }; } /** * โหลดข้อมูลลูกค้าจาก localStorage * ผสานข้อมูลที่บันทึกไว้กับข้อมูลเริ่มต้น * ส่งอีเวนต์แจ้งการเปลี่ยนแปลงสถานะหลังจากโหลด * @throws จะส่งข้อผิดพลาดหากการโหลดข้อมูลล้มเหลว */ loadCustomerData() { try { const savedInfo = localStorage.getItem(this.STORAGE_KEYS.CUSTOMER); if (savedInfo) { this.customerInfo = { ...this.getDefaultCustomerInfo(), ...JSON.parse(savedInfo) }; } else { this.customerInfo = this.getDefaultCustomerInfo(); } const savedHistory = localStorage.getItem(this.STORAGE_KEYS.ORDERS); this.orderHistory = savedHistory ? JSON.parse(savedHistory) : []; EventBus.emit('state:changed', { property: 'customerInfo', value: this.customerInfo }); } catch (error) { console.error('เกิดข้อผิดพลาดในการโหลดข้อมูลลูกค้า:', error); throw new Error('ไม่สามารถโหลดข้อมูลลูกค้าได้'); } } /** * อัพเดทข้อมูลลูกค้าด้วยข้อมูลที่ระบุ * ตรวจสอบความถูกต้องของข้อมูลก่อนอัพเดท * บันทึกข้อมูลที่อัพเดทลง localStorage และส่งอีเวนต์แจ้งการเปลี่ยนแปลง * @param {Object} info - ข้อมูลลูกค้าที่จะอัพเดท * @returns {Promise} ออบเจ็กต์ที่มีสถานะความสำเร็จและข้อมูลที่อัพเดท * @throws จะส่งข้อผิดพลาดหากการตรวจสอบหรือบันทึกข้อมูลล้มเหลว */ async updateCustomerInfo(info) { try { const validationResult = this.validateCustomerInfo(info); if (!validationResult.isValid) { throw new Error(validationResult.message); } this.customerInfo = { ...this.customerInfo, ...info, metadata: { ...this.customerInfo.metadata, updatedAt: new Date().toISOString() } }; await this.saveCustomerInfo(); EventBus.emit('state:changed', { property: 'customerInfo', value: this.customerInfo }); return { success: true, data: this.customerInfo }; } catch (error) { console.error('เกิดข้อผิดพลาดในการอัพเดทข้อมูลลูกค้า:', error); throw error; } } /** * ตรวจสอบความถูกต้องของข้อมูลลูกค้า * ตรวจสอบฟิลด์ที่จำเป็นและรูปแบบของข้อมูล * @param {Object} info - ข้อมูลลูกค้าที่ต้องการตรวจสอบ * @returns {Object} ผลการตรวจสอบ ระบุว่าข้อมูลถูกต้องหรือไม่พร้อมข้อความ */ validateCustomerInfo(info) { if (!info) { return { isValid: false, field: null, message: 'ข้อมูลไม่ถูกต้อง' }; } const required = { name: 'ชื่อ-นามสกุล', phone: 'เบอร์โทรศัพท์', addressLine1: 'ที่อยู่', district: 'เขต/อำเภอ', province: 'จังหวัด', postalCode: 'รหัสไปรษณีย์' }; for (const [field, label] of Object.entries(required)) { if (info[field] !== undefined && !info[field]?.trim()) { return { isValid: false, field, message: `กรุณากรอก${label}` }; } } const validations = [ { field: 'name', pattern: /^[ก-์\sa-zA-Z]{2,100}$/, message: 'ชื่อต้องเป็นภาษาไทยหรืออังกฤษ และมีความยาว 2-100 ตัวอักษร' }, { field: 'phone', pattern: /^0[1-9][0-9]{8}$/, message: 'เบอร์โทรศัพท์ไม่ถูกต้อง ต้องขึ้นต้นด้วย 0 และมี 10 หลัก' }, { field: 'email', pattern: /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message: 'รูปแบบอีเมลไม่ถูกต้อง', optional: true }, { field: 'district', pattern: /^[ก-์\s]{2,50}$/, message: 'เขต/อำเภอต้องเป็นภาษาไทยและมีความยาว 2-50 ตัวอักษร' }, { field: 'province', pattern: /^[ก-์\s]{2,50}$/, message: 'จังหวัดต้องเป็นภาษาไทยและมีความยาว 2-50 ตัวอักษร' }, { field: 'postalCode', pattern: /^[0-9]{5}$/, message: 'รหัสไปรษณีย์ต้องเป็นตัวเลข 5 หลัก' } ]; for (const validation of validations) { if (info[validation.field] !== undefined) { const value = info[validation.field]?.trim(); if (!value && validation.optional) continue; if (!validation.pattern.test(value)) { return { isValid: false, field: validation.field, message: validation.message }; } } } if (info.addressLine1 !== undefined && info.addressLine1.length < 5) { return { isValid: false, field: 'addressLine1', message: 'ที่อยู่ต้องมีความยาวอย่างน้อย 5 ตัวอักษร' }; } if (info.deliveryNotes && info.deliveryNotes.length > 200) { return { isValid: false, field: 'deliveryNotes', message: 'หมายเหตุการจัดส่งต้องไม่เกิน 200 ตัวอักษร' }; } return { isValid: true, message: 'ข้อมูลถูกต้อง' }; } /** * บันทึกข้อมูลลูกค้าปัจจุบันลงใน localStorage * @returns {Promise} * @throws จะส่งข้อผิดพลาดหากการบันทึกล้มเหลว */ async saveCustomerInfo() { try { localStorage.setItem( this.STORAGE_KEYS.CUSTOMER, JSON.stringify(this.customerInfo) ); } catch (error) { console.error('เกิดข้อผิดพลาดในการบันทึกข้อมูลลูกค้า:', error); throw new Error('ไม่สามารถบันทึกข้อมูลลูกค้าได้'); } } /** * จัดรูปแบบที่อยู่หลักจากข้อมูลลูกค้า * @returns {string} ที่อยู่ที่จัดรูปแบบแล้ว */ formatMainAddress() { const info = this.customerInfo; return [ info.addressLine1, info.addressLine2, info.district, info.province, info.postalCode ].filter(Boolean).join(' '); } /** * จัดรูปแบบที่อยู่ที่ระบุให้เป็นข้อความเดียว * @param {Object} address - ออบเจ็กต์ที่อยู่ที่ต้องการจัดรูปแบบ * @returns {string} ที่อยู่ที่จัดรูปแบบแล้ว */ formatAddress(address) { return [ address.addressLine1, address.addressLine2, address.district, address.province, address.postalCode ].filter(Boolean).join(' '); } /** * เพิ่มออเดอร์ใหม่ลงในประวัติการสั่งซื้อ * อัพเดทข้อมูล metadata และบันทึกประวัติที่อัพเดทแล้วพร้อมข้อมูลลูกค้า * ส่งอีเวนต์เมื่อเพิ่มสำเร็จ * @param {Object} order - ออบเจ็กต์ออเดอร์ที่จะเพิ่ม * @returns {Promise} ออเดอร์ที่เพิ่มใหม่ * @throws จะส่งข้อผิดพลาดหากการเพิ่มออเดอร์ล้มเหลว */ async addToOrderHistory(order) { try { const newOrder = { ...order, status: CONFIG.ORDER_STATUS.PENDING, orderDate: new Date().toISOString(), statusHistory: { [CONFIG.ORDER_STATUS.PENDING]: new Date().toISOString() } }; this.orderHistory.unshift(newOrder); this.customerInfo.metadata.lastOrder = newOrder.orderDate; await Promise.all([ this.saveOrderHistory(), this.saveCustomerInfo() ]); EventBus.emit('order:created', newOrder); return newOrder; } catch (error) { console.error('เกิดข้อผิดพลาดในการเพิ่มออเดอร์:', error); throw new Error('ไม่สามารถเพิ่มออเดอร์ได้'); } } /** * ดึงประวัติการสั่งซื้อพร้อมตัวกรอง * @param {Object} [filters={}] - ตัวกรองที่ต้องการใช้ * @param {string} [filters.status] - กรองตามสถานะออเดอร์ * @param {string} [filters.dateFrom] - กรองออเดอร์ตั้งแต่วันที่ระบุ * @param {string} [filters.dateTo] - กรองออเดอร์จนถึงวันที่ระบุ * @returns {Array} รายการออเดอร์ที่ผ่านการกรอง */ getOrderHistory(filters = {}) { let filtered = [...this.orderHistory]; if (filters.status) { filtered = filtered.filter(order => order.status === filters.status); } if (filters.dateFrom) { filtered = filtered.filter(order => new Date(order.orderDate) >= new Date(filters.dateFrom) ); } if (filters.dateTo) { filtered = filtered.filter(order => new Date(order.orderDate) <= new Date(filters.dateTo) ); } return filtered; } /** * บันทึกประวัติการสั่งซื้อปัจจุบันลงใน localStorage * @returns {Promise} * @throws จะส่งข้อผิดพลาดหากการบันทึกล้มเหลว */ async saveOrderHistory() { try { localStorage.setItem( this.STORAGE_KEYS.ORDER_HISTORY, JSON.stringify(this.orderHistory) ); } catch (error) { console.error('เกิดข้อผิดพลาดในการบันทึกประวัติการสั่งซื้อ:', error); throw new Error('ไม่สามารถบันทึกประวัติการสั่งซื้อได้'); } } /** * ล้างข้อมูลลูกค้าทั้งหมดจากระบบจัดเก็บและรีเซ็ตเป็นค่าเริ่มต้น * ส่งอีเวนต์แจ้งการเปลี่ยนแปลงหลังจากล้างข้อมูล * @returns {Promise} * @throws จะส่งข้อผิดพลาดหากการล้างข้อมูลล้มเหลว */ async clearAllData() { try { Object.values(this.STORAGE_KEYS).forEach(key => { localStorage.removeItem(key); }); this.resetToDefaults(); EventBus.emit('state:changed', { property: 'customerInfo', value: this.customerInfo }); } catch (error) { console.error('เกิดข้อผิดพลาดในการล้างข้อมูลลูกค้า:', error); throw new Error('ไม่สามารถล้างข้อมูลได้'); } } }