Source Code Aplikasi Administrasi Guru Lengkap, bisa untuk satu sekolah, dengan fitur admin dan password
Manajemen data sekolah yang berantakan, tumpukan kertas, dan lamanya proses rekapitulasi nilai serta absensi seringkali menjadi kendala utama bagi tenaga pendidik. Untuk menjawab tantangan tersebut, pada artikel ini saya akan membagikan Source Code Sistem Administrasi Guru Lengkap berbasis web.
Aplikasi ini dirancang cukup untuk mengelola administrasi satu sekolah penuh, dilengkapi dengan berbagai fitur mulai dari input absensi, jurnal mengajar, leger nilai, buku kasus bimbingan wali, hingga dashboard khusus Administrator untuk memanajemen pengguna.
Apa itu Google Apps Script?
Google Apps Script (GAS) adalah platform pengembangan aplikasi berbasis cloud dari Google yang menggunakan bahasa pemrograman JavaScript. Secara sederhana, GAS memungkinkan Anda untuk membuat aplikasi web ringan dan mengotomatiskan tugas-tugas di ekosistem Google Workspace (seperti Google Sheets, Docs, dan Drive) tanpa perlu menyewa hosting atau server (gratis 100%).
Perbedaan Administrasi Konvensional vs Online (Apps Script)
| Aspek | Konvensional (Buku/Excel Offline) | Online (Aplikasi Google Apps Script) |
|---|---|---|
| Penyimpanan | Rentan hilang, rusak, atau terselip. | Aman di cloud (Google Drive), otomatis tersimpan. |
| Aksesibilitas | Hanya bisa diakses di sekolah/komputer tertentu. | Dapat diakses kapan saja via Smartphone, Tablet, atau Laptop. |
| Rekapitulasi | Manual, memakan waktu lama, rawan salah hitung. | Otomatis secara real-time, sekali klik laporan siap cetak (PDF). |
| Kolaborasi | Sulit dipantau secara bersamaan oleh Kepsek/Admin. | Terpusat. Admin dapat memantau seluruh kinerja guru secara live. |
Manfaat Menggunakan Aplikasi Ini Untuk Pendidik
- Paperless (Ramah Lingkungan): Mengurangi penggunaan kertas yang menumpuk di meja guru.
- Efisiensi Waktu: Fitur rekapitulasi otomatis (Absensi dan Leger Nilai) sangat menghemat waktu guru saat akhir semester.
- Keamanan Data Terjamin: Data guru, absensi, dan nilai tersimpan aman di Google Spreadsheet milik sekolah. Sistem login juga sudah dilengkapi proteksi Server-Side Authentication.
- Fleksibilitas Tinggi: Guru bisa mengisi jurnal mengajar atau absensi langsung dari HP saat berada di dalam kelas.
Setelah Anda menyalin dan menerapkan aplikasi ini, Anda dapat login untuk pertama kalinya menggunakan akun Admin.
- Pilih Nama Pengguna: Admin
- Password Default: 123456
Langkah-Langkah Instalasi (Deployment)
- Buka Google Drive Anda, buat sebuah file Google Spreadsheet kosong baru. Beri nama (misal: Database Sekolah).
- Pada menu Spreadsheet, klik tab Ekstensi (Extensions) > pilih Apps Script.
- Hapus kode bawaan yang ada di editor. Salin kode Code.gs di bawah ini, lalu tempel (paste) ke editor tersebut.
- Buat file HTML baru dengan cara klik ikon (+) Tambahkan File di panel kiri > pilih HTML. Beri nama file tepat dengan
Index(Perhatikan huruf 'I' besar). - Salin kode Index.html di bawah, lalu tempel ke dalam file HTML yang baru dibuat.
- Simpan proyek dengan menekan ikon Save (atau Ctrl+S).
- Di pojok kanan atas, klik tombol biru Terapkan (Deploy) > Deployment baru (New deployment).
- Pilih ikon roda gigi (⚙️) > centang Aplikasi Web. Konfigurasikan:
- Jalankan sebagai: Saya (Email Anda)
- Siapa yang memiliki akses: Siapa saja (Anyone)
- Klik Terapkan. Jika diminta otorisasi, ikuti panduan Google untuk memberikan izin akses (Beri Akses > Akun Google > Lanjutan > Buka aplikasi).
- Selesai! Salin URL Aplikasi Web yang muncul dan bagikan ke rekan-rekan pendidik di sekolah Anda.
Source Code Sistem Administrasi Guru v9.4
/**
* SISTEM ADMINISTRASI GURU v9.4 (REVISI PLATINUM - FIX BUG LOGIN)
* Update: Dynamic Indexing Password, Safe Mapping & Server-Side Auth
*/
function doGet() {
var template = HtmlService.createTemplateFromFile('Index');
// --- SECURITY CHECK ---
var _raw = template.getRawContent();
var _k1 = "\x77\x77\x77\x2e\x79\x65\x66\x72\x69\x68\x61\x72\x79\x61\x6e\x74\x6f\x2e\x69\x64";
var _k2 = "\x59\x65\x66\x72\x69\x20\x48\x61\x72\x79\x61\x6e\x74\x6f";
if (_raw.indexOf(_k1) === -1 || _raw.indexOf(_k2) === -1) {
return HtmlService.createHtmlOutput(
"<div style='font-family:monospace;text-align:center;margin-top:20%;color:red;'>" +
"<h1>FATAL ERROR: 0xCOPYRIGHT_MISSING</h1>" +
"<p>Code Integrity Check Failed.</p>" +
"<p>Please restore the original footer credit.</p>" +
"</div>"
);
}
return template
.evaluate()
.setTitle('Sistem Administrasi Guru')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
function openDatabase() {
return SpreadsheetApp.getActiveSpreadsheet();
}
function setupStructure(ss) {
const struct = [
{ name: "Config", headers: ["Key", "Value"] },
{ name: "Users", headers: ["Nama", "Role", "MapelAmpuan", "Status", "NIP", "FotoURL", "Password"] },
{ name: "Kelas", headers: ["Nama Kelas"] },
{ name: "Mapel", headers: ["Nama Mapel"] },
{ name: "DataSiswa", headers: ["No", "Nama Siswa", "Kelas"] },
{ name: "Absensi", headers: ["Waktu", "Kelas", "Mapel", "Nama Siswa", "Status", "Nama Guru", "Bulan", "Tahun"] },
{ name: "Nilai", headers: ["Waktu", "Jenis", "Mapel", "Kelas", "Nama Siswa", "Nilai", "Keterangan", "Nama Guru"] },
{ name: "Agenda", headers: ["Hari/Tgl", "Jam", "Kelas", "Mapel", "Materi", "Status", "Absen Siswa", "Ket", "Nama Guru"] },
{ name: "BimbinganWali", headers: ["Tanggal", "Nama Siswa", "Kelas", "Jenis", "Masalah", "Solusi", "Guru Wali"] },
{ name: "SiswaBimbingan", headers: ["Nama Siswa", "Kelas", "Guru Wali"] }
];
struct.forEach(s => {
let sheet = ss.getSheetByName(s.name);
if (!sheet) {
sheet = ss.insertSheet(s.name);
sheet.appendRow(s.headers);
sheet.getRange(1, 1, 1, s.headers.length).setFontWeight("bold").setBackground("#2c3e50").setFontColor("white");
}
});
const uS = ss.getSheetByName("Users");
if (uS) {
let uHeaders = uS.getRange(1, 1, 1, uS.getLastColumn() || 1).getValues()[0];
if (uHeaders.indexOf("Password") === -1) {
let nextCol = uS.getLastColumn() + 1;
uS.getRange(1, nextCol).setValue("Password").setFontWeight("bold").setBackground("#2c3e50").setFontColor("white");
let lr = uS.getLastRow();
if (lr > 1) {
let arr = [];
for(let i = 0; i < lr - 1; i++) arr.push(["123456"]);
uS.getRange(2, nextCol, lr - 1, 1).setValues(arr);
}
}
if (uS.getLastRow() < 2) {
let newHeaders = uS.getRange(1, 1, 1, uS.getLastColumn() || 1).getValues()[0];
let pIdx = newHeaders.indexOf("Password");
let rowData = ["Admin", "Admin", "-", "Aktif", "-", ""];
if(pIdx !== -1) {
while(rowData.length <= pIdx) rowData.push("");
rowData[pIdx] = "123456";
} else {
rowData.push("123456");
}
uS.appendRow(rowData);
}
}
const cS = ss.getSheetByName("Config");
if(cS.getLastRow() < 2) {
const defs = [
["Nama Pemerintah", "PEMERINTAH KABUPATEN KERINCI"],
["Nama Sekolah", "SMP NEGERI 3 KERINCI"],
["Alamat Sekolah", "Jalan Muradi, Kec. Danau Kerinci Barat"],
["Nama Kepala Sekolah", "NAMA KEPSEK, M.Pd"],
["NIP Kepala Sekolah", "19800101 200001 1 001"],
["Logo Kiri", "https://upload.wikimedia.org/wikipedia/commons/e/ed/Logo_Kabupaten_Kerinci.png"],
["Logo Kanan", "https://upload.wikimedia.org/wikipedia/commons/9/9c/Logo_Tut_Wuri_Handayani.png"],
["Link Spreadsheet", ss.getUrl()],
["Tempat Tanda Tangan", "Kerinci"]
];
defs.forEach(d=>cS.appendRow(d));
} else {
let cData = cS.getDataRange().getValues();
let hasTtd = cData.some(r => r[0] === "Tempat Tanda Tangan");
if(!hasTtd) {
cS.appendRow(["Tempat Tanda Tangan", "Kerinci"]);
}
}
}
function getInitData() {
const ss = openDatabase();
setupStructure(ss);
const cData = ss.getSheetByName("Config").getDataRange().getValues();
let conf = {}; cData.forEach(r => { if(r[0]) conf[r[0]] = r[1]; });
const mapel = ss.getSheetByName("Mapel").getDataRange().getValues().slice(1).flat().filter(String);
const kelas = ss.getSheetByName("Kelas").getDataRange().getValues().slice(1).flat().filter(String);
const uS = ss.getSheetByName("Users");
const uData = uS.getDataRange().getValues();
// FIX: Hanya mengirimkan data publik ke frontend, JANGAN kirim password!
const users = uData.slice(1).filter(r=>r[0]).map(r => {
return [r[0], r[1], r[2], r[3], r[4], r[5]];
});
const jmlSiswa = ss.getSheetByName("DataSiswa").getLastRow() - 1;
const jmlGuru = users.length;
const jmlRombel = kelas.length;
return { config: conf, mapel: mapel, kelas: kelas, users: users, stats: {siswa: jmlSiswa > 0 ? jmlSiswa : 0, guru: jmlGuru, rombel: jmlRombel} };
}
// --- SERVER-SIDE AUTHENTICATION ---
function verifikasiLogin(username, passwordInput) {
const ss = openDatabase();
const uS = ss.getSheetByName("Users");
if (!uS) return { success: false, message: "Database User tidak ditemukan!" };
const uData = uS.getDataRange().getValues();
const uHeaders = uData[0] || [];
const passIdx = uHeaders.indexOf("Password");
for(let i = 1; i < uData.length; i++) {
if(String(uData[i][0]).trim() === String(username).trim()) {
let realPass = passIdx !== -1 ? uData[i][passIdx] : "123456";
if(String(realPass).trim() === String(passwordInput).trim()) {
return { success: true };
} else {
return { success: false, message: "Password salah!" };
}
}
}
return { success: false, message: "User tidak ditemukan!" };
}
// --- ADMIN FUNCTIONS ---
function saveConfig(key, val) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
const s = openDatabase().getSheetByName("Config");
const data = s.getDataRange().getValues();
let found = false;
for(let i=0; i<data.length; i++){
if(data[i][0] == key){ s.getRange(i+1, 2).setValue(val); found=true; break; }
}
if(!found) s.appendRow([key, val]);
return "Berhasil";
} catch(e) { return "Error: Server sibuk, coba lagi."; }
finally { lock.releaseLock(); }
}
function manageItem(type, action, v1, v2, v3, v4) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
const ss = openDatabase();
let sheet;
if(type=='User') sheet=ss.getSheetByName("Users");
if(type=='Kelas') sheet=ss.getSheetByName("Kelas");
if(type=='Mapel') sheet=ss.getSheetByName("Mapel");
if(action=='add'){
const data = sheet.getDataRange().getValues();
for(let i=0; i<data.length; i++) {
if(String(data[i][0]).trim().toLowerCase() == String(v1).trim().toLowerCase()) return type + " Sudah Ada!";
}
if(type=='User') {
let uHeaders = sheet.getRange(1, 1, 1, sheet.getLastColumn() || 1).getValues()[0];
let pIdx = uHeaders.indexOf("Password");
let newRow = [v1, 'Guru', v2, 'Aktif', v3 || '-', ''];
if(pIdx !== -1) {
while(newRow.length <= pIdx) newRow.push("");
newRow[pIdx] = v4 || '123456';
sheet.appendRow(newRow);
} else {
sheet.appendRow([v1, 'Guru', v2, 'Aktif', v3 || '-', '', v4 || '123456']);
}
}
else sheet.appendRow([v1]);
} else {
const data = sheet.getDataRange().getValues();
for(let i=data.length-1; i>=0; i--){
if(data[i][0] == v1) { sheet.deleteRow(i+1); break; }
}
}
return "Sukses";
} catch(e) { return "Gagal menyimpan data"; }
finally { lock.releaseLock(); }
}
function importSiswa(arr, kls) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
const s = openDatabase().getSheetByName("DataSiswa");
let last = s.getLastRow(); let rows = [];
arr.forEach(n=>{ if(n && n.trim()) { rows.push([last, n.trim(), kls]); last++; } });
if(rows.length) s.getRange(s.getLastRow()+1,1,rows.length,3).setValues(rows);
return rows.length + " Data Masuk";
} catch(e) { return "Gagal Import"; }
finally { lock.releaseLock(); }
}
function getSiswa(k) {
const s = openDatabase().getSheetByName("DataSiswa");
if(s.getLastRow()<2) return [];
return s.getRange(2,2,s.getLastRow()-1,2).getValues().filter(r=>r[1]==k).map(r=>r[0]).sort();
}
// --- ABSENSI ---
function simpanAbsen(kls, mapel, guru, data) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
const s = openDatabase().getSheetByName("Absensi");
const time = new Date();
const bln = time.getMonth()+1; const thn = time.getFullYear();
if(!data || data.length === 0) return "Error: Data Kosong";
const rows = data.map(d=>[time, kls, mapel, d.nama, d.sts, guru, bln, thn]);
s.getRange(s.getLastRow()+1,1,rows.length,8).setValues(rows);
return "Absensi Tersimpan";
} catch(e) { return "Error: " + e.message; }
finally { lock.releaseLock(); }
}
function getLaporanAbsen(kls, mapel, bln, thn, guru) {
const s = openDatabase().getSheetByName("Absensi");
const sList = getSiswa(kls);
if(sList.length==0) return { error: "Siswa Kosong" };
let data = [];
if(s.getLastRow() > 1) {
const raw = s.getRange(2,1,s.getLastRow()-1,8).getValues();
if(bln === "ALL") {
data = raw.filter(r => r[1]==kls && r[2]==mapel && r[7]==thn && r[5]==guru);
} else {
data = raw.filter(r => r[1]==kls && r[2]==mapel && r[6]==bln && r[7]==thn && r[5]==guru);
}
}
let rekap = sList.map((nama, i) => {
let my = data.filter(r=>r[3]==nama);
let st = {H:0, S:0, I:0, A:0};
my.forEach(r=>{
let s = r[4].charAt(0);
if(s=='S') st.S++; else if(s=='I') st.I++; else if(s=='A') st.A++; else st.H++;
});
let tot = st.H+st.S+st.I+st.A;
let pct = tot>0 ? Math.round((st.H/tot)*100) : 0;
return { no:i+1, nama:nama, s:st.S, i:st.I, a:st.A, h:st.H, pct:pct };
});
let harian = data.map((r,i) => ({
no: i+1,
waktu: Utilities.formatDate(new Date(r[0]), "Asia/Jakarta", "dd/MM/yyyy HH:mm"),
nama: r[3],
status: r[4]
}));
return { rekap: rekap, harian: harian };
}
// --- NILAI & RIWAYAT ---
function simpanNilai(jns, mapel, kls, guru, data) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
const s = openDatabase().getSheetByName("Nilai");
const time = new Date();
if(!data || data.length === 0) return "Tidak ada data nilai";
const rows = data.map(d=>[time, jns, mapel, kls, d.nama, d.val, '', guru]);
s.getRange(s.getLastRow()+1,1,rows.length,8).setValues(rows);
return "Nilai Tersimpan";
} catch(e) { return "Error Simpan Nilai"; }
finally { lock.releaseLock(); }
}
function getLeger(kls, mapel, guru) {
const s = openDatabase().getSheetByName("Nilai");
const sList = getSiswa(kls);
if(sList.length==0) return { error: "Siswa Kosong" };
let data = [];
if(s.getLastRow() > 1) {
const raw = s.getRange(2,1,s.getLastRow()-1,8).getValues();
data = raw.filter(r => r[3]==kls && r[2]==mapel && r[7]==guru);
}
let headers = [...new Set(data.map(r=>r[1]))].sort();
let res = sList.map((nama,i) => {
let row = { no:i+1, nama:nama, tot:0, cnt:0 };
headers.forEach(h => {
let f = data.filter(r => r[4]==nama && r[1]==h);
let rawVal = f.length ? String(f[f.length-1][5]).replace(',','.') : 0;
let val = parseFloat(rawVal) || 0;
row[h] = val;
if(val !== null) { row.tot+=val; row.cnt++; }
});
let pembagi = headers.length > 0 ? headers.length : 1;
row.avg = (row.tot / pembagi).toFixed(1);
row.tot = row.tot.toFixed(0);
return row;
});
return { headers: headers, data: res };
}
function getRiwayatNilai(kls, mapel, guru) {
const s = openDatabase().getSheetByName("Nilai");
if(s.getLastRow() < 2) return [];
const raw = s.getRange(2,1,s.getLastRow()-1,8).getValues();
const filtered = raw.filter(r => r[3]==kls && r[2]==mapel && r[7]==guru);
return filtered.sort((a,b) => new Date(b[0]) - new Date(a[0])).map(r => {
r[0] = Utilities.formatDate(new Date(r[0]), "Asia/Jakarta", "dd/MM/yyyy HH:mm");
return r;
});
}
// --- AGENDA & WALI ---
function saveAgenda(d) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
openDatabase().getSheetByName("Agenda").appendRow(d);
return "Agenda Tersimpan";
} finally { lock.releaseLock(); }
}
function getAgenda(guru) {
const s = openDatabase().getSheetByName("Agenda");
const lastRow = s.getLastRow();
if (lastRow < 2) return [];
const d = s.getRange(2, 1, lastRow - 1, 9).getDisplayValues();
let res = [];
const searchKey = String(guru).toLowerCase().trim();
if (guru === 'ALL') {
res = d;
} else {
res = d.filter(r => {
let guruDiSheet = String(r[8]).toLowerCase().trim();
if (guruDiSheet === "") return false;
return guruDiSheet.includes(searchKey) || searchKey.includes(guruDiSheet);
});
}
return res;
}
function simpanSiswaBimbingan(n, k, g) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
openDatabase().getSheetByName("SiswaBimbingan").appendRow([n, k, g]);
} finally {
lock.releaseLock();
}
}
function simpanCatatanBimbingan(d) {
const lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
openDatabase().getSheetByName("BimbinganWali").appendRow(d);
} finally {
lock.releaseLock();
}
}
function getRiwayatBimbingan(g) {
const s = openDatabase().getSheetByName("BimbinganWali");
if(s.getLastRow()<2) return [];
let d = s.getRange(2,1,s.getLastRow()-1,7).getValues().filter(r=>r[6]==g);
return d.map(r => {
r[0] = Utilities.formatDate(new Date(r[0]), "Asia/Jakarta", "dd/MM/yyyy");
return r;
});
}
function getAllBimbingan() {
const s = openDatabase().getSheetByName("BimbinganWali");
if(s.getLastRow() < 2) return [];
let d = s.getRange(2, 1, s.getLastRow()-1, 7).getValues();
return d.map(r => {
r[0] = Utilities.formatDate(new Date(r[0]), "Asia/Jakarta", "dd/MM/yyyy");
return r;
});
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Sistem Administrasi Guru</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
<style>
/* REVISI TEMA: Platinum, Modern & Profesional */
:root {
--primary: #2C3E50;
--secondary: #7F8C8D;
--accent: #E67E22;
--bg: #F0F3F4;
--sidebar-w: 260px;
--glass: rgba(255, 255, 255, 0.95);
}
body { font-family: 'Plus Jakarta Sans', sans-serif; background: var(--bg); font-size: 0.92rem; overflow-x: hidden; color: #2C3E50; }
.hidden { display: none !important; }
#sidebar {
width: var(--sidebar-w); background: white; color: #2C3E50;
height: 100vh; position: fixed; top: 0; left: 0; z-index: 1050;
transition: 0.3s; border-right: 1px solid #dfe4ea; box-shadow: 2px 0 10px rgba(0,0,0,0.02);
}
#sidebar.active { margin-left: calc(var(--sidebar-w) * -1); }
#content {
margin-left: var(--sidebar-w); padding: 25px; transition: 0.3s;
min-height: 100vh; display: flex; flex-direction: column;
}
@media (max-width: 991px) {
#sidebar { margin-left: calc(var(--sidebar-w) * -1); }
#sidebar.active { margin-left: 0; }
#content { margin-left: 0; padding: 15px; }
}
.profile-box { padding: 30px 20px; text-align: center; background: linear-gradient(135deg, #1A252F, #2C3E50); margin-bottom: 20px; color: #ECF0F1; }
.avatar { width: 60px; height: 60px; background: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 10px; font-weight: 800; color: var(--primary); font-size: 1.5rem; border: 4px solid rgba(255,255,255,0.2); overflow: hidden; }
.sb-menu { list-style: none; padding: 0 15px; margin: 0; }
.sb-menu li a { display: flex; align-items: center; padding: 12px 15px; color: #57606f; text-decoration: none; border-radius: 12px; margin-bottom: 5px; transition: 0.2s; font-weight: 600; cursor: pointer; }
.sb-menu li a:hover, .sb-menu li a.active { background: var(--primary); color: white; box-shadow: 0 5px 15px rgba(44, 62, 80, 0.2); }
.sb-menu i { width: 25px; margin-right: 10px; text-align: center; font-size: 1.1rem; }
.view { flex: 1; }
.card-u { background: var(--glass); border-radius: 16px; box-shadow: 0 8px 25px rgba(0,0,0,0.04); border: 1px solid #fff; margin-bottom: 25px; overflow: hidden; backdrop-filter: blur(10px); }
.card-h { padding: 18px 25px; font-weight: 700; color: var(--primary); border-bottom: 1px solid #ECF0F1; display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.9); }
.card-b { padding: 25px; }
.table-striped-custom tbody tr:nth-of-type(odd) { background-color: rgba(44, 62, 80, 0.02); }
.table-striped-custom tbody tr:hover { background-color: rgba(44, 62, 80, 0.06); }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.welcome-card {
background: linear-gradient(135deg, #2C3E50, #4CA1AF);
border-radius: 20px; padding: 30px; color: white;
margin-bottom: 25px; box-shadow: 0 10px 25px rgba(44, 62, 80, 0.2);
position: relative; overflow: hidden;
animation: fadeInUp 0.6s ease-out;
}
.welcome-card::after { content:''; position: absolute; right: -20px; bottom: -50px; width: 150px; height: 150px; background: rgba(255,255,255,0.08); border-radius: 50%; }
.stat-card {
background: white; border-radius: 16px; padding: 20px;
display: flex; align-items: center; gap: 15px;
box-shadow: 0 5px 20px rgba(0,0,0,0.03); border: 1px solid #fff;
transition: 0.3s; cursor: pointer; height: 100%;
animation: fadeInUp 0.6s ease-out forwards;
opacity: 0;
}
.stat-card:hover { transform: translateY(-5px); box-shadow: 0 15px 30px rgba(0,0,0,0.08); border-color: #ECF0F1; }
.stat-icon { width: 50px; height: 50px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 1.4rem; color: white; flex-shrink: 0; box-shadow: 0 5px 15px rgba(0,0,0,0.1); }
.sc-1 { background: linear-gradient(135deg, #34495e, #2c3e50); }
.sc-2 { background: linear-gradient(135deg, #27ae60, #2ecc71); }
.sc-3 { background: linear-gradient(135deg, #8e44ad, #9b59b6); }
.sc-4 { background: linear-gradient(135deg, #e67e22, #f39c12); }
.sc-5 { background: linear-gradient(135deg, #2980b9, #3498db); }
.sc-6 { background: linear-gradient(135deg, #7f8c8d, #95a5a6); border: none; }
.stat-card.d-1 { animation-delay: 0.1s; }
.stat-card.d-2 { animation-delay: 0.2s; }
.stat-card.d-3 { animation-delay: 0.3s; }
.stat-card.d-4 { animation-delay: 0.4s; }
.stat-card.d-5 { animation-delay: 0.5s; }
.stat-card.d-6 { animation-delay: 0.6s; }
.footer-platinum {
margin-top: 30px;
background: linear-gradient(90deg, #1A252F, #000000);
color: #bdc3c7;
padding: 15px 0;
text-align: center;
font-size: 0.8rem;
border-top: 3px solid var(--secondary);
border-radius: 15px 15px 0 0;
letter-spacing: 0.5px;
box-shadow: 0 -5px 20px rgba(0,0,0,0.1);
}
.footer-platinum a { color: #fff; text-decoration: none; font-weight: 700; transition: 0.3s; }
.footer-platinum a:hover { color: var(--accent); }
#login-screen { position: fixed; top: 0; left: 0; width: 100%; height: 100vh; background: #eef2f5; z-index: 2000; display: flex; align-items: center; justify-content: center; }
.login-box { background: white; padding: 40px; border-radius: 24px; width: 90%; max-width: 400px; text-align: center; box-shadow: 0 20px 60px rgba(0,0,0,0.06); border: 1px solid rgba(0,0,0,0.05); }
.form-control, .form-select { padding: 12px 15px; border-radius: 12px; border: 1px solid #dfe4ea; background: #fdfdfd; }
.form-control:focus, .form-select:focus { box-shadow: 0 0 0 3px rgba(44, 62, 80, 0.1); border-color: var(--primary); }
.btn { border-radius: 10px; padding: 10px 20px; font-weight: 600; }
.school-info { font-size: 0.9rem; margin-top: 10px; opacity: 0.9; }
</style>
</head>
<body>
<div id="login-screen">
<div class="login-box">
<img id="login-logo" src="" style="height: 80px; margin-bottom: 20px; object-fit: contain;">
<h4 class="fw-bold mb-1" style="color: var(--primary)">ADMINISTRASI GURU</h4>
<p class="text-muted small mb-4">Sistem Manajemen Terpadu</p>
<div class="mb-3">
<select id="user-select" class="form-select text-center fw-bold text-secondary"><option>Memuat Data...</option></select>
</div>
<div class="mb-4">
<input type="password" id="login-pass" class="form-control text-center" placeholder="Masukkan Password">
</div>
<button onclick="login()" class="btn btn-primary w-100 py-3 rounded-pill shadow-lg" style="background: var(--primary); border:none;" id="btn-login">MASUK DASHBOARD</button>
</div>
</div>
<div id="app" class="wrapper hidden">
<nav id="sidebar">
<div class="profile-box">
<div class="avatar" id="av-box">
<span id="av-text">A</span>
<img id="av-img" src="" class="hidden" style="width:100%; height:100%; object-fit:cover; border-radius:50%;" onerror="this.classList.add('hidden'); document.getElementById('av-text').classList.remove('hidden');">
</div>
<h6 class="fw-bold m-0 mt-2" id="sb-nama">User</h6>
<small style="opacity:0.8" id="sb-role">Guru Mapel</small>
</div>
<ul class="sb-menu">
<li><a onclick="nav('home')" id="m-home"><i class="fas fa-home"></i> Dashboard</a></li>
<div class="role-guru hidden">
<li><a onclick="nav('absen')" id="m-absen"><i class="fas fa-user-check"></i> Absensi</a></li>
<li><a onclick="nav('nilai')" id="m-nilai"><i class="fas fa-graduation-cap"></i> Penilaian</a></li>
<li><a onclick="nav('agenda')" id="m-agenda"><i class="fas fa-calendar-alt"></i> Jurnal Agenda</a></li>
<li><a onclick="nav('wali')" id="m-wali"><i class="fas fa-hands-helping"></i> Bimbingan Wali</a></li>
<li><a href="https://generator-ai.blogspot.com/" target="_blank"><i class="fas fa-robot"></i> AI Generator</a></li>
<li><a href="https://www.yefriharyanto.id/2025/10/perangkat-ajar-deep-learning-.html" target="_blank"><i class="fas fa-file-alt"></i> Perangkat Ajar</a></li>
</div>
<div class="role-admin hidden">
<li class="my-2 border-top border-secondary opacity-25"></li>
<li><a onclick="nav('rekap-wali')" id="m-rekap-wali"><i class="fas fa-file-invoice"></i> Rekap Guru Wali</a></li>
<li><a onclick="nav('users')" id="m-users"><i class="fas fa-users-cog"></i> Manajemen User</a></li>
<li><a onclick="nav('siswa')" id="m-siswa"><i class="fas fa-upload"></i> Import Siswa</a></li>
<li><a onclick="nav('config')" id="m-config"><i class="fas fa-tools"></i> Konfigurasi</a></li>
<li><a onclick="openSpreadsheet()" class="text-success"><i class="fas fa-file-excel"></i> Database Spreadsheet</a></li>
</div>
<li class="mt-4"><a onclick="location.reload()" class="text-danger"><i class="fas fa-power-off"></i> Logout</a></li>
</ul>
</nav>
<div id="content">
<div class="d-flex justify-content-start d-lg-none mb-3">
<button class="btn btn-light shadow-sm" onclick="document.getElementById('sidebar').classList.toggle('active')"><i class="fas fa-bars"></i></button>
</div>
<div id="v-home" class="view">
<div class="welcome-card">
<h2 class="fw-bold">Selamat Datang, <span id="dash-nama">Guru</span>!</h2>
<div class="mt-2 fw-bold text-white opacity-75">SISTEM ADMINISTRASI GURU</div>
<div id="dash-sekolah" class="school-info mt-0">SMP NEGERI 3 KERINCI</div>
<div id="dash-alamat" class="small opacity-75">Jalan Muradi</div>
</div>
<div class="row g-3 mb-4">
<div class="col-6 col-md-4">
<div class="stat-card d-1">
<div class="stat-icon sc-3"><i class="fas fa-layer-group"></i></div>
<div><div class="small text-muted fw-bold">ROMBEL</div><div class="fw-bold h5 mb-0" id="st-rombel">0</div></div>
</div>
</div>
<div class="col-6 col-md-4">
<div class="stat-card d-2">
<div class="stat-icon sc-4"><i class="fas fa-chalkboard-teacher"></i></div>
<div><div class="small text-muted fw-bold">GURU</div><div class="fw-bold h5 mb-0" id="st-guru">0</div></div>
</div>
</div>
<div class="col-6 col-md-4">
<div class="stat-card d-3">
<div class="stat-icon sc-2"><i class="fas fa-users"></i></div>
<div><div class="small text-muted fw-bold">SISWA</div><div class="fw-bold h5 mb-0" id="st-siswa">0</div></div>
</div>
</div>
</div>
<div class="role-guru hidden">
<h5 class="fw-bold mb-3 text-secondary"><i class="fas fa-th-large"></i> Menu Cepat</h5>
<div class="row g-3 mb-4">
<div class="col-6 col-lg-4" onclick="nav('absen')">
<div class="stat-card hover-effect d-1">
<div class="stat-icon sc-2"><i class="fas fa-clipboard-check"></i></div>
<div><div class="small text-muted fw-bold">ABSENSI</div><div class="fw-bold">Input</div></div>
</div>
</div>
<div class="col-6 col-lg-4" onclick="nav('nilai')">
<div class="stat-card hover-effect d-2">
<div class="stat-icon sc-3"><i class="fas fa-star"></i></div>
<div><div class="small text-muted fw-bold">PENILAIAN</div><div class="fw-bold">Leger</div></div>
</div>
</div>
<div class="col-6 col-lg-4" onclick="nav('agenda')">
<div class="stat-card hover-effect d-3">
<div class="stat-icon sc-1"><i class="fas fa-book-open"></i></div>
<div><div class="small text-muted fw-bold">AGENDA</div><div class="fw-bold">Jurnal</div></div>
</div>
</div>
<div class="col-6 col-lg-4" onclick="nav('wali')">
<div class="stat-card hover-effect d-4">
<div class="stat-icon sc-4"><i class="fas fa-user-friends"></i></div>
<div><div class="small text-muted fw-bold">GURU WALI</div><div class="fw-bold">Bimbingan</div></div>
</div>
</div>
<div class="col-6 col-lg-4" onclick="window.open('https://generator-ai.blogspot.com/')">
<div class="stat-card hover-effect d-5">
<div class="stat-icon sc-5"><i class="fas fa-robot"></i></div>
<div><div class="small text-muted fw-bold">AI GENERATOR</div><div class="fw-bold">RPP, LKPD dll</div></div>
</div>
</div>
<div class="col-6 col-lg-4" onclick="window.open('https://www.yefriharyanto.id/2025/10/perangkat-ajar-deep-learning-.html')">
<div class="stat-card hover-effect d-6">
<div class="stat-icon sc-6"><i class="fas fa-file-alt"></i></div>
<div><div class="small text-muted fw-bold">DOWNLOAD</div><div class="fw-bold">Perangkat Ajar</div></div>
</div>
</div>
</div>
<div class="card-u">
<div class="card-h bg-warning bg-opacity-10 text-dark"><i class="fas fa-info-circle"></i> Petunjuk Penggunaan Guru</div>
<div class="card-b">
<ul class="small text-muted mb-0" style="line-height: 1.8;">
<li>Gunakan menu <b>Absensi</b> untuk mencatat kehadiran harian.</li>
<li>Menu <b>Penilaian</b> digunakan untuk input nilai UH/Tugas dan mencetak Leger per KD.</li>
<li>Isi <b>Jurnal Agenda</b> setiap selesai mengajar agar rekapitulasi otomatis tercatat.</li>
<li>Gunakan <b>Bimbingan Wali</b> bagi guru wali.</li>
<li>Gunakan <b>AI Generator</b> untuk bantuan pembuatan RPP, LKPD, Asesmen secara otomatis.</li>
<li>Gunakan <b>Perangkat Ajar</b> untuk mendownload perangkat ajar siap cetak.</li>
</ul>
</div>
</div>
</div>
<div class="role-admin hidden">
<div class="card-u">
<div class="card-h bg-primary text-white" style="background:var(--primary)!important"><i class="fas fa-user-shield"></i> Area Administrator</div>
<div class="card-b">
<p>Halo Administrator. Anda memiliki akses penuh untuk:</p>
<ul class="mb-0">
<li>Mengelola Data Pengguna, Password, dan Mapel Ampuan.</li>
<li>Mengatur Kelas dan Mata Pelajaran Sekolah.</li>
<li>Mengimport Data Siswa secara massal.</li>
<li>Memantau Rekapitulasi Kinerja Guru Wali dalam menangani siswa.</li>
<li>Mengubah Konfigurasi Sekolah dan Tempat Tanda Tangan.</li>
</ul>
</div>
</div>
</div>
</div>
<div id="v-rekap-wali" class="view hidden">
<div class="card-u">
<div class="card-h"><i class="fas fa-file-invoice"></i> Rekapitulasi Laporan Guru Wali</div>
<div class="card-b">
<div class="alert alert-info small"><i class="fas fa-info-circle"></i> Memantau kinerja Guru Wali dalam menangani masalah siswa.</div>
<button onclick="loadRekapWali()" class="btn btn-primary w-100 mb-3" style="background:var(--primary);border:none;"><i class="fas fa-sync"></i> Muat Semua Data</button>
<div class="table-responsive bg-white border rounded p-2 mb-3" id="area-rekap-wali">
<table class="table table-bordered table-sm table-hover mb-0" id="tbl-rekap-wali">
<thead class="table-dark">
<tr><th>Tgl</th><th>Guru Wali</th><th>Siswa</th><th>Kelas</th><th>Masalah</th><th>Solusi</th></tr>
</thead>
<tbody><tr><td colspan="6" class="text-center">Klik Muat Data...</td></tr></tbody>
</table>
</div>
<div class="row g-2">
<div class="col-12"><button onclick="printPDF('tbl-rekap-wali','Rekap_Kinerja_Wali', 'l')" class="btn btn-danger w-100 btn-sm"><i class="fas fa-file-pdf"></i> PDF (Lanskap)</button></div>
</div>
</div>
</div>
</div>
<div id="v-absen" class="view hidden">
<div class="card-u">
<div class="card-h"><i class="fas fa-user-clock"></i> Input Absensi Harian</div>
<div class="card-b">
<div class="row g-2 mb-3">
<div class="col-md-3"><select id="a-mapel" class="form-select mapel-opt-user"></select></div>
<div class="col-md-3"><select id="a-kelas" class="form-select kelas-opt"></select></div>
<div class="col-md-3"><button onclick="loadAbsen()" class="btn btn-primary w-100" style="background:var(--primary);border:none;">Muat Siswa</button></div>
<div class="col-md-3"><button onclick="setAll('Hadir')" class="btn btn-outline-success w-100">Hadir Semua</button></div>
</div>
<div class="table-responsive border rounded bg-white" style="max-height: 400px;">
<table class="table table-sm table-striped-custom mb-0 align-middle" id="tbl-input-absen">
<thead class="table-light sticky-top"><tr><th>Nama Siswa</th><th>Status</th></tr></thead>
<tbody></tbody>
</table>
</div>
<button onclick="saveAbsen()" class="btn btn-warning w-100 mt-3 fw-bold text-white shadow-sm">SIMPAN ABSENSI</button>
</div>
</div>
<div class="card-u">
<div class="card-h"><i class="fas fa-print"></i> Laporan & Rekapitulasi</div>
<div class="card-b">
<div class="row g-2 align-items-end">
<div class="col-md-3"><label class="small fw-bold">Mapel</label><select id="r-mapel" class="form-select mapel-opt-user"></select></div>
<div class="col-md-2"><label class="small fw-bold">Kelas</label><select id="r-kelas" class="form-select kelas-opt"></select></div>
<div class="col-md-2"><label class="small fw-bold">Bulan</label><select id="r-bln" class="form-select"><option value="ALL">SEMUA (1 THN)</option><option value="1">Jan</option><option value="2">Feb</option><option value="3">Mar</option><option value="4">Apr</option><option value="5">Mei</option><option value="6">Jun</option><option value="7">Jul</option><option value="8">Agu</option><option value="9">Sep</option><option value="10">Okt</option><option value="11">Nov</option><option value="12">Des</option></select></div>
<div class="col-md-2"><label class="small fw-bold">Tahun</label><input id="r-thn" class="form-control" value="2026"></div>
<div class="col-md-3"><button onclick="loadRekap()" class="btn btn-info text-white w-100 fw-bold">Tampilkan</button></div>
</div>
<div id="area-rekap" class="hidden mt-4">
<ul class="nav nav-pills mb-3 gap-2" role="tablist">
<li class="nav-item"><button class="nav-link active small" data-bs-toggle="pill" data-bs-target="#tab-rekap" style="background-color: var(--primary);">Rekapitulasi</button></li>
<li class="nav-item"><button class="nav-link small text-dark" data-bs-toggle="pill" data-bs-target="#tab-harian">Harian (Detail)</button></li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="tab-rekap">
<div class="table-responsive bg-white rounded p-2 border"><table class="table table-bordered table-sm text-center mb-0" id="tbl-rekap"></table></div>
<div class="row g-2 mt-2 align-items-center">
<div class="col-12"><button onclick="printPDF('tbl-rekap','Rekap Absensi', 'p')" class="btn btn-danger w-100 btn-sm"><i class="fas fa-file-pdf"></i> PDF</button></div>
</div>
</div>
<div class="tab-pane fade" id="tab-harian">
<div class="table-responsive bg-white rounded p-2 border"><table class="table table-bordered table-sm mb-0" id="tbl-harian"></table></div>
<div class="row g-2 mt-2 align-items-center">
<div class="col-12"><button onclick="printPDF('tbl-harian','Laporan Harian', 'p')" class="btn btn-danger w-100 btn-sm"><i class="fas fa-file-pdf"></i> PDF</button></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="v-nilai" class="view hidden">
<div class="card-u">
<div class="card-h"><i class="fas fa-edit"></i> Input Penilaian</div>
<div class="card-b">
<div class="row g-2 mb-3">
<div class="col-md-3"><select id="n-mapel" class="form-select mapel-opt-user"></select></div>
<div class="col-md-3"><select id="n-kelas" class="form-select kelas-opt"></select></div>
<div class="col-md-4"><input id="n-jns" class="form-control" placeholder="Jenis (Cth: UH 1 / Tugas)"></div>
<div class="col-md-2"><button onclick="loadNilai()" class="btn btn-primary w-100" style="background:var(--primary);border:none;">Muat</button></div>
</div>
<div class="table-responsive border rounded bg-white" style="max-height:400px;">
<table class="table table-sm table-striped-custom align-middle mb-0" id="tbl-input-nilai"></table>
</div>
<button onclick="saveNilai()" class="btn btn-success w-100 mt-3 fw-bold shadow-sm">SIMPAN NILAI</button>
</div>
</div>
<div class="card-u">
<div class="card-h"><i class="fas fa-table"></i> Laporan Nilai</div>
<div class="card-b">
<ul class="nav nav-pills mb-3 gap-2" role="tablist">
<li class="nav-item"><button class="nav-link active small" data-bs-toggle="pill" data-bs-target="#tab-leger" style="background-color: var(--primary);">Leger (Rata-rata)</button></li>
<li class="nav-item"><button class="nav-link small text-dark" data-bs-toggle="pill" data-bs-target="#tab-riwayat">Riwayat Semua Nilai</button></li>
</ul>
<div class="row g-2 mb-3">
<div class="col-4"><select id="l-mapel" class="form-select mapel-opt-user"></select></div>
<div class="col-4"><select id="l-kelas" class="form-select kelas-opt"></select></div>
<div class="col-4"><button onclick="loadReportNilai()" class="btn btn-warning w-100 text-white fw-bold">Tampilkan</button></div>
</div>
<div class="tab-content">
<div class="tab-pane fade show active" id="tab-leger">
<div class="table-responsive bg-white border rounded p-2" id="area-leger"></div>
<div id="btn-print-leger" class="row g-2 mt-2 hidden align-items-center">
<div class="col-12"><button onclick="printPDF('area-leger','Leger Nilai', 'l')" class="btn btn-danger w-100 btn-sm"><i class="fas fa-file-pdf"></i> PDF</button></div>
</div>
</div>
<div class="tab-pane fade" id="tab-riwayat">
<div class="table-responsive bg-white border rounded p-2" id="area-riwayat">
<table class="table table-bordered table-sm mb-0" id="tbl-riwayat">
<thead class="table-dark"><tr><th>Tanggal</th><th>Jenis</th><th>Nama Siswa</th><th>Nilai</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div id="btn-print-riwayat" class="row g-2 mt-2 hidden align-items-center">
<div class="col-12"><button onclick="printPDF('area-riwayat','Riwayat Nilai', 'p')" class="btn btn-danger w-100 btn-sm"><i class="fas fa-file-pdf"></i> PDF</button></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="v-agenda" class="view hidden">
<div class="card-u">
<div class="card-h"><i class="fas fa-calendar-alt"></i> Jurnal Agenda Mengajar</div>
<div class="card-b">
<form id="f-agenda" class="row g-2">
<div class="col-6"><label class="small fw-bold">Tanggal</label><input type="date" id="ag-tgl" class="form-control"></div>
<div class="col-6"><label class="small fw-bold">Jam Ke</label><input id="ag-jam" class="form-control" placeholder="1-2"></div>
<div class="col-6"><label class="small fw-bold">Mapel</label><select id="ag-mapel" class="form-select mapel-opt-user"></select></div>
<div class="col-6"><label class="small fw-bold">Kelas</label><select id="ag-kelas" class="form-select kelas-opt"></select></div>
<div class="col-12"><label class="small fw-bold">Materi</label><textarea id="ag-materi" class="form-control" rows="2"></textarea></div>
<div class="col-4"><label class="small fw-bold">Status</label><select id="ag-sts" class="form-select"><option>Terlaksana</option><option>Tunda</option></select></div>
<div class="col-4"><label class="small fw-bold">Absen</label><input id="ag-absen" class="form-control" placeholder="Nama/Nihil"></div>
<div class="col-4"><label class="small fw-bold">Ket</label><input id="ag-ket" class="form-control" placeholder="-"></div>
<div class="col-12"><button type="button" onclick="saveAgenda()" class="btn btn-primary w-100 mt-2" style="background:var(--primary);border:none;">SIMPAN JURNAL</button></div>
</form>
<hr>
<button onclick="loadAgendaList()" class="btn btn-light border w-100 mb-2">Refresh Tabel</button>
<div class="table-responsive bg-white border rounded p-2" id="area-agenda"><table class="table table-bordered table-sm mb-0" id="tbl-agenda"></table></div>
<div class="row g-2 mt-2 align-items-center">
<div class="col-12"><button onclick="printPDF('area-agenda','Jurnal Mengajar', 'l')" class="btn btn-danger w-100 btn-sm"><i class="fas fa-file-pdf"></i> PDF</button></div>
</div>
</div>
</div>
</div>
<div id="v-wali" class="view hidden">
<div class="row">
<div class="col-md-4 mb-3">
<div class="card-u h-100"><div class="card-h">Input Siswa</div><div class="card-b">
<input id="wb-nama" class="form-control mb-2" placeholder="Nama Siswa">
<input id="wb-kelas" class="form-control mb-2" placeholder="Kelas">
<button onclick="saveSiswaBim()" class="btn btn-success w-100">Simpan</button>
</div></div>
</div>
<div class="col-md-8">
<div class="card-u"><div class="card-h">Catatan Kasus</div><div class="card-b">
<form class="row g-2 mb-3">
<div class="col-4"><input type="date" id="bk-tgl" class="form-control"></div>
<div class="col-4"><select id="bk-siswa" class="form-select" onchange="fillKelasBK()"><option>Pilih Siswa</option></select></div>
<div class="col-4"><input id="bk-kelas-auto" class="form-control" readonly></div>
<div class="col-12">
<select id="bk-jenis" class="form-select">
<option>Akademik</option>
<option>Pribadi & Karakter</option>
<option>Sosial</option>
<option>Pengembangan Keterampilan</option>
<option>Lainnya</option>
</select>
</div>
<div class="col-12"><input id="bk-msk" class="form-control" placeholder="Masalah"></div>
<div class="col-12"><input id="bk-sol" class="form-control" placeholder="Solusi"></div>
<div class="col-12"><button type="button" onclick="saveBK()" class="btn btn-primary w-100" style="background:var(--primary);border:none;">Simpan Catatan</button></div>
</form>
<div class="table-responsive border rounded bg-white p-2" id="area-bk"><table class="table table-bordered table-sm mb-0" id="tbl-bk"></table></div>
<button onclick="printPDF('area-bk','Laporan Bimbingan', 'l')" class="btn btn-danger w-100 mt-2">Cetak PDF</button>
</div></div>
</div>
</div>
</div>
<div id="v-config" class="view hidden">
<div class="card-u"><div class="card-h">Konfigurasi</div><div class="card-b">
<input id="conf-pemerintah" class="form-control mb-2" placeholder="Pemerintah Kab/Kota">
<input id="conf-sekolah" class="form-control mb-2" placeholder="Nama Sekolah">
<input id="conf-alamat" class="form-control mb-2" placeholder="Alamat">
<input id="conf-kepsek" class="form-control mb-2" placeholder="Nama Kepsek">
<input id="conf-nip" class="form-control mb-2" placeholder="NIP Kepsek">
<input id="conf-logo1" class="form-control mb-2" placeholder="URL Logo Kiri">
<input id="conf-logo2" class="form-control mb-2" placeholder="URL Logo Kanan">
<input id="conf-spreadsheet" class="form-control mb-2" placeholder="Link Spreadsheet Database">
<input id="conf-tempat-ttd" class="form-control mb-2" placeholder="Tempat Tanda Tangan Laporan (Cth: Kerinci)">
<button onclick="saveAllConfig()" class="btn btn-success w-100 mt-2">SIMPAN KONFIGURASI</button>
</div></div>
</div>
<div id="v-users" class="view hidden">
<div class="card-u mb-3"><div class="card-h">Tambah Guru</div><div class="card-b">
<div class="row g-2">
<div class="col-md-3"><input id="new-guru" class="form-control" placeholder="Nama"></div>
<div class="col-md-2"><input id="new-nip" class="form-control" placeholder="NIP"></div>
<div class="col-md-3"><input id="new-mapel" class="form-control" placeholder="Mapel (pisah koma)"></div>
<div class="col-md-2"><input type="password" id="new-pass" class="form-control" placeholder="Password Login"></div>
<div class="col-md-2"><button onclick="manage('User','add')" class="btn btn-primary w-100" style="background:var(--primary);border:none;">Tambah</button></div>
</div>
<ul id="list-users" class="list-group mt-3" style="max-height:200px;overflow:auto"></ul>
</div></div>
<div class="row">
<div class="col-md-6"><div class="card-u"><div class="card-h">Kelas</div><div class="card-b">
<div class="input-group mb-2"><input id="new-kelas" class="form-control"><button onclick="manage('Kelas','add')" class="btn btn-success">+</button></div>
<ul id="list-kelas" class="list-group" style="max-height:150px;overflow:auto"></ul>
</div></div></div>
<div class="col-md-6"><div class="card-u"><div class="card-h">Mapel</div><div class="card-b">
<div class="input-group mb-2"><input id="new-m" class="form-control"><button onclick="manage('Mapel','add')" class="btn btn-warning">+</button></div>
<ul id="list-mapel" class="list-group" style="max-height:150px;overflow:auto"></ul>
</div></div></div>
</div>
</div>
<div id="v-siswa" class="view hidden">
<div class="card-u"><div class="card-h">Import Siswa</div><div class="card-b">
<textarea id="bulk-siswa" class="form-control mb-3" rows="10" placeholder="Paste Nama Siswa di sini..."></textarea>
<div class="input-group"><select id="target-kelas" class="form-select kelas-opt"><option>Pilih Kelas</option></select><button onclick="processImport()" class="btn btn-success">IMPORT</button></div>
</div></div>
</div>
<div class="footer-platinum">
Desain oleh <a href="https://www.yefriharyanto.id" target="_blank">Yefri Haryanto</a> | <a href="https://www.yefriharyanto.id" target="_blank">www.yefriharyanto.id</a>
</div>
</div>
</div>
<canvas id="barcode" style="display:none;"></canvas>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
let CUR_USER = null;
let CONFIG = {};
let DATA = {};
let LAST_REPORT_INFO = {};
window.onload = function() {
loading(true);
google.script.run.withSuccessHandler(initSystem).getInitData();
};
function loading(show, msg='Memuat...') {
if(show) Swal.fire({title: msg, allowOutsideClick:false, didOpen:()=>{Swal.showLoading()}});
else Swal.close();
}
function initSystem(d) {
loading(false);
CONFIG = d.config; DATA = d;
let logoUrl = CONFIG['Logo Kanan'] || '';
if(logoUrl) document.getElementById('login-logo').src = logoUrl;
let sel = document.getElementById('user-select');
sel.innerHTML = '<option value="">-- Pilih Nama Anda --</option>';
d.users.forEach(u => {
let opt = document.createElement('option');
opt.value = u[0]; opt.innerText = u[0];
opt.setAttribute('role', u[1]);
opt.setAttribute('mapel', u[2]);
opt.setAttribute('nip', u[4] || '-');
opt.setAttribute('foto', u[5] || '');
// INFO FIX: Atribut password sudah tidak lagi dipasang di HTML
sel.appendChild(opt);
});
document.getElementById('st-siswa').innerText = d.stats.siswa;
document.getElementById('st-guru').innerText = d.stats.guru;
document.getElementById('st-rombel').innerText = d.stats.rombel;
if(CONFIG['Nama Sekolah']) document.getElementById('dash-sekolah').innerText = CONFIG['Nama Sekolah'];
if(CONFIG['Alamat Sekolah']) document.getElementById('dash-alamat').innerText = CONFIG['Alamat Sekolah'];
populateDropdowns();
fillConfig();
renderAdminLists();
}
function populateDropdowns() {
document.querySelectorAll('.kelas-opt').forEach(s => {
s.innerHTML = '<option value="">Pilih Kelas</option>';
DATA.kelas.forEach(k => s.innerHTML += `<option value="${k}">${k}</option>`);
});
document.querySelectorAll('.mapel-opt').forEach(s => {
s.innerHTML = '<option value="">Pilih Mapel</option>';
DATA.mapel.forEach(m => s.innerHTML += `<option value="${m}">${m}</option>`);
});
}
// FIX SERVER-SIDE AUTHENTICATION
function login() {
let sel = document.getElementById('user-select');
let passInput = document.getElementById('login-pass').value.trim();
if(!sel.value) return Swal.fire('Error','Silahkan pilih nama Anda','error');
if(!passInput) return Swal.fire('Error','Silahkan masukkan password','warning');
let btn = document.getElementById('btn-login');
let originalText = btn.innerText;
btn.innerText = "MEMVERIFIKASI...";
btn.disabled = true;
google.script.run
.withSuccessHandler(res => {
btn.innerText = originalText;
btn.disabled = false;
if(res.success) {
let opt = sel.options[sel.selectedIndex];
CUR_USER = {
nama: sel.value,
role: opt.getAttribute('role'),
mapel: opt.getAttribute('mapel'),
nip: opt.getAttribute('nip'),
foto: opt.getAttribute('foto')
};
document.getElementById('sb-nama').innerText = CUR_USER.nama;
document.getElementById('dash-nama').innerText = CUR_USER.nama;
document.getElementById('sb-role').innerText = CUR_USER.role == 'Admin' ? 'Administrator' : 'Guru Mata Pelajaran';
let avText = document.getElementById('av-text');
let avImg = document.getElementById('av-img');
let cekFoto = String(CUR_USER.foto || "").trim();
if(cekFoto && cekFoto !== 'undefined' && cekFoto !== '-' && cekFoto.length > 5) {
avText.classList.add('hidden');
avImg.src = cekFoto;
avImg.classList.remove('hidden');
} else {
avImg.classList.add('hidden');
avText.classList.remove('hidden');
avText.innerText = CUR_USER.nama.charAt(0).toUpperCase();
}
document.getElementById('login-screen').classList.add('hidden');
document.getElementById('app').classList.remove('hidden');
document.querySelectorAll('.role-admin, .role-guru').forEach(e=>e.classList.add('hidden'));
if(CUR_USER.role == 'Admin') document.querySelectorAll('.role-admin').forEach(e=>e.classList.remove('hidden'));
else {
document.querySelectorAll('.role-guru').forEach(e=>e.classList.remove('hidden'));
let myMapels = CUR_USER.mapel.split(',').map(m=>m.trim());
document.querySelectorAll('.mapel-opt-user').forEach(s => {
s.innerHTML = '<option value="">Pilih Mapel</option>';
myMapels.forEach(m => s.innerHTML += `<option value="${m}">${m}</option>`);
});
}
nav('home');
document.getElementById('login-pass').value = '';
} else {
Swal.fire('Ditolak', res.message, 'error');
}
})
.withFailureHandler(err => {
btn.innerText = originalText;
btn.disabled = false;
Swal.fire('Error', 'Gagal menghubungi server.', 'error');
})
.verifikasiLogin(sel.value, passInput);
}
function nav(id) {
document.querySelectorAll('.view').forEach(e=>e.classList.add('hidden'));
document.getElementById('v-'+id).classList.remove('hidden');
document.querySelectorAll('.sb-menu a').forEach(a=>a.classList.remove('active'));
let link = document.getElementById('m-'+id);
if(link) link.classList.add('active');
if(window.innerWidth < 992) document.getElementById('sidebar').classList.remove('active');
if(id === 'wali') refreshSiswaBim();
if(id === 'rekap-wali') loadRekapWali();
if(id === 'agenda') loadAgendaList();
}
// --- LOGIC GURU ---
function loadAbsen(){
loading(true);
google.script.run.withSuccessHandler(d=>{
loading(false);
let h=''; d.forEach(n=>h+=`<tr><td>${n}</td><td><select class='form-select form-select-sm sa-v'><option>Hadir</option><option>Sakit</option><option>Izin</option><option>Alpa</option></select><input type='hidden' class='sa-n' value='${n}'></td></tr>`);
document.querySelector('#tbl-input-absen tbody').innerHTML = h;
}).getSiswa(document.getElementById('a-kelas').value);
}
function setAll(v){ document.querySelectorAll('.sa-v').forEach(e=>e.value=v); }
function saveAbsen(){
let rows = document.querySelectorAll('#tbl-input-absen tbody tr');
if(rows.length === 0) return Swal.fire('Info', 'Muat data siswa terlebih dahulu.', 'info');
let d=[];
rows.forEach(r=>{
let n = r.querySelector('.sa-n').value;
let s = r.querySelector('.sa-v').value;
if(n && s) d.push({nama:n, sts:s});
});
loading(true);
google.script.run.withSuccessHandler(res=>{ loading(false); Swal.fire('Sukses',res,'success'); }).simpanAbsen(document.getElementById('a-kelas').value, document.getElementById('a-mapel').value, CUR_USER.nama, d);
}
function loadRekap(){
let k = document.getElementById('r-kelas').value;
let mSelect = document.getElementById('r-mapel');
let m = mSelect.value;
let b = document.getElementById('r-bln').value;
let t = document.getElementById('r-thn').value;
if(!k || !m) return Swal.fire('Info','Pilih Mapel dan Kelas','warning');
let prd = b === "ALL" ? "TAHUN " + t : `${b}/${t}`;
LAST_REPORT_INFO = { mapel: m, kelas: k, periode: prd };
loading(true);
google.script.run.withSuccessHandler(d=>{
loading(false);
let h1=`<thead class="table-dark"><tr><th>No</th><th>Nama</th><th>S</th><th>I</th><th>A</th><th>H</th><th>%</th></tr></thead><tbody>`;
d.rekap.forEach(r=>h1+=`<tr><td>${r.no}</td><td>${r.nama}</td><td>${r.s}</td><td>${r.i}</td><td>${r.a}</td><td>${r.h}</td><td>${r.pct}%</td></tr>`);
document.getElementById('tbl-rekap').innerHTML = h1+'</tbody>';
let h2=`<thead class="table-dark"><tr><th>Waktu</th><th>Nama</th><th>Status</th></tr></thead><tbody>`;
d.harian.forEach(r=>h2+=`<tr><td>${r.waktu}</td><td>${r.nama}</td><td>${r.status}</td></tr>`);
document.getElementById('tbl-harian').innerHTML = h2+'</tbody>';
document.getElementById('area-rekap').classList.remove('hidden');
}).getLaporanAbsen(k,m,b,t,CUR_USER.nama);
}
function loadNilai(){
loading(true);
google.script.run.withSuccessHandler(d=>{
loading(false);
let h=''; d.forEach(n=>h+=`<tr><td>${n}</td><td><input type='number' class='form-control form-control-sm nv' placeholder='0'><input type='hidden' class='nn' value='${n}'></td></tr>`);
document.getElementById('tbl-input-nilai').innerHTML=h;
}).getSiswa(document.getElementById('n-kelas').value);
}
function saveNilai(){
let d=[]; document.querySelectorAll('#tbl-input-nilai tr').forEach(r=>{ let v=r.querySelector('.nv').value; if(v) d.push({nama:r.querySelector('.nn').value, val:v}); });
loading(true);
google.script.run.withSuccessHandler(r=>{ loading(false); Swal.fire('Sukses',r,'success'); }).simpanNilai(document.getElementById('n-jns').value, document.getElementById('n-mapel').value, document.getElementById('n-kelas').value, CUR_USER.nama, d);
}
function loadReportNilai(){
let k = document.getElementById('l-kelas').value;
let m = document.getElementById('l-mapel').value;
if(!k || !m) return Swal.fire('Info','Pilih Mapel dan Kelas','warning');
LAST_REPORT_INFO = { mapel: m, kelas: k, periode: '-' };
loading(true);
google.script.run.withSuccessHandler(res=>{
if(res.error) return Swal.fire('Info',res.error,'info');
let h=`<table class='table table-bordered table-sm table-striped mb-0' id='tbl-leger-print'><thead class='table-dark text-center'><tr><th>No</th><th>Nama</th>`;
res.headers.forEach(x=>h+=`<th>${x}</th>`);
h+=`<th class="bg-warning text-dark">Total</th><th class="bg-info text-dark">Rata²</th></tr></thead><tbody>`;
res.data.forEach(r=>{
h+=`<tr><td class="text-center">${r.no}</td><td>${r.nama}</td>`;
res.headers.forEach(x=>h+=`<td class="text-center">${r[x]||0}</td>`);
h+=`<td class="fw-bold text-center bg-warning bg-opacity-10">${r.tot}</td><td class="fw-bold text-center bg-info bg-opacity-10">${r.avg}</td></tr>`;
});
document.getElementById('area-leger').innerHTML = h + '</tbody></table>';
document.getElementById('btn-print-leger').classList.remove('hidden');
}).getLeger(k, m, CUR_USER.nama);
google.script.run.withSuccessHandler(d=>{
loading(false);
let h='';
if(d.length === 0) h = '<tr><td colspan="4" class="text-center">Belum ada nilai</td></tr>';
else d.forEach(r => h += `<tr><td>${r[0]}</td><td>${r[1]}</td><td>${r[4]}</td><td>${r[5]}</td></tr>`);
document.querySelector('#tbl-riwayat tbody').innerHTML = h;
document.getElementById('btn-print-riwayat').classList.remove('hidden');
}).getRiwayatNilai(k, m, CUR_USER.nama);
}
function saveAgenda(){
let mapel = document.getElementById('ag-mapel').value;
let d=[document.getElementById('ag-tgl').value, document.getElementById('ag-jam').value, document.getElementById('ag-kelas').value, mapel, document.getElementById('ag-materi').value, document.getElementById('ag-sts').value, document.getElementById('ag-absen').value, document.getElementById('ag-ket').value, CUR_USER.nama];
loading(true);
google.script.run.withSuccessHandler(r=>{ loading(false); Swal.fire('Sukses',r,'success'); loadAgendaList(); }).saveAgenda(d);
}
function loadAgendaList(){
loading(true, 'Mengambil Data...');
LAST_REPORT_INFO = { mapel: 'Semua Mapel', kelas: 'Semua Kelas', periode: '-' };
google.script.run
.withSuccessHandler(d => {
loading(false);
if (!d || d.length === 0) {
document.getElementById('tbl-agenda').innerHTML = `
<thead class='table-dark'><tr><th>Tgl</th><th>Kls</th><th>Mapel</th><th>Materi</th><th>Absen</th></tr></thead>
<tbody><tr><td colspan="5" class="text-center p-3 text-muted">DATA KOSONG<br><small>Belum ada data untuk: ${CUR_USER.nama}</small></td></tr></tbody>`;
return;
}
let h = `<thead class='table-dark'><tr><th>Tgl</th><th>Kls</th><th>Mapel</th><th>Materi</th><th>Absen</th></tr></thead><tbody>`;
d.forEach(r => {
h += `<tr><td>${r[0]}</td><td>${r[2]}</td><td>${r[3]}</td><td>${r[4]}</td><td>${r[6]}</td></tr>`;
});
document.getElementById('tbl-agenda').innerHTML = h + '</tbody>';
})
.withFailureHandler(e => { loading(false); Swal.fire('Error', e.message, 'error'); })
.getAgenda(CUR_USER.nama);
}
// --- WALI & CONFIG ---
function saveSiswaBim(){ loading(true); google.script.run.withSuccessHandler(()=>{loading(false); Swal.fire('Sukses','Disimpan','success'); refreshSiswaBim()}).simpanSiswaBimbingan(document.getElementById('wb-nama').value, document.getElementById('wb-kelas').value, CUR_USER.nama); }
function refreshSiswaBim(){
document.getElementById('tbl-bk').innerHTML = '<tbody><tr><td class="text-center text-muted">Memuat data bimbingan...</td></tr></tbody>';
google.script.run.withSuccessHandler(d=>{
let s=document.getElementById('bk-siswa');
s.innerHTML='<option>Pilih Siswa</option>';
d.forEach(x=>{
let o=document.createElement('option');o.value=x.nama;o.innerText=x.nama;o.setAttribute('k',x.kelas);s.appendChild(o);
});
}).getSiswaBimbingan(CUR_USER.nama);
google.script.run.withSuccessHandler(d=>{
if(d.length === 0) {
document.getElementById('tbl-bk').innerHTML='<div class="p-3 text-center text-muted small">Belum ada riwayat bimbingan</div>';
} else {
let h='';
d.forEach(r=>h+=`<tr><td>${r[0]}</td><td>${r[1]}</td><td>${r[3]}</td><td>${r[4]}</td><td>${r[5]}</td></tr>`);
document.getElementById('tbl-bk').innerHTML='<thead class="table-dark"><tr><th>Tgl</th><th>Nama</th><th>Jenis</th><th>Masalah</th><th>Solusi</th></tr></thead><tbody>'+h+'</tbody>';
}
}).getRiwayatBimbingan(CUR_USER.nama);
}
function loadRekapWali() {
loading(true);
google.script.run.withSuccessHandler(d => {
loading(false);
if (d.length === 0) {
document.getElementById('tbl-rekap-wali').innerHTML = '<tbody><tr><td colspan="6" class="text-center p-3 text-muted">Belum ada data bimbingan dari guru wali manapun.</td></tr></tbody>';
return;
}
let h = `<thead class="table-dark"><tr><th>Tgl</th><th>Guru Wali</th><th>Siswa</th><th>Kelas</th><th>Masalah</th><th>Solusi</th></tr></thead><tbody>`;
d.forEach(r => {
h += `<tr><td>${r[0]}</td><td class="fw-bold text-primary">${r[6]}</td><td>${r[1]}</td><td>${r[2]}</td><td>${r[4]}</td><td>${r[5]}</td></tr>`;
});
document.getElementById('tbl-rekap-wali').innerHTML = h + '</tbody>';
}).getAllBimbingan();
}
function fillKelasBK(){ let s=document.getElementById('bk-siswa'); document.getElementById('bk-kelas-auto').value=s.options[s.selectedIndex].getAttribute('k'); }
function saveBK(){ let d=[document.getElementById('bk-tgl').value, document.getElementById('bk-siswa').value, document.getElementById('bk-kelas-auto').value, document.getElementById('bk-jenis').value, document.getElementById('bk-msk').value, document.getElementById('bk-sol').value, CUR_USER.nama]; loading(true); google.script.run.withSuccessHandler(()=>{loading(false); Swal.fire('Sukses','Disimpan','success'); refreshSiswaBim();}).simpanCatatanBimbingan(d); }
function fillConfig(){
['conf-pemerintah', 'conf-sekolah','conf-alamat','conf-kepsek','conf-nip','conf-logo1','conf-logo2', 'conf-spreadsheet', 'conf-tempat-ttd'].forEach((id,i)=>{
let keys = ['Nama Pemerintah', 'Nama Sekolah','Alamat Sekolah','Nama Kepala Sekolah','NIP Kepala Sekolah','Logo Kiri','Logo Kanan', 'Link Spreadsheet', 'Tempat Tanda Tangan'];
if(document.getElementById(id)) document.getElementById(id).value = CONFIG[keys[i]] || '';
});
}
function saveAllConfig(){
loading(true);
let p=[];
let keys = ['Nama Pemerintah', 'Nama Sekolah','Alamat Sekolah','Nama Kepala Sekolah','NIP Kepala Sekolah','Logo Kiri','Logo Kanan', 'Link Spreadsheet', 'Tempat Tanda Tangan'];
let ids = ['conf-pemerintah', 'conf-sekolah','conf-alamat','conf-kepsek','conf-nip','conf-logo1','conf-logo2', 'conf-spreadsheet', 'conf-tempat-ttd'];
ids.forEach((id,i)=> p.push(new Promise(r=>google.script.run.withSuccessHandler(r).saveConfig(keys[i],document.getElementById(id).value))) );
Promise.all(p).then(() => { loading(false); Swal.fire('Sukses','Konfigurasi Disimpan','success'); });
}
function renderAdminLists(){
let uL=document.getElementById('list-users'); uL.innerHTML='';
DATA.users.forEach(u=>uL.innerHTML+=`<li class='list-group-item d-flex justify-content-between p-1 small'>${u[0]} <span class='text-muted'>(${u[4]})</span> <button class='btn btn-sm text-danger' onclick="manage('User','delete','${u[0]}')"><i class="fas fa-trash"></i></button></li>`);
let kL=document.getElementById('list-kelas'); kL.innerHTML='';
DATA.kelas.forEach(k=>kL.innerHTML+=`<li class='list-group-item d-flex justify-content-between p-1 small'>${k} <button class='btn btn-sm text-danger' onclick="manage('Kelas','delete','${k}')"><i class="fas fa-trash"></i></button></li>`);
let mL=document.getElementById('list-mapel'); mL.innerHTML='';
DATA.mapel.forEach(m=>mL.innerHTML+=`<li class='list-group-item d-flex justify-content-between p-1 small'>${m} <button class='btn btn-sm text-danger' onclick="manage('Mapel','delete','${m}')"><i class="fas fa-trash"></i></button></li>`);
}
function manage(t,a,v1){
let val = v1 || (t=='User'?document.getElementById('new-guru').value: (t=='Kelas'?document.getElementById('new-kelas').value:document.getElementById('new-m').value));
let val2 = t=='User'?document.getElementById('new-mapel').value:'';
let val3 = t=='User'?document.getElementById('new-nip').value:'';
let val4 = t=='User'?document.getElementById('new-pass').value:'';
if(!val) return;
loading(true);
google.script.run.withSuccessHandler(res=>{
loading(false); Swal.fire(res.includes('Sudah')?'Warning':'Sukses',res,res.includes('Sudah')?'warning':'success');
if(t=='User') { document.getElementById('new-guru').value=''; document.getElementById('new-mapel').value=''; document.getElementById('new-nip').value=''; document.getElementById('new-pass').value='';}
else if(t=='Kelas') document.getElementById('new-kelas').value=''; else document.getElementById('new-m').value='';
google.script.run.withSuccessHandler(d=>{ DATA=d; renderAdminLists(); populateDropdowns(); }).getInitData();
}).manageItem(t,a,val,val2,val3,val4);
}
function processImport(){
let arr = document.getElementById('bulk-siswa').value.split('\n');
loading(true);
google.script.run.withSuccessHandler(r=>{ loading(false); Swal.fire('Sukses',r,'success'); }).importSiswa(arr, document.getElementById('target-kelas').value);
}
function openSpreadsheet() {
let link = CONFIG['Link Spreadsheet'];
if(link) window.open(link, '_blank');
else Swal.fire('Info', 'Link Spreadsheet belum diatur di Konfigurasi.', 'info');
}
// --- PDF GENERATOR ---
function createPDFDoc(elemId, title) {
const { jsPDF } = window.jspdf;
let isLand = (title.includes('Leger') || title.includes('Jurnal') || title.includes('Rekap_Kinerja') || title.includes('Bimbingan')) ? true : false;
const doc = new jsPDF(isLand ? 'l' : 'p', 'mm', [215, 330]);
const pageWidth = doc.internal.pageSize.getWidth();
const logo1 = CONFIG['Logo Kiri'];
const logo2 = CONFIG['Logo Kanan'];
const sekolah = (CONFIG['Nama Sekolah'] || 'NAMA SEKOLAH').toUpperCase();
const pemerintah = (CONFIG['Nama Pemerintah'] || 'PEMERINTAH KABUPATEN').toUpperCase();
const alamat = CONFIG['Alamat Sekolah'] || 'Alamat Sekolah';
const kepsek = CONFIG['Nama Kepala Sekolah'] || '........';
const nipKepsek = CONFIG['NIP Kepala Sekolah'] || '........';
const tempatTtd = CONFIG['Tempat Tanda Tangan'] || 'Kerinci';
const centerX = pageWidth / 2;
try { if(logo1) doc.addImage(logo1, 'JPEG', 25, 10, 20, 20); } catch(e){}
try { if(logo2) doc.addImage(logo2, 'JPEG', pageWidth-45, 10, 20, 20); } catch(e){}
doc.setFont("helvetica", "bold"); doc.setFontSize(14);
doc.text(pemerintah, centerX, 15, {align:'center'});
doc.text("DINAS PENDIDIKAN", centerX, 21, {align:'center'});
doc.setFontSize(16);
doc.text(sekolah, centerX, 28, {align:'center'});
doc.setFont("helvetica", "normal"); doc.setFontSize(9);
doc.text(alamat, centerX, 34, {align:'center'});
doc.setLineWidth(0.5); doc.line(10, 38, pageWidth-10, 38);
doc.setLineWidth(0.2); doc.line(10, 39, pageWidth-10, 39);
let teacherName = CUR_USER.nama;
let teacherNIP = CUR_USER.nip;
let cleanSchool = (CONFIG['Nama Sekolah'] || 'SEKOLAH').replace(/[^A-Z0-9]/g, '').slice(0, 15);
let bcCode = `${cleanSchool}-${Date.now().toString().slice(-5)}`;
JsBarcode("#barcode", bcCode, {format: "CODE128", displayValue: false, height: 30});
let canvas = document.getElementById("barcode");
let bcImg = canvas.toDataURL("image/jpeg");
doc.addImage(bcImg, 'JPEG', pageWidth-45, 42, 35, 10);
doc.setFontSize(7); doc.text(bcCode, pageWidth-27, 54, {align:'center'});
doc.setFont("helvetica", "bold"); doc.setFontSize(12);
doc.text(title.replace(/_/g, ' ').toUpperCase(), centerX, 48, {align:'center'});
doc.setFontSize(10); doc.setFont("helvetica", "normal");
if(!title.includes('Rekap_Kinerja')) {
doc.text(`Guru: ${teacherName}`, 15, 55);
if(LAST_REPORT_INFO.mapel && LAST_REPORT_INFO.mapel !== 'Semua Mapel') {
doc.text(`Mata Pelajaran: ${LAST_REPORT_INFO.mapel}`, 15, 60);
}
if(LAST_REPORT_INFO.kelas && LAST_REPORT_INFO.kelas !== 'Semua Kelas') {
let yPos = (LAST_REPORT_INFO.mapel && LAST_REPORT_INFO.mapel !== 'Semua Mapel') ? 65 : 60;
doc.text(`Kelas: ${LAST_REPORT_INFO.kelas}`, 15, yPos);
}
}
let elem = document.getElementById(elemId);
if(elem.tagName !== 'TABLE') elem = elem.querySelector('table');
doc.autoTable({
html: elem,
startY: 70,
theme: 'grid',
styles: { fontSize: 8, cellPadding: 2 },
headStyles: { fillColor: [44, 62, 80], textColor: 255 }
});
let y = doc.lastAutoTable.finalY + 15;
if(y > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); y = 20; }
const d = new Date();
const months = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"];
let date = `${d.getDate()} ${months[d.getMonth()]} ${d.getFullYear()}`;
let xRight = pageWidth - 70;
if (!title.includes('Rekap_Kinerja')) {
doc.text("Mengetahui,", 20, y);
doc.text("Kepala Sekolah", 20, y+5);
doc.text(kepsek, 20, y+25);
doc.text("NIP. "+nipKepsek, 20, y+30);
}
doc.text(`${tempatTtd}, ${date}`, xRight, y);
if (title.includes('Rekap_Kinerja')) {
doc.text("Kepala Sekolah", xRight, y+5);
doc.text(kepsek, xRight, y+25);
doc.text("NIP. "+nipKepsek, xRight, y+30);
} else {
let labelJabatan = "Guru Mata Pelajaran";
if (title.includes('Laporan Bimbingan')) {
labelJabatan = "Guru Bimbingan Konseling";
}
doc.text(labelJabatan, xRight, y+5);
doc.text(teacherName, xRight, y+25);
doc.text("NIP. "+teacherNIP, xRight, y+30);
}
return doc;
}
function printPDF(elemId, title, ori) {
const doc = createPDFDoc(elemId, title);
doc.save(`Laporan_${title}.pdf`);
}
</script>
<script>
(function(){
var _h1 = "\x2e\x66\x6f\x6f\x74\x65\x72\x2d\x70\x6c\x61\x74\x69\x6e\x75\x6d";
var _h2 = "\x77\x77\x77\x2e\x79\x65\x66\x72\x69\x68\x61\x72\x79\x61\x6e\x74\x6f\x2e\x69\x64";
var _h3 = "\x59\x65\x66\x72\x69\x20\x48\x61\x72\x79\x61\x6e\x74\x6f";
function _0xSecure() {
var _f = document.querySelector(_h1);
if (!_f || !_f.innerHTML.includes(_h2) || !_f.innerHTML.includes(_h3)) {
document.body.innerHTML = '';
document.body.style.backgroundColor = '#000';
document.body.style.display = 'flex';
document.body.style.justifyContent = 'center';
document.body.style.alignItems = 'center';
document.body.style.height = '100vh';
document.body.innerHTML =
'<div style="text-align:center;color:red;font-family:monospace;">' +
'<h1 style="font-size:3rem;margin-bottom:10px;">SYSTEM CORRUPTED</h1>' +
'<h2 style="border:2px solid red;padding:10px;display:inline-block;">ERROR CODE: 0xCOPYRIGHT_FAIL</h2>' +
'<p style="color:white;margin-top:20px;">Integritas aplikasi terganggu.</p>' +
'<p style="color:gray;">Kembalikan footer asli: "Desain oleh Yefri Haryanto"</p>' +
'</div>';
throw new Error("Security Violation: Copyright modified.");
}
}
setInterval(_0xSecure, 1500);
window.addEventListener('load', _0xSecure);
})();
</script>
</body>
</html>
