bahar2509

Presensi/GoogleScript

Jan 19th, 2026 (edited)
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 16.98 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="id">
  3. <head>
  4.     <base target="_top">
  5.     <meta charset="utf-8">
  6.     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  7.     <title>Online Attendance Records System</title>
  8.    
  9.     <!-- Google Fonts & Bootstrap -->
  10.     <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
  11.     <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
  12.     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  13.    
  14.     <style>
  15.         :root {
  16.             --sea-deep: #002b5b;
  17.             --sea-main: #0052d4;
  18.             --sea-light: #00d2ff;
  19.             --bg-body: #f8fafc;
  20.             --card-border: rgba(255, 255, 255, 0.8);
  21.             --shadow-sm: 0 2px 4px rgba(0,0,0,0.02);
  22.             --shadow-md: 0 10px 25px -5px rgba(0,0,0,0.05);
  23.             --glass: rgba(255, 255, 255, 0.9);
  24.         }
  25.  
  26.         body {
  27.             background-color: var(--bg-body);
  28.             font-family: 'Plus Jakarta Sans', sans-serif;
  29.             color: #1e293b;
  30.             padding-bottom: 120px;
  31.         }
  32.  
  33.         /* --- Professional Header --- */
  34.         .header-enterprise {
  35.             background: linear-gradient(135deg, var(--sea-deep) 0%, var(--sea-main) 50%, var(--sea-light) 100%);
  36.             padding: 40px 0 80px;
  37.             color: white;
  38.             border-bottom-left-radius: 50px;
  39.             border-bottom-right-radius: 50px;
  40.             position: relative;
  41.             box-shadow: 0 10px 30px rgba(0, 82, 212, 0.2);
  42.         }
  43.  
  44.         .header-content {
  45.             display: flex;
  46.             align-items: center;
  47.             justify-content: space-between;
  48.             gap: 20px;
  49.         }
  50.  
  51.         .logo-placeholder {
  52.             width: 70px;
  53.             height: 70px;
  54.             background: rgba(255,255,255,0.15);
  55.             backdrop-filter: blur(10px);
  56.             border-radius: 15px;
  57.             display: flex;
  58.             align-items: center;
  59.             justify-content: center;
  60.             border: 1px solid rgba(255,255,255,0.2);
  61.         }
  62.        
  63.         .logo-placeholder img { width: 50px; height: auto; }
  64.  
  65.         .brand-title h2 {
  66.             font-weight: 800;
  67.             letter-spacing: -1px;
  68.             margin: 0;
  69.             text-shadow: 0 2px 4px rgba(0,0,0,0.1);
  70.         }
  71.  
  72.         /* --- Selector Styling --- */
  73.         .class-selector-wrapper {
  74.             max-width: 500px;
  75.             margin: -40px auto 0;
  76.             position: relative;
  77.             z-index: 10;
  78.             padding: 0 20px;
  79.         }
  80.  
  81.         .custom-select-card {
  82.             background: var(--glass);
  83.             backdrop-filter: blur(15px);
  84.             padding: 20px;
  85.             border-radius: 24px;
  86.             box-shadow: var(--shadow-md);
  87.             border: 1px solid var(--card-border);
  88.         }
  89.  
  90.         .form-select-custom {
  91.             border-radius: 12px;
  92.             border: 2px solid #e2e8f0;
  93.             padding: 12px 20px;
  94.             font-weight: 600;
  95.             transition: all 0.3s;
  96.         }
  97.  
  98.         .form-select-custom:focus {
  99.             border-color: var(--sea-main);
  100.             box-shadow: 0 0 0 4px rgba(0, 82, 212, 0.1);
  101.         }
  102.  
  103.         /* --- List Section --- */
  104.         .stats-bar {
  105.             display: flex;
  106.             justify-content: space-between;
  107.             align-items: center;
  108.             margin-bottom: 15px;
  109.             padding: 0 10px;
  110.         }
  111.  
  112.         .badge-stats {
  113.             background: white;
  114.             color: var(--sea-deep);
  115.             padding: 8px 16px;
  116.             border-radius: 12px;
  117.             font-weight: 700;
  118.             font-size: 0.9rem;
  119.             box-shadow: var(--shadow-sm);
  120.             border: 1px solid #e2e8f0;
  121.         }
  122.  
  123.         .student-card {
  124.             background: white;
  125.             border-radius: 20px;
  126.             padding: 15px 20px;
  127.             margin-bottom: 12px;
  128.             border: 1px solid #f1f5f9;
  129.             transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  130.             display: flex;
  131.             align-items: center;
  132.             justify-content: space-between;
  133.             gap: 15px;
  134.         }
  135.  
  136.         .student-card:hover {
  137.             transform: translateY(-3px);
  138.             box-shadow: 0 12px 20px -5px rgba(0,0,0,0.05);
  139.             border-color: var(--sea-light);
  140.         }
  141.  
  142.         .student-identity {
  143.             display: flex;
  144.             align-items: center;
  145.             gap: 15px;
  146.         }
  147.  
  148.         .student-id-circle {
  149.             width: 38px;
  150.             height: 38px;
  151.             background: #f1f5f9;
  152.             color: var(--sea-deep);
  153.             display: flex;
  154.             align-items: center;
  155.             justify-content: center;
  156.             border-radius: 12px;
  157.             font-weight: 800;
  158.             font-size: 0.85rem;
  159.         }
  160.  
  161.         .student-name {
  162.             font-weight: 700;
  163.             color: #334155;
  164.             font-size: 1.05rem;
  165.         }
  166.  
  167.         /* --- Radio Button Groups --- */
  168.         .presence-btn-group {
  169.             display: grid;
  170.             grid-template-columns: repeat(6, 1fr);
  171.             gap: 6px;
  172.             background: #f8fafc;
  173.             padding: 5px;
  174.             border-radius: 14px;
  175.         }
  176.  
  177.         .btn-check + .btn-pills {
  178.             width: 42px;
  179.             height: 42px;
  180.             display: flex;
  181.             align-items: center;
  182.             justify-content: center;
  183.             border-radius: 10px;
  184.             font-weight: 700;
  185.             font-size: 0.8rem;
  186.             border: none;
  187.             background: transparent;
  188.             color: #94a3b8;
  189.             transition: all 0.2s;
  190.             cursor: pointer;
  191.         }
  192.  
  193.         /* Active States Colors */
  194.         .btn-check:checked + .btn-pills.h { background: #10b981; color: white; box-shadow: 0 4px 10px rgba(16, 185, 129, 0.3); }
  195.         .btn-check:checked + .btn-pills.a { background: #ef4444; color: white; box-shadow: 0 4px 10px rgba(239, 68, 68, 0.3); }
  196.         .btn-check:checked + .btn-pills.i { background: #3b82f6; color: white; box-shadow: 0 4px 10px rgba(59, 130, 246, 0.3); }
  197.         .btn-check:checked + .btn-pills.s { background: #f59e0b; color: white; box-shadow: 0 4px 10px rgba(245, 158, 11, 0.3); }
  198.         .btn-check:checked + .btn-pills.t { background: #64748b; color: white; box-shadow: 0 4px 10px rgba(100, 116, 139, 0.3); }
  199.         .btn-check:checked + .btn-pills.b { background: #1e293b; color: white; box-shadow: 0 4px 10px rgba(30, 41, 59, 0.3); }
  200.  
  201.         /* --- Floating Footer --- */
  202.         .fab-container {
  203.             position: fixed;
  204.             bottom: 30px;
  205.             left: 50%;
  206.             transform: translateX(-50%);
  207.             width: 100%;
  208.             max-width: 400px;
  209.             padding: 0 25px;
  210.             z-index: 100;
  211.         }
  212.  
  213.         .btn-submit-modern {
  214.             background: linear-gradient(135deg, var(--sea-deep) 0%, var(--sea-main) 100%);
  215.             border: none;
  216.             color: white;
  217.             width: 100%;
  218.             padding: 16px;
  219.             border-radius: 20px;
  220.             font-weight: 700;
  221.             font-size: 1.1rem;
  222.             box-shadow: 0 15px 30px rgba(0, 82, 212, 0.3);
  223.             display: flex;
  224.             align-items: center;
  225.             justify-content: center;
  226.             gap: 12px;
  227.             transition: all 0.3s;
  228.         }
  229.  
  230.         .btn-submit-modern:hover {
  231.             transform: translateY(-5px);
  232.             box-shadow: 0 20px 40px rgba(0, 82, 212, 0.4);
  233.         }
  234.  
  235.         @media (max-width: 768px) {
  236.             .header-enterprise { padding: 30px 0 60px; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; }
  237.             .logo-placeholder { width: 50px; height: 50px; }
  238.             .logo-placeholder img { width: 35px; }
  239.             .brand-title h2 { font-size: 1.3rem; }
  240.             .student-card { flex-direction: column; align-items: flex-start; }
  241.             .presence-btn-group { width: 100%; }
  242.             .btn-check + .btn-pills { width: 100%; }
  243.         }
  244.     </style>
  245. </head>
  246. <body>
  247.  
  248.     <!-- Professional Header -->
  249.     <header class="header-enterprise">
  250.         <div class="container">
  251.             <div class="header-content">
  252.                 <!-- Logo Kiri -->
  253.                 <div class="logo-placeholder">
  254.                     <img src="https://cdn-icons-png.flaticon.com/512/2830/2830305.png" alt="Logo Left">
  255.                 </div>
  256.                
  257.                 <div class="brand-title text-center">
  258.                     <p class="mb-1 text-uppercase fw-bold opacity-75" style="font-size: 0.75rem; letter-spacing: 2px;">Smart Education System</p>
  259.                     <h2>PRESENSI ONLINE</h2>
  260.                     <div class="mt-2 opacity-75 small">
  261.                         <i class="far fa-calendar-check me-1"></i> <span id="tanggalHariIni"></span>
  262.                     </div>
  263.                 </div>
  264.  
  265.                 <!-- Logo Kanan -->
  266.                 <div class="logo-placeholder">
  267.                     <img src="https://cdn-icons-png.flaticon.com/512/5968/5968260.png" alt="Logo Right">
  268.                 </div>
  269.             </div>
  270.         </div>
  271.     </header>
  272.  
  273.     <!-- Class Selector Box -->
  274.     <section class="class-selector-wrapper">
  275.         <div class="custom-select-card">
  276.             <label class="form-label fw-bold text-muted mb-2 ps-1" style="font-size: 0.8rem;">PILIH UNIT/KELAS :</label>
  277.             <select id="selectKelas" class="form-select form-select-custom" onchange="loadSiswaKelas(this.value)">
  278.                 <option value="" selected disabled>-- Pilih Kelas --</option>
  279.                 <option value="" disabled>Menghubungkan ke server...</option>
  280.             </select>
  281.         </div>
  282.     </section>
  283.  
  284.     <!-- Main List Container -->
  285.     <main class="container mt-5" style="max-width: 800px;">
  286.        
  287.         <!-- Loading State -->
  288.         <div id="loading-spinner" class="text-center py-5">
  289.             <div class="spinner-border text-primary mb-3" role="status" style="width: 3rem; height: 3rem;"></div>
  290.             <p class="text-muted fw-bold">Sinkronisasi Database...</p>
  291.         </div>
  292.  
  293.         <!-- Form Area -->
  294.         <div id="formArea" style="display:none;">
  295.             <div class="stats-bar">
  296.                 <h6 class="m-0 fw-800 text-uppercase" id="labelKelasTerpilih" style="color: var(--sea-deep); letter-spacing: 0.5px;"></h6>
  297.                 <div class="badge-stats" id="countBadge">
  298.                     <i class="fas fa-users me-2 text-primary"></i> Jumlah Siswa: <span id="jumlahSiswa">0</span>
  299.                 </div>
  300.             </div>
  301.            
  302.             <div id="listSiswa">
  303.                 <!-- Cards injected here -->
  304.             </div>
  305.         </div>
  306.     </main>
  307.  
  308.     <!-- Floating Action Button -->
  309.     <div class="fab-container" id="submitArea" style="display:none;">
  310.         <button type="button" class="btn btn-submit-modern" onclick="konfirmasiKirim()">
  311.             <i class="fas fa-cloud-upload-alt"></i> SIMPAN PRESENSI
  312.         </button>
  313.     </div>
  314.  
  315.     <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
  316.     <script>
  317.         // Set Tanggal
  318.         document.getElementById('tanggalHariIni').innerText = new Date().toLocaleDateString('id-ID', {
  319.             weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
  320.         });
  321.  
  322.         // 1. Ambil Daftar Kelas
  323.         google.script.run.withSuccessHandler(initKelas).getDaftarKelas();
  324.  
  325.         function initKelas(data) {
  326.             var select = document.getElementById('selectKelas');
  327.             select.innerHTML = '<option value="" selected disabled>-- Pilih Kelas --</option>';
  328.             data.kelas.forEach(function(cls) {
  329.                 var option = document.createElement("option");
  330.                 option.text = cls; option.value = cls;
  331.                 select.add(option);
  332.             });
  333.             document.getElementById('loading-spinner').style.display = 'none';
  334.         }
  335.  
  336.         // 2. Load Siswa
  337.         function loadSiswaKelas(namaKelas) {
  338.             if(!namaKelas) return;
  339.             document.getElementById('loading-spinner').style.display = 'block';
  340.             document.getElementById('formArea').style.display = 'none';
  341.             document.getElementById('submitArea').style.display = 'none';
  342.             google.script.run.withSuccessHandler(renderSiswa).getDaftarSiswa(namaKelas);
  343.         }
  344.  
  345.         function renderSiswa(siswaList) {
  346.             var container = document.getElementById('listSiswa');
  347.             var namaKelas = document.getElementById('selectKelas').value;
  348.            
  349.             document.getElementById('labelKelasTerpilih').innerText = 'Daftar ' + namaKelas;
  350.             document.getElementById('jumlahSiswa').innerText = siswaList.length;
  351.            
  352.             var html = '';
  353.             if (!siswaList || siswaList.length === 0) {
  354.                 html = `<div class="text-center py-5"><img src="https://cdn-icons-png.flaticon.com/512/7486/7486744.png" style="width:80px; opacity:0.3" class="mb-3"><p class="text-muted">Data siswa tidak ditemukan pada sheet ${namaKelas}.</p></div>`;
  355.             } else {
  356.                 siswaList.forEach(function(nama, index) {
  357.                     html += `
  358.                     <div class="student-card">
  359.                         <div class="student-identity">
  360.                             <div class="student-id-circle">${index + 1}</div>
  361.                             <div class="student-name">${nama}</div>
  362.                         </div>
  363.                         <div class="presence-btn-group" role="group">
  364.                             <input type="radio" class="btn-check" name="siswa${index}" id="h_${index}" value="H" checked>
  365.                             <label class="btn-pills h" for="h_${index}" title="Hadir">H</label>
  366.                            
  367.                             <input type="radio" class="btn-check" name="siswa${index}" id="a_${index}" value="A">
  368.                             <label class="btn-pills a" for="a_${index}" title="Alpha">A</label>
  369.                            
  370.                             <input type="radio" class="btn-check" name="siswa${index}" id="i_${index}" value="I">
  371.                             <label class="btn-pills i" for="i_${index}" title="Izin">I</label>
  372.                            
  373.                             <input type="radio" class="btn-check" name="siswa${index}" id="s_${index}" value="S">
  374.                             <label class="btn-pills s" for="s_${index}" title="Sakit">S</label>
  375.  
  376.                             <input type="radio" class="btn-check" name="siswa${index}" id="t_${index}" value="T">
  377.                             <label class="btn-pills t" for="t_${index}" title="Terlambat">T</label>
  378.  
  379.                             <input type="radio" class="btn-check" name="siswa${index}" id="b_${index}" value="B">
  380.                             <label class="btn-pills b" for="b_${index}" title="Bolos">B</label>
  381.                         </div>
  382.                     </div>`;
  383.                 });
  384.             }
  385.             container.innerHTML = html;
  386.             document.getElementById('loading-spinner').style.display = 'none';
  387.             document.getElementById('formArea').style.display = 'block';
  388.             if(siswaList.length > 0) document.getElementById('submitArea').style.display = 'block';
  389.         }
  390.  
  391.         function konfirmasiKirim() {
  392.             Swal.fire({
  393.                 title: 'Simpan Kehadiran?',
  394.                 text: "Data akan dicatat ke sistem pusat.",
  395.                 icon: 'question',
  396.                 showCancelButton: true,
  397.                 confirmButtonColor: '#0052d4',
  398.                 cancelButtonColor: '#94a3b8',
  399.                 confirmButtonText: 'Ya, Simpan!',
  400.                 cancelButtonText: 'Batal',
  401.                 customClass: { popup: 'rounded-4' }
  402.             }).then((result) => {
  403.                 if (result.isConfirmed) kirimData();
  404.             })
  405.         }
  406.  
  407.         function kirimData() {
  408.             Swal.fire({
  409.                 title: 'Memproses...',
  410.                 html: 'Mohon tunggu sebentar',
  411.                 allowOutsideClick: false,
  412.                 didOpen: () => Swal.showLoading()
  413.             });
  414.  
  415.             var cards = document.getElementsByClassName('student-card');
  416.             var dataKehadiran = [];
  417.             var namaKelas = document.getElementById('selectKelas').value;
  418.  
  419.             for (var i = 0; i < cards.length; i++) {
  420.                var checkedRadio = document.querySelector(`input[name="siswa${i}"]:checked`);
  421.                dataKehadiran.push(checkedRadio ? checkedRadio.value : 'A');
  422.            }
  423.  
  424.            google.script.run.withSuccessHandler(function(resp) {
  425.                Swal.fire({
  426.                    icon: 'success',
  427.                    title: 'Berhasil!',
  428.                    text: resp,
  429.                    timer: 2000,
  430.                    showConfirmButton: false
  431.                });
  432.            }).simpanPresensi(dataKehadiran, namaKelas);
  433.        }
  434.    </script>
  435. </body>
  436. </html>
Tags: presensi
Add Comment
Please, Sign In to add comment