// Empty data initially - will be populated from uploaded files let sentimentData = []; let filteredData = [...sentimentData]; let currentPage = 1; let itemsPerPage = 10; // Initialize charts let sentimentChart; function initCharts() { const ctx = document.getElementById("sentimentChart").getContext("2d"); sentimentChart = new Chart(ctx, { type: "bar", data: { labels: ["Sentimen Positif", "Sentimen Negatif"], datasets: [ { label: "Jumlah Data", data: [0, 0], backgroundColor: [ "rgba(16, 185, 129, 0.8)", "rgba(239, 68, 68, 0.8)", ], borderColor: ["rgb(16, 185, 129)", "rgb(239, 68, 68)"], borderWidth: 2, borderRadius: 8, borderSkipped: false, }, ], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false, }, }, scales: { y: { beginAtZero: true, grid: { color: "rgba(0, 0, 0, 0.1)", }, ticks: { font: { size: 12, weight: "bold", }, }, }, x: { grid: { display: false, }, ticks: { font: { size: 12, weight: "bold", }, }, }, }, animation: { duration: 2000, easing: "easeOutBounce", }, }, }); } // Character counter for textarea document.addEventListener("DOMContentLoaded", function () { const textInput = document.getElementById("textInput"); const charCount = document.getElementById("charCount"); textInput.addEventListener("input", function () { const count = this.value.length; charCount.textContent = `${count} karakter`; if (count > 500) { charCount.classList.add("text-red-500"); charCount.classList.remove("text-gray-500"); } else { charCount.classList.add("text-gray-500"); charCount.classList.remove("text-red-500"); } }); }); // Enhanced predict sentiment function async function predictSentiment() { const text = document.getElementById("textInput").value.trim(); if (!text) { showNotification("Silakan masukkan teks untuk dianalisis", "warning"); return; } // Show loading state document.getElementById("predictionResult").innerHTML = `

Menganalisis sentimen...

