Employee Productivity Benchmarking Tool

Employee Productivity Benchmarking

Setup Benchmark Context

Define Productivity Metrics

Defined Metrics:

    Input Employee Performance Data

    Entered Employee Data:

      Benchmark Report

      Your benchmark report will appear here.

      Please define metrics in Tab 2 first.

      '; return; } let gridColumnCount = epbtBenchmarkSetup.metrics.length > 2 ? 2 : epbtBenchmarkSetup.metrics.length; if (gridColumnCount === 0) gridColumnCount = 1; // Avoid 0 columns container.style.gridTemplateColumns = `repeat(${gridColumnCount}, 1fr)`; epbtBenchmarkSetup.metrics.forEach(metric => { const group = document.createElement('div'); group.className = 'epbt-form-group'; group.innerHTML = ` `; container.appendChild(group); }); } function epbtAddEmployeeData() { const identifier = document.getElementById('epbt-employee-identifier').value.trim(); if (!identifier) { alert('Employee identifier is required.'); return; } if (epbtBenchmarkSetup.metrics.length === 0) { alert('Please define metrics first (Tab 2).'); return;} const metricValues = {}; let hasValues = false; epbtBenchmarkSetup.metrics.forEach(metric => { const inputEl = document.getElementById(`emp-metric-${metric.id}`); const val = parseFloat(inputEl.value); if (!isNaN(val)) { metricValues[metric.id] = val; hasValues = true; } else { metricValues[metric.id] = null; // Or some placeholder for not entered } }); if (!hasValues) { alert('Please enter at least one metric value for the employee.'); return;} epbtEmployeeData.push({ id: epbtNextEmployeeEntryId++, identifier, metricValues }); epbtRenderEmployeeDataList(); document.getElementById('epbt-employee-identifier').value = ''; epbtBenchmarkSetup.metrics.forEach(metric => { document.getElementById(`emp-metric-${metric.id}`).value = ''; }); } function epbtDeleteEmployeeData(empEntryId) { epbtEmployeeData = epbtEmployeeData.filter(emp => emp.id !== empEntryId); epbtRenderEmployeeDataList(); } function epbtRenderEmployeeDataList() { const listEl = document.getElementById('epbt-employee-data-list'); if (!listEl) return; listEl.innerHTML = ''; if (epbtEmployeeData.length === 0) { listEl.innerHTML = '
    • No employee data entered yet.
    • '; return; } epbtEmployeeData.forEach(emp => { let details = ''; epbtBenchmarkSetup.metrics.forEach(metricDef => { const val = emp.metricValues[metricDef.id]; details += `${metricDef.name}: ${val !== null && val !== undefined ? val : 'N/A'} ${metricDef.unit}; `; }); const item = document.createElement('li'); item.className = 'epbt-item'; item.innerHTML = ` ${emp.identifier}
      ${details}
      `; listEl.appendChild(item); }); } // --- Tab 4: Benchmark Report --- function epbtGenerateReport() { epbtBenchmarkSetup.periodLabel = document.getElementById('epbt-period-label').value.trim() || "N/A Period"; epbtBenchmarkSetup.teamName = document.getElementById('epbt-team-name').value.trim() || "N/A Team"; const reportArea = document.getElementById('epbt-report-display-area'); if (!reportArea) return; reportArea.innerHTML = ''; if (epbtBenchmarkSetup.metrics.length === 0) { reportArea.innerHTML = '

      No metrics defined. Please define metrics in Tab 2.

      '; return; } if (epbtEmployeeData.length === 0) { reportArea.innerHTML = '

      No employee data entered. Please input data in Tab 3.

      '; return; } epbtGeneratedReportData = { // For PDF setup: JSON.parse(JSON.stringify(epbtBenchmarkSetup)), employees: JSON.parse(JSON.stringify(epbtEmployeeData)), metricDetails: [] }; let overallReportHTML = `

      Benchmark Context

      Team/Project: ${epbtBenchmarkSetup.teamName}

      Reporting Period: ${epbtBenchmarkSetup.periodLabel}

      `; epbtBenchmarkSetup.metrics.forEach(metric => { let validValuesCount = 0; let sumOfValues = 0; epbtEmployeeData.forEach(emp => { const val = emp.metricValues[metric.id]; if (val !== null && val !== undefined && !isNaN(val)) { sumOfValues += val; validValuesCount++; } }); const teamAverage = validValuesCount > 0 ? (sumOfValues / validValuesCount) : 0; let metricDetailForPDF = { name: metric.name, unit: metric.unit, target: metric.target, lowerIsBetter: metric.lowerIsBetter, teamAverage: teamAverage.toFixed(2), employeePerformances: [] }; overallReportHTML += `

      Metric: ${metric.name}

      Unit: ${metric.unit} | Target/Benchmark: ${metric.target} ${metric.lowerIsBetter ? '(Lower is Better)' : ''} | Team Average: ${teamAverage.toFixed(2)}

      `; epbtEmployeeData.forEach(emp => { const actualValue = emp.metricValues[metric.id]; let deviation = 'N/A', percentVsTarget = 'N/A', statusClass = '', barPercent = 0; if (actualValue !== null && actualValue !== undefined && !isNaN(actualValue)) { deviation = (actualValue - metric.target).toFixed(2); if (metric.target !== 0) { percentVsTarget = ((actualValue / metric.target) * 100).toFixed(1) + '%'; barPercent = Math.min(150, Math.max(0, (actualValue / metric.target) * 100)); // Cap bar at 150% for display } else if (actualValue > 0) { // Target is 0, actual is positive percentVsTarget = 'High (Target was 0)'; barPercent = 100; // Show full bar if target 0 and actual > 0 } else { // Target is 0, actual is 0 percentVsTarget = 'Met (Target was 0)'; barPercent = 100; } if (metric.lowerIsBetter) { barPercent = Math.min(150, Math.max(0, (metric.target / actualValue) * 100)); // Inverted logic for bar if (actualValue < metric.target) statusClass = 'epbt-status-exceeds'; // Good else if (actualValue === metric.target) statusClass = 'epbt-status-meets'; // Okay else statusClass = 'epbt-status-below'; // Bad } else { if (actualValue > metric.target) statusClass = 'epbt-status-exceeds'; else if (actualValue === metric.target) statusClass = 'epbt-status-meets'; else statusClass = 'epbt-status-below'; } } let barFillClass = 'epbt-progress-bar-fill'; if(metric.lowerIsBetter){ if (actualValue > metric.target * 1.1) barFillClass += ' well-below'; // Significantly worse else if (actualValue > metric.target) barFillClass += ' below'; // Worse } else { if (actualValue < metric.target * 0.75) barFillClass += ' well-below'; // Significantly worse else if (actualValue < metric.target) barFillClass += ' below'; // Worse } metricDetailForPDF.employeePerformances.push({ identifier: emp.identifier, actual: actualValue, deviation, percentVsTarget, statusClass }); overallReportHTML += ` `; }); overallReportHTML += `
      EmployeeActualDeviation% vs TargetPerformance Bar
      ${emp.identifier} ${actualValue !== null && actualValue !== undefined ? actualValue : 'N/A'} ${deviation} ${percentVsTarget}
      ${barPercent > 10 ? barPercent.toFixed(0) + '%' : ''}
      `; epbtGeneratedReportData.metricDetails.push(metricDetailForPDF); }); reportArea.innerHTML = overallReportHTML; document.getElementById('epbt-pdf-download').style.display = 'inline-block'; } function epbtDownloadPDF() { if (!jsPDF_constructor_epbt || !epbtGeneratedReportData) { alert("Please generate the report first, or PDF library is not loaded."); return; } const { setup, metricDetails } = epbtGeneratedReportData; const doc = new jsPDF_constructor_epbt('p', 'pt', 'a4'); const FONT_FAMILY = "helvetica"; const ACCENT_COLOR_HEX = getComputedStyle(document.documentElement).getPropertyValue('--accent-color').trim(); const PRIMARY_TEXT_COLOR_HEX = getComputedStyle(document.documentElement).getPropertyValue('--primary-text').trim(); const BUTTON_TEXT_COLOR_HEX = getComputedStyle(document.documentElement).getPropertyValue('--button-text-color').trim(); let yPos = 40; const pageWidth = doc.internal.pageSize.getWidth(); const margin = 30; // Smaller margin for more content const usableWidth = pageWidth - (2 * margin); doc.setFillColor(ACCENT_COLOR_HEX); doc.rect(0, 0, pageWidth, yPos + 10, 'F'); doc.setFont(FONT_FAMILY, "bold"); doc.setFontSize(16); doc.setTextColor(BUTTON_TEXT_COLOR_HEX); doc.text("Employee Productivity Benchmark Report", pageWidth / 2, yPos, { align: "center" }); yPos += 20; doc.setFont(FONT_FAMILY, "normal"); doc.setFontSize(10); doc.setTextColor(PRIMARY_TEXT_COLOR_HEX); doc.text(`Team/Project: ${setup.teamName}`, margin, yPos); yPos += 12; doc.text(`Reporting Period: ${setup.periodLabel}`, margin, yPos); yPos += 12; const genDate = new Date(2025, 5, 18).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }); // Using context date doc.text(`Generated on: ${genDate}`, margin, yPos); yPos += 20; metricDetails.forEach(metric => { if (yPos > doc.internal.pageSize.getHeight() - 100) { doc.addPage(); yPos = margin; } doc.setFont(FONT_FAMILY, "bold"); doc.setFontSize(12); doc.setTextColor(ACCENT_COLOR_HEX); doc.text(`Metric: ${metric.name}`, margin, yPos); yPos += 15; doc.setFont(FONT_FAMILY, "normal"); doc.setFontSize(9); doc.text(`Unit: ${metric.unit} | Target: ${metric.target} ${metric.lowerIsBetter ? '(Lower is Better)' : ''} | Team Avg: ${metric.teamAverage}`, margin, yPos); yPos += 15; const tableColumns = ["Employee", "Actual", "Deviation", "% vs Target"]; const tableRows = metric.employeePerformances.map(empPerf => [ empPerf.identifier, empPerf.actual !== null && empPerf.actual !== undefined ? empPerf.actual : 'N/A', empPerf.deviation, empPerf.percentVsTarget ]); if (jsPDF_autoTable_plugin_epbt) { doc.autoTable({ head: [tableColumns], body: tableRows, startY: yPos, theme: 'grid', margin: {left: margin, right: margin}, headStyles: { fillColor: ACCENT_COLOR_HEX, textColor: BUTTON_TEXT_COLOR_HEX, fontSize: 8, fontStyle:'bold' }, styles: { fontSize: 8, cellPadding: 2, overflow: 'linebreak' }, didParseCell: function(data) { // Simple status coloring in PDF if (data.section === 'body') { const empPerf = metric.employeePerformances[data.row.index]; if (empPerf && empPerf.statusClass) { if(empPerf.statusClass.includes('exceeds')) data.cell.styles.fillColor = '#d4edda'; // green else if(empPerf.statusClass.includes('below')) data.cell.styles.fillColor = '#f8d7da'; // red else if(empPerf.statusClass.includes('meets')) data.cell.styles.fillColor = '#fff3cd'; // yellow } } } }); yPos = doc.lastAutoTable.finalY + 15; } else { // Fallback for no autoTable tableRows.forEach(row => { if (yPos > doc.internal.pageSize.getHeight() - 30) { doc.addPage(); yPos = margin; } doc.text(row.join(" | "), margin, yPos); yPos += 10; }); yPos += 10; } }); // Footer const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); const pageHeight = doc.internal.pageSize.getHeight(); doc.setLineWidth(0.5); doc.setDrawColor(ACCENT_COLOR_HEX); doc.line(margin, pageHeight - 25, pageWidth - margin, pageHeight - 25); doc.setFont(FONT_FAMILY, "normal"); doc.setFontSize(8); doc.setTextColor(PRIMARY_TEXT_COLOR_HEX); doc.text(`Page ${i} of ${pageCount}`, pageWidth / 2, pageHeight - 15, { align: "center" }); } doc.save(`Employee_Benchmark_Report_${setup.periodLabel.replace(/[^a-z0-9]/gi, '_')}.pdf`); }
      Scroll to Top