Lompat ke konten Lompat ke sidebar Lompat ke footer

Cara Membuat Aplikasi Sistem Sekolah Digital Menggunakan Google Appscript

Di era digital saat ini, administrasi guru tidak harus rumit. Bayangkan memiliki aplikasi sendiri di mana Anda bisa mengelola absensi siswa, input nilai, dan melihat jadwal mengajar hanya melalui HP atau Laptop, tanpa biaya hosting bulanan.

Dalam panduan ini, saya akan membagikan cara membuat Aplikasi Sistem Sekolah Digital (Single Page Application) yang responsif dan profesional menggunakan Google Apps Script (GAS). Aplikasi ini gratis, aman, dan terintegrasi langsung dengan Google Spreadsheet.

Fitur Utama Aplikasi:
  • Login Guru yang Aman.
  • Dashboard Menu Modern (Grid Layout).
  • Kelola Absensi (Input, Rekap Bulanan, Download CSV).
  • Input Nilai (Ulangan, UTS, UAS).
  • Manajemen Data Siswa (Tambah & Hapus).
  • Mobile Friendly (Bisa dibuka di HP).

Langkah 1: Persiapan Spreadsheet (Database)

Aplikasi ini membutuhkan "otak" untuk menyimpan data. Buatlah Google Spreadsheet baru, lalu buat 4 Sheet (Tab) dengan nama dan struktur kolom persis seperti tabel di bawah ini:

Nama Sheet (Tab) Struktur Kolom (Baris 1) Keterangan
DataSiswa No | Nama Siswa Isi dummy data 1-2 siswa di baris ke-2 untuk tes.
Absensi Waktu Input | Nama Siswa | Status Biarkan kosong (akan terisi otomatis).
Nilai Waktu | Jenis Ujian | Nama | Nilai Biarkan kosong.
Jadwal Hari | Jam | Mapel | Kelas Untuk menyimpan jadwal mengajar.

Langkah 2: Prompt AI (Opsional)

Jika Anda ingin mengembangkan atau memodifikasi aplikasi ini menggunakan bantuan AI (seperti ChatGPT atau Gemini), Anda bisa menggunakan prompt berikut ini:

"Bertindaklah sebagai Web Developer. Buatkan kode Google Apps Script dan HTML untuk aplikasi manajemen sekolah berbasis web (SPA). Database menggunakan Spreadsheet dengan sheet: DataSiswa, Absensi, Nilai, Jadwal. Fitur mencakup Login, Dashboard Grid menu, Input Absensi dengan rekap bulanan, Input Nilai dengan kategori ujian, dan CRUD data siswa. Tampilan harus responsif menggunakan Bootstrap 5."

Langkah 3: Kode Aplikasi (Copy-Paste)

Berikut adalah inti dari aplikasi ini. Buka Spreadsheet Anda, klik menu Ekstensi > Apps Script. Hapus kode bawaan, dan ikuti panduan di bawah.

A. File Backend (Code.gs)

Salin kode ini ke file Code.gs. PENTING: Isi bagian Username, Password, dan ID Spreadsheet Anda sendiri.

Filename: Code.gs
// --- KONFIGURASI USER (SILAKAN ISI) ---
// Isi username yang Anda inginkan untuk login
const USERNAME_GURU = ""; 
// Isi password untuk login
const PASSWORD_BENAR = ""; 

// --- ID SPREADSHEET (WAJIB DIISI) ---
// Ambil ID dari URL Spreadsheet Anda (bagian acak antara /d/ dan /edit)
const SHEET_ID = ""; 

