const PAGE_SIZE = 25; let nfItems = []; let nfTotal = 0; let nfUnread = 0; let nfUnreadOnly = false; let nfLoading = false; async function nfMarkRead(id) { const res = await fetch(`${API}/api/notifications/${id}/read`, { method: 'POST', headers: hdr() }); if (!res.ok) { showToast('Не удалось отметить уведомление'); return; } const item = nfItems.find(n => n.id === id); if (item) item.read = true; nfUnread = Math.max(0, nfUnread - 1); if (typeof notifyList !== 'undefined') { const shared = notifyList.find(n => n.id === id); if (shared) shared.read = true; } if (nfUnreadOnly) { nfItems = nfItems.filter(n => n.id !== id); nfTotal = Math.max(0, nfTotal - 1); loadNotifyPage(true); return; } renderNotifyPage(); if (typeof setNotifyUnread === 'function') setNotifyUnread(nfUnread); } async function nfDelete(id) { if (!confirm('Удалить уведомление у всех пользователей?')) return; const res = await fetch(`${API}/api/notifications/${id}`, { method: 'DELETE', headers: hdr() }); if (!res.ok) { showToast('Не удалось удалить уведомление'); return; } if (typeof notifyList !== 'undefined') notifyList = notifyList.filter(n => n.id !== id); loadNotifyPage(true); } async function nfMarkAllRead() { const res = await fetch(`${API}/api/notifications/read-all`, { method: 'POST', headers: hdr() }); if (!res.ok) { showToast('Не удалось отметить уведомления'); return; } showToast('Все уведомления прочитаны'); if (typeof notifyList !== 'undefined') notifyList.forEach(n => { n.read = true; }); if (typeof setNotifyUnread === 'function') setNotifyUnread(0); loadNotifyPage(true); } async function nfClearAll() { if (!confirm('Удалить все уведомления у всех пользователей? Действие необратимо.')) return; const res = await fetch(`${API}/api/notifications`, { method: 'DELETE', headers: hdr() }); if (!res.ok) { showToast('Не удалось очистить уведомления'); return; } const data = await res.json().catch(() => ({})); showToast(`Удалено уведомлений: ${data.deleted || 0}`); if (typeof notifyList !== 'undefined') notifyList = []; if (typeof setNotifyUnread === 'function') setNotifyUnread(0); loadNotifyPage(true); } function onNotificationArrived() { loadNotifyPage(true); } function onNotifyListChanged() { loadNotifyPage(true); } document.getElementById('nfList').addEventListener('click', e => { const readBtn = e.target.closest('[data-nf-read]'); if (readBtn) { e.preventDefault(); nfMarkRead(parseInt(readBtn.dataset.nfRead, 10)); return; } const delBtn = e.target.closest('[data-nf-del]'); if (delBtn) { e.preventDefault(); nfDelete(parseInt(delBtn.dataset.nfDel, 10)); return; } if (e.target.closest('a[href]')) { const card = e.target.closest('[data-nf-card]'); if (card) nfMarkRead(parseInt(card.dataset.nfCard, 10)); } }); document.getElementById('notifyFilter').addEventListener('click', e => { const btn = e.target.closest('button[data-filter]'); if (!btn) return; nfUnreadOnly = btn.dataset.filter === 'unread'; document.querySelectorAll('#notifyFilter button').forEach(b => b.classList.toggle('active', b === btn)); loadNotifyPage(true); }); document.getElementById('nfReadAllBtn').addEventListener('click', nfMarkAllRead); document.getElementById('nfClearBtn').addEventListener('click', nfClearAll); document.getElementById('nfMoreBtn').addEventListener('click', () => loadNotifyPage(false)); document.getElementById('nfReloadBtn').addEventListener('click', () => loadNotifyPage(true)); (async () => { if (!(await requireAdminPage())) return; buildSidebar(document.body.dataset.page); const clearBtn = document.getElementById('nfClearBtn'); if (clearBtn) clearBtn.hidden = !isAdmin(); const authStatus = document.getElementById('authStatus'); if (authStatus) authStatus.classList.add('loaded'); await loadNotifyPage(true); })(); function nfIcon(type) { return typeof NOTIFY_ICONS !== 'undefined' && NOTIFY_ICONS[type] ? NOTIFY_ICONS[type] : 'bell'; } function nfLevelClass(level) { return level === 'warning' || level === 'critical' ? ` level-${level}` : ''; } function nfCardHtml(n) { const when = typeof notifyTime === 'function' ? notifyTime(n.created_at) : ''; const linkAttr = typeof attrEsc === 'function' ? attrEsc(n.link) : esc(n.link); return `