From 39adcc21ee3ca5f451690eb5c3e030f6b48c3329 Mon Sep 17 00:00:00 2001 From: cygouw Date: Thu, 4 Jun 2026 23:06:53 +0700 Subject: [PATCH 1/6] feat(staff): protect WhatsApp links from basic web scraping --- groups/C4A/js/ui.js | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/groups/C4A/js/ui.js b/groups/C4A/js/ui.js index 193ed38..5984123 100644 --- a/groups/C4A/js/ui.js +++ b/groups/C4A/js/ui.js @@ -36,6 +36,29 @@ function isValidWhatsAppNumber(phone) { return norm.startsWith('628') && norm.length >= 10 && norm.length <= 15; } +/** + * Enkripsi nomor telepon menggunakan Base64 untuk keamanan kontak. + * @param {string} phone - Nomor telepon ternormalisasi + * @returns {string} Nomor telepon terenkripsi Base64 + */ +function encodePhone(phone) { + return btoa(normalizePhone(phone)); +} + +/** + * Dekripsi nomor telepon dari Base64. + * @param {string} encoded - Nomor telepon terenkripsi Base64 + * @returns {string} Nomor telepon terdekripsi + */ +function decodePhone(encoded) { + try { + return atob(encoded); + } catch (error) { + console.error('Gagal mendekripsi nomor telepon:', error); + return ''; + } +} + /** * Merender daftar staf pendukung ke dalam container HTML. * @param {Array} staffs - Array data staf pendukung @@ -58,6 +81,7 @@ export function renderStaffCards(staffs) { image, location, phone, + contact_consent: contactConsent, schedule, tasks } = staff; @@ -70,6 +94,11 @@ export function renderStaffCards(staffs) { ).join(''); const tasksHTML = tasks.map(task => `
  • ${task}
  • `).join(''); + const isPhoneValid = isValidWhatsAppNumber(phone); + const encodedPhone = isPhoneValid ? encodePhone(phone) : ''; + const displayPhone = isPhoneValid ? formatPhoneForDisplay(phone) : '-'; + const hideContactItemClass = contactConsent ? '' : 'is-hidden'; + const hideContactActionClass = (contactConsent && isPhoneValid) ? '' : 'is-hidden'; const initial = name ? name.charAt(0).toUpperCase() : ''; const card = document.createElement('div'); @@ -96,15 +125,29 @@ export function renderStaffCards(staffs) {
    ${scheduleHTML}
    -
    📞
    Kontak${phone}
    +
    📞
    Kontak WhatsApp${displayPhone}
    Tugas & Tanggung Jawab
    +
    + +
    `; container.appendChild(card); + + // Setup WA button click handler dengan dekrips nomor + const waBtn = card.querySelector('[data-wa]'); + if (waBtn) { + waBtn.addEventListener('click', () => { + const phoneNum = decodePhone(waBtn.dataset.wa); + if (phoneNum) { + window.open(`https://wa.me/${phoneNum}`, '_blank', 'noopener,noreferrer'); + } + }); + } }); } // ============================================================ From 2edbcb0c889a47bb1dbb4218ae80e837043f6219 Mon Sep 17 00:00:00 2001 From: cygouw Date: Thu, 4 Jun 2026 23:31:09 +0700 Subject: [PATCH 2/6] feat(ui): add language switcher for Indonesian and English --- assets/global/localization.js | 80 +++++++++++++++++++++++++++++++++++ groups/C4A/index.html | 42 +++++++++--------- groups/C4A/js/data.js | 2 + groups/C4A/js/main.js | 18 ++++++-- groups/C4A/js/ui.js | 62 ++++++++++++++++++--------- 5 files changed, 159 insertions(+), 45 deletions(-) diff --git a/assets/global/localization.js b/assets/global/localization.js index 82d1ca7..dfcc686 100644 --- a/assets/global/localization.js +++ b/assets/global/localization.js @@ -569,6 +569,46 @@ const translations = { kontrib_guideline_3: "Sertakan fakta dan data yang akurat dan dapat diverifikasi.", kontrib_guideline_4: "Hindari plagiarisme. Karya harus orisinal.", kontrib_guideline_5: "Tim redaksi berhak menyunting konten untuk keperluan editorial.", + + // --- Group C4A: Staf Pendukung & SOP --- + grp_c4a_hero_tag: "LAYANAN AKADEMIK \u2022 INFORMATIKA UNTAN", + grp_c4a_hero_title: "Staf Pendukung &
    Standar Operasional Prosedur (SOP)", + grp_c4a_hero_desc: "Halaman ini menampilkan informasi petugas layanan akademik serta prosedur administrasi mahasiswa Program Studi Informatika UNTAN.", + grp_c4a_stat_staf: "Staf Aktif", + grp_c4a_stat_sop: "Jenis SOP", + grp_c4a_staff_tag: "Tim Kami", + grp_c4a_staff_title: "Staf Pendukung", + grp_c4a_staff_subtitle: "Staf yang siap melayani kebutuhan akademik dan operasional laboratorium di Prodi Informatika UNTAN.", + grp_c4a_sop_tag: "Prosedur Resmi", + grp_c4a_sop_title: "Standar Operasional Prosedur (SOP)", + grp_c4a_sop_subtitle: "Standar Operasional Prosedur untuk berbagai kebutuhan administrasi akademik mahasiswa. Gunakan pencarian atau filter untuk menemukan SOP yang Anda butuhkan.", + grp_c4a_sop_search_placeholder: "Cari SOP berdasarkan judul...", + grp_c4a_sop_chip_semua: "Semua", + grp_c4a_sop_chip_akademik: "Akademik", + grp_c4a_sop_chip_praktikum: "Praktikum", + grp_c4a_sop_empty_title: "SOP tidak ditemukan", + grp_c4a_sop_empty_desc: "Coba kata kunci lain atau reset filter pencarian.", + grp_c4a_sop_reset_btn: "Reset Pencarian", + grp_c4a_faq_tag: "Pertanyaan Umum", + grp_c4a_faq_title: "Pertanyaan yang Sering Diajukan", + grp_c4a_faq_subtitle: "Temukan jawaban cepat atas pertanyaan umum mahasiswa seputar layanan akademik prodi.", + grp_c4a_empty_staff: "Data staf pendukung tidak tersedia saat ini.", + grp_c4a_empty_sop: "Data SOP tidak tersedia saat ini.", + grp_c4a_empty_faq: "Data FAQ tidak tersedia saat ini.", + grp_c4a_label_location: "Lokasi", + grp_c4a_label_service_hours: "Jam Layanan", + grp_c4a_label_wa_contact: "Kontak WhatsApp", + grp_c4a_label_tasks: "Tugas & Tanggung Jawab", + grp_c4a_label_send_wa: "Kirim Pesan WA", + grp_c4a_label_steps: "Langkah", + grp_c4a_label_view_detail: "Lihat Detail", + grp_c4a_label_responsible_staff: "Staf Bertanggung Jawab (Bersifat Opsional)", + grp_c4a_label_additional_info: "Informasi Tambahan (Bersifat Opsional):", + grp_c4a_label_purpose: "Tujuan", + grp_c4a_label_requirements: "Persyaratan", + grp_c4a_label_procedure: "Alur Prosedur", + grp_c4a_sop_showing_all: "Menampilkan {total} SOP", + grp_c4a_sop_showing_filtered: "Menampilkan {shown} dari {total} SOP", }, en: { @@ -1135,6 +1175,46 @@ const translations = { kontrib_guideline_3: "Include accurate and verifiable facts and data.", kontrib_guideline_4: "Avoid plagiarism. Work must be original.", kontrib_guideline_5: "The editorial team reserves the right to edit content for editorial purposes.", + + // --- Group C4A: Supporting Staff & SOP --- + grp_c4a_hero_tag: "ACADEMIC SERVICES \u2022 INFORMATICS UNTAN", + grp_c4a_hero_title: "Supporting Staff &
    Standard Operating Procedures (SOP)", + grp_c4a_hero_desc: "This page presents information about academic service officers and administrative procedures for students of the Informatics Study Program at UNTAN.", + grp_c4a_stat_staf: "Active Staff", + grp_c4a_stat_sop: "SOP Types", + grp_c4a_staff_tag: "Our Team", + grp_c4a_staff_title: "Supporting Staff", + grp_c4a_staff_subtitle: "Staff ready to serve academic and laboratory operational needs at the Informatics Study Program, UNTAN.", + grp_c4a_sop_tag: "Official Procedures", + grp_c4a_sop_title: "Standard Operating Procedures (SOP)", + grp_c4a_sop_subtitle: "Standard Operating Procedures for various student academic administration needs. Use search or filter to find the SOP you need.", + grp_c4a_sop_search_placeholder: "Search SOP by title...", + grp_c4a_sop_chip_semua: "All", + grp_c4a_sop_chip_akademik: "Academic", + grp_c4a_sop_chip_praktikum: "Practicum", + grp_c4a_sop_empty_title: "No SOP found", + grp_c4a_sop_empty_desc: "Try another keyword or reset the search filter.", + grp_c4a_sop_reset_btn: "Reset Search", + grp_c4a_faq_tag: "Frequently Asked", + grp_c4a_faq_title: "Frequently Asked Questions", + grp_c4a_faq_subtitle: "Find quick answers to common student questions about the study program's academic services.", + grp_c4a_empty_staff: "Staff data is currently unavailable.", + grp_c4a_empty_sop: "SOP data is currently unavailable.", + grp_c4a_empty_faq: "FAQ data is currently unavailable.", + grp_c4a_label_location: "Location", + grp_c4a_label_service_hours: "Service Hours", + grp_c4a_label_wa_contact: "WhatsApp Contact", + grp_c4a_label_tasks: "Tasks & Responsibilities", + grp_c4a_label_send_wa: "Send WA Message", + grp_c4a_label_steps: "Steps", + grp_c4a_label_view_detail: "View Details", + grp_c4a_label_responsible_staff: "Responsible Staff (Optional)", + grp_c4a_label_additional_info: "Additional Information (Optional):", + grp_c4a_label_purpose: "Purpose", + grp_c4a_label_requirements: "Requirements", + grp_c4a_label_procedure: "Procedure Flow", + grp_c4a_sop_showing_all: "Showing {total} SOP", + grp_c4a_sop_showing_filtered: "Showing {shown} of {total} SOP", } }; diff --git a/groups/C4A/index.html b/groups/C4A/index.html index de2df19..e6f10bd 100644 --- a/groups/C4A/index.html +++ b/groups/C4A/index.html @@ -1,4 +1,4 @@ -īģŋ + @@ -1159,13 +1159,13 @@
    -
    LAYANAN AKADEMIK • INFORMATIKA UNTAN +
    LAYANAN AKADEMIK • INFORMATIKA UNTAN
    -

    +

    Staf Pendukung &
    Standar Operasional Prosedur (SOP)

    -

    Halaman ini menampilkan informasi petugas +

    Halaman ini menampilkan informasi petugas layanan akademik serta prosedur administrasi mahasiswa @@ -1173,12 +1173,12 @@

    0
    -
    Staf Aktif
    +
    Staf Aktif
    0
    -
    Jenis SOP
    +
    Jenis SOP
    @@ -1187,10 +1187,10 @@
    - @@ -1202,10 +1202,10 @@
    - @@ -1243,8 +1244,8 @@ + `; container.appendChild(card); @@ -165,8 +178,11 @@ const CATEGORY_COLORS = { * @returns {string} Label kategori */ function getCatLabel(category) { - const map = { akademik: 'Akademik', praktikum: 'Praktikum' }; - return map[category] || 'Akademik'; + const map = { + akademik: t('grp_c4a_sop_chip_akademik', 'Akademik'), + praktikum: t('grp_c4a_sop_chip_praktikum', 'Praktikum') + }; + return map[category] || t('grp_c4a_sop_chip_akademik', 'Akademik'); } /** @@ -204,10 +220,10 @@ function buildSopCard(sop) { `; @@ -244,7 +260,7 @@ function buildSopModalContent(sop) { : ''; const staffBlockHTML = hasStaff ? `
    -
    Staf Bertanggung Jawab (Bersifat Opsional)
    +
    ${t('grp_c4a_label_responsible_staff', 'Staf Bertanggung Jawab (Bersifat Opsional)')}
    ${staffHTML}
    ` : ''; @@ -253,7 +269,7 @@ function buildSopModalContent(sop) { const additionalInfoHTML = additionalInfo ? `
    â„šī¸ -

    Informasi Tambahan (Bersifat Opsional): ${additionalInfo}

    +

    ${t('grp_c4a_label_additional_info', 'Informasi Tambahan (Bersifat Opsional):')} ${additionalInfo}

    ` : ''; @@ -274,16 +290,16 @@ function buildSopModalContent(sop) {
    -
    Tujuan
    +
    ${t('grp_c4a_label_purpose', 'Tujuan')}

    ${purpose}

    -
    Persyaratan
    +
    ${t('grp_c4a_label_requirements', 'Persyaratan')}
      ${reqsHTML}
    -
    Alur Prosedur
    +
    ${t('grp_c4a_label_procedure', 'Alur Prosedur')}
      ${procsHTML}
    @@ -305,7 +321,7 @@ export function renderSopCards(sops) { // Tampilkan pesan jika data SOP kosong atau gagal dimuat if (!sops || sops.length === 0) { - container.innerHTML = `

    Data SOP tidak tersedia saat ini.

    `; + container.innerHTML = `

    ${t('grp_c4a_empty_sop', 'Data SOP tidak tersedia saat ini.')}

    `; updateResultInfo(0, 0); return; } @@ -319,16 +335,18 @@ function updateResultInfo(shown, total) { const el = document.getElementById('sop-result-info'); if (!el) return; if (shown === total) { - el.textContent = `Menampilkan ${total} SOP`; + const allTemplate = t('grp_c4a_sop_showing_all', 'Menampilkan {total} SOP'); + el.textContent = allTemplate.replace('{total}', total); } else { - el.textContent = `Menampilkan ${shown} dari ${total} SOP`; + const filteredTemplate = t('grp_c4a_sop_showing_filtered', 'Menampilkan {shown} dari {total} SOP'); + el.textContent = filteredTemplate.replace('{shown}', shown).replace('{total}', total); } } /** * Memfilter dan menyaring kartu SOP berdasarkan query pencarian dan chip filter aktif. */ -function applySopFilter() { +export function applySopFilter() { const container = document.getElementById('sop-cards-container'); if (!container) return; @@ -358,6 +376,7 @@ function applySopFilter() { updateResultInfo(visibleCount, cards.length); } + /** * Membuka modal detail untuk SOP tertentu berdasarkan tab_id. * @param {string} tabId - ID unik tab/SOP yang ingin ditampilkan @@ -595,10 +614,11 @@ export function renderFaqSection(faqs) { if (!container) return; if (!faqs || faqs.length === 0) { - container.innerHTML = `

    Data FAQ tidak tersedia saat ini.

    `; + container.innerHTML = `

    ${t('grp_c4a_empty_faq', 'Data FAQ tidak tersedia saat ini.')}

    `; return; } + const delayClasses = ['reveal-delay-1', 'reveal-delay-2', 'reveal-delay-3']; const itemsHTML = faqs.map((faq, index) => { From 5ccdba561e70d4117def648f67bbd40bd0b87bb0 Mon Sep 17 00:00:00 2001 From: cygouw Date: Thu, 4 Jun 2026 23:40:06 +0700 Subject: [PATCH 3/6] fix(ui): re-init animations after language toggle to prevent content from disappearing --- groups/C4A/js/main.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/groups/C4A/js/main.js b/groups/C4A/js/main.js index 96f990c..56a0743 100644 --- a/groups/C4A/js/main.js +++ b/groups/C4A/js/main.js @@ -59,6 +59,11 @@ document.addEventListener('languageChanged', () => { renderFaqSection(cachedFaqData); initFaq(); applySopFilter(); + + // Re-inisialisasi observer agar elemen .reveal yang baru di-render mendapat class .visible + setTimeout(() => { + initAnimations(); + }, 100); }); // Jalankan inisialisasi aplikasi saat DOM siap From d2ae63fe62c603f48c3276a81c1d8b77682ecc78 Mon Sep 17 00:00:00 2001 From: cygouw Date: Thu, 4 Jun 2026 23:50:12 +0700 Subject: [PATCH 4/6] docs(c4a): add readme for data management and template --- groups/C4A/README.md | 86 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 groups/C4A/README.md diff --git a/groups/C4A/README.md b/groups/C4A/README.md new file mode 100644 index 0000000..a5f6721 --- /dev/null +++ b/groups/C4A/README.md @@ -0,0 +1,86 @@ +# Panduan Pengelolaan Data Kelompok C4A + +Dokumentasi ini dibuat untuk memudahkan pengembang atau pengguna berikutnya dalam mengelola data Staf Pendukung, SOP, dan FAQ pada halaman C4A. + +--- + +## 📋 Aturan Umum Pengelolaan Data + +1. **Lokasi Berkas Data**: + Seluruh data disimpan dalam format JSON di direktori `groups/C4A/data/`: + - Data Staf: `groups/C4A/data/data-staff.json` + - Data SOP: `groups/C4A/data/data-sop.json` + - Data FAQ: `groups/C4A/data/data-faq.json` + +2. **Ikon FontAwesome (v4)**: + Jika ingin mengubah atau menambahkan logo/ikon pada data SOP, harap gunakan daftar ikon dari **FontAwesome v4** melalui tautan berikut: + 👉 **[FontAwesome v4 Icons](https://fontawesome.com/v4/icons/)** (Contoh penggunaan: `fa-dot-circle-o`, `fa-graduation-cap`, `fa-flask`). + +3. **Field Opsional**: + Pada data SOP, properti **`staff_responsible`** (Staf Bertanggung Jawab) dan **`additional_info`** (Informasi Tambahan) bersifat **opsional**. Jika informasi tersebut tidak tersedia, kosongkan nilainya atau hapus propertinya dari JSON agar UI secara otomatis menyembunyikan bloknya. + +--- + +## đŸ› ī¸ Template Penambahan Data + +Silakan salin-tempel (copy-paste) format di bawah ini ke dalam array JSON yang sesuai di folder `data/` untuk menambahkan data baru. + +### 1. Template Data Staf (`data-staff.json`) +```json +{ + "id": 3, + "name": "Nama Staf Beserta Gelar", + "role": "Peran / Jabatan Staf", + "image": "assets/images/nama-file-foto.png", + "location": "Lokasi Ruang Kerja / Kantor Staf", + "contact_consent": true, + "phone": "628xxxxxxxxxx", + "schedule": [ + { + "day": "Hari Kerja (contoh: Senin – Jumat)", + "time": "Jam Kerja (contoh: 07.30–16.00 WIB)", + "break": "Jam Istirahat (contoh: istirahat 12.00–13.00 WIB)" + } + ], + "tasks": [ + "Tugas dan tanggung jawab utama 1", + "Tugas dan tanggung jawab utama 2" + ] +} +``` +*Catatan: Letakkan file gambar/foto staf ke dalam direktori `groups/C4A/assets/images/` dan masukkan nama berkas fotonya pada properti `image` di atas. Untuk nomor telepon (`phone`), Anda dapat menggunakan format `08xxxxxxxxxx` atau `628xxxxxxxxxx` karena skrip JavaScript akan melakukan normalisasi secara otomatis.* + +### 2. Template Data SOP (`data-sop.json`) +```json +{ + "tab_id": "id-unik-sop-tanpa-spasi", + "tab_number": 9, + "category": "akademik", + "icon": "fa-file-text", + "title": "Judul Lengkap SOP", + "description": "Deskripsi singkat mengenai SOP ini.", + "purpose": "Tujuan dibuatnya standar operasional prosedur ini.", + "requirements": [ + "Persyaratan administratif/akademik 1", + "Persyaratan administratif/akademik 2" + ], + "procedures": [ + "Langkah alur prosedur ke-1", + "Langkah alur prosedur ke-2" + ], + "staff_responsible": [ + "👤 Staf Administrasi Informatika" + ], + "additional_info": "Informasi tambahan penting." +} +``` +*Catatan: Properti `category` bernilai antara `"akademik"` atau `"praktikum"`. Properti `staff_responsible` dan `additional_info` bersifat opsional.* + +### 3. Template Data FAQ (`data-faq.json`) +```json +{ + "id": 11, + "question": "Pertanyaan yang sering diajukan?", + "answer": "Jawaban dari pertanyaan tersebut. Boleh menggunakan tag HTML seperti tebal." +} +``` From 946497214b178b920ce06eb39131d3f7f149fec3 Mon Sep 17 00:00:00 2001 From: cygouw Date: Fri, 5 Jun 2026 14:18:12 +0700 Subject: [PATCH 5/6] docs: revisi README C4A untuk menyesuaikan format data JSON bilingual --- groups/C4A/README.md | 79 +++++++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/groups/C4A/README.md b/groups/C4A/README.md index a5f6721..abb90fa 100644 --- a/groups/C4A/README.md +++ b/groups/C4A/README.md @@ -12,11 +12,14 @@ Dokumentasi ini dibuat untuk memudahkan pengembang atau pengguna berikutnya dala - Data SOP: `groups/C4A/data/data-sop.json` - Data FAQ: `groups/C4A/data/data-faq.json` -2. **Ikon FontAwesome (v4)**: +2. **Dukungan Bilingual (ID/EN)**: + Semua teks konten yang ditampilkan ke pengguna mendukung dua bahasa. Anda perlu menggunakan objek dengan properti `id` (Bahasa Indonesia) dan `en` (Bahasa Inggris) pada setiap field teks. Jika properti `en` tidak diisi atau data masih berupa *string* biasa, sistem akan otomatis menggunakan Bahasa Indonesia sebagai *fallback*. + +3. **Ikon FontAwesome (v4)**: Jika ingin mengubah atau menambahkan logo/ikon pada data SOP, harap gunakan daftar ikon dari **FontAwesome v4** melalui tautan berikut: 👉 **[FontAwesome v4 Icons](https://fontawesome.com/v4/icons/)** (Contoh penggunaan: `fa-dot-circle-o`, `fa-graduation-cap`, `fa-flask`). -3. **Field Opsional**: +4. **Field Opsional**: Pada data SOP, properti **`staff_responsible`** (Staf Bertanggung Jawab) dan **`additional_info`** (Informasi Tambahan) bersifat **opsional**. Jika informasi tersebut tidak tersedia, kosongkan nilainya atau hapus propertinya dari JSON agar UI secara otomatis menyembunyikan bloknya. --- @@ -30,21 +33,39 @@ Silakan salin-tempel (copy-paste) format di bawah ini ke dalam array JSON yang s { "id": 3, "name": "Nama Staf Beserta Gelar", - "role": "Peran / Jabatan Staf", + "role": { + "id": "Peran / Jabatan Staf", + "en": "Staff Role / Title" + }, "image": "assets/images/nama-file-foto.png", - "location": "Lokasi Ruang Kerja / Kantor Staf", + "location": { + "id": "Lokasi Ruang Kerja / Kantor Staf", + "en": "Staff Workspace / Office Location" + }, "contact_consent": true, "phone": "628xxxxxxxxxx", "schedule": [ { - "day": "Hari Kerja (contoh: Senin – Jumat)", - "time": "Jam Kerja (contoh: 07.30–16.00 WIB)", - "break": "Jam Istirahat (contoh: istirahat 12.00–13.00 WIB)" + "day": { + "id": "Hari Kerja (contoh: Senin – Jumat)", + "en": "Work Days (e.g., Monday – Friday)" + }, + "time": "07.30–16.00 WIB", + "break": { + "id": "Jam Istirahat (contoh: istirahat 12.00–13.00 WIB)", + "en": "Break Time (e.g., break 12.00–13.00 WIB)" + } } ], "tasks": [ - "Tugas dan tanggung jawab utama 1", - "Tugas dan tanggung jawab utama 2" + { + "id": "Tugas dan tanggung jawab utama 1", + "en": "Main task and responsibility 1" + }, + { + "id": "Tugas dan tanggung jawab utama 2", + "en": "Main task and responsibility 2" + } ] } ``` @@ -57,21 +78,33 @@ Silakan salin-tempel (copy-paste) format di bawah ini ke dalam array JSON yang s "tab_number": 9, "category": "akademik", "icon": "fa-file-text", - "title": "Judul Lengkap SOP", - "description": "Deskripsi singkat mengenai SOP ini.", - "purpose": "Tujuan dibuatnya standar operasional prosedur ini.", + "title": { + "id": "Judul Lengkap SOP", + "en": "Full SOP Title" + }, + "description": { + "id": "Deskripsi singkat mengenai SOP ini.", + "en": "Brief description of this SOP." + }, + "purpose": { + "id": "Tujuan dibuatnya standar operasional prosedur ini.", + "en": "The purpose of creating this standard operating procedure." + }, "requirements": [ - "Persyaratan administratif/akademik 1", - "Persyaratan administratif/akademik 2" + { "id": "Persyaratan administratif/akademik 1", "en": "Administrative/academic requirement 1" }, + { "id": "Persyaratan administratif/akademik 2", "en": "Administrative/academic requirement 2" } ], "procedures": [ - "Langkah alur prosedur ke-1", - "Langkah alur prosedur ke-2" + { "id": "Langkah alur prosedur ke-1", "en": "Procedure flow step 1" }, + { "id": "Langkah alur prosedur ke-2", "en": "Procedure flow step 2" } ], "staff_responsible": [ - "👤 Staf Administrasi Informatika" + "👤 Staf Administrasi Informatika / Informatics Administrative Staff" ], - "additional_info": "Informasi tambahan penting." + "additional_info": { + "id": "Informasi tambahan penting.", + "en": "Important additional information." + } } ``` *Catatan: Properti `category` bernilai antara `"akademik"` atau `"praktikum"`. Properti `staff_responsible` dan `additional_info` bersifat opsional.* @@ -80,7 +113,13 @@ Silakan salin-tempel (copy-paste) format di bawah ini ke dalam array JSON yang s ```json { "id": 11, - "question": "Pertanyaan yang sering diajukan?", - "answer": "Jawaban dari pertanyaan tersebut. Boleh menggunakan tag HTML seperti tebal." + "question": { + "id": "Pertanyaan yang sering diajukan?", + "en": "Frequently asked question?" + }, + "answer": { + "id": "Jawaban dari pertanyaan tersebut. Boleh menggunakan tag HTML seperti tebal.", + "en": "Answer to the question. HTML tags like bold can be used." + } } ``` From 24e1d6ec1744a46cad2a41e5b268bbaae15e994b Mon Sep 17 00:00:00 2001 From: cygouw Date: Fri, 5 Jun 2026 14:50:20 +0700 Subject: [PATCH 6/6] fix(c4a): fix 'SOP tidak ditemukan' visibility logic on page load --- groups/C4A/js/ui.js | 72 ++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/groups/C4A/js/ui.js b/groups/C4A/js/ui.js index 7dbca5d..10cdf4b 100644 --- a/groups/C4A/js/ui.js +++ b/groups/C4A/js/ui.js @@ -12,6 +12,20 @@ function t(key, fallback) { return fallback; } +/** + * Helper untuk melokalisasi teks dari objek bilingual {id, en} atau string biasa. + * @param {string|Object} field - Data teks (bisa string biasa atau objek {id, en}) + * @returns {string} Teks terlokalisasi + */ +function loc(field) { + if (!field) return ''; + if (typeof field === 'object') { + const lang = (window.getLang && typeof window.getLang === 'function') ? window.getLang() : 'id'; + return field[lang] || field['id'] || ''; + } + return String(field); +} + /** * Normalisasi nomor telepon ke format standard e.g. 628... * @param {string} phone - Nomor telepon mentah @@ -100,12 +114,12 @@ export function renderStaffCards(staffs) { const scheduleHTML = schedule.map(sched => `
    ` + - `${sched.day}` + - `${sched.time} | ${sched.break}` + + `${loc(sched.day)}` + + `${loc(sched.time)} | ${loc(sched.break)}` + `
    ` ).join(''); - const tasksHTML = tasks.map(task => `
  • ${task}
  • `).join(''); + const tasksHTML = tasks.map(task => `
  • ${loc(task)}
  • `).join(''); const isPhoneValid = isValidWhatsAppNumber(phone); const encodedPhone = isPhoneValid ? encodePhone(phone) : ''; const displayPhone = isPhoneValid ? formatPhoneForDisplay(phone) : '-'; @@ -123,13 +137,13 @@ export function renderStaffCards(staffs) { ${name}
    -
    ${name}
    -
    ${role}
    +
    ${loc(name)}
    +
    ${loc(role)}
    -
    📍
    ${t('grp_c4a_label_location', 'Lokasi')}${location}
    +
    📍
    ${t('grp_c4a_label_location', 'Lokasi')}${loc(location)}
    🕒
    @@ -202,27 +216,29 @@ function buildSopCard(sop) { const catLabel = getCatLabel(category); const catClass = CATEGORY_COLORS[category] || ''; + const titleText = loc(title); + const descText = loc(description); return ` -
    +
    -

    ${title}

    +

    ${titleText}

    ${catLabel}
    -

    ${description}

    +

    ${descText}

    @@ -250,13 +266,18 @@ function buildSopModalContent(sop) { const catLabel = getCatLabel(category); const catClass = CATEGORY_COLORS[category] || ''; - const reqsHTML = requirements.map(req => `
  • ${req}
  • `).join(''); - const procsHTML = procedures.map(proc => `
  • ${proc}
  • `).join(''); + const titleText = loc(title); + const descText = loc(description); + const purposeText = loc(purpose); + const additionalInfoText = loc(additionalInfo); + + const reqsHTML = requirements.map(req => `
  • ${loc(req)}
  • `).join(''); + const procsHTML = procedures.map(proc => `
  • ${loc(proc)}
  • `).join(''); // staffResponsible bersifat opsional — sembunyikan blok jika tidak ada const hasStaff = Array.isArray(staffResponsible) && staffResponsible.length > 0; const staffHTML = hasStaff - ? staffResponsible.map(staffName => `${staffName}`).join('') + ? staffResponsible.map(staffName => `${loc(staffName)}`).join('') : ''; const staffBlockHTML = hasStaff ? `
    @@ -266,10 +287,10 @@ function buildSopModalContent(sop) { : ''; // additionalInfo bersifat opsional — sembunyikan blok jika tidak ada - const additionalInfoHTML = additionalInfo + const additionalInfoHTML = additionalInfoText ? `
    â„šī¸ -

    ${t('grp_c4a_label_additional_info', 'Informasi Tambahan (Bersifat Opsional):')} ${additionalInfo}

    +

    ${t('grp_c4a_label_additional_info', 'Informasi Tambahan (Bersifat Opsional):')} ${additionalInfoText}

    ` : ''; @@ -283,15 +304,15 @@ function buildSopModalContent(sop) {
    ${catLabel}
    -

    ${title}

    -

    ${description}

    +

    ${titleText}

    +

    ${descText}

    ${t('grp_c4a_label_purpose', 'Tujuan')}
    -

    ${purpose}

    +

    ${purposeText}

    @@ -319,15 +340,19 @@ export function renderSopCards(sops) { // simpan data global di element agar bisa diakses filter/search container._sopData = sops; + const emptyState = document.getElementById('sop-empty-state'); + // Tampilkan pesan jika data SOP kosong atau gagal dimuat if (!sops || sops.length === 0) { container.innerHTML = `

    ${t('grp_c4a_empty_sop', 'Data SOP tidak tersedia saat ini.')}

    `; updateResultInfo(0, 0); + if (emptyState) emptyState.style.display = 'none'; return; } container.innerHTML = sops.map(buildSopCard).join(''); updateResultInfo(sops.length, sops.length); + if (emptyState) emptyState.style.display = 'none'; } /** Update teks info jumlah hasil */ @@ -371,7 +396,12 @@ export function applySopFilter() { }); const emptyState = document.getElementById('sop-empty-state'); - if (emptyState) emptyState.style.display = visibleCount === 0 ? 'flex' : 'none'; + if (emptyState) { + // Tampilkan "SOP tidak ditemukan" HANYA jika memang ada data SOP tetapi hasil filter 0. + // Jika data SOP memang kosong (tidak ada card sama sekali di data source), sembunyikan. + const hasData = container._sopData && container._sopData.length > 0; + emptyState.style.display = (hasData && visibleCount === 0) ? 'flex' : 'none'; + } updateResultInfo(visibleCount, cards.length); } @@ -626,11 +656,11 @@ export function renderFaqSection(faqs) { return `
    - ${faq.question} + ${loc(faq.question)}
    +
    -
    ${faq.answer}
    +
    ${loc(faq.answer)}
    `; }).join('');