Kamis, 05 Februari 2026

Absensi UIN SCC

 







Struktur Spreadsheet

Buat Google Spreadsheet baru dengan 2 sheet:

  1. Sheet Students (daftar siswa)

    • Header (baris 1): Nama | NIS | Kelas

    • Isi baris 2..n: data siswa (contoh: Aisyah Putri | 12345 | 6 SD)

  2. 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

Google Apps Script Beginner

  https://www.youtube.com/watch?v=YVGZI6IEN3I&list=PL_xiAt6o4ZXwYTKr7G6R_ajM7C7UZ32fB&index=8