Prompt dan Source Code Aplikasi Administrasi Guru EdAdmin Pro Fitur Lengkap dan Scan Absensi QRCode
Prompt Aplikasi Administrasi Guru EdAdmin Pro Lengkap dengan fitur Scan dan Source Codenya
Di era digitalisasi pendidikan, guru dituntut untuk bekerja lebih cepat, akurat, dan terstruktur. Mengelola data siswa, jadwal, absensi harian, jurnal mengajar, hingga bimbingan konseling secara manual tentu memakan banyak waktu dan tenaga. Inilah mengapa EdAdmin Pro hadir sebagai solusi web aplikasi administrasi guru yang komprehensif, premium, dan mudah digunakan.
Menariknya, Anda tidak perlu menyewa server (hosting) berbayar untuk menjalankan aplikasi ini. Kita akan memanfaatkan kolaborasi dua platform gratis dari Google: Google Apps Script dan Blogger.
Apa itu Google Apps Script dan Blogger? Serta Apa Manfaatnya?
1. Google Apps Script (Backend & Database)
Google Apps Script adalah platform *scripting* berbasis cloud dari Google yang memungkinkan kita membuat aplikasi web ringan dan mengotomatiskan layanan Google Workspace (seperti Google Sheets). Dalam aplikasi EdAdmin Pro, Google Sheets akan disulap menjadi Database yang gratis, tidak ada batasan masa aktif, dan mudah diedit kapan saja.
Manfaatnya: 100% Gratis, Serverless (tidak perlu mengurus server), sangat stabil, dan data aman tersimpan di Google Drive pribadi Anda.
2. Blogger (Frontend / Antarmuka Pengguna)
Blogger bukan hanya sekadar platform untuk menulis artikel blog. Dengan teknik Single Page Application (SPA), kita bisa mengubah tema/XML Blogger menjadi antarmuka (User Interface) aplikasi web yang interaktif dan profesional.
Manfaatnya: Hosting gratis selamanya dari Google, bandwidth tidak terbatas, memiliki sertifikat keamanan (HTTPS) otomatis, dan sangat responsif saat diakses melalui *smartphone* maupun laptop.
Prompt Lengkap untuk Membuat EdAdmin Pro di Gemini AI
Jika Anda ingin membuat ulang, memodifikasi, atau mempelajari cara kerja EdAdmin Pro menggunakan bantuan AI seperti Google Gemini (Sangat Disarankan), Anda bisa menggunakan Prompt Engineer detail di bawah ini:
Aplikasi harus memiliki desain UI/UX yang profesional, modern, menggunakan framework Tailwind CSS, Font Awesome, jsPDF, dan QRCode.js. Tema warna menggunakan Biru Dongker dan Putih. Fitur wajib meliputi:
1. Dashboard Statistik Interaktif dengan Chart.js.
2. Kelola Data Siswa (CRUD manual & Import Excel multi-data).
3. Jadwal Mengajar & Kelola Mata Pelajaran.
4. Input Absensi Harian (Input Manual dan Fitur Scanner QR Code via Kamera HP/Laptop dengan feedback live).
5. Input Penilaian Akademik per KD/Tugas.
6. Jurnal/Agenda Mengajar Harian.
7. Bimbingan Wali Kelas (Input masalah dan tindak lanjut).
8. Pusat Cetak Laporan PDF (Rekap & Riwayat) untuk Absensi, Nilai, Agenda, dan BK.
9. Fitur Cetak Kartu Pelajar Premium: Format Kertas A4 Portrait, berisi 3 deret presisi Kiri (Depan) dan Kanan (Belakang). Desain formal kotak solid biru, tabel data, Barcode NISN di belakang, QR Code besar di depan. Ada fitur Live Preview.
10. Menu Pengaturan Kop Surat, Identitas Guru, Kepsek, dan Link Logo Sekolah.
Berikan kode Google Apps Script (Code.gs) yang optimal dan kode XML Blogger (tema kosong tanpa widget bawaan) yang menyatu dengan JavaScript."
Source Code EdAdmin Pro Lengkap
Berikut adalah panduan instalasi dan Source Code final dari EdAdmin Pro yang sudah siap pakai (Ready to Deploy).
1. Kode Backend (Google Apps Script)
Buka Google Apps Script, buat project baru, hapus semua kode bawaan, dan paste kode di bawah ini. Jangan lupa lakukan Deploy sebagai Aplikasi Web (Web App) dengan akses "Siapa saja / Anyone".
// =========================================================================
// BACKEND API - EDADMIN PRO (FINAL VERSION)
// =========================================================================
const SHEETS = {
SISWA: { name: "Data_Siswa", headers: ["ID", "NISN", "Nama Siswa", "Kelas"] },
MAPEL: { name: "Mapel", headers: ["ID", "Nama Mapel", "Semester", "Tahun Ajaran"] },
JADWAL: { name: "Jadwal", headers: ["ID", "Hari", "Jam", "Kelas", "Mapel"] },
ABSENSI: { name: "Log_Absensi", headers: ["Waktu", "Kelas", "Mapel", "ID_Siswa", "Nama Siswa", "Status", "Nama Guru", "Bulan", "Tahun", "Tanggal"] },
NILAI: { name: "Data_Nilai", headers: ["Waktu", "Jenis", "Mapel", "Kelas", "ID_Siswa", "Nama Siswa", "Nilai", "Nama Guru"] },
AGENDA: { name: "Jurnal_Mengajar", headers: ["Tanggal", "Jam", "Kelas", "Mapel", "Materi", "Status", "Absen Siswa", "Ket", "Nama Guru"] },
SISWA_BIMBINGAN: { name: "Siswa_Bimbingan", headers: ["ID", "Nama Siswa", "Kelas"] },
BIMBINGAN: { name: "Bimbingan_Wali", headers: ["Tanggal", "Nama Siswa", "Kelas", "Jenis", "Kasus", "Tindak Lanjut", "Guru Wali"] },
CONFIG: { name: "Pengaturan", headers: ["Key", "Value"] }
};
function initDatabase() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
for (const key in SHEETS) {
const sInfo = SHEETS[key];
let sheet = ss.getSheetByName(sInfo.name);
if (!sheet) {
sheet = ss.insertSheet(sInfo.name);
sheet.appendRow(sInfo.headers);
sheet.getRange(1, 1, 1, sInfo.headers.length).setFontWeight("bold").setBackground("#1e3a8a").setFontColor("white");
sheet.setFrozenRows(1);
}
}
}
function doGet(e) {
try {
initDatabase();
if (e.parameter.action === "all") {
return responseJson("success", "Berhasil sinkronisasi database", getAllData());
}
return responseJson("error", "Parameter GET tidak valid", null);
} catch (err) {
return responseJson("error", err.message, null);
}
}
function doPost(e) {
try {
initDatabase();
let req = {};
if(e.postData && e.postData.contents) {
req = JSON.parse(e.postData.contents);
} else {
throw new Error("Tidak ada data payload yang diterima.");
}
const action = req.action;
const payload = req.payload || {};
let result = null;
if (action === "saveSiswa") result = saveData(SHEETS.SISWA.name, payload);
else if (action === "saveSiswaBulk") result = saveBulkData(SHEETS.SISWA.name, payload);
else if (action === "saveMapel") result = saveData(SHEETS.MAPEL.name, payload);
else if (action === "saveJadwal") result = saveData(SHEETS.JADWAL.name, payload);
else if (action === "saveAbsensi") result = processAbsensiManual(payload);
else if (action === "scanAbsen") result = processScanAbsen(payload);
else if (action === "savePenilaian") result = processPenilaian(payload);
else if (action === "saveAgenda") result = saveData(SHEETS.AGENDA.name, payload);
else if (action === "saveSiswaBimbingan") result = saveData(SHEETS.SISWA_BIMBINGAN.name, payload);
else if (action === "saveBimbingan") result = saveData(SHEETS.BIMBINGAN.name, payload);
else if (action === "saveConfig") result = saveConfig(payload);
else throw new Error("Aksi tidak dikenali");
return responseJson("success", "Berhasil menyimpan", result);
} catch (err) {
return responseJson("error", err.message, null);
}
}
function responseJson(status, message, data) {
return ContentService.createTextOutput(JSON.stringify({ status: status, message: message, data: data }))
.setMimeType(ContentService.MimeType.JSON);
}
function getAllData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let db = {};
for (const key in SHEETS) {
const sInfo = SHEETS[key];
const sheet = ss.getSheetByName(sInfo.name);
if (!sheet) continue;
const data = sheet.getDataRange().getValues();
let dbKey = sInfo.name.toLowerCase();
if (dbKey === 'pengaturan') dbKey = 'config_raw';
if (data.length <= 1) {
db[dbKey] = [];
} else {
const headers = data[0];
const rows = [];
for (let i = 1; i < data.length; i++) {
let obj = {};
for (let j = 0; j < headers.length; j++) {
obj[headers[j]] = data[i][j] !== undefined ? data[i][j] : "";
}
rows.push(obj);
}
db[dbKey] = rows;
}
}
let configObj = {};
if (db.config_raw) {
db.config_raw.forEach(row => { configObj[row.Key] = row.Value; });
delete db.config_raw;
}
db.config = configObj;
return db;
}
function saveData(sheetName, payload) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
const rowData = headers.map(h => payload[h] !== undefined ? payload[h] : "");
sheet.appendRow(rowData);
return "Tersimpan";
}
function saveBulkData(sheetName, payloadArray) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
const rowsData = payloadArray.map(payload => headers.map(h => payload[h] !== undefined ? payload[h] : ""));
if (rowsData.length > 0) sheet.getRange(sheet.getLastRow() + 1, 1, rowsData.length, headers.length).setValues(rowsData);
return "Bulk tersimpan";
}
function processAbsensiManual(payload) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEETS.ABSENSI.name);
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
let [thn, bln] = payload.tanggal.split('-');
const existingData = sheet.getDataRange().getValues();
payload.records.forEach(rec => {
let rowIdx = -1;
for(let i = 1; i < existingData.length; i++) {
let r = existingData[i];
if(r[0] === payload.tanggal && r[1] === payload.kelas && r[2] === payload.mapel && String(r[3]) === String(rec.ID_Siswa)) {
rowIdx = i + 1; break;
}
}
if(rowIdx > -1) {
sheet.getRange(rowIdx, headers.indexOf("Status") + 1).setValue(rec.Status);
} else {
let newRec = { Waktu: payload.tanggal, Tanggal: payload.tanggal, Kelas: payload.kelas, Mapel: payload.mapel, ID_Siswa: rec.ID_Siswa, "Nama Siswa": rec.Nama_Siswa, Status: rec.Status, "Nama Guru": payload.guru, Bulan: bln, Tahun: thn };
sheet.appendRow(headers.map(h => newRec[h] !== undefined ? newRec[h] : ""));
}
});
return "Absensi manual tersimpan";
}
// BUG 1: FIX DOUBLE SCAN
function processScanAbsen(payload) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheetSiswa = ss.getSheetByName(SHEETS.SISWA.name);
const sheetAbsen = ss.getSheetByName(SHEETS.ABSENSI.name);
const dataSiswa = sheetSiswa.getDataRange().getValues();
let namaSiswa = null;
for (let i = 1; i < dataSiswa.length; i++) {
if (String(dataSiswa[i][1]) === String(payload.nisn) || String(dataSiswa[i][0]) === String(payload.nisn)) {
namaSiswa = dataSiswa[i][2]; break;
}
}
if(!namaSiswa) throw new Error("Gagal: NISN tidak ada di database!");
const existingData = sheetAbsen.getDataRange().getValues();
const headers = sheetAbsen.getRange(1, 1, 1, sheetAbsen.getLastColumn()).getValues()[0];
let [thn, bln] = payload.tanggal.split('-');
let rowIdx = -1;
for(let i = 1; i < existingData.length; i++) {
if(existingData[i][0] === payload.tanggal && existingData[i][1] === payload.kelas && existingData[i][2] === payload.mapel && String(existingData[i][3]) === String(payload.nisn)) {
rowIdx = i + 1; break;
}
}
if(rowIdx > -1) {
sheetAbsen.getRange(rowIdx, headers.indexOf("Status") + 1).setValue("Hadir");
} else {
let newRec = { Waktu: payload.tanggal, Tanggal: payload.tanggal, Kelas: payload.kelas, Mapel: payload.mapel, ID_Siswa: payload.nisn, "Nama Siswa": namaSiswa, Status: "Hadir", "Nama Guru": "Scanner", Bulan: bln, Tahun: thn };
sheetAbsen.appendRow(headers.map(h => newRec[h] !== undefined ? newRec[h] : ""));
}
return `BERHASIL: Absen ${namaSiswa} Tercatat`;
}
// BUG 2: FIX TIMEZONE WIB INDONESIA
function processPenilaian(payload) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEETS.NILAI.name);
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
let tgl = Utilities.formatDate(new Date(), "Asia/Jakarta", "yyyy-MM-dd");
const existingData = sheet.getDataRange().getValues();
payload.records.forEach(rec => {
let rowIdx = -1;
for(let i = 1; i < existingData.length; i++) {
if(existingData[i][1] === payload.jenis && existingData[i][2] === payload.mapel && existingData[i][3] === payload.kelas && String(existingData[i][4]) === String(rec.ID_Siswa)) {
rowIdx = i + 1; break;
}
}
if(rowIdx > -1) {
sheet.getRange(rowIdx, headers.indexOf("Nilai") + 1).setValue(rec.Nilai);
} else {
let newRec = { Waktu: tgl, Jenis: payload.jenis, Mapel: payload.mapel, Kelas: payload.kelas, ID_Siswa: rec.ID_Siswa, "Nama Siswa": rec.Nama_Siswa, Nilai: rec.Nilai, "Nama Guru": payload.guru };
sheet.appendRow(headers.map(h => newRec[h] !== undefined ? newRec[h] : ""));
}
});
return "Nilai disimpan";
}
function saveConfig(payload) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEETS.CONFIG.name);
const lastRow = sheet.getLastRow();
if (lastRow > 1) {
sheet.getRange(2, 1, lastRow - 1, 2).clearContent();
}
let rows = [];
for (const key in payload) { rows.push([key, payload[key]]); }
if(rows.length > 0) sheet.getRange(2, 1, rows.length, 2).setValues(rows);
return "Pengaturan tersimpan";
}
2. Kode Frontend (Tema Blogger XML)
Untuk menghindari error Parsing XML saat menyimpan tema di Blogger, kami telah menyiapkan file XML utuh yang bisa Anda unduh langsung. Silakan klik tombol di bawah ini untuk mengunduh tema EdAdmin Pro, lalu cukup buka file-nya di Notepad/Notepad++, copy semua isinya, dan paste di menu Tema > Edit HTML Blogger Anda (timpa/hapus semua kode bawaan sebelumnya).
Download File Template XML
Download Tema BloggerLink Download Langsung via Google Drive. Aman dan bebas virus.
Jangan Lupa: Setelah mem-paste kode XML ke dalam Blogger, cari teks const GAS_URL = "URL_APPS_SCRIPT_ANDA_DISINI"; di baris paling bawah, lalu ubah URL tersebut dengan Link Deployment Apps Script Anda agar sistem bisa saling terhubung.
