- 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
416 lines
20 KiB
HTML
416 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>All Assets | AMS</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="css/styles.css">
|
||
</head>
|
||
<body>
|
||
<div class="app-layout">
|
||
|
||
<!-- SIDEBAR -->
|
||
<aside class="sidebar">
|
||
<div class="sidebar-logo"><div class="logo-icon">📦</div><div><div class="logo-title">AMS</div><div class="logo-sub">Asset Management</div></div></div>
|
||
<nav class="sidebar-nav">
|
||
<div class="nav-section-label">Overview</div>
|
||
<a href="dashboard.html" class="nav-item"><span class="nav-icon">📊</span> Dashboard</a>
|
||
<div class="nav-section-label">Assets</div>
|
||
<a href="assets.html" class="nav-item active"><span class="nav-icon">📦</span> All Assets</a>
|
||
<a href="asset-create.html" class="nav-item"><span class="nav-icon">➕</span> Add Asset</a>
|
||
<div class="nav-section-label">Supply Chain</div>
|
||
<a href="inventory.html" class="nav-item"><span class="nav-icon">🏪</span> Inventory</a>
|
||
<a href="procurement.html" class="nav-item"><span class="nav-icon">🛒</span> Procurement <span class="nav-badge">8</span></a>
|
||
<div class="nav-section-label">Operations</div>
|
||
<a href="maintenance.html" class="nav-item"><span class="nav-icon">🔧</span> Maintenance <span class="nav-badge">12</span></a>
|
||
<a href="reports.html" class="nav-item"><span class="nav-icon">📈</span> Reports & Audit</a>
|
||
<div class="nav-section-label">Administration</div>
|
||
<a href="users.html" class="nav-item"><span class="nav-icon">👥</span> Users & Roles</a>
|
||
<a href="settings.html" class="nav-item"><span class="nav-icon">⚙️</span> Settings</a>
|
||
</nav>
|
||
<div class="sidebar-footer"><div class="user-card"><div class="user-av">AS</div><div style="flex:1;min-width:0"><div class="user-name">Arjun Sharma</div><div class="user-role">Asset Manager</div></div></div></div>
|
||
</aside>
|
||
|
||
<!-- MAIN -->
|
||
<div class="main-wrapper">
|
||
<header class="topbar">
|
||
<div class="topbar-left"><div class="topbar-title">All Assets <span class="topbar-sub" id="totalCount"></span></div></div>
|
||
<div class="topbar-search"><span style="color:var(--text-muted)">🔍</span><input type="text" placeholder="Search assets…" id="assetSearch" data-search-table="assetTable"></div>
|
||
<div class="topbar-actions">
|
||
<a href="asset-create.html" class="btn btn-primary btn-sm">+ Add Asset</a>
|
||
<button class="btn btn-secondary btn-sm" onclick="exportAssets()">📥 Export</button>
|
||
<a href="index.html" class="icon-btn">🚪</a>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="content">
|
||
<!-- Stat Mini Cards -->
|
||
<div class="grid-4 mb-4" id="assetStats"></div>
|
||
|
||
<!-- Filters -->
|
||
<div class="filters-row" id="filtersRow">
|
||
<div class="search-wrap" style="max-width:300px">
|
||
<span style="color:var(--text-muted);font-size:13px">🔍</span>
|
||
<input type="text" id="tableSearch" placeholder="Filter list…">
|
||
</div>
|
||
<select class="filter-sel" id="statusFilter">
|
||
<option value="">All Statuses</option>
|
||
<option>Active</option><option>Idle</option>
|
||
<option>Under Maintenance</option><option>Disposed</option>
|
||
</select>
|
||
<select class="filter-sel" id="catFilter">
|
||
<option value="">All Categories</option>
|
||
<option>Laptops</option><option>Desktops</option><option>Servers</option>
|
||
<option>Printers</option><option>Networking</option><option>Mobile Devices</option>
|
||
<option>AV Equipment</option><option>Displays</option><option>Furniture</option>
|
||
<option>Vehicles</option><option>HVAC</option><option>Power Equipment</option>
|
||
</select>
|
||
<select class="filter-sel" id="deptFilter">
|
||
<option value="">All Departments</option>
|
||
<option>IT</option><option>Finance</option><option>HR</option>
|
||
<option>Operations</option><option>Marketing</option><option>Admin</option>
|
||
</select>
|
||
<button class="btn btn-ghost btn-sm" onclick="clearFilters()">✕ Clear</button>
|
||
<div style="margin-left:auto;font-size:12.5px;color:var(--text-muted)">Showing <strong id="visCount" style="color:var(--text-primary)">20</strong> of 1,247 assets</div>
|
||
</div>
|
||
|
||
<!-- Bulk Bar -->
|
||
<div class="bulk-bar" id="assetTableBulk">
|
||
<span class="bulk-count">0 items selected</span>
|
||
<button class="btn btn-secondary btn-sm" onclick="bulkAction('transfer')">↔ Transfer</button>
|
||
<button class="btn btn-secondary btn-sm" onclick="bulkAction('assign')">👤 Assign</button>
|
||
<button class="btn btn-secondary btn-sm" onclick="bulkAction('export')">📥 Export</button>
|
||
<button class="btn btn-danger btn-sm" onclick="bulkAction('dispose')">🗑️ Dispose</button>
|
||
</div>
|
||
|
||
<!-- Table -->
|
||
<div class="table-wrapper">
|
||
<table class="data-table" id="assetTable" data-table-checkboxes>
|
||
<thead>
|
||
<tr>
|
||
<th style="width:40px"><input type="checkbox" class="select-all"></th>
|
||
<th onclick="sortTable(1)">Asset ↕</th>
|
||
<th onclick="sortTable(2)">Category ↕</th>
|
||
<th onclick="sortTable(3)">Status ↕</th>
|
||
<th onclick="sortTable(4)">Department ↕</th>
|
||
<th onclick="sortTable(5)">Assigned To ↕</th>
|
||
<th onclick="sortTable(6)">Purchase Cost ↕</th>
|
||
<th onclick="sortTable(7)">Net Value ↕</th>
|
||
<th>Warranty</th>
|
||
<th style="width:110px">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="assetTbody"></tbody>
|
||
</table>
|
||
<div class="pagination" id="assetPagination"></div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Assign Modal ── -->
|
||
<div class="modal-overlay" id="assignModal">
|
||
<div class="modal">
|
||
<div class="modal-header"><span class="modal-title">Assign Asset</span><button class="modal-close">✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="alert alert-info mb-4"><div class="alert-icon">ℹ️</div><div><div class="alert-title" id="assignAssetName">Asset Name</div><div class="alert-text">Current assignee will receive a handover notification</div></div></div>
|
||
<div class="form-group">
|
||
<label class="form-label">Assign To (Employee) <span class="req">*</span></label>
|
||
<select class="form-select" id="assignEmployee">
|
||
<option value="">Select employee…</option>
|
||
<option>Priya Kumar – IT</option><option>Rahul Mehta – Finance</option>
|
||
<option>Anita Singh – HR</option><option>Vikram Reddy – Operations</option>
|
||
<option>Sneha Patel – Marketing</option><option>Deepak Joshi – Admin</option>
|
||
<option>Kavya Nair – IT</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">Effective From</label>
|
||
<input type="date" class="form-input" id="assignFrom" value="2025-05-28">
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Expected Return (if temp)</label>
|
||
<input type="date" class="form-input" id="assignTo">
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Acknowledgment Required?</label>
|
||
<select class="form-select">
|
||
<option>Yes – Send email to employee for confirmation</option>
|
||
<option>No – Direct assignment</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Notes</label>
|
||
<textarea class="form-textarea" placeholder="Any notes about this assignment…" rows="2"></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-ghost" onclick="closeModal('assignModal')">Cancel</button>
|
||
<button class="btn btn-primary" onclick="saveAssignment()">Assign Asset</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── QR Modal ── -->
|
||
<div class="modal-overlay" id="qrModal">
|
||
<div class="modal" style="max-width:380px">
|
||
<div class="modal-header"><span class="modal-title">QR Code Label</span><button class="modal-close">✕</button></div>
|
||
<div class="modal-body" style="text-align:center">
|
||
<div id="qrDisplay" style="margin:0 auto 16px;display:inline-block"></div>
|
||
<div id="qrAssetInfo" style="font-size:12.5px;color:var(--text-muted);margin-bottom:16px"></div>
|
||
<button class="btn btn-primary" onclick="printQR()">🖨️ Print Label</button>
|
||
<button class="btn btn-secondary" onclick="downloadQR()">📥 Download PNG</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Transfer Modal ── -->
|
||
<div class="modal-overlay" id="transferModal">
|
||
<div class="modal">
|
||
<div class="modal-header"><span class="modal-title">Transfer Asset</span><button class="modal-close">✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="form-group">
|
||
<label class="form-label">Transfer From (Current Location)</label>
|
||
<input type="text" class="form-input" id="transferFrom" disabled>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">To Department <span class="req">*</span></label>
|
||
<select class="form-select">
|
||
<option>IT</option><option>Finance</option><option>HR</option>
|
||
<option>Operations</option><option>Marketing</option><option>Admin</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">To Location <span class="req">*</span></label>
|
||
<select class="form-select">
|
||
<option>IT Dept – Floor 2</option><option>Finance – Floor 1</option>
|
||
<option>HR – Floor 2</option><option>Server Room – B1</option>
|
||
<option>Marketing – Floor 3</option><option>Conference Room A</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Reason for Transfer <span class="req">*</span></label>
|
||
<select class="form-select">
|
||
<option>Department Restructuring</option><option>Employee Transfer</option>
|
||
<option>Better Utilization</option><option>Location Upgrade</option><option>Other</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Approval Required?</label>
|
||
<select class="form-select"><option>Yes – Send for Department Head approval</option><option>No (Self-authorized)</option></select>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-ghost" onclick="closeModal('transferModal')">Cancel</button>
|
||
<button class="btn btn-primary" onclick="saveTransfer()">Initiate Transfer</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Confirm Modal -->
|
||
<div class="modal-overlay" id="confirmModal">
|
||
<div class="modal" style="max-width:400px">
|
||
<div class="modal-header"><span class="modal-title confirm-title">Confirm</span><button class="modal-close">✕</button></div>
|
||
<div class="modal-body"><p class="confirm-message" style="color:var(--text-secondary);font-size:13.5px"></p></div>
|
||
<div class="modal-footer"><button class="btn btn-ghost" onclick="closeModal('confirmModal')">Cancel</button><button class="btn btn-danger confirm-ok">Confirm</button></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast-container" id="toastContainer"></div>
|
||
<script src="js/data.js"></script>
|
||
<script src="js/app.js"></script>
|
||
<script>
|
||
let currentPage = 1;
|
||
const perPage = 10;
|
||
let sortCol = -1, sortDir = 1;
|
||
let filteredAssets = [];
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
renderStats();
|
||
renderTable();
|
||
initCheckboxes('assetTable','assetTableBulk');
|
||
document.getElementById('tableSearch').addEventListener('input', applyFilters);
|
||
document.getElementById('statusFilter').addEventListener('change', applyFilters);
|
||
document.getElementById('catFilter').addEventListener('change', applyFilters);
|
||
document.getElementById('deptFilter').addEventListener('change', applyFilters);
|
||
});
|
||
|
||
function renderStats() {
|
||
const s = AMS.stats;
|
||
document.getElementById('totalCount').textContent = `— ${s.total.toLocaleString()} total`;
|
||
document.getElementById('assetStats').innerHTML = [
|
||
{label:'Total',icon:'📦',val:s.total,color:'var(--primary)',link:'?status='},
|
||
{label:'Active',icon:'✅',val:s.active,color:'var(--success)',link:'?status=active'},
|
||
{label:'Maintenance',icon:'🔧',val:s.maintenance,color:'var(--info)',link:'?status=maintenance'},
|
||
{label:'Idle',icon:'😴',val:s.idle,color:'var(--warning)',link:'?status=idle'}
|
||
].map(k=>`
|
||
<div class="stat-card" style="--sc-color:${k.color};cursor:pointer" onclick="filterByStatus('${k.label}')">
|
||
<div class="stat-icon" style="background:${k.color}18;color:${k.color}">${k.icon}</div>
|
||
<div class="stat-label">${k.label}</div>
|
||
<div class="stat-value" style="color:${k.color}">${k.val.toLocaleString('en-IN')}</div>
|
||
</div>`).join('');
|
||
}
|
||
|
||
function filterByStatus(s) {
|
||
if(s==='Total') { document.getElementById('statusFilter').value=''; }
|
||
else if(s==='Active') document.getElementById('statusFilter').value='Active';
|
||
else if(s==='Maintenance') document.getElementById('statusFilter').value='Under Maintenance';
|
||
else if(s==='Idle') document.getElementById('statusFilter').value='Idle';
|
||
applyFilters();
|
||
}
|
||
|
||
function applyFilters() {
|
||
const q = document.getElementById('tableSearch').value.toLowerCase();
|
||
const st = document.getElementById('statusFilter').value.toLowerCase();
|
||
const ct = document.getElementById('catFilter').value.toLowerCase();
|
||
const dt = document.getElementById('deptFilter').value.toLowerCase();
|
||
filteredAssets = AMS.assets.filter(a =>
|
||
(!q || a.name.toLowerCase().includes(q) || a.id.toLowerCase().includes(q) || a.serial.toLowerCase().includes(q)) &&
|
||
(!st || a.status.toLowerCase() === st) &&
|
||
(!ct || a.cat.toLowerCase() === ct) &&
|
||
(!dt || a.dept.toLowerCase() === dt)
|
||
);
|
||
currentPage = 1;
|
||
renderTable();
|
||
}
|
||
|
||
function clearFilters() {
|
||
document.getElementById('tableSearch').value = '';
|
||
document.getElementById('statusFilter').value = '';
|
||
document.getElementById('catFilter').value = '';
|
||
document.getElementById('deptFilter').value = '';
|
||
applyFilters();
|
||
}
|
||
|
||
function renderTable() {
|
||
if (!filteredAssets.length && !document.getElementById('tableSearch').value) {
|
||
filteredAssets = [...AMS.assets];
|
||
}
|
||
document.getElementById('visCount').textContent = filteredAssets.length;
|
||
const start = (currentPage-1)*perPage;
|
||
const page = filteredAssets.slice(start, start+perPage);
|
||
const tbody = document.getElementById('assetTbody');
|
||
if (!page.length) {
|
||
tbody.innerHTML = `<tr><td colspan="10"><div class="empty-state"><div class="empty-icon">📦</div><div class="empty-title">No assets found</div><div class="empty-text">Try adjusting your filters</div></div></td></tr>`;
|
||
return;
|
||
}
|
||
const warningDate = new Date(); warningDate.setDate(warningDate.getDate()+90);
|
||
tbody.innerHTML = page.map(a => {
|
||
const warrantyExpiring = a.warranty !== 'N/A' && new Date(a.warranty) < warningDate;
|
||
const depPct = Math.round((a.cost - a.value)/a.cost*100);
|
||
return `<tr onclick="goToDetail('${a.id}')">
|
||
<td onclick="event.stopPropagation()"><input type="checkbox" class="row-check"></td>
|
||
<td><div class="asset-cell">
|
||
<div class="asset-ic">${a.icon}</div>
|
||
<div><div class="asset-n">${a.name}</div><div class="asset-id">${a.id} · ${a.serial}</div></div>
|
||
</div></td>
|
||
<td><span class="badge badge-neutral" style="font-size:10px">${a.cat}</span></td>
|
||
<td><span class="badge ${statusBadge(a.status)}"><span class="badge-dot-ind" style="background:${statusDot(a.status)}"></span>${a.status}</span></td>
|
||
<td>${a.dept}</td>
|
||
<td>${a.assignee}</td>
|
||
<td style="font-weight:600">${fmt(a.cost)}</td>
|
||
<td>
|
||
<div style="font-weight:600;color:${depPct>60?'var(--warning)':'var(--text-primary)'}">${fmt(a.value)}</div>
|
||
<div style="font-size:10px;color:var(--text-muted)">${depPct}% depreciated</div>
|
||
</td>
|
||
<td><span style="color:${warrantyExpiring?'var(--warning)':'var(--text-muted)';};font-size:12px">${a.warranty==='N/A'?'—':fmtDate(a.warranty)}</span></td>
|
||
<td onclick="event.stopPropagation()">
|
||
<div class="flex gap-1">
|
||
<button class="btn btn-icon btn-ghost" title="View" onclick="goToDetail('${a.id}')">👁️</button>
|
||
<button class="btn btn-icon btn-ghost" title="QR Code" onclick="showQR('${a.id}')">📱</button>
|
||
<button class="btn btn-icon btn-ghost" title="Assign" onclick="openAssignModal('${a.id}','${a.name}','${a.dept}')">👤</button>
|
||
<button class="btn btn-icon btn-ghost" title="Transfer" onclick="openTransferModal('${a.id}','${a.loc}')">↔</button>
|
||
</div>
|
||
</td>
|
||
</tr>`;
|
||
}).join('');
|
||
renderPagBtn();
|
||
}
|
||
|
||
function renderPagBtn() {
|
||
const total = filteredAssets.length;
|
||
const pages = Math.max(1,Math.ceil(total/perPage));
|
||
let html = `<button class="page-btn" onclick="goPage(${currentPage-1})" ${currentPage===1?'disabled':''}>‹</button>`;
|
||
for(let i=1;i<=pages;i++) html+=`<button class="page-btn${i===currentPage?' active':''}" onclick="goPage(${i})">${i}</button>`;
|
||
html+=`<button class="page-btn" onclick="goPage(${currentPage+1})" ${currentPage===pages?'disabled':''}>›</button>`;
|
||
document.getElementById('assetPagination').innerHTML = html;
|
||
}
|
||
function goPage(p) { if(p<1||p>Math.ceil(filteredAssets.length/perPage))return; currentPage=p; renderTable(); window.scrollTo(0,0); }
|
||
|
||
function sortTable(col) {
|
||
if(sortCol===col) sortDir*=-1; else { sortCol=col; sortDir=1; }
|
||
const keys=['','name','cat','status','dept','assignee','cost','value','warranty'];
|
||
filteredAssets.sort((a,b)=>{
|
||
const av=a[keys[col]]||''; const bv=b[keys[col]]||'';
|
||
if(typeof av==='number') return (av-bv)*sortDir;
|
||
return av.localeCompare(bv)*sortDir;
|
||
});
|
||
renderTable();
|
||
}
|
||
|
||
function goToDetail(id) { window.location.href = `asset-detail.html?id=${encodeURIComponent(id)}`; }
|
||
|
||
function openAssignModal(id, name, dept) {
|
||
document.getElementById('assignAssetName').textContent = name;
|
||
openModal('assignModal');
|
||
}
|
||
function saveAssignment() {
|
||
const emp = document.getElementById('assignEmployee').value;
|
||
if(!emp) { showToast('Error','Please select an employee','error'); return; }
|
||
closeModal('assignModal');
|
||
showToast('Asset Assigned','Acknowledgment email sent to employee','success');
|
||
}
|
||
|
||
function openTransferModal(id, loc) {
|
||
document.getElementById('transferFrom').value = loc;
|
||
openModal('transferModal');
|
||
}
|
||
function saveTransfer() { closeModal('transferModal'); showToast('Transfer Initiated','Approval request sent to Department Head','info'); }
|
||
|
||
function showQR(id) {
|
||
const a = AMS.assets.find(x=>x.id===id);
|
||
if(!a) return;
|
||
const cells = generateQRPattern(id);
|
||
const qrHTML = `<div class="qr-box">
|
||
<div style="display:grid;grid-template-columns:repeat(7,12px);grid-template-rows:repeat(7,12px);gap:2px;background:#fff;padding:10px">
|
||
${cells.map(c=>`<div style="background:${c?'#000':'#fff'};border-radius:1px"></div>`).join('')}
|
||
</div>
|
||
<div style="font-size:9px;color:#333;font-weight:700;font-family:monospace">${a.id}</div>
|
||
<div style="font-size:8px;color:#555;max-width:120px;text-align:center">${a.name}</div>
|
||
</div>`;
|
||
document.getElementById('qrDisplay').innerHTML = qrHTML;
|
||
document.getElementById('qrAssetInfo').innerHTML = `<strong>${a.name}</strong><br>Serial: ${a.serial}<br>Category: ${a.cat}`;
|
||
openModal('qrModal');
|
||
}
|
||
function printQR() { window.print(); }
|
||
function downloadQR() { showToast('Download','QR code image downloaded','success'); }
|
||
|
||
function bulkAction(action) {
|
||
const checked = document.querySelectorAll('#assetTable .row-check:checked').length;
|
||
if(!checked) { showToast('No selection','Please select assets first','warning'); return; }
|
||
if(action==='dispose') {
|
||
confirmAction('Dispose Assets',`Mark ${checked} asset(s) as disposed? This action cannot be undone.`,()=>{
|
||
showToast('Assets Disposed',`${checked} asset(s) marked for disposal`,'success');
|
||
});
|
||
} else if(action==='transfer') {
|
||
openModal('transferModal');
|
||
} else if(action==='export') {
|
||
showToast('Export','Exporting selected assets to CSV…','info');
|
||
setTimeout(()=>{ downloadCSV(AMS.assets.slice(0,checked),'selected_assets.csv'); },600);
|
||
} else if(action==='assign') {
|
||
openModal('assignModal');
|
||
}
|
||
}
|
||
|
||
function exportAssets() {
|
||
showToast('Export','Preparing CSV export…','info');
|
||
setTimeout(()=>{ downloadCSV(AMS.assets.map(a=>({ID:a.id,Name:a.name,Category:a.cat,Status:a.status,Dept:a.dept,Assignee:a.assignee,Cost:a.cost,Value:a.value,Serial:a.serial,Vendor:a.vendor,Warranty:a.warranty})),'all_assets_export.csv'); },800);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|