Introduction
${proposalData.introduction.replace(/\n/g, '
')}
Key Deliverables
- ${deliverablesHtml}
Timeline
${proposalData.timeline.replace(/\n/g, '
')}
Investment
The following table outlines the investment for this project.
Total: $${proposalData.total.toFixed(2)}
`;
};
const generatePdf = () => {
gatherData();
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' });
const margin = 20;
let yPos = 30;
// Header
doc.setFontSize(22);
doc.setFont(undefined, 'bold');
doc.text(proposalData.projectTitle, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 15;
doc.setFontSize(11);
doc.setFont(undefined, 'normal');
doc.text(`Prepared for: ${proposalData.clientName}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 7;
doc.text(`Prepared by: ${proposalData.preparedBy}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 7;
doc.text(`Date: ${proposalData.proposalDate}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 20;
// Sections
const addSection = (title, content) => {
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text(title, margin, yPos);
yPos += 8;
doc.setFontSize(11);
doc.setFont(undefined, 'normal');
const splitText = doc.splitTextToSize(content, doc.internal.pageSize.getWidth() - margin * 2);
doc.text(splitText, margin, yPos);
yPos += (splitText.length * 5) + 10;
};
addSection('Introduction', proposalData.introduction);
// Deliverables List
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text('Key Deliverables', margin, yPos);
yPos += 8;
doc.setFontSize(11);
proposalData.deliverables.forEach(item => {
doc.text(`• ${item}`, margin + 5, yPos);
yPos += 7;
});
yPos += 5;
addSection('Project Timeline', proposalData.timeline);
// Pricing Table
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text('Investment', margin, yPos);
yPos += 8;
const tableBody = proposalData.pricing.map(item => [item.description, `$${item.cost.toFixed(2)}`]);
tableBody.push([{ content: 'Total', styles: { fontStyle: 'bold' } }, { content: `$${proposalData.total.toFixed(2)}`, styles: { fontStyle: 'bold' } }]);
doc.autoTable({
startY: yPos,
head: [['Description', 'Cost']],
body: tableBody,
theme: 'grid',
headStyles: { fillColor: [243, 244, 246], textColor: [55, 65, 81], fontStyle: 'bold' },
styles: { cellPadding: 2.5, fontSize: 10 },
columnStyles: { 1: { halign: 'right' } },
didDrawCell: (data) => {
if (data.row.index === tableBody.length - 1) {
data.cell.styles.fillColor = '#F3F4F6';
}
}
});
doc.save(`${proposalData.projectTitle.replace(/\s/g, '_')}_Proposal.pdf`);
};
// --- Event Listeners ---
tabButtons.forEach((btn, index) => btn.addEventListener('click', () => changeTab(index + 1)));
prevBtn.addEventListener('click', () => navigateTab(-1));
nextBtn.addEventListener('click', () => navigateTab(1));
addPriceItemBtn.addEventListener('click', () => addPriceItem());
downloadPdfBtn.addEventListener('click', generatePdf);
// --- Initial Setup ---
document.getElementById('proposal-date').value = new Date().toISOString().split('T')[0];
document.getElementById('introduction').value = "Innovate Inc requires a complete overhaul of its corporate website to better reflect its market leadership and improve user engagement. The current site lacks a modern user interface and is not optimized for mobile devices, leading to a high bounce rate. This proposal outlines a comprehensive redesign project to address these issues.";
document.getElementById('deliverables').value = "Fully responsive website design and development\nContent Management System (CMS) integration\nSEO optimization for all pages\nUser training and documentation";
document.getElementById('timeline').value = "The project is estimated to be completed within 8-10 weeks from the start date. A detailed GANTT chart will be provided upon project kickoff.";
addPriceItem('Phase 1: Design & Prototyping', '5000');
addPriceItem('Phase 2: Development & CMS Integration', '12000');
addPriceItem('Phase 3: Content Migration & SEO', '3000');
updateTotalPrice();
updateNavButtons();
});
