https://docs.google.com/spreadsheets/d/1YmJfnum-2PR2ArQd1OoTarprkK-c9ylTdrVJqzywb7o/edit?gid=0#gid=0
Struktur Spreadsheet
Buat Google Spreadsheet baru dengan 2 sheet:
Sheet Students (daftar siswa)
Header (baris 1): Nama | NIS | Kelas
Isi baris 2..n: data siswa (contoh: Aisyah Putri | 12345 | 6 SD)
Sheet Absensi (tempat menyimpan hasil presensi)
Header (baris 1): Timestamp | Nama | NIS | Kelas | Status | Catatan
Catatan: Nama pada sheet Students harus unik bila memungkinkan. Jika ada duplikat, gunakan NIS sebagai pembeda.
Code.gs
// Apps Script: Absensi untuk Google Sites
var SPREADSHEET_ID = "1YmJfnum-2PR2ArQd1OoTarprkK-c9ylTdrVJqzywb7o";
var SECRET_TOKEN = "GANTI_DENGAN_TOKEN_RAHASIA_acak123!";
/** Ambil params dari request (support urlencoded & json) */
function _getParams(e) {
var params = {};
if (e.parameter && Object.keys(e.parameter).length) {
params = JSON.parse(JSON.stringify(e.parameter));
} else if (e.postData && e.postData.contents) {
var t = e.postData.type || "";
var content = e.postData.contents || "";
try {
if (t.indexOf("application/json") !== -1) {
params = JSON.parse(content || "{}");
} else {
var arr = content.split("&");
arr.forEach(function(pair){
if(!pair) return;
var kv = pair.split("=");
var key = decodeURIComponent(kv[0]||"");
var val = decodeURIComponent(kv[1]||"");
params[key] = val;
});
}
} catch (err) {
params = {};
}
}
return params;
}
/** Endpoint GET untuk: health check atau ambil daftar siswa
* contoh: https://.../exec?action=students
*/
function doGet(e) {
try {
var action = (e.parameter && e.parameter.action) ? e.parameter.action : "";
if (action === "students") {
return getStudents();
}
// default health
return ContentService.createTextOutput(JSON.stringify({status:"ok", info:"Absensi Apps Script ready"})).setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService.createTextOutput(JSON.stringify({status:"error", message: err.message})).setMimeType(ContentService.MimeType.JSON);
}
}
/** Kembalikan JSON daftar siswa dari sheet "Students" */
function getStudents() {
try {
var ss = SpreadsheetApp.openById(SPREADSHEET_ID);
var sh = ss.getSheetByName("Students");
if (!sh) {
return ContentService.createTextOutput(JSON.stringify({status:"error", message:"Students sheet not found"})).setMimeType(ContentService.MimeType.JSON);
}
var data = sh.getDataRange().getValues(); // 2D array
if (data.length <= 1) {
return ContentService.createTextOutput(JSON.stringify({status:"ok", students:[] })).setMimeType(ContentService.MimeType.JSON);
}
var headers = data[0];
var rows = data.slice(1);
var students = rows.map(function(r){
return {
nama: (r[0] || "").toString(),
nis: (r[1] || "").toString(),
kelas:(r[2] || "").toString()
};
});
return ContentService.createTextOutput(JSON.stringify({status:"ok", students:students})).setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService.createTextOutput(JSON.stringify({status:"error", message:err.message})).setMimeType(ContentService.MimeType.JSON);
}
}
/** doPost: terima absensi, validasi token, validasi nama↔nis berdasarkan sheet Students */
function doPost(e) {
var response = { status: "error", message: "Unknown error" };
try {
var params = _getParams(e);
// token check
if (!params.token || params.token !== SECRET_TOKEN) {
response.message = "Unauthorized: invalid token";
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
var nama = (params.nama || "").trim();
var nis = (params.nim || params.nis || "").trim();
var kelas= (params.kelas || "").trim();
var status = (params.status || "Hadir").trim();
var catatan = (params.catatan || "").trim();
if (!nama || !nis || !kelas) {
response.message = "Validation error: missing nama / nis / kelas";
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
// buka spreadsheet & ambil daftar siswa
var ss;
try {
ss = SpreadsheetApp.openById(SPREADSHEET_ID);
} catch (err) {
response.message = "Failed to open spreadsheet. Check SPREADSHEET_ID and permissions.";
Logger.log("openById error: " + err);
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
var shStudents = ss.getSheetByName("Students");
if (!shStudents) {
response.message = "Students sheet not found.";
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
var stuData = shStudents.getDataRange().getValues();
var map = {}; // map nama -> nis, kelas (normalize nama lower-case)
for (var i = 1; i < stuData.length; i++) {
var row = stuData[i];
var n = (row[0] || "").toString().trim();
var id = (row[1] || "").toString().trim();
var k = (row[2] || "").toString().trim();
if (n) map[n.toLowerCase()] = { nis: id, kelas: k };
}
var key = nama.toLowerCase();
if (!map[key]) {
response.message = "Nama tidak ditemukan di daftar siswa.";
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
// Validasi NIS cocok dengan nama
if (map[key].nis !== nis) {
response.message = "NIS tidak cocok dengan nama. Silakan pilih nama dari daftar.";
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
// Optional: override kelas dengan yang ada di daftar siswa (untuk konsistensi)
kelas = map[key].kelas || kelas;
// Simpan ke sheet Absensi (sheet pertama atau bernama "Absensi")
var shAbs = ss.getSheetByName("Absensi");
if (!shAbs) {
shAbs = ss.insertSheet("Absensi");
shAbs.appendRow(["Timestamp","Nama","NIS","Kelas","Status","Catatan"]);
}
shAbs.appendRow([new Date(), nama, nis, kelas, status, catatan]);
response = { status: "success", message: "Absensi tersimpan" };
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
} catch (err) {
Logger.log("doPost error: " + err);
response.message = "Unexpected server error: " + (err.message || String(err));
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
}
Embeded Code Google Site
<div style="font-family:Inter, Roboto, Arial, sans-serif; max-width:720px; margin:auto; padding:18px; border-radius:12px; background:linear-gradient(180deg,#f7fcff,#fffef6); box-shadow:0 8px 28px rgba(8,24,40,0.06);">
<div style="text-align:center; margin-bottom:8px;">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Logo_of_Ministry_of_Education_and_Culture_of_Republic_of_Indonesia.svg/400px-Logo_of_Ministry_of_Education_and_Culture_of_Republic_of_Indonesia.svg.png" width="72" alt="absensi" style="display:block;margin:0 auto 8px;">
<h2 style="margin:0;color:#0b6e99;">📚 Form Absensi Siswa</h2>
<br>
<p style="margin:6px 0 0;color:#4a5568;">Sekolah Menengah Pertama 88 Cirebon</p>
</div>
<form id="absenForm" style="display:grid; gap:10px; margin-top:12px;">
<label style="font-weight:700;">Nama Lengkap</label>
<select id="namaSelect" name="nama" required style="padding:10px;border-radius:8px;border:1px solid #d0d7de;background:white;">
<option value="">-- Pilih Nama --</option>
<!-- options akan diisi lewat JS -->
</select>
<label style="font-weight:700;">NIS / NISN</label>
<input type="text" id="nisInput" name="nim" placeholder="Nomor Induk Siswa" required style="padding:10px;border-radius:8px;border:1px solid #d0d7de;background:#f7f9fb;">
<label style="font-weight:700;">Kelas</label>
<input type="text" id="kelasInput" name="kelas" placeholder="Contoh: 6 SD / 9 SMP" required style="padding:10px;border-radius:8px;border:1px solid #d0d7de;background:#f7f9fb;">
<label style="font-weight:700;">Kehadiran</label>
<select name="status" required style="padding:10px;border-radius:8px;border:1px solid #d0d7de;">
<option value="Hadir">Hadir 😊</option>
<option value="Izin">Izin 🤒</option>
<option value="Sakit">Sakit 🤧</option>
<option value="Alfa">Alfa 😢</option>
</select>
<label style="font-weight:700;">Catatan (opsional)</label>
<textarea name="catatan" rows="2" placeholder="Alasan izin / keterangan..." style="padding:10px;border-radius:8px;border:1px solid #d0d7de;"></textarea>
<button id="btnSend" type="button" style="background:#0b6e99;color:white;padding:12px;border-radius:10px;border:none;font-size:16px;cursor:pointer;">✅ Kirim Absensi</button>
<div id="msg" style="text-align:center;font-weight:700;color:#0b6e99;"></div>
</form>
</div>
<script>
(function(){
// ====== GANTI dua nilai ini sebelum paste di Google Sites ======
var WEB_APP_URL = "https://script.google.com/macros/s/AKfycbyqBBKtgHBPK1XkwKqR0_i8QvftPpbe37Ia2lX4SXat68SaWAgpo5DIZRbc5AK18UZKWQ/exec"; // contoh: https://script.google.com/macros/s/AKfycb.../exec
var SECRET_TOKEN = "GANTI_DENGAN_TOKEN_RAHASIA_acak123!";
// Elemen
var namaSelect = document.getElementById("namaSelect");
var nisInput = document.getElementById("nisInput");
var kelasInput = document.getElementById("kelasInput");
var btn = document.getElementById("btnSend");
var msg = document.getElementById("msg");
var students = []; // array objek {nama, nis, kelas}
// Ambil daftar siswa dari Apps Script ?action=students
fetch(WEB_APP_URL + "?action=students")
.then(function(r){ return r.json(); })
.then(function(j){
if (j && j.status === "ok" && Array.isArray(j.students)) {
students = j.students;
// isi dropdown
students.forEach(function(s, idx){
var opt = document.createElement("option");
opt.value = idx; // simpan index sebagai value
opt.textContent = s.nama + " — " + (s.kelas || "");
// simpan data sebagai atribut untuk fallback (tidak diperlukan namun berguna)
opt.dataset.nis = s.nis || "";
opt.dataset.kelas = s.kelas || "";
namaSelect.appendChild(opt);
});
} else {
console.warn("Daftar siswa kosong atau error:", j);
}
})
.catch(function(err){
console.error("Gagal ambil daftar siswa:", err);
msg.style.color = "red";
msg.innerText = "Gagal ambil daftar siswa — periksa konfigurasi Apps Script.";
});
// Ketika pengguna memilih nama dari dropdown, isi NIS & Kelas
namaSelect.addEventListener("change", function(){
var v = namaSelect.value;
if (v === "" || typeof v === "undefined") {
nisInput.value = "";
kelasInput.value = "";
return;
}
var idx = parseInt(v, 10);
if (!isNaN(idx) && students[idx]) {
nisInput.value = students[idx].nis || "";
kelasInput.value = students[idx].kelas || "";
} else {
// fallback ke data attribute (semoga tidak terjadi)
var opt = namaSelect.options[namaSelect.selectedIndex];
nisInput.value = opt.dataset.nis || "";
kelasInput.value = opt.dataset.kelas || "";
}
});
// Kirim form ke Apps Script (x-www-form-urlencoded)
btn.addEventListener("click", function(){
msg.style.color = "#0b6e99";
msg.innerText = "⏳ Mengirim...";
var form = document.getElementById("absenForm");
if (!form.reportValidity()) {
msg.style.color = "red";
msg.innerText = "Lengkapi form terlebih dahulu.";
return;
}
// Validasi: jika nama dipilih dari daftar, pastikan nis cocok
var idx = namaSelect.value;
var namaVal = namaSelect.options[namaSelect.selectedIndex].text.split(" — ")[0] || ""; // teks tanpa kelas
var nisVal = nisInput.value.trim();
if (idx !== "" && students[idx]) {
if (students[idx].nis !== nisVal) {
msg.style.color = "red";
msg.innerText = "NIS tidak cocok dengan nama yang dipilih. Periksa kembali.";
return;
}
// gunakan nama as-is dari daftar (untuk konsistensi)
namaVal = students[idx].nama;
} else {
// kalau pengguna tidak memilih (manual), tetap gunakan input nama
namaVal = form.nama.value.trim();
}
// Buat FormData & tambahkan token
var fd = new FormData(form);
// override nama field untuk pastikan konsisten
fd.set("nama", namaVal);
fd.append("token", SECRET_TOKEN);
// Convert ke x-www-form-urlencoded
var params = new URLSearchParams();
for (var pair of fd.entries()) {
params.append(pair[0], pair[1]);
}
fetch(WEB_APP_URL, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params.toString()
})
.then(function(res){
return res.json ? res.json() : res.text();
})
.then(function(data){
if (typeof data === "string") {
// fallback: Apps Script kadang mengembalikan plain text
msg.style.color = "#0b7a5f";
msg.innerText = "✅ " + data;
form.reset();
} else if (data && data.status === "success") {
msg.style.color = "#0b7a5f";
msg.innerText = "✅ Absensi berhasil dikirim! Terima kasih.";
form.reset();
} else {
msg.style.color = "red";
msg.innerText = "❌ Gagal: " + (data.message || "Server error");
}
setTimeout(()=>msg.innerText="",3500);
})
.catch(function(err){
console.error("Kirim error:", err);
msg.style.color = "red";
msg.innerText = "❌ Terjadi kesalahan koneksi.";
});
});
})();
</script>


Tidak ada komentar:
Posting Komentar