'use strict';
const _store = SM.loadData();
if (!_store) {
window.location.replace('upload');
throw new Error('No data — redirecting');
}
SM.injectLayout('nav-tweets');
document.addEventListener('DOMContentLoaded', function () {
const { rows, meta } = _store;
document.getElementById('topbarMeta').textContent =
`${meta.filename} — ${rows.length} tweets`;
let pageSize = 20;
let currentPage = 1;
let activeFilter = 'all';
let searchQuery = '';
let sortMode = 'default';
const pos = rows.filter(r => r.sentiment === 'Positif').length;
const neg = rows.filter(r => r.sentiment === 'Negatif').length;
const neu = rows.filter(r => r.sentiment === 'Netral').length;
// Update tab counts
document.getElementById('cntPos').textContent = pos;
document.getElementById('cntNeg').textContent = neg;
document.getElementById('cntNeu').textContent = neu;
function getFiltered() {
let result = rows;
if (activeFilter !== 'all') result = result.filter(r => r.sentiment === activeFilter);
if (searchQuery) {
const q = searchQuery.toLowerCase();
result = result.filter(r =>
(r.raw||'').toLowerCase().includes(q) ||
(r.username||'').toLowerCase().includes(q) ||
(r.location||'').toLowerCase().includes(q)
);
}
// Sort
if (sortMode === 'conf-desc') result = [...result].sort((a,b) => b.confidence - a.confidence);
else if (sortMode === 'conf-asc') result = [...result].sort((a,b) => a.confidence - b.confidence);
else if (sortMode === 'engage-desc') result = [...result].sort((a,b) => (b.engagement||0) - (a.engagement||0));
else if (sortMode === 'date-desc') result = [...result].sort((a,b) => (b.date||'').localeCompare(a.date||''));
else if (sortMode === 'date-asc') result = [...result].sort((a,b) => (a.date||'').localeCompare(b.date||''));
return result;
}
function formatDate(ds) {
if (!ds) return '';
const d = new Date(ds);
if (isNaN(d.getTime())) return ds.slice(0,16).replace('T',' ');
return d.toLocaleDateString('id-ID', {day:'numeric',month:'short',year:'numeric'}) +
' · ' + d.toLocaleTimeString('id-ID', {hour:'2-digit',minute:'2-digit'});
}
function initials(name) {
if (!name || name === '—') return '?';
return name.replace('@','').split(/[\s_]/)[0].slice(0,2).toUpperCase();
}
function avatarColor(name) {
const colors = ['#6c8fff','#a78bfa','#34d399','#f472b6','#fb923c','#60d9f9','#f87171','#fbbf24'];
let h = 0;
for (let c of (name||'')) h = (h * 31 + c.charCodeAt(0)) & 0xffffffff;
return colors[Math.abs(h) % colors.length];
}
function renderHashtags(text) {
return (text.match(/#(\w+)/g) || []).slice(0,5)
.map(t => `${SM.esc(t)}`).join('');
}
function highlightText(text, q) {
if (!q) return SM.esc(text);
const escaped = q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return SM.esc(text).replace(new RegExp(`(${escaped})`, 'gi'),
'$1');
}
function sentimentClass(s) {
return s === 'Positif' ? 'pos' : s === 'Negatif' ? 'neg' : 'neu';
}
function sentimentBadgeClass(s) {
return s === 'Positif' ? 'badge-pos' : s === 'Negatif' ? 'badge-neg' : 'badge-neu';
}
function renderConfBar(conf, sent) {
const pct = (conf * 100).toFixed(0);
const cls = sent === 'Positif' ? 'conf-fill-pos' : sent === 'Negatif' ? 'conf-fill-neg' : 'conf-fill-neu';
return `
${(conf*100).toFixed(1)}%
`;
}
function renderCard(r) {
const sc = sentimentClass(r.sentiment);
const sbc = sentimentBadgeClass(r.sentiment);
const col = avatarColor(r.username);
const init = initials(r.username);
const tags = renderHashtags(r.raw);
const hasMedia = r.media_url || r.photo_url || r.image_url;
const mediaURL = r.media_url || r.photo_url || r.image_url || '';
return `
${tags ? `
${tags}
` : ''}
${hasMedia ? `
` : ''}
`;
}
function render() {
const filtered = getFiltered();
const total = filtered.length;
const totalPages = Math.max(1, Math.ceil(total / pageSize));
currentPage = Math.min(currentPage, totalPages);
const start = (currentPage - 1) * pageSize;
const end = Math.min(start + pageSize, total);
const page = filtered.slice(start, end);
const feed = document.getElementById('tweetFeed');
if (!page.length) {
feed.innerHTML = `
Tidak ada tweet ditemukan
Coba ubah filter atau kata kunci pencarian
`;
} else {
feed.innerHTML = page.map(r => renderCard(r)).join('');
}
// Info
document.getElementById('tweetInfo').textContent =
total > 0
? `Menampilkan ${start + 1}–${end} dari ${total} data (total ${rows.length})`
: '';
// Pagination
renderPagination(totalPages);
// Stats pills
document.getElementById('tweetStats').innerHTML =
`${pos} Positif
${neg} Negatif
${neu} Netral`;
}
function renderPagination(totalPages) {
const pg = document.getElementById('tweetPagination');
if (totalPages <= 1) { pg.innerHTML = ''; return; }
const range = [];
range.push(1);
if (currentPage > 3) range.push('…');
for (let i = Math.max(2, currentPage-1); i <= Math.min(totalPages-1, currentPage+1); i++) range.push(i);
if (currentPage < totalPages - 2) range.push('…');
if (totalPages > 1) range.push(totalPages);
pg.innerHTML = `
${range.map(p => p === '…'
? `…`
: ``
).join('')}
`;
pg.querySelectorAll('[data-page]').forEach(btn => {
btn.addEventListener('click', () => { currentPage = +btn.dataset.page; render(); scrollToFeed(); });
});
const prev = pg.querySelector('#pgPrev');
const next = pg.querySelector('#pgNext');
if (prev) prev.addEventListener('click', () => { currentPage--; render(); scrollToFeed(); });
if (next) next.addEventListener('click', () => { currentPage++; render(); scrollToFeed(); });
}
function scrollToFeed() {
document.getElementById('tweetFeed').scrollIntoView({ behavior:'smooth', block:'start' });
}
// ── Filter tabs ──
document.querySelectorAll('.tl-tab').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tl-tab').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
activeFilter = btn.dataset.filter;
currentPage = 1;
render();
});
});
// ── Search ──
let searchTimer;
document.getElementById('tweetSearch').addEventListener('input', e => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
searchQuery = e.target.value.trim();
document.getElementById('searchClear').style.display = searchQuery ? 'flex' : 'none';
currentPage = 1;
render();
}, 220);
});
document.getElementById('searchClear').addEventListener('click', () => {
document.getElementById('tweetSearch').value = '';
searchQuery = '';
document.getElementById('searchClear').style.display = 'none';
currentPage = 1;
render();
});
// ── Sort ──
document.getElementById('sortSel').addEventListener('change', e => {
sortMode = e.target.value;
currentPage = 1;
render();
});
// Initial render
render();
// ── Custom Dropdowns ──
SM.initCustomSelect(document.getElementById('sortSel'), { compact: false });
SM.initCustomSelect(document.getElementById('pageSizeSel'), { compact: true });
// ── Page Size Change ──
document.getElementById('pageSizeSel').addEventListener('change', e => {
pageSize = parseInt(e.target.value);
currentPage = 1;
render();
});
});