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)}
| Employee | Actual | Deviation | % vs Target | Performance Bar |
`;
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 += `
| ${emp.identifier} |
${actualValue !== null && actualValue !== undefined ? actualValue : 'N/A'} |
${deviation} |
${percentVsTarget} |
${barPercent > 10 ? barPercent.toFixed(0) + '%' : ''}
|
`;
});
overallReportHTML += `
`;
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`);
}