Cara Membuat E-Presensi Menarik Tanpa Koneksi Internet
Di zaman sekarang, pencatatan kehadiran secara digital atau e-presensi sudah menjadi hal yang umum. Namun, seringkali kita terkendala oleh koneksi internet yang tidak stabil. Artikel ini akan memandu Anda untuk membuat sistem presensi digital yang menarik, mudah, dan yang terpenting, bisa berfungsi 100% offline hanya dengan satu file HTML.
Dengan solusi ini, Anda bisa melakukan absensi di mana saja, bahkan di lokasi tanpa sinyal sekalipun. Data yang dihasilkan pun dapat diekspor ke format PDF dan Excel dengan mudah. Mari kita mulai!
Kelebihan E-Presensi Offline Ini
- Anti Gagal Internet: Sepenuhnya berjalan di browser tanpa butuh koneksi.
- Sangat Portabel: Cukup simpan di flashdisk dan gunakan di komputer mana pun.
- Gratis Selamanya: Tidak ada biaya langganan atau fitur terkunci.
- Privasi Terjamin: Data presensi aman tersimpan di perangkat Anda.
- Mudah Disesuaikan: Ganti daftar nama siswa semudah mengedit teks.
Langkah-Langkah Pembuatan
Ikuti panduan sederhana berikut untuk membuat aplikasi presensi Anda.
-
Siapkan Editor Teks
Buka aplikasi editor teks bawaan seperti Notepad di Windows. Anda tidak perlu menginstal aplikasi khusus.
-
Salin dan Tempel Kode hmtl
buatlah sebuah kode html presensi online menggunakan prompt melalui Chatgpt atau Salin seluruh kode di bawah ini dan tempelkan ke dalam Notepad. .
<!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Presensi Siswa Ceria (dengan Ekspor)</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> <style> @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&family=Fredoka+One&display=swap'); :root { --primary-color: #3498db; --secondary-color: #2ecc71; --accent-color: #f1c40f; --danger-color: #e74c3c; --pdf-color: #e74c3c; --excel-color: #27ae60; --bg-color: #f7f9fc; --text-color: #333; --light-gray: #ecf0f1; --white: #ffffff; --shadow: 0 4px 15px rgba(0, 0, 0, 0.1); } body { font-family: 'Poppins', sans-serif; background-color: var(--bg-color); color: var(--text-color); margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .container { width: 100%; max-width: 800px; background-color: var(--white); border-radius: 20px; box-shadow: var(--shadow); overflow: hidden; } .header { background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: var(--white); padding: 30px; text-align: center; } .header h1 { font-family: 'Fredoka One', cursive; margin: 0; font-size: 2.5em; letter-spacing: 2px; text-shadow: 2px 2px 4px rgba(0,0,0,0.2); } .info-sekolah { padding: 20px 30px; background-color: #f8f9fa; border-bottom: 1px solid var(--light-gray); display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; } .info-item { display: flex; flex-direction: column; } .info-item label { font-weight: 600; color: var(--primary-color); margin-bottom: 5px; } .info-item input { padding: 10px; border: 1px solid var(--light-gray); border-radius: 8px; font-size: 1em; } input[type="date"] { cursor: pointer; } .main-content { padding: 30px; } .kontrol-presensi { display: flex; justify-content: space-around; flex-wrap: wrap; gap: 10px; margin-bottom: 25px; padding: 15px; background-color: var(--bg-color); border-radius: 12px; } .kontrol-presensi button { flex-grow: 1; padding: 12px 20px; border: none; border-radius: 8px; font-size: 1em; font-weight: 600; cursor: pointer; transition: all 0.3s ease; color: var(--white); } #btn-hadir-semua { background-color: var(--secondary-color); } #btn-izin-semua { background-color: var(--accent-color); color: #5c4b01; } #btn-sakit-semua { background-color: var(--danger-color); } #btn-reset { background-color: #95a5a6; } .kontrol-presensi button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.15); } .tabel-presensi { width: 100%; border-collapse: collapse; } .tabel-presensi th, .tabel-presensi td { padding: 15px; text-align: left; border-bottom: 1px solid var(--light-gray); } .tabel-presensi th { background-color: #f2f2f2; font-weight: 600; } .tabel-presensi tr:last-child td { border-bottom: none; } .tabel-presensi tr:hover { background-color: #f5f5f5; } .status-kehadiran { display: flex; gap: 10px; justify-content: center; } .status-kehadiran input[type="radio"] { display: none; } .status-kehadiran label { padding: 5px 12px; border-radius: 20px; cursor: pointer; transition: all 0.3s ease; font-weight: 500; border: 2px solid transparent; } .status-kehadiran input[type="radio"]:checked + label { color: var(--white); box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .hadir-label { border-color: var(--secondary-color); color: var(--secondary-color); } .izin-label { border-color: var(--accent-color); color: var(--accent-color); } .sakit-label { border-color: var(--danger-color); color: var(--danger-color); } .status-kehadiran input[name^="status_"]:checked + .hadir-label { background-color: var(--secondary-color); } .status-kehadiran input[name^="status_"]:checked + .izin-label { background-color: var(--accent-color); } .status-kehadiran input[name^="status_"]:checked + .sakit-label { background-color: var(--danger-color); } .tombol-aksi { display: flex; gap: 15px; margin-top: 30px; } .export-button { flex-grow: 1; padding: 15px; border: none; border-radius: 10px; color: var(--white); font-size: 1.1em; font-weight: 700; cursor: pointer; transition: all 0.3s ease; display: flex; justify-content: center; align-items: center; gap: 8px; } .export-button:hover { transform: scale(1.02); box-shadow: var(--shadow); } #export-pdf { background-color: var(--pdf-color); } #export-excel { background-color: var(--excel-color); } .footer { text-align: center; padding: 20px; font-size: 0.9em; color: #777; background-color: #f8f9fa; } </style> </head> <body> <div class="container" id="area-cetak"> <header class="header"> <h1>📝 Presensi Siswa</h1> </header> <section class="info-sekolah"> <div class="info-item"><label for="nama-sekolah">Nama Sekolah</label><input type="text" id="nama-sekolah" value="SDN Ceria 1 Pagi"></div> <div class="info-item"><label for="nama-guru">Nama Guru</label><input type="text" id="nama-guru" value="Ibu Budiarti, S.Pd."></div> <div class="info-item"><label for="kelas">Kelas</label><input type="text" id="kelas" value="4A"></div> <div class="info-item"><label for="tanggal">Tanggal</label><input type="date" id="tanggal"></div> </section> <main class="main-content"> <div class="kontrol-presensi"> <button id="btn-hadir-semua">✅ Hadir Semua</button> <button id="btn-izin-semua">📒 Izin Semua</button> <button id="btn-sakit-semua"> Sakit Semua</button> <button id="btn-reset">🔄 Reset</button> </div> <form id="form-presensi"> <table class="tabel-presensi"> <thead> <tr> <th>No.</th> <th>Nama Siswa</th> <th style="text-align: center;">Kehadiran</th> </tr> </thead> <tbody id="daftar-siswa"> </tbody> </table> </form> <div class="tombol-aksi"> <button id="export-pdf" class="export-button">📄 Ekspor ke PDF</button> <button id="export-excel" class="export-button">📊 Ekspor ke Excel</button> </div> </main> <footer class="footer"> <p>© 2025 - Dibuat dengan ❤️ untuk Pendidikan</p> </footer> </div> <script> const daftarNamaSiswa = [ "Aditya Pratama", "Aisyah Putri", "Alif Rahman", "Amanda Sari", "Bayu Segara", "Bunga Lestari", "Cahya Wulan", "Daffa Firmansyah", "Dewi Anggraini", "Eka Saputra", "Fajar Nugroho", "Fitriani", "Gita Permata", "Hafiz Maulana", "Indah Puspita", "Joko Susilo", "Kartika Chandra", "Lia Amelia", "Maulana Yusuf", "Nadia ananda", "Putri Lestari", "Rizky Akbar", "Siti Nurhaliza", "Tegar Perkasa", "Zahra Aulia" ]; function generateDaftarSiswa() { const tbody = document.getElementById('daftar-siswa'); tbody.innerHTML = ''; daftarNamaSiswa.forEach((nama, index) => { const tr = document.createElement('tr'); tr.innerHTML = ` <td>${index + 1}</td> <td>${nama}</td> <td> <div class="status-kehadiran"> <input type="radio" id="hadir_${index}" name="status_${index}" value="Hadir" checked> <label for="hadir_${index}" class="hadir-label">Hadir</label> <input type="radio" id="izin_${index}" name="status_${index}" value="Izin"> <label for="izin_${index}" class="izin-label">Izin</label> <input type="radio" id="sakit_${index}" name="status_${index}" value="Sakit"> <label for="sakit_${index}" class="sakit-label">Sakit</label> </div> </td>`; tbody.appendChild(tr); }); } function tandaiSemua(status) { document.querySelectorAll(`input[type="radio"][value="${status}"]`).forEach(radio => radio.checked = true); } document.getElementById('btn-hadir-semua').addEventListener('click', () => tandaiSemua('Hadir')); document.getElementById('btn-izin-semua').addEventListener('click', () => tandaiSemua('Izin')); document.getElementById('btn-sakit-semua').addEventListener('click', () => tandaiSemua('Sakit')); document.getElementById('btn-reset').addEventListener('click', () => tandaiSemua('Hadir')); function getInfo() { const kelas = document.getElementById('kelas').value || "kelas"; const tanggal = document.getElementById('tanggal').value || new Date().toISOString().split('T')[0]; return { kelas, tanggal }; } // --- FUNGSI EKSPOR PDF --- document.getElementById('export-pdf').addEventListener('click', () => { const { jsPDF } = window.jspdf; const element = document.getElementById('area-cetak'); const { kelas, tanggal } = getInfo(); const fileName = `Presensi_${kelas}_${tanggal}.pdf`; html2canvas(element, { scale: 2 }).then(canvas => { // scale: 2 untuk kualitas lebih baik const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF('p', 'mm', 'a4'); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = (canvas.height * pdfWidth) / canvas.width; pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); pdf.save(fileName); }); }); // --- FUNGSI EKSPOR EXCEL --- document.getElementById('export-excel').addEventListener('click', () => { const { kelas, tanggal } = getInfo(); const fileName = `Presensi_${kelas}_${tanggal}.xlsx`; // 1. Kumpulkan data siswa const dataSiswa = daftarNamaSiswa.map((nama, index) => { const status = document.querySelector(`input[name="status_${index}"]:checked`).value; return { "No.": index + 1, "Nama Siswa": nama, "Status Kehadiran": status }; }); // 2. Kumpulkan info sekolah const infoSekolah = [ { "Laporan Presensi Kelas": document.getElementById('kelas').value }, { "Nama Sekolah": document.getElementById('nama-sekolah').value }, { "Nama Guru": document.getElementById('nama-guru').value }, { "Tanggal": tanggal }, {} // Baris kosong sebagai pemisah ]; // 3. Buat worksheet dari JSON const wsInfo = XLSX.utils.json_to_sheet(infoSekolah, { skipHeader: true }); const wsSiswa = XLSX.utils.json_to_sheet(dataSiswa); // 4. Buat workbook baru dan tambahkan worksheet const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, wsInfo, "Presensi"); // Sheet utama XLSX.utils.sheet_add_json(wb.Sheets["Presensi"], dataSiswa, { origin: "A6" }); // Tambahkan data siswa mulai dari baris A6 // Atur lebar kolom agar rapi wb.Sheets["Presensi"]["!cols"] = [{ wch: 5 }, { wch: 30 }, { wch: 20 }]; // 5. Tulis file Excel dan unduh XLSX.writeFile(wb, fileName); }); document.addEventListener('DOMContentLoaded', () => { const today = new Date().toISOString().split('T')[0]; document.getElementById('tanggal').value = today; generateDaftarSiswa(); }); </script> </body> </html>
-
Simpan Sebagai File HTML
Ini adalah bagian terpenting. Saat menyimpan, ikuti cara ini:
- Pilih menu File > Save As...
- Pada File name, ketik nama file dengan akhiran .html, contoh:
presensi.html
- Pada Save as type, pastikan Anda memilih "All Files".
-
Jalankan di Browser
Cari file yang baru Anda simpan, lalu klik dua kali. File tersebut akan langsung terbuka di browser (seperti Chrome atau Firefox) dan siap digunakan untuk mencatat kehadiran.
Kustomisasi Daftar Nama
Untuk mengubah daftar nama siswa, buka kembali file presensi.html dengan Notepad. rubah kode nama di dalam tanda petik
const const daftarNamaSiswa = [
"Aditya Pratama", "Aisyah Putri", "Alif Rahman", "Amanda Sari", "Bayu Segara",
"Bunga Lestari", "Cahya Wulan", "Daffa Firmansyah", "Dewi Anggraini", "Eka Saputra",
"Fajar Nugroho", "Fitriani", "Gita Permata", "Hafiz Maulana", "Indah Puspita",
"Joko Susilo", "Kartika Chandra", "Lia Amelia", "Maulana Yusuf", "Nadia ananda",
"Putri Lestari", "Rizky Akbar", "Siti Nurhaliza", "Tegar Perkasa", "Zahra Aulia"
];/code>