// =================================================================
// 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}
NIS | Nama | Aksi |
${siswaHtml || `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));
});
});
}