|
|
|
|
|
class NotificationManager { |
|
|
constructor() { |
|
|
this.notifications = []; |
|
|
this.toastContainer = null; |
|
|
this.modalContainer = null; |
|
|
this.init(); |
|
|
} |
|
|
|
|
|
|
|
|
init() { |
|
|
this.createToastContainer(); |
|
|
this.createModalContainer(); |
|
|
this.loadStoredNotifications(); |
|
|
} |
|
|
|
|
|
|
|
|
createToastContainer() { |
|
|
this.toastContainer = document.createElement('div'); |
|
|
this.toastContainer.className = 'toast-container'; |
|
|
this.toastContainer.innerHTML = ''; |
|
|
document.body.appendChild(this.toastContainer); |
|
|
} |
|
|
|
|
|
|
|
|
createModalContainer() { |
|
|
this.modalContainer = document.createElement('div'); |
|
|
this.modalContainer.className = 'modal-container'; |
|
|
this.modalContainer.innerHTML = ` |
|
|
<div class="modal-overlay" onclick="this.parentElement.style.display='none'"></div> |
|
|
<div class="modal-content"> |
|
|
<div class="modal-header"> |
|
|
<h3 class="modal-title"></h3> |
|
|
<button class="modal-close" onclick="this.closest('.modal-container').style.display='none'"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
<div class="modal-body"></div> |
|
|
<div class="modal-footer"></div> |
|
|
</div> |
|
|
`; |
|
|
document.body.appendChild(this.modalContainer); |
|
|
} |
|
|
|
|
|
|
|
|
showToast(message, type = 'info', duration = 5000) { |
|
|
const toast = document.createElement('div'); |
|
|
toast.className = `toast toast-${type}`; |
|
|
|
|
|
const icon = this.getToastIcon(type); |
|
|
|
|
|
toast.innerHTML = ` |
|
|
<div class="toast-icon"> |
|
|
<i class="${icon}"></i> |
|
|
</div> |
|
|
<div class="toast-content"> |
|
|
<div class="toast-message">${message}</div> |
|
|
</div> |
|
|
<button class="toast-close" onclick="this.parentElement.remove()"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
`; |
|
|
|
|
|
|
|
|
this.toastContainer.appendChild(toast); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
toast.classList.add('toast-show'); |
|
|
}, 100); |
|
|
|
|
|
|
|
|
if (duration > 0) { |
|
|
setTimeout(() => { |
|
|
this.removeToast(toast); |
|
|
}, duration); |
|
|
} |
|
|
|
|
|
return toast; |
|
|
} |
|
|
|
|
|
|
|
|
removeToast(toast) { |
|
|
toast.classList.add('toast-hide'); |
|
|
setTimeout(() => { |
|
|
if (toast.parentElement) { |
|
|
toast.parentElement.removeChild(toast); |
|
|
} |
|
|
}, 300); |
|
|
} |
|
|
|
|
|
|
|
|
getToastIcon(type) { |
|
|
const icons = { |
|
|
success: 'fas fa-check-circle', |
|
|
error: 'fas fa-exclamation-circle', |
|
|
warning: 'fas fa-exclamation-triangle', |
|
|
info: 'fas fa-info-circle', |
|
|
loading: 'fas fa-spinner fa-spin' |
|
|
}; |
|
|
return icons[type] || icons.info; |
|
|
} |
|
|
|
|
|
|
|
|
showModal(title, content, buttons = []) { |
|
|
const modal = this.modalContainer; |
|
|
|
|
|
modal.querySelector('.modal-title').textContent = title; |
|
|
modal.querySelector('.modal-body').innerHTML = content; |
|
|
|
|
|
|
|
|
const footer = modal.querySelector('.modal-footer'); |
|
|
footer.innerHTML = ''; |
|
|
|
|
|
buttons.forEach(button => { |
|
|
const btn = document.createElement('button'); |
|
|
btn.className = `btn ${button.class || 'btn-primary'}`; |
|
|
btn.textContent = button.text; |
|
|
btn.onclick = () => { |
|
|
if (button.action) button.action(); |
|
|
modal.style.display = 'none'; |
|
|
}; |
|
|
footer.appendChild(btn); |
|
|
}); |
|
|
|
|
|
|
|
|
if (buttons.length === 0) { |
|
|
const closeBtn = document.createElement('button'); |
|
|
closeBtn.className = 'btn btn-secondary'; |
|
|
closeBtn.textContent = 'إغلاق'; |
|
|
closeBtn.onclick = () => modal.style.display = 'none'; |
|
|
footer.appendChild(closeBtn); |
|
|
} |
|
|
|
|
|
modal.style.display = 'flex'; |
|
|
return modal; |
|
|
} |
|
|
|
|
|
|
|
|
showConfirm(title, message, onConfirm, onCancel) { |
|
|
return this.showModal(title, `<p>${message}</p>`, [ |
|
|
{ |
|
|
text: 'تأكيد', |
|
|
class: 'btn-primary', |
|
|
action: onConfirm |
|
|
}, |
|
|
{ |
|
|
text: 'إلغاء', |
|
|
class: 'btn-secondary', |
|
|
action: onCancel |
|
|
} |
|
|
]); |
|
|
} |
|
|
|
|
|
|
|
|
showLoading(message = 'جاري التحميل...') { |
|
|
const loadingContent = ` |
|
|
<div class="loading-content"> |
|
|
<div class="loading-spinner"></div> |
|
|
<p>${message}</p> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
const modal = this.showModal('', loadingContent, []); |
|
|
modal.classList.add('loading-modal'); |
|
|
|
|
|
|
|
|
modal.querySelector('.modal-overlay').onclick = null; |
|
|
modal.querySelector('.modal-close').style.display = 'none'; |
|
|
|
|
|
return modal; |
|
|
} |
|
|
|
|
|
|
|
|
hideLoading() { |
|
|
const loadingModal = document.querySelector('.loading-modal'); |
|
|
if (loadingModal) { |
|
|
loadingModal.style.display = 'none'; |
|
|
loadingModal.classList.remove('loading-modal'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
addNotification(notification) { |
|
|
const newNotification = { |
|
|
id: Date.now(), |
|
|
timestamp: new Date().toISOString(), |
|
|
read: false, |
|
|
...notification |
|
|
}; |
|
|
|
|
|
this.notifications.unshift(newNotification); |
|
|
this.saveNotifications(); |
|
|
this.updateNotificationBadge(); |
|
|
|
|
|
return newNotification; |
|
|
} |
|
|
|
|
|
|
|
|
markAsRead(notificationId) { |
|
|
const notification = this.notifications.find(n => n.id === notificationId); |
|
|
if (notification) { |
|
|
notification.read = true; |
|
|
this.saveNotifications(); |
|
|
this.updateNotificationBadge(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
markAllAsRead() { |
|
|
this.notifications.forEach(n => n.read = true); |
|
|
this.saveNotifications(); |
|
|
this.updateNotificationBadge(); |
|
|
} |
|
|
|
|
|
|
|
|
deleteNotification(notificationId) { |
|
|
this.notifications = this.notifications.filter(n => n.id !== notificationId); |
|
|
this.saveNotifications(); |
|
|
this.updateNotificationBadge(); |
|
|
} |
|
|
|
|
|
|
|
|
getUnreadNotifications() { |
|
|
return this.notifications.filter(n => !n.read); |
|
|
} |
|
|
|
|
|
|
|
|
getUnreadCount() { |
|
|
return this.getUnreadNotifications().length; |
|
|
} |
|
|
|
|
|
|
|
|
updateNotificationBadge() { |
|
|
const badge = document.querySelector('#notifications-btn .badge'); |
|
|
const unreadCount = this.getUnreadCount(); |
|
|
|
|
|
if (badge) { |
|
|
if (unreadCount > 0) { |
|
|
badge.textContent = unreadCount > 99 ? '99+' : unreadCount; |
|
|
badge.style.display = 'block'; |
|
|
} else { |
|
|
badge.style.display = 'none'; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
showNotificationsList() { |
|
|
const notificationsHtml = this.notifications.length > 0 ? |
|
|
this.notifications.map(n => this.renderNotification(n)).join('') : |
|
|
'<div class="no-notifications">لا توجد إشعارات</div>'; |
|
|
|
|
|
const content = ` |
|
|
<div class="notifications-header"> |
|
|
<button class="btn btn-link" onclick="window.notificationManager.markAllAsRead()"> |
|
|
وضع علامة مقروء على الكل |
|
|
</button> |
|
|
</div> |
|
|
<div class="notifications-list"> |
|
|
${notificationsHtml} |
|
|
</div> |
|
|
`; |
|
|
|
|
|
this.showModal('الإشعارات', content, []); |
|
|
} |
|
|
|
|
|
|
|
|
renderNotification(notification) { |
|
|
const timeAgo = this.getTimeAgo(notification.timestamp); |
|
|
const readClass = notification.read ? 'read' : 'unread'; |
|
|
|
|
|
return ` |
|
|
<div class="notification-item ${readClass}" data-id="${notification.id}"> |
|
|
<div class="notification-icon ${notification.type}"> |
|
|
<i class="${this.getNotificationIcon(notification.type)}"></i> |
|
|
</div> |
|
|
<div class="notification-content"> |
|
|
<h4>${notification.title}</h4> |
|
|
<p>${notification.message}</p> |
|
|
<span class="notification-time">${timeAgo}</span> |
|
|
</div> |
|
|
<div class="notification-actions"> |
|
|
${!notification.read ? ` |
|
|
<button onclick="window.notificationManager.markAsRead(${notification.id})" title="وضع علامة مقروء"> |
|
|
<i class="fas fa-check"></i> |
|
|
</button> |
|
|
` : ''} |
|
|
<button onclick="window.notificationManager.deleteNotification(${notification.id})" title="حذف"> |
|
|
<i class="fas fa-trash"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
getNotificationIcon(type) { |
|
|
const icons = { |
|
|
transaction: 'fas fa-exchange-alt', |
|
|
security: 'fas fa-shield-alt', |
|
|
system: 'fas fa-cog', |
|
|
promotion: 'fas fa-gift', |
|
|
warning: 'fas fa-exclamation-triangle' |
|
|
}; |
|
|
return icons[type] || 'fas fa-bell'; |
|
|
} |
|
|
|
|
|
|
|
|
getTimeAgo(timestamp) { |
|
|
const now = new Date(); |
|
|
const time = new Date(timestamp); |
|
|
const diffInSeconds = Math.floor((now - time) / 1000); |
|
|
|
|
|
if (diffInSeconds < 60) return 'الآن'; |
|
|
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} دقيقة`; |
|
|
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} ساعة`; |
|
|
if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)} يوم`; |
|
|
return time.toLocaleDateString('ar-SA'); |
|
|
} |
|
|
|
|
|
|
|
|
saveNotifications() { |
|
|
localStorage.setItem('unifiedWallet_notifications', JSON.stringify(this.notifications)); |
|
|
} |
|
|
|
|
|
|
|
|
loadStoredNotifications() { |
|
|
const stored = localStorage.getItem('unifiedWallet_notifications'); |
|
|
if (stored) { |
|
|
this.notifications = JSON.parse(stored); |
|
|
this.updateNotificationBadge(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
addTransactionNotification(type, amount, wallet) { |
|
|
const messages = { |
|
|
send: `تم إرسال ${amount.toLocaleString()} ر.ي من ${wallet}`, |
|
|
receive: `تم استلام ${amount.toLocaleString()} ر.ي في ${wallet}`, |
|
|
bill: `تم دفع فاتورة بمبلغ ${amount.toLocaleString()} ر.ي من ${wallet}` |
|
|
}; |
|
|
|
|
|
this.addNotification({ |
|
|
type: 'transaction', |
|
|
title: 'معاملة جديدة', |
|
|
message: messages[type] || `معاملة بمبلغ ${amount.toLocaleString()} ر.ي` |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
addSecurityNotification(message) { |
|
|
this.addNotification({ |
|
|
type: 'security', |
|
|
title: 'تنبيه أمني', |
|
|
message: message |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
addSystemNotification(message) { |
|
|
this.addNotification({ |
|
|
type: 'system', |
|
|
title: 'إشعار النظام', |
|
|
message: message |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
window.NotificationManager = NotificationManager; |
|
|
|