forked from izu/student-web-if-development-kit
feature/halaman-stafPendukung-SOP #1
Binary file not shown.
|
After Width: | Height: | Size: 263 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
@@ -0,0 +1,52 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"question": "Apa saja syarat administratif agar saya bisa mengikuti perkuliahan?",
|
||||
"answer": "Syarat untuk mengikuti perkuliahan adalah mahasiswa telah melakukan <strong>pembayaran UKT</strong>, mengisi <strong>KRS</strong>, dan terdaftar di <strong>kelas Edlink</strong>."
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"question": "Berapa persentase kehadiran minimal agar bisa mengikuti Ujian Akhir Semester (UAS)?",
|
||||
"answer": "Mahasiswa wajib memenuhi minimal <strong>75% kehadiran</strong> dari total pertemuan perkuliahan. Mahasiswa yang tidak memenuhi syarat kehadiran ini tidak diperkenankan mengikuti UAS."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"question": "Apakah pengisian evaluasi perkuliahan itu wajib?",
|
||||
"answer": "Ya, karena pengisian evaluasi perkuliahan dapat menjadi salah satu syarat untuk <strong>mengakses hasil studi</strong> sesuai dengan kebijakan yang berlaku. Hasil ini sangat penting untuk perbaikan dan pengembangan kualitas pembelajaran prodi ke depannya."
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"question": "Berapa minimal SKS dan IPK untuk bisa mendaftar Kerja Praktik (KP)?",
|
||||
"answer": "Untuk mendaftar KP, mahasiswa harus sudah lulus minimal <strong>100 SKS</strong> dan memiliki Indeks Prestasi Kumulatif (IPK) <strong>≥ 2,00</strong>."
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"question": "Apakah ada syarat kelulusan mata kuliah tertentu sebelum saya bisa mengajukan Kerja Praktik (KP)?",
|
||||
"answer": "Ya, selain memenuhi syarat minimal SKS dan IPK, mahasiswa juga diwajibkan telah lulus mata kuliah <strong>Penulisan Proposal Tugas Akhir dengan nilai minimal C</strong>. Selain itu, mahasiswa juga harus menyerahkan berkas pendukung pengajuan KP seperti KTM, riwayat studi, transkrip nilai, dan surat permohonan."
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"question": "Apa kualifikasi yang dibutuhkan untuk Pembimbing Lapangan di instansi tempat KP?",
|
||||
"answer": "Pembimbing Lapangan dari tempat pelaksanaan KP diwajibkan memiliki kualifikasi pendidikan minimal <strong>S1 atau D4</strong>."
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"question": "Bolehkah saya mengambil mata kuliah Kerja Praktik (KP) bersamaan dengan Tugas Akhir?",
|
||||
"answer": "Boleh. Mata Kuliah Kerja Praktik (KP) dapat diambil secara bersamaan dengan <strong>Tugas Akhir 1 (TA1)</strong> dan <strong>Tugas Akhir 2 (TA2)</strong> dalam satu semester yang sama."
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"question": "Apa saja syarat akademik utama sebelum mengajukan judul Tugas Akhir (Skripsi)?",
|
||||
"answer": "Syarat utamanya adalah mahasiswa harus telah lulus minimal <strong>120 SKS</strong>, memiliki <strong>IPK minimal 2,00</strong>, telah lulus mata kuliah <strong>Penulisan Proposal Tugas Akhir dengan nilai minimal C</strong>, serta telah mendaftarkan mata kuliah skripsi pada KRS."
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"question": "Siapa yang berhak mendaftar menjadi Asisten Praktikum (Aslab)?",
|
||||
"answer": "Rekrutmen terbuka bagi <strong>mahasiswa aktif Program Studi Informatika UNTAN</strong> yang telah lulus mata kuliah terkait (dengan nilai minimal sesuai ketentuan Dosen Pengampu) dan berkomitmen mengikuti seluruh jadwal praktikum selama semester berjalan."
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"question": "Bagaimana prosedur meminjam Laboratorium Komputer untuk keperluan kegiatan kampus?",
|
||||
"answer": "Peminjam (dosen atau mahasiswa) harus mengajukan <strong>surat permohonan peminjaman</strong> kepada staf laboratorium pengelola dan menyampaikan jadwal penggunaannya. Penggunaan lab juga harus didampingi oleh <strong>dosen atau asisten praktikum</strong> yang bertanggung jawab."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,197 @@
|
||||
[
|
||||
{
|
||||
"tab_id": "kuliah",
|
||||
"tab_number": 1,
|
||||
"category": "akademik",
|
||||
"icon": "fa-chalkboard-teacher",
|
||||
"title": "SOP Pelaksanaan Kuliah",
|
||||
"description": "Mengatur tata cara dan tahapan pelaksanaan perkuliahan mulai dari persiapan hingga evaluasi pembelajaran.",
|
||||
"purpose": "SOP ini bertujuan untuk menjelaskan tata cara pelaksanaan perkuliahan bagi mahasiswa Program Studi Informatika Universitas Tanjungpura.",
|
||||
"requirements": [
|
||||
"Mahasiswa telah melakukan pembayaran UKT",
|
||||
"Mahasiswa telah mengisi KRS",
|
||||
"Mahasiswa terdaftar di kelas Edlink",
|
||||
"Mahasiswa wajib mengikuti perkuliahan dan menyelesaikan tugas tepat waktu",
|
||||
"Mahasiswa wajib mengisi daftar hadir dan memberikan alasan resmi jika tidak hadir",
|
||||
"Mahasiswa memenuhi minimal 70% kehadiran untuk mengikuti UAS"
|
||||
],
|
||||
"procedures": [
|
||||
"Dosen memasuki ruang perkuliahan sesuai jadwal yang telah ditetapkan",
|
||||
"Dosen mengabsensi mahasiswa dengan mencantumkan keterangan hadir, izin, atau tidak hadir",
|
||||
"Pada pertemuan pertama, dosen membuat kontrak perkuliahan secara tertulis bersama mahasiswa",
|
||||
"Dosen menjelaskan materi perkuliahan sesuai RPS yang telah diserahkan ke program studi",
|
||||
"Mahasiswa mendiskusikan, mencatat, dan mengkritisi materi perkuliahan",
|
||||
"Dosen menyimpulkan materi perkuliahan dan memberikan penugasan"
|
||||
],
|
||||
"additional_info": "Mahasiswa yang tidak memenuhi persyaratan kehadiran minimal tidak diperkenankan mengikuti Ujian Akhir Semester (UAS) sesuai ketentuan akademik yang berlaku."
|
||||
},
|
||||
{
|
||||
"tab_id": "ujian",
|
||||
"tab_number": 2,
|
||||
"category": "akademik",
|
||||
"icon": "fa-file-signature",
|
||||
"title": "SOP Pelaksanaan Ujian (UTS & UAS)",
|
||||
"description": "Mengatur pedoman pelaksanaan Ujian Tengah Semester (UTS) dan Ujian Akhir Semester (UAS) mulai dari penentuan kriteria evaluasi, pelaksanaan, hingga perekapan nilai.",
|
||||
"purpose": "SOP ini bertujuan untuk memastikan kelancaran proses evaluasi pembelajaran mahasiswa melalui UTS dan UAS agar berjalan tertib, terukur, dan sesuai dengan instruksi dosen pengampu mata kuliah.",
|
||||
"requirements": [
|
||||
"Mahasiswa terdaftar aktif pada mata kuliah yang sedang berjalan pada semester tersebut",
|
||||
"Persentase kehadiran perkuliahan mahasiswa tidak kurang dari 75%"
|
||||
],
|
||||
"procedures": [
|
||||
"Dosen pengampu menentukan kriteria UTS dan UAS",
|
||||
"Mahasiswa mengikuti UTS atau UAS sesuai instruksi dosen pengampu, dan wajib melakukan presensi",
|
||||
"Mahasiswa mengumpulkan hasil UTS atau UAS sesuai rentang waktu yang diberikan",
|
||||
"Dosen menilai dan merekap pengerjaan UTS atau UAS mahasiswa"
|
||||
],
|
||||
"additional_info": "Segala bentuk kecurangan saat ujian, maupun tindakan plagiarisme dalam pengerjaan UTS/UAS, dapat mengakibatkan pembatalan nilai atau pemberian sanksi."
|
||||
},
|
||||
{
|
||||
"tab_id": "evaluasi",
|
||||
"tab_number": 3,
|
||||
"category": "akademik",
|
||||
"icon": "fa-chart-line",
|
||||
"title": "SOP Pelaksanaan Evaluasi Pengembangan Perkuliahan",
|
||||
"description": "Mengatur tata cara pelaksanaan evaluasi perkuliahan oleh mahasiswa sebagai sarana umpan balik untuk meningkatkan kualitas proses belajar mengajar.",
|
||||
"purpose": "SOP ini bertujuan memberikan pedoman pelaksanaan evaluasi perkuliahan guna mendukung peningkatan mutu pembelajaran di Program Studi Informatika Universitas Tanjungpura.",
|
||||
"requirements": [
|
||||
"Mahasiswa terdaftar aktif pada semester berjalan",
|
||||
"Telah mengikuti kegiatan perkuliahan pada mata kuliah yang dievaluasi",
|
||||
"Tersedia akses ke sistem evaluasi perkuliahan"
|
||||
],
|
||||
"procedures": [
|
||||
"Program Studi membuka periode evaluasi perkuliahan",
|
||||
"Mahasiswa mengakses satu untan dan mengisi kuesioner evaluasi perkuliahan",
|
||||
"Sistem merekap hasil evaluasi yang telah diisi mahasiswa",
|
||||
"Program Studi meninjau hasil evaluasi sebagai bahan pengembangan perkuliahan"
|
||||
],
|
||||
"additional_info": "Hasil evaluasi digunakan sebagai dasar perbaikan dan pengembangan kualitas pembelajaran pada semester berikutnya. Pengisian evaluasi perkuliahan dapat menjadi salah satu syarat untuk mengakses hasil studi sesuai kebijakan yang berlaku."
|
||||
},
|
||||
{
|
||||
"tab_id": "kp",
|
||||
"tab_number": 4,
|
||||
"category": "akademik",
|
||||
"icon": "fa-briefcase",
|
||||
"title": "SOP Kerja Praktik",
|
||||
"description": "Panduan resmi pelaksanaan Kerja Praktek (KP) mulai dari tata cara pendaftaran, pelaksanaan, hingga seminar Kerja Praktek (KP).",
|
||||
"purpose": "SOP ini bertujuan memberikan pedoman kepada seluruh pihak terkait dalam pelaksanaan Kerja Praktek serta meningkatkan efisiensi dan efektivitas pelaksanaannya di Program Studi Informatika Fakultas Teknik Universitas Tanjungpura.",
|
||||
"requirements": [
|
||||
"Telah lulus minimal 100 SKS",
|
||||
"Memiliki IPK minimal 2,00",
|
||||
"Telah lulus mata kuliah Penulisan Proposal Tugas Akhir dengan nilai minimal C",
|
||||
"Menyerahkan berkas pendukung pengajuan KP (KTM, riwayat studi, transkrip, dan surat permohonan)",
|
||||
"Pembimbing Lapangan dari tempat KP memiliki kualifikasi pendidikan minimal S1/D4"
|
||||
],
|
||||
"procedures": [
|
||||
"Mahasiswa mengusulkan topik, draf proposal, dan menyerahkan berkas KP",
|
||||
"Staf Administrasi memverifikasi kelengkapan berkas",
|
||||
"Ketua Prodi menerbitkan surat pengantar KP",
|
||||
"Mahasiswa melaksanakan KP dan mengikuti bimbingan",
|
||||
"Pembimbing Lapangan memberikan penilaian KP",
|
||||
"Mahasiswa mengajukan seminar dan Prodi menerbitkan undangan",
|
||||
"Mahasiswa melaksanakan seminar KP",
|
||||
"Mahasiswa melakukan revisi laporan akhir KP"
|
||||
],
|
||||
"staff_responsible": [
|
||||
"👤 Staf Administrasi Informatika"
|
||||
],
|
||||
"additional_info": "Mata Kuliah Kerja Praktik (KP) dapat diambil bersamaan dengan Tugas Akhir 1 (TA1) dan Tugas Akhir 2 (TA2) dalam semester yang sama."
|
||||
},
|
||||
{
|
||||
"tab_id": "ta",
|
||||
"tab_number": 5,
|
||||
"category": "akademik",
|
||||
"icon": "fa-graduation-cap",
|
||||
"title": "SOP Tugas Akhir/Skripsi",
|
||||
"description": "Mengatur tata cara pelaksanaan skripsi mulai dari pengajuan judul hingga ujian dan penyelesaian skripsi mahasiswa.",
|
||||
"purpose": "SOP ini bertujuan untuk memberikan pedoman kepada mahasiswa dan dosen dalam pelaksanaan skripsi agar proses penyusunan skripsi berjalan tertib, terarah, dan sesuai ketentuan akademik.",
|
||||
"requirements": [
|
||||
"Telah lulus minimal 120 SKS",
|
||||
"Memiliki IPK minimal 2,00",
|
||||
"Telah lulus mata kuliah Penulisan Proposal Tugas Akhir dengan nilai minimal C",
|
||||
"Telah mendaftarkan mata kuliah skripsi pada KRS",
|
||||
"Telah menyiapkan judul dan proposal skripsi",
|
||||
"Memiliki dosen pembimbing skripsi",
|
||||
"Melengkapi berkas persyaratan"
|
||||
],
|
||||
"procedures": [
|
||||
"Mahasiswa mengajukan judul dan proposal skripsi",
|
||||
"Program studi menetapkan dosen pembimbing skripsi",
|
||||
"Mahasiswa melakukan bimbingan dan penyusunan skripsi",
|
||||
"Mahasiswa mengajukan seminar atau sidang skripsi",
|
||||
"Tim penguji dan pembimbing melaksanakan seminar skripsi",
|
||||
"Mahasiswa melakukan revisi skripsi",
|
||||
"Mahasiswa menyerahkan skripsi final"
|
||||
],
|
||||
"staff_responsible": [
|
||||
"👤 Staf Administrasi Informatika"
|
||||
],
|
||||
"additional_info": "Mahasiswa diberi waktu maksimal 2 semester untuk menyelesaikan Tugas Akhir. Perpanjangan dapat dilakukan dengan persetujuan tertulis dari Kaprodi dan Pembimbing."
|
||||
},
|
||||
{
|
||||
"tab_id": "praktikum",
|
||||
"tab_number": 6,
|
||||
"category": "praktikum",
|
||||
"icon": "fa-flask",
|
||||
"title": "SOP Praktikum Mata Kuliah",
|
||||
"description": "Mengatur tata cara pelaksanaan praktikum mata kuliah agar kegiatan praktikum berjalan tertib, aman, dan sesuai ketentuan pembelajaran.",
|
||||
"purpose": "SOP ini bertujuan untuk menjelaskan tata cara pelaksanaan perkuliahan yang memiliki komponen praktikum pada semester berjalan.",
|
||||
"requirements": [
|
||||
"Terdaftar sebagai peserta mata kuliah yang memiliki komponen praktikum pada semester berjalan",
|
||||
"Mahasiswa membawa perlengkapan praktikum yang diperlukan"
|
||||
],
|
||||
"procedures": [
|
||||
"Dosen mata kuliah menuntaskan pembahasan materi yang menjadi dasar praktikum",
|
||||
"Mahasiswa mengisi daftar hadir dan menerima penjelasan prosedur praktikum",
|
||||
"Mahasiswa melaksanakan praktikum sesuai modul di bawah pengawasan dosen atau asisten praktikum",
|
||||
"Mahasiswa menyusun dan mengumpulkan laporan praktikum sesuai tenggat waktu",
|
||||
"Dosen atau asisten melakukan penilaian laporan dan hasil praktikum sebagai komponen nilai akhir"
|
||||
],
|
||||
"additional_info": "Mahasiswa wajib mengikuti seluruh ketentuan praktikum dan menjaga ketertiban serta keamanan selama kegiatan praktikum berlangsung."
|
||||
},
|
||||
{
|
||||
"tab_id": "rekrut_asprak",
|
||||
"tab_number": 7,
|
||||
"category": "praktikum",
|
||||
"icon": "fa-user-plus",
|
||||
"title": "SOP Perekrutan Asisten Praktikum",
|
||||
"description": "Prosedur perekrutan Asisten Praktikum (Aslab) mulai dari pendataan kebutuhan mata kuliah, proses seleksi, hingga pengumuman kelulusan.",
|
||||
"purpose": "SOP ini bertujuan sebagai panduan baku untuk menyelenggarakan rekrutmen Asisten Praktikum secara transparan, guna menyaring mahasiswa Informatika UNTAN yang kompeten dan berkomitmen sesuai kualifikasi mata kuliah.",
|
||||
"requirements": [
|
||||
"Mahasiswa aktif Program Studi Informatika UNTAN",
|
||||
"Lulus mata kuliah terkait dengan nilai minimal ketentuan Dosen Pengampu",
|
||||
"Berkomitmen mengikuti seluruh jadwal dan kontrak praktikum selama semester berjalan"
|
||||
],
|
||||
"procedures": [
|
||||
"Dosen mendata kebutuhan kuota dan kualifikasi Asisten Praktikum",
|
||||
"Dosen mengumumkan pembukaan rekrutmen asisten praktikum",
|
||||
"Mahasiswa mendaftarkan diri sesuai persyaratan yang diberikan",
|
||||
"Dosen melakukan seleksi wawancara kepada calon asisten praktikum",
|
||||
"Dosen mengumumkan calon yang diterima berdasarkan hasil seleksi"
|
||||
],
|
||||
"additional_info": "Keputusan hasil seleksi bersifat final dan ditetapkan oleh dosen pengampu mata kuliah. Asisten Praktikum yang terpilih wajib mematuhi seluruh ketentuan yang berlaku selama menjalankan tugas sebagai Asisten Praktikum."
|
||||
},
|
||||
{
|
||||
"tab_id": "labkom",
|
||||
"tab_number": 8,
|
||||
"category": "praktikum",
|
||||
"icon": "fa-desktop",
|
||||
"title": "SOP Peminjaman Laboratorium Komputer",
|
||||
"description": "Mengatur tata cara peminjaman dan penggunaan laboratorium komputer untuk mendukung kegiatan praktikum dan pembelajaran.",
|
||||
"purpose": "SOP ini bertujuan menjelaskan tata cara peminjaman lab komputer dan memastikan peminjaman mendukung kelancaran proses belajar mengajar di Program Studi Informatika Universitas Tanjungpura.",
|
||||
"requirements": [
|
||||
"Melampirkan surat permohonan peminjaman laboratorium",
|
||||
"Terdapat dosen atau asisten praktikum yang bertanggung jawab",
|
||||
"Pengguna menaati tata tertib dan ketentuan penggunaan perangkat laboratorium"
|
||||
],
|
||||
"procedures": [
|
||||
"Dosen atau mahasiswa mengajukan surat permohonan peminjaman laboratorium kepada pengelola",
|
||||
"Pengguna menyampaikan jadwal penggunaan laboratorium",
|
||||
"Pengelola menyusun jadwal penggunaan laboratorium",
|
||||
"Laboratorium digunakan sesuai jadwal dan ketentuan yang berlaku"
|
||||
],
|
||||
"staff_responsible": [
|
||||
"👤 Staf Laboratorium Informatika"
|
||||
],
|
||||
"additional_info": "Pengguna laboratorium wajib menjaga kebersihan, keamanan, dan kondisi perangkat laboratorium selama kegiatan berlangsung."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Tuning Rahayu, S.Hut.",
|
||||
"role": "Staf Administrasi Informatika",
|
||||
"image": "assets/images/staff-female.png",
|
||||
"location": "Ruang Administrasi Prodi Informatika",
|
||||
"contact_consent": true,
|
||||
"phone": "6282350250892",
|
||||
"schedule": [
|
||||
{
|
||||
"day": "Senin – Jumat",
|
||||
"time": "07.30–16.00 WIB",
|
||||
"break": "istirahat 12.00–13.00 WIB"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
"Administrasi mahasiswa",
|
||||
"Informasi berkas akademik"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Mahyus Safutra, A.Md.",
|
||||
"role": "Staf Laboratorium Informatika",
|
||||
"image": "assets/images/staff-male.png",
|
||||
"location": "Laboratorium Informatika",
|
||||
"contact_consent": true,
|
||||
"phone": "895338672533",
|
||||
"schedule": [
|
||||
{
|
||||
"day": "Senin – Jumat",
|
||||
"time": "07.30–16.00 WIB",
|
||||
"break": "istirahat 12.00–13.00 WIB"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
"Pengelolaan laboratorium",
|
||||
"Dukungan praktikum",
|
||||
"Peminjaman ruangan dan alat"
|
||||
]
|
||||
}
|
||||
]
|
||||
+170
-39
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -7,9 +8,12 @@
|
||||
<link rel="stylesheet" href="../../assets/global/output.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<style>
|
||||
/* ===== MOBILE NAV STABILITY FIX =====
|
||||
Prevents logo bar from collapsing/shifting on Android when
|
||||
address bar hides/shows and triggers viewport reflow.
|
||||
@@ -293,9 +297,10 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="text-slate-800 antialiased bg-white selection:bg-untan-yellow selection:text-black">
|
||||
|
||||
<a href="#main-content" class="skip-link" data-i18n="skip_to_content">Langsung ke Konten Utama</a>
|
||||
<a href="#main-content" class="skip-link">Langsung ke Konten Utama</a>
|
||||
|
||||
<div id="nav-wrapper"
|
||||
class="fixed top-0 left-0 right-0 z-50 transition-shadow duration-300 shadow-sm border-b border-slate-200 will-change-auto">
|
||||
@@ -373,7 +378,7 @@
|
||||
<div class="nav-menu-item relative" data-menu="tentang">
|
||||
<button
|
||||
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
|
||||
<span data-i18n="nav_about">Tentang Kami</span>
|
||||
<span>Tentang Kami</span>
|
||||
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -382,7 +387,7 @@
|
||||
<div class="nav-menu-item relative" data-menu="program">
|
||||
<button
|
||||
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
|
||||
<span data-i18n="nav_program">Program</span>
|
||||
<span>Program</span>
|
||||
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -391,7 +396,7 @@
|
||||
<div class="nav-menu-item relative" data-menu="sdm">
|
||||
<button
|
||||
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
|
||||
<span data-i18n="nav_sdm">SDM</span>
|
||||
<span>SDM</span>
|
||||
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -400,7 +405,7 @@
|
||||
<div class="nav-menu-item relative" data-menu="fasilitas">
|
||||
<button
|
||||
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
|
||||
<span data-i18n="nav_facilities">Fasilitas</span>
|
||||
<span>Fasilitas</span>
|
||||
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -409,7 +414,7 @@
|
||||
<div class="nav-menu-item relative" data-menu="karya">
|
||||
<button
|
||||
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
|
||||
<span data-i18n="nav_works">Karya & Agenda</span>
|
||||
<span>Karya & Agenda</span>
|
||||
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -418,7 +423,7 @@
|
||||
<div class="nav-menu-item relative" data-menu="kontak">
|
||||
<button
|
||||
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
|
||||
<span data-i18n="nav_contact">Kontak</span>
|
||||
<span>Kontak</span>
|
||||
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -1139,18 +1144,143 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white">
|
||||
<main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white">
|
||||
<div class="container mx-auto px-4 lg:px-8 py-12">
|
||||
<div class="mb-8 pb-8 border-b border-slate-200">
|
||||
<span class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas Halaman Statis</span>
|
||||
<h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Judul Halaman Group 04</h1>
|
||||
<p class="text-slate-500 text-sm">Kelompok 04 — C4A · Informatika UNTAN 2026</p>
|
||||
<span
|
||||
class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas
|
||||
Halaman Statis</span>
|
||||
<h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Staf Pendukung dan Standar Operasional
|
||||
Prosedur (SOP)</h1>
|
||||
<p class="text-slate-500 text-sm">Kelompok 04 — C4A · Informatika UNTAN 2026
|
||||
</p>
|
||||
</div>
|
||||
<div id="assignment-body" class="prose prose-slate max-w-none">
|
||||
<!-- ================= START: BAGIAN YANG DIEDIT KELOMPOK ================= -->
|
||||
<p>Tulis konten halaman kelompok di sini.</p>
|
||||
<h3>Subjudul</h3>
|
||||
<p>Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.</p>
|
||||
<!-- HERO SECTION -->
|
||||
<section class="c4a-hero reveal">
|
||||
<div class="c4a-hero-inner">
|
||||
<div class="c4a-hero-tag">LAYANAN AKADEMIK • INFORMATIKA UNTAN
|
||||
</div>
|
||||
<h1 class="c4a-hero-title">
|
||||
Staf Pendukung & <br />
|
||||
<span class="highlight-orange">Standar Operasional Prosedur (SOP)</span>
|
||||
</h1>
|
||||
<p class="c4a-hero-desc">Halaman ini menampilkan informasi petugas
|
||||
layanan akademik serta
|
||||
prosedur administrasi
|
||||
mahasiswa
|
||||
Program Studi Informatika UNTAN.</p>
|
||||
<div class="hero-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number" id="stat-staf">0</div>
|
||||
<div class="stat-label">Staf Aktif</div>
|
||||
</div>
|
||||
<div class="stat-divider"></div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number" id="stat-sop">0</div>
|
||||
<div class="stat-label">Jenis SOP</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- STAFF SECTION -->
|
||||
<section class="content-section">
|
||||
<div class="section-header">
|
||||
<div class="section-tag"><span class="dot"></span> <span>Tim
|
||||
Kami</span></div>
|
||||
<h2 class="section-title">Staf Pendukung</h2>
|
||||
<p class="section-subtitle">Staf yang siap melayani kebutuhan
|
||||
akademik dan operasional
|
||||
laboratorium di Prodi Informatika UNTAN.</p>
|
||||
</div>
|
||||
<div class="staff-grid" id="staff-container">
|
||||
<!-- Kartu staf akan diisi secara dinamis melalui main.js berdasarkan data-staff.json -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SOP SECTION -->
|
||||
<section class="sop-section">
|
||||
<div class="section-header">
|
||||
<div class="section-tag"><span class="dot"></span> <span>Prosedur
|
||||
Resmi</span></div>
|
||||
<h2 class="section-title">Standar Operasional Prosedur (SOP)</h2>
|
||||
<p class="section-subtitle">Standar Operasional Prosedur untuk
|
||||
berbagai kebutuhan administrasi
|
||||
akademik mahasiswa. Gunakan pencarian atau filter untuk menemukan SOP yang Anda butuhkan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- SEARCH & FILTER BAR -->
|
||||
<div class="sop-toolbar reveal">
|
||||
<div class="sop-search-wrap">
|
||||
<i class="fas fa-search sop-search-icon"></i>
|
||||
<input type="search" id="sop-search-input" class="sop-search-input"
|
||||
placeholder="Cari SOP berdasarkan judul..." autocomplete="off" aria-label="Cari SOP" />
|
||||
<button class="sop-search-clear" id="sop-search-clear" aria-label="Hapus pencarian"
|
||||
style="display:none;">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="sop-filter-chips" id="sop-filter-chips" role="group"
|
||||
aria-label="Filter kategori SOP">
|
||||
<button class="sop-chip active" data-filter="semua" id="chip-semua">Semua</button>
|
||||
<button class="sop-chip" data-filter="akademik" id="chip-akademik">
|
||||
<i class="fas fa-graduation-cap"></i> <span>Akademik</span>
|
||||
</button>
|
||||
<button class="sop-chip" data-filter="praktikum" id="chip-praktikum">
|
||||
<i class="fas fa-flask"></i> <span>Praktikum</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RESULT COUNT -->
|
||||
<div class="sop-result-info" id="sop-result-info" aria-live="polite"></div>
|
||||
|
||||
<!-- SOP CARD GRID -->
|
||||
<div class="sop-card-grid" id="sop-cards-container"></div>
|
||||
|
||||
<!-- EMPTY STATE -->
|
||||
<div class="sop-empty-state" id="sop-empty-state" style="display:none;">
|
||||
<i class="fas fa-search-minus sop-empty-icon"></i>
|
||||
<p class="sop-empty-title">SOP tidak ditemukan</p>
|
||||
<p class="sop-empty-desc">Coba kata kunci lain atau reset
|
||||
filter pencarian.</p>
|
||||
<button class="sop-empty-reset" id="sop-empty-reset">
|
||||
<i class="fas fa-undo"></i> <span data-i18n="grp_c4a_sop_reset_btn">Reset Pencarian</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- SOP DETAIL MODAL -->
|
||||
<div class="sop-modal-overlay" id="sop-modal-overlay" role="dialog" aria-modal="true"
|
||||
aria-labelledby="sop-modal-title">
|
||||
<div class="sop-modal-box" id="sop-modal-box">
|
||||
<button class="sop-modal-close" id="sop-modal-close" aria-label="Tutup detail SOP">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
<div id="sop-modal-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ SECTION -->
|
||||
<section class="faq-section-wrap">
|
||||
<div class="faq-section-inner">
|
||||
<div class="faq-header reveal">
|
||||
<div class="section-tag"><span class="dot"></span> <span>Pertanyaan Umum</span></div>
|
||||
<h2>Pertanyaan yang Sering Diajukan</h2>
|
||||
<p>Temukan jawaban cepat atas pertanyaan umum mahasiswa
|
||||
seputar layanan akademik prodi.</p>
|
||||
</div>
|
||||
|
||||
<div class="faq-wrap" id="faq-container">
|
||||
<!-- Item FAQ diisi secara dinamis melalui main.js berdasarkan data-faq.json -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script type="module" src="./js/main.js"></script>
|
||||
<!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -1215,8 +1345,8 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full relative z-0 mt-4 pointer-events-none flex justify-center">
|
||||
<img src="../../assets/global/footer.png" alt="Footer Illustration" class="w-full h-auto object-cover object-bottom"
|
||||
style="object-position: bottom;">
|
||||
<img src="../../assets/global/footer.png" alt="Footer Illustration"
|
||||
class="w-full h-auto object-cover object-bottom" style="object-position: bottom;">
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1312,27 +1442,28 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(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');
|
||||
});
|
||||
}
|
||||
if (closeMobileBtn && mobileMenu) {
|
||||
closeMobileBtn.addEventListener('click', function() {
|
||||
mobileMenu.classList.add('-translate-x-full');
|
||||
if (mobileBtn) mobileBtn.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(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');
|
||||
});
|
||||
}
|
||||
if (closeMobileBtn && mobileMenu) {
|
||||
closeMobileBtn.addEventListener('click', function () {
|
||||
mobileMenu.classList.add('-translate-x-full');
|
||||
if (mobileBtn) mobileBtn.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script src="../../assets/global/localization.js"></script>
|
||||
<script src="../../assets/global/localization.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Berisi fungsi-fungsi untuk mengambil data (Data Fetching).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mengambil data staf pendukung dari berkas JSON.
|
||||
* @returns {Promise<Array<Object>>} Daftar objek data staf
|
||||
*/
|
||||
export async function getStaffData() {
|
||||
try {
|
||||
const response = await fetch('data/data-staff.json');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Gagal memuat data staf:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mengambil data SOP dari berkas JSON.
|
||||
* @returns {Promise<Array<Object>>} Daftar objek data SOP
|
||||
*/
|
||||
export async function getSopData() {
|
||||
try {
|
||||
const response = await fetch('data/data-sop.json');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Gagal memuat data SOP:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mengambil data FAQ dari berkas JSON.
|
||||
* @returns {Promise<Array<Object>>} Daftar objek data FAQ
|
||||
*/
|
||||
export async function getFaqData() {
|
||||
try {
|
||||
const response = await fetch('data/data-faq.json');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Gagal memuat data FAQ:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Main Entry Point Kelompok C4A.
|
||||
* Menghubungkan pengambilan data dengan rendering UI dan interaksi halaman.
|
||||
*/
|
||||
|
||||
import { getStaffData, getSopData, getFaqData } from './data.js';
|
||||
import {
|
||||
renderStaffCards,
|
||||
renderSopCards,
|
||||
renderFaqSection,
|
||||
initSopInteraction,
|
||||
initAnimations,
|
||||
initFaq
|
||||
} from './ui.js';
|
||||
|
||||
// Cache data secara global untuk mendukung rendering UI
|
||||
let cachedStaffData = [];
|
||||
let cachedSopData = [];
|
||||
|
||||
/**
|
||||
* Menginisialisasi aplikasi dengan mengambil seluruh data secara pararel,
|
||||
* merender konten awal, serta mendaftarkan event listener interaksi UI.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function initApp() {
|
||||
try {
|
||||
const [staffData, sopData, faqData] = await Promise.all([
|
||||
getStaffData(),
|
||||
getSopData(),
|
||||
getFaqData()
|
||||
]);
|
||||
|
||||
cachedStaffData = staffData || [];
|
||||
cachedSopData = sopData || [];
|
||||
|
||||
renderStaffCards(cachedStaffData);
|
||||
renderSopCards(cachedSopData);
|
||||
renderFaqSection(faqData || []);
|
||||
|
||||
initSopInteraction();
|
||||
initFaq();
|
||||
|
||||
// Memberikan sedikit waktu tunda (jeda) agar rendering DOM selesai sebelum memulai animasi.
|
||||
setTimeout(() => {
|
||||
initAnimations();
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error('Terjadi kesalahan saat menginisialisasi aplikasi C4A:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Fitur bilingual sudah dihapus - languageChanged listener tidak lagi diperlukan
|
||||
// Silakan hapus atau archive jika ada implementasi global untuk bahasa
|
||||
|
||||
// Jalankan inisialisasi aplikasi saat DOM siap
|
||||
document.addEventListener('DOMContentLoaded', initApp);
|
||||
@@ -0,0 +1,576 @@
|
||||
// --- Helper Fungsi ---
|
||||
|
||||
/**
|
||||
* Normalisasi nomor telepon ke format standard e.g. 628...
|
||||
* @param {string} phone - Nomor telepon mentah
|
||||
* @returns {string} Nomor telepon ternormalisasi
|
||||
*/
|
||||
function normalizePhone(phone) {
|
||||
let digits = String(phone || '').replace(/\D/g, '');
|
||||
if (digits.startsWith('0')) {
|
||||
digits = `62${digits.slice(1)}`;
|
||||
} else if (digits.startsWith('8')) {
|
||||
digits = `62${digits}`;
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format nomor telepon untuk tampilan UI
|
||||
* @param {string} phone - Nomor telepon mentah
|
||||
* @returns {string} Nomor telepon terformat (e.g. +62 8xx-xxxx-xxxx)
|
||||
*/
|
||||
function formatPhoneForDisplay(phone) {
|
||||
const norm = normalizePhone(phone);
|
||||
if (!norm) return '-';
|
||||
return `+${norm.slice(0, 2)} ${norm.slice(2, 5)}-${norm.slice(5, 9)}-${norm.slice(9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Memvalidasi apakah nomor telepon valid untuk digunakan di WhatsApp.
|
||||
* @param {string} phone - Nomor telepon mentah
|
||||
* @returns {boolean} True jika nomor valid (seluler Indonesia 10-15 digit)
|
||||
*/
|
||||
function isValidWhatsAppNumber(phone) {
|
||||
const norm = normalizePhone(phone);
|
||||
return norm.startsWith('628') && norm.length >= 10 && norm.length <= 15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merender daftar staf pendukung ke dalam container HTML.
|
||||
* @param {Array<Object>} staffs - Array data staf pendukung
|
||||
*/
|
||||
export function renderStaffCards(staffs) {
|
||||
const container = document.getElementById('staff-container');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
if (!staffs || staffs.length === 0) {
|
||||
container.innerHTML = `<p class="empty-state-msg">Data staf pendukung tidak tersedia saat ini.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
staffs.forEach((staff, index) => {
|
||||
const {
|
||||
name,
|
||||
role,
|
||||
image,
|
||||
location,
|
||||
phone,
|
||||
schedule,
|
||||
tasks
|
||||
} = staff;
|
||||
|
||||
const scheduleHTML = schedule.map(sched =>
|
||||
`<div class="schedule-item">` +
|
||||
`<span class="s-day">${sched.day}</span>` +
|
||||
`<span class="s-time">${sched.time} <span class="s-break">| ${sched.break}</span></span>` +
|
||||
`</div>`
|
||||
).join('');
|
||||
|
||||
const tasksHTML = tasks.map(task => `<li>${task}</li>`).join('');
|
||||
const initial = name ? name.charAt(0).toUpperCase() : '';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = `staff-card reveal reveal-delay-${index + 1}`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="staff-card-header">
|
||||
<div class="staff-avatar">
|
||||
<span class="staff-initial">${initial}</span>
|
||||
<img src="${image}" alt="${name}" onerror="this.style.display='none'"/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="staff-name">${name}</div>
|
||||
<div class="staff-role">${role}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="staff-card-body">
|
||||
<div class="staff-info-grid">
|
||||
<div class="staff-info-item"><span class="icon">📍</span><div><span class="label">Lokasi</span><span class="val">${location}</span></div></div>
|
||||
<div class="staff-info-item">
|
||||
<span class="icon">🕒</span>
|
||||
<div style="flex:1">
|
||||
<span class="label">Jam Layanan</span>
|
||||
<div class="schedule-list">${scheduleHTML}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="staff-info-item"><span class="icon">📞</span><div><span class="label">Kontak</span><span class="val">${phone}</span></div></div>
|
||||
</div>
|
||||
<div class="staff-divider"></div>
|
||||
<div class="staff-tasks-title">Tugas & Tanggung Jawab</div>
|
||||
<ul class="staff-tasks">${tasksHTML}</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
}
|
||||
// ============================================================
|
||||
// --- Render SOP sebagai Card Grid dengan Search & Filter ---
|
||||
// ============================================================
|
||||
|
||||
const CATEGORY_COLORS = {
|
||||
akademik: 'sop-cat-akademik',
|
||||
praktikum: 'sop-cat-praktikum',
|
||||
};
|
||||
|
||||
/**
|
||||
* Ambil label kategori
|
||||
* @param {string} category - Kategori SOP ('akademik' atau 'praktikum')
|
||||
* @returns {string} Label kategori
|
||||
*/
|
||||
function getCatLabel(category) {
|
||||
const map = { akademik: 'Akademik', praktikum: 'Praktikum' };
|
||||
return map[category] || 'Akademik';
|
||||
}
|
||||
|
||||
/**
|
||||
* Buat HTML untuk satu SOP card di grid.
|
||||
* @param {Object} sop - Data objek SOP
|
||||
* @returns {string} String markup HTML kartu SOP
|
||||
*/
|
||||
function buildSopCard(sop) {
|
||||
const {
|
||||
tab_id: tabId,
|
||||
category,
|
||||
icon = 'fa-file-alt',
|
||||
title,
|
||||
description,
|
||||
procedures
|
||||
} = sop;
|
||||
|
||||
const catLabel = getCatLabel(category);
|
||||
const catClass = CATEGORY_COLORS[category] || '';
|
||||
|
||||
return `
|
||||
<article class="sop-grid-card reveal" data-tab-id="${tabId}" data-category="${category}" data-title="${title.toLowerCase()}">
|
||||
<div class="sgc-header">
|
||||
<div class="sgc-icon-wrap">
|
||||
<i class="fas ${icon}"></i>
|
||||
</div>
|
||||
<div class="sgc-meta">
|
||||
<h3 class="sgc-title">${title}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sgc-category-row">
|
||||
<span class="sgc-category ${catClass}">${catLabel}</span>
|
||||
</div>
|
||||
<p class="sgc-desc">${description}</p>
|
||||
<div class="sgc-footer">
|
||||
<div class="sgc-steps">
|
||||
<i class="fas fa-list-ol"></i>
|
||||
<span>${procedures.length} Langkah</span>
|
||||
</div>
|
||||
<button class="sgc-detail-btn" data-tab-id="${tabId}" aria-label="Lihat Detail ${title}">
|
||||
Lihat Detail <i class="fas fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buat HTML untuk isi modal detail SOP.
|
||||
* @param {Object} sop - Data objek SOP
|
||||
* @returns {string} String markup HTML isi modal
|
||||
*/
|
||||
function buildSopModalContent(sop) {
|
||||
const {
|
||||
category,
|
||||
icon = 'fa-file-alt',
|
||||
title,
|
||||
description,
|
||||
purpose,
|
||||
requirements,
|
||||
procedures,
|
||||
staff_responsible: staffResponsible,
|
||||
additional_info: additionalInfo
|
||||
} = sop;
|
||||
|
||||
const catLabel = getCatLabel(category);
|
||||
const catClass = CATEGORY_COLORS[category] || '';
|
||||
|
||||
const reqsHTML = requirements.map(req => `<li>${req}</li>`).join('');
|
||||
const procsHTML = procedures.map(proc => `<li>${proc}</li>`).join('');
|
||||
|
||||
// staffResponsible bersifat opsional — sembunyikan blok jika tidak ada
|
||||
const hasStaff = Array.isArray(staffResponsible) && staffResponsible.length > 0;
|
||||
const staffHTML = hasStaff
|
||||
? staffResponsible.map(staffName => `<span class="sop-staff-pill">${staffName}</span>`).join('')
|
||||
: '';
|
||||
const staffBlockHTML = hasStaff
|
||||
? `<div class="sop-staff-block">
|
||||
<div class="sop-block-label">Staf Bertanggung Jawab (Bersifat Opsional)</div>
|
||||
<div class="sop-staff-pills">${staffHTML}</div>
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
// additionalInfo bersifat opsional — sembunyikan blok jika tidak ada
|
||||
const additionalInfoHTML = additionalInfo
|
||||
? `<div class="sop-info-box">
|
||||
<span class="iico">ℹ️</span>
|
||||
<p class="sop-info-box-text"><strong>Informasi Tambahan (Bersifat Opsional):</strong> ${additionalInfo}</p>
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="sop-modal-header">
|
||||
<div class="sop-modal-header-top">
|
||||
<div class="sop-modal-icon-wrap">
|
||||
<i class="fas ${icon}"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sop-modal-badges">
|
||||
<span class="sgc-category ${catClass}">${catLabel}</span>
|
||||
</div>
|
||||
<h2 class="sop-modal-title" id="sop-modal-title">${title}</h2>
|
||||
<p class="sop-modal-desc">${description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sop-modal-body">
|
||||
<div class="sop-block">
|
||||
<div class="sop-block-label">Tujuan</div>
|
||||
<p class="sop-block-text">${purpose}</p>
|
||||
</div>
|
||||
<div class="sop-two-col">
|
||||
<div>
|
||||
<div class="sop-block-label">Persyaratan</div>
|
||||
<ul class="sop-list bullets">${reqsHTML}</ul>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sop-block-label">Alur Prosedur</div>
|
||||
<ul class="sop-list numbered">${procsHTML}</ul>
|
||||
</div>
|
||||
</div>
|
||||
${staffBlockHTML}
|
||||
${additionalInfoHTML}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render semua SOP card ke grid.
|
||||
* @param {Array<Object>} sops - Array objek SOP
|
||||
*/
|
||||
export function renderSopCards(sops) {
|
||||
const container = document.getElementById('sop-cards-container');
|
||||
if (!container) return;
|
||||
|
||||
// simpan data global di element agar bisa diakses filter/search
|
||||
container._sopData = sops;
|
||||
|
||||
// Tampilkan pesan jika data SOP kosong atau gagal dimuat
|
||||
if (!sops || sops.length === 0) {
|
||||
container.innerHTML = `<p class="empty-state-msg">Data SOP tidak tersedia saat ini.</p>`;
|
||||
updateResultInfo(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = sops.map(buildSopCard).join('');
|
||||
updateResultInfo(sops.length, sops.length);
|
||||
}
|
||||
|
||||
/** Update teks info jumlah hasil */
|
||||
function updateResultInfo(shown, total) {
|
||||
const el = document.getElementById('sop-result-info');
|
||||
if (!el) return;
|
||||
if (shown === total) {
|
||||
el.textContent = `Menampilkan ${total} SOP`;
|
||||
} else {
|
||||
el.textContent = `Menampilkan ${shown} dari ${total} SOP`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Memfilter dan menyaring kartu SOP berdasarkan query pencarian dan chip filter aktif.
|
||||
*/
|
||||
function applySopFilter() {
|
||||
const container = document.getElementById('sop-cards-container');
|
||||
if (!container) return;
|
||||
|
||||
const query = (document.getElementById('sop-search-input')?.value || '').toLowerCase().trim();
|
||||
const activeChip = document.querySelector('.sop-chip.active');
|
||||
const activeFilter = activeChip ? activeChip.dataset.filter : 'semua';
|
||||
|
||||
const cards = container.querySelectorAll('.sop-grid-card');
|
||||
let visibleCount = 0;
|
||||
|
||||
cards.forEach(card => {
|
||||
const title = card.dataset.title || '';
|
||||
const code = card.dataset.code || '';
|
||||
const category = card.dataset.category || '';
|
||||
|
||||
const matchesSearch = !query || title.includes(query) || code.includes(query);
|
||||
const matchesFilter = activeFilter === 'semua' || category === activeFilter;
|
||||
|
||||
const isVisible = matchesSearch && matchesFilter;
|
||||
card.style.display = isVisible ? '' : 'none';
|
||||
if (isVisible) visibleCount++;
|
||||
});
|
||||
|
||||
const emptyState = document.getElementById('sop-empty-state');
|
||||
if (emptyState) emptyState.style.display = visibleCount === 0 ? 'flex' : 'none';
|
||||
|
||||
updateResultInfo(visibleCount, cards.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Membuka modal detail untuk SOP tertentu berdasarkan tab_id.
|
||||
* @param {string} tabId - ID unik tab/SOP yang ingin ditampilkan
|
||||
*/
|
||||
function openSopModal(tabId) {
|
||||
const container = document.getElementById('sop-cards-container');
|
||||
if (!container || !container._sopData) return;
|
||||
const sop = container._sopData.find(item => item.tab_id === tabId);
|
||||
if (!sop) return;
|
||||
|
||||
const modalContent = document.getElementById('sop-modal-content');
|
||||
const modalOverlay = document.getElementById('sop-modal-overlay');
|
||||
if (!modalContent || !modalOverlay) return;
|
||||
|
||||
modalContent.innerHTML = buildSopModalContent(sop);
|
||||
modalOverlay.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Gulir isi modal ke paling atas
|
||||
const modalBox = document.getElementById('sop-modal-box');
|
||||
if (modalBox) modalBox.scrollTop = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menutup modal detail SOP yang sedang aktif.
|
||||
*/
|
||||
function closeSopModal() {
|
||||
const modalOverlay = document.getElementById('sop-modal-overlay');
|
||||
if (modalOverlay) modalOverlay.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Menginisialisasi interaksi halaman SOP (pencarian, filter chip, modal detail).
|
||||
*/
|
||||
export function initSopInteraction() {
|
||||
// Search input
|
||||
const searchInput = document.getElementById('sop-search-input');
|
||||
const searchClear = document.getElementById('sop-search-clear');
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', () => {
|
||||
if (searchClear) {
|
||||
searchClear.style.display = searchInput.value ? 'flex' : 'none';
|
||||
}
|
||||
applySopFilter();
|
||||
});
|
||||
}
|
||||
|
||||
if (searchClear) {
|
||||
searchClear.addEventListener('click', () => {
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
searchClear.style.display = 'none';
|
||||
applySopFilter();
|
||||
});
|
||||
}
|
||||
|
||||
// Filter chips
|
||||
document.querySelectorAll('.sop-chip').forEach(chip => {
|
||||
chip.addEventListener('click', () => {
|
||||
document.querySelectorAll('.sop-chip').forEach(activeChip => {
|
||||
activeChip.classList.remove('active');
|
||||
});
|
||||
chip.classList.add('active');
|
||||
applySopFilter();
|
||||
});
|
||||
});
|
||||
|
||||
// Empty state reset button
|
||||
const emptyReset = document.getElementById('sop-empty-reset');
|
||||
if (emptyReset) {
|
||||
emptyReset.addEventListener('click', () => {
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
if (searchClear) {
|
||||
searchClear.style.display = 'none';
|
||||
}
|
||||
document.querySelectorAll('.sop-chip').forEach(activeChip => {
|
||||
activeChip.classList.remove('active');
|
||||
});
|
||||
const allChip = document.querySelector('[data-filter="semua"]');
|
||||
if (allChip) {
|
||||
allChip.classList.add('active');
|
||||
}
|
||||
applySopFilter();
|
||||
});
|
||||
}
|
||||
|
||||
// Card detail button (event delegation)
|
||||
const container = document.getElementById('sop-cards-container');
|
||||
if (container) {
|
||||
container.addEventListener('click', (event) => {
|
||||
const btn = event.target.closest('.sgc-detail-btn');
|
||||
if (btn) {
|
||||
openSopModal(btn.dataset.tabId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Modal close button
|
||||
const closeBtn = document.getElementById('sop-modal-close');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', closeSopModal);
|
||||
}
|
||||
|
||||
// Close on overlay click
|
||||
const overlay = document.getElementById('sop-modal-overlay');
|
||||
if (overlay) {
|
||||
overlay.addEventListener('click', (event) => {
|
||||
if (event.target === overlay) {
|
||||
closeSopModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Close on Escape key
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeSopModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Kompatibilitas Mundur ---
|
||||
|
||||
/**
|
||||
* Stub kompatibilitas mundur untuk renderSopTabs.
|
||||
* @param {Array<Object>} sops - Array objek SOP
|
||||
*/
|
||||
export function renderSopTabs(sops) {
|
||||
renderSopCards(sops);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub kompatibilitas mundur untuk initSopTabsInteraction.
|
||||
*/
|
||||
export function initSopTabsInteraction() {
|
||||
initSopInteraction();
|
||||
}
|
||||
|
||||
// --- Interaksi & Animasi ---
|
||||
|
||||
/**
|
||||
* Menginisialisasi animasi scroll reveal dan counter statistik pada hero section.
|
||||
*/
|
||||
export function initAnimations() {
|
||||
// Scroll Reveal
|
||||
const revealObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) return;
|
||||
entry.target.classList.add('visible');
|
||||
revealObserver.unobserve(entry.target);
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
document.querySelectorAll('.reveal').forEach((el) => revealObserver.observe(el));
|
||||
|
||||
// Hero Counter
|
||||
const COUNT_ANIMATION_DURATION_MS = 800;
|
||||
|
||||
/**
|
||||
* Menjalankan animasi hitung angka (counter)
|
||||
* @param {Element} targetElement - Elemen target angka
|
||||
* @param {number} targetValue - Nilai akhir counter
|
||||
*/
|
||||
function animateCount(targetElement, targetValue) {
|
||||
if (!targetElement) return;
|
||||
const stepValue = targetValue / (COUNT_ANIMATION_DURATION_MS / 16);
|
||||
let currentValue = 0;
|
||||
const timerId = setInterval(() => {
|
||||
currentValue += stepValue;
|
||||
if (currentValue >= targetValue) {
|
||||
currentValue = targetValue;
|
||||
clearInterval(timerId);
|
||||
}
|
||||
targetElement.textContent = Math.floor(currentValue).toString();
|
||||
}, 16);
|
||||
}
|
||||
|
||||
const staffCounter = document.getElementById('stat-staf');
|
||||
const sopCounter = document.getElementById('stat-sop');
|
||||
|
||||
// Hitung jumlah data yang berhasil di-render (tidak termasuk template)
|
||||
const totalStaffCards = document.querySelectorAll('.staff-card').length;
|
||||
const totalSopCards = document.querySelectorAll('.sop-grid-card').length;
|
||||
|
||||
animateCount(staffCounter, totalStaffCards);
|
||||
animateCount(sopCounter, totalSopCards);
|
||||
}
|
||||
|
||||
// --- FAQ Accordion ---
|
||||
|
||||
/**
|
||||
* Membuka item FAQ yang diklik; menutup item lain yang sebelumnya
|
||||
* terbuka (perilaku accordion satu-terbuka-sekaligus).
|
||||
* @param {Element} questionEl - Elemen .faq-question yang diklik
|
||||
*/
|
||||
function toggleFaq(questionEl) {
|
||||
const item = questionEl.closest('.faq-item');
|
||||
if (!item) return;
|
||||
const isOpen = item.classList.contains('open');
|
||||
|
||||
// Tutup semua item terlebih dahulu
|
||||
document.querySelectorAll('.faq-item').forEach(faqItem => {
|
||||
faqItem.classList.remove('open');
|
||||
});
|
||||
|
||||
// Buka item yang diklik hanya jika sebelumnya tertutup
|
||||
if (!isOpen) {
|
||||
item.classList.add('open');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menginisialisasi event listener klik untuk setiap accordion FAQ.
|
||||
*/
|
||||
export function initFaq() {
|
||||
document.querySelectorAll('.faq-question').forEach(questionEl => {
|
||||
questionEl.addEventListener('click', () => toggleFaq(questionEl));
|
||||
});
|
||||
}
|
||||
|
||||
// --- Render FAQ ---
|
||||
|
||||
/**
|
||||
* Merender section FAQ secara dinamis dari data-faq.json.
|
||||
* Diinjeksikan ke dalam #faq-container di index.html.
|
||||
* @param {Array<Object>} faqs - Array objek FAQ dari data-faq.json
|
||||
*/
|
||||
export function renderFaqSection(faqs) {
|
||||
const container = document.getElementById('faq-container');
|
||||
if (!container) return;
|
||||
|
||||
if (!faqs || faqs.length === 0) {
|
||||
container.innerHTML = `<p class="empty-state-msg">Data FAQ tidak tersedia saat ini.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const delayClasses = ['reveal-delay-1', 'reveal-delay-2', 'reveal-delay-3'];
|
||||
|
||||
const itemsHTML = faqs.map((faq, index) => {
|
||||
const delayClass = delayClasses[index] || '';
|
||||
return `
|
||||
<div class="faq-item reveal ${delayClass}">
|
||||
<div class="faq-question">
|
||||
<span class="faq-question-text">${faq.question}</span>
|
||||
<div class="faq-icon">+</div>
|
||||
</div>
|
||||
<div class="faq-answer">
|
||||
<div class="faq-answer-inner">${faq.answer}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = itemsHTML;
|
||||
}
|
||||
+1487
-2
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user