`; try { // Panggil Flask API untuk prediksi const response = await fetch("/predict", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ text }), }); const result = await response.json(); let sentiment = result.label || "Netral"; let gradientClass, iconClass; let confidence = Math.random() * 20 + 80; // Dummy confidence, bisa diupdate jika API mengirimkan if (sentiment === "Positif") { gradientClass = "from-green-400 to-emerald-600"; iconClass = "fas fa-smile"; } else if (sentiment === "Negatif") { gradientClass = "from-red-400 to-pink-600"; iconClass = "fas fa-frown"; } else { gradientClass = "from-yellow-400 to-orange-500"; iconClass = "fas fa-meh"; } document.getElementById("predictionResult").innerHTML = `
${sentiment}
`; } catch (error) { document.getElementById("predictionResult").innerHTML = `
Terjadi kesalahan saat memproses prediksi.
`; showNotification( "Gagal memprediksi sentimen. Pastikan server Flask berjalan.", "error" ); } } // Search functionality function searchTable() { const searchTerm = document .getElementById("searchInput") .value.toLowerCase(); const sentimentFilter = document.getElementById("sentimentFilter").value; filteredData = sentimentData.filter((item) => { const matchesSearch = item.text.toLowerCase().includes(searchTerm) || item.sentiment.toLowerCase().includes(searchTerm); const matchesFilter = !sentimentFilter || item.sentiment === sentimentFilter; return matchesSearch && matchesFilter; }); currentPage = 1; updateTable(); updatePagination(); } // Filter functionality function filterTable() { searchTable(); // Reuse search logic } // Change items per page function changeItemsPerPage() { itemsPerPage = parseInt(document.getElementById("itemsPerPage").value); currentPage = 1; updateTable(); updatePagination(); } // Pagination functions function changePage(direction) { const totalPages = Math.ceil(filteredData.length / itemsPerPage); const newPage = currentPage + direction; if (newPage >= 1 && newPage <= totalPages) { currentPage = newPage; updateTable(); updatePagination(); } } function goToPage(page) { currentPage = page; updateTable(); updatePagination(); } // Enhanced update table function function updateTable() { const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const pageData = filteredData.slice(startIndex, endIndex); const tableBody = document.getElementById("dataTable"); tableBody.innerHTML = pageData .map( (item, index) => ` ${ startIndex + index + 1 }
${item.text}
${item.sentiment} ` ) .join(""); // Update showing info const showingStart = filteredData.length === 0 ? 0 : startIndex + 1; const showingEnd = Math.min(endIndex, filteredData.length); document.getElementById("showingStart").textContent = showingStart; document.getElementById("showingEnd").textContent = showingEnd; document.getElementById("totalItems").textContent = filteredData.length; } // Update pagination function updatePagination() { const totalPages = Math.ceil(filteredData.length / itemsPerPage); const pageNumbers = document.getElementById("pageNumbers"); // Update prev/next buttons document.getElementById("prevBtn").disabled = currentPage === 1; document.getElementById("nextBtn").disabled = currentPage === totalPages || totalPages === 0; // Generate page numbers let paginationHTML = ""; const maxVisiblePages = 5; let startPage = Math.max( 1, currentPage - Math.floor(maxVisiblePages / 2) ); let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); if (endPage - startPage + 1 < maxVisiblePages) { startPage = Math.max(1, endPage - maxVisiblePages + 1); } for (let i = startPage; i <= endPage; i++) { paginationHTML += ` `; } pageNumbers.innerHTML = paginationHTML; } // Enhanced file upload function handleFileUpload(event) { const file = event.target.files[0]; if (!file) return; showNotification("Mengupload file...", "info"); const reader = new FileReader(); reader.onload = function (e) { try { const data = new Uint8Array(e.target.result); const workbook = XLSX.read(data, { type: "array" }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet); sentimentData = jsonData.map((row, index) => ({ text: row.Teks || row.Text || row.teks || `Sample text ${index + 1}`, sentiment: row.Sentimen || row.Sentiment || (Math.random() > 0.5 ? "Positif" : "Negatif"), })); filteredData = [...sentimentData]; currentPage = 1; updateTable(); updatePagination(); updateStats(); showNotification( `Berhasil mengimport ${sentimentData.length} data dari Excel!`, "success" ); } catch (error) { showNotification( "Error membaca file Excel. Pastikan format file benar.", "error" ); } }; reader.readAsArrayBuffer(file); } // Enhanced export function function exportToExcel() { const ws = XLSX.utils.json_to_sheet( filteredData.map((item, index) => ({ No: index + 1, Teks: item.text, Sentimen: item.sentiment, })) ); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Data Sentimen"); XLSX.writeFile( wb, `analisis_sentimen_${new Date().toISOString().split("T")[0]}.xlsx` ); showNotification("Data berhasil diexport ke Excel!", "success"); } // Update stats and chart function updateStats() { const total = sentimentData.length; const positive = sentimentData.filter( (item) => item.sentiment === "Positif" ).length; const negative = total - positive; const accuracy = 76; // Static accuracy // Calculate percentages const positivePercent = total > 0 ? Math.round((positive / total) * 100) : 0; const negativePercent = total > 0 ? Math.round((negative / total) * 100) : 0; // Update stats cards document.getElementById("totalComments").textContent = total.toLocaleString(); document.getElementById("positiveCount").textContent = positive.toLocaleString(); document.getElementById("negativeCount").textContent = negative.toLocaleString(); document.getElementById("accuracy").textContent = accuracy + "%"; // Update percentage text in cards document.getElementById("positivePercent").textContent = positivePercent + "% dari total"; document.getElementById("negativePercent").textContent = negativePercent + "% dari total"; // Update progress bars document.getElementById("positiveProgress").style.width = positivePercent + "%"; document.getElementById("negativeProgress").style.width = negativePercent + "%"; // Update chart if (sentimentChart) { sentimentChart.data.datasets[0].data = [positive, negative]; sentimentChart.update(); } // Update percentage displays in chart section document.querySelector( ".grid.grid-cols-2.gap-4.mt-6 .text-center:first-child .text-2xl" ).textContent = positivePercent + "%"; document.querySelector( ".grid.grid-cols-2.gap-4.mt-6 .text-center:last-child .text-2xl" ).textContent = negativePercent + "%"; } // Notification system function showNotification(message, type = "info") { const notification = document.createElement("div"); const bgColor = { success: "bg-green-500", error: "bg-red-500", warning: "bg-yellow-500", info: "bg-blue-500", }[type]; notification.className = `fixed top-4 right-4 ${bgColor} text-white px-6 py-4 rounded-xl shadow-lg z-50 transform translate-x-full transition-transform duration-300`; notification.innerHTML = `
${message}
`; document.body.appendChild(notification); setTimeout(() => { notification.classList.remove("translate-x-full"); }, 100); setTimeout(() => { notification.classList.add("translate-x-full"); setTimeout(() => { document.body.removeChild(notification); }, 300); }, 3000); } // Tab switching functionality function switchTab(tabName) { // Hide all tab contents document.getElementById("predictionContent").classList.add("hidden"); document.getElementById("dataContent").classList.add("hidden"); // Reset all tab buttons document.getElementById("predictionTab").className = "tab-btn px-6 py-3 rounded-xl font-semibold transition-all duration-300 text-gray-600 hover:text-gray-800"; document.getElementById("dataTab").className = "tab-btn px-6 py-3 rounded-xl font-semibold transition-all duration-300 text-gray-600 hover:text-gray-800"; // Show selected tab content and activate button if (tabName === "prediction") { document .getElementById("predictionContent") .classList.remove("hidden"); document.getElementById("predictionTab").className = "tab-btn px-6 py-3 rounded-xl font-semibold transition-all duration-300 bg-gradient-to-r from-indigo-500 to-purple-600 text-white shadow-lg"; document.getElementById("dataActions").classList.add("hidden"); } else if (tabName === "data") { document.getElementById("dataContent").classList.remove("hidden"); document.getElementById("dataTab").className = "tab-btn px-6 py-3 rounded-xl font-semibold transition-all duration-300 bg-gradient-to-r from-indigo-500 to-purple-600 text-white shadow-lg"; document.getElementById("dataActions").classList.remove("hidden"); } } // Initialize on page load document.addEventListener("DOMContentLoaded", function () { initCharts(); updateTable(); updatePagination(); updateStats(); });