// // // Lalu ganti loginDenganAkun() local dengan loginSupabase(), dan ganti // localStorage absensi/siswa dengan fungsi di bawah ini. (function () { function getConfig() { return window.SUPABASE_CONFIG || {}; } function isConfigured() { const c = getConfig(); return c.url && c.anonKey && !String(c.url).includes("GANTI-DENGAN") && !String(c.anonKey).includes("GANTI-DENGAN"); } function usernameToEmail(username) { const domain = window.SUPABASE_EMAIL_DOMAIN || "absensi.smpn2saketi"; return String(username).trim().toLowerCase() + "@" + domain; } function createClient() { if (!isConfigured()) return null; if (!window.supabase) return null; return window.supabase.createClient(getConfig().url, getConfig().anonKey); } async function loginSupabase(username, password) { const client = createClient(); if (!client) throw new Error("Supabase belum dikonfigurasi di config.js"); const email = usernameToEmail(username); const { data, error } = await client.auth.signInWithPassword({ email, password }); if (error) throw error; const { data: profile, error: perr } = await client .from("profiles") .select("id, username, nama, peran, aktif") .eq("id", data.user.id) .single(); if (perr) throw perr; if (!profile.aktif) throw new Error("Akun ini nonaktif. Hubungi Master."); return { user: data.user, profile }; } async function loadSiswa(client) { const { data, error } = await client.from("siswa").select("*").eq("aktif", true).order("rombel").order("nama"); if (error) throw error; return data.map(s => ({ id: s.id, nama: s.nama, rombel: s.rombel, jk: s.jk })); } async function loadAbsensi(client) { const { data, error } = await client.from("absensi").select("siswa_id, tanggal, status, siswa:siswa_id(rombel)"); if (error) throw error; const out = {}; data.forEach(r => { const rombel = r.siswa ? r.siswa.rombel : ""; out[r.tanggal + "|" + rombel + "|" + r.siswa_id] = r.status; }); return out; } async function saveAbsensi(client, siswaId, tanggal, status, diinputOleh) { const { error } = await client.from("absensi").upsert( { siswa_id: siswaId, tanggal, status, diinput_oleh: diinputOleh }, { onConflict: "siswa_id,tanggal" } ); if (error) throw error; } async function saveReview(client, siswaId, bulan, sudah, catatan, diupdateOleh) { const { error } = await client.from("review_privat").upsert( { siswa_id: siswaId, bulan, sudah_dikonfirmasi: sudah, catatan, diupdate_oleh: diupdateOleh }, { onConflict: "siswa_id,bulan" } ); if (error) throw error; } async function saveAlert(client, siswaId, bulan, status, catatan, diupdateOleh) { const { error } = await client.from("tindak_lanjut_alert").upsert( { siswa_id: siswaId, bulan, status, catatan, diupdate_oleh: diupdateOleh }, { onConflict: "siswa_id,bulan" } ); if (error) throw error; } window.AbsensiSupabase = { isConfigured, usernameToEmail, createClient, loginSupabase, loadSiswa, loadAbsensi, saveAbsensi, saveReview, saveAlert }; })();

Absensi Shalat Dhuhr

SMPN 2 Saketi - Prototipe program sekolah, jam sekolah, hanya Dhuhr

Login

Isi username dan password saja. Webapp yang akan menentukan siapa yang login, Master atau Admin Putra atau Admin Putri, berdasarkan akun itu. Tidak ada lagi tombol pilih peran di halaman ini.

2 jenis user di versi online nanti:
1. Master: akses penuh, bisa bikin user baru, dipakai kepala sekolah atau staff IT.
2. Admin: Admin Putra dan Admin Putri, bertugas input kehadiran siswa sesuai kelompoknya.

Batas privasi tetap berlaku: data Berhalangan dan Review Privat putri tidak tampil di akun Admin Putra. Untuk Master, daftar review putri tetap dibuka lewat gerbang privasi di menu Rekap, dan sebaiknya tindak lanjut privatnya tetap dipegang guru perempuan atau UKS.

Peringatan keamanan: password demo di atas hanya untuk prototipe lokal. Versi online wajib menyimpan password dalam bentuk hash, bukan teks asli, dan wajib pakai HTTPS.

Data di sini masih data contoh, bukan nama siswa asli. Data tersimpan lokal di browser ini, belum online ke server.