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.

      '; 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