`;
// --- 5. Deliverables & Evaluation ---
html += `
`;
html += `
5. Deliverables & Evaluation
`; html += `Key Deliverables (Timeline: ${data.project_timeline || 'N/A'} months)
- `;
data.key_deliverables_list.forEach(del => {
html += `
- ${del} `; }); html += `
Evaluation Criteria
| Criteria | Weighting |
|---|---|
| ${critName} | ${weighting} |
Thank you for your interest in partnering with ${data.issuing_org || 'Acme Corp.'}.
`;
reviewArea.innerHTML = html;
pdfDownloadBtn.disabled = false;
switchTab(1); // Switch to review tab
}
/**
* PDF Generation Function
* V.A.1, IX: MUST be fully functional and nicely formatted.
*/
function downloadPDF() {
const data = getFormData();
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'pt', 'a4');
let currentY = 40;
const margin = 40;
const pageWidth = doc.internal.pageSize.width;
const maxWidth = pageWidth - (margin * 2);
const checkPageBreak = (spaceNeeded) => {
if (currentY + spaceNeeded > doc.internal.pageSize.height - margin) {
doc.addPage();
currentY = margin;
}
};
const addSectionHeader = (title) => {
checkPageBreak(30);
doc.setFontSize(16);
doc.setFont('Times', 'bold');
doc.setTextColor(0, 123, 255); // Primary color
doc.text(title, margin, currentY);
currentY += 12;
doc.setLineWidth(1);
doc.setDrawColor(200);
doc.line(margin, currentY, pageWidth - margin, currentY);
currentY += 15;
doc.setTextColor(0);
};
const addText = (text, size = 11, style = 'normal', indent = 0) => {
doc.setFontSize(size);
doc.setFont('Times', style);
// Estimate height needed for text wrapping
const lines = doc.splitTextToSize(text, maxWidth - indent);
checkPageBreak(lines.length * 10);
doc.text(lines, margin + indent, currentY);
currentY += (lines.length * 12);
};
// --- PDF Content ---
// Title Block
doc.setFontSize(24);
doc.setFont('Times', 'bold');
doc.setTextColor(0, 123, 255);
doc.text(data.rfp_title || "REQUEST FOR PROPOSAL", pageWidth / 2, currentY, { align: 'center' });
currentY += 15;
doc.setFontSize(14);
doc.setFont('Times', 'normal');
doc.setTextColor(100);
doc.text(`Issued by: ${data.issuing_org || 'N/A'}`, pageWidth / 2, currentY, { align: 'center' });
currentY += 25;
doc.setTextColor(0);
// 1. Overview
addSectionHeader("1. Project Overview & Context");
// Overview Table
const overviewHead = [["Attribute", "Value"]];
const overviewBody = [
["Date Issued", data.issue_date],
["Submission Deadline", data.submission_date],
["Primary Contact", `${data.contact_name} (${data.contact_email})`],
["Maximum Budget", `$${parseFloat(data.budget_usd || 0).toLocaleString('en-US')} USD`],
["Project Timeline", `${data.project_timeline || 'N/A'} months`]
];
doc.autoTable({
startY: currentY,
head: overviewHead,
body: overviewBody,
theme: 'grid',
headStyles: { fillColor: [230, 230, 230], textColor: [0] },
styles: { fontSize: 10, cellPadding: 4, font: 'Times' },
columnStyles: { 0: { fontStyle: 'bold' } }
});
currentY = doc.autoTable.previous.finalY + 10;
// 2. Background
addSectionHeader("2. Background & Problem Statement");
addText(data.project_background || 'No background provided.', 11, 'normal', 5);
currentY += 5;
// 3. Scope & Objectives
addSectionHeader("3. Project Scope & Objectives");
addText("Scope of Work:", 12, 'bold', 0);
addText(data.project_scope || 'No scope provided.', 11, 'normal', 5);
currentY += 5;
addText("Key Objectives:", 12, 'bold', 0);
if (data.project_objectives && data.project_objectives.trim() !== '') {
addText(data.project_objectives, 11, 'normal', 5);
} else {
addText('No objectives provided.', 11, 'normal', 5);
}
currentY += 5;
// 4. Technical Requirements
addSectionHeader("4. Technical Requirements");
if (data.requirements.length > 0) {
const reqTableHead = [["ID", "Requirement"]];
const reqTableBody = data.requirements.map((req, index) => [`R-${index + 1}`, req]);
doc.autoTable({
startY: currentY,
head: reqTableHead,
body: reqTableBody,
theme: 'grid',
headStyles: { fillColor: [200, 200, 200], textColor: [0] },
styles: { fontSize: 10, cellPadding: 4, font: 'Times' }
});
currentY = doc.autoTable.previous.finalY + 10;
} else {
addText("No technical requirements defined.", 11, 'normal', 5);
currentY += 5;
}
// 5. Deliverables & Evaluation
addSectionHeader("5. Deliverables & Evaluation");
addText("Key Deliverables:", 12, 'bold', 0);
if (data.key_deliverables_list.length > 0) {
data.key_deliverables_list.forEach(del => {
addText(`• ${del}`, 11, 'normal', 5);
});
} else {
addText('No deliverables provided.', 11, 'normal', 5);
}
currentY += 5;
addText("Evaluation Criteria:", 12, 'bold', 0);
if (data.evaluation_criteria_list.length > 0) {
const evalTableHead = [["Criteria", "Weighting"]];
const evalTableBody = data.evaluation_criteria_list.map(crit => {
const parts = crit.split('(');
const critName = parts[0].trim();
const weighting = parts[1] ? `(${parts[1]}` : 'N/A';
return [critName, weighting];
});
doc.autoTable({
startY: currentY,
head: evalTableHead,
body: evalTableBody,
theme: 'grid',
headStyles: { fillColor: [230, 230, 230], textColor: [0] },
styles: { fontSize: 10, cellPadding: 4, font: 'Times' }
});
currentY = doc.autoTable.previous.finalY + 10;
} else {
addText('No evaluation criteria defined.', 11, 'normal', 5);
currentY += 5;
}
// Footer
addText(`Thank you for your interest in partnering with ${data.issuing_org || 'Acme Corp.'}.`, 10, 'italic', 0);
doc.save(`${data.rfp_title.replace(/\s/g, '_') || 'RFP'}_Document.pdf`);
}
generateBtn.addEventListener('click', generateRFP);
pdfDownloadBtn.addEventListener('click', downloadPDF);
// --- Tab Navigation ---
function switchTab(tabIndex) {
tabs.forEach((tab, index) => {
tab.classList.toggle('active', index === tabIndex);
contents[index].classList.toggle('active', index === tabIndex);
});
currentTab = tabIndex;
updateNavButtons();
}
function updateNavButtons() {
prevBtn.disabled = currentTab === 0;
nextBtn.disabled = currentTab === tabs.length - 1;
}
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => switchTab(index));
});
nextBtn.addEventListener('click', () => { if (currentTab < tabs.length - 1) switchTab(currentTab + 1); });
prevBtn.addEventListener('click', () => { if (currentTab > 0) switchTab(currentTab - 1); });
// --- Initial Setup ---
setInitialDates();
renderRequirements();
renderLibrary();
updateNavButtons();
});
