// ================================================================
// 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 => `
`).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 = ''; }
});
});
});