Vaibhav Surve 2dfe150a5c feat: AMS V1 — multi-page HTML prototype
- Login page with animated floating stat cards
- Executive dashboard with Chart.js KPIs and activity feed
- Full asset registry (list, search, filter, bulk actions, QR)
- Asset detail page with 6 tabs (financial, maintenance, history…)
- 3-step asset creation wizard with category-specific fields
- Inventory: stock overview, GRN, location tree, physical audit
- Procurement: PR → approval → PO → GRN → asset lifecycle
- Maintenance: Kanban board, PM schedule, AMC contracts
- Reports: depreciation schedule, utilization, compliance gauge
- User management: roles, permission matrix, session control
- Settings: 8 config sections (org, depreciation, security, integrations)
- Premium dark UI with CSS variables, Chart.js 4, toast system
2026-05-28 18:09:32 +05:30

276 lines
12 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ================================================================
// 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 = `<div class="toast-icon">${icons[type]}</div>
<div class="toast-msg"><div class="toast-title">${title}</div>${text?`<div class="toast-text">${text}</div>`:''}</div>
<button onclick="this.parentElement.remove()" style="background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:18px;padding:0 2px;margin-left:8px;line-height:1">×</button>`;
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 => `
<div class="notif-item" onclick="markNotifRead('${n.id}')">
${n.read ? '' : '<div class="notif-dot"></div>'}
<div style="flex:1">
<div class="notif-text">${n.text}</div>
<div class="notif-time">${n.time}</div>
</div>
</div>`).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 = `<button class="page-btn" onclick="(${onChange})(${Math.max(1,current-1)})" ${current===1?'disabled':''}></button>`;
for (let i=1;i<=pages;i++) {
html += `<button class="page-btn${i===current?' active':''}" onclick="(${onChange})(${i})">${i}</button>`;
}
html += `<button class="page-btn" onclick="(${onChange})(${Math.min(pages,current+1)})" ${current===pages?'disabled':''}></button>`;
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<seed.length; i++) hash = (hash * 31 + seed.charCodeAt(i)) | 0;
const rand = (n) => { 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 = ''; }
});
});
});