'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 `
${init}
${SM.esc(r.sentiment)} ${r.tweetId ? ` ` : ''}
${highlightText(r.raw, searchQuery)}
${tags ? `
${tags}
` : ''} ${hasMedia ? `
tweet media
` : ''}
`; } 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(); }); });