registerPage('dashboard', async (content) => { document.getElementById('topbar-title').textContent = 'Dashboard'; const r = await api('dashboard.php'); if (!r.success) { content.innerHTML = emptyHTML('Failed to load dashboard'); return; } const { stats, booking_statuses, recent_bookings } = r; const statItems = [ { label: 'Clients', value: stats.clients, icon: '🏢', page: 'clients' }, { label: 'Employees', value: stats.employees, icon: '👷', page: 'employees' }, { label: 'Bookings', value: stats.bookings, icon: '📅', page: 'bookings' }, { label: 'Assessments', value: stats.assessments, icon: '📋', page: 'assessments' }, { label: 'Tests', value: stats.tests, icon: '📝', page: 'tests' }, { label: 'Users', value: stats.users, icon: '👤', page: 'users' }, { label: 'Certificates', value: stats.certificates, icon: '🏅', page: 'courses' }, ]; const statsHtml = statItems.map(s => `
${s.icon}
${s.value}
${s.label}
`).join(''); const recentHtml = recent_bookings.length === 0 ? emptyHTML('No recent bookings') : `
${recent_bookings.map(b => ` `).join('')}
#DateClientAssessorStatus
${b.booking_number || b.record_id} ${b.date_booked} ${b.clients_name || '—'} ${b.assessor || '—'} ${statusBadge(b.status)}
`; // Load expiry data const er = await api('expiry.php', { action: 'dashboard' }); const expiryGrouped = er.success ? er.grouped : {}; const expiryTotal = er.success ? er.total : 0; function daysBadge(d) { const n = parseInt(d); if (isNaN(n)) return `Unknown`; const abs = Math.abs(n); const months = Math.round(abs / 30.44); const label = abs < 30 ? `${abs}d` : `${months}mo`; if (n < 0) return `Expired ${label} ago`; if (n === 0) return `Expires today`; if (n <= 30) return `${label} left`; if (n <= 60) return `${label} left`; if (n <= 180) return `${label} left`; return `${label} left`; } const expiryHtml = Object.keys(expiryGrouped).length === 0 ? `
✓ Nothing expiring within 60 days
` : Object.entries(expiryGrouped).map(([client, rows], ci) => `
${client} ${rows.length} ${rows.filter(r => parseInt(r.days_left) < 0).length ? `${rows.filter(r => parseInt(r.days_left) < 0).length} expired` : ''} ${rows.filter(r => parseInt(r.days_left) >= 0 && parseInt(r.days_left) <= 30).length ? `${rows.filter(r => parseInt(r.days_left) >= 0 && parseInt(r.days_left) <= 30).length} critical` : ''}
`).join(''); // Toggle open/close for collapsible groups function initExpiry() { document.querySelectorAll('.exp-body').forEach(b => { b.style.display = 'block'; // start open }); document.querySelectorAll('[onclick*="exp-body"]').forEach(h => { h.addEventListener('click', function () { const arrow = this.querySelector('.exp-arrow'); if (arrow) arrow.style.transform = arrow.style.transform === 'rotate(180deg)' ? '' : 'rotate(180deg)'; }); }); } content.innerHTML = `
${statsHtml}

Expiring / Expired (within 60 days)

${expiryTotal} records
${expiryHtml}

Recent Bookings

${recentHtml}
`; initExpiry(); });