445 lines
22 KiB
HTML
Raw Permalink 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.

<!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" id="appSidebar"></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)"><i data-lucide="search" style="width:14px;height:14px"></i></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" title="Logout"><i data-lucide="log-out"></i></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:240px">
<span style="color:var(--text-muted);font-size:13px">🔍</span>
<input type="text" id="tableSearch" placeholder="Filter list…">
</div>
<select class="filter-sel" id="typeFilter">
<option value="">All Types</option>
<option value="physical">Physical Assets</option>
<option value="digital">Digital Assets</option>
<option value="other">Other Assets</option>
</select>
<select class="filter-sel" id="assignFilter">
<option value="">All Assignments</option>
<option value="user">Assigned to User</option>
<option value="project">Assigned to Project</option>
<option value="unassigned">Unassigned</option>
</select>
<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>
<option>Software Licenses</option><option>Cloud Subscriptions</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)">0</strong> 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="https://unpkg.com/lucide@latest"></script>
<script src="js/data.js"></script>
<script src="js/sidebar.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();
applyFilters();
initCheckboxes('assetTable','assetTableBulk');
document.getElementById('tableSearch').addEventListener('input', applyFilters);
document.getElementById('typeFilter').addEventListener('change', applyFilters);
document.getElementById('assignFilter').addEventListener('change', 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 ty = document.getElementById('typeFilter').value.toLowerCase();
const as = document.getElementById('assignFilter').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 => {
const matchesSearch = !q || a.name.toLowerCase().includes(q) || a.id.toLowerCase().includes(q) || (a.serial && a.serial.toLowerCase().includes(q));
const matchesType = !ty || (a.type && a.type.toLowerCase() === ty);
const matchesStatus = !st || a.status.toLowerCase() === st;
const matchesCat = !ct || a.cat.toLowerCase() === ct;
const matchesDept = !dt || a.dept.toLowerCase() === dt;
let matchesAssign = true;
if (as === 'user') {
matchesAssign = a.assignee && a.assignee !== 'Unassigned' && (!a.project || a.project === 'N/A' || a.project === 'Unassigned / No Project');
} else if (as === 'project') {
matchesAssign = a.project && a.project !== 'N/A' && a.project !== 'Unassigned / No Project';
} else if (as === 'unassigned') {
matchesAssign = !a.assignee || a.assignee === 'Unassigned';
}
// Role-based restrictions: Employees can only view assets assigned to them
const isEmployee = AMS.currentUser.role === 'Employee';
const matchesRole = !isEmployee || a.assignee === AMS.currentUser.name;
return matchesSearch && matchesType && matchesAssign && matchesStatus && matchesCat && matchesDept && matchesRole;
});
currentPage = 1;
renderTable();
}
function clearFilters() {
document.getElementById('tableSearch').value = '';
document.getElementById('typeFilter').value = '';
document.getElementById('assignFilter').value = '';
document.getElementById('statusFilter').value = '';
document.getElementById('catFilter').value = '';
document.getElementById('deptFilter').value = '';
applyFilters();
}
function renderTable() {
if (!filteredAssets.length && !document.getElementById('tableSearch').value && !document.getElementById('typeFilter').value && !document.getElementById('assignFilter').value && !document.getElementById('statusFilter').value && !document.getElementById('catFilter').value && !document.getElementById('deptFilter').value) {
// If no filters are active and no search, check employee role list
const isEmployee = AMS.currentUser.role === 'Employee';
filteredAssets = isEmployee ? AMS.assets.filter(a => a.assignee === AMS.currentUser.name) : [...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 isDigital = a.type === 'digital';
const warrantyExpiring = a.warranty !== 'N/A' && new Date(a.warranty) < warningDate;
const depPct = isDigital ? 100 : Math.round((a.cost - a.value)/a.cost*100);
const assigneeText = (a.project && a.project !== 'N/A' && a.project !== 'Unassigned / No Project')
? `<span class="badge badge-primary" style="font-size:10.5px;padding:3px 6px">📁 ${escapeHtml(a.project)}</span>`
: escapeHtml(a.assignee || 'Unassigned');
const valueText = isDigital
? `<div style="font-weight:600;color:var(--primary-light)">${a.seatsAlloc}/${a.seatsTotal} seats</div><div style="font-size:10px;color:var(--text-muted)">allocated</div>`
: `<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>`;
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">${escapeHtml(a.name)}</div><div class="asset-id">${escapeHtml(a.id)} · ${isDigital ? 'Software License' : escapeHtml(a.serial)}</div></div>
</div></td>
<td><span class="badge badge-neutral" style="font-size:10px">${escapeHtml(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>${escapeHtml(a.dept)}</td>
<td>${assigneeText}</td>
<td style="font-weight:600">${fmt(a.cost)}</td>
<td>${valueText}</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}')"><i data-lucide="eye" style="width:14px;height:14px"></i></button>
<button class="btn btn-icon btn-ghost" title="QR Code" onclick="showQR('${a.id}')"><i data-lucide="qr-code" style="width:14px;height:14px"></i></button>
<button class="btn btn-icon btn-ghost" title="Assign" onclick="openAssignModal('${escapeJs(a.id)}','${escapeJs(a.name)}','${escapeJs(a.dept)}')"><i data-lucide="user-check" style="width:14px;height:14px"></i></button>
<button class="btn btn-icon btn-ghost" title="Transfer" onclick="openTransferModal('${a.id}','${a.loc}')"><i data-lucide="arrow-left-right" style="width:14px;height:14px"></i></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>