Company: ${data.companyName || ''}
Date: ${new Date().toLocaleDateString()}
Introduction
${introText.replace(/\n/g, '
')}
Project Scope
${(data.scope || '').replace(/\n/g, '
')}
Pricing
${pricingTableHTML}
About Us
${config.about.replace(/\n/g, '
')}
Terms & Conditions
${config.terms.replace(/\n/g, '
')}
`;
};
const renderConfigForm = () => {
templateIntro.value = config.intro;
templateAbout.value = config.about;
templateTerms.value = config.terms;
};
const renderSavedProposals = () => {
savedProposalsBody.innerHTML = '';
if (savedProposals.length === 0) {
noSavedMsg.style.display = 'block';
savedProposalsBody.style.display = 'none';
} else {
noSavedMsg.style.display = 'none';
savedProposalsBody.style.display = '';
savedProposals.forEach(p => {
const row = document.createElement('tr');
row.innerHTML = `
${new Date(p.date).toLocaleDateString()} |
${p.projectTitle} |
${p.clientName} |
|
`;
savedProposalsBody.appendChild(row);
});
}
};
// --- CORE LOGIC ---
const getCurrentProposalData = () => {
const pricing = [];
document.querySelectorAll('.pricing-item').forEach(item => {
pricing.push({
description: item.querySelector('.item-desc').value,
price: item.querySelector('.item-price').value
});
});
return {
clientName: document.getElementById('client-name').value,
companyName: document.getElementById('company-name').value,
projectTitle: document.getElementById('project-title').value,
scope: document.getElementById('project-scope').value,
pricing: pricing
};
};
// --- EVENT HANDLERS ---
Object.keys(tabButtons).forEach(tabId => {
tabButtons[tabId].addEventListener('click', () => switchTab(tabId));
});
nextBtn.addEventListener('click', () => {
const currentIndex = tabs.indexOf(currentTab);
if (currentIndex < tabs.length - 1) switchTab(tabs[currentIndex + 1]);
});
prevBtn.addEventListener('click', () => {
const currentIndex = tabs.indexOf(currentTab);
if (currentIndex > 0) switchTab(tabs[currentIndex - 1]);
});
addItemBtn.addEventListener('click', () => addPricingItem());
pricingItemsContainer.addEventListener('click', (e) => {
if (e.target.classList.contains('remove-item-btn')) {
e.target.closest('.pricing-item').remove();
updatePreview();
}
});
proposalForm.addEventListener('input', updatePreview);
configForm.addEventListener('submit', (e) => {
e.preventDefault();
config.intro = templateIntro.value;
config.about = templateAbout.value;
config.terms = templateTerms.value;
saveState();
updatePreview(); // Update preview in case user is on dashboard
alert('Template saved successfully!');
});
saveProposalBtn.addEventListener('click', () => {
const data = getCurrentProposalData();
if (!data.clientName || !data.projectTitle) {
alert('Please fill in at least the Client Name and Project Title.');
return;
}
const newProposal = { ...data, id: proposalIdCounter++, date: new Date().toISOString() };
savedProposals.push(newProposal);
saveState();
renderSavedProposals();
alert('Proposal saved!');
});
downloadPdfBtn.addEventListener('click', () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const data = getCurrentProposalData();
const margin = 15;
let yPos = 20;
doc.setFontSize(22);
doc.text(`Proposal: ${data.projectTitle}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 15;
doc.setFontSize(12);
doc.text(`Client: ${data.clientName}`, margin, yPos);
doc.text(`Date: ${new Date().toLocaleDateString()}`, doc.internal.pageSize.getWidth() - margin, yPos, { align: 'right' });
yPos += 10;
const addSection = (title, text) => {
if (yPos > 260) { doc.addPage(); yPos = 20; }
doc.setFontSize(16);
doc.text(title, margin, yPos);
yPos += 8;
doc.setFontSize(11);
const lines = doc.splitTextToSize(text.replace(/\n/g, '\n'), 180);
doc.text(lines, margin, yPos);
yPos += lines.length * 5 + 10;
};
const introText = config.intro.replace('{clientName}', data.clientName).replace('{projectTitle}', data.projectTitle);
addSection('Introduction', introText);
addSection('Project Scope', data.scope);
if (yPos > 240) { doc.addPage(); yPos = 20; }
doc.setFontSize(16);
doc.text('Pricing', margin, yPos);
yPos += 8;
const tableColumn = ["Item Description", "Price"];
const tableRows = [];
let total = 0;
data.pricing.forEach(item => {
const price = parseFloat(item.price) || 0;
total += price;
tableRows.push([item.description, `$${price.toFixed(2)}`]);
});
tableRows.push([{content: 'Total', styles: {fontStyle: 'bold'}}, {content: `$${total.toFixed(2)}`, styles: {fontStyle: 'bold'}}]);
doc.autoTable({
head: [tableColumn],
body: tableRows,
startY: yPos,
theme: 'grid',
headStyles: { fillColor: [59, 130, 246] },
});
yPos = doc.autoTable.previous.finalY + 15;
addSection('About Us', config.about);
addSection('Terms & Conditions', config.terms);
doc.save(`${data.projectTitle}_Proposal.pdf`);
});
// --- GLOBAL FUNCTIONS ---
window.loadProposal = (id) => {
const proposal = savedProposals.find(p => p.id === id);
if (!proposal) return;
document.getElementById('client-name').value = proposal.clientName;
document.getElementById('company-name').value = proposal.companyName;
document.getElementById('project-title').value = proposal.projectTitle;
document.getElementById('project-scope').value = proposal.scope;
pricingItemsContainer.innerHTML = '';
proposal.pricing.forEach(item => addPricingItem(item.description, item.price));
updatePreview();
switchTab('dashboard');
};
window.deleteProposal = (id) => {
if (confirm('Are you sure you want to delete this proposal?')) {
savedProposals = savedProposals.filter(p => p.id !== id);
saveState();
renderSavedProposals();
}
};
// --- START THE APP ---
initialize();
});