Internal Promotion Dashboard

Promotion Rate

0%

Avg. Time to Promotion

0 yrs

Managerial Promotions

0

Avg. Performance Score

0/5

Promotion Rate by Department

Promotions by Gender

Performance vs. Tenure at Promotion

Promotion Log (YTD)

EmployeeOld RoleNew RoleDepartmentDate

Manage Promotion Records

All Promotion Records

EmployeeDepartmentNew RoleAction

Error: A required library is missing.

'; return; } // --- DATA MANAGEMENT --- const TOTAL_EMPLOYEES = 250; let promotions = JSON.parse(localStorage.getItem('ipd_promotions')) || []; const getSampleData = () => [ { id: 1, name: 'Jessica Miller', gender: 'Female', department: 'Engineering', oldRole: 'Software Engineer II', newRole: 'Senior Software Engineer', isManager: false, tenure: 3.5, performance: 4.8, date: '2025-02-15' }, { id: 2, name: 'David Wilson', gender: 'Male', department: 'Sales', oldRole: 'Sales Associate', newRole: 'Account Executive', isManager: false, tenure: 2.1, performance: 4.5, date: '2025-03-01' }, { id: 3, name: 'Sarah Davis', gender: 'Female', department: 'Marketing', oldRole: 'Marketing Specialist', newRole: 'Marketing Manager', isManager: true, tenure: 4.0, performance: 4.6, date: '2025-04-20' }, { id: 4, name: 'John Smith', gender: 'Male', department: 'Engineering', oldRole: 'Senior Software Engineer', newRole: 'Engineering Manager', isManager: true, tenure: 5.2, performance: 4.9, date: '2025-05-10' }, { id: 5, name: 'Michael Brown', gender: 'Male', department: 'Operations', oldRole: 'Operations Analyst', newRole: 'Senior Operations Analyst', isManager: false, tenure: 1.8, performance: 4.2, date: '2025-06-05' }, ]; if (promotions.length === 0) promotions = getSampleData(); const saveState = () => localStorage.setItem('ipd_promotions', JSON.stringify(promotions)); // --- CHART INSTANCES & UTILITIES --- let deptChart, genderChart, perfTenureChart; const tabButtons = document.querySelectorAll('.ipd-tab-button'); const tabContents = document.querySelectorAll('.ipd-tab-content'); const nextBtn = document.getElementById('ipd-next-btn'); const prevBtn = document.getElementById('ipd-prev-btn'); // --- RENDER FUNCTIONS --- const renderAll = () => { try { renderKPIs(); renderDeptChart(); renderGenderChart(); renderPerfTenureChart(); renderLogTable(); renderManageTable(); updateNavButtons(); } catch(error) { console.error("Dashboard rendering failed:", error); } }; const renderKPIs = () => { const promotionRate = (promotions.length / TOTAL_EMPLOYEES) * 100; const avgTimeToPromo = promotions.length > 0 ? promotions.reduce((sum,p) => sum + p.tenure, 0) / promotions.length : 0; const managerPromos = promotions.filter(p => p.isManager).length; const avgPerfScore = promotions.length > 0 ? promotions.reduce((sum,p) => sum + p.performance, 0) / promotions.length : 0; document.getElementById('ipd-promo-rate-kpi').textContent = `${promotionRate.toFixed(1)}%`; document.getElementById('ipd-avg-time-kpi').textContent = `${avgTimeToPromo.toFixed(1)} yrs`; document.getElementById('ipd-manager-promo-kpi').textContent = managerPromos; document.getElementById('ipd-perf-score-kpi').textContent = `${avgPerfScore.toFixed(1)}/5`; }; const renderDeptChart = () => { // Assuming a static headcount per department for rate calculation const deptHeadcounts = { 'Engineering': 80, 'Sales': 60, 'Marketing': 40, 'Operations': 50, 'HR': 20 }; const promosByDept = promotions.reduce((acc, p) => { acc[p.department] = (acc[p.department] || 0) + 1; return acc; }, {}); const rates = Object.keys(deptHeadcounts).map(dept => { const rate = ((promosByDept[dept] || 0) / deptHeadcounts[dept]) * 100; return rate.toFixed(1); }); const options = { chart: { type: 'bar', height: 350, toolbar: { show: false } }, series: [{ name: 'Promotion Rate', data: rates }], xaxis: { categories: Object.keys(deptHeadcounts) }, yaxis: { labels: { formatter: val => `${val}%` } }, plotOptions: { bar: { horizontal: false, distributed: true } }, legend: { show: false } }; if(deptChart) deptChart.destroy(); document.querySelector("#ipd-dept-chart").innerHTML = ''; deptChart = new ApexCharts(document.querySelector("#ipd-dept-chart"), options); deptChart.render(); }; const renderGenderChart = () => { const promosByGender = promotions.reduce((acc, p) => { acc[p.gender] = (acc[p.gender] || 0) + 1; return acc; }, {}); const options = { chart: { type: 'donut', height: 350 }, series: Object.values(promosByGender), labels: Object.keys(promosByGender), legend: { position: 'bottom' } }; if(genderChart) genderChart.destroy(); document.querySelector("#ipd-gender-chart").innerHTML = ''; genderChart = new ApexCharts(document.querySelector("#ipd-gender-chart"), options); genderChart.render(); }; const renderPerfTenureChart = () => { const seriesData = promotions.map(p => ({ x: p.tenure, y: p.performance })); const options = { chart: { type: 'scatter', height: 350, zoom: { enabled: false }, toolbar: { show: false } }, series: [{ name: 'Promotion', data: seriesData }], xaxis: { type: 'numeric', title: { text: 'Tenure (Years)' }, tickAmount: 5 }, yaxis: { tickAmount: 5, min: 3.5, max: 5, title: { text: 'Performance Score' } }, tooltip: { y: { formatter: (val) => val.toFixed(1) }, x: { formatter: (val) => `${val.toFixed(1)} years` } } }; if(perfTenureChart) perfTenureChart.destroy(); document.querySelector("#ipd-perf-tenure-chart").innerHTML = ''; perfTenureChart = new ApexCharts(document.querySelector("#ipd-perf-tenure-chart"), options); perfTenureChart.render(); }; const renderLogTable = () => { const tbody = document.getElementById('ipd-log-tbody'); tbody.innerHTML = ''; promotions.sort((a,b) => new Date(b.date) - new Date(a.date)).forEach(p => { tbody.innerHTML += `${p.name}${p.oldRole}${p.newRole}${p.department}${p.date}`; }); }; const renderManageTable = () => { const tbody = document.getElementById('ipd-manage-tbody'); tbody.innerHTML = ''; promotions.forEach(p => { tbody.innerHTML += `${p.name}${p.department}${p.newRole}`; }); const depts = [...new Set(promotions.map(p => p.department))]; document.getElementById('ipd-dept-list').innerHTML = depts.map(d => ``).join(''); }; // --- EVENT HANDLING --- const switchTab = (tabId) => { tabContents.forEach(c => c.style.display = 'none'); tabButtons.forEach(b => b.classList.remove('active')); const activeContent = document.getElementById(tabId); const activeButton = document.querySelector(`.ipd-tab-button[data-tab="${tabId}"]`); if (activeContent && activeButton) { activeContent.style.display = 'block'; activeButton.classList.add('active'); } updateNavButtons(); }; const updateNavButtons = () => { const i = [...tabButtons].findIndex(b => b.classList.contains('active')); prevBtn.disabled = i === 0; nextBtn.disabled = i === tabButtons.length - 1; }; tabButtons.forEach(b => b.addEventListener('click', () => switchTab(b.dataset.tab))); nextBtn.addEventListener('click', () => { const i = [...tabButtons].findIndex(b=>b.classList.contains('active')); if (i < tabButtons.length - 1) switchTab(tabButtons[i+1].dataset.tab); }); prevBtn.addEventListener('click', () => { const i = [...tabButtons].findIndex(b=>b.classList.contains('active')); if (i > 0) switchTab(tabButtons[i-1].dataset.tab); }); document.getElementById('ipd-promo-form').addEventListener('submit', e => { e.preventDefault(); promotions.push({ id: (promotions.length > 0 ? Math.max(...promotions.map(p => p.id)) : 0) + 1, name: document.getElementById('ipd-form-name').value, gender: document.getElementById('ipd-form-gender').value, department: document.getElementById('ipd-form-dept').value, oldRole: document.getElementById('ipd-form-old-role').value, newRole: document.getElementById('ipd-form-new-role').value, isManager: document.getElementById('ipd-form-is-manager').checked, tenure: parseFloat(document.getElementById('ipd-form-tenure').value), performance: parseFloat(document.getElementById('ipd-form-perf-score').value), date: document.getElementById('ipd-form-date').value }); saveState(); renderAll(); e.target.reset(); switchTab('ipd-dashboard-tab'); }); document.getElementById('ipd-manage-tbody').addEventListener('click', e => { if(e.target.tagName === 'BUTTON') { const id = parseInt(e.target.dataset.id); if(confirm('Are you sure?')) { promotions = promotions.filter(p => p.id !== id); saveState(); renderAll(); } } }); // --- PDF EXPORT --- document.getElementById('ipd-download-pdf-btn').addEventListener('click', function() { const btn = this; btn.textContent = 'Generating...'; btn.disabled = true; const pdfCaptureArea = document.getElementById('ipd-pdf-capture-area'); html2canvas(pdfCaptureArea, { scale: 2 }).then(canvas => { const doc = new jsPDF(); const imgData = canvas.toDataURL('image/png'); doc.setFontSize(18); doc.text('Internal Promotion Report', 14, 22); doc.autoTable({ html: '.ipd-table', startY: 30, theme: 'striped' }); doc.save('Internal_Promotion_Report.pdf'); }).finally(() => { btn.textContent = 'Download Report'; btn.disabled = false; }); }); // --- INITIALIZATION --- renderAll(); });
Scroll to Top