forked from izu/student-web-if-development-kit
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc173ff4ec | |||
| 471ed6cd18 | |||
| fe5c645cbd | |||
| ec0288fd8a | |||
| 7cf671d826 | |||
| f75c04c052 | |||
| 73964fe874 |
@@ -578,6 +578,7 @@ const translations = {
|
||||
grp_m4n1fest_hero_btn_stats: "Lihat Statistik Alumni",
|
||||
grp_m4n1fest_hero_btn_join: "Gabung Grup IKA",
|
||||
grp_m4n1fest_hero_stat_alumni: "TOTAL ALUMNI",
|
||||
grp_m4n1fest_hero_stat_alumni_desc: "data diperoleh dari alumni terdata di tracer study",
|
||||
grp_m4n1fest_hero_stat_batch: "ANGKATAN",
|
||||
grp_m4n1fest_hero_stat_company: "PERUSAHAAN",
|
||||
grp_m4n1fest_eyebrow_achievers: "High Achievers",
|
||||
@@ -1285,6 +1286,7 @@ const translations = {
|
||||
grp_m4n1fest_hero_btn_stats: "View Alumni Statistics",
|
||||
grp_m4n1fest_hero_btn_join: "Join Alumni Group",
|
||||
grp_m4n1fest_hero_stat_alumni: "TOTAL ALUMNI",
|
||||
grp_m4n1fest_hero_stat_alumni_desc: "data obtained from alumni recorded in tracer study",
|
||||
grp_m4n1fest_hero_stat_batch: "CLASSES",
|
||||
grp_m4n1fest_hero_stat_company: "COMPANIES",
|
||||
grp_m4n1fest_eyebrow_achievers: "High Achievers",
|
||||
|
||||
+434
-203
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@@ -50,6 +50,11 @@
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
*,
|
||||
@@ -1183,15 +1188,16 @@
|
||||
|
||||
<div class="custom-stats">
|
||||
<div class="custom-stat-box">
|
||||
<div class="custom-stat-num custom-serif">850<span class="custom-stat-plus">+</span></div>
|
||||
<div class="custom-stat-num custom-serif">212<span class="custom-stat-plus">+</span></div>
|
||||
<div class="custom-stat-label" data-i18n="grp_m4n1fest_hero_stat_alumni">TOTAL ALUMNI</div>
|
||||
<div class="custom-stat-desc" style="font-size: 0.65rem; color: #a1a1aa; margin-top: 4px; line-height: 1.35; font-weight: normal; font-family: sans-serif;" data-i18n="grp_m4n1fest_hero_stat_alumni_desc">data diperoleh dari alumni terdata di tracer study</div>
|
||||
</div>
|
||||
<div class="custom-stat-box">
|
||||
<div class="custom-stat-num custom-serif">18<span class="custom-stat-plus">+</span></div>
|
||||
<div class="custom-stat-num custom-serif">22</div>
|
||||
<div class="custom-stat-label" data-i18n="grp_m4n1fest_hero_stat_batch">ANGKATAN</div>
|
||||
</div>
|
||||
<div class="custom-stat-box" style="border-right: none;">
|
||||
<div class="custom-stat-num custom-serif">150<span class="custom-stat-plus">+</span></div>
|
||||
<div class="custom-stat-num custom-serif">202<span class="custom-stat-plus">+</span></div>
|
||||
<div class="custom-stat-label" data-i18n="grp_m4n1fest_hero_stat_company">PERUSAHAAN</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1466,50 +1472,66 @@
|
||||
</div>
|
||||
|
||||
<div class="two-col">
|
||||
<!-- bars -->
|
||||
<div class="rv">
|
||||
<!-- bars: skeleton selalu tampil, garis biru terisi otomatis dari data approved -->
|
||||
<div class="rv" id="skill-bars-container">
|
||||
|
||||
<!-- Web Development -->
|
||||
<div class="skill-row">
|
||||
<span class="skill-label">Web Development</span>
|
||||
<div class="skill-track">
|
||||
<div class="skill-fill" style="width: 78%"></div>
|
||||
</div>
|
||||
<span class="skill-pct">78%</span>
|
||||
<div class="skill-track"><div class="skill-fill" id="bar-web-development" style="width:0%;transition:width 0.8s ease"></div></div>
|
||||
<span class="skill-pct" id="pct-web-development">—</span>
|
||||
</div>
|
||||
<!-- Python / Data Science -->
|
||||
<div class="skill-row">
|
||||
<span class="skill-label">Python / Data Science</span>
|
||||
<div class="skill-track">
|
||||
<div class="skill-fill" style="width: 65%"></div>
|
||||
</div>
|
||||
<span class="skill-pct">65%</span>
|
||||
<div class="skill-track"><div class="skill-fill" id="bar-python-data-science" style="width:0%;transition:width 0.8s ease"></div></div>
|
||||
<span class="skill-pct" id="pct-python-data-science">—</span>
|
||||
</div>
|
||||
<!-- Mobile Development -->
|
||||
<div class="skill-row">
|
||||
<span class="skill-label">Mobile Development</span>
|
||||
<div class="skill-track">
|
||||
<div class="skill-fill" style="width: 54%"></div>
|
||||
</div>
|
||||
<span class="skill-pct">54%</span>
|
||||
<div class="skill-track"><div class="skill-fill" id="bar-mobile-development" style="width:0%;transition:width 0.8s ease"></div></div>
|
||||
<span class="skill-pct" id="pct-mobile-development">—</span>
|
||||
</div>
|
||||
<!-- UI/UX Design -->
|
||||
<div class="skill-row">
|
||||
<span class="skill-label">UI/UX Design</span>
|
||||
<div class="skill-track">
|
||||
<div class="skill-fill" style="width: 48%"></div>
|
||||
</div>
|
||||
<span class="skill-pct">48%</span>
|
||||
<div class="skill-track"><div class="skill-fill" id="bar-uiux-design" style="width:0%;transition:width 0.8s ease"></div></div>
|
||||
<span class="skill-pct" id="pct-uiux-design">—</span>
|
||||
</div>
|
||||
<!-- Networking / DevOps -->
|
||||
<div class="skill-row">
|
||||
<span class="skill-label">Networking / DevOps</span>
|
||||
<div class="skill-track">
|
||||
<div class="skill-fill" style="width: 38%"></div>
|
||||
</div>
|
||||
<span class="skill-pct">38%</span>
|
||||
<div class="skill-track"><div class="skill-fill" id="bar-networking-devops" style="width:0%;transition:width 0.8s ease"></div></div>
|
||||
<span class="skill-pct" id="pct-networking-devops">—</span>
|
||||
</div>
|
||||
<!-- Cybersecurity -->
|
||||
<div class="skill-row">
|
||||
<span class="skill-label">Cybersecurity</span>
|
||||
<div class="skill-track">
|
||||
<div class="skill-fill" style="width: 25%"></div>
|
||||
</div>
|
||||
<span class="skill-pct">25%</span>
|
||||
<div class="skill-track"><div class="skill-fill" id="bar-cybersecurity" style="width:0%;transition:width 0.8s ease"></div></div>
|
||||
<span class="skill-pct" id="pct-cybersecurity">—</span>
|
||||
</div>
|
||||
<!-- Machine Learning / AI -->
|
||||
<div class="skill-row">
|
||||
<span class="skill-label">Machine Learning / AI</span>
|
||||
<div class="skill-track"><div class="skill-fill" id="bar-machine-learning-ai" style="width:0%;transition:width 0.8s ease"></div></div>
|
||||
<span class="skill-pct" id="pct-machine-learning-ai">—</span>
|
||||
</div>
|
||||
<!-- Database -->
|
||||
<div class="skill-row">
|
||||
<span class="skill-label">Database</span>
|
||||
<div class="skill-track"><div class="skill-fill" id="bar-database" style="width:0%;transition:width 0.8s ease"></div></div>
|
||||
<span class="skill-pct" id="pct-database">—</span>
|
||||
</div>
|
||||
|
||||
<!-- Status note -->
|
||||
<p id="skill-data-note" style="font-size:0.72rem;color:#9ca3af;margin-top:14px;text-align:right;font-style:italic;">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
style="display:inline-block;vertical-align:middle;margin-right:3px;animation:spin 1s linear infinite" id="skill-note-spinner">
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
||||
</svg>
|
||||
<span id="skill-note-text">Memuat data skill alumni...</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- stat box -->
|
||||
<div class="rv d2" style="height: 100%;">
|
||||
@@ -1547,12 +1569,24 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="two-col" style="align-items: flex-start;">
|
||||
<!-- left: Table -->
|
||||
<div class="two-col stat-cols" style="align-items: flex-start;">
|
||||
<!-- left: Chart -->
|
||||
<div class="chart-box rv d2" style="margin-top: 0;">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title" data-i18n="grp_m4n1fest_chart_title">
|
||||
Grafik Jumlah Lulusan
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: relative; height: 280px; width: 100%;">
|
||||
<canvas id="kelulusanChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- right: Table -->
|
||||
<div class="rv d1" style="background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; overflow: hidden; padding: 0;">
|
||||
<table style="width: 100%; border-collapse: collapse; text-align: left; font-size: 0.9rem;">
|
||||
<thead>
|
||||
<tr style="background: #374151; color: #fff;">
|
||||
<tr style="background: #1a3a6b; color: #fff;">
|
||||
<th style="padding: 14px 20px; font-weight: 600;" data-i18n="grp_m4n1fest_table_head_period">Periode</th>
|
||||
<th style="padding: 14px 20px; font-weight: 600; text-align: right;" data-i18n="grp_m4n1fest_table_head_amount">Jumlah</th>
|
||||
</tr>
|
||||
@@ -1584,18 +1618,6 @@
|
||||
Sumber : BAK UNTAN
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- right: Chart -->
|
||||
<div class="chart-box rv d2" style="margin-top: 0;">
|
||||
<div class="chart-header">
|
||||
<div class="chart-title" data-i18n="grp_m4n1fest_chart_title">
|
||||
Grafik Jumlah Lulusan
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: relative; height: 280px; width: 100%;">
|
||||
<canvas id="kelulusanChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -2510,13 +2532,15 @@
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Manajemen Mega Menu Navigasi Desktop
|
||||
(function () {
|
||||
const menuItems = document.querySelectorAll('[data-menu]');
|
||||
const megaOverlay = document.getElementById('mega-overlay');
|
||||
|
||||
let activeMenu = null;
|
||||
let activeMenuId = null; // Menyimpan ID menu yang sedang aktif/terbuka
|
||||
|
||||
const menuMap = {
|
||||
// Pemetaan menu ke panel konten mega menu
|
||||
const menuPanelMap = {
|
||||
tentang: 'mega-tentang',
|
||||
program: 'mega-program',
|
||||
sdm: 'mega-sdm',
|
||||
@@ -2525,98 +2549,127 @@
|
||||
kontak: 'mega-kontak'
|
||||
};
|
||||
|
||||
function setButtonState(menuId) {
|
||||
menuItems.forEach(item => {
|
||||
const isActive = item.getAttribute('data-menu') === menuId;
|
||||
const btn = item.querySelector('.nav-menu-btn');
|
||||
const chevron = item.querySelector('.fa-chevron-down');
|
||||
// Mengubah status visual tombol navigasi berdasarkan menu aktif
|
||||
function setMenuButtonState(menuId) {
|
||||
menuItems.forEach(menuItemElement => {
|
||||
const isActive = menuItemElement.getAttribute('data-menu') === menuId;
|
||||
const buttonElement = menuItemElement.querySelector('.nav-menu-btn');
|
||||
const chevronIconElement = menuItemElement.querySelector('.fa-chevron-down');
|
||||
|
||||
btn.classList.toggle('active', isActive);
|
||||
btn.setAttribute('aria-expanded', isActive ? 'true' : 'false');
|
||||
chevron.style.transform = isActive ? 'rotate(180deg)' : '';
|
||||
if (buttonElement) {
|
||||
buttonElement.classList.toggle('active', isActive);
|
||||
buttonElement.setAttribute('aria-expanded', isActive ? 'true' : 'false');
|
||||
}
|
||||
if (chevronIconElement) {
|
||||
chevronIconElement.style.transform = isActive ? 'rotate(180deg)' : '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeMenus() {
|
||||
Object.values(menuMap).forEach(panelId => {
|
||||
const panel = document.getElementById(panelId);
|
||||
if (panel) panel.classList.remove('active');
|
||||
// Menutup semua panel mega menu yang sedang terbuka
|
||||
function closeActiveMenus() {
|
||||
Object.values(menuPanelMap).forEach(panelId => {
|
||||
const panelElement = document.getElementById(panelId);
|
||||
if (panelElement) {
|
||||
panelElement.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
megaOverlay.classList.remove('active');
|
||||
setButtonState(null);
|
||||
activeMenu = null;
|
||||
if (megaOverlay) {
|
||||
megaOverlay.classList.remove('active');
|
||||
}
|
||||
setMenuButtonState(null);
|
||||
activeMenuId = null;
|
||||
}
|
||||
|
||||
function openMenu(menuId) {
|
||||
const targetPanel = document.getElementById(menuMap[menuId]);
|
||||
if (!targetPanel) return;
|
||||
// Membuka panel mega menu tertentu dan menutup yang lainnya
|
||||
function openSelectedMenu(menuId) {
|
||||
const targetPanelElement = document.getElementById(menuPanelMap[menuId]);
|
||||
if (!targetPanelElement) return;
|
||||
|
||||
Object.values(menuMap).forEach(panelId => {
|
||||
const panel = document.getElementById(panelId);
|
||||
if (panel) panel.classList.remove('active');
|
||||
Object.values(menuPanelMap).forEach(panelId => {
|
||||
const panelElement = document.getElementById(panelId);
|
||||
if (panelElement) {
|
||||
panelElement.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
targetPanel.classList.add('active');
|
||||
megaOverlay.classList.add('active');
|
||||
setButtonState(menuId);
|
||||
activeMenu = menuId;
|
||||
targetPanelElement.classList.add('active');
|
||||
if (megaOverlay) {
|
||||
megaOverlay.classList.add('active');
|
||||
}
|
||||
setMenuButtonState(menuId);
|
||||
activeMenuId = menuId;
|
||||
}
|
||||
|
||||
menuItems.forEach(item => {
|
||||
const menuId = item.getAttribute('data-menu');
|
||||
const button = item.querySelector('.nav-menu-btn');
|
||||
// Menginisialisasi event listener untuk setiap tombol navigasi
|
||||
menuItems.forEach(menuItemElement => {
|
||||
const menuId = menuItemElement.getAttribute('data-menu');
|
||||
const buttonElement = menuItemElement.querySelector('.nav-menu-btn');
|
||||
if (!buttonElement) return;
|
||||
|
||||
button.setAttribute('aria-haspopup', 'true');
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
buttonElement.setAttribute('aria-haspopup', 'true');
|
||||
buttonElement.setAttribute('aria-expanded', 'false');
|
||||
|
||||
button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
buttonElement.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (activeMenu === menuId) {
|
||||
closeMenus();
|
||||
if (activeMenuId === menuId) {
|
||||
closeActiveMenus();
|
||||
} else {
|
||||
openMenu(menuId);
|
||||
openSelectedMenu(menuId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
megaOverlay.addEventListener('click', closeMenus);
|
||||
// Menutup menu ketika overlay (latar belakang redup) diklik
|
||||
if (megaOverlay) {
|
||||
megaOverlay.addEventListener('click', closeActiveMenus);
|
||||
}
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const clickedInsideNav = e.target.closest('#main-nav');
|
||||
const clickedInsidePanel = e.target.closest('.mega-panel');
|
||||
// Menutup menu jika klik dilakukan di luar area navigasi dan panel menu
|
||||
document.addEventListener('click', (event) => {
|
||||
const isClickInsideNav = event.target.closest('#main-nav');
|
||||
const isClickInsidePanel = event.target.closest('.mega-panel');
|
||||
|
||||
if (!clickedInsideNav && !clickedInsidePanel && activeMenu) {
|
||||
closeMenus();
|
||||
if (!isClickInsideNav && !isClickInsidePanel && activeMenuId) {
|
||||
closeActiveMenus();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && activeMenu) {
|
||||
closeMenus();
|
||||
// Menutup menu jika menekan tombol Escape keyboard
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && activeMenuId) {
|
||||
closeActiveMenus();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Manajemen Menu Navigasi Seluler (Mobile Menu)
|
||||
(function() {
|
||||
const mobileBtn = document.getElementById('mobile-menu-button');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
const closeMobileBtn= document.getElementById('close-mobile-menu');
|
||||
if (mobileBtn && mobileMenu) {
|
||||
mobileBtn.addEventListener('click', function() {
|
||||
const isOpen = !mobileMenu.classList.contains('-translate-x-full');
|
||||
mobileMenu.classList.toggle('-translate-x-full');
|
||||
mobileBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
|
||||
const mobileMenuButton = document.getElementById('mobile-menu-button');
|
||||
const mobileMenuPanel = document.getElementById('mobile-menu');
|
||||
const closeMobileMenuButton = document.getElementById('close-mobile-menu');
|
||||
|
||||
// Menampilkan atau menyembunyikan menu seluler saat tombol menu diklik
|
||||
if (mobileMenuButton && mobileMenuPanel) {
|
||||
mobileMenuButton.addEventListener('click', () => {
|
||||
const isHidden = mobileMenuPanel.classList.contains('-translate-x-full');
|
||||
mobileMenuPanel.classList.toggle('-translate-x-full');
|
||||
mobileMenuButton.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
if (closeMobileBtn && mobileMenu) {
|
||||
closeMobileBtn.addEventListener('click', function() {
|
||||
mobileMenu.classList.add('-translate-x-full');
|
||||
if (mobileBtn) mobileBtn.setAttribute('aria-expanded', 'false');
|
||||
|
||||
// Menyembunyikan kembali menu seluler saat tombol silang (tutup) diklik
|
||||
if (closeMobileMenuButton && mobileMenuPanel) {
|
||||
closeMobileMenuButton.addEventListener('click', () => {
|
||||
mobileMenuPanel.classList.add('-translate-x-full');
|
||||
if (mobileMenuButton) {
|
||||
mobileMenuButton.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -2624,127 +2677,305 @@
|
||||
|
||||
<!-- Scroll Reveal Observer & Chart.js Initialization -->
|
||||
<script>
|
||||
/* ── IKA FORM SUBMIT ── */
|
||||
// Silakan ganti URL di bawah dengan Web App URL (akhiran /exec) hasil Deploy project Apps Script Anda.
|
||||
// Cara Deploy: di Apps Script editor -> Deploy -> New deployment -> Web app -> Who has access: Anyone.
|
||||
// URL Web App Google Apps Script untuk penanganan pengiriman form IKA
|
||||
const IKA_SCRIPT_URL = "https://script.google.com/macros/s/AKfycbwrXG3YFu4PEY3Z3acYeFTLCozM6iERfA7_yWxXaiJn7ULwau5vTIpt0Zv04OziMit-/exec";
|
||||
|
||||
// Checkbox visual toggle
|
||||
const consentCheck = document.getElementById('ika-consent');
|
||||
if (consentCheck) {
|
||||
consentCheck.addEventListener('change', function() {
|
||||
const box = document.getElementById('ika-consent-box');
|
||||
if (box) {
|
||||
if (this.checked) {
|
||||
box.innerHTML = `<svg width="10" height="10" viewBox="0 0 24 24" fill="none"
|
||||
stroke="#2352A0" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="20 6 9 17 4 12"/></svg>`;
|
||||
} else {
|
||||
box.innerHTML = '';
|
||||
}
|
||||
// Efek visual checkbox kustom pada Form Persetujuan
|
||||
const consentCheckbox = document.getElementById('ika-consent');
|
||||
if (consentCheckbox) {
|
||||
consentCheckbox.addEventListener('change', (event) => {
|
||||
const consentBoxElement = document.getElementById('ika-consent-box');
|
||||
if (!consentBoxElement) return;
|
||||
|
||||
const isChecked = event.target.checked;
|
||||
if (isChecked) {
|
||||
consentBoxElement.innerHTML = `<svg width="10" height="10" viewBox="0 0 24 24" fill="none"
|
||||
stroke="#2352A0" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="20 6 9 17 4 12"/></svg>`;
|
||||
} else {
|
||||
consentBoxElement.innerHTML = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function submitIkaForm() {
|
||||
const nama = document.getElementById('ika-nama').value.trim();
|
||||
const nim = document.getElementById('ika-nim').value.trim();
|
||||
const angkatan = document.getElementById('ika-angkatan').value.trim();
|
||||
const hp = document.getElementById('ika-hp').value.trim();
|
||||
const email = document.getElementById('ika-email').value.trim();
|
||||
const status = document.getElementById('ika-status').value;
|
||||
const instansi = document.getElementById('ika-instansi').value.trim();
|
||||
const skill1 = document.getElementById('ika-skill1').value;
|
||||
const skill2 = document.getElementById('ika-skill2').value;
|
||||
const skill3 = document.getElementById('ika-skill3').value;
|
||||
const consent = document.getElementById('ika-consent').checked;
|
||||
// Mengumpulkan dan merapikan data masukan dari form
|
||||
function getIkaFormData() {
|
||||
return {
|
||||
nama: document.getElementById('ika-nama').value.trim(),
|
||||
nim: document.getElementById('ika-nim').value.trim(),
|
||||
angkatan: document.getElementById('ika-angkatan').value.trim(),
|
||||
hp: document.getElementById('ika-hp').value.trim(),
|
||||
email: document.getElementById('ika-email').value.trim(),
|
||||
status: document.getElementById('ika-status').value,
|
||||
instansi: document.getElementById('ika-instansi').value.trim(),
|
||||
skill1: document.getElementById('ika-skill1').value,
|
||||
skill2: document.getElementById('ika-skill2').value,
|
||||
skill3: document.getElementById('ika-skill3').value,
|
||||
hasConsent: document.getElementById('ika-consent').checked
|
||||
};
|
||||
}
|
||||
|
||||
const msg = document.getElementById('ika-status-msg');
|
||||
// Melakukan validasi pengisian field wajib dan persetujuan checkbox
|
||||
function validateIkaFormData(formData) {
|
||||
const hasRequiredFields = formData.nama &&
|
||||
formData.angkatan &&
|
||||
formData.hp &&
|
||||
formData.email &&
|
||||
formData.status &&
|
||||
formData.skill1 &&
|
||||
formData.hasConsent;
|
||||
return !!hasRequiredFields;
|
||||
}
|
||||
|
||||
// Validasi
|
||||
if (!nama || !angkatan || !hp || !email || !status || !skill1 || !consent) {
|
||||
msg.textContent = '⚠️ Lengkapi semua field wajib (*) dan centang persetujuan.';
|
||||
msg.style.color = '#ef4444';
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('ika-submit-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Mengirim...';
|
||||
msg.textContent = 'Sedang mengirim data...';
|
||||
msg.style.color = '#9ca3af';
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
nama, nim, angkatan, noHp: hp,
|
||||
email, status, instansi, skill1, skill2, skill3
|
||||
});
|
||||
|
||||
await fetch(IKA_SCRIPT_URL + "?" + params.toString(), {
|
||||
method: 'GET',
|
||||
mode: 'no-cors',
|
||||
});
|
||||
|
||||
msg.textContent = '✅ Data berhasil dikirim! Admin akan menghubungimu dalam 1–2 hari kerja.';
|
||||
msg.style.color = '#16a34a';
|
||||
btn.textContent = 'Terkirim ✓';
|
||||
// Reset form
|
||||
['ika-nama','ika-nim','ika-angkatan','ika-hp','ika-email','ika-instansi']
|
||||
.forEach(id => document.getElementById(id).value = '');
|
||||
['ika-status','ika-skill1','ika-skill2','ika-skill3']
|
||||
.forEach(id => document.getElementById(id).selectedIndex = 0);
|
||||
document.getElementById('ika-consent').checked = false;
|
||||
const consentBox = document.getElementById('ika-consent-box');
|
||||
if (consentBox) consentBox.innerHTML = '';
|
||||
|
||||
} catch (err) {
|
||||
msg.textContent = '❌ Gagal mengirim. Coba lagi atau hubungi admin.';
|
||||
msg.style.color = '#ef4444';
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Daftar Sekarang';
|
||||
// Memperbarui pesan status pengiriman pada UI
|
||||
function updateIkaFormStatusMessage(message, color) {
|
||||
const statusMessageElement = document.getElementById('ika-status-msg');
|
||||
if (statusMessageElement) {
|
||||
statusMessageElement.textContent = message;
|
||||
statusMessageElement.style.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── REVEAL ON SCROLL ── */
|
||||
const ro = new IntersectionObserver(
|
||||
// Mengubah kondisi tombol kirim dan pesan selama proses mengunggah data
|
||||
function setIkaFormLoadingState(isLoading) {
|
||||
const submitButtonElement = document.getElementById('ika-submit-btn');
|
||||
if (!submitButtonElement) return;
|
||||
|
||||
if (isLoading) {
|
||||
submitButtonElement.disabled = true;
|
||||
submitButtonElement.textContent = 'Mengirim...';
|
||||
updateIkaFormStatusMessage('Sedang mengirim data...', '#9ca3af');
|
||||
} else {
|
||||
submitButtonElement.disabled = false;
|
||||
submitButtonElement.textContent = 'Daftar Sekarang';
|
||||
}
|
||||
}
|
||||
|
||||
// Mengirimkan data form ke Google Apps Script via GET fetch (mode no-cors)
|
||||
async function sendIkaFormData(formData) {
|
||||
const params = new URLSearchParams({
|
||||
nama: formData.nama,
|
||||
nim: formData.nim,
|
||||
angkatan: formData.angkatan,
|
||||
noHp: formData.hp,
|
||||
email: formData.email,
|
||||
status: formData.status,
|
||||
instansi: formData.instansi,
|
||||
skill1: formData.skill1,
|
||||
skill2: formData.skill2,
|
||||
skill3: formData.skill3
|
||||
});
|
||||
|
||||
const targetUrl = `${IKA_SCRIPT_URL}?${params.toString()}`;
|
||||
await fetch(targetUrl, {
|
||||
method: 'GET',
|
||||
mode: 'no-cors'
|
||||
});
|
||||
}
|
||||
|
||||
// Mereset isian seluruh kolom formulir kembali ke kosong/awal
|
||||
function resetIkaForm() {
|
||||
const inputIds = ['ika-nama', 'ika-nim', 'ika-angkatan', 'ika-hp', 'ika-email', 'ika-instansi'];
|
||||
inputIds.forEach(id => {
|
||||
const inputElement = document.getElementById(id);
|
||||
if (inputElement) inputElement.value = '';
|
||||
});
|
||||
|
||||
const selectIds = ['ika-status', 'ika-skill1', 'ika-skill2', 'ika-skill3'];
|
||||
selectIds.forEach(id => {
|
||||
const selectElement = document.getElementById(id);
|
||||
if (selectElement) selectElement.selectedIndex = 0;
|
||||
});
|
||||
|
||||
const consentCheckbox = document.getElementById('ika-consent');
|
||||
if (consentCheckbox) consentCheckbox.checked = false;
|
||||
|
||||
const consentBoxElement = document.getElementById('ika-consent-box');
|
||||
if (consentBoxElement) consentBoxElement.innerHTML = '';
|
||||
}
|
||||
|
||||
// Alur utama penanganan tombol submit pendaftaran IKA
|
||||
async function submitIkaForm() {
|
||||
const formData = getIkaFormData();
|
||||
|
||||
const isValid = validateIkaFormData(formData);
|
||||
if (!isValid) {
|
||||
updateIkaFormStatusMessage('⚠️ Lengkapi semua field wajib (*) dan centang persetujuan.', '#ef4444');
|
||||
return;
|
||||
}
|
||||
|
||||
setIkaFormLoadingState(true);
|
||||
|
||||
try {
|
||||
await sendIkaFormData(formData);
|
||||
|
||||
updateIkaFormStatusMessage('✅ Data berhasil dikirim! Admin akan menghubungimu dalam 1–2 hari kerja.', '#16a34a');
|
||||
|
||||
const submitButtonElement = document.getElementById('ika-submit-btn');
|
||||
if (submitButtonElement) {
|
||||
submitButtonElement.textContent = 'Terkirim ✓';
|
||||
}
|
||||
|
||||
resetIkaForm();
|
||||
} catch (error) {
|
||||
updateIkaFormStatusMessage('❌ Gagal mengirim. Coba lagi atau hubungi admin.', '#ef4444');
|
||||
setIkaFormLoadingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Manajemen Penarikan Data Keahlian Alumni dari Google Sheets secara Live
|
||||
(function fetchSkillData() {
|
||||
const SHEET_ID = '1mtVB1vLev-OWz6aTuuddGKPS8tCv8MJcGpzws8irpxk';
|
||||
const query = encodeURIComponent("select I,J,K where lower(L)='approved'");
|
||||
const url = `https://docs.google.com/spreadsheets/d/${SHEET_ID}/gviz/tq?tqx=out:json&tq=${query}`;
|
||||
|
||||
// Pemetaan kategori keahlian ke ID element grafik di HTML
|
||||
const SKILL_MAP = {
|
||||
'Web Development': 'web-development',
|
||||
'Mobile Development': 'mobile-development',
|
||||
'Python / Data Science': 'python-data-science',
|
||||
'UI/UX Design': 'uiux-design',
|
||||
'Networking / DevOps': 'networking-devops',
|
||||
'Cybersecurity': 'cybersecurity',
|
||||
'Machine Learning / AI': 'machine-learning-ai',
|
||||
'Database': 'database',
|
||||
};
|
||||
|
||||
const spinnerElement = document.getElementById('skill-note-spinner');
|
||||
const noteTextElement = document.getElementById('skill-note-text');
|
||||
|
||||
// Menampilkan catatan status di bawah grafik keahlian
|
||||
function setSkillNote(text, shouldSpin, shouldShowCta) {
|
||||
if (spinnerElement) {
|
||||
spinnerElement.style.animation = shouldSpin ? 'spin 1s linear infinite' : 'none';
|
||||
}
|
||||
if (!noteTextElement) return;
|
||||
|
||||
if (shouldShowCta) {
|
||||
noteTextElement.innerHTML = `${text} <a href="#kontribusi" style="color:#2352a0;font-weight:700;text-decoration:underline;text-underline-offset:2px;">Isi form kontribusi</a>`;
|
||||
} else {
|
||||
noteTextElement.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
// Menyaring dan mengelompokkan teks bebas ke kategori keahlian terstandar
|
||||
function normalizeSkill(rawSkillText) {
|
||||
if (!rawSkillText) return null;
|
||||
const lowercasedSkill = rawSkillText.toLowerCase().trim();
|
||||
if (lowercasedSkill.includes('web')) return 'Web Development';
|
||||
if (lowercasedSkill.includes('mobile')) return 'Mobile Development';
|
||||
if (lowercasedSkill.includes('python') || lowercasedSkill.includes('data science')) return 'Python / Data Science';
|
||||
if (lowercasedSkill.includes('ui') || lowercasedSkill.includes('ux')) return 'UI/UX Design';
|
||||
if (lowercasedSkill.includes('network') || lowercasedSkill.includes('devops')) return 'Networking / DevOps';
|
||||
if (lowercasedSkill.includes('cyber')) return 'Cybersecurity';
|
||||
if (lowercasedSkill.includes('machine') || lowercasedSkill.includes('ai') || lowercasedSkill.includes('artificial')) return 'Machine Learning / AI';
|
||||
if (lowercasedSkill.includes('database') || lowercasedSkill.includes('db')) return 'Database';
|
||||
return null;
|
||||
}
|
||||
|
||||
// Memotong pembungkus JSONP Google Sheets agar diperoleh array baris murni
|
||||
function parseGoogleSheetsJson(responseText) {
|
||||
const sanitizedText = responseText.replace(/^[^{]+/, '').replace(/[);]+$/, '');
|
||||
const parsedData = JSON.parse(sanitizedText);
|
||||
return (parsedData.table && parsedData.table.rows) ? parsedData.table.rows : [];
|
||||
}
|
||||
|
||||
// Menghitung kemunculan setiap kategori keahlian dari data Sheets
|
||||
function calculateSkillCounts(rows) {
|
||||
const counts = {};
|
||||
rows.forEach(row => {
|
||||
[0, 1, 2].forEach(colIndex => {
|
||||
const cell = row.c && row.c[colIndex];
|
||||
const rawValue = cell && cell.v ? String(cell.v).trim() : '';
|
||||
const normalized = normalizeSkill(rawValue);
|
||||
if (normalized) {
|
||||
counts[normalized] = (counts[normalized] || 0) + 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
return counts;
|
||||
}
|
||||
|
||||
// Memperbarui lebar progress bar keahlian dan persentase teks di UI
|
||||
function updateSkillBarsUI(counts) {
|
||||
const totalMentions = Object.values(counts).reduce((sum, count) => sum + count, 0);
|
||||
const maxCount = Math.max(...Object.values(counts), 0);
|
||||
|
||||
Object.entries(SKILL_MAP).forEach(([label, idKey]) => {
|
||||
const count = counts[label] || 0;
|
||||
const percentage = totalMentions > 0 ? Math.round((count / totalMentions) * 100) : 0;
|
||||
const barPercentage = maxCount > 0 ? Math.round((count / maxCount) * 100) : 0;
|
||||
|
||||
const barElement = document.getElementById(`bar-${idKey}`);
|
||||
const percentElement = document.getElementById(`pct-${idKey}`);
|
||||
|
||||
if (barElement) barElement.style.width = `${barPercentage}%`;
|
||||
if (percentElement) percentElement.textContent = count > 0 ? `${percentage}%` : '—';
|
||||
});
|
||||
}
|
||||
|
||||
// Melakukan fetch data live dari spreadsheet
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(responseText => {
|
||||
const rows = parseGoogleSheetsJson(responseText);
|
||||
|
||||
if (rows.length === 0) {
|
||||
setSkillNote('Data skill masih kosong, bantu kami lengkapi data ini!', false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const counts = calculateSkillCounts(rows);
|
||||
updateSkillBarsUI(counts);
|
||||
setSkillNote(`Data dari ${rows.length} alumni yang diverifikasi.`, false, false);
|
||||
})
|
||||
.catch(() => {
|
||||
setSkillNote('Data skill masih kosong, bantu kami lengkapi data ini!', false, false);
|
||||
});
|
||||
})();
|
||||
|
||||
// Observer Efek Muncul Perlahan saat Pengguliran Halaman (Scroll Reveal)
|
||||
const revealObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((e) => {
|
||||
if (e.isIntersecting) e.target.classList.add("in");
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add("in");
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.08 },
|
||||
{ threshold: 0.08 }
|
||||
);
|
||||
document.querySelectorAll(".rv").forEach((el) => ro.observe(el));
|
||||
document.querySelectorAll(".rv").forEach((el) => revealObserver.observe(el));
|
||||
|
||||
/* ── FAQ TOGGLE ── */
|
||||
// Menampilkan atau menyembunyikan panel jawaban FAQ
|
||||
function toggleFaq(item) {
|
||||
item.classList.toggle("open");
|
||||
}
|
||||
|
||||
/* ── CHART.JS WITH BILINGUAL SUPPORT ── */
|
||||
window.addEventListener("load", function () {
|
||||
// Inisialisasi Grafik Jumlah Kelulusan (Chart.js) dengan Dukungan Bilingual
|
||||
window.addEventListener("load", () => {
|
||||
if (typeof Chart === "undefined") return;
|
||||
const ctx = document.getElementById("kelulusanChart");
|
||||
if (!ctx) return;
|
||||
const chartCanvasElement = document.getElementById("kelulusanChart");
|
||||
if (!chartCanvasElement) return;
|
||||
|
||||
const getChartLabels = (lang) => {
|
||||
if (lang === 'en') {
|
||||
return ['2022 Even', '2023 Odd', '2023 Even', '2024 Odd', '2024 Even'];
|
||||
}
|
||||
return ['2022 Genap', '2023 Ganjil', '2023 Genap', '2024 Ganjil', '2024 Genap'];
|
||||
// Mengembalikan nama label kuartal berdasarkan pilihan bahasa aktif
|
||||
const getChartLabels = (languageCode) => {
|
||||
return languageCode === 'en'
|
||||
? ['2022 Even', '2023 Odd', '2023 Even', '2024 Odd', '2024 Even']
|
||||
: ['2022 Genap', '2023 Ganjil', '2023 Genap', '2024 Ganjil', '2024 Genap'];
|
||||
};
|
||||
|
||||
const initialLang = typeof getLang === 'function' ? getLang() : 'id';
|
||||
const initialLanguageCode = typeof getLang === 'function' ? getLang() : 'id';
|
||||
|
||||
window.kelulusanChartInstance = new Chart(ctx.getContext("2d"), {
|
||||
window.kelulusanChartInstance = new Chart(chartCanvasElement.getContext("2d"), {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: getChartLabels(initialLang),
|
||||
labels: getChartLabels(initialLanguageCode),
|
||||
datasets: [
|
||||
{
|
||||
label: initialLang === 'en' ? 'Number of Graduates' : 'Jumlah Lulusan',
|
||||
label: initialLanguageCode === 'en' ? 'Number of Graduates' : 'Jumlah Lulusan',
|
||||
data: [45, 27, 63, 27, 50],
|
||||
backgroundColor: "#299bc1", // sesuai dengan warna biru pada gambar
|
||||
backgroundColor: "#299bc1",
|
||||
borderRadius: 0,
|
||||
borderSkipped: false,
|
||||
}
|
||||
@@ -2767,9 +2998,9 @@
|
||||
cornerRadius: 8,
|
||||
callbacks: {
|
||||
label: (context) => {
|
||||
const lang = typeof getLang === 'function' ? getLang() : 'id';
|
||||
const text = lang === 'en' ? 'Graduates' : 'Jumlah';
|
||||
const unit = lang === 'en' ? 'People' : 'Orang';
|
||||
const languageCode = typeof getLang === 'function' ? getLang() : 'id';
|
||||
const text = languageCode === 'en' ? 'Graduates' : 'Jumlah';
|
||||
const unit = languageCode === 'en' ? 'People' : 'Orang';
|
||||
return ` ${text}: ${context.raw} ${unit}`;
|
||||
},
|
||||
},
|
||||
@@ -2800,12 +3031,12 @@
|
||||
},
|
||||
});
|
||||
|
||||
// Listen for languageChanged event to dynamically update the chart
|
||||
document.addEventListener('languageChanged', (e) => {
|
||||
const lang = e.detail.lang;
|
||||
// Memperbarui grafik secara dinamis saat terjadi event pergantian bahasa
|
||||
document.addEventListener('languageChanged', (event) => {
|
||||
const languageCode = event.detail.lang;
|
||||
if (window.kelulusanChartInstance) {
|
||||
window.kelulusanChartInstance.data.labels = getChartLabels(lang);
|
||||
window.kelulusanChartInstance.data.datasets[0].label = lang === 'en' ? 'Number of Graduates' : 'Jumlah Lulusan';
|
||||
window.kelulusanChartInstance.data.labels = getChartLabels(languageCode);
|
||||
window.kelulusanChartInstance.data.datasets[0].label = languageCode === 'en' ? 'Number of Graduates' : 'Jumlah Lulusan';
|
||||
window.kelulusanChartInstance.update();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -666,11 +666,15 @@ ul.achiever-achievements li::before {
|
||||
gap: 52px;
|
||||
align-items: start;
|
||||
}
|
||||
.stat-cols {
|
||||
grid-template-columns: 1.3fr 0.7fr;
|
||||
}
|
||||
.two-col > * {
|
||||
min-width: 0;
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.two-col {
|
||||
.two-col,
|
||||
.stat-cols {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 32px;
|
||||
}
|
||||
@@ -807,6 +811,19 @@ ul.achiever-achievements li::before {
|
||||
min-width: 34px;
|
||||
text-align: right;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
.skill-label {
|
||||
min-width: 120px;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.skill-row {
|
||||
gap: 8px;
|
||||
}
|
||||
.skill-pct {
|
||||
font-size: 0.78rem;
|
||||
min-width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/* skill stat box */
|
||||
.skill-stat-box {
|
||||
@@ -1843,6 +1860,13 @@ ul.achiever-achievements li::before {
|
||||
#assignment-body {
|
||||
min-width: 0;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
#assignment-body {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================================
|
||||
CUSTOM HERO FOR M4N1FEST
|
||||
|
||||
Reference in New Issue
Block a user