🌟 Mengenal SIAKAD Platinum
SIAKAD Platinum adalah Sistem Informasi Akademik revolusioner khusus untuk Guru Kelas. Sistem ini dibangun menggunakan arsitektur tanpa server (Serverless) dengan menggabungkan kehebatan dua ekosistem gratis dari Google: Google Apps Script dan Blogger.
💡 Apa itu Google Apps Script & Blogger?
Google Apps Script (GAS) adalah bahasa pemrograman berbasis cloud dari Google. Dalam aplikasi ini, GAS bertugas menyulap Google Spreadsheet biasa menjadi sebuah Database dan API yang hidup dan canggih.
Sedangkan Blogger, yang biasanya hanya digunakan untuk menulis artikel, kita retas menjadi sebuah antarmuka aplikasi (Frontend) yang premium menggunakan React JS dan Tailwind CSS tanpa memerlukan hosting berbayar.
Manfaat Utama Aplikasi Ini:
- ✅ 100% Gratis Selamanya: Tidak perlu sewa hosting atau beli nama domain bulanan.
- ✅ Keamanan Database Tingkat Dewa: Data Anda disimpan langsung di infrastruktur Google Drive milik Anda sendiri.
- ✅ Penyimpanan Tak Terbatas: Anda bisa memasukkan ribuan data siswa dan nilai karena berbasis sel Spreadsheet.
- ✅ Responsif & Mobile Friendly: Tampilan premium yang menyesuaikan diri dengan sempurna saat dibuka di HP, Tablet, maupun Laptop.
- ✅ Pusat Cetak Profesional: Laporan dilengkapi format Kop Surat resmi dan Barcode Keaslian dokumen.
🛠️ Opsi 1: Buat Mandiri (Dengan Bantuan AI)
Jika Anda adalah seorang pengembang yang ingin mengeksplorasi kode ini secara mandiri menggunakan AI (seperti ChatGPT, Gemini, atau Claude), cukup gunakan Prompt Master super lengkap di bawah ini:
Bertindaklah sebagai Senior Software Engineer. Buatkan saya aplikasi SIAKAD (Sistem Informasi Akademik) tingkat Platinum untuk Guru Kelas. Arsitektur Serverless yang digunakan: 1. Backend: Google Apps Script (GAS) yang menjadikan Google Sheets sebagai Database (API). 2. Frontend: Tema XML Blogger tunggal dengan React JS dan Tailwind CSS via CDN. Spesifikasi Wajib Backend (GAS): - Wajib ada fitur AUTO-HEAL: Standarisasi Header untuk mencegah Bug Key Mismatch pada Spreadsheet. - Wajib menggunakan algoritma BATCHING Array Storage agar eksekusi data massal (absensi/upload siswa) tidak terkena Limit Timeout. - Gunakan Validasi Token Base64 pada setiap request POST untuk keamanan login. - Spreadsheet harus berisi Sheet: Students, Attendance, Grades, Journals, Subjects, dan Profile. Spesifikasi Wajib Frontend (Blogger): - Semua kode UI (React) wajib dibungkus tag CDATA di dalam 1 file tema XML. - Layout: Platinum design, Responsive/Mobile-friendly (Sidebar berubah menjadi Drawer otomatis di layar HP), UI tidak boleh menjorok ke kanan di layar Desktop. - Fitur Utama: Dashboard dengan Akses Cepat 6 Tombol (Data Siswa, Absensi, Nilai, Jurnal, Pusat Cetak, Setting). - Jurnal Guru harus mendeteksi secara dinamis perubahan nama kolom dari backend. - Pusat Cetak: Fitur print dengan layout Landscape khusus, dilengkapi Kop Surat (Logo Kiri/Kanan) dan Barcode 1D Keaslian menggunakan API TEC-IT.
🚀 Opsi 2: Langsung Pakai (Source Code SIAKAD)
Anda tidak paham koding? Jangan khawatir! Anda hanya perlu menyalin dan menempelkan kode di bawah ini dengan mengikuti langkah-langkah sederhana berikut.
TAHAP 1: Menyiapkan Database (Backend)
- Buka Google Apps Script dan buat Proyek Baru.
- Hapus seluruh kode bawaan yang ada di editor layar, lalu Salin dan Paste kode GS di bawah ini.
- Perhatikan menu dropdown di bagian atas layar (sebelah tombol Jalankan), pilih fungsi setupDatabase lalu klik tombol Jalankan (Run).
*Catatan: Langkah ini wajib dilakukan. Skrip akan secara ajaib membuat file Google Spreadsheet baru di Google Drive Anda dengan nama "Database_Guru_Kelas_Platinum". - Klik tombol biru Terapkan (Deploy) di sudut kanan atas > Deployment Baru.
- Pilih jenis ikon roda gigi ⚙️ -> Aplikasi Web (Web App). Ubah kolom akses menjadi "Siapa saja (Anyone)". Terakhir, klik Terapkan dan Salin (Copy) URL Web App yang muncul.
// =========================================================
// BACKEND GAS - V5 (PRODUCTION READY: AUTO-HEAL & BATCHING)
// =========================================================
const SPREADSHEET_NAME = "Database_Guru_Kelas_Platinum";
const SECRET_KEY = "SIAKAD_PLATINUM_2026_SECURE_TOKEN";
function setupDatabase() {
let files = DriveApp.searchFiles(`title contains "${SPREADSHEET_NAME}" and mimeType = "${MimeType.GOOGLE_SHEETS}"`);
let ss = files.hasNext() ? SpreadsheetApp.open(files.next()) : SpreadsheetApp.create(SPREADSHEET_NAME);
// AUTO-HEAL: Standarisasi Header untuk mencegah Bug Key Mismatch
const expectedHeaders = {
'Students': ['id', 'nisn', 'nama', 'jk', 'ayah', 'ibu', 'kerjaAyah', 'kerjaIbu'],
'Attendance': ['id', 'studentId', 'date', 'status'],
'Grades': ['id', 'studentId', 'subject', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'h9', 'h10', 'mid', 'pas'],
'Journals': ['id', 'date', 'pertemuan', 'mapel', 'materi', 'absensi', 'catatan'],
'Subjects': ['id', 'name'],
'Profile': ['key', 'value']
};
for (let sheetName in expectedHeaders) {
let sheet = ss.getSheetByName(sheetName);
if (!sheet) {
sheet = ss.insertSheet(sheetName);
sheet.appendRow(expectedHeaders[sheetName]);
} else {
// Paksakan header ke format standar sistem agar parsing tidak pernah gagal
sheet.getRange(1, 1, 1, expectedHeaders[sheetName].length).setValues([expectedHeaders[sheetName]]);
}
}
let profileSheet = ss.getSheetByName('Profile');
if (profileSheet.getLastRow() <= 1) {
const defaultProfile = [
['password', 'admin123'], ['namaPemerintah', 'Pemerintah Kabupaten Kerinci'],
['namaSekolah', 'SDN 7 Kerinci'], ['kelas', 'Kelas 5'],
['namaKepsek', 'Budi Santoso, S.Pd., M.Pd'], ['nipKepsek', '19700101 199512 1 001'],
['logoKiri', 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a6/Coat_of_arms_of_Jambi.svg/200px-Coat_of_arms_of_Jambi.svg.png'],
['logoKanan', 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9e/Tut_Wuri_Handayani.svg/200px-Tut_Wuri_Handayani.svg.png'],
['nama', 'Andi Abdi Hadi, S.Pd'], ['nip', '19870512 201212 1 002'],
['dbLink', ''], ['jabatan', 'Guru / Wali Kelas'], ['hp', '081234567890'], ['alamat', 'Kerinci, Jambi']
];
defaultProfile.forEach(row => profileSheet.appendRow(row));
}
PropertiesService.getScriptProperties().setProperty('SS_ID', ss.getId());
return "Database Setup & Auto-Heal Sukses!";
}
function getSS() { return SpreadsheetApp.openById(PropertiesService.getScriptProperties().getProperty('SS_ID')); }
function doPost(e) {
try {
const req = JSON.parse(e.postData.contents);
const action = req.action;
let result = null;
const profile = getProfileData();
const serverToken = Utilities.base64Encode(String(profile.password).trim() + SECRET_KEY);
if (action === 'login') {
if (String(profile.password).trim() === String(req.data.password).trim()) {
return ContentService.createTextOutput(JSON.stringify({success: true, token: serverToken})).setMimeType(ContentService.MimeType.JSON);
} else {
return ContentService.createTextOutput(JSON.stringify({success: false, error: "Password Salah!"})).setMimeType(ContentService.MimeType.JSON);
}
}
if (req.token !== serverToken) {
throw new Error("Akses Ditolak! Sesi login tidak valid.");
}
if (action === 'getData') {
result = {
students: getSheetData('Students'),
attendance: getSheetData('Attendance'),
grades: getSheetData('Grades'),
journals: getSheetData('Journals'),
subjects: getSheetData('Subjects'),
profile: profile
};
delete result.profile.password;
}
else if (action === 'saveStudent') {
const sheet = getSS().getSheetByName('Students');
const data = sheet.getDataRange().getValues();
for(let i=1; i {
if(std.nisn && std.nama){
const id = new Date().getTime().toString() + Math.floor(Math.random()*1000);
dataToInsert.push([id, std.nisn, std.nama, std.jk, std.ayah, std.ibu, std.kerjaAyah, std.kerjaIbu]);
}
});
if(dataToInsert.length > 0) sheet.getRange(sheet.getLastRow() + 1, 1, dataToInsert.length, 8).setValues(dataToInsert);
result = dataToInsert.length;
}
else if (action === 'saveAttendanceBatch') {
const sheet = getSS().getSheetByName('Attendance');
const data = sheet.getDataRange().getValues();
const headers = data[0];
let newRows = [];
req.data.forEach(att => {
let found = false;
let inputDate = String(att.date);
for(let i=1; i 1) sheet.getRange(1, 1, data.length, headers.length).setValues(data);
if(newRows.length > 0) sheet.getRange(sheet.getLastRow() + 1, 1, newRows.length, headers.length).setValues(newRows);
result = true;
}
else if (action === 'deleteStudent') { result = deleteRow('Students', req.data ? req.data.id : req.id); }
else if (action === 'saveGrade') { result = saveRow('Grades', req.data, ['studentId', 'subject']); }
else if (action === 'saveJournal') { result = saveRow('Journals', req.data); }
else if (action === 'saveSubject') { result = saveRow('Subjects', req.data, ['name']); }
// BUG FIX: Hapus mapel berdasarkan nama mapel, bukan baris ID.
else if (action === 'deleteSubject') {
const sheet = getSS().getSheetByName('Subjects');
const data = sheet.getDataRange().getValues();
result = false;
for(let i=1; i {
let obj = {};
headers.forEach((h, i) => {
if (row[i] instanceof Date) obj[h] = Utilities.formatDate(row[i], "Asia/Jakarta", "yyyy-MM-dd");
else obj[h] = displayData[rIdx + 1][i];
});
return obj;
});
}
function getProfileData() {
const data = getSheetData('Profile'); let obj = {};
data.forEach(item => obj[item.key] = item.value); return obj;
}
function saveProfileData(profileObj) {
const sheet = getSS().getSheetByName('Profile');
sheet.clearContents(); sheet.appendRow(['key', 'value']);
for (let key in profileObj) { sheet.appendRow([key, profileObj[key]]); }
return profileObj;
}
function saveRow(sheetName, dataObj, matchKeys = ['id']) {
const sheet = getSS().getSheetByName(sheetName);
const data = sheet.getDataRange().getValues();
const headers = data[0];
if (!dataObj.id) dataObj.id = new Date().getTime().toString() + Math.floor(Math.random()*1000);
let rowIndex = -1;
for (let i = 1; i < data.length; i++) {
let match = true;
for (let key of matchKeys) {
let headerIdx = headers.indexOf(key);
if (headerIdx === -1 || String(data[i][headerIdx]) !== String(dataObj[key])) match = false;
}
if (match) { rowIndex = i + 1; break; }
}
const rowData = headers.map(h => dataObj[h] !== undefined ? dataObj[h] : "");
if (rowIndex > -1) sheet.getRange(rowIndex, 1, 1, headers.length).setValues([rowData]);
else sheet.appendRow(rowData);
return dataObj;
}
function deleteRow(sheetName, id) {
const sheet = getSS().getSheetByName(sheetName);
const data = sheet.getDataRange().getValues();
for (let i = 1; i < data.length; i++) {
if (data[i][0] == id) { sheet.deleteRow(i + 1); return true; }
}
return false;
}
Tahap 2: Memasang Tema (Frontend) di Blogger
- Silakan Unduh (Download) Kode Template XML SIAKAD Platinum melalui tombol Direct Download di bawah ini.
- Buka Dashboard Blogger Anda, masuk ke menu Tema, klik tanda panah ke bawah di sebelah tombol Sesuaikan, lalu pilih Edit HTML.
- Hapus seluruh kode yang ada di dalamnya (Gunakan Ctrl + A, lalu Delete).
- Buka file XML yang baru saja diunduh (menggunakan aplikasi Notepad di PC), salin seluruh isinya, lalu tempel (paste) ke editor Blogger Anda.
- LANGKAH WAJIB: Cari tulisan "GANTI_DENGAN_URL_GAS_ANDA" (biasanya berada di sekitar baris ke-50). Ganti teks tersebut dengan URL Web App yang telah Anda salin dari Tahap 1. (Pastikan tanda kutip ganda `"` tetap membungkus link Anda).
- Terakhir, klik tombol Simpan Tema di pojok kanan atas. Kunjungi web Anda, dan SIAKAD siap digunakan!
🔒 Info Password Default: Saat pertama kali Anda atau wali murid membuka blog, sistem akan meminta password. Gunakan password admin123 untuk masuk. Anda diwajibkan untuk segera mengubahnya melalui menu Biodata & Setting agar keamanan data siswa terjaga.
