TIF_E41221116/positif.html

456 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>SB Admin 2 - Tables</title>
<!-- Custom fonts for this template -->
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template -->
<link href="css/sb-admin-2.min.css" rel="stylesheet">
<link href="css/custom.css" rel="stylesheet">
<!-- Custom styles for this page -->
<link href="vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<style>
/* Hide DataTables search box and columns text */
.dataTables_filter { display: none !important; }
/* Style DataTables bottom controls to match tables.html */
.dataTables_length { margin-right: 1.5rem; }
.dataTables_length label { margin-bottom: 0; display: flex; align-items: center; gap: 8px; font-size: 0.875rem; color: #6c757d; }
.dataTables_length select { width: auto !important; display: inline-block !important; }
.dataTables_info { font-size: 0.875rem; color: #6c757d; padding-top: 0 !important; }
</style>
</head>
<body id="page-top">
<!-- Page Wrapper -->
<div id="wrapper">
<!-- Content Wrapper -->
<div id="content-wrapper" class="d-flex flex-column">
<!-- Main Content -->
<div id="content">
<!-- Topbar -->
<nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
<!-- Page Title - Centered -->
<div class="d-flex align-items-center justify-content-center w-100">
<h1 class="h3 mb-0 text-gray-800 dashboard-title">Positif</h1>
</div>
<!-- Topbar Navbar -->
<ul class="navbar-nav ml-auto">
</ul>
</nav>
<!-- End of Topbar -->
<!-- Begin Page Content -->
<div class="container-fluid">
<!-- Page Heading -->
<div class="d-flex align-items-center justify-content-between mb-4" style="position: relative;">
<a href="index.html" class="btn btn-secondary shadow-sm" style="display: inline-flex; align-items: center; gap: 8px;">
<i class="fas fa-arrow-left fa-sm"></i> Kembali
</a>
</div>
<!-- DataTales Example -->
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Uploaded Data (Positif)</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
<thead>
<tr>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>Belum ada data yang di-upload. Silakan upload file di halaman Dashboard.</td>
</tr>
</tbody>
</table>
</div>
<div class="mt-3">
<small id="uploadedFileName" class="text-muted d-block">&nbsp;</small>
</div>
</div>
</div>
</div>
<!-- /.container-fluid -->
</div>
<!-- End of Main Content -->
<!-- Footer -->
<footer class="sticky-footer bg-white">
<div class="container my-auto">
<div class="copyright text-center my-auto">
</div>
</div>
</footer>
<!-- End of Footer -->
</div>
<!-- End of Content Wrapper -->
</div>
<!-- End of Page Wrapper -->
<!-- Scroll to Top Button-->
<a class="scroll-to-top rounded" href="#page-top">
<i class="fas fa-angle-up"></i>
</a>
<!-- Logout Modal-->
<div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Ready to Leave?</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
<a class="btn btn-primary" href="login.html">Logout</a>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="js/sb-admin-2.min.js"></script>
<!-- Page level plugins -->
<script src="vendor/datatables/jquery.dataTables.min.js"></script>
<script src="vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script>
// Apply pending topbar search when this page loads
document.addEventListener('DOMContentLoaded', function () {
try {
const search = sessionStorage.getItem('topbar_search');
const target = sessionStorage.getItem('topbar_search_target');
if (search && target === 'tables') {
const input = document.querySelector('#tableSearchInput');
if (input) { input.value = search; const ev = new Event('input', { bubbles: true }); input.dispatchEvent(ev); }
// clear the pending search so it doesn't reapply on reload
sessionStorage.removeItem('topbar_search');
sessionStorage.removeItem('topbar_search_target');
}
} catch (e) { /* ignore storage errors */ }
});
</script>
<!-- Page level custom scripts -->
<!-- <script src="js/demo/datatables-demo.js"></script> -->
<!-- Load uploaded data from sessionStorage and populate the DataTable with POSITIVE rows only -->
<script>
// Fungsi untuk melarikan (escape) karakter HTML agar aman ditampilkan
function escapeHtml(text) {
return String(text === null || text === undefined ? '' : text)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
// Fungsi untuk mendeteksi indeks kolom sentimen positif berdasarkan header dan baris data
function detectPositiveColumnIndex(header, rows) {
// Mencoba mendeteksi berdasarkan nama header, tetapi diverifikasi dengan memeriksa nilai kolom (heuristik berbasis nilai)
if (!header || !header.length) return -1;
const headersLower = header.map(h => String(h || '').toLowerCase());
// Fungsi internal untuk mengecek apakah suatu nilai adalah label sentimen
function isLabelValue(v) {
if (v === null || v === undefined) return false;
const s = String(v).trim().toLowerCase();
if (!s) return false;
if (/^(positif|positive|pos|p|1|true|negatif|negative|neg|n|0|false)$/i.test(s)) return true;
if (s.includes('positif') || s.includes('positive') || s.includes('negatif') || s.includes('negative')) return true;
return false;
}
// Periksa kandidat header terlebih dahulu dan hitung tingkat kecocokan
const headerCandidates = [];
for (let i = 0; i < headersLower.length; i++) {
if (/\b(sentim|sentimen|sentiment|label|kelas|predik|hasil|pelabel|pelabelan)\b/i.test(headersLower[i])) headerCandidates.push(i);
}
// Fungsi internal untuk menghitung tingkat kecocokan label pada kolom
function columnLabelMatchRate(idx) {
let match = 0, total = 0;
for (const r of rows) {
const v = r[idx];
if (v !== null && v !== undefined && String(v).trim() !== '') {
total++; if (isLabelValue(v)) match++; }
}
return total === 0 ? 0 : match / total;
}
let bestIdx = -1, bestRate = 0;
for (const idx of headerCandidates) {
const rate = columnLabelMatchRate(idx);
if (rate > bestRate) { bestRate = rate; bestIdx = idx; }
}
// memerlukan tingkat kecocokan yang lebih ketat untuk menerima kandidat header (mengurangi positif palsu)
if (bestRate >= 0.25) return bestIdx;
// Cadangan: pindai semua kolom dan pilih satu dengan tingkat kecocokan label tertinggi
for (let i = 0; i < header.length; i++) {
const rate = columnLabelMatchRate(i);
if (rate > bestRate) { bestRate = rate; bestIdx = i; }
}
if (bestRate >= 0.25) return bestIdx;
// Sebagai cadangan terakhir, gunakan heuristik sebelumnya yang mencari banyak nilai seperti positif
const scores = new Array(header.length).fill(0);
function isPos(v) {
if (v === null || v === undefined) return false;
const s = String(v).toLowerCase();
return /^(positif|positive|pos|p|1|true)$/i.test(s) || s.includes('positif') || s.includes('positive');
}
for (const r of rows) {
for (let i = 0; i < header.length; i++) if (isPos(r[i])) scores[i]++;
}
let best = -1, bestScore = 0;
for (let i = 0; i < scores.length; i++) { if (scores[i] > bestScore) { best = i; bestScore = scores[i]; } }
if (bestScore > 0) return best;
return -1; // tidak ditemukan
}
// Fungsi untuk mengecek apakah suatu nilai menunjukkan sentimen positif
function isPositiveValue(v) {
if (v === null || v === undefined) return false;
const s = String(v).trim().toLowerCase();
// cocokkan kata utuh atau indikator numerik; hindari kecocokan substring di dalam teks panjang
return /\b(positif|positive|pos|p)\b/i.test(s) || /^(1|true)$/i.test(s);
}
// Penilaian sentimen heuristik berdasarkan daftar kata kunci (Bahasa Indonesia + Inggris)
function sentimentScoreRow(row, header) {
const candidates = ['text','teks','isi','tweet','message','review','komentar','comment','isi_pesan','judul'];
let text = '';
for (let i = 0; i < header.length; i++) {
const h = String(header[i] || '').toLowerCase();
if (candidates.some(c => h.includes(c))) { text = String(row[i] || ''); break; }
}
if (!text) text = (row || []).join(' ');
const s = String(text).toLowerCase();
const positiveWords = ['bagus','baik','senang','suka','puas','terbaik','mantap','bagus sekali','cinta','positif','recommend','love','great','excellent','amazing','happy'];
const negativeWords = ['buruk','jelek','sedih','benci','tidak suka','gagal','buruk sekali','hate','bad','terrible','poor','sad','angry'];
let score = 0;
for (const w of positiveWords) { if (s.indexOf(w) !== -1) score++; }
for (const w of negativeWords) { if (s.indexOf(w) !== -1) score--; }
return score;
}
// Fungsi untuk mengecek apakah baris data memiliki sentimen positif berdasarkan skor heuristik
function isPositiveBySentiment(row, header) {
return sentimentScoreRow(row, header) > 0;
}
// Fungsi utama untuk merender tabel data positif
function renderPositifDataTable() {
try {
const raw = sessionStorage.getItem('uploadedTableData');
const fileEl = document.getElementById('uploadedFileName');
const countEl = document.getElementById('positifCount');
if (!raw) {
if (fileEl) fileEl.textContent = '';
if (countEl) countEl.textContent = '';
if ($.fn.DataTable.isDataTable('#dataTable')) {
try { $('#dataTable').DataTable().clear().destroy(); } catch (e) { }
}
const thead = document.querySelector('#dataTable thead');
const tbody = document.querySelector('#dataTable tbody');
thead.innerHTML = '<tr><th>&nbsp;</th></tr>';
tbody.innerHTML = '<tr><td>Belum ada data yang di-upload. Silakan upload file di halaman Dashboard.</td></tr>';
// Inisialisasi DataTable kosong tanpa fitur pencarian
if (!$.fn.DataTable.isDataTable('#dataTable')) {
$('#dataTable').DataTable({
searching: false,
ordering: false,
info: false,
paging: false,
language: {
zeroRecords: 'Belum ada data yang di-upload. Silakan upload file di halaman Dashboard.'
}
});
}
return false;
}
const payload = JSON.parse(raw);
if (!payload || !payload.header || !payload.rows) return false;
const header = payload.header;
const rows = payload.rows;
if (fileEl) fileEl.textContent = payload.fileName || '';
// deteksi kolom positif (disimpan untuk referensi) tetapi pemilihan dilakukan oleh heuristik sentimen
const posIdx = detectPositiveColumnIndex(header, rows);
const thead = document.querySelector('#dataTable thead');
const tfoot = document.querySelector('#dataTable tfoot');
const tbody = document.querySelector('#dataTable tbody');
// membangun header dan footer tabel
const headRow = document.createElement('tr');
const footRow = document.createElement('tr');
for (const h of header) {
const th = document.createElement('th'); th.textContent = String(h);
headRow.appendChild(th);
const thf = document.createElement('th'); thf.textContent = String(h);
footRow.appendChild(thf);
}
thead.innerHTML = ''; thead.appendChild(headRow);
if (tfoot) { tfoot.innerHTML = ''; tfoot.appendChild(footRow); }
// memfilter hanya baris data positif
const positiveRows = [];
if (posIdx === -1) {
console.error('Kolom Sentiment tidak ditemukan');
} else {
for (const r of rows) {
const val = String(r[posIdx] || '').trim().toLowerCase();
if (val === 'positif') {
positiveRows.push(r);
}
}
}
// memperbarui jumlah data positif di UI
if (countEl) {
countEl.textContent = 'Positif: ' + positiveRows.length + ' / ' + rows.length;
}
// membangun isi (body) tabel
let bodyHtml = '';
if (positiveRows.length === 0) {
bodyHtml = '<tr><td>Tidak ada baris dengan sentimen positif yang terdeteksi.</td></tr>';
} else {
for (const r of positiveRows) {
bodyHtml += '<tr>';
for (let i = 0; i < header.length; i++) {
const cell = i < r.length ? escapeHtml(r[i]) : '';
// jika ini adalah kolom sentimen/label yang terdeteksi, render sebagai badge
if (i === posIdx) {
const rawVal = i < r.length ? String(r[i]) : '';
const isPosVal = isPositiveValue(rawVal);
const badgeClass = isPosVal ? 'pos' : 'neg';
bodyHtml += '<td><span class="sent-badge ' + badgeClass + '">' + escapeHtml(rawVal) + '</span></td>';
} else {
bodyHtml += '<td>' + cell + '</td>';
}
}
bodyHtml += '</tr>';
}
}
tbody.innerHTML = bodyHtml;
// menginisialisasi ulang DataTables dengan aman dan menerapkan label yang sama
if ($.fn.DataTable.isDataTable('#dataTable')) {
try { $('#dataTable').DataTable().clear().destroy(); } catch (e) { /* abaikan */ }
}
$('#dataTable').DataTable({
dom: 'rt<"d-flex align-items-center justify-content-between mt-2" <"d-flex align-items-center"li> p>',
ordering: false,
pageLength: 10,
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
pagingType: 'simple_numbers',
searching: false,
language: {
lengthMenu: 'Tampilkan _MENU_',
info: 'Menampilkan _START__END_ dari _TOTAL_',
infoEmpty: 'Menampilkan 00 dari 0',
zeroRecords: 'Tidak ada data',
paginate: { previous: 'Previous', next: 'Next' },
search: 'Cari:'
}
});
return true;
} catch (e) {
console.warn('renderPositifDataTable error', e);
return false;
}
}
// Fungsi pembantu: melakukan pencarian pada DataTable atau cadangan
function performTableSearch(q) {
try {
if ($.fn.DataTable.isDataTable('#dataTable')) {
const dt = $('#dataTable').DataTable(); dt.search(q).draw();
} else {
// cadangan: filter DOM sederhana
const tbody = document.querySelector('#dataTable tbody');
if (!tbody) return;
const rows = tbody.querySelectorAll('tr');
const qq = String(q).toLowerCase();
rows.forEach(function (r) {
const text = (r.textContent || '').toLowerCase(); r.style.display = text.includes(qq) ? '' : 'none';
});
}
} catch (e) { }
}
// run on page load
document.addEventListener('DOMContentLoaded', function () { try { renderPositifDataTable(); } catch (e){} });
// consume topbar search when arriving here (target 'positif')
document.addEventListener('DOMContentLoaded', function () {
try {
const q = sessionStorage.getItem('topbar_search');
const t = sessionStorage.getItem('topbar_search_target');
if (q && t === 'positif') {
// small delay to allow DataTable to initialize
setTimeout(function () { try { performTableSearch(q); } catch (e) {} }, 50);
sessionStorage.removeItem('topbar_search'); sessionStorage.removeItem('topbar_search_target');
}
} catch (e) {}
});
// update when uploaded data changes in other tabs
window.addEventListener('storage', function (e) {
if (e.key === 'uploadedTableData' || e.key === 'uploadedTableData_update') {
renderPositifDataTable();
}
});
</script>
</body>
</html>