Absensi Harian
Rekap Kehadiran
Rekap Kehadiran Per Siswa - Semua Mapel
Kelas: -
Mata Pelajaran: -
| No | NISN | Nama | Rata-rata |
|---|
Ringkasan Kehadiran Kelas
Kelas: -
* Daftar siswa diambil dari kelas induk. Absensi disimpan per mata pelajaran. Status: H/I/S/A/B.
Integrasi Google Sheets (v6 - Siswa Per Kelas)
Struktur: Siswa disimpan di tab kelas induk (misal "XI-2"). Absensi disimpan di tab mapel (misal "XI-2 (Informatika)"). Tab "DaftarKelas" berisi daftar tab mapel. Tab "Config" menyimpan PIN master di A1 dan data guru (JSON) di A2.
💡 Pindah Device? Cukup masukkan URL Web App yang sama di device baru untuk memulihkan seluruh data, PIN, daftar guru, dan logo sekolah secara otomatis.
URL berhasil disimpan!
// Google Apps Script - Absen Digital v7 (Siswa Per Kelas + Icon)
// STRUKTUR:
// - Sheet "DaftarKelas": daftar nama tab mapel (misal "XI-2 (Informatika)")
// - Sheet "Config": A1 = PIN master, A2 = JSON data guru, A3 = JSON mapel, A4 = JSON kelas induk, A5 = Base64 Logo Sekolah
// - Sheet kelas induk (misal "XI-2"): Baris 1 = NISN | Nama (daftar siswa)
// - Sheet mapel (misal "XI-2 (Informatika)"): Baris 1 = NISN | Nama | tgl1 | tgl2 ...
// Deploy: Extensions > Apps Script > Deploy > Web App > "Anyone"
function doGet(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var action = (e && e.parameter && e.parameter.action) || '';
if (action === 'get_pin') {
var sheet = ss.getSheetByName('Config');
if (!sheet) {
sheet = ss.insertSheet('Config');
sheet.getRange(1,1).setValue('77980');
sheet.getRange(1,2).setValue('[]');
sheet.getRange(1,3).setValue('["Informatika","Matematika","B. Indonesia","B. Inggris","Fisika","Kimia","Biologi","Sejarah","PKN","Seni Budaya","PJOK","Ekonomi","Geografi","Sosiologi","PAI","Prakarya"]');
sheet.getRange(1,4).setValue('[]');
sheet.getRange(1,5).setValue('');
}
var pin = sheet.getRange(1, 1).getValue();
var guruData = sheet.getRange(1, 2).getValue();
var subjectsData = sheet.getRange(1, 3).getValue();
var baseClassesData = sheet.getRange(1, 4).getValue();
var schoolIcon = sheet.getRange(1, 5).getValue() || '';
if (!subjectsData) {
subjectsData = '["Informatika","Matematika","B. Indonesia","B. Inggris","Fisika","Kimia","Biologi","Sejarah","PKN","Seni Budaya","PJOK","Ekonomi","Geografi","Sosiologi","PAI","Prakarya"]';
sheet.getRange(1,3).setValue(subjectsData);
}
if (!baseClassesData) {
baseClassesData = '[]';
sheet.getRange(1,4).setValue(baseClassesData);
}
try { guruData = JSON.parse(guruData || '[]'); } catch(ex) { guruData = []; }
try { subjectsData = JSON.parse(subjectsData || '[]'); } catch(ex) { subjectsData = []; }
try { baseClassesData = JSON.parse(baseClassesData || '[]'); } catch(ex) { baseClassesData = []; }
return jsonOut({
pin: String(pin || '77980'),
teachers: guruData,
subjects: subjectsData,
base_classes: baseClassesData,
school_icon: String(schoolIcon)
});
}
if (action === 'classes') {
var sheet = ss.getSheetByName('DaftarKelas');
if (!sheet || sheet.getLastRow() < 1) return jsonOut([]);
var data = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues();
var classes = data.map(r => r[0]).filter(Boolean);
return jsonOut(classes);
}
if (action === 'students') {
var cls = e.parameter.class || '';
var baseClass = cls.split('(')[0].trim();
var sheet = ss.getSheetByName(baseClass);
if (!sheet || sheet.getLastRow() <= 1) return jsonOut([]);
var data = sheet.getRange(2, 1, sheet.getLastRow()-1, 2).getValues();
var results = data.filter(r => r[1]).map(r => ({
class_name: cls, student_nis: String(r[0]), student_name: r[1]
}));
return jsonOut(results);
}
if (action === 'attendance') {
var cls = e.parameter.class || '';
var sheet = ss.getSheetByName(cls);
if (!sheet || sheet.getLastRow() <= 1 || sheet.getLastColumn() <= 2) return jsonOut([]);
var allData = sheet.getDataRange().getValues();
var headers = allData[0];
var results = [];
for (var i = 1; i < allData.length; i++) {
var nis = String(allData[i][0]);
var name = allData[i][1];
if (!name) continue;
for (var c = 2; c < headers.length; c++) {
var dateVal = headers[c];
if (dateVal instanceof Date) dateVal = Utilities.formatDate(dateVal, Session.getScriptTimeZone(), "yyyy-MM-dd");
var status = allData[i][c];
if (status) {
results.push({ date: String(dateVal), class_name: cls, student_nis: nis, student_name: name, status: String(status) });
}
}
}
return jsonOut(results);
}
return jsonOut({error:'unknown action'});
}
function doPost(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var payload = JSON.parse(e.postData.contents);
var action = payload.action || '';
if (action === 'save_pin') {
var sheet = ss.getSheetByName('Config');
if (!sheet) { sheet = ss.insertSheet('Config'); }
sheet.getRange(1, 1).setValue(String(payload.pin));
return jsonOut({result:'success'});
}
if (action === 'save_school_icon') {
var sheet = ss.getSheetByName('Config');
if (!sheet) { sheet = ss.insertSheet('Config'); }
sheet.getRange(1, 5).setValue(String(payload.icon || ''));
return jsonOut({result:'success'});
}
if (action === 'save_teachers') {
var sheet = ss.getSheetByName('Config');
if (!sheet) { sheet = ss.insertSheet('Config'); }
sheet.getRange(1, 2).setValue(JSON.stringify(payload.teachers || []));
return jsonOut({result:'success'});
}
if (action === 'save_subjects') {
var sheet = ss.getSheetByName('Config');
if (!sheet) { sheet = ss.insertSheet('Config'); }
sheet.getRange(1, 3).setValue(JSON.stringify(payload.subjects || []));
return jsonOut({result:'success'});
}
if (action === 'save_base_classes') {
var sheet = ss.getSheetByName('Config');
if (!sheet) { sheet = ss.insertSheet('Config'); }
sheet.getRange(1, 4).setValue(JSON.stringify(payload.base_classes || []));
return jsonOut({result:'success'});
}
if (action === 'save_base_class') {
var className = payload.class_name;
var sheet = ss.getSheetByName(className);
if (!sheet) {
sheet = ss.insertSheet(className);
sheet.appendRow(['NISN', 'Nama']);
}
return jsonOut({result:'success'});
}
if (action === 'save_class') {
var tabName = payload.class_name;
var listSheet = ss.getSheetByName('DaftarKelas');
if (!listSheet) { listSheet = ss.insertSheet('DaftarKelas'); }
listSheet.appendRow([tabName]);
var clsSheet = ss.getSheetByName(tabName);
if (!clsSheet) {
clsSheet = ss.insertSheet(tabName);
clsSheet.appendRow(['NISN', 'Nama']);
}
return jsonOut({result:'success'});
}
if (action === 'save_student') {
var cls = payload.class_name;
var sheet = ss.getSheetByName(cls);
if (!sheet) { sheet = ss.insertSheet(cls); sheet.appendRow(['NISN','Nama']); }
sheet.appendRow([payload.student_nis, payload.student_name]);
return jsonOut({result:'success'});
}
if (action === 'delete_student') {
var sheet = ss.getSheetByName(payload.class_name);
if (!sheet) return jsonOut({result:'error'});
var data = sheet.getDataRange().getValues();
for (var i = data.length-1; i >= 1; i--) {
if (data[i][1] === payload.student_name) { sheet.deleteRow(i+1); break; }
}
return jsonOut({result:'success'});
}
if (action === 'edit_student') {
var sheet = ss.getSheetByName(payload.class_name);
if (!sheet) return jsonOut({result:'error', message:'Sheet not found'});
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
if (data[i][1] === payload.old_name) {
if (payload.new_name) sheet.getRange(i+1, 2).setValue(payload.new_name);
if (payload.new_nis !== undefined) sheet.getRange(i+1, 1).setValue(String(payload.new_nis));
break;
}
}
return jsonOut({result:'success'});
}
if (action === 'delete_class') {
var listSheet = ss.getSheetByName('DaftarKelas');
if (listSheet) {
var data = listSheet.getDataRange().getValues();
for (var i = data.length-1; i >= 0; i--) {
if (data[i][0] === payload.class_name) { listSheet.deleteRow(i+1); break; }
}
}
var clsSheet = ss.getSheetByName(payload.class_name);
if (clsSheet) ss.deleteSheet(clsSheet);
return jsonOut({result:'success'});
}
if (action === 'rename_class') {
var oldName = payload.old_name;
var newName = payload.new_name;
var sheet = ss.getSheetByName(oldName);
if (sheet) sheet.setName(newName);
var listSheet = ss.getSheetByName('DaftarKelas');
if (listSheet) {
var data = listSheet.getDataRange().getValues();
for (var i = 0; i < data.length; i++) {
var tabName = data[i][0];
if (tabName === oldName) {
listSheet.getRange(i+1, 1).setValue(newName);
} else if (tabName.indexOf(oldName + ' (') === 0) {
var newTabName = tabName.replace(oldName + ' (', newName + ' (');
listSheet.getRange(i+1, 1).setValue(newTabName);
var subSheet = ss.getSheetByName(tabName);
if (subSheet) subSheet.setName(newTabName);
}
}
}
return jsonOut({result:'success'});
}
if (action === 'reset_semester') {
var cls = payload.class_name;
var sheet = ss.getSheetByName(cls);
if (!sheet) return jsonOut({result:'error'});
var lastCol = sheet.getLastColumn();
if (lastCol > 2) sheet.deleteColumns(3, lastCol - 2);
return jsonOut({result:'success'});
}
if (action === 'sync_students_to_subject_tab') {
var tabName = payload.class_name;
var baseClass = payload.base_class;
var students = payload.students || [];
var sheet = ss.getSheetByName(tabName);
if (!sheet) return jsonOut({result:'error', message:'Sheet not found'});
// Check if sheet is empty (only header row)
if (sheet.getLastRow() <= 1 && students.length > 0) {
// Add all students from base class to subject tab
students.forEach(function(st) {
sheet.appendRow([st.student_nis, st.student_name]);
});
} else if (sheet.getLastRow() > 1 && students.length > 0) {
// Sheet already has data - check for missing students and add only those
var existingData = sheet.getDataRange().getValues();
var existingNames = {};
for (var i = 1; i < existingData.length; i++) {
existingNames[existingData[i][1]] = true;
}
// Add only students that don't already exist
students.forEach(function(st) {
if (!existingNames[st.student_name]) {
sheet.appendRow([st.student_nis, st.student_name]);
existingNames[st.student_name] = true;
}
});
}
return jsonOut({result:'success'});
}
if (action === 'save_attendance') {
var cls = payload.class_name;
var date = payload.date;
var sheet = ss.getSheetByName(cls);
if (!sheet) return jsonOut({result:'error', message:'Sheet not found'});
var baseClass = cls.split('(')[0].trim();
if (sheet.getLastRow() <= 1) {
var baseSheet = ss.getSheetByName(baseClass);
if (baseSheet && baseSheet.getLastRow() > 1) {
var students = baseSheet.getRange(2, 1, baseSheet.getLastRow()-1, 2).getValues();
students.forEach(function(row) { if (row[1]) sheet.appendRow(row); });
}
}
var allData = sheet.getDataRange().getValues();
var headers = allData[0];
var dateCol = -1;
for (var c = 2; c < headers.length; c++) {
var hd = headers[c];
if (hd instanceof Date) hd = Utilities.formatDate(hd, Session.getScriptTimeZone(), "yyyy-MM-dd");
if (String(hd) === date) { dateCol = c + 1; break; }
}
if (dateCol === -1) {
dateCol = headers.length + 1;
sheet.getRange(1, dateCol).setValue(date);
allData = sheet.getDataRange().getValues();
}
var records = payload.records || [];
for (var r = 0; r < records.length; r++) {
var rec = records[r];
for (var i = 1; i < allData.length; i++) {
if (allData[i][1] === rec.student_name) {
sheet.getRange(i+1, dateCol).setValue(rec.status || '');
break;
}
}
}
return jsonOut({result:'success'});
}
if (action === 'delete_attendance_date') {
var cls = payload.class_name;
var date = payload.date;
var sheet = ss.getSheetByName(cls);
if (!sheet) return jsonOut({result:'error', message:'Sheet not found'});
var allData = sheet.getDataRange().getValues();
var headers = allData[0];
var dateCol = -1;
for (var c = 2; c < headers.length; c++) {
var hd = headers[c];
if (hd instanceof Date) hd = Utilities.formatDate(hd, Session.getScriptTimeZone(), "yyyy-MM-dd");
if (String(hd) === date) { dateCol = c + 1; break; }
}
if (dateCol !== -1) { sheet.deleteColumn(dateCol); return jsonOut({result:'success'}); }
return jsonOut({result:'error', message:'Date not found'});
}
return jsonOut({result:'error', message:'unknown action'});
}
Kelola Data
Kelola Kelas Induk
Tambahkan kelas induk terlebih dahulu (misal: XI-1, XI-2, XII-IPA-1). Siswa yang diinput ke kelas induk akan otomatis tersedia di semua mata pelajaran yang dipasangkan.
Import Siswa (Per Kelas Induk)
Masukkan nama siswa dan NISN satu per baris. Data disimpan ke kelas induk (misal XI-2). Semua mapel di kelas ini otomatis menggunakan daftar siswa yang sama.
Daftar Siswa Per Kelas
Daftar Mata Pelajaran
Tambahkan mata pelajaran yang tersedia di sekolah. Daftar ini akan digunakan saat memasangkan kelas dengan mata pelajaran.
Pasangkan Kelas & Mata Pelajaran
Pilih kelas dan mata pelajaran untuk membuat pasangan baru. Ini akan membuat tab baru di sistem database (misal: "XI-1 (Informatika)").
Daftar Pasangan Aktif:
Kelola Guru & Mata Pelajaran
Tambahkan guru dengan PIN unik. Tetapkan kelas+mapel yang diajarkan.
Logo / Icon Sekolah
Upload logo sekolah untuk ditampilkan di samping judul header dan sebagai tab icon browser (favicon). Logo akan disimpan secara permanen di database cloud & lokal agar otomatis tersinkronisasi saat Anda berpindah device.
Format: PNG, JPG, WebP. Maks 500KB.
PIN Master Admin
Pengaturan Zona Waktu
Pilih zona waktu Indonesia untuk menentukan tanggal absensi hari ini secara presisi.