Cara Canggih Pantau Literasi Murid Tanpa Perlu Periksa Buku Satu-Satu!


Jurnal baca digital murid menjadi salah satu program sekolah untuk mendongkrak minat baca siswa, hal ini juga terjadi di sekolah tempat saya mengajar. Beberapa lalu setelah menganasila rapot sekolah dewan guru menentukan program-program yang diperlukan untuk meningkatkan nilai-nilai yang perlu menjadi perhatian, dari banyak program yang disodorkan program Literasi menjadi program keberlanjutan yang masih dipertahankan. 

Setelah berdiskusi ada beberapa yang perlu ditambahkan dalam program rutin literasi  untuk memperdala kepedulian siswa dalam hal membaca. Selanjutnya munculah ide untuk membiasakan menulis jurnal baca minimal 1 minggu sekali. Pada awal program ini jurnal berbentuk fisik dimana siswa menuliskan hasil bacanya, kendala muncul ketika Jurnal ini harus dikumpulkan sedangkan kegiatan ini dikelola oleh beberapa guru yang ditugaskan dengan wali kelas sebagai aktor penting dilapangan. 

Keterukuran dan ketercapaian siswa tidak dapat diolah langsung dan dilaporkan kepada atasan sehingga Tim mencari cara agar memudahkan semua pihak dengan hasil yang maksimal akhirnya munculah ide membuat Jurnal digital.

Jurnal digital ini dibuat menggunakan Google Sheets dan Apps Script, dengan tampilan menarik jika kamu menemukan postingan ini saya tidak akan menjelaskan dari awal langkahnya, kamu bisa mencari postingan saya yang berjudul "Membuat Sistem Presensi Mandiri menggunakan Google Sheets dan Apps Script", saya akan langsung pada bagian pentingnya yaitu code Apps Scriptnya.

Apa yang ditawarkan oleh jurnal digital kali ini

  1. Kemudahan dalam mengecek bacaan siswa
  2. siswa dapat mendownload jurnal dalam bentuk pdf. dan melaporkannya langsung pada Pendamping atau wali kelas
  3. Guru Juga dapat Menjadikan Jurnal sebagai laporan kerja yang otomatis dapat di download melalui akses panel admin

Isian Kolon dengan Nama Sheet (DataSiswa)

Pada bagian NAMA isikan nama Siswa Pasword Isikan pasword acak yang kamu buat, Tenang nantinya siswa dapat merubahnya setelah masuk pertama kali. Sebelum Kamu melanjutkan kamu bisa mencoba mengkasesnya.

Isikan  Jika kamu ingin masuk pada panel admin gunakan password ini "adminsejarah31.com" . Selamat Mencoba 😎


Jika kamu merasa perlu maka saya lampirkan code.gs dan htmlnya Jika ada hal yang ingin didiskusikan dan ditanyakan silahkan tinggalkan pesan.


GOOGLE APPS SCRIPT (code.gs)
/**
 * JURNAL MEMBACA DIGITAL 
 * VERSI FINAL: Auto-Sheet, Penanganan Jurnal, Ganti Password, & Panel Admin
 */

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index')
      .setTitle('Jurnal Membaca')
      .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
      .addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

/**
 * 1. FUNGSI LOGIN SISWA
 */
function loginSiswa(namaInput, passInput) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheetUtama = ss.getSheetByName("DataSiswa");
  const dataSiswa = sheetUtama.getDataRange().getValues();
  
  const userRow = dataSiswa.find(row => 
    row[0].toString().trim().toLowerCase() === namaInput.trim().toLowerCase() && 
    row[1].toString().trim() === passInput.trim()
  );
  
  if (!userRow) {
    return { sukses: false, pesan: "Nama atau Password salah!" };
  }

  const namaSiswa = userRow[0];
  const namaKelas = userRow[2].toString().trim();
  let riwayat = [];
  let sheetKelas = ss.getSheetByName(namaKelas);
  
  if (sheetKelas) {
    const dataKelas = sheetKelas.getDataRange().getValues();
    const barisDataSiswa = dataKelas.find(row => row[0].toString().trim() === namaSiswa.trim());
    if (barisDataSiswa) {
      riwayat = barisDataSiswa.slice(1, 11).filter(item => item !== "" && item !== null);
    }
  }
  
  return { sukses: true, nama: namaSiswa, kelas: namaKelas, dataRiwayat: riwayat };
}

