// ================================================================= // SCRIPT UTAMA SISTEM ABSENSI DENGAN FIREBASE // ================================================================= import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js"; import { getFirestore, collection, onSnapshot, doc, getDoc, setDoc, deleteDoc, query, where, getDocs, writeBatch, serverTimestamp, addDoc } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js"; import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js"; // --- Inisialisasi Firebase & Variabel Global --- const appId = typeof __app_id !== 'undefined' ? __app_id : 'siap-hadir-smpn3'; const firebaseConfig = { apiKey: "AIzaSyCERObGtCsYc_8kxSKm1bdeNHgkGgEZ5tY", authDomain: "absensi-smpn3-lempuingja-74934.firebaseapp.com", projectId: "absensi-smpn3-lempuingja-74934", storageBucket: "absensi-smpn3-lempuingja-74934.firebasestorage.app", messagingSenderId: "345526404692", appId: "1:345526404692:web:30643884548cecf7604fc1" }; const app = initializeApp(firebaseConfig); const db = getFirestore(app); const auth = getAuth(app); // --- Referensi Koleksi Firestore --- const personilCol = collection(db, `artifacts/${appId}/public/data/personil`); const kelasCol = collection(db, `artifacts/${appId}/public/data/kelas`); const siswaCol = collection(db, `artifacts/${appId}/public/data/siswa`); const absensiPersonalCol = collection(db, `artifacts/${appId}/public/data/absensiPersonal`); const absensiSiswaCol = collection(db, `artifacts/${appId}/public/data/absensiSiswa`); const pengaturanCol = collection(db, `artifacts/${appId}/public/data/pengaturan`); const hariLiburCol = collection(db, `artifacts/${appId}/public/data/hariLibur`); const pengumumanCol = collection(db, `artifacts/${appId}/public/data/pengumuman`); // --- State Lokal --- let semuaPersonil = []; let semuaKelas = []; let semuaSiswa = []; let semuaHariLibur = []; let semuaPengumuman = []; let pengaturanSistem = {}; let currentUserData = null; let removeListeners = []; // --- Event Listener Utama --- document.addEventListener('DOMContentLoaded', () => { // Logika Toggle Sidebar const sidebar = document.getElementById('sidebar'); const sidebarToggle = document.getElementById('sidebarToggle'); const sidebarOverlay = document.getElementById('sidebarOverlay'); const toggleSidebar = (closeOnly = false) => { if (closeOnly || !sidebar.classList.contains('-translate-x-full')) { sidebar.classList.add('-translate-x-full'); sidebarOverlay.classList.add('hidden'); } else { sidebar.classList.remove('-translate-x-full'); sidebarOverlay.classList.remove('hidden'); } }; sidebarToggle.addEventListener('click', () => toggleSidebar()); sidebarOverlay.addEventListener('click', () => toggleSidebar(true)); // Update Jam & Tanggal updateDateTime(); setInterval(updateDateTime, 1000); // Event Listener Form & Tombol Global document.getElementById('loginForm').addEventListener('submit', handleLogin); document.getElementById('logoutButton').addEventListener('click', handleLogout); document.getElementById('closeModalBtn').addEventListener('click', closeModal); // Inisialisasi Sistem Routing setupRouting(); // Cek Sesi Login if (sessionStorage.getItem('loggedInUserId')) { document.getElementById('loginPage').style.display = 'none'; document.getElementById('mainApp').classList.remove('hidden'); } }); // ================================================================= // BAGIAN ROUTING & PEMUATAN HALAMAN DINAMIS // ================================================================= const mainContent = document.getElementById('main-content'); async function loadPage(pageName) { try { mainContent.innerHTML = `
`; const response = await fetch(`pages/${pageName}.html`); if (!response.ok) throw new Error(`Halaman ${pageName}.html tidak ditemukan!`); mainContent.innerHTML = await response.text(); runPageInitializer(pageName); } catch (error) { mainContent.innerHTML = `
Gagal memuat halaman: ${error.message}
`; console.error("Error loading page:", error); } } function runPageInitializer(pageName) { switch (pageName) { case 'home': updateDashboardStats(); updateMarquee(); break; case 'absensi_personal': if(currentUserData) document.getElementById('personalAbsenName').innerText = currentUserData.name; setAbsenInputsToNow('inputTanggalAbsen', 'inputWaktuAbsen'); updatePersonalAbsenStatus(document.getElementById('inputTanggalAbsen').value); toggleKeterangan(); document.getElementById('formAbsenPersonal').addEventListener('submit', simpanAbsensiPersonal); document.getElementById('formAbsenPulang').addEventListener('submit', handleAbsenPulang); document.getElementById('inputStatusAbsen').addEventListener('change', toggleKeterangan); document.getElementById('inputTanggalAbsen').addEventListener('change', (e) => updatePersonalAbsenStatus(e.target.value)); break; case 'absensi_siswa': setAbsenInputsToNow('inputTanggalAbsenSiswa'); updateAllKelasDropdowns(); updateGuruPiketDropdown(); document.getElementById('pilihKelas').addEventListener('change', loadSiswaForAbsen); document.getElementById('btnSimpanAbsensiSiswa').addEventListener('click', simpanAbsensiSiswa); break; case 'lihat_absensi': setAbsenInputsToNow('inputTanggalLihatAbsensi'); updateAllKelasDropdowns(); renderAbsensiHarian(); setupTabListeners('guru-tab', 'siswa-tab', 'guru', 'siswa'); document.getElementById('pilihKelasLihat').addEventListener('change', renderAbsensiHarianSiswa); document.getElementById('inputTanggalLihatAbsensi').addEventListener('change', renderAbsensiHarian); break; case 'rekap_absensi': populateBulanDropdown(); updateAllKelasDropdowns(); renderRekapSiswa(); renderRekapGuruPegawai(); setupRekapTabs(); document.getElementById('pilihBulanRekapSiswa').onchange = renderRekapSiswa; document.getElementById('pilihTahunRekapSiswa').onchange = renderRekapSiswa; document.getElementById('pilihKelasRekap').onchange = renderRekapSiswa; document.getElementById('pilihBulanRekapGuru').onchange = renderRekapGuruPegawai; document.getElementById('pilihTahunRekapGuru').onchange = renderRekapGuruPegawai; document.getElementById('btnPrintRekap').addEventListener('click', printRekap); document.getElementById('btnUnduhExcel').addEventListener('click', unduhExcel); break; case 'admin': renderManajemenPersonil(); renderManajemenSiswa(); renderManajemenKelas(); renderManajemenHariLibur(); renderManajemenPengumuman(); applyPengaturan(pengaturanSistem); updateAllKelasDropdowns(); setupAdminTabs(); document.getElementById('formPengaturan').addEventListener('submit', simpanPengaturan); document.getElementById('formHariLibur').addEventListener('submit', simpanHariLibur); document.getElementById('formPengumuman').addEventListener('submit', simpanPengumuman); document.getElementById('logoSekolah').addEventListener('change', (e) => { const file = e.target.files[0]; if (file) document.getElementById('logoPreview').src = URL.createObjectURL(file); }); document.getElementById('waAuthType').addEventListener('change', (e) => { document.getElementById('customAuthHeaderContainer').style.display = e.target.value === 'customHeader' ? 'block' : 'none'; }); document.getElementById('addPersonilBtn').addEventListener('click', () => showPersonilForm()); document.getElementById('addSiswaBtn').addEventListener('click', () => showSiswaForm(null, document.getElementById('filterKelasSiswa').value)); document.getElementById('addKelasBtn').addEventListener('click', () => showKelasForm()); document.getElementById('filterKelasSiswa').addEventListener('change', renderManajemenSiswa); break; } } function setupRouting() { document.querySelectorAll('.nav-link, .nav-link-card').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const pageName = link.getAttribute('href').substring(1); window.location.hash = pageName; }); }); window.addEventListener('hashchange', navigate); navigate(); } function navigate() { let page = window.location.hash.substring(1) || 'home'; loadPage(page); document.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('bg-blue-700'); if(link.getAttribute('href') === `#${page}`) { link.classList.add('bg-blue-700'); } }); if (window.innerWidth < 1024) { document.getElementById('sidebar').classList.add('-translate-x-full'); document.getElementById('sidebarOverlay').classList.add('hidden'); } } // ================================================================= // BAGIAN FIREBASE & DATA LISTENER // ================================================================= onAuthStateChanged(auth, user => { if (user) { initDataListeners(); } else { stopDataListeners(); const token = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null; if (token) { signInWithCustomToken(auth, token).catch(err => console.error("Token sign-in failed:", err)); } else { signInAnonymously(auth).catch(err => console.error("Anonymous sign-in failed:", err)); } } }); function initDataListeners() { stopDataListeners(); const unSubPersonil = onSnapshot(personilCol, (snapshot) => { semuaPersonil = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); const wasUserDataNull = !currentUserData; if (!currentUserData) { const loggedInUserId = sessionStorage.getItem('loggedInUserId'); if (loggedInUserId) { const user = semuaPersonil.find(p => p.id === loggedInUserId); if (user) { currentUserData = user; } else { handleLogout(); return; } } } document.getElementById('appLoader')?.classList.add('hidden'); if (currentUserData) { currentUserData = semuaPersonil.find(p => p.id === currentUserData.id) || currentUserData; } if (wasUserDataNull && currentUserData) { setupDashboard(); const currentPage = window.location.hash.substring(1) || 'home'; loadPage(currentPage); } else { const currentPage = window.location.hash.substring(1); if (currentPage === 'admin') renderManajemenPersonil(); if (currentPage === 'home') updateDashboardStats(); } }); const unSubKelas = onSnapshot(kelasCol, (snapshot) => { semuaKelas = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); const currentPage = window.location.hash.substring(1); if (['absensi_siswa', 'lihat_absensi', 'rekap_absensi', 'admin'].includes(currentPage)) { updateAllKelasDropdowns(); if (currentPage === 'admin') renderManajemenKelas(); } }); const unSubSiswa = onSnapshot(siswaCol, (snapshot) => { semuaSiswa = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); const currentPage = window.location.hash.substring(1); if (currentPage === 'admin') renderManajemenSiswa(); if (currentPage === 'home') updateDashboardStats(); }); const unSubPengaturan = onSnapshot(doc(pengaturanCol, "sekolah"), (doc) => { if(doc.exists()){ pengaturanSistem = doc.data(); applyPengaturan(pengaturanSistem); } }); const unSubHariLibur = onSnapshot(hariLiburCol, (snapshot) => { semuaHariLibur = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); const currentPage = window.location.hash.substring(1); if(currentPage === 'admin') renderManajemenHariLibur(); if(currentPage === 'rekap_absensi'){ renderRekapGuruPegawai(); renderRekapSiswa(); } }); const unSubPengumuman = onSnapshot(query(pengumumanCol), (snapshot) => { semuaPengumuman = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); if(window.location.hash.substring(1) === 'admin') renderManajemenPengumuman(); updateMarquee(); }); removeListeners.push(unSubPersonil, unSubKelas, unSubSiswa, unSubPengaturan, unSubHariLibur, unSubPengumuman); } function stopDataListeners() { removeListeners.forEach(unsubscribe => unsubscribe()); removeListeners = []; } // ================================================================= // BAGIAN OTENTIKASI & PENGATURAN UI UTAMA // ================================================================= async function handleLogin(e) { e.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; const errorDiv = document.getElementById('loginError'); errorDiv.classList.add('hidden'); try { const userDoc = await getDoc(doc(personilCol, username)); if (!userDoc.exists()) { errorDiv.classList.remove('hidden'); return; } const user = { id: userDoc.id, ...userDoc.data() }; const [year, month, day] = (user.tglLahir || '').split('-'); const formattedPassword = `${day}${month}${year}`; const passwordValid = (user.role === 'Admin' && user.password === password) || (password === formattedPassword); if (passwordValid) { currentUserData = user; sessionStorage.setItem('loggedInUserId', user.id); document.getElementById('loginPage').style.display = 'none'; document.getElementById('mainApp').classList.remove('hidden'); window.location.hash = '#home'; setupDashboard(); } else { errorDiv.classList.remove('hidden'); } } catch (error) { console.error("Login error:", error); showErrorToast("Terjadi kesalahan saat login."); } } function handleLogout() { sessionStorage.removeItem('loggedInUserId'); currentUserData = null; window.location.hash = ''; window.location.reload(); } function setupDashboard() { if (!currentUserData) return; document.getElementById('userName').innerText = currentUserData.name; document.getElementById('userRole').innerText = currentUserData.role; document.getElementById('welcomeName').innerText = currentUserData.name.split(',')[0]; document.getElementById('adminMenu').classList.toggle('hidden', currentUserData.role !== 'Admin'); updateDashboardStats(); } // ================================================================= // BAGIAN HALAMAN HOME // ================================================================= async function updateDashboardStats() { if (!document.getElementById('guruStatus')) return; // Pastikan elemen ada const today = getTodayDateString(); if (currentUserData) { const docSnap = await getDoc(doc(absensiPersonalCol, `${today}_${currentUserData.id}`)); const guruStatusEl = document.getElementById('guruStatus'); const guruTimeEl = document.getElementById('guruTime'); if (docSnap.exists()) { const absen = docSnap.data(); if (absen.status === 'Hadir') { guruStatusEl.innerText = absen.jamPulang ? 'Sudah Pulang' : 'Sudah Masuk'; guruTimeEl.innerText = absen.jamPulang ? `Pulang pukul ${absen.jamPulang} WIB` : `Masuk pukul ${absen.jamMasuk} WIB`; } else { guruStatusEl.innerText = 'Izin'; guruTimeEl.innerText = `Ket: ${absen.keterangan}`; } } else { guruStatusEl.innerText = 'Belum Absen'; guruTimeEl.innerText = 'Silakan lakukan absensi hari ini.'; } } const qSiswa = query(absensiSiswaCol, where("tanggal", "==", today)); const qStaf = query(absensiPersonalCol, where("tanggal", "==", today), where("status", "==", "Hadir")); const [siswaSnapshot, stafSnapshot] = await Promise.all([getDocs(qSiswa), getDocs(qStaf)]); let siswaHadir = 0; siswaSnapshot.forEach(d => { siswaHadir += d.data().records.filter(r => r.status === 'H').length; }); const stafHadir = stafSnapshot.size; const totalSiswa = semuaSiswa.length || 1; const totalStaf = semuaPersonil.filter(p => ['Guru', 'Admin', 'Kepala Sekolah', 'Wakil Kepala Sekolah', 'Pegawai'].includes(p.role)).length || 1; document.getElementById('siswaHadirPersen').innerText = `${((siswaHadir / totalSiswa) * 100).toFixed(0)}%`; document.getElementById('siswaHadirDetail').innerText = `${siswaHadir} dari ${semuaSiswa.length} siswa hadir`; document.getElementById('stafHadirPersen').innerText = `${((stafHadir / totalStaf) * 100).toFixed(0)}%`; document.getElementById('stafHadirDetail').innerText = `${stafHadir} dari ${totalStaf} staf hadir`; } function updateMarquee() { const marquee = document.getElementById('marqueeContent'); if (!marquee) return; if (semuaPengumuman.length > 0) { marquee.innerHTML = semuaPengumuman.map(p => `${p.judul}: ${p.isi}`).join(''); } else { marquee.innerHTML = `Selamat datang di Sistem Absensi SIAP Hadir.`; } } // ================================================================= // BAGIAN HALAMAN ABSENSI PERSONAL // ================================================================= async function simpanAbsensiPersonal(e) { e.preventDefault(); const date = document.getElementById('inputTanggalAbsen').value; const time = document.getElementById('inputWaktuAbsen').value; const status = document.getElementById('inputStatusAbsen').value; const keterangan = document.getElementById('inputKeteranganAbsen').value; if ((status === 'Izin' && !keterangan.trim()) || (status === 'Hadir' && !time)) { return showErrorToast("Harap lengkapi semua field yang diperlukan!"); } const absenData = { tanggal: date, personilId: currentUserData.id, nama: currentUserData.name, nip: currentUserData.nip, status: status, jamMasuk: status === 'Hadir' ? time : null, jamPulang: null, keterangan: status === 'Izin' ? keterangan : null, timestampMasuk: serverTimestamp() }; try { await setDoc(doc(absensiPersonalCol, `${date}_${currentUserData.id}`), absenData); showSuccessToast(`Kehadiran ${status} berhasil disimpan.`); updatePersonalAbsenStatus(date); updateDashboardStats(); } catch (error) { showErrorToast("Gagal menyimpan absensi."); console.error(error); } } async function handleAbsenPulang(e) { e.preventDefault(); const date = document.getElementById('inputTanggalPulang').value; const time = document.getElementById('inputWaktuPulang').value; try { await setDoc(doc(absensiPersonalCol, `${date}_${currentUserData.id}`), { jamPulang: time, timestampPulang: serverTimestamp() }, { merge: true }); showSuccessToast('Absen pulang berhasil dicatat!'); updatePersonalAbsenStatus(date); updateDashboardStats(); } catch(error) { showErrorToast("Gagal mencatat absen pulang."); console.error(error); } } async function updatePersonalAbsenStatus(date) { if (!currentUserData) return; // Failsafe guard clause const statusDiv = document.getElementById('personalAbsenStatus'); const formMasuk = document.getElementById('formAbsenPersonal'); const formPulang = document.getElementById('formAbsenPulang'); statusDiv.classList.add('hidden'); formMasuk.classList.add('hidden'); formPulang.classList.add('hidden'); if (!date) { formMasuk.classList.remove('hidden'); return; } const docSnap = await getDoc(doc(absensiPersonalCol, `${date}_${currentUserData.id}`)); if (!docSnap.exists()) { formMasuk.classList.remove('hidden'); } else { const absen = docSnap.data(); let statusText = `Status untuk tanggal ${new Date(date + 'T00:00:00').toLocaleDateString('id-ID', {day:'numeric', month:'long'})}: `; statusDiv.classList.remove('hidden', 'bg-blue-100', 'text-blue-800'); if (absen.status === 'Izin') { statusText += `Izin. Ket: ${absen.keterangan || '-'}.`; formMasuk.classList.remove('hidden'); } else if (absen.status === 'Hadir') { statusText += `Masuk pukul ${absen.jamMasuk}. `; if (absen.jamPulang) { statusText += `Pulang pukul ${absen.jamPulang}.`; formMasuk.classList.remove('hidden'); } else { formPulang.classList.remove('hidden'); document.getElementById('inputTanggalPulang').value = date; setAbsenInputsToNow(null, 'inputWaktuPulang'); } } statusDiv.innerHTML = statusText; } } function toggleKeterangan() { const status = document.getElementById('inputStatusAbsen').value; document.getElementById('keteranganContainer').classList.toggle('hidden', status !== 'Izin'); document.getElementById('inputWaktuAbsen').disabled = status === 'Izin'; } // ================================================================= // BAGIAN HALAMAN ABSENSI SISWA // ================================================================= async function simpanAbsensiSiswa() { const btn = document.getElementById('btnSimpanAbsensiSiswa'); const tanggal = document.getElementById('inputTanggalAbsenSiswa').value; const kelasId = document.getElementById('pilihKelas').value; if (!tanggal || !kelasId || kelasId === "-- Pilih Kelas --") return showErrorToast("Harap pilih tanggal dan kelas!"); btn.disabled = true; btn.innerHTML = ` Menyimpan...`; try { const records = semuaSiswa.filter(s => s.kelasId === kelasId).map(siswa => ({ nis: siswa.nis, nama: siswa.name, status: document.querySelector(`input[name="status-${siswa.nis}"]:checked`).value })); const kelasInfo = semuaKelas.find(k => k.id === kelasId); const absensiData = { tanggal, kelasId, kelasNama: kelasInfo?.name || '', guruPiketId: document.getElementById('pilihGuruPiket').value || null, catatanKhusus: document.getElementById('inputCatatanKhusus').value || null, records, timestamp: serverTimestamp() }; await setDoc(doc(absensiSiswaCol, `${tanggal}_${kelasId}`), absensiData); showSuccessToast(`Absensi kelas ${kelasInfo.name} berhasil disimpan!`); updateDashboardStats(); if (document.getElementById('kirimNotifikasiWA').checked) { btn.innerHTML = ` Mengirim Notifikasi...`; const { success, failed } = await kirimNotifikasiMassal(records, kelasInfo, tanggal); showSuccessToast(`Notifikasi WA: ${success} terkirim, ${failed} gagal.`); } } catch (error) { showErrorToast("Gagal menyimpan absensi siswa."); console.error(error); } finally { btn.disabled = false; btn.innerHTML = ` Simpan Absensi`; } } async function kirimNotifikasiMassal(records, kelasInfo, tanggal) { let successCount = 0, failedCount = 0; const statusLengkap = { 'H': 'Hadir', 'S': 'Sakit', 'I': 'Izin', 'A': 'Alfa' }; const promises = records.map(absen => { if(absen.status === 'H') return Promise.resolve({status: 'fulfilled'}); // Jangan kirim jika hadir const siswaData = semuaSiswa.find(s => s.nis === absen.nis); if (siswaData && siswaData.noHp) { const template = pengaturanSistem.waPesanTemplate; const pesan = template .replace(/\[nama_siswa\]/g, siswaData.name).replace(/\[nama_ortu\]/g, siswaData.namaOrtu || 'Wali Murid') .replace(/\[kelas\]/g, kelasInfo.name).replace(/\[tanggal\]/g, new Date(tanggal + 'T00:00:00').toLocaleDateString('id-ID')) .replace(/\[status\]/g, statusLengkap[absen.status]); return kirimNotifikasiWA(siswaData.noHp, pesan); } return Promise.resolve({ status: 'rejected' }); }); const results = await Promise.allSettled(promises); results.forEach(r => r.status === 'fulfilled' ? successCount++ : failedCount++); return { success: successCount, failed: failedCount }; } async function kirimNotifikasiWA(nomorHp, pesan) { const { waApiKey, waApiEndpoint, waAuthType, waAuthHeader, waPhoneField, waMessageField } = pengaturanSistem; if (!waApiKey || !waApiEndpoint) throw new Error("Pengaturan API WhatsApp belum lengkap."); let formattedPhone = nomorHp.replace(/[\s-]/g, ''); if (formattedPhone.startsWith('08')) formattedPhone = '62' + formattedPhone.substring(1); const headers = { 'Content-Type': 'application/json' }; if (waAuthType === 'bearer') headers['Authorization'] = `Bearer ${waApiKey}`; else headers[waAuthHeader || 'X-Secret-Key'] = waApiKey; const body = {}; body[waPhoneField || 'phone'] = formattedPhone; body[waMessageField || 'message'] = pesan; const response = await fetch(waApiEndpoint, { method: 'POST', headers, body: JSON.stringify(body) }); if (!response.ok) throw new Error(`API Error: ${response.status}`); return await response.json(); } async function loadSiswaForAbsen() { const kelasId = document.getElementById('pilihKelas').value; const tanggal = document.getElementById('inputTanggalAbsenSiswa').value; const tbody = document.getElementById('daftarSiswa'); const siswa = semuaSiswa.filter(s => s.kelasId === kelasId); tbody.innerHTML = ''; if (siswa.length === 0) return tbody.innerHTML = 'Pilih kelas yang valid.'; const docSnap = await getDoc(doc(absensiSiswaCol, `${tanggal}_${kelasId}`)); const existingRecords = docSnap.exists() ? docSnap.data().records : []; if (docSnap.exists()) { const data = docSnap.data(); const guruPiket = semuaPersonil.find(p => p.id === data.guruPiketId); document.getElementById('infoAbsenSiswaText').innerHTML = `Kelas ini sudah diabsen pada pukul ${new Date(data.timestamp.seconds * 1000).toLocaleTimeString('id-ID')} oleh ${guruPiket?.name || '-'}.`; document.getElementById('infoAbsenSiswa').classList.remove('hidden'); document.getElementById('inputCatatanKhusus').value = data.catatanKhusus || ''; } else { document.getElementById('infoAbsenSiswa').classList.add('hidden'); document.getElementById('inputCatatanKhusus').value = ''; } siswa.sort((a,b) => a.name.localeCompare(b.name)).forEach((s, index) => { const existingStatus = existingRecords.find(rec => rec.nis === s.nis)?.status || 'H'; const rowHtml = ` ${index + 1} ${s.name} ${s.nis}
${['H', 'S', 'I', 'A'].map(st => ` `).join('')}
`; tbody.innerHTML += rowHtml; }); } // ================================================================= // BAGIAN HALAMAN LIHAT ABSENSI // ================================================================= async function renderAbsensiHarian() { await renderAbsensiHarianGuru(); await renderAbsensiHarianSiswa(); } async function renderAbsensiHarianGuru() { const selectedDate = document.getElementById('inputTanggalLihatAbsensi').value; const tbody = document.getElementById('lihatAbsensiGuruBody'); tbody.innerHTML = 'Memuat...'; const q = query(absensiPersonalCol, where("tanggal", "==", selectedDate)); const snapshot = await getDocs(q); const absensiHariIni = {}; snapshot.forEach(doc => { absensiHariIni[doc.data().personilId] = doc.data(); }); tbody.innerHTML = ''; const staff = semuaPersonil.filter(u => ['Guru', 'Pegawai', 'Kepala Sekolah', 'Wakil Kepala Sekolah'].includes(u.role)); staff.forEach(person => { const absen = absensiHariIni[person.id]; let status = 'Belum Absen'; let jamHadir = '-', jamPulang = '-', keterangan = '-'; if (absen) { status = absen.status === 'Hadir' ? 'Hadir' : `Izin`; jamHadir = absen.jamMasuk || '-'; jamPulang = absen.jamPulang || '-'; keterangan = absen.keterangan || '-'; } tbody.innerHTML += `${person.name}${status}${jamHadir}${jamPulang}${keterangan}`; }); } async function renderAbsensiHarianSiswa() { const selectedDate = document.getElementById('inputTanggalLihatAbsensi').value; const selectedKelas = document.getElementById('pilihKelasLihat').value; const tbody = document.getElementById('lihatAbsensiSiswaBody'); tbody.innerHTML = ''; if (!selectedKelas) return tbody.innerHTML = 'Pilih kelas.'; const docSnap = await getDoc(doc(absensiSiswaCol, `${selectedDate}_${selectedKelas}`)); if (!docSnap.exists()) return tbody.innerHTML = `Belum ada data absensi.`; const data = docSnap.data(); document.getElementById('lihatCatatanSiswa').classList.toggle('hidden', !data.catatanKhusus); document.getElementById('lihatCatatanSiswaText').innerText = data.catatanKhusus || ''; const statusMap = { H: 'green', S: 'blue', I: 'yellow', A: 'red' }; data.records.sort((a,b) => a.nama.localeCompare(b.nama)).forEach(rec => { const color = statusMap[rec.status]; tbody.innerHTML += `${rec.nama}${{H:'Hadir',S:'Sakit',I:'Izin',A:'Alfa'}[rec.status]}-`; }); } // ================================================================= // BAGIAN HALAMAN REKAP ABSENSI // ================================================================= async function renderRekapSiswa() { const kelasId = document.getElementById('pilihKelasRekap').value; const tbody = document.getElementById('rekapTabelBodySiswa'); const month = parseInt(document.getElementById('pilihBulanRekapSiswa').value); const year = parseInt(document.getElementById('pilihTahunRekapSiswa').value); tbody.innerHTML = 'Memuat...'; if (!kelasId) return tbody.innerHTML = 'Pilih kelas.'; const siswaDiKelas = semuaSiswa.filter(s => s.kelasId === kelasId); const startDate = `${year}-${String(month + 1).padStart(2, '0')}-01`; const endDate = `${year}-${String(month + 1).padStart(2, '0')}-${new Date(year, month + 1, 0).getDate()}`; const q = query(absensiSiswaCol, where("kelasId", "==", kelasId), where("tanggal", ">=", startDate), where("tanggal", "<=", endDate)); const snapshot = await getDocs(q); const absensiBulanIni = {}; snapshot.forEach(doc => { absensiBulanIni[doc.data().tanggal] = doc.data().records; }); const daysInMonth = new Date(year, month + 1, 0).getDate(); generateRekapHeader(document.getElementById('rekapTanggalHeaderSiswa'), year, month, daysInMonth, ['H', 'S', 'I', 'A']); tbody.innerHTML = ''; siswaDiKelas.sort((a,b) => a.name.localeCompare(b.name)).forEach((s, index) => { let row = `${index + 1}${s.name}`; let counts = { H: 0, S: 0, I: 0, A: 0 }; for (let day = 1; day <= daysInMonth; day++) { const { status, cellClass } = getDailyStatus(year, month, day, absensiBulanIni, s.nis, true); if (counts[status] !== undefined) counts[status]++; row += `${status}`; } for (let i = daysInMonth + 1; i <= 31; i++) row += ``; row += `${counts.H}${counts.S}${counts.I}${counts.A}`; tbody.innerHTML += row; }); } async function renderRekapGuruPegawai() { const tbody = document.getElementById('rekapTabelBodyGuru'); const month = parseInt(document.getElementById('pilihBulanRekapGuru').value); const year = parseInt(document.getElementById('pilihTahunRekapGuru').value); tbody.innerHTML = 'Memuat...'; const personilToRecap = semuaPersonil.filter(p => ['Guru', 'Kepala Sekolah', 'Wakil Kepala Sekolah', 'Pegawai'].includes(p.role)); if (personilToRecap.length === 0) return tbody.innerHTML = 'Tidak ada data personil.'; const startDate = `${year}-${String(month + 1).padStart(2, '0')}-01`; const endDate = `${year}-${String(month + 1).padStart(2, '0')}-${new Date(year, month + 1, 0).getDate()}`; const q = query(absensiPersonalCol, where("tanggal", ">=", startDate), where("tanggal", "<=", endDate)); const snapshot = await getDocs(q); const absensiBulanIni = {}; snapshot.forEach(doc => { const data = doc.data(); if (!absensiBulanIni[data.tanggal]) absensiBulanIni[data.tanggal] = {}; absensiBulanIni[data.tanggal][data.personilId] = data; }); const daysInMonth = new Date(year, month + 1, 0).getDate(); generateRekapHeader(document.getElementById('rekapTanggalHeaderGuru'), year, month, daysInMonth, ['H', 'I', 'A', 'L']); tbody.innerHTML = ''; personilToRecap.sort((a,b) => a.name.localeCompare(b.name)).forEach((p, index) => { let rowHtml = `${index + 1}${p.name}${p.nip}`; let counts = { H: 0, I: 0, A: 0, L: 0 }; for (let day = 1; day <= daysInMonth; day++) { const { status, cellClass } = getDailyStatus(year, month, day, absensiBulanIni, p.id, false); if (counts[status] !== undefined) counts[status]++; rowHtml += `${status}`; } for (let i = daysInMonth + 1; i <= 31; i++) rowHtml += ``; rowHtml += `${counts.H}${counts.I}${counts.A}${counts.L}`; tbody.innerHTML += rowHtml; }); } function generateRekapHeader(headerRow, year, month, daysInMonth, countHeaders) { const holidaySet = new Set(semuaHariLibur.map(h => h.id)); headerRow.innerHTML = ''; for (let i = 1; i <= 31; i++) { const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(i).padStart(2, '0')}`; const d = new Date(dateStr + 'T00:00:00Z'); const dayOfWeek = d.getUTCDay(); let dayClass = (dayOfWeek === 0) ? 'bg-red-200' : (holidaySet.has(dateStr) ? 'bg-gray-200' : ''); headerRow.innerHTML += (i > daysInMonth) ? `` : `${i}`; } const colorMap = { H: 'green', S: 'blue', I: 'yellow', A: 'red', L: 'gray'}; countHeaders.forEach(h => headerRow.innerHTML += `${h}`); } function getDailyStatus(year, month, day, absensiData, userId, isSiswa) { const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const d = new Date(dateStr + 'T00:00:00Z'); const dayOfWeek = d.getUTCDay(); const holidaySet = new Set(semuaHariLibur.map(h => h.id)); const colors = { H: 'bg-green-100', S: 'bg-blue-100', I: 'bg-yellow-100', A: 'bg-red-100', L: 'bg-gray-200' }; if (dayOfWeek === 0 || holidaySet.has(dateStr)) { return { status: 'L', cellClass: dayOfWeek === 0 ? 'bg-red-200' : 'bg-gray-200' }; } let status; if (isSiswa) { status = absensiData[dateStr]?.find(r => r.nis === userId)?.status || 'A'; } else { const absenRecord = absensiData[dateStr]?.[userId]; if (absenRecord) { status = absenRecord.status === 'Hadir' ? 'H' : (absenRecord.status === 'Izin' ? 'I' : 'A'); } else { status = 'A'; } } return { status, cellClass: colors[status] || '' }; } function printRekap() { const activeTabContent = document.querySelector('.rekap-tab-content:not(.hidden)'); const isSiswa = activeTabContent.id === 'rekapSiswa'; const kopSurat = document.getElementById(isSiswa ? 'kopSuratSiswa' : 'kopSuratGuru'); const bulanEl = document.getElementById(isSiswa ? 'pilihBulanRekapSiswa' : 'pilihBulanRekapGuru'); const tahunEl = document.getElementById(isSiswa ? 'pilihTahunRekapSiswa' : 'pilihTahunRekapGuru'); document.querySelectorAll('.print-date').forEach(el => el.innerText = new Date().toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })); kopSurat.querySelector('.rekapBulan').innerText = bulanEl.options[bulanEl.selectedIndex].text; kopSurat.querySelector('.rekapTahun').innerText = tahunEl.value; kopSurat.querySelector('.kopNamaSekolah').innerText = pengaturanSistem.namaSekolah || "NAMA SEKOLAH"; if (isSiswa) { const kelasEl = document.getElementById('pilihKelasRekap'); kopSurat.querySelector('.rekapKelas').innerText = kelasEl.options[kelasEl.selectedIndex].text; const waliKelas = semuaPersonil.find(p => p.id === semuaKelas.find(k => k.id === kelasEl.value)?.waliKelasNip); document.getElementById('printNamaWaliKelas').innerText = waliKelas ? waliKelas.name : '(Nama Wali Kelas)'; document.getElementById('printNipWaliKelas').innerText = waliKelas ? `NIP. ${waliKelas.nip}` : '(NIP Wali Kelas)'; } else { document.getElementById('printNamaKepsek').innerText = pengaturanSistem.namaKepsek || '(Nama Kepala Sekolah)'; document.getElementById('printNipKepsek').innerText = pengaturanSistem.nipKepsek ? `NIP. ${pengaturanSistem.nipKepsek}` : '(NIP Kepala Sekolah)'; } window.print(); } function unduhExcel() { const activeTab = document.querySelector('.rekap-tab-content:not(.hidden)'); const table = document.getElementById(activeTab.id === 'rekapSiswa' ? 'tabelRekapSiswa' : 'tabelRekapGuru'); if (!table || table.querySelector('tbody tr td[colspan]')) return showErrorToast('Tidak ada data untuk diunduh.'); const monthName = document.querySelector(`#${activeTab.id === 'rekapSiswa' ? 'pilihBulanRekapSiswa' : 'pilihBulanRekapGuru'} option:checked`).text; const year = document.querySelector(`#${activeTab.id === 'rekapSiswa' ? 'pilihTahunRekapSiswa' : 'pilihTahunRekapGuru'}`).value; const kelasName = activeTab.id === 'rekapSiswa' ? document.querySelector('#pilihKelasRekap option:checked').text.replace(/\s/g, '_') : ''; const fileName = `Rekap_${activeTab.id === 'rekapSiswa' ? 'Siswa_' + kelasName : 'Guru'}_${monthName}_${year}.xlsx`; const wb = XLSX.utils.table_to_book(table, { sheet: "Rekap" }); XLSX.writeFile(wb, fileName); showSuccessToast('Mengunduh file Excel...'); } function populateBulanDropdown() { const selects = [document.getElementById('pilihBulanRekapSiswa'), document.getElementById('pilihBulanRekapGuru')]; const bulan = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"]; selects.forEach(select => { if (!select) return; select.innerHTML = bulan.map((nama, i) => ``).join(''); select.value = new Date().getMonth(); }); } // ================================================================= // BAGIAN PANEL ADMIN // ================================================================= function renderManajemenPersonil() { const tbody = document.getElementById('personilTableBody'); if (!tbody) return; tbody.innerHTML = semuaPersonil.map(user => ` ${user.name}${user.nip}${user.role} `).join(''); tbody.querySelectorAll('.edit-personil-btn').forEach(b => b.addEventListener('click', e => showPersonilForm(e.currentTarget.dataset.id))); tbody.querySelectorAll('.hapus-personil-btn').forEach(b => b.addEventListener('click', e => hapusPersonil(e.currentTarget.dataset.id))); } function renderManajemenSiswa() { const container = document.getElementById('siswaListContainer'); const filterKelasId = document.getElementById('filterKelasSiswa').value; if(!container) return; container.innerHTML = ''; (filterKelasId ? semuaKelas.filter(k => k.id === filterKelasId) : semuaKelas).forEach(kelas => { let siswaHtml = semuaSiswa.filter(s => s.kelasId === kelas.id).sort((a,b)=>a.name.localeCompare(b.name)).map(s => ` ${s.nis}${s.name} `).join(''); container.innerHTML += `

${kelas.name}

${siswaHtml || ``}
NISNamaAksi
Belum ada siswa.
`; }); container.querySelectorAll('.edit-siswa-btn').forEach(b => b.addEventListener('click', e => showSiswaForm(e.currentTarget.dataset.id))); container.querySelectorAll('.hapus-siswa-btn').forEach(b => b.addEventListener('click', e => hapusSiswa(e.currentTarget.dataset.id))); } function renderManajemenKelas() { const tbody = document.getElementById('kelasTableBody'); if (!tbody) return; tbody.innerHTML = semuaKelas.map(k => { const wali = semuaPersonil.find(p => p.id === k.waliKelasNip); return `${k.name}${wali?.name || '-'} ${semuaSiswa.filter(s => s.kelasId === k.id).length} `; }).join(''); tbody.querySelectorAll('.edit-kelas-btn').forEach(b => b.addEventListener('click', e => showKelasForm(e.currentTarget.dataset.id))); tbody.querySelectorAll('.hapus-kelas-btn').forEach(b => b.addEventListener('click', e => hapusKelas(e.currentTarget.dataset.id))); } function renderManajemenHariLibur() { const tbody = document.getElementById('hariLiburTableBody'); if (!tbody) return; tbody.innerHTML = semuaHariLibur.sort((a,b) => a.id.localeCompare(b.id)).map(libur => ` ${libur.id}${libur.keterangan} `).join(''); tbody.querySelectorAll('.hapus-libur-btn').forEach(b => b.addEventListener('click', e => hapusHariLibur(e.currentTarget.dataset.id))); } function renderManajemenPengumuman() { const tbody = document.getElementById('pengumumanTableBody'); if (!tbody) return; tbody.innerHTML = semuaPengumuman.sort((a,b) => (b.timestamp?.seconds || 0) - (a.timestamp?.seconds || 0)).map(p => ` ${p.judul}${p.isi} `).join(''); tbody.querySelectorAll('.edit-pengumuman-btn').forEach(b => b.addEventListener('click', e => showPengumumanForm(e.currentTarget.dataset.id))); tbody.querySelectorAll('.hapus-pengumuman-btn').forEach(b => b.addEventListener('click', e => hapusPengumuman(e.currentTarget.dataset.id))); } function showPersonilForm(id = null) { const user = id ? semuaPersonil.find(p => p.id === id) : null; const title = user ? 'Edit Personil' : 'Tambah Personil'; const bodyHtml = `
`; openModal(title, bodyHtml); document.getElementById('formPersonil').addEventListener('submit', (e) => { e.preventDefault(); simpanPersonil(); }); } function showKelasForm(id = null) { const kelas = id ? semuaKelas.find(k => k.id === id) : null; const title = kelas ? 'Edit Kelas' : 'Tambah Kelas'; let teacherOptions = '' + semuaPersonil.filter(u => u.role === 'Guru').map(t => ``).join(''); const bodyHtml = `
`; openModal(title, bodyHtml); document.getElementById('formKelas').addEventListener('submit', (e) => { e.preventDefault(); simpanKelas(); }); } function showSiswaForm(id = null, kelasId = null) { const siswa = id ? semuaSiswa.find(s => s.id === id) : null; const title = siswa ? 'Edit Siswa' : 'Tambah Siswa'; let kelasOptions = semuaKelas.map(k => ``).join(''); const bodyHtml = `
`; openModal(title, bodyHtml); document.getElementById('formSiswa').addEventListener('submit', (e) => { e.preventDefault(); simpanSiswa(); }); } function showPengumumanForm(id) { const p = semuaPengumuman.find(peng => peng.id === id); if (p) { document.getElementById('pengumumanId').value = p.id; document.getElementById('judulPengumuman').value = p.judul; document.getElementById('isiPengumuman').value = p.isi; document.getElementById('formPengumuman').scrollIntoView({ behavior: 'smooth' }); } } async function simpanPersonil() { const originalNip = document.getElementById('formOriginalNip').value; const nip = document.getElementById('formNip').value; const name = document.getElementById('formNama').value; const role = document.getElementById('formPeran').value; const tglLahir = document.getElementById('formTglLahir').value; const data = { name, role, tglLahir, nip }; if (role === 'Admin') { const pass = prompt("Masukkan password untuk Admin (biarkan kosong jika tidak ingin mengubah):"); if(pass) data.password = pass; } try { if (originalNip && originalNip !== nip) { const batch = writeBatch(db); batch.set(doc(personilCol, nip), data); batch.delete(doc(personilCol, originalNip)); const absensiSnapshot = await getDocs(query(absensiPersonalCol, where("personilId", "==", originalNip))); absensiSnapshot.forEach(oldDoc => { const newData = { ...oldDoc.data(), personilId: nip, nip: nip }; batch.set(doc(absensiPersonalCol, `${newData.tanggal}_${nip}`), newData); batch.delete(oldDoc.ref); }); await batch.commit(); showSuccessToast('Data personil berhasil dimigrasikan.'); } else { await setDoc(doc(personilCol, nip), data, { merge: true }); showSuccessToast('Data personil berhasil disimpan!'); } closeModal(); } catch(error) { showErrorToast("Gagal menyimpan data."); console.error(error); } } async function simpanKelas() { const id = document.getElementById('formKelasId').value.toUpperCase().trim(); const name = document.getElementById('formKelasNama').value; const waliKelasNip = document.getElementById('formWaliKelas').value; try { await setDoc(doc(kelasCol, id || name.replace(/\s+/g, '-')), { name, waliKelasNip }); showSuccessToast('Data kelas berhasil disimpan.'); closeModal(); } catch(error) { showErrorToast("Gagal menyimpan data kelas."); console.error(error); } } async function simpanSiswa() { const nis = document.getElementById('formSiswaNis').value; const name = document.getElementById('formSiswaNama').value; const kelasId = document.getElementById('formSiswaKelas').value; const namaOrtu = document.getElementById('formSiswaNamaOrtu').value; const noHp = document.getElementById('formSiswaNoHp').value; try { await setDoc(doc(siswaCol, nis), { nis, name, namaOrtu, noHp, kelasId }); showSuccessToast('Data siswa berhasil disimpan.'); closeModal(); } catch(error) { showErrorToast("Gagal menyimpan data siswa."); console.error(error); } } async function simpanPengumuman(e) { e.preventDefault(); const id = document.getElementById('pengumumanId').value; const data = { judul: document.getElementById('judulPengumuman').value, isi: document.getElementById('isiPengumuman').value, timestamp: serverTimestamp() }; try { if (id) await setDoc(doc(pengumumanCol, id), data, { merge: true }); else await addDoc(pengumumanCol, data); showSuccessToast('Pengumuman berhasil disimpan.'); e.target.reset(); document.getElementById('pengumumanId').value = ''; } catch (error) { showErrorToast("Gagal menyimpan pengumuman."); console.error(error); } } async function simpanHariLibur(e) { e.preventDefault(); const startDate = new Date(document.getElementById('inputTanggalLiburMulai').value + 'T00:00:00'); const endDate = new Date((document.getElementById('inputTanggalLiburSelesai').value || document.getElementById('inputTanggalLiburMulai').value) + 'T00:00:00'); const keterangan = document.getElementById('inputKeteranganLibur').value; try { const batch = writeBatch(db); for (let d = startDate; d <= endDate; d.setDate(d.getDate() + 1)) { batch.set(doc(hariLiburCol, d.toISOString().split('T')[0]), { keterangan }); } await batch.commit(); showSuccessToast('Hari libur berhasil disimpan.'); e.target.reset(); } catch (error) { showErrorToast("Gagal menyimpan hari libur."); console.error(error); } } async function simpanPengaturan(e) { e.preventDefault(); const toBase64 = file => new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); const data = { namaSekolah: document.getElementById('namaSekolah').value, npsnSekolah: document.getElementById('npsnSekolah').value, namaKepsek: document.getElementById('namaKepsek').value, nipKepsek: document.getElementById('nipKepsek').value, waApiEndpoint: document.getElementById('waApiEndpoint').value, waApiKey: document.getElementById('waApiKey').value, waAuthType: document.getElementById('waAuthType').value, waAuthHeader: document.getElementById('waAuthHeader').value, waPhoneField: document.getElementById('waPhoneField').value, waMessageField: document.getElementById('waMessageField').value, waPesanTemplate: document.getElementById('waPesanTemplate').value, }; try { const logoFile = document.getElementById('logoSekolah').files[0]; if (logoFile) data.logoBase64 = await toBase64(logoFile); await setDoc(doc(pengaturanCol, 'sekolah'), data, { merge: true }); showSuccessToast('Pengaturan berhasil disimpan.'); } catch (error) { showErrorToast('Gagal menyimpan pengaturan.'); console.error(error); } } function hapusPersonil(id) { showConfirmModal('Hapus Personil', `Yakin hapus personil ID ${id}? Aksi ini tidak bisa dibatalkan.`, async () => { await deleteDoc(doc(personilCol, id)); showSuccessToast('Personil dihapus.'); }); } function hapusKelas(id) { showConfirmModal('Hapus Kelas', `Yakin hapus kelas ${id}?`, async () => { await deleteDoc(doc(kelasCol, id)); showSuccessToast('Kelas dihapus.'); }); } function hapusSiswa(id) { showConfirmModal('Hapus Siswa', `Yakin hapus siswa NIS ${id}?`, async () => { await deleteDoc(doc(siswaCol, id)); showSuccessToast('Siswa dihapus.'); }); } function hapusPengumuman(id) { showConfirmModal('Hapus Pengumuman', `Yakin hapus pengumuman ini?`, async () => { await deleteDoc(doc(pengumumanCol, id)); showSuccessToast('Pengumuman dihapus.'); }); } function hapusHariLibur(id) { showConfirmModal('Hapus Hari Libur', `Yakin hapus libur pada ${id}?`, async () => { await deleteDoc(doc(hariLiburCol, id)); showSuccessToast('Hari libur dihapus.'); }); } // ================================================================= // FUNGSI BANTUAN (HELPERS) // ================================================================= function openModal(title, bodyHtml) { document.getElementById('modalTitle').innerText = title; document.getElementById('modalBody').innerHTML = bodyHtml; document.getElementById('formModal').classList.remove('hidden'); document.getElementById('formModal').classList.add('flex'); } function closeModal() { document.getElementById('formModal').classList.add('hidden'); document.getElementById('formModal').classList.remove('flex'); } function showSuccessToast(message) { const toast = document.getElementById('successToast'); document.getElementById('successMessage').innerText = message; toast.classList.remove('hidden', 'translate-x-[110%]'); setTimeout(() => { toast.classList.add('translate-x-[110%]'); setTimeout(() => toast.classList.add('hidden'), 500); }, 3000); } function showErrorToast(message) { const toast = document.getElementById('errorToast'); document.getElementById('errorMessage').innerText = message; toast.classList.remove('hidden', 'translate-x-[110%]'); setTimeout(() => { toast.classList.add('translate-x-[110%]'); setTimeout(() => toast.classList.add('hidden'), 4000); }, 4000); } function showConfirmModal(title, message, onConfirm) { const modal = document.getElementById('confirmModal'); document.getElementById('confirmTitle').innerText = title; document.getElementById('confirmMessage').innerText = message; modal.classList.remove('hidden'); modal.classList.add('flex'); const confirmOk = document.getElementById('confirmOk'); const newConfirmOk = confirmOk.cloneNode(true); confirmOk.parentNode.replaceChild(newConfirmOk, confirmOk); newConfirmOk.addEventListener('click', () => { onConfirm(); closeConfirmModal(); }); document.getElementById('confirmCancel').onclick = closeConfirmModal; } function closeConfirmModal() { document.getElementById('confirmModal').classList.add('hidden'); } function updateDateTime() { const now = new Date(); const dateEl = document.getElementById('currentDate'); const timeEl = document.getElementById('currentTime'); if(dateEl) dateEl.innerText = now.toLocaleDateString('id-ID', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); if(timeEl) timeEl.innerText = now.toLocaleTimeString('id-ID'); } function getTodayDateString() { return new Date().toISOString().split('T')[0]; } function setAbsenInputsToNow(dateInputId, timeInputId) { const now = new Date(); const dateInput = document.getElementById(dateInputId); if(dateInput) dateInput.value = now.toISOString().split('T')[0]; if(timeInputId) document.getElementById(timeInputId).value = now.toTimeString().substring(0, 5); } function updateAllKelasDropdowns() { const dropdowns = [document.getElementById('pilihKelas'), document.getElementById('pilihKelasLihat'), document.getElementById('pilihKelasRekap'), document.getElementById('filterKelasSiswa')]; const optionsHtml = semuaKelas.sort((a,b)=>a.name.localeCompare(b.name)).map(k => ``).join(''); dropdowns.forEach(dropdown => { if (!dropdown) return; const firstOptHTML = dropdown.querySelector('option')?.outerHTML || ''; dropdown.innerHTML = firstOptHTML + optionsHtml; }); } function updateGuruPiketDropdown() { const dropdown = document.getElementById('pilihGuruPiket'); if(!dropdown) return; const optionsHtml = semuaPersonil.filter(p => p.role === 'Guru').sort((a,b)=>a.name.localeCompare(b.name)).map(g => ``).join(''); dropdown.innerHTML = '' + optionsHtml; } function applyPengaturan(data) { if(!data) return; document.title = `SIAP Hadir - ${data.namaSekolah || 'Absensi Online'}`; ['loginSchoolName', 'sidebarSchoolName'].forEach(id => { const el = document.getElementById(id); if(el) el.innerText = data.namaSekolah || 'NAMA SEKOLAH'; }); ['loginLogo', 'headerLogo', 'logoPreview', 'userAvatar'].forEach(id => { const el = document.getElementById(id); if(el && data.logoBase64) el.src = data.logoBase64; }); Object.keys(data).forEach(key => { const el = document.getElementById(key); if(el) el.value = data[key]; }); const waAuthTypeEl = document.getElementById('waAuthType'); if(waAuthTypeEl) { document.getElementById('customAuthHeaderContainer').style.display = (waAuthTypeEl.value === 'bearer') ? 'none' : 'block'; } } function setupTabListeners(btn1Id, btn2Id, content1Id, content2Id) { const tab1Btn = document.getElementById(btn1Id); const tab2Btn = document.getElementById(btn2Id); const content1 = document.getElementById(content1Id); const content2 = document.getElementById(content2Id); if(!tab1Btn || !tab2Btn) return; const switchFunc = (activeTab) => { content1.classList.toggle('hidden', activeTab !== 1); content2.classList.toggle('hidden', activeTab === 1); tab1Btn.classList.toggle('border-blue-600', activeTab === 1); tab1Btn.classList.toggle('text-blue-600', activeTab === 1); tab2Btn.classList.toggle('border-blue-600', activeTab !== 1); tab2Btn.classList.toggle('text-blue-600', activeTab !== 1); }; tab1Btn.addEventListener('click', () => switchFunc(1)); tab2Btn.addEventListener('click', () => switchFunc(2)); switchFunc(1); } function setupAdminTabs() { const tabs = document.querySelectorAll('.admin-tab'); const contents = document.querySelectorAll('.admin-tab-content'); tabs.forEach(tab => { tab.addEventListener('click', () => { const target = tab.getAttribute('data-tab'); tabs.forEach(t => t.classList.remove('border-blue-600', 'text-blue-600')); tab.classList.add('border-blue-600', 'text-blue-600'); contents.forEach(c => c.classList.toggle('hidden', c.id !== target)); }); }); } function setupRekapTabs() { const tabs = document.querySelectorAll('.rekap-tab'); const contents = document.querySelectorAll('.rekap-tab-content'); tabs.forEach(tab => { tab.addEventListener('click', () => { const target = tab.getAttribute('data-tab'); tabs.forEach(t => t.classList.remove('border-blue-600', 'text-blue-600')); tab.classList.add('border-blue-600', 'text-blue-600'); contents.forEach(c => c.classList.toggle('hidden', c.id !== target)); }); }); }