Highest Growth
+${results.topTrending.growth.toFixed(2)}%
Most Stable Product
${results.mostStable.name}
Total Sales Volume
${results.totalVolume.toLocaleString()}
`;
let tableHtml = `
| Product |
Trend Score |
% Growth |
Total Sales |
`;
results.analyzedProducts.forEach(p => {
const trendColor = p.trendScore > 1 ? 'text-green-600' : p.trendScore < -1 ? 'text-red-600' : 'text-gray-600';
tableHtml += `
| ${p.name} |
${p.trendScore.toFixed(2)} |
${p.growth.toFixed(2)}% |
${p.totalSales.toLocaleString()} |
`;
});
tableHtml += `
`;
resultsContainer.innerHTML = tableHtml;
renderChart(results.analyzedProducts);
};
const renderChart = (data) => {
const ctx = document.getElementById('trends-chart').getContext('2d');
if(trendsChart) trendsChart.destroy();
const datasets = data.map((p, index) => {
const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6'];
return {
label: p.name,
data: p.numericSales,
borderColor: colors[index % colors.length],
tension: 0.1
}
});
const maxDataPoints = Math.max(...data.map(p => p.numericSales.length));
const labels = Array.from({length: maxDataPoints}, (_, i) => `Month ${i+1}`);
trendsChart = new Chart(ctx, {
type: 'line',
data: { labels, datasets },
options: { responsive: true, plugins: { title: { display: true, text: 'Monthly Product Sales' }}}
});
};
// --- CORE LOGIC ---
const handleAnalysis = () => {
analyzeBtnSpinner.classList.remove('hidden');
analyzeBtnText.textContent = 'Analyzing...';
analyzeBtn.disabled = true;
setTimeout(() => {
let totalVolume = 0;
const analyzedProducts = products.map(p => {
const numericSales = p.salesData.split(',').map(s => parseFloat(s.trim())).filter(n => !isNaN(n));
if (numericSales.length < 2) {
return { ...p, numericSales, trendScore: 0, growth: 0, totalSales: 0 };
}
// Simple linear regression for trend score (slope)
const n = numericSales.length;
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 += numericSales[i]; sum_xy += i * numericSales[i]; sum_xx += i * i;
}
const slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
const totalSales = numericSales.reduce((a, b) => a + b, 0);
totalVolume += totalSales;
const growth = ((numericSales[n - 1] - numericSales[0]) / numericSales[0]) * 100;
return { ...p, numericSales, trendScore: slope, growth, totalSales };
}).sort((a,b) => b.trendScore - a.trendScore);
const results = {
analyzedProducts,
totalVolume,
topTrending: analyzedProducts[0],
mostStable: [...analyzedProducts].sort((a,b) => Math.abs(a.trendScore) - Math.abs(b.trendScore))[0]
};
renderDashboard(results);
switchTab('dashboard');
downloadPdfBtn.disabled = false;
analyzeBtnSpinner.classList.add('hidden');
analyzeBtnText.textContent = 'Analyze Trends';
analyzeBtn.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 Trend Analysis 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('Product-Trend-Report.pdf');
});
};
// --- EVENT LISTENERS ---
window.switchTab = switchTab;
window.navigateTabs = navigateTabs;
analyzeBtn.addEventListener('click', handleAnalysis);
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');
});