/**
 * 2. FUNGSI SIMPAN JURNAL & AUTO-CREATE SHEET
 */
function prosesJurnal(namaSiswa, namaKelas, teksJurnal) {
  try {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    let sheetKelas = ss.getSheetByName(namaKelas);
    
    if (!sheetKelas) {
      sheetKelas = ss.insertSheet(namaKelas);
      const sheetDataSiswa = ss.getSheetByName("DataSiswa");
      const allData = sheetDataSiswa.getDataRange().getValues();
      
      const daftarNamaKelas = allData
        .filter(row => row[2].toString().trim() === namaKelas)
        .map(row => [row[0]]);
      
      const headers = ["Nama Siswa", "Jurnal 1", "Jurnal 2", "Jurnal 3", "Jurnal 4", "Jurnal 5", "Jurnal 6", "Jurnal 7", "Jurnal 8", "Jurnal 9", "Jurnal 10"];
      sheetKelas.getRange(1, 1, 1, headers.length).setValues([headers]);
      
      if (daftarNamaKelas.length > 0) {
        sheetKelas.getRange(2, 1, daftarNamaKelas.length, 1).setValues(daftarNamaKelas);
      }
      
      sheetKelas.getRange(1, 1, 1, headers.length).setFontWeight("bold").setBackground("#e1f5fe");
      sheetKelas.setFrozenRows(1);
    }
    
    const dataNama = sheetKelas.getRange(1, 1, sheetKelas.getLastRow(), 1).getValues();
    const barisTarget = dataNama.findIndex(row => row[0].toString().trim() === namaSiswa.trim()) + 1;
    
    if (barisTarget === 0) return "Nama tidak ditemukan di daftar kelas.";

    const rowValues = sheetKelas.getRange(barisTarget, 1, 1, 11).getValues()[0];
    let kolomTarget = -1;
    for (let i = 1; i < 11; i++) {
      if (rowValues[i] === "" || rowValues[i] === null) {
        kolomTarget = i + 1;
        break;
      }
    }
    
    if (kolomTarget === -1) return "Kuota jurnal (10) sudah penuh.";

    const waktu = Utilities.formatDate(new Date(), "GMT+7", "dd/MM/yyyy");
    const dataFinal = "[" + waktu + "] " + teksJurnal;
    sheetKelas.getRange(barisTarget, kolomTarget).setValue(dataFinal);
    
    return "Berhasil"; 
  } catch (e) {
    return "Terjadi kesalahan: " + e.message;
  }
}

/**
 * 3. FUNGSI GANTI PASSWORD
 */
function gantiPassword(namaInput, passLama, passBaru) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheetUtama = ss.getSheetByName("DataSiswa");
  const data = sheetUtama.getDataRange().getValues();
  
  const idx = data.findIndex(row => 
    row[0].toString().trim() === namaInput.trim() && 
    row[1].toString().trim() === passLama.trim()
  );
  
  if (idx !== -1) { 
    sheetUtama.getRange(idx + 1, 2).setValue(passBaru); 
    return "Berhasil"; 
  }
  return "Gagal! Password lama salah.";
}

/**
 * 4. FUNGSI ADMIN
 */
function ambilDaftarKelas() {
  return SpreadsheetApp.getActiveSpreadsheet().getSheets()
    .map(s => s.getName())
    .filter(n => n !== "DataSiswa");
}

