Request for Proposal (RFP) Generator

Request for Proposal (RFP) Generator

1. Project Overview & Context
2. Project Scope & Objectives
3. Technical Requirements
4. Deliverables & Budget

Click "Generate RFP" in the builder to preview your document.

Add New Reusable Requirement

Reusable Requirements Library

Requirement Action

No technical requirements defined.

`; } html += `
`; // --- 5. Deliverables & Evaluation --- html += `

5. Deliverables & Evaluation

`; html += `

Key Deliverables (Timeline: ${data.project_timeline || 'N/A'} months)

    `; data.key_deliverables_list.forEach(del => { html += `
  • ${del}
  • `; }); html += `
`; html += `

Evaluation Criteria

`; data.evaluation_criteria_list.forEach(crit => { // Simple split for display in two columns const parts = crit.split('('); const critName = parts[0].trim(); const weighting = parts[1] ? `(${parts[1]}` : 'N/A'; html += ``; }); html += `
CriteriaWeighting
${critName}${weighting}
`; html += `
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(); });
Scroll to Top