// ================================================================ // AMS — Core Application Logic | app.js // ================================================================ /* ── Active Nav ─────────────────────────────────────────────────── */ function setActiveNav() { const page = window.location.pathname.split('/').pop() || 'dashboard.html'; document.querySelectorAll('.nav-item').forEach(el => { const href = el.getAttribute('href') || ''; if (href && page === href) el.classList.add('active'); else el.classList.remove('active'); }); } /* ── Modals ─────────────────────────────────────────────────────── */ function openModal(id) { const el = document.getElementById(id); if (el) { el.classList.add('open'); document.body.style.overflow = 'hidden'; } } function closeModal(id) { const el = document.getElementById(id); if (el) { el.classList.remove('open'); document.body.style.overflow = ''; } } /* ── Tabs ─────────────────────────────────────────────────────── */ function initTabs(container) { const root = container || document; root.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { const tabsEl = btn.closest('.tabs'); const tabGroup = tabsEl ? tabsEl.dataset.group : null; const targetId = btn.dataset.tab; tabsEl.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); const scope = btn.closest('.tab-container') || btn.closest('.card') || btn.closest('.content') || document; scope.querySelectorAll('.tab-content').forEach(tc => { if (tabGroup && tc.dataset.group && tc.dataset.group !== tabGroup) return; tc.classList.toggle('active', tc.id === targetId); }); }); }); } /* ── Toast ─────────────────────────────────────────────────────── */ function showToast(title, text='', type='success', dur=4200) { const icons = { success:'✅', error:'❌', warning:'⚠️', info:'ℹ️' }; const c = document.getElementById('toastContainer'); if (!c) return; const t = document.createElement('div'); t.className = `toast ${type}`; t.innerHTML = `
${icons[type]}
${title}
${text?`
${text}
`:''}
`; c.appendChild(t); setTimeout(() => { t.style.opacity='0'; t.style.transform='translateX(30px)'; t.style.transition='all .3s ease'; setTimeout(()=>t.remove(),350); }, dur); } /* ── Table Search ─────────────────────────────────────────────── */ function initSearch(inputId, tableId) { const inp = document.getElementById(inputId); const tbl = document.getElementById(tableId); if (!inp || !tbl) return; inp.addEventListener('input', () => { const q = inp.value.toLowerCase().trim(); let vis = 0; tbl.querySelectorAll('tbody tr').forEach(r => { const show = !q || r.textContent.toLowerCase().includes(q); r.style.display = show ? '' : 'none'; if (show) vis++; }); const cnt = document.getElementById(tableId + '-count'); if (cnt) cnt.textContent = vis; }); } /* ── Select Filter ─────────────────────────────────────────────── */ function initFilter(selId, tableId, colIdx) { const sel = document.getElementById(selId); const tbl = document.getElementById(tableId); if (!sel || !tbl) return; sel.addEventListener('change', () => { const v = sel.value.toLowerCase(); tbl.querySelectorAll('tbody tr').forEach(r => { if (!v) { r.style.display = ''; return; } const cell = r.cells[colIdx]; r.style.display = (cell && cell.textContent.toLowerCase().includes(v)) ? '' : 'none'; }); }); } /* ── Bulk Checkboxes ─────────────────────────────────────────── */ function initCheckboxes(tableId, bulkBarId) { const tbl = document.getElementById(tableId); const bar = document.getElementById(bulkBarId); if (!tbl) return; const all = tbl.querySelector('.select-all'); const rows = () => [...tbl.querySelectorAll('.row-check')]; const updateBar = () => { if (!bar) return; const n = rows().filter(c => c.checked).length; bar.style.display = n > 0 ? 'flex' : 'none'; const cnt = bar.querySelector('.bulk-count'); if (cnt) cnt.textContent = `${n} item${n!==1?'s':''} selected`; }; if (all) { all.addEventListener('change', () => { rows().forEach(c => { c.checked = all.checked; c.closest('tr').classList.toggle('row-selected', all.checked); }); updateBar(); }); } rows().forEach(c => { c.addEventListener('change', () => { c.closest('tr').classList.toggle('row-selected', c.checked); if (all) all.checked = rows().every(r => r.checked); updateBar(); }); }); } /* ── Confirm Dialog ─────────────────────────────────────────── */ function confirmAction(title, msg, cb, type='danger') { const m = document.getElementById('confirmModal'); if (!m) { if (confirm(msg) && cb) cb(); return; } m.querySelector('.confirm-title').textContent = title; m.querySelector('.confirm-message').textContent = msg; const btn = m.querySelector('.confirm-ok'); btn.onclick = () => { closeModal('confirmModal'); if (cb) cb(); }; openModal('confirmModal'); } /* ── Dropdowns ─────────────────────────────────────────────── */ function initDropdowns() { document.querySelectorAll('[data-dropdown]').forEach(trigger => { trigger.addEventListener('click', e => { e.stopPropagation(); const menuId = trigger.dataset.dropdown; const menu = document.getElementById(menuId); if (!menu) return; document.querySelectorAll('.dropdown-menu.open').forEach(m => { if (m.id !== menuId) m.classList.remove('open'); }); menu.classList.toggle('open'); }); }); document.addEventListener('click', () => { document.querySelectorAll('.dropdown-menu.open').forEach(m => m.classList.remove('open')); }); } /* ── Tree ─────────────────────────────────────────────────── */ function initTrees() { document.querySelectorAll('.tree-toggle').forEach(t => { t.addEventListener('click', () => { const ch = t.closest('.tree-node').querySelector('.tree-children'); const ar = t.querySelector('.tree-arrow'); if (ch) { ch.classList.toggle('open'); if (ar) ar.classList.toggle('open'); } }); }); } /* ── Notif Panel ─────────────────────────────────────────────── */ function initNotifPanel() { const bell = document.getElementById('notifBell'); const panel = document.getElementById('notifPanel'); if (bell && panel) { bell.addEventListener('click', e => { e.stopPropagation(); panel.classList.toggle('open'); }); document.addEventListener('click', e => { if (!panel.contains(e.target) && e.target !== bell) panel.classList.remove('open'); }); } } /* ── Sidebar User Info ─────────────────────────────────────── */ function populateSidebarUser() { if (!window.AMS) return; const u = AMS.currentUser; const nameEl = document.getElementById('sidebarUserName'); const roleEl = document.getElementById('sidebarUserRole'); const avEl = document.getElementById('sidebarUserAv'); if (nameEl) nameEl.textContent = u.name; if (roleEl) roleEl.textContent = u.role; if (avEl) avEl.textContent = u.avatar; } /* ── Topbar Notifications Badge ─────────────────────────────── */ function populateNotifBadge() { if (!window.AMS) return; const unread = AMS.notifications.filter(n => !n.read).length; const dot = document.querySelector('#notifBell .badge-dot'); if (dot) dot.style.display = unread > 0 ? '' : 'none'; } /* ── Render Notifications Panel ─────────────────────────────── */ function renderNotifPanel() { if (!window.AMS) return; const list = document.getElementById('notifList'); if (!list) return; list.innerHTML = AMS.notifications.map(n => `
${n.read ? '' : '
'}
${n.text}
${n.time}
`).join(''); } function markNotifRead(id) { if (window.AMS) { const n = AMS.notifications.find(x => x.id === id); if (n) n.read = true; populateNotifBadge(); renderNotifPanel(); } } /* ── Pagination ─────────────────────────────────────────────── */ function renderPagination(containerId, total, perPage=10, current=1, onChange) { const c = document.getElementById(containerId); if (!c) return; const pages = Math.ceil(total / perPage); let html = ``; for (let i=1;i<=pages;i++) { html += ``; } html += ``; c.innerHTML = html; } /* ── Download CSV ─────────────────────────────────────────── */ function downloadCSV(data, filename='export.csv') { if (!data.length) return; const headers = Object.keys(data[0]).join(','); const rows = data.map(r => Object.values(r).map(v => `"${String(v).replace(/"/g,'""')}"`).join(',')); const csv = [headers, ...rows].join('\n'); const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([csv],{type:'text/csv'})); a.download = filename; a.click(); } /* ── Generate QR SVG Pattern ───────────────────────────────── */ function generateQRPattern(seed) { // Deterministic pseudo-QR pattern based on seed string let hash = 0; for (let i=0; i { hash = (hash * 1103515245 + 12345) | 0; return Math.abs(hash) % n; }; const cells = []; for (let i=0;i<49;i++) cells.push(rand(2)); // Force finder pattern corners const corners = [0,1,2,3,4,5,6,7,14,21,28,35,42,43,44,45,46,47,48]; corners.forEach(i => cells[i] = i%7<1 || i%7>5 || Math.floor(i/7)<1 || Math.floor(i/7)>5 ? 1 : 0); return cells; } /* ── DOMContentLoaded Init ─────────────────────────────────── */ document.addEventListener('DOMContentLoaded', () => { setActiveNav(); initTabs(); initDropdowns(); initTrees(); initNotifPanel(); populateSidebarUser(); populateNotifBadge(); renderNotifPanel(); // Auto close modal on overlay click document.querySelectorAll('.modal-overlay').forEach(ov => { ov.addEventListener('click', e => { if (e.target === ov) { ov.classList.remove('open'); document.body.style.overflow = ''; } }); }); // Auto close modal on .modal-close document.querySelectorAll('.modal-close').forEach(btn => { btn.addEventListener('click', () => { const ov = btn.closest('.modal-overlay'); if (ov) { ov.classList.remove('open'); document.body.style.overflow = ''; } }); }); });