marco.asseto.prototype/dashboard.html

292 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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>Dashboard | AMS</title>
<meta name="description" content="Asset Management System Executive Dashboard with KPIs and analytics">
<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">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
</head>
<body>
<div class="app-layout">
<!-- ════ SIDEBAR ════ -->
<aside class="sidebar" id="appSidebar"></aside>
<!-- ════ MAIN ════ -->
<div class="main-wrapper">
<!-- Topbar -->
<header class="topbar">
<div class="topbar-left">
<div class="topbar-title">Dashboard <span class="topbar-sub">Welcome back, Arjun 👋</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, tickets…" id="globalSearch">
</div>
<div class="topbar-actions">
<div style="position:relative">
<button class="icon-btn" id="notifBell"><i data-lucide="bell"></i><span class="badge-dot"></span></button>
<div class="notif-panel" id="notifPanel">
<div class="notif-header"><span class="notif-title">Notifications</span><button class="btn btn-ghost btn-sm" onclick="markAllRead()" style="font-size:11px">Mark all read</button></div>
<div id="notifList"></div>
<div style="padding:10px 16px;text-align:center;border-top:1px solid var(--border)"><a href="#" style="font-size:12px;color:var(--primary-light)">View all</a></div>
</div>
</div>
<a href="index.html" class="icon-btn" title="Logout"><i data-lucide="log-out"></i></a>
</div>
</header>
<main class="content">
<!-- ── Alerts ──────────────────────── -->
<div id="alertsArea" class="mb-4"></div>
<!-- ── KPI Cards ──────────────────── -->
<div class="grid-4 mb-4" id="kpiCards"></div>
<!-- ── Charts Row ─────────────────── -->
<div class="grid-charts mb-4">
<div class="card">
<div class="card-header">
<span class="card-title"><i data-lucide="bar-chart-3" style="width:16px;height:16px;margin-right:6px;vertical-align:middle"></i> Asset Status Distribution</span>
<button class="btn btn-ghost btn-sm" onclick="window.location.href='reports.html'">View Report →</button>
</div>
<div class="card-body">
<canvas id="statusChart" height="260"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title"><i data-lucide="pie-chart" style="width:16px;height:16px;margin-right:6px;vertical-align:middle"></i> Assets by Category</span>
</div>
<div class="card-body">
<canvas id="categoryChart" height="260"></canvas>
</div>
</div>
</div>
<!-- ── Second Charts Row ──────────── -->
<div class="grid-2 mb-4">
<div class="card">
<div class="card-header">
<span class="card-title"><i data-lucide="trending-down" style="width:16px;height:16px;margin-right:6px;vertical-align:middle"></i> Depreciation Overview</span>
<button class="btn btn-ghost btn-sm" onclick="window.location.href='reports.html'">Details →</button>
</div>
<div class="card-body">
<canvas id="depreciationChart" height="210"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title"><i data-lucide="alert-triangle" style="width:16px;height:16px;margin-right:6px;vertical-align:middle"></i> Action Items</span>
<span class="badge badge-danger" style="font-size:11px">Needs Attention</span>
</div>
<div class="card-body" id="actionItems"></div>
</div>
</div>
<!-- ── Bottom Row ─────────────────── -->
<div class="grid-2">
<div class="card">
<div class="card-header">
<span class="card-title"><i data-lucide="clock" style="width:16px;height:16px;margin-right:6px;vertical-align:middle"></i> Recent Activity</span>
<span style="font-size:12px;color:var(--text-muted)">Last 7 days</span>
</div>
<div class="card-body" id="activityFeed" style="padding-top:8px"></div>
</div>
<div class="card">
<div class="card-header">
<span class="card-title"><i data-lucide="building" style="width:16px;height:16px;margin-right:6px;vertical-align:middle"></i> Assets by Department</span>
</div>
<div class="card-body">
<canvas id="deptChart" height="240"></canvas>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- Confirm Modal (shared) -->
<div class="modal-overlay" id="confirmModal">
<div class="modal" style="max-width:420px">
<div class="modal-header"><span class="modal-title confirm-title">Confirm Action</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>
document.addEventListener('DOMContentLoaded', () => {
renderAlerts();
renderKPIs();
renderActivity();
renderActionItems();
initCharts();
});
function renderAlerts() {
const alerts = [
{ type:'warning', icon:'⚠️', title:'3 AMC contracts expiring within 30 days', text:'Cisco (Jun 30), HP Warranty (Aug 20), Epson (Nov 10). Renew before expiry to avoid service gaps.' },
{ type:'danger', icon:'🔴', title:'PM overdue: 2 scheduled maintenances not completed', text:'Daikin AC (Floor 2) and Ricoh Photocopier (Admin) are past due for preventive maintenance.' }
];
document.getElementById('alertsArea').innerHTML = alerts.map(a => `
<div class="alert alert-${a.type}">
<div class="alert-icon">${a.icon}</div>
<div><div class="alert-title">${a.title}</div><div class="alert-text">${a.text}</div></div>
<button onclick="this.closest('.alert').remove()" style="background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:16px;margin-left:auto;padding:0">×</button>
</div>`).join('');
}
function renderKPIs() {
const s = AMS.stats;
const kpis = [
{ label:'Total Assets', value: s.total.toLocaleString('en-IN'), icon:'📦', color:'var(--primary)', bg:'var(--primary-glow)', change:'+3.2%', up:true, link:'assets.html' },
{ label:'Active Assets', value: s.active.toLocaleString('en-IN'),icon:'✅', color:'var(--success)', bg:'var(--success-bg)', change:'+1.4%', up:true, link:'assets.html' },
{ label:'Net Book Value', value: fmt(s.netValue), icon:'💰', color:'var(--cyan)', bg:'var(--cyan-glow)', change:'SLM/WDV applied', up:true, link:'reports.html' },
{ label:'Pending Tickets', value: s.pendingTickets, icon:'🔧', color:'var(--warning)', bg:'var(--warning-bg)', change:'Needs attention', up:false,link:'maintenance.html' },
{ label:'Under Maintenance', value: s.maintenance, icon:'🛠️', color:'var(--info)', bg:'var(--info-bg)', change:'2 critical', up:false, link:'maintenance.html' },
{ label:'Idle Assets', value: s.idle, icon:'😴', color:'var(--warning)', bg:'var(--warning-bg)', change:'Review needed', up:false, link:'assets.html' }
];
document.getElementById('kpiCards').innerHTML = kpis.map(k => `
<div class="stat-card" style="--sc-color:${k.color};cursor:pointer" onclick="window.location.href='${k.link}'">
<div class="stat-icon" style="background:${k.bg};color:${k.color}">${k.icon}</div>
<div class="stat-label">${k.label}</div>
<div class="stat-value" style="color:${k.color}">${k.value}</div>
<div class="stat-change ${k.up?'up':'down'}">${k.up?'↑':'↓'} ${k.change}</div>
</div>`).join('');
}
function renderActivity() {
document.getElementById('activityFeed').innerHTML = AMS.activityFeed.map(a => `
<div class="activity-item">
<div class="act-av" style="background:${a.color}22;color:${a.color}">${a.av}</div>
<div style="flex:1">
<div class="act-text"><strong>${escapeHtml(a.user)}</strong> ${escapeHtml(a.action)} <strong>${escapeHtml(a.target)}</strong> ${escapeHtml(a.detail)}</div>
<div class="act-time">${a.time}</div>
</div>
</div>`).join('');
}
function renderActionItems() {
const items = [
{ icon:'❄️', text:'Daikin AC service overdue by 13 days', link:'maintenance.html', priority:'danger' },
{ icon:'📄', text:'AMC renewal: Cisco contract expires Jun 30', link:'maintenance.html', priority:'warning' },
{ icon:'🔋', text:'UPS batteries awaiting parts delivery', link:'maintenance.html', priority:'info' },
{ icon:'📋', text:'Physical audit Q2 not yet scheduled', link:'reports.html', priority:'info' },
{ icon:'🖨️', text:'Ricoh photocopier out of service (TKT-005)', link:'maintenance.html', priority:'danger' }
];
document.getElementById('actionItems').innerHTML = items.map(item => `
<div class="flex items-center gap-3 mb-3" style="cursor:pointer;padding:10px;background:var(--bg-surface);border-radius:var(--radius-md);border:1px solid var(--border);transition:var(--t)" onclick="window.location.href='${item.link}'" onmouseenter="this.style.borderColor='var(--${item.priority})'" onmouseleave="this.style.borderColor='var(--border)'">
<span style="font-size:18px">${item.icon}</span>
<span style="font-size:12.5px;color:var(--text-secondary);flex:1">${item.text}</span>
<span class="badge badge-${item.priority}" style="font-size:9px">${item.priority==='danger'?'Critical':item.priority==='warning'?'High':'Info'}</span>
</div>`).join('');
}
function initCharts() {
const gridColor = 'rgba(255,255,255,0.04)';
const tickColor = '#4B5563';
// Status Chart (Bar)
new Chart(document.getElementById('statusChart'), {
type: 'bar',
data: {
labels: ['Active','Idle','Maintenance','Disposed','Lost/Stolen'],
datasets: [{
label: 'Assets',
data: [1089, 63, 47, 48, 0],
backgroundColor: ['rgba(16,185,129,.75)','rgba(245,158,11,.75)','rgba(59,130,246,.75)','rgba(75,85,99,.75)','rgba(239,68,68,.75)'],
borderColor: ['#10B981','#F59E0B','#3B82F6','#4B5563','#EF4444'],
borderWidth: 1, borderRadius: 6
}]
},
options: {
responsive:true, maintainAspectRatio:false, plugins:{ legend:{display:false} },
scales:{
x:{grid:{color:gridColor},ticks:{color:tickColor,font:{family:'Inter',size:11}}},
y:{grid:{color:gridColor},ticks:{color:tickColor,font:{family:'Inter',size:11}}}
}
}
});
// Category Doughnut
const cats = AMS.categories.slice(0,8);
new Chart(document.getElementById('categoryChart'), {
type: 'doughnut',
data: {
labels: cats.map(c=>c.name),
datasets:[{ data: cats.map(c=>c.count),
backgroundColor:['#6366F1','#06B6D4','#10B981','#F59E0B','#EF4444','#A855F7','#3B82F6','#14B8A6'],
borderColor: 'rgba(19,25,38,0)', borderWidth:2, hoverOffset:6
}]
},
options:{
responsive:true, maintainAspectRatio:false, cutout:'62%',
plugins:{ legend:{position:'right', labels:{color:'#94A3B8',font:{family:'Inter',size:11},padding:10,usePointStyle:true}} }
}
});
// Depreciation Line
const months = ['Oct','Nov','Dec','Jan','Feb','Mar','Apr','May'];
new Chart(document.getElementById('depreciationChart'), {
type: 'line',
data: {
labels: months,
datasets: [
{ label:'Gross Value', data:[45200000,45500000,45800000,46200000,46800000,47200000,47600000,48000000],
borderColor:'#6366F1', backgroundColor:'rgba(99,102,241,.08)', fill:true, tension:.4, pointRadius:3, borderWidth:2 },
{ label:'Net Book Value', data:[33200000,32800000,32300000,31900000,31400000,30900000,30500000,30100000],
borderColor:'#10B981', backgroundColor:'rgba(16,185,129,.07)', fill:true, tension:.4, pointRadius:3, borderWidth:2 }
]
},
options:{
responsive:true, maintainAspectRatio:false,
plugins:{ legend:{labels:{color:'#94A3B8',font:{family:'Inter',size:11},usePointStyle:true}} },
scales:{
x:{grid:{color:gridColor},ticks:{color:tickColor,font:{family:'Inter',size:11}}},
y:{grid:{color:gridColor},ticks:{color:tickColor,font:{family:'Inter',size:11},callback:v=>'₹'+(v/1e5).toFixed(0)+'L'}}
}
}
});
// Department Horizontal Bar
const depts = AMS.departments;
new Chart(document.getElementById('deptChart'), {
type: 'bar',
data: {
labels: depts.map(d=>d.code),
datasets:[{
label:'Assets', data: depts.map(d=>d.assetCount),
backgroundColor:'rgba(99,102,241,.6)', borderColor:'#6366F1', borderWidth:1, borderRadius:5
}]
},
options:{
indexAxis:'y', responsive:true, maintainAspectRatio:false, plugins:{legend:{display:false}},
scales:{
x:{grid:{color:gridColor},ticks:{color:tickColor,font:{family:'Inter',size:11}}},
y:{grid:{color:gridColor},ticks:{color:tickColor,font:{family:'Inter',size:11}}}
}
}
});
}
// Notification panel
function markAllRead() {
AMS.notifications.forEach(n=>n.read=true);
renderNotifPanel();
document.querySelector('.badge-dot').style.display='none';
showToast('All caught up!','All notifications marked as read','success');
}
</script>
</body>
</html>