marco.asseto.prototype/renewals.html

314 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Renewals & Reminders | 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">Renewals &amp; Expirations <span class="topbar-sub">— V1 Alert Center</span></div></div>
<div class="topbar-actions">
<a href="assets.html" class="btn btn-secondary btn-sm">← Back to Assets</a>
<a href="index.html" class="icon-btn" title="Logout"><i data-lucide="log-out"></i></a>
</div>
</header>
<main class="content animate-fadein">
<!-- Stats row -->
<div class="grid-4 mb-4" id="statsRow">
<div class="stat-card" style="--sc-color:var(--primary)">
<div class="stat-icon" style="background:var(--primary-glow);color:var(--primary)">🔑</div>
<div class="stat-label">Software Expirations</div>
<div class="stat-value" id="countSoftware">0</div>
</div>
<div class="stat-card" style="--sc-color:var(--warning)">
<div class="stat-icon" style="background:var(--warning-bg);color:var(--warning)">🔌</div>
<div class="stat-label">AMC Contracts Expiring</div>
<div class="stat-value" id="countAMC">0</div>
</div>
<div class="stat-card" style="--sc-color:var(--info)">
<div class="stat-icon" style="background:var(--info-bg);color:var(--info)">🛡️</div>
<div class="stat-label">Warranty Near End</div>
<div class="stat-value" id="countWarranty">0</div>
</div>
<div class="stat-card" style="--sc-color:var(--danger)">
<div class="stat-icon" style="background:var(--danger-bg);color:var(--danger)"></div>
<div class="stat-label">Overdue Maintenance</div>
<div class="stat-value" id="countPM">0</div>
</div>
</div>
<!-- Tabs -->
<div class="tab-container">
<div class="tabs" data-group="renew">
<button class="tab-btn active" data-tab="tab-software" data-group="renew">🔑 Software Licenses</button>
<button class="tab-btn" data-tab="tab-amc" data-group="renew">🔌 AMC Agreements</button>
<button class="tab-btn" data-tab="tab-warranty" data-group="renew">🛡️ Hardware Warranties</button>
</div>
<!-- Software Licenses Tab -->
<div class="tab-content active" id="tab-software" data-group="renew">
<div class="table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Software Asset</th>
<th>Category</th>
<th>License Key</th>
<th>Cost (Annual)</th>
<th>Expiry Date</th>
<th>Status</th>
<th>Project</th>
<th style="width:120px">Actions</th>
</tr>
</thead>
<tbody id="softwareTbody"></tbody>
</table>
</div>
</div>
<!-- AMC Tab -->
<div class="tab-content" id="tab-amc" data-group="renew">
<div class="table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Contract ID</th>
<th>Vendor</th>
<th>Scope of Cover</th>
<th>Annual Value</th>
<th>Valid From</th>
<th>Expiry Date</th>
<th>Status</th>
<th style="width:120px">Actions</th>
</tr>
</thead>
<tbody id="amcTbody"></tbody>
</table>
</div>
</div>
<!-- Warranties Tab -->
<div class="tab-content" id="tab-warranty" data-group="renew">
<div class="table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Asset Name</th>
<th>Serial Number</th>
<th>Category</th>
<th>Purchase Cost</th>
<th>Warranty Expiry</th>
<th>Days Left</th>
<th>Assignee</th>
<th style="width:120px">Actions</th>
</tr>
</thead>
<tbody id="warrantyTbody"></tbody>
</table>
</div>
</div>
</div>
</main>
</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', () => {
renderRenewals();
initTabs();
});
function calculateDaysLeft(dateStr) {
if (!dateStr || dateStr === 'N/A') return Infinity;
const diff = new Date(dateStr) - new Date();
return Math.ceil(diff / (1000 * 60 * 60 * 24));
}
function renderRenewals() {
if (!window.AMS) return;
// Filter Software Licenses (Digital assets near renewal)
const software = AMS.assets.filter(a => a.type === 'digital');
// Filter AMCs
const amcs = AMS.amcContracts;
// Filter Warranties
const warranties = AMS.assets.filter(a => a.type === 'physical' && a.warranty !== 'N/A');
// Filter Overdue PMs
const overduePMs = AMS.pmSchedule.filter(pm => pm.status === 'Overdue');
// Set top counter stats
document.getElementById('countSoftware').textContent = software.length;
document.getElementById('countAMC').textContent = amcs.length;
document.getElementById('countWarranty').textContent = warranties.filter(w => calculateDaysLeft(w.warranty) < 180).length;
document.getElementById('countPM').textContent = overduePMs.length;
// Render Software Tbody
const softTbody = document.getElementById('softwareTbody');
if (software.length === 0) {
softTbody.innerHTML = `<tr><td colspan="8"><div class="empty-state"><div class="empty-icon">🔑</div><div class="empty-title">No digital software assets found</div></div></td></tr>`;
} else {
softTbody.innerHTML = software.map(s => {
const days = calculateDaysLeft(s.warranty);
const isExpiring = days < 60;
const statusBadge = isExpiring ? 'badge-danger' : 'badge-success';
const statusText = isExpiring ? `Expiring in ${days}d` : 'Active';
return `
<tr>
<td><div style="font-weight:600">${s.name}</div><div style="font-size:11px;color:var(--text-muted)">${s.id}</div></td>
<td><span class="badge badge-neutral" style="font-size:10px">${s.cat}</span></td>
<td><code style="font-size:11px;color:var(--primary-light)">${s.licenseKey || 'N/A'}</code></td>
<td style="font-weight:600">${fmt(s.cost)}</td>
<td style="color:${isExpiring ? 'var(--danger)' : 'var(--text-primary)'};font-weight:600">${fmtDate(s.warranty)}</td>
<td><span class="badge ${statusBadge}">${statusText}</span></td>
<td><span class="badge badge-primary" style="font-size:10.5px">📁 ${s.project}</span></td>
<td>
<button class="btn btn-primary btn-sm" onclick="renewSoftware('${s.id}')">🔄 Renew</button>
</td>
</tr>
`;
}).join('');
}
// Render AMC Tbody
const amcTbody = document.getElementById('amcTbody');
if (amcs.length === 0) {
amcTbody.innerHTML = `<tr><td colspan="8"><div class="empty-state"><div class="empty-icon">🔌</div><div class="empty-title">No active AMC contracts</div></div></td></tr>`;
} else {
amcTbody.innerHTML = amcs.map(c => {
const days = calculateDaysLeft(c.end);
const isExpiring = days < 60;
const statusBadge = isExpiring ? 'badge-danger' : 'badge-success';
const statusText = isExpiring ? `Expiring in ${days}d` : 'Active';
return `
<tr>
<td><code style="font-size:11px;color:var(--primary-light)">${c.id}</code></td>
<td style="font-weight:600">${c.vendor}</td>
<td style="font-size:12px;color:var(--text-secondary)">${c.scope}</td>
<td style="font-weight:600">${fmt(c.value)}</td>
<td>${fmtDate(c.start)}</td>
<td style="color:${isExpiring ? 'var(--danger)' : 'var(--text-primary)'};font-weight:600">${fmtDate(c.end)}</td>
<td><span class="badge ${statusBadge}">${statusText}</span></td>
<td>
<button class="btn btn-warning btn-sm" onclick="renewAMC('${c.id}')" style="color:#000">🔄 Renew</button>
</td>
</tr>
`;
}).join('');
}
// Render Warranties Tbody
const warrTbody = document.getElementById('warrantyTbody');
const criticalWarranties = warranties.filter(w => calculateDaysLeft(w.warranty) < 365); // showing those expiring in a year
if (criticalWarranties.length === 0) {
warrTbody.innerHTML = `<tr><td colspan="8"><div class="empty-state"><div class="empty-icon">🛡️</div><div class="empty-title">No physical assets near warranty expiry</div></div></td></tr>`;
} else {
warrTbody.innerHTML = criticalWarranties.map(w => {
const days = calculateDaysLeft(w.warranty);
const isCritical = days < 90;
const color = isCritical ? 'var(--danger)' : 'var(--text-secondary)';
return `
<tr>
<td><div style="font-weight:600">${w.name}</div><div style="font-size:11px;color:var(--text-muted)">${w.id}</div></td>
<td><code style="font-size:11px">${w.serial}</code></td>
<td><span class="badge badge-neutral" style="font-size:10px">${w.cat}</span></td>
<td style="font-weight:600">${fmt(w.cost)}</td>
<td style="color:${color};font-weight:600">${fmtDate(w.warranty)}</td>
<td style="color:${color};font-weight:600">${days <= 0 ? 'Expired' : `${days} days`}</td>
<td>${w.assignee}</td>
<td>
<button class="btn btn-secondary btn-sm" onclick="extendWarranty('${w.id}')">🛡️ Extend</button>
</td>
</tr>
`;
}).join('');
}
}
function renewSoftware(id) {
const s = AMS.assets.find(x => x.id === id);
if (s) {
const oldW = new Date(s.warranty);
oldW.setFullYear(oldW.getFullYear() + 1);
s.warranty = oldW.toISOString().split('T')[0];
AMS.activityFeed.unshift({
user: AMS.currentUser.name,
av: AMS.currentUser.avatar,
color: '#A855F7',
action: 'renewed software license',
target: s.name,
detail: `by +1 year (new renewal: ${fmtDate(s.warranty)})`,
time: 'Just now'
});
AMS.save();
showToast('License Renewed', `${s.name} extended by 1 year.`, 'success');
renderRenewals();
}
}
function renewAMC(id) {
const c = AMS.amcContracts.find(x => x.id === id);
if (c) {
const oldE = new Date(c.end);
oldE.setFullYear(oldE.getFullYear() + 1);
c.end = oldE.toISOString().split('T')[0];
c.status = 'Active';
AMS.activityFeed.unshift({
user: AMS.currentUser.name,
av: AMS.currentUser.avatar,
color: '#F59E0B',
action: 'renewed AMC contract',
target: c.vendor,
detail: `for ${c.scope} (+1 year)`,
time: 'Just now'
});
AMS.save();
showToast('Contract Renewed', `AMC with ${c.vendor} extended by 1 year.`, 'success');
renderRenewals();
}
}
function extendWarranty(id) {
const w = AMS.assets.find(x => x.id === id);
if (w) {
const oldW = new Date(w.warranty);
oldW.setFullYear(oldW.getFullYear() + 1);
w.warranty = oldW.toISOString().split('T')[0];
AMS.activityFeed.unshift({
user: AMS.currentUser.name,
av: AMS.currentUser.avatar,
color: '#10B981',
action: 'extended hardware warranty',
target: w.name,
detail: `by +1 year (new expiry: ${fmtDate(w.warranty)})`,
time: 'Just now'
});
AMS.save();
showToast('Warranty Extended', `${w.name} warranty extended by 1 year.`, 'success');
renderRenewals();
}
}
</script>
</body>
</html>