From 946497214b178b920ce06eb39131d3f7f149fec3 Mon Sep 17 00:00:00 2001 From: cygouw Date: Fri, 5 Jun 2026 14:18:12 +0700 Subject: [PATCH 1/2] 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." + } } ``` -- 2.52.0 From 24e1d6ec1744a46cad2a41e5b268bbaae15e994b Mon Sep 17 00:00:00 2001 From: cygouw Date: Fri, 5 Jun 2026 14:50:20 +0700 Subject: [PATCH 2/2] 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(''); -- 2.52.0