Local SEO Performance Dashboard

Local SEO Performance Dashboard

Google Business Profile

Review Summary

Citation Health

GBP Performance (Last 6 Months)

Keyword Rankings

Keyword Local Pack Rank Organic Rank Search Volume
Google Business Profile Data
Reviews & Citations
Chart Data (Last 6 Months)
Keyword Rankings

Average Rating: ${seoData.reviews.avgRating} / 5.0

New Reviews: ${seoData.reviews.newReviews}

`; // Citation Health citationHealthEl.innerHTML = `

Consistency: ${seoData.citations.consistency}%

Total Citations: ${seoData.citations.total}

`; // Keyword Rankings Table keywordRankingsBodyEl.innerHTML = seoData.keywords.map(k => ` ${k.keyword} ${k.localRank} ${k.organicRank} ${k.volume.toLocaleString()} `).join(''); createOrUpdateChart(); } /** * Populates the configuration form with data from the seoData object. */ function renderConfigForm() { // GBP document.getElementById('config-search-views').value = seoData.gbp.searchViews; document.getElementById('config-map-views').value = seoData.gbp.mapViews; document.getElementById('config-website-clicks').value = seoData.gbp.websiteClicks; document.getElementById('config-phone-calls').value = seoData.gbp.phoneCalls; document.getElementById('config-direction-requests').value = seoData.gbp.directionRequests; // Reviews & Citations document.getElementById('config-avg-rating').value = seoData.reviews.avgRating; document.getElementById('config-new-reviews').value = seoData.reviews.newReviews; document.getElementById('config-citation-consistency').value = seoData.citations.consistency; document.getElementById('config-total-citations').value = seoData.citations.total; // Chart Data configChartDataEl.innerHTML = ''; // Clear previous seoData.chartData.labels.forEach((label, index) => { configChartDataEl.innerHTML += ` `; }); // Keywords renderKeywordConfigRows(); } /** * Renders the editable keyword rows in the config tab. */ function renderKeywordConfigRows() { configKeywordListEl.innerHTML = ''; // Clear existing rows seoData.keywords.forEach((kw, index) => { const row = document.createElement('div'); row.className = 'grid grid-cols-1 md:grid-cols-5 gap-2 items-center'; row.innerHTML = ` `; configKeywordListEl.appendChild(row); }); // Re-attach event listeners for remove buttons document.querySelectorAll('.remove-keyword-btn').forEach(btn => { btn.addEventListener('click', (e) => { const indexToRemove = parseInt(e.target.dataset.index); seoData.keywords.splice(indexToRemove, 1); renderKeywordConfigRows(); // Re-render the list }); }); } /** * Creates or updates the Chart.js instance. */ function createOrUpdateChart() { if (gbpChartInstance) { gbpChartInstance.destroy(); } const ctx = chartCanvas.getContext('2d'); gbpChartInstance = new Chart(ctx, { type: 'bar', data: { labels: seoData.chartData.labels, datasets: [{ label: 'Search Views', data: seoData.chartData.searchViews, backgroundColor: 'rgba(79, 70, 229, 0.8)', // Indigo borderColor: 'rgba(79, 70, 229, 1)', borderWidth: 1 }, { label: 'Map Views', data: seoData.chartData.mapViews, backgroundColor: 'rgba(34, 197, 94, 0.8)', // Green borderColor: 'rgba(34, 197, 94, 1)', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { color: '#4b5563' } }, x: { ticks: { color: '#4b5563' } } }, plugins: { legend: { labels: { color: '#1f2937' } } } } }); } // --- EVENT HANDLERS --- /** * Handles the "Update Dashboard" button click. * Reads all data from the config form, updates the seoData object, * then re-renders the dashboard and switches to it. */ function handleUpdate() { // Helper to safely parse numbers const getNum = (id) => parseFloat(document.getElementById(id).value) || 0; const getInt = (id) => parseInt(document.getElementById(id).value) || 0; // Update GBP seoData.gbp.searchViews = getInt('config-search-views'); seoData.gbp.mapViews = getInt('config-map-views'); seoData.gbp.websiteClicks = getInt('config-website-clicks'); seoData.gbp.phoneCalls = getInt('config-phone-calls'); seoData.gbp.directionRequests = getInt('config-direction-requests'); // Update Reviews & Citations seoData.reviews.avgRating = getNum('config-avg-rating'); seoData.reviews.newReviews = getInt('config-new-reviews'); seoData.citations.consistency = getInt('config-citation-consistency'); seoData.citations.total = getInt('config-total-citations'); // Update Chart Data document.querySelectorAll('.chart-label-input').forEach(input => { seoData.chartData.labels[input.dataset.index] = input.value; }); document.querySelectorAll('.chart-search-input').forEach(input => { seoData.chartData.searchViews[input.dataset.index] = parseInt(input.value) || 0; }); document.querySelectorAll('.chart-map-input').forEach(input => { seoData.chartData.mapViews[input.dataset.index] = parseInt(input.value) || 0; }); // Update Keywords const newKeywords = []; document.querySelectorAll('#config-keyword-list .grid').forEach(row => { newKeywords.push({ keyword: row.querySelector('.keyword-input').value, localRank: parseInt(row.querySelector('.local-rank-input').value) || 0, organicRank: parseInt(row.querySelector('.organic-rank-input').value) || 0, volume: parseInt(row.querySelector('.volume-input').value) || 0, }); }); seoData.keywords = newKeywords; renderDashboard(); showTab('dashboard'); } /** * Handles PDF download, ensuring proper rendering without truncation. */ async function handlePdfDownload() { const { jsPDF } = window.jspdf; const content = document.getElementById('pdf-content'); const pdfTitle = document.getElementById('pdf-title'); const downloadBtn = document.getElementById('download-pdf-btn'); // Temporarily show the title for the PDF capture pdfTitle.style.display = 'block'; downloadBtn.textContent = 'Generating PDF...'; downloadBtn.disabled = true; try { // Use html2canvas to capture the specified content area const canvas = await html2canvas(content, { scale: 2, // Increase scale for better resolution useCORS: true, logging: false, width: content.scrollWidth, height: content.scrollHeight, windowWidth: content.scrollWidth, windowHeight: content.scrollHeight }); const imgData = canvas.toDataURL('image/png'); // Create a new PDF document (A4 size, portrait) const pdf = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const canvasWidth = canvas.width; const canvasHeight = canvas.height; const ratio = canvasHeight / canvasWidth; const imgWidth = pdfWidth - 40; // With some margin const imgHeight = imgWidth * ratio; let heightLeft = imgHeight; let position = 20; // Top margin // Add the image to the PDF, handling pagination if necessary pdf.addImage(imgData, 'PNG', 20, position, imgWidth, imgHeight); heightLeft -= (pdfHeight - 40); while (heightLeft > 0) { position = -heightLeft - 20; pdf.addPage(); pdf.addImage(imgData, 'PNG', 20, position, imgWidth, imgHeight); heightLeft -= (pdfHeight - 40); } // Save the PDF pdf.save('Local-SEO-Performance-Report.pdf'); } catch (error) { console.error("Error generating PDF:", error); alert("Could not generate PDF. Please try again."); } finally { // Clean up: hide the PDF title and restore the button state pdfTitle.style.display = 'none'; downloadBtn.textContent = 'Download as PDF'; downloadBtn.disabled = false; } } // --- TABBING LOGIC --- const tabs = ['dashboard', 'config']; let currentTabIndex = 0; window.showTab = (tabName) => { currentTabIndex = tabs.indexOf(tabName); tabs.forEach(t => { document.getElementById(`${t}Tab`).classList.toggle('active', t === tabName); document.getElementById(`btn-${t}`).classList.toggle('active', t === tabName); document.getElementById(`btn-${t}`).classList.toggle('inactive', t !== tabName); }); updateNavButtons(); }; function updateNavButtons() { prevBtn.style.visibility = (currentTabIndex === 0) ? 'hidden' : 'visible'; nextBtn.style.visibility = (currentTabIndex === tabs.length - 1) ? 'hidden' : 'visible'; } // --- EVENT LISTENER ATTACHMENT --- updateDashboardBtn.addEventListener('click', handleUpdate); downloadPdfBtn.addEventListener('click', handlePdfDownload); addKeywordBtn.addEventListener('click', () => { seoData.keywords.push({ keyword: "", localRank: 0, organicRank: 0, volume: 0 }); renderKeywordConfigRows(); }); prevBtn.addEventListener('click', () => { if (currentTabIndex > 0) { showTab(tabs[currentTabIndex - 1]); } }); nextBtn.addEventListener('click', () => { if (currentTabIndex < tabs.length - 1) { showTab(tabs[currentTabIndex + 1]); } }); // --- INITIALIZATION --- renderDashboard(); renderConfigForm(); showTab('dashboard'); // Start on the dashboard tab });
Scroll to Top