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.

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