Biggest Loser
${analysis.biggestLoser.name} (${analysis.biggestLoser.change})
Average Position
${analysis.averagePosition}
`;
// Table
dashboardTableBody.innerHTML = '';
analysis.processedKeywords.forEach(kw => {
const tr = document.createElement('tr');
tr.className = 'bg-white border-b hover:bg-gray-50';
let changeHtml = '';
if (kw.change > 0) {
changeHtml = `
â–² +${kw.change}`;
} else if (kw.change < 0) {
changeHtml = `
â–¼ ${kw.change}`;
} else {
changeHtml = `
-`;
}
tr.innerHTML = `
${kw.name} |
${kw.currentRank} |
${kw.prevRank} |
${changeHtml} |
`;
dashboardTableBody.appendChild(tr);
});
// Chart
chartKeywordSelect.innerHTML = state.keywords.map(kw => `
`).join('');
renderChart(state.keywords[0].id);
};
const renderChart = (keywordId) => {
const keyword = state.keywords.find(kw => kw.id == keywordId);
if (!keyword) return;
const ranks = parseRanks(keyword.ranks).map(r => r === 0 ? null : r); // Handle 0 as no data point
const labels = ranks.map((_, i) => `Day ${i + 1}`);
if (rankingChart) rankingChart.destroy();
rankingChart = new Chart(rankingChartCanvas, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Rank',
data: ranks,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.1,
fill: true,
pointRadius: 4,
pointBackgroundColor: '#3b82f6'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { reverse: true, beginAtZero: false, suggestedMin: 1 }
},
plugins: { legend: { display: false } }
}
});
};
// --- Event Handlers ---
addKeywordBtn.addEventListener('click', () => createKeywordRow());
chartKeywordSelect.addEventListener('change', (e) => renderChart(e.target.value));
// --- Tab & Navigation Logic ---
function switchTab(targetTabId) {
if (currentTab === 'config') {
saveConfig();
}
currentTab = targetTabId;
tabs.forEach(tab => tab.classList.toggle('active', tab.dataset.tab === currentTab));
tabContents.forEach(content => content.classList.toggle('active', content.id === currentTab));
if (currentTab === 'dashboard') {
renderDashboard();
} else {
renderConfig();
}
updateNavButtons();
}
function updateNavButtons() {
if (currentTab === 'dashboard') {
nextBtn.textContent = 'Configure Data';
prevBtn.style.display = 'none';
pdfButtonContainer.style.display = 'block';
} else {
nextBtn.textContent = 'Update Dashboard';
prevBtn.style.display = 'inline-flex';
pdfButtonContainer.style.display = 'none';
}
}
tabs.forEach(tab => tab.addEventListener('click', () => switchTab(tab.dataset.tab)));
nextBtn.addEventListener('click', () => currentTab === 'dashboard' ? switchTab('config') : switchTab('dashboard'));
prevBtn.addEventListener('click', () => currentTab === 'config' ? switchTab('dashboard') : switchTab('config'));
// --- PDF Download ---
downloadPdfBtn.addEventListener('click', async () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' });
const content = document.getElementById('pdf-content');
document.getElementById('pdf-date').textContent = `Report Generated: ${new Date().toLocaleString()}`;
const canvas = await html2canvas(content, { scale: 2 });
const imgData = canvas.toDataURL('image/jpeg', 1.0);
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth() - 28;
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(imgData, 'JPEG', 14, 15, pdfWidth, pdfHeight);
document.getElementById('pdf-date').textContent = '';
doc.save(`Ranking-Fluctuation-Report-${new Date().toISOString().slice(0,10)}.pdf`);
});
// --- Initial Load ---
renderDashboard();
updateNavButtons();
});