Workforce Planning Dashboard

Workforce Planning Dashboard

Total Headcount

0

Salary Budget (Annual)

$0

Open Positions

0

Headcount vs. Target

0%

Headcount vs. Target by Role

Salary Budget by Department

Role Breakdown

No workforce data to display.

`; } renderRoleChart(metrics.roleData); renderDeptChart(metrics.deptData); }; const renderRoleChart = (roleData) => { const ctx = elements.roleChartCanvas?.getContext('2d'); if (!ctx) return; const labels = Object.keys(roleData); if (roleChart) roleChart.destroy(); roleChart = new Chart(ctx, { type: 'bar', data: { labels, datasets: [ { label: 'Current Headcount', data: labels.map(l => roleData[l].current), backgroundColor: '#a5b4fc' }, { label: 'Target Headcount', data: labels.map(l => roleData[l].target), backgroundColor: '#4f46e5' } ] }, options: { responsive: true, maintainAspectRatio: true, indexAxis: 'y' } }); }; const renderDeptChart = (deptData) => { const ctx = elements.deptChartCanvas?.getContext('2d'); if (!ctx) return; const labels = Object.keys(deptData); const data = Object.values(deptData); if (deptChart) deptChart.destroy(); deptChart = new Chart(ctx, { type: 'pie', data: { labels, datasets: [{ label: 'Salary Budget', data, backgroundColor: ['#3b82f6', '#10b981', '#ef4444', '#f59e0b', '#8b5cf6'], hoverOffset: 4 }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { position: 'top' }, tooltip: { callbacks: { label: (c) => `${c.label}: ${formatCurrency(c.raw)}` } } } } }); }; const renderConfigPanel = () => { const container = elements.editableList; if (!container) return; container.innerHTML = ''; workforceData.forEach(d => { const card = document.createElement('div'); card.className = 'grid grid-cols-1 md:grid-cols-4 gap-4 items-center bg-white p-3 rounded-lg border'; card.innerHTML = `
`; container.appendChild(card); }); }; const handleAddLog = (e) => { e.preventDefault(); try { const getNumericValue = (id) => parseInt(document.getElementById(id).value) || 0; const roleName = document.getElementById('roleName').value; const existingRecord = workforceData.find(d => d.role.toLowerCase() === roleName.toLowerCase()); if (existingRecord) { // Update existing record existingRecord.department = document.getElementById('department').value; existingRecord.current = getNumericValue('currentHeadcount'); existingRecord.target = getNumericValue('targetHeadcount'); existingRecord.salary = getNumericValue('avgSalary'); showToast('Role updated!'); } else { // Add new record const newLog = { id: workforceData.length > 0 ? Math.max(...workforceData.map(d => d.id)) + 1 : 1, role: roleName, department: document.getElementById('department').value, current: getNumericValue('currentHeadcount'), target: getNumericValue('targetHeadcount'), salary: getNumericValue('avgSalary'), }; workforceData.push(newLog); showToast('Role added!'); } elements.addForm.reset(); renderAll(); } catch (error) { console.error("Error adding record:", error); showToast("Error: Could not add record."); } }; const handleConfigListClick = (e) => { const target = e.target.closest('button'); if (!target) return; const action = target.dataset.action; const id = parseInt(target.dataset.id, 10); if (action === 'update') { const index = workforceData.findIndex(d => d.id === id); if (index === -1) return; const inputs = target.closest('.grid').querySelectorAll('input'); inputs.forEach(input => { const field = input.dataset.field; let value = input.value; if (input.type === 'number') value = parseInt(value) || 0; workforceData[index][field] = value; }); } else if (action === 'delete') { workforceData = workforceData.filter(d => d.id !== id); } renderAll(); showToast(`Record ${action}d successfully!`); }; const handleGeneratePDF = () => { try { const { jsPDF } = window.jspdf; const doc = new jsPDF(); const metrics = calculateMetrics(); doc.setFontSize(20); doc.text("Workforce Planning Report", 105, 20, null, null, 'center'); doc.setFontSize(10); doc.text(`Generated on: ${new Date().toLocaleDateString()}`, 105, 26, null, null, 'center'); doc.autoTable({ startY: 35, head: [['Metric', 'Value']], body: [ ['Total Headcount', metrics.headcount], ['Salary Budget (Annual)', formatCurrency(metrics.budget)], ['Open Positions', metrics.open], ['Headcount vs. Target', `${metrics.targetRate.toFixed(0)}%`] ], theme: 'grid' }); let finalY = doc.lastAutoTable.finalY || 60; doc.setFontSize(14); doc.text("Headcount vs. Target by Role", 14, finalY + 15); doc.addImage(elements.roleChartCanvas.toDataURL('image/png', 1.0), 'PNG', 14, finalY + 20, 180, 90); finalY += 105; doc.setFontSize(14); doc.text("Role Breakdown", 14, finalY); const sortedData = [...workforceData].sort((a, b) => b.current - a.current); const tableBody = sortedData.map(d => [d.role, d.department, d.current, d.target, formatCurrency(d.salary)]); doc.autoTable({ head: [['Role', 'Department', 'Current', 'Target', 'Avg. Salary']], body: tableBody, startY: finalY + 5, theme: 'striped', headStyles: { fillColor: [220, 38, 38] } }); doc.save('Workforce-Planning-Report.pdf'); } catch (error) { console.error("Failed to generate PDF:", error); showToast("Error: Could not generate PDF."); } }; const switchTab = (tabName) => { currentTab = tabName; Object.values(elements.tabs).forEach(tab => tab.classList.add('hidden')); Object.values(elements.tabButtons).forEach(btn => btn.classList.remove('active')); elements.tabs[tabName].classList.remove('hidden'); elements.tabButtons[tabName].classList.add('active'); updateNavButtons(); }; const navigateTabs = (direction) => { const currentIndex = tabOrder.indexOf(currentTab); const newIndex = direction === 'next' ? Math.min(currentIndex + 1, tabOrder.length - 1) : Math.max(currentIndex - 1, 0); if (newIndex !== currentIndex) switchTab(tabOrder[newIndex]); }; const updateNavButtons = () => { const currentIndex = tabOrder.indexOf(currentTab); elements.navButtons.prev.disabled = currentIndex === 0; elements.navButtons.next.disabled = currentIndex === tabOrder.length - 1; }; const showToast = (message) => { if (!elements.toast) return; elements.toast.textContent = message; elements.toast.classList.add('show'); setTimeout(() => { elements.toast.classList.remove('show'); }, 3000); } elements.addForm.addEventListener('submit', handleAddLog); elements.editableList.addEventListener('click', handleConfigListClick); elements.pdfButton.addEventListener('click', handleGeneratePDF); elements.tabButtons.dashboard.addEventListener('click', () => switchTab('dashboard')); elements.tabButtons.config.addEventListener('click', () => switchTab('config')); elements.navButtons.prev.addEventListener('click', () => navigateTabs('prev')); elements.navButtons.next.addEventListener('click', () => navigateTabs('next')); renderAll(); updateNavButtons(); });
Scroll to Top