function ambilDataSatuKelas(namaKelas) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(namaKelas);
  if (!sheet) return { sukses: false };
  const data = sheet.getDataRange().getValues();
  return { sukses: true, header: data[0], rows: data.slice(1), kelas: namaKelas };
}
code (index.html)
<!doctype html>
<html>
<head>
  <base target="_top">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'Segoe UI', sans-serif; background: #667eea; display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; }
    .card { background: white; padding: 30px; border-radius: 16px; width: 100%; max-width: 450px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }
    .logo-container { text-align: center; margin-bottom: 15px; }
    .logo-container img { width: 80px; height: 80px; border-radius: 50%; border: 3px solid #667eea; }
    input, textarea, select { width: 100%; padding: 12px; margin: 8px 0; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; }
    button { width: 100%; padding: 12px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; margin-top: 10px; color: white; transition: 0.2s; }
    button:active { transform: scale(0.98); }
    .btn-blue { background: #667eea; }
    .btn-green { background: #34a853; }
    .btn-red { background: #ea4335; }
    .riwayat-box { margin-top: 20px; border-top: 1px solid #eee; padding-top: 15px; }
    .jurnal-item { background: #f8f9fa; padding: 10px; border-radius: 6px; margin-bottom: 8px; border-left: 4px solid #667eea; font-size: 13px; text-align: left; line-height: 1.4; }
    #loader { display: none; text-align: center; color: #667eea; font-weight: bold; padding: 10px; }

    /* MODAL STYLE */
    .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); }
    .modal-content { background: white; margin: 20% auto; padding: 25px; border-radius: 15px; width: 85%; max-width: 350px; text-align: center; box-shadow: 0 5px 15px rgba(0,0,0,0.3); animation: pop 0.3s ease-out; }
    @keyframes pop { from {transform: scale(0.8); opacity: 0;} to {transform: scale(1); opacity: 1;} }
    
    /* PRINT STYLE */
    #printAreaSiswa, #printAreaAdmin { display: none !important; }
    @media print {
      body { background: white !important; } #app, .modal { display: none !important; }
      .mode-siswa #printAreaSiswa { display: block !important; }
      .mode-admin #printAreaAdmin { display: block !important; }
      table { width: 100%; border-collapse: collapse; margin-top: 15px; font-size: 11px; }
      th, td { border: 1px solid black; padding: 6px; }
      .ttd-container { margin-top: 40px; float: right; width: 250px; text-align: left; font-size: 12px; }
      .ttd-space { height: 60px; }
    }
  </style>
</head>
<body id="mainBody">

  <div class="card" id="app">
    <div class="logo-container"><img src="https://em-content.zobj.net/source/lg/57/book_1f56e.png"></div>
    <div style="text-align:center; margin-bottom:20px;">
      <h2 style="color:#667eea;">SMAN 23 ........</h2>
      <p style="color:#777; font-size:14px;">Jurnal Membaca Digital</p>
    </div>

    <div id="loader">⏳ Memproses data...</div>

    <div id="loginSec">
      <input type="text" id="user" placeholder="Nama Lengkap">
      <input type="password" id="pass" placeholder="Password">
      <button class="btn-blue" onclick="doLogin()">MASUK</button>
      <button onclick="showAdmin()" style="background:none; border:none; color:#667eea; text-decoration:underline; font-size:12px; margin-top:15px; cursor:pointer; width:100%;">Panel Admin</button>
    </div>

    <div id="dashSec" style="display:none;">
      <h3 id="txtNama" style="color: #667eea;"></h3>
      <p id="txtKelas" style="color:#888; font-size:13px; margin-bottom:15px;"></p>
      
      <textarea id="isiJurnal" rows="4" placeholder="Tulis ringkasan buku yang dibaca hari ini..."></textarea>
      <button class="btn-blue" onclick="simpanJurnal()"> SIMPAN JURNAL</button>
      <button class="btn-green" onclick="cetakSiswa()"> UNDUH PDF RIWAYAT</button>
      
      <div class="riwayat-box">
        <strong> Riwayat Jurnal Anda:</strong>
        <div id="listRiwayat" style="margin-top:10px;"></div>
      </div>
      
      <button class="btn-green" onclick="openPassModal()" style="background:#6c757d; font-size:11px; margin-top:20px;"> UBAH PASSWORD</button>
      <button class="btn-red" onclick="location.reload()"> LOGOUT</button>
    </div>

    <div id="adminSec" style="display:none;">
      <h3 style="text-align:center;">PANEL ADMIN</h3>
      <input type="password" id="adminPass" placeholder="Password Admin">
      <button class="btn-blue" onclick="cekAdmin()">VERIFIKASI</button>
      <div id="adminControls" style="display:none; margin-top:20px;">
        <select id="selKelas"></select>
        <button class="btn-green" onclick="cetakAdmin()"> UNDUH PDF KELAS</button>
      </div>
      <button class="btn-red" onclick="location.reload()" style="margin-top:20px;">KEMBALI</button>
    </div>
  </div>

  <div id="successModal" class="modal">
    <div class="modal-content">
      <div style="font-size: 50px; margin-bottom: 10px;"></div>
      <h3 style="color: #667eea; margin-bottom: 10px;">Terima Kasih!</h3>
      <p style="font-size: 14px; color: #555; line-height: 1.5;">
        Terima kasih telah mengisi jurnal baca.<br>
        <strong>Salam hangat dari Sejarah31.com.</strong>
      </p>
      <button class="btn-blue" onclick="tutupModalSukses()" style="margin-top: 20px;">SAMA-SAMA</button>
    </div>
  </div>

  <div id="printAreaSiswa">
    <h2 style="text-align:center">LAPORAN JURNAL MEMBACA</h2>
    <p><strong>Nama:</strong> <span id="pNama"></span> | <strong>Kelas:</strong> <span id="pKelas"></span></p>
    <table>
      <thead><tr><th width="40">No</th><th>Catatan Jurnal</th></tr></thead>
      <tbody id="pTabelSiswa"></tbody>
    </table>
    <div class="ttd-container">
      <p>Garut, <span class="tglCetak"></span></p>
      <p>Kepala Sekolah SMAN ...,</p>
      <div class="ttd-space"></div>
      <p><strong><u>sejarah31.com</u></strong></p>
      <p>NIP. .......</p>
    </div>
  </div>

  <div id="printAreaAdmin">
    <h2 style="text-align:center">LAPORAN JURNAL MEMBACA PER KELAS</h2>
    <h3 style="text-align:center">KELAS: <span id="adKelas"></span></h3>
    <table><thead id="adHead"></thead><tbody id="adBody"></tbody></table>
    <div class="ttd-container">
      <p>Garut, <span class="tglCetak"></span></p>
      <p>Kepala Sekolah SMAN ....,</p>
      <div class="ttd-space"></div>
      <p><strong><u>sejarah31.com</u></strong></p>
      <p>NIP. 1234567</p>
    </div>
  </div>

  <div id="passModal" class="modal">
    <div class="modal-content">
      <h3>Ubah Password</h3>
      <input type="password" id="oldP" placeholder="Password Lama">
      <input type="password" id="newP" placeholder="Password Baru">
      <button class="btn-blue" onclick="prosesPass()">UPDATE</button>
      <button class="btn-red" onclick="document.getElementById('passModal').style.display='none'">BATAL</button>
    </div>
  </div>

  <script>
    let userData = {};

    function toggleLoader(s) { document.getElementById('loader').style.display = s ? 'block' : 'none'; }

    function doLogin() {
      const u = document.getElementById('user').value, p = document.getElementById('pass').value;
      if(!u || !p) return alert("Masukkan Nama & Password!");
      toggleLoader(true);
      google.script.run.withSuccessHandler(res => {
        toggleLoader(false);
        if(res.sukses) { userData = res; showDash(); } else { alert(res.pesan); }
      }).loginSiswa(u, p);
    }

    function showDash() {
      document.getElementById('loginSec').style.display='none';
      document.getElementById('dashSec').style.display='block';
      document.getElementById('txtNama').innerText = "Halo, " + userData.nama;
      document.getElementById('txtKelas').innerText = "Kelas: " + userData.kelas;
      renderRiwayatUI();
    }

    function renderRiwayatUI() {
      const list = document.getElementById('listRiwayat'), pTabel = document.getElementById('pTabelSiswa');
      list.innerHTML = ""; pTabel.innerHTML = "";
      userData.dataRiwayat.forEach((item, i) => {
        list.innerHTML += `<div class="jurnal-item">${item}</div>`;
        pTabel.innerHTML += `<tr><td align="center">${i+1}</td><td>${item}</td></tr>`;
      });
      document.getElementById('pNama').innerText = userData.nama;
      document.getElementById('pKelas').innerText = userData.kelas;
    }

    function simpanJurnal() {
      const teks = document.getElementById('isiJurnal').value;
      if(!teks) return alert("Isi jurnal terlebih dahulu!");
      toggleLoader(true);
      google.script.run.withSuccessHandler(res => {
        toggleLoader(false);
        if(res === "Berhasil") {
          const tgl = new Date().toLocaleDateString('id-ID');
          userData.dataRiwayat.push("[" + tgl + "] " + teks);
          document.getElementById('successModal').style.display = 'block';
        } else {
          alert(res);
        }
      }).prosesJurnal(userData.nama, userData.kelas, teks);
    }

    function tutupModalSukses() {
      document.getElementById('successModal').style.display = 'none';
      document.getElementById('isiJurnal').value = "";
      renderRiwayatUI();
    }

    function getFormattedDate() {
      const d = new Date();
      const bln = ["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember"];
      return d.getDate() + " " + bln[d.getMonth()] + " " + d.getFullYear();
    }

    function cetakSiswa() {
      document.querySelectorAll('.tglCetak').forEach(el => el.innerText = getFormattedDate());
      document.getElementById('mainBody').className = "mode-siswa";
      window.print();
    }

    function showAdmin() { document.getElementById('loginSec').style.display='none'; document.getElementById('adminSec').style.display='block'; }
    
    function cekAdmin() {
      if(document.getElementById('adminPass').value === 'adminsejarah31.com') {
        document.getElementById('adminControls').style.display='block';
        google.script.run.withSuccessHandler(list => {
          document.getElementById('selKelas').innerHTML = list.map(k => `<option value="${k}">${k}</option>`).join('');
        }).ambilDaftarKelas();
      } else { alert("Password Admin Salah!"); }
    }

    function cetakAdmin() {
      const kls = document.getElementById('selKelas').value;
      toggleLoader(true);
      google.script.run.withSuccessHandler(res => {
        toggleLoader(false);
        if(res.sukses) {
          document.querySelectorAll('.tglCetak').forEach(el => el.innerText = getFormattedDate());
          document.getElementById('adKelas').innerText = res.kelas;
          document.getElementById('adHead').innerHTML = "<tr>" + res.header.map(h => `<th>${h}</th>`).join('') + "</tr>";
          document.getElementById('adBody').innerHTML = res.rows.map(row => "<tr>" + row.map(c => `<td>${c||''}</td>`).join('') + "</tr>").join('');
          document.getElementById('mainBody').className = "mode-admin";
          window.print();
        }
      }).ambilDataSatuKelas(kls);
    }

    function openPassModal() { document.getElementById('passModal').style.display='block'; }
    
    function prosesPass() {
      const o = document.getElementById('oldP').value, n = document.getElementById('newP').value;
      toggleLoader(true);
      google.script.run.withSuccessHandler(res => { 
        toggleLoader(false); 
        alert(res === "Berhasil" ? "Password berhasil diubah!" : res); 
        if(res === "Berhasil") document.getElementById('passModal').style.display='none';
      }).gantiPassword(userData.nama, o, n);
    }
  </script>
</body>
</html>

sejarah31.com
sejarah31.com sejarah31.com adalah web yang dibuat untuk berbagi informasi seputar dunia pendidikan dan sejarah.

Posting Komentar untuk "Cara Canggih Pantau Literasi Murid Tanpa Perlu Periksa Buku Satu-Satu!"