Spaces:
Sleeping
Sleeping
| /* ββ AIMS Insight β Dashboard JS ββ */ | |
| let donutChart = null; | |
| let barChart = null; | |
| /* βββββββββββββββββββββββββββββββ | |
| FILTER RESET | |
| βββββββββββββββββββββββββββββββ */ | |
| function resetFilters() { | |
| document.getElementById('startDate').value = ''; | |
| document.getElementById('endDate').value = ''; | |
| document.getElementById('filterTopic').value = 'all'; | |
| document.getElementById('filterSentiment').value = 'all'; | |
| setTimeout(() => { | |
| loadData(); | |
| }, 50); | |
| } | |
| /* βββββββββββββββββββββββββββββββ | |
| LOAD DATA FROM API | |
| βββββββββββββββββββββββββββββββ */ | |
| async function loadData() { | |
| const params = new URLSearchParams({ | |
| start_date: document.getElementById('startDate').value, | |
| end_date: document.getElementById('endDate').value, | |
| topic: document.getElementById('filterTopic').value, | |
| sentiment: document.getElementById('filterSentiment').value | |
| }); | |
| try { | |
| const res = await fetch(`/api/dashboard-data?${params}`); | |
| const data = await res.json(); | |
| updateKPIs(data); | |
| updateDonut(data.sentiment); | |
| updateBar(data.topics); | |
| updateTable(data.recent); | |
| } catch (e) { | |
| console.error('Dashboard load error:', e); | |
| } | |
| } | |
| /* βββββββββββββββββββββββββββββββ | |
| KPI UPDATE | |
| βββββββββββββββββββββββββββββββ */ | |
| function updateKPIs(data) { | |
| document.getElementById('kpiTotal').textContent = data.total; | |
| document.getElementById('kpiSatisfaction').textContent = data.satisfaction + '%'; | |
| document.getElementById('kpiNeutral').textContent = data.sentiment.Neutral; | |
| document.getElementById('kpiNegative').textContent = data.sentiment.Negative; | |
| } | |
| /* βββββββββββββββββββββββββββββββ | |
| DONUT CHART | |
| βββββββββββββββββββββββββββββββ */ | |
| function updateDonut(sentiment) { | |
| const ctx = document.getElementById('donutChart').getContext('2d'); | |
| const dataArr = [ | |
| sentiment.Positive, | |
| sentiment.Neutral, | |
| sentiment.Negative | |
| ]; | |
| if (donutChart) donutChart.destroy(); | |
| donutChart = new Chart(ctx, { | |
| type: 'doughnut', | |
| data: { | |
| labels: ['Positive', 'Neutral', 'Negative'], | |
| datasets: [{ | |
| data: dataArr, | |
| backgroundColor: ['#28a745', '#6c757d', '#dc3545'], | |
| borderWidth: 3, | |
| borderColor: '#fff', | |
| hoverOffset: 6 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| cutout: '65%', | |
| plugins: { | |
| legend: { | |
| position: 'bottom', | |
| labels: { padding: 14, font: { size: 12 } } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| /* βββββββββββββββββββββββββββββββ | |
| BAR CHART | |
| βββββββββββββββββββββββββββββββ */ | |
| function updateBar(topics) { | |
| const ctx = document.getElementById('barChart').getContext('2d'); | |
| const sorted = Object.entries(topics).sort((a, b) => b[1] - a[1]); | |
| const labels = sorted.map(([k]) => k); | |
| const values = sorted.map(([, v]) => v); | |
| const colors = values.map((_, i) => { | |
| const opacity = 1 - (i / Math.max(values.length, 1)) * 0.5; | |
| return `rgba(128,0,0,${opacity})`; | |
| }); | |
| if (barChart) barChart.destroy(); | |
| barChart = new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels, | |
| datasets: [{ | |
| label: 'Feedbacks', | |
| data: values, | |
| backgroundColor: colors, | |
| borderRadius: 6, | |
| borderSkipped: false | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { display: false } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| ticks: { stepSize: 1, font: { size: 11 } }, | |
| grid: { color: 'rgba(0,0,0,.04)' } | |
| }, | |
| x: { | |
| ticks: { font: { size: 11 } }, | |
| grid: { display: false } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| /* βββββββββββββββββββββββββββββββ | |
| TABLE UPDATE | |
| βββββββββββββββββββββββββββββββ */ | |
| function updateTable(rows) { | |
| const tbody = document.getElementById('recentTable'); | |
| if (!rows || rows.length === 0) { | |
| tbody.innerHTML = ` | |
| <tr> | |
| <td colspan="4" style="text-align:center; color:#aaa; padding:30px"> | |
| No data available for these filters. | |
| </td> | |
| </tr>`; | |
| return; | |
| } | |
| tbody.innerHTML = rows.map(r => { | |
| const label = | |
| r.sentiment_label === 'Positive' ? 'badge-positive' | |
| : r.sentiment_label === 'Negative' ? 'badge-negative' | |
| : 'badge-neutral'; | |
| const labelFR = | |
| r.sentiment_label === 'Positive' ? 'Positive' | |
| : r.sentiment_label === 'Negative' ? 'Negative' | |
| : 'Neutral'; | |
| const date = r.created_at ? r.created_at.slice(0, 16) : 'β'; | |
| const comment = (r.comment_text || '').length > 80 | |
| ? r.comment_text.slice(0, 80) + 'β¦' | |
| : r.comment_text; | |
| return ` | |
| <tr> | |
| <td style="color:#888; font-size:12px; white-space:nowrap">${date}</td> | |
| <td> | |
| <span style="background:#f3f0ef; padding:3px 9px; | |
| border-radius:6px; font-size:12px; font-weight:600; color:#555"> | |
| ${r.topic} | |
| </span> | |
| </td> | |
| <td class="comment-cell">${comment}</td> | |
| <td><span class="badge ${label}">${labelFR}</span></td> | |
| </tr>`; | |
| }).join(''); | |
| } | |
| /* βββββββββββββββββββββββββββββββ | |
| USER DROPDOWN MENU (PROFILE) | |
| βββββββββββββββββββββββββββββββ */ | |
| function toggleUserMenu() { | |
| const menu = document.getElementById("userMenu"); | |
| if (menu) menu.classList.toggle("show"); | |
| } | |
| // close dropdown when clicking outside | |
| document.addEventListener("click", function (e) { | |
| const badge = document.querySelector(".user-badge"); | |
| const menu = document.getElementById("userMenu"); | |
| if (!badge || !menu) return; | |
| if (!badge.contains(e.target)) { | |
| menu.classList.remove("show"); | |
| } | |
| }); | |
| /* βββββββββββββββββββββββββββββββ | |
| INIT | |
| βββββββββββββββββββββββββββββββ */ | |
| document.addEventListener('DOMContentLoaded', loadData); |