Product Demand Forecasting Tool

Product Demand Forecasting Tool

Analyze historical sales to forecast future product demand.

Forecast Summary

Configure your sales data and click "Forecast Demand" to see the report.

${results.highestDemand.name}

Next Period Demand

${results.highestDemand.forecast[0].toLocaleString()}

Fastest Growing

${results.fastestGrowing.name}

Total Forecasted Units

${results.totalForecastVolume.toLocaleString()}

`; let tableHtml = ` ${results.forecastLabels.map(l => ``).join('')} `; results.forecastData.forEach(p => { tableHtml += ` ${p.forecast.map(f => ``).join('')} `; }); tableHtml += `
Product${l}
${p.name}${Math.round(f).toLocaleString()}
`; resultsContainer.innerHTML = tableHtml; renderChart(results.chartData, results.chartLabels); }; const renderChart = (data, labels) => { const ctx = document.getElementById('forecast-chart').getContext('2d'); if(forecastChart) forecastChart.destroy(); const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6']; const datasets = data.map((p, index) => ({ label: p.name, data: [...p.history, ...p.forecast.map(f => f)], borderColor: colors[index % colors.length], tension: 0.1, borderDash: [0, 0, ...Array(p.forecast.length).fill(5)] // Dashed line for forecast })); forecastChart = new Chart(ctx, { type: 'line', data: { labels, datasets }, options: { responsive: true, plugins: { title: { display: true, text: 'Sales History & Demand Forecast' }}} }); }; // --- CORE LOGIC --- const handleForecast = () => { forecastBtnSpinner.classList.remove('hidden'); forecastBtnText.textContent = 'Forecasting...'; forecastBtn.disabled = true; setTimeout(() => { const periodsToForecast = parseInt(document.getElementById('forecast-periods').value) || 3; let totalForecastVolume = 0; const forecastData = products.map(p => { const history = p.salesData.split(',').map(s => parseFloat(s.trim())).filter(n => !isNaN(n)); const n = history.length; let forecast = []; if (n >= 2) { // Simple linear regression let sum_x = 0, sum_y = 0, sum_xy = 0, sum_xx = 0; for (let i = 0; i < n; i++) { sum_x += i; sum_y += history[i]; sum_xy += i * history[i]; sum_xx += i * i; } const slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x); const intercept = (sum_y - slope * sum_x) / n; for (let i = 0; i < periodsToForecast; i++) { const futureX = n + i; const predictedValue = slope * futureX + intercept; forecast.push(Math.max(0, predictedValue)); // Don't forecast negative sales } } else { // Not enough data, just repeat last value for (let i = 0; i < periodsToForecast; i++) forecast.push(history[0] || 0); } totalForecastVolume += forecast.reduce((a,b) => a+b, 0); const growth = n > 1 ? ((history[n - 1] - history[0]) / history[0]) * 100 : 0; return { name: p.name, history, forecast, growth }; }); const highestDemand = [...forecastData].sort((a,b) => b.forecast[0] - a.forecast[0])[0]; const fastestGrowing = [...forecastData].sort((a,b) => b.growth - a.growth)[0]; const maxHistory = Math.max(...forecastData.map(p => p.history.length)); const chartLabels = Array.from({length: maxHistory}, (_, i) => `M${i+1}`); const forecastLabels = Array.from({length: periodsToForecast}, (_, i) => `F${i+1}`); chartLabels.push(...forecastLabels); const results = { forecastData, totalForecastVolume: Math.round(totalForecastVolume), highestDemand, fastestGrowing, chartData: forecastData, chartLabels, forecastLabels, }; renderDashboard(results); switchTab('dashboard'); downloadPdfBtn.disabled = false; forecastBtnSpinner.classList.add('hidden'); forecastBtnText.textContent = 'Forecast Demand'; forecastBtn.disabled = false; }, 500); }; // --- UI & EVENT HANDLERS --- const switchTab = (tabId) => { currentTab = tabId; Object.values(tabPanes).forEach(pane => pane.classList.add('hidden')); tabPanes[tabId].classList.remove('hidden'); Object.values(tabButtons).forEach(btn => btn.classList.replace('tab-active', 'tab-inactive')); tabButtons[tabId].classList.replace('tab-inactive', 'tab-active'); updateNavButtons(); }; const navigateTabs = (direction) => { const currentIndex = tabs.indexOf(currentTab); const newIndex = direction === 'next' ? currentIndex + 1 : currentIndex - 1; if (newIndex >= 0 && newIndex < tabs.length) switchTab(tabs[newIndex]); }; const updateNavButtons = () => { const currentIndex = tabs.indexOf(currentTab); prevBtn.disabled = currentIndex === 0; nextBtn.disabled = currentIndex === tabs.length - 1; prevBtn.classList.toggle('opacity-50', prevBtn.disabled); nextBtn.classList.toggle('opacity-50', nextBtn.disabled); }; const handlePdfDownload = () => { const pdfRenderContainer = document.getElementById('pdf-render-content'); const pdfContent = document.getElementById('pdf-content').innerHTML; const header = `

Product Demand Forecast Report

`; pdfRenderContainer.innerHTML = header + pdfContent + '
'; html2canvas(pdfRenderContainer, { scale: 2 }).then(canvas => { const imgData = canvas.toDataURL('image/png'); const { jsPDF } = window.jspdf; const pdf = new jsPDF({ orientation: 'landscape', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(), margin = 40; const contentWidth = pdfWidth - margin * 2; const pdfHeight = (canvas.height * contentWidth) / canvas.width; pdf.addImage(imgData, 'PNG', margin, margin, contentWidth, pdfHeight); pdf.save('Demand-Forecast-Report.pdf'); }); }; // --- EVENT LISTENERS --- window.switchTab = switchTab; window.navigateTabs = navigateTabs; forecastBtn.addEventListener('click', handleForecast); downloadPdfBtn.addEventListener('click', handlePdfDownload); addProductBtn.addEventListener('click', () => { products.push({ id: nextId++, name: 'New Product', salesData: '10,20,30' }); renderConfigTable(); }); configTableBody.addEventListener('input', e => { if (!e.target.classList.contains('cfg-input')) return; const id = parseInt(e.target.closest('tr').dataset.id); const prop = e.target.dataset.prop; const item = products.find(p => p.id === id); if (item) item[prop] = e.target.value; }); configTableBody.addEventListener('click', e => { if (!e.target.classList.contains('rm-btn')) return; const id = parseInt(e.target.closest('tr').dataset.id); products = products.filter(p => p.id !== id); renderConfigTable(); }); // --- INITIALIZATION --- renderConfigTable(); updateNavButtons(); switchTab('dashboard'); });
Scroll to Top