function doGet() {
  return HtmlService.createTemplateFromFile('Index')
      .evaluate().setTitle('Sistem Sekolah Digital')
      .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
      .addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

// --- SYSTEM LOGIN ---
function prosesLogin(u, p) {
  // Validasi login sederhana
  if (u === USERNAME_GURU && p === PASSWORD_BENAR) {
    return { status: true, nama: u };
  }
  return { status: false };
}

// --- KONEKSI DATABASE ---
function getSheet(name) {
  return SpreadsheetApp.openById(SHEET_ID).getSheetByName(name);
}

// --- DATA SISWA ---
function getDaftarSiswa() {
  const sheet = getSheet("DataSiswa");
  if (sheet.getLastRow() < 2) return [];
  return sheet.getRange(2, 2, sheet.getLastRow() - 1, 1).getValues().flat();
}

function tambahSiswaBaru(nama) {
  const sheet = getSheet("DataSiswa");
  sheet.appendRow([sheet.getLastRow(), nama]); 
  return "Siswa berhasil ditambahkan!";
}

function hapusSiswaPermanen(index) {
  const sheet = getSheet("DataSiswa");
  sheet.deleteRow(index + 2);
  return "Data siswa dihapus.";
}

// --- ABSENSI SYSTEM ---
function simpanAbsensiServer(data) {
  const sheet = getSheet("Absensi");
  const time = new Date();
  const rows = data.map(d => [time, d.nama, d.status]);
  sheet.getRange(sheet.getLastRow()+1, 1, rows.length, 3).setValues(rows);
  return "Absensi disimpan!";
}

function getRekapAbsensi(bulan, tahun) {
  const sheet = getSheet("Absensi");
  if (sheet.getLastRow() < 2) return [];
  const data = sheet.getRange(2, 1, sheet.getLastRow()-1, 3).getValues();
  const filtered = data.filter(row => {
    let d = new Date(row[0]);
    return d.getMonth() === (bulan - 1) && d.getFullYear() == tahun;
  });
  return filtered.map(row => {
    let d = new Date(row[0]);
    let tgl = d.getDate() + "/" + (d.getMonth()+1) + "/" + d.getFullYear();
    return [tgl, row[1], row[2]];
  });
}

function getAllAbsensiForCSV() {
  const sheet = getSheet("Absensi");
  if (sheet.getLastRow() < 2) return [];
  const data = sheet.getRange(2, 1, sheet.getLastRow()-1, 3).getValues();
  return data.map(row => [new Date(row[0]).toLocaleString(), row[1], row[2]]);
}

// --- NILAI & JADWAL ---
function simpanNilaiServer(jenis, dataNilai) {
  const sheet = getSheet("Nilai");
  const time = new Date();
  const rows = dataNilai.map(d => [time, jenis, d.nama, d.nilai]);
  sheet.getRange(sheet.getLastRow()+1, 1, rows.length, 4).setValues(rows);
  return "Nilai tersimpan!";
}

function getJadwal() {
  const sheet = getSheet("Jadwal");
  if (sheet.getLastRow() <= 1) return [];
  return sheet.getRange(2, 1, sheet.getLastRow()-1, 4).getValues();
}

function tambahJadwal(h, j, m, k) { getSheet("Jadwal").appendRow([h, j, m, k]); }
function hapusJadwal(i) { getSheet("Jadwal").deleteRow(i + 2); }
function getUrlSheet() { return "https://docs.google.com/spreadsheets/d/" + SHEET_ID; }

B. File Frontend (Index.html)

Buat file baru tipe HTML beri nama Index, lalu salin kode ini. Kode ini sudah mendukung tampilan mobile (responsive).

Filename: Index.html
<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- Bootstrap 5 & FontAwesome -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <style>
    body { font-family: 'Segoe UI', sans-serif; background-color: #f4f6f9; overflow-x: hidden; }
    .hidden { display: none !important; }
    
    /* LOGIN STYLE */
    #login-box { height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #1a1a40 0%, #3b3b98 100%); }
    .card-login { width: 90%; max-width: 400px; padding: 40px; background: white; border-radius: 15px; box-shadow: 0 15px 30px rgba(0,0,0,0.2); }
    
    /* DASHBOARD STYLE */
    .wrapper { display: flex; width: 100%; align-items: stretch; }
    #sidebar { min-width: 250px; max-width: 250px; background: #1a1a40; color: #fff; min-height: 100vh; transition: all 0.3s; position: fixed; z-index: 999; }
    #sidebar.active { margin-left: -250px; }
    #sidebar ul.components { padding: 20px 0; }
    #sidebar ul li a { padding: 15px 20px; display: block; color: #cfd8dc; text-decoration: none; cursor: pointer; }
    #sidebar ul li a:hover, #sidebar ul li a.active { background: #3b3b98; border-left: 5px solid #00d2d3; color: #fff; }
    
    #content { width: 100%; margin-left: 250px; padding: 20px; transition: all 0.3s; }
    #content.active { margin-left: 0; }

    @media (max-width: 768px) {
      #sidebar { margin-left: -250px; }
      #sidebar.active { margin-left: 0; }
      #content { margin-left: 0; }
    }

    /* CARDS */
    .card-custom { border: none; border-radius: 10px; box-shadow: 0 5px 15px rgba(0,0,0,0.05); background: white; margin-bottom: 20px; }
    .card-header-custom { padding: 15px 20px; border-bottom: 1px solid #eee; font-weight: bold; color: #1a1a40; }
    .card-body-custom { padding: 20px; }
    
    .menu-card { cursor: pointer; border-radius: 12px; color: white; border: none; transition: transform 0.3s; }
    .menu-card:hover { transform: translateY(-5px); }
    .bg-1 { background: linear-gradient(45deg, #ff9f43, #ff6b6b); }
    .bg-2 { background: linear-gradient(45deg, #00d2d3, #54a0ff); }
    .bg-3 { background: linear-gradient(45deg, #5f27cd, #9b59b6); }
    .bg-4 { background: linear-gradient(45deg, #1dd1a1, #10ac84); }
  </style>
</head>
<body>

<!-- LOGIN SCREEN -->
<div id="login-box">
  <div class="card-login text-center">
    <div class="mb-4"><i class="fas fa-school fa-4x text-primary"></i></div>
    <h4 class="fw-bold mb-4">Portal Guru</h4>
    <div class="text-start mb-3">
      <label class="fw-bold">Username</label>
      <select id="user" class="form-select">
        <!-- Ganti VALUE di bawah ini sesuai username di Code.gs -->
        <option value="">-- Pilih User --</option>
        <option value="Guru A">Guru A</option>
      </select>
    </div>
    <div class="text-start mb-4">
      <label class="fw-bold">Password</label>
      <input type="password" id="pass" class="form-control" placeholder="Password">
    </div>
    <button onclick="login()" class="btn btn-primary w-100">MASUK</button>
    <p id="msg" class="text-danger mt-3"></p>
  </div>
</div>

<!-- APP DASHBOARD -->
<div id="app" class="wrapper hidden">
  <!-- Sidebar -->
  <nav id="sidebar">
    <div class="p-3 border-bottom border-secondary"><h4>Menu Guru</h4></div>
    <ul class="list-unstyled components">
      <li><a onclick="nav('home')" id="m-home" class="active"><i class="fas fa-home me-2"></i> Dashboard</a></li>
      <li><a onclick="nav('siswa')" id="m-siswa"><i class="fas fa-user-graduate me-2"></i> Siswa</a></li>
      <li><a onclick="nav('absen')" id="m-absen"><i class="fas fa-calendar-check me-2"></i> Absensi</a></li>
      <li><a onclick="nav('nilai')" id="m-nilai"><i class="fas fa-marker me-2"></i> Nilai</a></li>
      <li><a onclick="nav('jadwal')" id="m-jadwal"><i class="fas fa-clock me-2"></i> Jadwal</a></li>
      <li><a onclick="location.reload()"><i class="fas fa-sign-out-alt me-2"></i> Logout</a></li>
    </ul>
  </nav>

  <!-- Content -->
  <div id="content">
    <nav class="navbar navbar-light bg-white shadow-sm rounded mb-4">
      <div class="container-fluid">
        <button type="button" id="sidebarCollapse" class="btn btn-outline-primary"><i class="fas fa-bars"></i></button>
        <span class="fw-bold">Halo, <span id="nama-guru">User</span></span>
      </div>
    </nav>

    <!-- VIEW: HOME -->
    <div id="v-home" class="view">
      <div class="row g-4">
        <div class="col-md-3 col-6"><div class="card menu-card bg-1 p-3 text-center" onclick="nav('siswa')"><i class="fas fa-user-graduate fa-2x mb-2"></i><h6>Siswa</h6></div></div>
        <div class="col-md-3 col-6"><div class="card menu-card bg-2 p-3 text-center" onclick="nav('absen')"><i class="fas fa-calendar-check fa-2x mb-2"></i><h6>Absensi</h6></div></div>
        <div class="col-md-3 col-6"><div class="card menu-card bg-3 p-3 text-center" onclick="nav('nilai')"><i class="fas fa-marker fa-2x mb-2"></i><h6>Nilai</h6></div></div>
        <div class="col-md-3 col-6"><div class="card menu-card bg-4 p-3 text-center" onclick="nav('jadwal')"><i class="fas fa-clock fa-2x mb-2"></i><h6>Jadwal</h6></div></div>
      </div>
    </div>

    <!-- VIEW: SISWA -->
    <div id="v-siswa" class="view hidden">
      <div class="card card-custom">
        <div class="card-header-custom">Manajemen Siswa</div>
        <div class="card-body-custom">
          <div class="input-group mb-3">
            <input id="new-siswa" class="form-control" placeholder="Nama Siswa">
            <button onclick="addSiswa()" class="btn btn-primary">Tambah</button>
          </div>
          <div class="table-responsive">
             <table class="table table-striped"><tbody id="list-siswa"></tbody></table>
          </div>
        </div>
      </div>
    </div>

    <!-- VIEW: ABSENSI -->
    <div id="v-absen" class="view hidden">
      <div class="card card-custom mb-3">
        <div class="card-header-custom">Input Absensi</div>
        <div class="card-body-custom">
          <button onclick="allHadir()" class="btn btn-success btn-sm mb-2">Semua Hadir</button>
          <div class="table-responsive"><table class="table table-bordered"><tbody id="tabel-absen"></tbody></table></div>
          <button onclick="saveAbsen()" class="btn btn-primary w-100">SIMPAN</button>
        </div>
      </div>
      <div class="card card-custom">
        <div class="card-body-custom">
           <h6>Rekap Bulanan</h6>
           <div class="row g-2 mb-2">
             <div class="col-4"><input type="number" id="f-bulan" class="form-control" placeholder="Bulan (1-12)"></div>
             <div class="col-4"><input type="number" id="f-tahun" class="form-control" placeholder="Tahun"></div>
             <div class="col-4"><button onclick="loadRekap()" class="btn btn-info w-100 text-white">Lihat</button></div>
           </div>
           <div class="table-responsive"><table class="table table-sm" id="tabel-rekap"></table></div>
           <button onclick="dlCSV()" class="btn btn-secondary btn-sm mt-2">Download CSV</button>
        </div>
      </div>
    </div>
    
    <!-- View lainnya (Nilai & Jadwal) memiliki struktur serupa, disederhanakan untuk artikel -->
    <div id="v-nilai" class="view hidden">
       <div class="alert alert-info">Fitur Input Nilai (Ulangan/UTS/UAS) tersedia di versi lengkap.</div>
    </div>
    <div id="v-jadwal" class="view hidden">
       <div class="alert alert-info">Fitur Jadwal tersedia di versi lengkap.</div>
    </div>

  </div>
</div>

<script>
  // --- JAVASCRIPT CLIENT SIDE ---
  // Toggle Sidebar Mobile
  document.getElementById('sidebarCollapse').addEventListener('click', () => {
    document.getElementById('sidebar').classList.toggle('active');
    document.getElementById('content').classList.toggle('active');
  });

  // Login Logic
  function login() {
    let u = document.getElementById('user').value;
    let p = document.getElementById('pass').value;
    if(!u || !p) return alert("Isi lengkap!");
    
    let btn = document.querySelector('#login-box button');
    btn.innerText = "Loading...";
    
    google.script.run.withSuccessHandler(res => {
      if(res.status) {
        document.getElementById('login-box').classList.add('hidden');
        document.getElementById('app').classList.remove('hidden');
        document.getElementById('nama-guru').innerText = res.nama;
        loadSiswa();
      } else {
        document.getElementById('msg').innerText = "Login Gagal!";
        btn.innerText = "MASUK";
      }
    }).prosesLogin(u, p);
  }

  // Navigasi
  function nav(id) {
    document.querySelectorAll('.view').forEach(e => e.classList.add('hidden'));
    document.getElementById('v-'+id).classList.remove('hidden');
    // Mobile auto close
    if(window.innerWidth < 768) {
       document.getElementById('sidebar').classList.remove('active');
       document.getElementById('content').classList.remove('active');
    }
  }

  // Load Data Siswa
  let GLOBAL_SISWA = [];
  function loadSiswa() {
    google.script.run.withSuccessHandler(d => {
      GLOBAL_SISWA = d;
      renderSiswa();
    }).getDaftarSiswa();
  }

  function renderSiswa() {
    let h = '', hAbsen = '';
    GLOBAL_SISWA.forEach((n, i) => {
      h += `<tr><td>${i+1}</td><td>${n}</td><td><button class='btn btn-danger btn-sm' onclick='delSiswa(${i})'>X</button></td></tr>`;
      hAbsen += `<tr><td class='n-siswa'>${n}</td><td><select class='s-absen form-select'><option value='Hadir'>Hadir</option><option value='Sakit'>Sakit</option><option value='Izin'>Izin</option><option value='Alpa'>Alpa</option></select></td></tr>`;
    });
    document.getElementById('list-siswa').innerHTML = h;
    document.getElementById('tabel-absen').innerHTML = hAbsen;
  }

  function addSiswa() {
    let n = document.getElementById('new-siswa').value;
    if(n) {
       GLOBAL_SISWA.push(n); renderSiswa();
       google.script.run.tambahSiswaBaru(n);
       document.getElementById('new-siswa').value = '';
    }
  }
  
  function delSiswa(i) {
    if(confirm('Hapus?')) {
       GLOBAL_SISWA.splice(i,1); renderSiswa();
       google.script.run.hapusSiswaPermanen(i);
    }
  }

  // Absensi Logic
  function allHadir() { document.querySelectorAll('.s-absen').forEach(e => e.value='Hadir'); }
  function saveAbsen() {
    let data = [];
    document.querySelectorAll('#tabel-absen tr').forEach(r => {
       data.push({nama: r.querySelector('.n-siswa').innerText, status: r.querySelector('.s-absen').value});
    });
    google.script.run.withSuccessHandler(alert).simpanAbsensiServer(data);
  }
  
  // Rekap Logic
  function loadRekap() {
     let b = document.getElementById('f-bulan').value;
     let t = document.getElementById('f-tahun').value;
     google.script.run.withSuccessHandler(d => {
        let h = '<tr><th>Tgl</th><th>Nama</th><th>Status</th></tr>';
        d.forEach(r => h += `<tr><td>${r[0]}</td><td>${r[1]}</td><td>${r[2]}</td></tr>`);
        document.getElementById('tabel-rekap').innerHTML = h;
     }).getRekapAbsensi(b, t);
  }
  
  function dlCSV() {
     google.script.run.withSuccessHandler(d => {
       let csv = "Waktu,Nama,Status\\n" + d.map(e => e.join(",")).join("\\n");
       let link = document.createElement("a");
       link.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
       link.download = 'rekap.csv';
       link.click();
     }).getAllAbsensiForCSV();
  }
</script>

</body>
</html>

Langkah 4: Publikasi (Deploy)

Agar aplikasi bisa diakses dari HP tanpa membuka editor kode:

  1. Klik tombol biru Terapkan (Deploy) > Deployment Baru.
  2. Pilih jenis: Aplikasi Web.
  3. Pada bagian "Yang memiliki akses", pilih: Siapa Saja (Anyone).
  4. Klik Terapkan.
  5. Salin URL yang diberikan (biasanya berakhiran /exec).

Selamat! Sekarang Anda memiliki aplikasi sekolah digital sendiri. Anda bisa membagikan link tersebut kepada rekan guru atau menyimpannya sebagai bookmark di HP.