Quantitative Metrics
Tasks: ${tasksCompleted} / ${tasksPlanned} completed (${tpdDashboardSnapshot.tasksOverdue} overdue)
${ (effortPlanned > 0 || effortCompleted > 0) ? `
Effort (${effortUnit}): ${effortCompleted} / ${effortPlanned} completed
` : ''}
Blockers: ${blockersResolved} / ${blockersIdentified} resolved
${blockerResolutionRate}%
Team Morale: ${tpdDashboardSnapshot.teamMorale} / 5
Qualitative Insights
Major Achievements:
${tpdDashboardSnapshot.achievements}
Key Challenges:
${tpdDashboardSnapshot.challenges}
Lessons Learned:
${tpdDashboardSnapshot.lessonsLearned}
Focus for Next Period:
${tpdDashboardSnapshot.nextPeriodFocus}
`;
document.getElementById('tpd-pdf-download').style.display = 'inline-block';
}
function tpdDownloadPDF() {
if (Object.keys(tpdDashboardSnapshot).length === 0) {
alert("Please generate the dashboard first."); return;
}
const {
teamName, reportingPeriod, periodGoals,
tasksPlanned, tasksCompleted, tasksOverdue,
effortUnit, effortPlanned, effortCompleted,
blockersIdentified, blockersResolved, teamMorale,
achievements, challenges, lessonsLearned, nextPeriodFocus,
calculatedRates
} = tpdDashboardSnapshot;
const doc = new jsPDF('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 SECTION_TITLE_COLOR_HEX = getComputedStyle(document.documentElement).getPropertyValue('--section-title-color').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 = 40;
const usableWidth = pageWidth - (2 * margin);
// PDF Header
doc.setFillColor(ACCENT_COLOR_HEX);
doc.rect(0, 0, pageWidth, yPos + 10, 'F');
doc.setFont(FONT_FAMILY, "bold");
doc.setFontSize(18);
doc.setTextColor(BUTTON_TEXT_COLOR_HEX);
doc.text("Team Productivity Dashboard", pageWidth / 2, yPos, { align: "center" });
yPos += 25;
// Context
doc.setFont(FONT_FAMILY, "normal");
doc.setFontSize(10);
doc.setTextColor(PRIMARY_TEXT_COLOR_HEX);
doc.text(`Team/Project: ${teamName}`, margin, yPos); yPos += 12;
doc.text(`Reporting Period: ${reportingPeriod}`, margin, yPos); yPos += 12;
const genDate = new Date(2025, 4, 17).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });
doc.text(`Generated on: ${genDate}`, margin, yPos); yPos += 20;
doc.setFont(FONT_FAMILY, "bold"); doc.setFontSize(12); doc.setTextColor(SECTION_TITLE_COLOR_HEX);
doc.text("Key Goals for this Period:", margin, yPos); yPos += 15;
doc.setFont(FONT_FAMILY, "normal"); doc.setFontSize(10); doc.setTextColor(PRIMARY_TEXT_COLOR_HEX);
const goalsLines = doc.splitTextToSize(periodGoals, usableWidth);
doc.text(goalsLines, margin, yPos); yPos += (goalsLines.length * 12) + 10;
// Quantitative Metrics
if (yPos > doc.internal.pageSize.getHeight() - 150) { doc.addPage(); yPos = margin; }
doc.setFont(FONT_FAMILY, "bold"); doc.setFontSize(14); doc.setTextColor(SECTION_TITLE_COLOR_HEX);
doc.text("Quantitative Metrics", margin, yPos); yPos += 20;
const metricsData = [
["Metric", "Planned", "Completed", "Details/Rate"],
["Tasks", tasksPlanned, tasksCompleted, `${tasksOverdue} Overdue, ${calculatedRates.taskCompletionRate}% Complete`],
(effortPlanned > 0 || effortCompleted > 0) ? ["Effort ("+effortUnit+")", effortPlanned, effortCompleted, `${calculatedRates.effortCompletionRate}% Complete`] : null,
["Blockers", blockersIdentified, blockersResolved, `${calculatedRates.blockerResolutionRate}% Resolved`],
["Team Morale", "-", "-", `${teamMorale} / 5 Score`]
].filter(row => row !== null); // Filter out null row for effort if not used
doc.autoTable({
startY: yPos,
head: [metricsData[0]], body: metricsData.slice(1),
theme: 'grid', margin: {left: margin, right: margin},
headStyles: { fillColor: ACCENT_COLOR_HEX, textColor: BUTTON_TEXT_COLOR_HEX, fontStyle: 'bold', fontSize:9 },
styles: { font: FONT_FAMILY, fontSize: 8, cellPadding: 3, overflow:'linebreak' },
columnStyles: { 0:{fontStyle:'bold'} }
});
yPos = doc.lastAutoTable.finalY + 20;
// Qualitative Insights
const addQualitativeSection = (title, content) => {
if (yPos > doc.internal.pageSize.getHeight() - 60) { doc.addPage(); yPos = margin; }
doc.setFont(FONT_FAMILY, "bold"); doc.setFontSize(12); doc.setTextColor(SECTION_TITLE_COLOR_HEX);
doc.text(title, margin, yPos); yPos += 15;
doc.setFont(FONT_FAMILY, "normal"); doc.setFontSize(10); doc.setTextColor(PRIMARY_TEXT_COLOR_HEX);
const lines = doc.splitTextToSize(content, usableWidth);
doc.text(lines, margin, yPos); yPos += (lines.length * 12) + 10;
};
if (yPos > doc.internal.pageSize.getHeight() - 150) { doc.addPage(); yPos = margin; }
doc.setFont(FONT_FAMILY, "bold"); doc.setFontSize(14); doc.setTextColor(SECTION_TITLE_COLOR_HEX);
doc.text("Qualitative Insights", margin, yPos); yPos += 20;
addQualitativeSection("Major Team Achievements:", achievements);
addQualitativeSection("Key Challenges Encountered:", challenges);
addQualitativeSection("Key Lessons Learned:", lessonsLearned);
addQualitativeSection("Main Focus for Next Period:", nextPeriodFocus);
// 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 - 30, pageWidth - margin, pageHeight - 30);
doc.setFont(FONT_FAMILY, "normal"); doc.setFontSize(8); doc.setTextColor(PRIMARY_TEXT_COLOR_HEX);
doc.text(`Team Productivity Dashboard - Page ${i} of ${pageCount}`, pageWidth / 2, pageHeight - 20, { align: "center" });
}
doc.save(`Team_Dashboard_${teamName.replace(/[^a-z0-9]/gi, '_')}_${reportingPeriod.replace(/[^a-z0-9]/gi, '_')}.pdf`);
}