/** * ระบบจัดการการแจ้งเตือน * รับผิดชอบการสร้าง แสดงผล อัพเดท และลบการแจ้งเตือน */ const NotificationManager = { /** * อิลิเมนต์ที่ใช้เก็บการแจ้งเตือนทั้งหมด * @type {HTMLElement|null} */ container: null, /** * อาเรย์เก็บการแจ้งเตือนที่กำลังแสดงอยู่ * @type {Array<{id: string, element: HTMLElement, timeout: number}>} */ notifications: [], /** * จำนวนการแจ้งเตือนทั้งหมดที่ถูกสร้าง * @type {number} */ notificationCount: 0, /** * การแจ้งเตือนที่กำลังแสดงอยู่ในปัจจุบัน * @type {Object|null} */ currentNotification: null, /** * เริ่มต้นระบบแจ้งเตือน * - สร้างคอนเทนเนอร์ * - ลงทะเบียนเทมเพลต * - ตั้งค่าตัวรับฟังเหตุการณ์ */ init() { // สร้างคอนเทนเนอร์สำหรับแสดงการแจ้งเตือน this.container = document.createElement('div'); this.container.className = 'notification-container'; document.body.appendChild(this.container); // ลงทะเบียนเทมเพลต TemplateManager.registerTemplate('notification-template', `
`); this.setupEventListeners(); }, /** * ตั้งค่าตัวรับฟังเหตุการณ์สำหรับการโต้ตอบกับการแจ้งเตือน */ setupEventListeners() { this.container.addEventListener('click', (e) => { if (e.target.matches('.notification-close')) { const notification = e.target.closest('.notification'); if (notification) { this.remove(notification.dataset.notificationId); } } }); }, /** * แสดงการแจ้งเตือนตามตัวเลือกที่กำหนด * @param {Object} options - ตัวเลือกสำหรับการแจ้งเตือน * @param {string} [options.type='info'] - ประเภทการแจ้งเตือน ('success', 'error', 'warning', 'info', 'loading') * @param {string} [options.title=''] - หัวข้อการแจ้งเตือน * @param {string} [options.message=''] - ข้อความแจ้งเตือน * @param {number} [options.duration=3000] - ระยะเวลาแสดง (มิลลิวินาที) * @param {boolean} [options.closeable=true] - สามารถปิดด้วยตนเองได้หรือไม่ * @param {boolean} [options.progress=true] - แสดงแถบความคืบหน้าหรือไม่ * @param {boolean} [options.animate=true] - แสดงแอนิเมชันหรือไม่ * @returns {string|undefined} - ID ของการแจ้งเตือนหรือ undefined หากล้มเหลว */ show(options) { const id = Utils.generateId('notification_'); const defaults = { id, type: 'info', title: '', message: '', duration: CONFIG.NOTIFICATION_DURATION || 3000, closeable: true, progress: true, animate: true }; const notification = {...defaults, ...options}; // ตรวจสอบการแจ้งเตือนซ้ำ const isSameNotification = this.currentNotification && this.currentNotification.type === notification.type && this.currentNotification.message === notification.message && this.currentNotification.title === notification.title; if (isSameNotification) { return; } this.currentNotification = notification; // สร้างอิลิเมนต์การแจ้งเตือน const element = TemplateManager.create('notification-template', { title: notification.title, message: notification.message, icon: 'icon-' + this.getIconForType(notification.type) }); const notificationEl = element.querySelector('.notification'); notificationEl.dataset.notificationId = id; notificationEl.classList.add(`notification-${notification.type}`); // จัดการปุ่มปิด if (!notification.closeable) { const closeButton = notificationEl.querySelector('.notification-close'); if (closeButton) { closeButton.remove(); } } // แสดงแถบความคืบหน้า if (notification.progress && notification.duration) { this.startProgress(notificationEl, notification.duration); } this.container.appendChild(element); // แสดงแอนิเมชัน if (notification.animate) { requestAnimationFrame(() => { notificationEl.classList.add('notification-show'); }); } // ตั้งเวลาลบอัตโนมัติ let timeoutId; if (notification.duration && notification.duration > 0) { timeoutId = setTimeout(() => { this.remove(id); }, notification.duration); } // เพิ่มเข้าอาเรย์ติดตาม this.notifications.push({ id, element: notificationEl, timeout: timeoutId }); // จำกัดจำนวนการแจ้งเตือนสูงสุด while (this.notifications.length > 5) { this.remove(this.notifications[0].id); } return id; }, /** * ลบการแจ้งเตือนตาม ID * @param {string} id - ID ของการแจ้งเตือนที่ต้องการลบ */ remove(id) { const notification = this.notifications.find(n => n.id === id); if (!notification) return; // ล้างตัวจับเวลา if (notification.timeout) { clearTimeout(notification.timeout); } // แสดงแอนิเมชันการลบ notification.element.classList.remove('notification-show'); notification.element.classList.add('notification-hide'); setTimeout(() => { notification.element.remove(); this.notifications = this.notifications.filter(n => n.id !== id); this.currentNotification = null; }, CONFIG.ANIMATION_DURATION || 300); }, /** * แสดงการแจ้งเตือนสำเร็จ * @param {string} message - ข้อความแจ้งเตือน * @param {Object} [options={}] - ตัวเลือกเพิ่มเติม */ success(message, options = {}) { return this.show({ type: 'success', message, icon: 'valid', ...options }); }, /** * แสดงการแจ้งเตือนข้อผิดพลาด * @param {string} message - ข้อความแจ้งเตือน * @param {Object} [options={}] - ตัวเลือกเพิ่มเติม */ error(message, options = {}) { return this.show({ type: 'error', message, icon: 'ban', duration: 0, ...options }); }, /** * แสดงการแจ้งเตือนคำเตือน * @param {string} message - ข้อความแจ้งเตือน * @param {Object} [options={}] - ตัวเลือกเพิ่มเติม */ warning(message, options = {}) { return this.show({ type: 'warning', message, icon: 'warning', ...options }); }, /** * แสดงการแจ้งเตือนข้อมูล * @param {string} message - ข้อความแจ้งเตือน * @param {Object} [options={}] - ตัวเลือกเพิ่มเติม */ info(message, options = {}) { return this.show({ type: 'info', message, icon: 'info', ...options }); }, /** * แสดงการแจ้งเตือนกำลังโหลด * @param {string} message - ข้อความแจ้งเตือน * @param {Object} [options={}] - ตัวเลือกเพิ่มเติม */ loading(message, options = {}) { return this.show({ type: 'loading', message, icon: 'loading', closeable: false, progress: false, duration: 0, ...options }); }, /** * อัพเดทการแจ้งเตือนกำลังโหลดด้วยข้อความและประเภทใหม่ * @param {string} id - ID ของการแจ้งเตือนที่ต้องการอัพเดท * @param {string} message - ข้อความใหม่ * @param {string} [type='success'] - ประเภทใหม่ */ updateLoading(id, message, type = 'success') { const notification = this.notifications.find(n => n.id === id); if (!notification) return; const element = notification.element; element.classList.remove('notification-loading'); element.classList.add(`notification-${type}`); const messageEl = element.querySelector('.notification-message'); if (messageEl) { messageEl.textContent = message; } const newTimeout = setTimeout(() => { this.remove(id); }, CONFIG.NOTIFICATION_DURATION || 3000); notification.timeout = newTimeout; }, /** * เริ่มแอนิเมชันแถบความคืบหน้า * @param {HTMLElement} element - อิลิเมนต์การแจ้งเตือน * @param {number} duration - ระยะเวลาแสดง (มิลลิวินาที) */ startProgress(element, duration) { const progress = element.querySelector('.notification-progress'); if (!progress) return; progress.style.transition = `width ${duration}ms linear`; requestAnimationFrame(() => { progress.style.width = '0%'; }); }, /** * ดึงคลาสไอคอนตามประเภทการแจ้งเตือน * @param {string} type - ประเภทการแจ้งเตือน * @returns {string} - คลาสไอคอน */ getIconForType(type) { const icons = { success: 'valid', error: 'ban', warning: 'warning', info: 'info', loading: 'loading' }; return icons[type] || icons.info; }, /** * ล้างการแจ้งเตือนทั้งหมด */ clear() { this.notifications.forEach(notification => { this.remove(notification.id); }); }, /** * อัพเดทการแจ้งเตือนที่มีอยู่ด้วยตัวเลือกใหม่ * @param {string} id - ID ของการแจ้งเตือนที่ต้องการอัพเดท * @param {Object} options - ตัวเลือกใหม่ */ update(id, options) { const notification = this.notifications.find(n => n.id === id); if (!notification) return; const element = notification.element; if (options.message) { const messageEl = element.querySelector('.notification-message'); if (messageEl) { messageEl.textContent = options.message; } } if (options.title) { const titleEl = element.querySelector('.notification-title'); if (titleEl) { titleEl.textContent = options.title; } } if (options.type) { const oldTypeMatch = element.className.match(/notification-(\w+)/); const oldType = oldTypeMatch ? oldTypeMatch[1] : null; if (oldType) { element.classList.remove(`notification-${oldType}`); } element.classList.add(`notification-${options.type}`); } } };