Sentiment Analysis Dashboard

Net Sentiment Score

Total Feedback

0

Positive

0

Negative

0

Sentiment Trend (Last 7 Days)

Analysis

Sentiment by Topic

Feedback by Source

Recent Feedback Log

CommentSourceTopicSentiment

Log New Feedback

All Feedback Records

DateCommentSentimentAction

Error: A required library is missing.

'; return; } // --- DATA MANAGEMENT --- let feedbackData = JSON.parse(localStorage.getItem('sa_feedback')) || []; const getSampleData = () => { const today = new Date('2025-07-09'); const d = (daysAgo) => new Date(today.getTime() - daysAgo * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; return [ { id: 1, text: "The delivery was so fast! I got my order a day early.", sentiment: 'Positive', source: 'Website Review', topic: 'Shipping', date: d(0) }, { id: 2, text: "Your customer service agent was incredibly helpful.", sentiment: 'Positive', source: 'Support Chat', topic: 'Customer Service', date: d(0) }, { id: 3, text: "The app keeps crashing. Very frustrating!", sentiment: 'Negative', source: 'App Store', topic: 'App Experience', date: d(0) }, { id: 4, text: "Product quality is amazing, feels very durable.", sentiment: 'Positive', source: 'Website Review', topic: 'Product Quality', date: d(1) }, { id: 5, text: "It's an okay product. Does the job.", sentiment: 'Neutral', source: 'Website Review', topic: 'Product Quality', date: d(1) }, { id: 6, text: "Why is shipping so expensive?", sentiment: 'Negative', source: 'Twitter', topic: 'Shipping', date: d(2) }, ]; }; if (feedbackData.length === 0) feedbackData = getSampleData(); const saveState = () => localStorage.setItem('sa_feedback', JSON.stringify(feedbackData)); // --- CHART INSTANCES & UTILITIES --- let overallChart, trendChart, topicChart, sourceChart; const tabButtons = document.querySelectorAll('.sa-tab-button'); const tabContents = document.querySelectorAll('.sa-tab-content'); const nextBtn = document.getElementById('sa-next-btn'); const prevBtn = document.getElementById('sa-prev-btn'); // --- RENDER FUNCTIONS --- const renderAll = () => { try { renderKPIs(); renderOverallChart(); renderTrendChart(); renderTopicChart(); renderSourceChart(); renderFeedbackLog(); renderManageTab(); updateNavButtons(); } catch(error) { console.error("Dashboard rendering failed:", error); } }; const renderKPIs = () => { const positiveCount = feedbackData.filter(f => f.sentiment === 'Positive').length; const negativeCount = feedbackData.filter(f => f.sentiment === 'Negative').length; const total = feedbackData.length; document.getElementById('sa-total-kpi').textContent = total; document.getElementById('sa-positive-kpi').textContent = positiveCount; document.getElementById('sa-negative-kpi').textContent = negativeCount; }; const renderOverallChart = () => { const positiveCount = feedbackData.filter(f => f.sentiment === 'Positive').length; const negativeCount = feedbackData.filter(f => f.sentiment === 'Negative').length; const total = positiveCount + negativeCount; const netScore = total > 0 ? Math.round(((positiveCount - negativeCount) / total) * 100) : 0; const options = { chart: { type: 'radialBar', height: 300 }, series: [netScore], plotOptions: { radialBar: { startAngle: -135, endAngle: 135, hollow: { size: '70%' }, dataLabels: { name: { show: false }, value: { show: true, fontSize: '36px', formatter: val => `${val}` } } } }, stroke: { lineCap: 'round' }, colors: [netScore >= 0 ? 'var(--sa-positive-color)' : 'var(--sa-negative-color)'] }; if(overallChart) overallChart.destroy(); document.querySelector("#sa-overall-chart").innerHTML = ''; overallChart = new ApexCharts(document.querySelector("#sa-overall-chart"), options); overallChart.render(); }; const renderTrendChart = () => { const last7Days = Array.from({ length: 7 }, (_, i) => { const d = new Date('2025-07-09'); d.setDate(d.getDate() - i); return d.toISOString().split('T')[0]; }).reverse(); const positiveTrend = last7Days.map(day => feedbackData.filter(f => f.date === day && f.sentiment === 'Positive').length); const negativeTrend = last7Days.map(day => feedbackData.filter(f => f.date === day && f.sentiment === 'Negative').length); const options = { chart: { type: 'area', height: 300, toolbar: { show: false }, stacked:true }, series: [ { name: 'Positive', data: positiveTrend }, { name: 'Negative', data: negativeTrend } ], xaxis: { type: 'datetime', categories: last7Days }, dataLabels: { enabled: false }, stroke: { curve: 'smooth', width: 2 }, colors: ['var(--sa-positive-color)', 'var(--sa-negative-color)'], legend: { position: 'top' } }; if(trendChart) trendChart.destroy(); document.querySelector("#sa-trend-chart").innerHTML = ''; trendChart = new ApexCharts(document.querySelector("#sa-trend-chart"), options); trendChart.render(); }; const renderTopicChart = () => { const sentimentByTopic = feedbackData.reduce((acc, f) => { if (f.sentiment === 'Neutral') return acc; if (!acc[f.topic]) acc[f.topic] = { positive: 0, negative: 0 }; if (f.sentiment === 'Positive') acc[f.topic].positive++; else if (f.sentiment === 'Negative') acc[f.topic].negative++; return acc; }, {}); const netScores = Object.entries(sentimentByTopic).map(([topic, counts]) => { const total = counts.positive + counts.negative; const score = total > 0 ? ((counts.positive - counts.negative) / total) * 100 : 0; return { x: topic, y: score.toFixed(0) }; }); const options = { chart: { type: 'bar', height: 350, toolbar: { show: false } }, series: [{ name: 'Net Score', data: netScores.map(d => d.y) }], xaxis: { categories: netScores.map(d => d.x) }, plotOptions: { bar: { horizontal: true, distributed: true } }, colors: netScores.map(d => d.y >= 0 ? 'var(--sa-positive-color)' : 'var(--sa-negative-color)'), legend: { show: false } }; if(topicChart) topicChart.destroy(); document.querySelector("#sa-topic-chart").innerHTML = ''; topicChart = new ApexCharts(document.querySelector("#sa-topic-chart"), options); topicChart.render(); }; const renderSourceChart = () => { const feedbackBySource = feedbackData.reduce((acc, f) => { acc[f.source] = (acc[f.source] || 0) + 1; return acc; }, {}); const options = { chart: { type: 'donut', height: 350 }, series: Object.values(feedbackBySource), labels: Object.keys(feedbackBySource), legend: { position: 'bottom' } }; if(sourceChart) sourceChart.destroy(); document.querySelector("#sa-source-chart").innerHTML = ''; sourceChart = new ApexCharts(document.querySelector("#sa-source-chart"), options); sourceChart.render(); }; const renderFeedbackLog = () => { const tbody = document.getElementById('sa-log-tbody'); tbody.innerHTML = ''; feedbackData.sort((a,b)=> new Date(b.date) - new Date(a.date)).slice(0, 5).forEach(f => { const sentimentClass = `sentiment-${f.sentiment.toLowerCase()}`; tbody.innerHTML += ` ${f.text} ${f.source} ${f.topic} ${f.sentiment} `; }); }; const renderManageTab = () => { const tbody = document.getElementById('sa-manage-tbody'); tbody.innerHTML = ''; feedbackData.forEach(f => { tbody.innerHTML += `${f.date}${f.text}${f.sentiment}`; }); const sources = [...new Set(feedbackData.map(f => f.source))]; document.getElementById('sa-source-list').innerHTML = sources.map(s => ``).join(''); const topics = [...new Set(feedbackData.map(f => f.topic))]; document.getElementById('sa-topic-list').innerHTML = topics.map(t => ``).join(''); }; // --- EVENT HANDLING --- const switchTab = (tabId) => { tabContents.forEach(c => c.style.display = 'none'); tabButtons.forEach(b => b.classList.remove('active')); const activeContent = document.getElementById(tabId); const activeButton = document.querySelector(`.sa-tab-button[data-tab="${tabId}"]`); if (activeContent && activeButton) { activeContent.style.display = 'block'; activeButton.classList.add('active'); } updateNavButtons(); }; const updateNavButtons = () => { const i = [...tabButtons].findIndex(b => b.classList.contains('active')); prevBtn.disabled = i === 0; nextBtn.disabled = i === tabButtons.length - 1; }; tabButtons.forEach(b => b.addEventListener('click', () => switchTab(b.dataset.tab))); nextBtn.addEventListener('click', () => { const i = [...tabButtons].findIndex(b=>b.classList.contains('active')); if (i < tabButtons.length - 1) switchTab(tabButtons[i+1].dataset.tab); }); prevBtn.addEventListener('click', () => { const i = [...tabButtons].findIndex(b=>b.classList.contains('active')); if (i > 0) switchTab(tabButtons[i-1].dataset.tab); }); document.getElementById('sa-feedback-form').addEventListener('submit', e => { e.preventDefault(); feedbackData.unshift({ id: 'f' + Date.now(), text: document.getElementById('sa-form-text').value, sentiment: document.getElementById('sa-form-sentiment').value, source: document.getElementById('sa-form-source').value, topic: document.getElementById('sa-form-topic').value, date: document.getElementById('sa-form-date').value }); saveState(); renderAll(); e.target.reset(); switchTab('sa-dashboard-tab'); }); document.getElementById('sa-manage-tbody').addEventListener('click', e => { if(e.target.tagName === 'BUTTON') { const id = e.target.dataset.id; if(confirm('Are you sure you want to delete this feedback?')) { feedbackData = feedbackData.filter(f => f.id != id); saveState(); renderAll(); } } }); // --- PDF EXPORT --- document.getElementById('sa-download-pdf-btn').addEventListener('click', function() { const btn = this; btn.textContent = 'Generating...'; btn.disabled = true; const content = document.getElementById('sa-pdf-capture-area'); html2canvas(content, { scale: 2, backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--sa-light-color') }).then(canvas => { const doc = new jsPDF(); const imgData = canvas.toDataURL('image/png'); doc.setFontSize(18); doc.text('Sentiment Analysis Report', 14, 22); const imgWidth = doc.internal.pageSize.getWidth() - 28; const imgHeight = (canvas.height * imgWidth) / canvas.width; doc.addImage(imgData, 'PNG', 14, 30, imgWidth, imgHeight); doc.save('Sentiment_Analysis_Report.pdf'); }).finally(() => { btn.textContent = 'Download Report'; btn.disabled = false; }); }); // --- INITIALIZATION --- renderAll(); });
Scroll to Top