${escHtml(a.title)}
${escHtml(a.description)}
/** * main.js — ACAAB Prestasi & Karya Mahasiswa * Vanilla JS: fetch data Directus CMS, render sections, handle interactions. */ // ─── Constants ──────────────────────────────────────────────────────────────── const API_BASE = 'https://api.ifuntanhub.dev'; const LANG_KEY = 'if_untan_lang'; const BIDANG_MAP = { 1: 'Kecerdasan Buatan', 2: 'Cyber Security', 3: 'Data', 4: 'Software Engineering', 5: 'Competitive Programming', 6: 'IoT', 7: 'Smart City', 8: 'Web3', 9: 'Karya Tulis Ilmiah', 10: 'Robotik', }; // ─── Utilities ──────────────────────────────────────────────────────────────── function escHtml(str) { if (str === null || str === undefined) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function getLang() { try { return localStorage.getItem(LANG_KEY) || (navigator.language && navigator.language.startsWith('en') ? 'en' : 'id'); } catch (_) { return 'id'; } } // Baca dari translations global yang didefinisikan localization.js function t(key) { try { const lang = getLang(); if (typeof translations !== 'undefined' && translations[lang] && translations[lang][key]) { return translations[lang][key]; } } catch (_) {} return key; } // ─── Data Transformation ────────────────────────────────────────────────────── function extractRank(title) { const m = (title || '').match(/(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)/i); return m ? m[0] : 'Pemenang'; } function extractLevel(title, category) { const text = ((title || '') + ' ' + (category || '')).toLowerCase(); if (/internasional|international|global|world|asean|unesco/.test(text)) return 'Internasional'; if (/regional|kalbar|kalimantan|provinsi|kota|kabupaten|lokal/.test(text)) return 'Regional'; return 'Nasional'; } function extractCompetition(title) { return (title || '') .replace(/^(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)\s+(di\s+|pada\s+|dalam\s+)?/i, '') .trim(); } function getImageUrl(item, seed) { if (item.galeri && Array.isArray(item.galeri) && item.galeri.length > 0) { const first = item.galeri[0]; const imgId = typeof first === 'string' ? first : (first.directus_files_id || first.id); if (imgId) return `${API_BASE}/assets/${imgId}`; } if (item.foto) return `${API_BASE}/assets/${item.foto}`; return `https://picsum.photos/seed/${seed}/800/600`; } function transformAchievement(item) { const id = String(item.id); const title = item.nama_prestasi || 'Untitled Achievement'; let category = 'Umum'; if (item.bidang_id) { category = (typeof item.bidang_id === 'object' && item.bidang_id.nama_bidang) ? item.bidang_id.nama_bidang : (BIDANG_MAP[item.bidang_id] || 'Umum'); } const rank = extractRank(title); const level = extractLevel(title, category); const competition = extractCompetition(title); const year = parseInt(item.tahun) || new Date().getFullYear(); const anggota = Array.isArray(item.anggota) ? item.anggota : []; const team = item.nama_tim || (anggota.length > 0 ? anggota[0].nama : 'Mahasiswa'); const avatar = (item.nama_tim || title || 'IF').substring(0, 2).toUpperCase(); const description = item.deskripsi || `${team} berhasil meraih prestasi luar biasa sebagai ${rank} dalam ajang ${competition} tahun ${year} pada bidang ${category.toLowerCase()}.`; return { id, title, category, competition, rank, year, level, description, team, nim: item.nim || '', generation: item.angkatan ? `Angkatan ${item.angkatan}` : 'Informatika', image: getImageUrl(item, `prestasi-${id}`), avatar, members: anggota.map(m => ({ nama: m.nama || '', angkatan: m.angkatan ? parseInt(m.angkatan) : undefined })), }; } function transformProject(item) { const id = String(item.id); const title = item.nama_proyek || 'Untitled Project'; const anggota = Array.isArray(item.anggota) ? item.anggota : []; const studentName = item.nama_mahasiswa || (anggota.length > 0 ? anggota[0].nama : 'Mahasiswa'); const year = parseInt(item.tahun) || new Date().getFullYear(); const description = item.deskripsi || 'Tidak ada deskripsi.'; let categories = []; if (item.bidang && Array.isArray(item.bidang)) { categories = item.bidang.map(b => { if (b.bidang_id && typeof b.bidang_id === 'object' && b.bidang_id.nama_bidang) return b.bidang_id.nama_bidang; if (typeof b.bidang_id === 'number') return BIDANG_MAP[b.bidang_id] || 'Umum'; return 'Umum'; }).filter(Boolean); } if (categories.length === 0) categories = ['Umum']; let link = item.url || null; if (link && !/^https?:\/\//i.test(link)) link = `https://${link}`; return { id, title, studentName, year, description, categories, link, nim: item.nim || '', image: getImageUrl(item, `project-${id}`), catatan: item.catatan || '', members: anggota.map(m => ({ nama: m.nama || '', nim: m.nim || '' })), }; } // ─── API Fetch ──────────────────────────────────────────────────────────────── async function fetchAchievements() { const res = await fetch(`${API_BASE}/items/prestasi?fields=*.*`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const { data } = await res.json(); return (data || []) .filter(item => item.status === 'published') .map(transformAchievement); } async function fetchProjects() { const res = await fetch(`${API_BASE}/items/portfolio?fields=*,bidang.bidang_id.nama_bidang,galeri.*`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const { data } = await res.json(); return (data || []) .filter(item => item.status === 'published') .map(transformProject); } // ─── App State ──────────────────────────────────────────────────────────────── let achievements = []; let projects = []; let achVisible = 3; let projVisible = 6; let achFilter = { search: '', level: '', cat: '', year: '' }; let projFilter = { search: '', cat: '', year: '' }; // ─── Init ───────────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', async () => { initWaButton(); // Loading placeholders showSkeleton('acaab-ach-grid', 3); showSkeleton('acaab-proj-track', 4); const [achResult, projResult] = await Promise.allSettled([ fetchAchievements(), fetchProjects(), ]); if (achResult.status === 'fulfilled') { achievements = achResult.value; } else { console.error('[ACAAB] achievements error:', achResult.reason); showError('acaab-ach-grid', t('grp_acaab_ach_error')); } if (projResult.status === 'fulfilled') { projects = projResult.value; } else { console.error('[ACAAB] projects error:', projResult.reason); showError('acaab-proj-track', t('grp_acaab_proj_error')); } initStats(); initAchievements(); initProjects(); updateDataNote(); }); // Re-render saat bahasa berubah dari kit navbar document.addEventListener('languageChanged', () => { updateStats(); renderAchievements(); buildAchFilterOptions(); renderProjects(); buildProjFilterOptions(); updateWaButton(); updateDataNote(); updateSearchPlaceholders(); }); // ─── Skeleton / Error ───────────────────────────────────────────────────────── function showSkeleton(containerId, count) { const el = document.getElementById(containerId); if (!el) return; el.innerHTML = Array(count).fill(0).map(() => `
` ).join(''); } function showError(containerId, msg) { const el = document.getElementById(containerId); if (el) el.innerHTML = `${escHtml(msg)}
`; } // ─── Stats ──────────────────────────────────────────────────────────────────── function initStats() { updateStats(); const statsEl = document.getElementById('acaab-stats'); if (!statsEl) return; const intl = achievements.filter(a => a.level === 'Internasional').length; const national = achievements.filter(a => a.level === 'Nasional').length; const regional = achievements.filter(a => a.level === 'Regional').length; const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (!entry.isIntersecting) return; animateCount('stat-total-ach', achievements.length, 1600); animateCount('stat-total-proj', projects.length, 1600); animateCount('stat-intl', intl, 1200); animateCount('stat-national', national, 1350); animateCount('stat-regional', regional, 1500); observer.disconnect(); }); }, { threshold: 0.4 }); observer.observe(statsEl); } function updateStats() { const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; }; set('stat-total-ach', achievements.length); set('stat-total-proj', projects.length); set('stat-intl', achievements.filter(a => a.level === 'Internasional').length); set('stat-national', achievements.filter(a => a.level === 'Nasional').length); set('stat-regional', achievements.filter(a => a.level === 'Regional').length); } function animateCount(elId, target, duration) { const el = document.getElementById(elId); if (!el) return; const start = performance.now(); (function step(now) { const p = Math.min((now - start) / duration, 1); const eased = 1 - Math.pow(1 - p, 4); // easeOutQuart el.textContent = Math.round(target * eased).toLocaleString(); if (p < 1) requestAnimationFrame(step); })(start); } function updateDataNote() { const el = document.getElementById('acaab-data-note'); if (!el) return; const lang = getLang(); const locale = lang === 'en' ? 'en-US' : 'id-ID'; const date = new Date().toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric' }); el.textContent = `${t('grp_acaab_stats_data_note')} ${date}`; } // ─── Achievements ───────────────────────────────────────────────────────────── const ACH_INIT = 3; const ACH_MORE = 3; const LEVEL_CLASS = { Internasional: 'acaab-badge-intl', Nasional: 'acaab-badge-nasional', Regional: 'acaab-badge-regional', }; function initAchievements() { buildAchFilterOptions(); renderAchievements(); bindAchEvents(); } function buildAchFilterOptions() { const levelSel = document.getElementById('acaab-ach-level'); const catSel = document.getElementById('acaab-ach-cat'); const yearSel = document.getElementById('acaab-ach-year'); if (!levelSel || !catSel || !yearSel) return; const levels = ['Internasional', 'Nasional', 'Regional']; const cats = [...new Set(achievements.map(a => a.category))].sort(); const years = [...new Set(achievements.map(a => String(a.year)))].filter(Boolean).sort((a, b) => b - a); levelSel.innerHTML = `` + levels.map(l => ``).join(''); catSel.innerHTML = `` + cats.map(c => ``).join(''); yearSel.innerHTML = `` + years.map(y => ``).join(''); } function filterAchievements() { const q = achFilter.search.toLowerCase(); return achievements.filter(a => { const matchSearch = !q || [a.title, a.team, a.category, a.competition] .some(s => (s || '').toLowerCase().includes(q)); const matchLevel = !achFilter.level || a.level === achFilter.level; const matchCat = !achFilter.cat || a.category === achFilter.cat; const matchYear = !achFilter.year || String(a.year) === achFilter.year; return matchSearch && matchLevel && matchCat && matchYear; }); } function renderAchievements() { const grid = document.getElementById('acaab-ach-grid'); const countEl = document.getElementById('acaab-ach-count'); const moreBtn = document.getElementById('acaab-ach-more'); const lessBtn = document.getElementById('acaab-ach-less'); const resetBtn= document.getElementById('acaab-ach-reset'); if (!grid) return; const filtered = filterAchievements(); const visible = filtered.slice(0, achVisible); const hasFilter = achFilter.search || achFilter.level || achFilter.cat || achFilter.year; if (countEl) countEl.textContent = `${t('grp_acaab_ach_showing')} ${filtered.length} ${t('grp_acaab_ach_results')}`; if (resetBtn) resetBtn.style.display = hasFilter ? 'flex' : 'none'; if (filtered.length === 0) { grid.innerHTML = `${escHtml(t('grp_acaab_ach_empty'))}
`; if (moreBtn) moreBtn.style.display = 'none'; if (lessBtn) lessBtn.style.display = 'none'; return; } grid.innerHTML = visible.map((a, i) => achCardHtml(a, i)).join(''); if (moreBtn) moreBtn.style.display = achVisible < filtered.length ? 'inline-block' : 'none'; if (lessBtn) lessBtn.style.display = achVisible > ACH_INIT ? 'inline-block' : 'none'; grid.querySelectorAll('.acaab-ach-readmore').forEach(btn => { btn.addEventListener('click', () => { const ach = achievements.find(a => a.id === btn.dataset.id); if (ach) openAchModal(ach); }); }); } function achCardHtml(a, idx) { return `${escHtml(a.description)}
${escHtml(label)}
${escHtml(value)}
Anggota Tim
${escHtml(String(a.year))} • ${escHtml(a.category)} • ${escHtml(a.level)}
${escHtml(a.description)}
${membersHtml}TIM / PESERTA
${escHtml(a.team)}
ANGKATAN
${escHtml(a.generation)}
${escHtml(t('grp_acaab_proj_empty'))}
`; if (moreBtn) moreBtn.style.display = 'none'; return; } track.innerHTML = visible.map((p, i) => projCardHtml(p, i)).join(''); if (moreBtn) moreBtn.style.display = projVisible < filtered.length ? 'inline-block' : 'none'; track.querySelectorAll('.acaab-proj-readmore').forEach(btn => { btn.addEventListener('click', () => { const proj = projects.find(p => p.id === btn.dataset.id); if (proj) openProjModal(proj); }); }); } function projCardHtml(p, idx) { const catBadges = p.categories.map(c => ` ${escHtml(c)} `).join(''); return `${escHtml(p.description)}
Anggota Tim
Deskripsi
${escHtml(p.description)}
${p.catatan ? `Catatan Tambahan
${escHtml(p.catatan)}