Sales Forecast Worksheet

Sales Forecast Worksheet

General Details

Note any assumptions affecting these probabilities (e.g., product launch date, market stability).

Add New Opportunity

Forecast Pipeline (Editable Dashboard)

Client / Opportunity Deal Value ($) Probability (%) Expected Value ($) Target Close Actions

Review the final Sales Forecast Report.

Click "Next" or "Previous" to refresh this preview.

Prepared by: ${escapeHTML(data.salesRep)} | Period: ${escapeHTML(data.period)}

1. Forecast Summary

Total Pipeline Value: ${formatCurrency(totals.totalValue)}
TOTAL EXPECTED REVENUE: ${formatCurrency(totals.totalExpected)}

2. Key Assumptions

${escapeHTML(data.assumptions).replace(/\n/g, '
')}

3. Pipeline Details

${itemRowsHTML.length > 0 ? itemRowsHTML : ''}
Client / Opportunity Deal Value ($) Probability (%) Expected Value ($) Target Close
No items logged.
`; }; const downloadTxt = () => { const data = getReportData(); const totals = data.totals; let content = `SALES FORECAST REPORT: ${data.title.toUpperCase()}\n`; content += "========================================================\n"; content += `Period: ${data.period}\n`; content += `Sales Rep: ${data.salesRep}\n\n`; content += "SUMMARY\n"; content += "--------------------------------------------------------\n"; content += `Total Pipeline Value: ${formatCurrency(totals.totalValue)}\n`; content += `TOTAL EXPECTED REVENUE: ${formatCurrency(totals.totalExpected)}\n\n`; content += "KEY ASSUMPTIONS\n"; content += "--------------------------------------------------------\n"; content += `${data.assumptions}\n\n`; content += "PIPELINE DETAILS\n"; content += "--------------------------------------------------------\n"; content += "Client / Opportunity | Value ($) | Prob. (%) | Expected ($) | Close Date\n"; content += "------------------------------------------------------------------------------------------------------------------------\n"; data.items.forEach(item => { const expectedValue = item.value * (item.probability / 100); content += `${item.description.padEnd(25).substring(0, 25)} | ${item.value.toFixed(2).padEnd(10)} | ${item.probability.toString().padEnd(9)} | ${expectedValue.toFixed(2).padEnd(12)} | ${item.close}\n`; }); content += "------------------------------------------------------------------------------------------------------------------------\n"; const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `forecast_${data.title.replace(/ /g, '_')}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }; const downloadPDF = () => { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('l', 'mm', 'a4'); // Landscape for better table fit const data = getReportData(); const totals = data.totals; const margin = 15; let yPos = 20; const pageWidth = doc.internal.pageSize.getWidth(); const usableWidth = pageWidth - margin * 2; const addTitle = (text, size, color, style = 'bold') => { if (yPos > 190) { doc.addPage('l'); yPos = 20; } doc.setFontSize(size); doc.setFont(undefined, style); doc.setTextColor(color[0], color[1], color[2]); doc.text(text, margin, yPos); yPos += size / 2 + 3; }; const addText = (text, size = 10, style = 'normal', indent = 0) => { doc.setFontSize(size); doc.setFont(undefined, style); doc.setTextColor(52, 73, 94); const lines = doc.splitTextToSize(text, usableWidth - indent); if (lines.length * 5 + yPos > 190) { doc.addPage('l'); yPos = 20; } doc.text(lines, margin + indent, yPos); yPos += (lines.length * 5) + 3; }; // 1. Header doc.setFontSize(20); doc.setFont(undefined, 'bold'); doc.setTextColor(44, 62, 80); doc.text(`Sales Forecast Report`, pageWidth / 2, yPos, { align: 'center' }); yPos += 8; doc.setFontSize(14); doc.setTextColor(46, 204, 113); doc.text(data.title, pageWidth / 2, yPos, { align: 'center' }); yPos += 10; doc.setFontSize(10); doc.setTextColor(107, 114, 128); doc.text(`Prepared by: ${data.salesRep} | Period: ${data.period}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 10; // 2. Forecast Summary addTitle("1. Forecast Summary", 12, [46, 204, 113]); const totalsX = pageWidth - margin - 60; // Start X for totals column (wider) const addTotalLine = (label, value, isGrand = false) => { doc.setFontSize(isGrand ? 12 : 10); doc.setFont(undefined, isGrand ? 'bold' : 'normal'); doc.setTextColor(isGrand ? 46 : 52, isGrand ? 204 : 73, isGrand ? 113 : 94); if (isGrand) { doc.setDrawColor(44, 62, 80); doc.setLineWidth(0.5); doc.line(totalsX - 10, yPos - 1, pageWidth - margin, yPos - 1); yPos += 2; } doc.text(label, totalsX, yPos, { align: 'left' }); doc.text(formatCurrency(value), pageWidth - margin, yPos, { align: 'right' }); yPos += 5; }; addTotalLine("Total Pipeline Value:", totals.totalValue); addTotalLine("TOTAL EXPECTED REVENUE:", totals.totalExpected, true); yPos += 5; // 3. Key Assumptions addTitle("2. Key Assumptions", 12, [46, 204, 113]); addText(data.assumptions, 10, 'normal', 5); yPos += 3; // 4. Pipeline Details Table addTitle("3. Pipeline Details", 12, [46, 204, 113]); const itemHead = [['Client / Opportunity', 'Deal Value ($)', 'Probability (%)', 'Expected Value ($)', 'Target Close']]; const itemBody = data.items.map(item => [ item.description, formatCurrency(item.value), `${item.probability}%`, formatCurrency(item.value * (item.probability / 100)), item.close ]); doc.autoTable({ startY: yPos, head: itemHead, body: itemBody, theme: 'grid', styles: { fontSize: 9, cellPadding: 2, textColor: [52, 73, 94] }, headStyles: { fillColor: [46, 204, 113], textColor: [255, 255, 255] }, columnStyles: { 0: { cellWidth: 70, fontStyle: 'normal' }, 1: { halign: 'right' }, 2: { halign: 'center', cellWidth: 20 }, 3: { halign: 'right', fontStyle: 'bold' }, 4: { halign: 'center', cellWidth: 25 } }, margin: { left: margin, right: margin } }); // yPos is updated automatically by autoTable doc.save(`sales_forecast_${data.title.replace(/ /g, '_')}.pdf`); }; // --- Event Listeners --- // Tab Buttons tabButtons.forEach((btn, index) => { btn.addEventListener('click', () => showTab(index + 1)); }); // Next/Prev Navigation nextBtn.addEventListener('click', () => showTab(currentTab + 1)); prevBtn.addEventListener('click', () => showTab(currentTab - 1)); // Tab 2 Actions (Add, Remove, Edit) itemInputs.addBtn.addEventListener('click', addItem); itemInputs.tbody.addEventListener('click', (e) => { if (e.target.dataset.removeId) { removeItem(parseInt(e.target.dataset.removeId)); } }); itemInputs.tbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; updateItem(id, field, e.target.textContent); } }, true); // Ensure inputs that affect calculations trigger a render Object.values(itemInputs).forEach(input => { if (input.tagName === 'INPUT' || input.tagName === 'SELECT') { input.addEventListener('change', renderItems); } }); // Tab 3 Actions downloadPdfBtn.addEventListener('click', downloadPDF); downloadTxtBtn.addEventListener('click', downloadTxt); // --- Initialization --- loadSampleData(); showTab(1); });
Scroll to Top