feat(ui): add language switcher for Indonesian and English

This commit is contained in:
cygouw
2026-06-04 23:31:09 +07:00
parent 39adcc21ee
commit 2edbcb0c88
5 changed files with 159 additions and 45 deletions
+80
View File
@@ -569,6 +569,46 @@ const translations = {
kontrib_guideline_3: "Sertakan fakta dan data yang akurat dan dapat diverifikasi.", kontrib_guideline_3: "Sertakan fakta dan data yang akurat dan dapat diverifikasi.",
kontrib_guideline_4: "Hindari plagiarisme. Karya harus orisinal.", kontrib_guideline_4: "Hindari plagiarisme. Karya harus orisinal.",
kontrib_guideline_5: "Tim redaksi berhak menyunting konten untuk keperluan editorial.", 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 &amp; <br/><span class=\"highlight-orange\">Standar Operasional Prosedur (SOP)</span>",
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: { en: {
@@ -1135,6 +1175,46 @@ const translations = {
kontrib_guideline_3: "Include accurate and verifiable facts and data.", kontrib_guideline_3: "Include accurate and verifiable facts and data.",
kontrib_guideline_4: "Avoid plagiarism. Work must be original.", kontrib_guideline_4: "Avoid plagiarism. Work must be original.",
kontrib_guideline_5: "The editorial team reserves the right to edit content for editorial purposes.", 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 &amp; <br/><span class=\"highlight-orange\">Standard Operating Procedures (SOP)</span>",
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",
} }
}; };
+22 -20
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="id" class="scroll-smooth"> <html lang="id" class="scroll-smooth">
<head> <head>
@@ -1159,13 +1159,13 @@
<!-- HERO SECTION --> <!-- HERO SECTION -->
<section class="c4a-hero reveal"> <section class="c4a-hero reveal">
<div class="c4a-hero-inner"> <div class="c4a-hero-inner">
<div class="c4a-hero-tag">LAYANAN AKADEMIK &bull; INFORMATIKA UNTAN <div class="c4a-hero-tag" data-i18n="grp_c4a_hero_tag">LAYANAN AKADEMIK &bull; INFORMATIKA UNTAN
</div> </div>
<h1 class="c4a-hero-title"> <h1 class="c4a-hero-title" data-i18n-html="grp_c4a_hero_title">
Staf Pendukung &amp; <br/> Staf Pendukung &amp; <br/>
<span class="highlight-orange">Standar Operasional Prosedur (SOP)</span> <span class="highlight-orange">Standar Operasional Prosedur (SOP)</span>
</h1> </h1>
<p class="c4a-hero-desc">Halaman ini menampilkan informasi petugas <p class="c4a-hero-desc" data-i18n="grp_c4a_hero_desc">Halaman ini menampilkan informasi petugas
layanan akademik serta layanan akademik serta
prosedur administrasi prosedur administrasi
mahasiswa mahasiswa
@@ -1173,12 +1173,12 @@
<div class="hero-stats"> <div class="hero-stats">
<div class="stat-item"> <div class="stat-item">
<div class="stat-number" id="stat-staf">0</div> <div class="stat-number" id="stat-staf">0</div>
<div class="stat-label">Staf Aktif</div> <div class="stat-label" data-i18n="grp_c4a_stat_staf">Staf Aktif</div>
</div> </div>
<div class="stat-divider"></div> <div class="stat-divider"></div>
<div class="stat-item"> <div class="stat-item">
<div class="stat-number" id="stat-sop">0</div> <div class="stat-number" id="stat-sop">0</div>
<div class="stat-label">Jenis SOP</div> <div class="stat-label" data-i18n="grp_c4a_stat_sop">Jenis SOP</div>
</div> </div>
</div> </div>
</div> </div>
@@ -1187,10 +1187,10 @@
<!-- STAFF SECTION --> <!-- STAFF SECTION -->
<section class="content-section"> <section class="content-section">
<div class="section-header"> <div class="section-header">
<div class="section-tag"><span class="dot"></span> <span>Tim <div class="section-tag"><span class="dot"></span> <span data-i18n="grp_c4a_staff_tag">Tim
Kami</span></div> Kami</span></div>
<h2 class="section-title">Staf Pendukung</h2> <h2 class="section-title" data-i18n="grp_c4a_staff_title">Staf Pendukung</h2>
<p class="section-subtitle">Staf yang siap melayani kebutuhan <p class="section-subtitle" data-i18n="grp_c4a_staff_subtitle">Staf yang siap melayani kebutuhan
akademik dan operasional akademik dan operasional
laboratorium di Prodi Informatika UNTAN.</p> laboratorium di Prodi Informatika UNTAN.</p>
</div> </div>
@@ -1202,10 +1202,10 @@
<!-- SOP SECTION --> <!-- SOP SECTION -->
<section class="sop-section"> <section class="sop-section">
<div class="section-header"> <div class="section-header">
<div class="section-tag"><span class="dot"></span> <span>Prosedur <div class="section-tag"><span class="dot"></span> <span data-i18n="grp_c4a_sop_tag">Prosedur
Resmi</span></div> Resmi</span></div>
<h2 class="section-title">Standar Operasional Prosedur (SOP)</h2> <h2 class="section-title" data-i18n="grp_c4a_sop_title">Standar Operasional Prosedur (SOP)</h2>
<p class="section-subtitle">Standar Operasional Prosedur untuk <p class="section-subtitle" data-i18n="grp_c4a_sop_subtitle">Standar Operasional Prosedur untuk
berbagai kebutuhan administrasi berbagai kebutuhan administrasi
akademik mahasiswa. Gunakan pencarian atau filter untuk menemukan SOP yang Anda butuhkan. akademik mahasiswa. Gunakan pencarian atau filter untuk menemukan SOP yang Anda butuhkan.
</p> </p>
@@ -1216,6 +1216,7 @@
<div class="sop-search-wrap"> <div class="sop-search-wrap">
<i class="fas fa-search sop-search-icon"></i> <i class="fas fa-search sop-search-icon"></i>
<input type="search" id="sop-search-input" class="sop-search-input" <input type="search" id="sop-search-input" class="sop-search-input"
data-i18n-placeholder="grp_c4a_sop_search_placeholder"
placeholder="Cari SOP berdasarkan judul..." autocomplete="off" aria-label="Cari SOP" /> placeholder="Cari SOP berdasarkan judul..." autocomplete="off" aria-label="Cari SOP" />
<button class="sop-search-clear" id="sop-search-clear" aria-label="Hapus pencarian" <button class="sop-search-clear" id="sop-search-clear" aria-label="Hapus pencarian"
style="display:none;"> style="display:none;">
@@ -1224,12 +1225,12 @@
</div> </div>
<div class="sop-filter-chips" id="sop-filter-chips" role="group" <div class="sop-filter-chips" id="sop-filter-chips" role="group"
aria-label="Filter kategori SOP"> aria-label="Filter kategori SOP">
<button class="sop-chip active" data-filter="semua" id="chip-semua">Semua</button> <button class="sop-chip active" data-filter="semua" id="chip-semua" data-i18n="grp_c4a_sop_chip_semua">Semua</button>
<button class="sop-chip" data-filter="akademik" id="chip-akademik"> <button class="sop-chip" data-filter="akademik" id="chip-akademik">
<i class="fas fa-graduation-cap"></i> <span>Akademik</span> <i class="fas fa-graduation-cap"></i> <span data-i18n="grp_c4a_sop_chip_akademik">Akademik</span>
</button> </button>
<button class="sop-chip" data-filter="praktikum" id="chip-praktikum"> <button class="sop-chip" data-filter="praktikum" id="chip-praktikum">
<i class="fas fa-flask"></i> <span>Praktikum</span> <i class="fas fa-flask"></i> <span data-i18n="grp_c4a_sop_chip_praktikum">Praktikum</span>
</button> </button>
</div> </div>
</div> </div>
@@ -1243,8 +1244,8 @@
<!-- EMPTY STATE --> <!-- EMPTY STATE -->
<div class="sop-empty-state" id="sop-empty-state" style="display:none;"> <div class="sop-empty-state" id="sop-empty-state" style="display:none;">
<i class="fas fa-search-minus sop-empty-icon"></i> <i class="fas fa-search-minus sop-empty-icon"></i>
<p class="sop-empty-title">SOP tidak ditemukan</p> <p class="sop-empty-title" data-i18n="grp_c4a_sop_empty_title">SOP tidak ditemukan</p>
<p class="sop-empty-desc">Coba kata kunci lain atau reset <p class="sop-empty-desc" data-i18n="grp_c4a_sop_empty_desc">Coba kata kunci lain atau reset
filter pencarian.</p> filter pencarian.</p>
<button class="sop-empty-reset" id="sop-empty-reset"> <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> <i class="fas fa-undo"></i> <span data-i18n="grp_c4a_sop_reset_btn">Reset Pencarian</span>
@@ -1267,12 +1268,13 @@
<section class="faq-section-wrap"> <section class="faq-section-wrap">
<div class="faq-section-inner"> <div class="faq-section-inner">
<div class="faq-header reveal"> <div class="faq-header reveal">
<div class="section-tag"><span class="dot"></span> <span>Pertanyaan Umum</span></div> <div class="section-tag"><span class="dot"></span> <span data-i18n="grp_c4a_faq_tag">Pertanyaan Umum</span></div>
<h2>Pertanyaan yang Sering Diajukan</h2> <h2 data-i18n="grp_c4a_faq_title">Pertanyaan yang Sering Diajukan</h2>
<p>Temukan jawaban cepat atas pertanyaan umum mahasiswa <p data-i18n="grp_c4a_faq_subtitle">Temukan jawaban cepat atas pertanyaan umum mahasiswa
seputar layanan akademik prodi.</p> seputar layanan akademik prodi.</p>
</div> </div>
<div class="faq-wrap" id="faq-container"> <div class="faq-wrap" id="faq-container">
<!-- Item FAQ diisi secara dinamis melalui main.js berdasarkan data-faq.json --> <!-- Item FAQ diisi secara dinamis melalui main.js berdasarkan data-faq.json -->
</div> </div>
+2
View File
@@ -52,3 +52,5 @@ export async function getFaqData() {
return []; return [];
} }
} }
+14 -4
View File
@@ -10,12 +10,14 @@ import {
renderFaqSection, renderFaqSection,
initSopInteraction, initSopInteraction,
initAnimations, initAnimations,
initFaq initFaq,
applySopFilter
} from './ui.js'; } from './ui.js';
// Cache data secara global untuk mendukung rendering UI // Cache data secara global untuk mendukung rendering UI
let cachedStaffData = []; let cachedStaffData = [];
let cachedSopData = []; let cachedSopData = [];
let cachedFaqData = [];
/** /**
* Menginisialisasi aplikasi dengan mengambil seluruh data secara pararel, * Menginisialisasi aplikasi dengan mengambil seluruh data secara pararel,
@@ -32,10 +34,11 @@ async function initApp() {
cachedStaffData = staffData || []; cachedStaffData = staffData || [];
cachedSopData = sopData || []; cachedSopData = sopData || [];
cachedFaqData = faqData || [];
renderStaffCards(cachedStaffData); renderStaffCards(cachedStaffData);
renderSopCards(cachedSopData); renderSopCards(cachedSopData);
renderFaqSection(faqData || []); renderFaqSection(cachedFaqData);
initSopInteraction(); initSopInteraction();
initFaq(); initFaq();
@@ -49,8 +52,15 @@ async function initApp() {
} }
} }
// NOTE: Fitur bilingual sudah dihapus - languageChanged listener tidak lagi diperlukan // Re-render UI saat bahasa berubah (data tetap sama, hanya label UI yang berganti via t())
// Silakan hapus atau archive jika ada implementasi global untuk bahasa document.addEventListener('languageChanged', () => {
renderStaffCards(cachedStaffData);
renderSopCards(cachedSopData);
renderFaqSection(cachedFaqData);
initFaq();
applySopFilter();
});
// Jalankan inisialisasi aplikasi saat DOM siap // Jalankan inisialisasi aplikasi saat DOM siap
document.addEventListener('DOMContentLoaded', initApp); document.addEventListener('DOMContentLoaded', initApp);
+41 -21
View File
@@ -1,5 +1,17 @@
// --- Helper Fungsi --- // --- Helper Fungsi ---
/**
* Helper untuk menerjemahkan string berdasarkan key dari localization.js.
* Menggunakan fallback jika key tidak ditemukan atau localization.js tidak aktif.
*/
function t(key, fallback) {
if (window.t) {
const val = window.t(key);
if (val !== key) return val;
}
return fallback;
}
/** /**
* Normalisasi nomor telepon ke format standard e.g. 628... * Normalisasi nomor telepon ke format standard e.g. 628...
* @param {string} phone - Nomor telepon mentah * @param {string} phone - Nomor telepon mentah
@@ -70,7 +82,7 @@ export function renderStaffCards(staffs) {
container.innerHTML = ''; container.innerHTML = '';
if (!staffs || staffs.length === 0) { if (!staffs || staffs.length === 0) {
container.innerHTML = `<p class="empty-state-msg">Data staf pendukung tidak tersedia saat ini.</p>`; container.innerHTML = `<p class="empty-state-msg">${t('grp_c4a_empty_staff', 'Data staf pendukung tidak tersedia saat ini.')}</p>`;
return; return;
} }
@@ -117,23 +129,24 @@ export function renderStaffCards(staffs) {
</div> </div>
<div class="staff-card-body"> <div class="staff-card-body">
<div class="staff-info-grid"> <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><span class="label">${t('grp_c4a_label_location', 'Lokasi')}</span><span class="val">${location}</span></div></div>
<div class="staff-info-item"> <div class="staff-info-item">
<span class="icon">🕒</span> <span class="icon">🕒</span>
<div style="flex:1"> <div style="flex:1">
<span class="label">Jam Layanan</span> <span class="label">${t('grp_c4a_label_service_hours', 'Jam Layanan')}</span>
<div class="schedule-list">${scheduleHTML}</div> <div class="schedule-list">${scheduleHTML}</div>
</div> </div>
</div> </div>
<div class="staff-info-item staff-contact-item ${hideContactItemClass}"><span class="icon">📞</span><div><span class="label">Kontak WhatsApp</span><span class="val">${displayPhone}</span></div></div> <div class="staff-info-item staff-contact-item ${hideContactItemClass}"><span class="icon">📞</span><div><span class="label">${t('grp_c4a_label_wa_contact', 'Kontak WhatsApp')}</span><span class="val">${displayPhone}</span></div></div>
</div> </div>
<div class="staff-divider"></div> <div class="staff-divider"></div>
<div class="staff-tasks-title">Tugas & Tanggung Jawab</div> <div class="staff-tasks-title">${t('grp_c4a_label_tasks', 'Tugas & Tanggung Jawab')}</div>
<ul class="staff-tasks">${tasksHTML}</ul> <ul class="staff-tasks">${tasksHTML}</ul>
</div> </div>
<div class="staff-card-actions staff-contact-actions ${hideContactActionClass}"> <div class="staff-card-actions staff-contact-actions ${hideContactActionClass}">
<button class="btn-staff btn-staff-wa" data-wa="${encodedPhone}" aria-label="Kirim Pesan WA ${name}"><svg class="wa-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg> Kirim Pesan WA</button> <button class="btn-staff btn-staff-wa" data-wa="${encodedPhone}" aria-label="${t('grp_c4a_label_send_wa', 'Kirim Pesan WA')} ${name}"><svg class="wa-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg> ${t('grp_c4a_label_send_wa', 'Kirim Pesan WA')}</button>
</div> </div>
`; `;
container.appendChild(card); container.appendChild(card);
@@ -165,8 +178,11 @@ const CATEGORY_COLORS = {
* @returns {string} Label kategori * @returns {string} Label kategori
*/ */
function getCatLabel(category) { function getCatLabel(category) {
const map = { akademik: 'Akademik', praktikum: 'Praktikum' }; const map = {
return map[category] || 'Akademik'; 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) {
<div class="sgc-footer"> <div class="sgc-footer">
<div class="sgc-steps"> <div class="sgc-steps">
<i class="fas fa-list-ol"></i> <i class="fas fa-list-ol"></i>
<span>${procedures.length} Langkah</span> <span>${procedures.length} ${t('grp_c4a_label_steps', 'Langkah')}</span>
</div> </div>
<button class="sgc-detail-btn" data-tab-id="${tabId}" aria-label="Lihat Detail ${title}"> <button class="sgc-detail-btn" data-tab-id="${tabId}" aria-label="${t('grp_c4a_label_view_detail', 'Lihat Detail')} ${title}">
Lihat Detail <i class="fas fa-arrow-right"></i> ${t('grp_c4a_label_view_detail', 'Lihat Detail')} <i class="fas fa-arrow-right"></i>
</button> </button>
</div> </div>
</article>`; </article>`;
@@ -244,7 +260,7 @@ function buildSopModalContent(sop) {
: ''; : '';
const staffBlockHTML = hasStaff const staffBlockHTML = hasStaff
? `<div class="sop-staff-block"> ? `<div class="sop-staff-block">
<div class="sop-block-label">Staf Bertanggung Jawab (Bersifat Opsional)</div> <div class="sop-block-label">${t('grp_c4a_label_responsible_staff', 'Staf Bertanggung Jawab (Bersifat Opsional)')}</div>
<div class="sop-staff-pills">${staffHTML}</div> <div class="sop-staff-pills">${staffHTML}</div>
</div>` </div>`
: ''; : '';
@@ -253,7 +269,7 @@ function buildSopModalContent(sop) {
const additionalInfoHTML = additionalInfo const additionalInfoHTML = additionalInfo
? `<div class="sop-info-box"> ? `<div class="sop-info-box">
<span class="iico">️</span> <span class="iico">️</span>
<p class="sop-info-box-text"><strong>Informasi Tambahan (Bersifat Opsional):</strong> ${additionalInfo}</p> <p class="sop-info-box-text"><strong>${t('grp_c4a_label_additional_info', 'Informasi Tambahan (Bersifat Opsional):')}</strong> ${additionalInfo}</p>
</div>` </div>`
: ''; : '';
@@ -274,16 +290,16 @@ function buildSopModalContent(sop) {
</div> </div>
<div class="sop-modal-body"> <div class="sop-modal-body">
<div class="sop-block"> <div class="sop-block">
<div class="sop-block-label">Tujuan</div> <div class="sop-block-label">${t('grp_c4a_label_purpose', 'Tujuan')}</div>
<p class="sop-block-text">${purpose}</p> <p class="sop-block-text">${purpose}</p>
</div> </div>
<div class="sop-two-col"> <div class="sop-two-col">
<div> <div>
<div class="sop-block-label">Persyaratan</div> <div class="sop-block-label">${t('grp_c4a_label_requirements', 'Persyaratan')}</div>
<ul class="sop-list bullets">${reqsHTML}</ul> <ul class="sop-list bullets">${reqsHTML}</ul>
</div> </div>
<div> <div>
<div class="sop-block-label">Alur Prosedur</div> <div class="sop-block-label">${t('grp_c4a_label_procedure', 'Alur Prosedur')}</div>
<ul class="sop-list numbered">${procsHTML}</ul> <ul class="sop-list numbered">${procsHTML}</ul>
</div> </div>
</div> </div>
@@ -305,7 +321,7 @@ export function renderSopCards(sops) {
// Tampilkan pesan jika data SOP kosong atau gagal dimuat // Tampilkan pesan jika data SOP kosong atau gagal dimuat
if (!sops || sops.length === 0) { if (!sops || sops.length === 0) {
container.innerHTML = `<p class="empty-state-msg">Data SOP tidak tersedia saat ini.</p>`; container.innerHTML = `<p class="empty-state-msg">${t('grp_c4a_empty_sop', 'Data SOP tidak tersedia saat ini.')}</p>`;
updateResultInfo(0, 0); updateResultInfo(0, 0);
return; return;
} }
@@ -319,16 +335,18 @@ function updateResultInfo(shown, total) {
const el = document.getElementById('sop-result-info'); const el = document.getElementById('sop-result-info');
if (!el) return; if (!el) return;
if (shown === total) { 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 { } 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. * Memfilter dan menyaring kartu SOP berdasarkan query pencarian dan chip filter aktif.
*/ */
function applySopFilter() { export function applySopFilter() {
const container = document.getElementById('sop-cards-container'); const container = document.getElementById('sop-cards-container');
if (!container) return; if (!container) return;
@@ -358,6 +376,7 @@ function applySopFilter() {
updateResultInfo(visibleCount, cards.length); updateResultInfo(visibleCount, cards.length);
} }
/** /**
* Membuka modal detail untuk SOP tertentu berdasarkan tab_id. * Membuka modal detail untuk SOP tertentu berdasarkan tab_id.
* @param {string} tabId - ID unik tab/SOP yang ingin ditampilkan * @param {string} tabId - ID unik tab/SOP yang ingin ditampilkan
@@ -595,10 +614,11 @@ export function renderFaqSection(faqs) {
if (!container) return; if (!container) return;
if (!faqs || faqs.length === 0) { if (!faqs || faqs.length === 0) {
container.innerHTML = `<p class="empty-state-msg">Data FAQ tidak tersedia saat ini.</p>`; container.innerHTML = `<p class="empty-state-msg">${t('grp_c4a_empty_faq', 'Data FAQ tidak tersedia saat ini.')}</p>`;
return; return;
} }
const delayClasses = ['reveal-delay-1', 'reveal-delay-2', 'reveal-delay-3']; const delayClasses = ['reveal-delay-1', 'reveal-delay-2', 'reveal-delay-3'];
const itemsHTML = faqs.map((faq, index) => { const itemsHTML = faqs.map((faq, index) => {