Pantau kehadiran siswa & notifikasi WhatsApp orang tua secara real-time
07:00:00 WITA•Jumat, 13 Nov 2026
Selamat Pagi, Admin! ☀️
Sistem Absensi QR Code & Notifikasi WhatsApp SD Inpres 2 Maninili (Semua Kelas) siap mencatat kehadiran siswa hari ini.
Hadir Hari Ini
0
/ 8 Siswa
100% tingkat kehadiran
Izin & Sakit
0
Siswa
Terkonfirmasi orang tua
Terlambat
0
Siswa
Batas jam masuk 07:15
Alpha / Tindak Lanjut
0
Perlu Atensi
Notifikasi WA siap dikirim
Grafik Kehadiran Mingguan
Persentase siswa hadir tepat waktu Senin – Sabtu
Minggu Ini
Komposisi Status
Proporsi status absensi hari ini
100%Tercatat
Aktivitas Scan Terbaru
Riwayat pemindaian QR Code & pengiriman WA otomatis
Simulasi & Uji Cepat
⚡ One-Click Demo Scan
Uji Scan Siswa & WA Gateway
Klik tombol petir di bawah untuk mensimulasikan siswa memindai kartu QR di gerbang sekolah beserta suara & WA otomatis.
Status Mesin WA:Aktif (Auto-Send)
⭐
Cetak Kartu Pelajar QR Code Siap Potong!
Buat kartu absensi siswa otomatis lengkap dengan QR Code unik per NISN.
Siswa
NISN / QR
Kelas
L/P
Orang Tua / Wali
No. WhatsApp Ortu
Aksi
Pemindai QR Code Kehadiran
Arahkan kartu QR siswa ke kamera atau gunakan tombol simulasi
Kamera Scanner Siap
Klik tombol Aktifkan Kamera QR di bawah untuk memindai kartu pelajar secara langsung, atau gunakan panel Simulasi 1-Klik di sebelah kanan.
Status Pemindaian Terakhir
Menunggu pemindaian QR...
Silakan scan kartu atau pilih siswa di panel simulasi
SIAP
⚡ Simulasi Scan 1-Klik
Klik nama siswa untuk langsung menguji scan QR & notifikasi WA
Instan
Input Absen Manual / Izin / Sakit
Gunakan jika kartu siswa tertinggal atau orang tua mengirim surat izin
Deteksi Dini Siswa Perlu Perhatian
Otomatis mendeteksi siswa dengan catatan Alpha atau Terlambat berulang
Jurnal Pembinaan & Panggilan Orang Tua
Catatan resmi tindak lanjut wali kelas / guru BK beserta tombol WA Orang Tua
Catat Tindak Lanjut Baru
Rekam hasil pembinaan dan kirim pemberitahuan resmi ke WhatsApp orang tua
Konfigurasi Mesin WA Gateway
Mendukung Fonnte, Wablas, Webhook GAS, atau Direct WhatsApp
Terhubung
Template Pesan Notifikasi Otomatis
Variabel aktif:
{nama_siswa}{nisn}{kelas}{ortu}{waktu}{status}{tanggal}{sekolah}
Riwayat Pengiriman WA Orang Tua
Log pesan otomatis yang dikirim saat siswa scan QR atau tindak lanjut
Kop Kiri
REKAP ABSENSI SELURUH KELAS
SD INPRES 2 MANINILI
NPSN: 40201892 • Alamat: Jl. Pendidikan No. 14, Kec. Tinombo Selatan
Kop Kanan
Periode:-
Jumlah Siswa:0 Orang
Kelas:Semua Kelas
Tanggal Cetak:-
No.
Nama Siswa
NISN
Kelas
Hadir
Terlambat
Izin
Sakit
Alpha
% Kehadiran
Area Pengesahan Dokumen Resmi
Log aktivitas di bawah tidak akan ikut tercetak. Hanya tabel rekap & pengesahan ini yang masuk ke cetakan/PDF.
13 November 2026
Mengetahui,
Kepala Sekolah
Hj. Siti Aminah, S.Pd., M.Pd.
NIP. 19740812 199903 2 004
Log Detail Riwayat Absensi (Layar Aplikasi)
Daftar waktu scan masuk/pulang & kirim ulang WA (Bagian ini otomatis disembunyikan saat cetak dokumen)
Hanya Tampil di Layar
Tanggal & Jam
Nama Siswa
Kelas
Tipe
Status
Keterangan
Aksi / WA
Administrator Control Panel
Manajemen Akun Login Wali Kelas & User (Sheet `Users`)
Kelola akun login untuk masing-masing Wali Kelas (Kelas 1 – 6) maupun Administrator
Nama Guru / Pengguna
Username
Password
Peran (Role)
Akses Kelas
Status
Aksi
Identitas & Parameter Jam Absensi
Atur nama sekolah, kepala sekolah, serta batas jam terlambat otomatis
Manajemen Database & Sinkronisasi 2 Arah
Tarik perubahan dari Google Sheets, kirim pembaruan, atau unduh cadangan JSON
Dokumen & Pengesahan Cetak
PENGATURAN IDENTITAS KOP REKAP ABSENSI
Kelola 4 aset gambar resmi secara terpisah dari logo utama aplikasi (Format: PNG, JPG, JPEG • Maks. 2 MB per file)
A. LOGO KOP KIRI
Gambar belum diunggah
B. LOGO KOP KANAN
Gambar belum diunggah
C. CAP / STEMPEL SEKOLAH
Gambar belum diunggah
D. TANDA TANGAN KEPSEK
Gambar belum diunggah
Tambah Data Siswa
Tambah Akun Wali Kelas / User
Kartu QR Siswa
Nama Siswa
NISN: -
Kode Backend `Code.gs` (Multi-User & Sheet `Users`)
Otomatis membuat Sheet Users, DataSiswa, dan LogAbsensi di Google Spreadsheet
💡 Struktur Sheet `Users` Otomatis:
Saat disinkronkan atau dijalankan pertama kali, fungsi ensureDefaultSheets() di bawah ini otomatis membuat tab sheet Users dengan kolom: ID | Username | Password | NamaLengkap | Role | Kelas | Status.
// =========================================================================
// CODE.GS TERBARU - TWO-WAY SYNC (TARIK & KIRIM DATA GOOGLE SHEETS)
// Mendukung Sheet: Users, DataSiswa, LogAbsensi, dan IDENTITAS SEKOLAH
// =========================================================================
function doGet(e) {
ensureDefaultSheets();
// Jika aplikasi (Cloudflare/Web) meminta tarik data terbaru (?action=PULL)
if (e && e.parameter && e.parameter.action === 'PULL') {
var payload = getAllStateFromSheets();
return ContentService.createTextOutput(JSON.stringify(payload))
.setMimeType(ContentService.MimeType.JSON);
}
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('Absensi QR — WA Gateway | Sistem Absensi Siswa')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
function doPost(e) {
ensureDefaultSheets();
var payload = JSON.parse(e.postData.contents);
if (payload.action === 'LOGIN') {
return ContentService.createTextOutput(JSON.stringify(authenticateUserFromSheet(payload.username, payload.password)))
.setMimeType(ContentService.MimeType.JSON);
}
if (payload.action === 'PULL') {
return ContentService.createTextOutput(JSON.stringify(getAllStateFromSheets()))
.setMimeType(ContentService.MimeType.JSON);
}
if (payload.action === 'SAVE_IDENTITAS_KOP') {
return ContentService.createTextOutput(JSON.stringify(saveSchoolIdentityAssets(payload.school, payload.kopAssets)))
.setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(JSON.stringify(syncAppStateToSheets(JSON.stringify(payload))))
.setMimeType(ContentService.MimeType.JSON);
}
// 1. Otomatis membuat / menyesuaikan Sheet Users, DataSiswa, LogAbsensi & IDENTITAS SEKOLAH
function ensureDefaultSheets() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var userSheet = ss.getSheetByName('Users');
if (!userSheet) {
userSheet = ss.insertSheet('Users');
userSheet.appendRow(['ID', 'Username', 'Password', 'NamaLengkap', 'Role', 'Kelas', 'Status']);
userSheet.appendRow(['U01', 'admin', 'admin123', 'Administrator Sekolah', 'ADMIN', 'ALL', 'Aktif']);
userSheet.appendRow(['U02', 'walikelas1', 'kelas1', 'Ibu Siti Rahma, S.Pd.', 'WALIKELAS', 'Kelas 1', 'Aktif']);
userSheet.appendRow(['U03', 'walikelas2', 'kelas2', 'Ibu Rahmawati, S.Pd.', 'WALIKELAS', 'Kelas 2', 'Aktif']);
userSheet.appendRow(['U04', 'walikelas3', 'kelas3', 'Bpk. Hendra Wijaya, S.Pd.', 'WALIKELAS', 'Kelas 3', 'Aktif']);
userSheet.appendRow(['U05', 'walikelas4', 'kelas4', 'Ibu Dewi Sartika, S.Pd.', 'WALIKELAS', 'Kelas 4', 'Aktif']);
userSheet.appendRow(['U06', 'walikelas5', 'kelas5', 'Bpk. Ahmad Fauzi, S.Pd.', 'WALIKELAS', 'Kelas 5', 'Aktif']);
userSheet.appendRow(['U07', 'walikelas6', 'kelas6', 'Ibu Hj. Nurhayati, S.Pd.', 'WALIKELAS', 'Kelas 6', 'Aktif']);
}
if (!ss.getSheetByName('DataSiswa')) {
var sSheet = ss.insertSheet('DataSiswa');
sSheet.appendRow(['ID', 'NISN', 'Nama', 'Kelas', 'Gender', 'OrangTua', 'NoWA']);
}
if (!ss.getSheetByName('LogAbsensi')) {
var lSheet = ss.insertSheet('LogAbsensi');
lSheet.appendRow(['ID', 'Tanggal', 'Waktu', 'NISN', 'Nama', 'Kelas', 'Tipe', 'Status', 'Keterangan']);
}
// Periksa & sesuaikan struktur sheet IDENTITAS SEKOLAH tanpa menghapus data lama
var idSheet = ss.getSheetByName('IDENTITAS SEKOLAH');
var requiredHeaders = [
'NamaSekolah', 'NPSN', 'KepalaSekolah', 'NIP', 'Alamat',
'JamMasuk', 'JamPulang', 'Logo Kop Kiri', 'Logo Kop Kanan',
'Cap/Stempel Sekolah', 'Tanda Tangan Kepala Sekolah'
];
if (!idSheet) {
idSheet = ss.insertSheet('IDENTITAS SEKOLAH');
idSheet.appendRow(requiredHeaders);
idSheet.appendRow([
'SD Inpres 2 Maninili', '40201892', 'Hj. Siti Aminah, S.Pd., M.Pd.',
'19740812 199903 2 004', 'Jl. Pendidikan No. 14, Kec. Tinombo Selatan',
'07:15', '12:30', '', '', '', ''
]);
} else {
var lastCol = idSheet.getLastColumn();
var existingHeaders = lastCol > 0 ? idSheet.getRange(1, 1, 1, lastCol).getValues()[0].map(String) : [];
requiredHeaders.forEach(function(h) {
if (existingHeaders.indexOf(h) === -1) {
var newColIdx = idSheet.getLastColumn() + 1;
idSheet.getRange(1, newColIdx).setValue(h);
existingHeaders.push(h);
}
});
}
}
// 1B. Fungsi khusus menyimpan identitas & 4 gambar kop ke sheet IDENTITAS SEKOLAH
function saveSchoolIdentityAssets(schoolObj, kopObj) {
ensureDefaultSheets();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var idSheet = ss.getSheetByName('IDENTITAS SEKOLAH');
var lastCol = idSheet.getLastColumn();
var headers = idSheet.getRange(1, 1, 1, lastCol).getValues()[0].map(String);
var rowValues = idSheet.getLastRow() >= 2
? idSheet.getRange(2, 1, 1, lastCol).getValues()[0]
: new Array(lastCol).fill('');
function setColVal(headerName, val) {
var idx = headers.indexOf(headerName);
if (idx !== -1 && val !== undefined && val !== null) {
rowValues[idx] = val;
}
}
if (schoolObj) {
setColVal('NamaSekolah', schoolObj.name);
setColVal('NPSN', schoolObj.npsn);
setColVal('KepalaSekolah', schoolObj.principal);
setColVal('NIP', schoolObj.nip);
setColVal('Alamat', schoolObj.address);
setColVal('JamMasuk', schoolObj.jamMasuk);
setColVal('JamPulang', schoolObj.jamPulang);
}
if (kopObj) {
setColVal('Logo Kop Kiri', kopObj.logoKopKiri || '');
setColVal('Logo Kop Kanan', kopObj.logoKopKanan || '');
setColVal('Cap/Stempel Sekolah', kopObj.capSekolah || '');
setColVal('Tanda Tangan Kepala Sekolah', kopObj.ttdKepsek || '');
}
idSheet.getRange(2, 1, 1, lastCol).setValues([rowValues]);
return { ok: true, updatedAt: new Date().toISOString() };
}
// 1C. Fungsi khusus membaca identitas & 4 gambar kop dari sheet IDENTITAS SEKOLAH
function getSchoolIdentityAssets() {
ensureDefaultSheets();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var idSheet = ss.getSheetByName('IDENTITAS SEKOLAH');
if (!idSheet || idSheet.getLastRow() < 2) return null;
var lastCol = idSheet.getLastColumn();
var headers = idSheet.getRange(1, 1, 1, lastCol).getValues()[0].map(String);
var row = idSheet.getRange(2, 1, 1, lastCol).getValues()[0];
function getColVal(headerName) {
var idx = headers.indexOf(headerName);
return idx !== -1 ? String(row[idx] || '').trim() : '';
}
return {
school: {
name: getColVal('NamaSekolah'),
npsn: getColVal('NPSN'),
principal: getColVal('KepalaSekolah'),
nip: getColVal('NIP'),
address: getColVal('Alamat'),
jamMasuk: getColVal('JamMasuk'),
jamPulang: getColVal('JamPulang')
},
kopAssets: {
logoKopKiri: getColVal('Logo Kop Kiri'),
logoKopKanan: getColVal('Logo Kop Kanan'),
capSekolah: getColVal('Cap/Stempel Sekolah'),
ttdKepsek: getColVal('Tanda Tangan Kepala Sekolah')
}
};
}
// 2. Fungsi TARIK DATA (PULL): Membaca seluruh baris dari Google Sheets ke Aplikasi
function getAllStateFromSheets() {
ensureDefaultSheets();
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Baca Sheet Users
var userRows = ss.getSheetByName('Users').getDataRange().getValues();
var users = [];
for (var i = 1; i < userRows.length; i++) {
if (!userRows[i][1]) continue;
users.push({
id: String(userRows[i][0] || 'U' + i),
username: String(userRows[i][1]).trim(),
password: String(userRows[i][2]).trim(),
fullName: String(userRows[i][3] || ''),
role: String(userRows[i][4] || 'WALIKELAS').trim().toUpperCase(),
kelas: String(userRows[i][5] || 'Kelas 1').trim(),
status: String(userRows[i][6] || 'Aktif').trim()
});
}
// Baca Sheet DataSiswa
var siswaRows = ss.getSheetByName('DataSiswa').getDataRange().getValues();
var students = [];
for (var j = 1; j < siswaRows.length; j++) {
if (!siswaRows[j][1] && !siswaRows[j][2]) continue;
students.push({
id: String(siswaRows[j][0] || 'S' + j),
nisn: String(siswaRows[j][1]).trim(),
nama: String(siswaRows[j][2]).trim(),
kelas: String(siswaRows[j][3] || 'Kelas 1').trim(),
gender: String(siswaRows[j][4] || 'L').trim().toUpperCase(),
ortu: String(siswaRows[j][5] || '').trim(),
phone: String(siswaRows[j][6] || '').trim()
});
}
// Baca Sheet LogAbsensi
var logRows = ss.getSheetByName('LogAbsensi').getDataRange().getValues();
var attendance = [];
for (var k = 1; k < logRows.length; k++) {
if (!logRows[k][3] && !logRows[k][4]) continue;
attendance.push({
id: String(logRows[k][0] || 'A' + k),
date: String(logRows[k][1]).trim(),
time: String(logRows[k][2]).trim(),
nisn: String(logRows[k][3]).trim(),
nama: String(logRows[k][4]).trim(),
kelas: String(logRows[k][5]).trim(),
type: String(logRows[k][6] || 'MASUK').trim(),
status: String(logRows[k][7] || 'Hadir').trim(),
note: String(logRows[k][8] || '')
});
}
var identityData = getSchoolIdentityAssets();
return {
ok: true,
pulledAt: new Date().toISOString(),
users: users,
students: students,
attendance: attendance,
identity: identityData
};
}
// 3. Validasi Login User langsung dari Sheet Users
function authenticateUserFromSheet(username, password) {
var state = getAllStateFromSheets();
for (var i = 0; i < state.users.length; i++) {
var u = state.users[i];
if (u.username.toLowerCase() === String(username).trim().toLowerCase() && u.password === String(password).trim()) {
if (u.status !== 'Aktif') return { ok: false, message: 'Akun sedang dinonaktifkan oleh Administrator.' };
return { ok: true, user: u };
}
}
return { ok: false, message: 'Username atau kata sandi tidak ditemukan di Sheet Users.' };
}
// 4. Fungsi KIRIM DATA (PUSH): Menyimpan perubahan dari Aplikasi ke Google Sheets
function syncAppStateToSheets(payloadJson) {
ensureDefaultSheets();
var data = JSON.parse(payloadJson);
var ss = SpreadsheetApp.getActiveSpreadsheet();
if (data.users && data.users.length) {
var userSheet = ss.getSheetByName('Users');
userSheet.clearContents();
userSheet.appendRow(['ID', 'Username', 'Password', 'NamaLengkap', 'Role', 'Kelas', 'Status']);
data.users.forEach(function(u) {
userSheet.appendRow([u.id, u.username, u.password, u.fullName, u.role, u.kelas, u.status]);
});
}
if (data.students && data.students.length) {
var siswaSheet = ss.getSheetByName('DataSiswa');
siswaSheet.clearContents();
siswaSheet.appendRow(['ID', 'NISN', 'Nama', 'Kelas', 'Gender', 'OrangTua', 'NoWA']);
data.students.forEach(function(s) {
siswaSheet.appendRow([s.id, s.nisn, s.nama, s.kelas, s.gender, s.ortu, s.phone]);
});
}
var logSheet = ss.getSheetByName('LogAbsensi');
logSheet.clearContents();
logSheet.appendRow(['ID', 'Tanggal', 'Waktu', 'NISN', 'Nama', 'Kelas', 'Tipe', 'Status', 'Keterangan']);
(data.attendance || []).forEach(function(r) {
logSheet.appendRow([r.id, r.date, r.time, r.nisn, r.nama, r.kelas, r.type, r.status, r.note || '']);
});
if (data.school || data.kopAssets) {
saveSchoolIdentityAssets(data.school, data.kopAssets);
}
return { ok: true, syncedAt: new Date().toISOString() };
}
// 5. Pengiriman Pesan WhatsApp via Fonnte
function sendWaViaFonnte(target, message, token) {
var res = UrlFetchApp.fetch('https://api.fonnte.com/send', {
method: 'post',
headers: { Authorization: token },
payload: { target: target, message: message }
});
return res.getContentText();
}