Total Tasks: ${member.taskCount}, Total Effort: ${member.totalEffort.toFixed(1)} units
`;
memberAssignmentsDiv.appendChild(memberDiv);
// Add to chart
const barGroup = document.createElement('div');
barGroup.className = 'chart-bar-group';
const bar = document.createElement('div');
bar.className = 'chart-bar';
const effortToDisplay = (strategy === 'byEffort' || member.totalEffort > 0) ? member.totalEffort : member.taskCount;
const barHeight = (effortToDisplay / maxEffort) * 100;
bar.style.height = `${Math.max(5, barHeight)}%`; // Min height 5% for visibility
bar.textContent = effortToDisplay.toFixed(1);
bar.title = `Effort: ${member.totalEffort.toFixed(1)}, Tasks: ${member.taskCount}`;
const label = document.createElement('div');
label.className = 'chart-label';
label.textContent = member.name;
barGroup.appendChild(bar);
barGroup.appendChild(label);
effortChartDiv.appendChild(barGroup);
});
balancedWorkloadResultsDiv.style.display = 'block';
}
clearBalancedWorkloadBtn.addEventListener('click', () => {
currentBalancedWorkload = null;
displayBalancedWorkload(null);
});
// --- PDF Export ---
downloadWorkloadPdfBtn.addEventListener('click', async () => {
if (!currentBalancedWorkload) {
alert('No workload balanced to download.');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'pt', 'a4'); // Using points for better control with html2canvas
const { strategy, memberWorkloads } = currentBalancedWorkload;
const strategyText = balancingStrategySelect.options[balancingStrategySelect.selectedIndex].text;
// Title and Info
doc.setFontSize(18);
doc.setTextColor(parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(1,3),16),
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(3,5),16),
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(5,7),16));
doc.text('Workload Balance Report', 40, 60);
doc.setFontSize(11);
doc.setTextColor(100);
doc.text(`Strategy Used: ${strategyText}`, 40, 80);
doc.text(`Generated on: ${new Date().toLocaleString()}`, 40, 95);
let yPos = 120;
// Add chart image
if (html2canvas && effortChartDiv.offsetHeight > 0) { // Check if chart is visible
try {
const canvas = await html2canvas(effortChartDiv, { scale: 2, backgroundColor: null }); // Increase scale for better quality, null bg for transparency
const imgData = canvas.toDataURL('image/png');
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth() - 80; // Page width - margins
const imgHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.setFontSize(12);
doc.setTextColor(parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(1,3),16),
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(3,5),16),
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(5,7),16));
doc.text('Workload Distribution Chart:', 40, yPos);
yPos += 15;
doc.addImage(imgData, 'PNG', 40, yPos, pdfWidth, imgHeight);
yPos += imgHeight + 20;
} catch (error) {
console.error("Error generating chart image for PDF:", error);
doc.setFontSize(10);
doc.setTextColor(255,0,0); // Red
doc.text("Chart image could not be generated.", 40, yPos);
yPos += 15;
}
}
// Add assignments per member
memberWorkloads.forEach(member => {
if (yPos > doc.internal.pageSize.getHeight() - 100) { // Check for page break before each member section
doc.addPage();
yPos = 40;
}
doc.setFontSize(14);
doc.setTextColor(parseInt(getComputedStyle(document.documentElement).getPropertyValue('--accent-color').substring(1,3),16),
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--accent-color').substring(3,5),16),
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--accent-color').substring(5,7),16));
doc.text(member.name, 40, yPos);
yPos += 10;
const summaryText = `Total Tasks: ${member.taskCount}, Total Effort: ${member.totalEffort.toFixed(1)} units`;
doc.setFontSize(10);
doc.setTextColor(50);
doc.text(summaryText, 40, yPos);
yPos += 15;
if (member.assignedTasks.length > 0) {
const taskTableBody = member.assignedTasks.map(task => [
task.description,
task.effort.toFixed(1),
task.priority
]);
doc.autoTable({
startY: yPos,
head: [['Task Description', 'Effort Units', 'Priority']],
body: taskTableBody,
theme: 'striped',
headStyles: { fillColor: [108, 117, 125] }, // --secondary-color
margin: { left: 40, right: 40 },
didParseCell: function (data) {
const priorityColorsPDF = {
high: getComputedStyle(document.documentElement).getPropertyValue('--danger-color').trim(),
medium: getComputedStyle(document.documentElement).getPropertyValue('--warning-color').trim(),
low: getComputedStyle(document.documentElement).getPropertyValue('--info-color').trim(),
};
if (data.column.index === 2 && data.cell.section === 'body') { // Priority column
const priorityValue = data.cell.raw.toString().toLowerCase();
if (priorityColorsPDF[priorityValue]) {
data.cell.styles.textColor = priorityColorsPDF[priorityValue];
data.cell.styles.fontStyle = 'bold';
}
}
}
});
yPos = doc.lastAutoTable.finalY + 15;
} else {
doc.text('No tasks assigned.', 45, yPos);
yPos += 15;
}
});
doc.save('Workload_Balance_Report.pdf');
});
// --- Initializations ---
renderTeamList();
renderTaskList();
resetTaskForm();
})();