Kode gs

 var SPREADSHEET_ID = "";

var SHEET_SISWA = "Siswa";

var SHEET_ABSENSI = "Absensi"; 

var SHEET_SETTINGS = "Settings";

function doGet(e) {

  var page = e.parameter.page || "student";

  var html = buildHtml(page);

  return HtmlService.createHtmlOutput(html).setTitle("Admin Absensi MTsN 1 Medan").setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);

}

function buildHtml(page) {

  var css = getCSS();

  var body = page === "admin" ? getAdminHTML() : getStudentHTML();

  var js = page === "admin" ? getAdminJS() + getAdminJS2() + getAdminJS3() + getAdminJS4() : getStudentJS();

  return "<!DOCTYPE html><html><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width,initial-scale=1'><link href='https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap' rel='stylesheet'><script src='https://unpkg.com/lucide@latest'></script><script src='https://unpkg.com/html5-qrcode@2.3.4/html5-qrcode.min.js'></script><script src='https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.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>" + css + "</style></head><body>" + body + "<script>" + js + "</script></body></html>";

}

function getCSS() {

  return "*{margin:0;padding:0;box-sizing:border-box}:root{--primary:#3b82f6;--success:#22c55e;--warning:#f59e0b;--danger:#ef4444;--info:#06b6d4;--purple:#8b5cf6;--bg:#f8fafc;--white:#fff;--border:#e5e7eb;--text:#1f2937;--gray:#6b7280}body{font-family:Poppins,sans-serif;background:var(--bg);color:var(--text)}.app{display:flex;min-height:100vh}.sidebar{width:240px;background:var(--white);border-right:1px solid var(--border);position:fixed;top:0;left:0;bottom:0;display:flex;flex-direction:column}.sidebar-header{padding:20px;display:flex;align-items:center;gap:12px}.logo-icon{width:40px;height:40px;background:var(--info);border-radius:10px;display:flex;align-items:center;justify-content:center;color:#fff}.logo-text h1{font-size:16px;font-weight:700}.logo-text p{font-size:11px;color:var(--gray)}.nav{flex:1;padding:10px 0}.nav-item{display:flex;align-items:center;gap:12px;padding:12px 20px;color:var(--gray);cursor:pointer;font-size:14px;font-weight:500;margin:2px 10px;border-radius:8px}.nav-item:hover{background:var(--bg);color:var(--text)}.nav-item.active{background:var(--primary);color:#fff}.nav-item i{width:20px;height:20px}.sidebar-footer{padding:15px 20px;border-top:1px solid var(--border)}.user{display:flex;align-items:center;gap:10px}.user-avatar{width:36px;height:36px;background:linear-gradient(135deg,var(--primary),var(--info));border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:600;font-size:12px}.user-info{font-size:13px;font-weight:600}.user-info p{color:var(--gray);font-size:11px;font-weight:400}.main{flex:1;margin-left:240px}.topbar{background:var(--white);padding:15px 25px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border)}.topbar h2{font-size:15px;font-weight:600}.topbar-right{display:flex;align-items:center;gap:12px}.search-box{display:flex;align-items:center;gap:8px;background:var(--bg);padding:8px 14px;border-radius:8px;border:1px solid var(--border)}.search-box input{border:none;background:transparent;outline:none;font-size:13px;width:150px;font-family:Poppins}.topbar-icon{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--gray)}.content{padding:25px}.page-header{margin-bottom:20px}.page-header h1{font-size:24px;font-weight:700;margin-bottom:5px}.page-header p{color:var(--gray);font-size:13px}.header-actions{display:flex;gap:10px;margin-top:15px}.btn{padding:10px 18px;border-radius:8px;font-size:13px;font-weight:500;border:none;cursor:pointer;display:inline-flex;align-items:center;gap:6px;font-family:Poppins}.btn-primary{background:var(--primary);color:#fff}.btn-success{background:var(--success);color:#fff}.btn-danger{background:var(--danger);color:#fff}.btn-outline{background:var(--white);border:1px solid var(--border);color:var(--text)}.card{background:var(--white);border-radius:12px;border:1px solid var(--border);margin-bottom:20px}.card-body{padding:20px}.filters{display:flex;align-items:flex-end;gap:30px;margin-bottom:20px}.filter-group{display:flex;flex-direction:column;gap:8px}.filter-label{font-size:11px;color:var(--gray);text-transform:uppercase;font-weight:600;letter-spacing:.5px}.filter-tabs{display:flex}.filter-tab{padding:8px 16px;font-size:13px;border:1px solid var(--border);background:var(--white);cursor:pointer;font-family:Poppins;font-weight:500}.filter-tab:first-child{border-radius:8px 0 0 8px}.filter-tab:last-child{border-radius:0 8px 8px 0}.filter-tab:not(:first-child){margin-left:-1px}.filter-tab.active{background:var(--primary);color:#fff;border-color:var(--primary)}.date-picker{padding:8px 14px;border:1px solid var(--border);border-radius:8px;font-size:13px;display:flex;align-items:center;gap:10px;background:var(--white)}.table{width:100%;border-collapse:collapse}.table th{padding:12px 16px;text-align:left;font-size:11px;text-transform:uppercase;color:var(--gray);font-weight:600;letter-spacing:.5px;border-bottom:1px solid var(--border)}.table td{padding:14px 16px;border-bottom:1px solid var(--border);font-size:13px}.table tr:hover{background:var(--bg)}.student-cell{display:flex;align-items:center;gap:12px}.avatar{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;color:#fff}.avatar.green{background:var(--success)}.avatar.blue{background:var(--primary)}.avatar.orange{background:var(--warning)}.avatar.purple{background:var(--purple)}.avatar.cyan{background:var(--info)}.badge{display:inline-block;padding:5px 12px;border-radius:6px;font-size:12px;font-weight:500}.badge-success{background:#dcfce7;color:#16a34a}.badge-warning{background:#fef3c7;color:#d97706}.badge-danger{background:#fee2e2;color:#dc2626}.badge-info{background:#cffafe;color:#0891b2}.time-late{color:var(--danger);font-weight:500}.verify-btn{width:32px;height:32px;border-radius:8px;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center}.verify-btn.verified{background:#dbeafe;color:var(--primary)}.verify-btn.not-verified{background:var(--bg);color:#9ca3af}.action-btn{width:28px;height:28px;border:none;background:transparent;cursor:pointer;color:var(--gray)}.pagination{display:flex;justify-content:space-between;align-items:center;padding:15px 0}.pagination-info{font-size:13px;color:var(--gray)}.pagination-btns{display:flex;gap:5px}.page-btn{width:32px;height:32px;border:1px solid var(--border);background:var(--white);border-radius:8px;font-size:13px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-family:Poppins}.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary)}.stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:15px;margin-bottom:25px}.stat-card{background:var(--white);padding:20px;border-radius:12px;border:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}.stat-info h3{font-size:11px;color:var(--gray);text-transform:uppercase;font-weight:600;margin-bottom:5px}.stat-info .value{font-size:32px;font-weight:700}.stat-info .trend{font-size:11px;margin-top:5px}.stat-info .trend.up{color:var(--success)}.stat-info .trend.down{color:var(--danger)}.stat-icon{width:48px;height:48px;border-radius:12px;display:flex;align-items:center;justify-content:center}.stat-icon.green{background:#dcfce7;color:var(--success)}.stat-icon.orange{background:#fef3c7;color:var(--warning)}.stat-icon.red{background:#fee2e2;color:var(--danger)}.stat-icon.blue{background:#dbeafe;color:var(--primary)}.feed-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.feed-title{font-size:18px;font-weight:700;display:flex;align-items:center;gap:12px}.live-badge{display:inline-flex;align-items:center;gap:6px;background:#fee2e2;color:#dc2626;padding:4px 10px;border-radius:6px;font-size:11px;font-weight:600}.live-dot{width:8px;height:8px;background:#dc2626;border-radius:50%;animation:pulse 1.5s infinite}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}.feed-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:20px}.feed-card{background:var(--white);border-radius:16px;overflow:hidden;border:1px solid var(--border);transition:all .2s}.feed-card:hover{transform:translateY(-4px);box-shadow:0 12px 30px rgba(0,0,0,.1)}.feed-img{position:relative}.feed-photo{width:100%;aspect-ratio:1;object-fit:cover;background:linear-gradient(135deg,#e0e7ff,#dbeafe)}.feed-badge{position:absolute;top:12px;left:12px;padding:5px 10px;border-radius:6px;font-size:10px;font-weight:600;text-transform:uppercase}.feed-badge.verified{background:var(--success);color:#fff}.feed-badge.late{background:var(--warning);color:#fff}.feed-check{position:absolute;bottom:12px;right:12px;width:24px;height:24px;background:var(--success);border-radius:50%;color:#fff;display:flex;align-items:center;justify-content:center}.feed-info{padding:14px}.feed-name{font-size:14px;font-weight:600;margin-bottom:3px}.feed-class{font-size:12px;color:var(--gray);margin-bottom:8px}.feed-time{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--gray)}.feed-time.late{color:var(--warning)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.4);display:none;align-items:center;justify-content:center;z-index:1000}.modal-overlay.active{display:flex}.modal{background:var(--white);border-radius:16px;width:100%;max-width:450px}.modal-header{padding:20px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}.modal-title{font-size:16px;font-weight:600}.modal-close{border:none;background:none;font-size:24px;cursor:pointer;color:var(--gray)}.modal-body{padding:20px}.modal-footer{padding:15px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:10px}.form-group{margin-bottom:15px}.form-label{display:block;margin-bottom:6px;font-size:13px;font-weight:500}.form-input,.form-select{width:100%;padding:10px 14px;border:1px solid var(--border);border-radius:8px;font-size:13px;font-family:Poppins}.toast-container{position:fixed;top:20px;right:20px;z-index:9999}.toast{background:var(--white);padding:12px 20px;border-radius:10px;box-shadow:0 4px 20px rgba(0,0,0,.1);margin-bottom:10px;border-left:4px solid var(--success);font-size:13px}.toast.error{border-left-color:var(--danger)}.student-page{min-height:100vh;background:linear-gradient(135deg,var(--primary),#1d4ed8);display:flex;align-items:center;justify-content:center;padding:20px}.att-box{background:var(--white);border-radius:20px;width:100%;max-width:420px;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.2)}.att-header{background:linear-gradient(135deg,var(--primary),#1d4ed8);color:#fff;padding:35px;text-align:center}.att-header h1{font-size:24px;font-weight:700}.att-body{padding:25px}.opt-btn{display:flex;align-items:center;gap:15px;padding:18px;border:1px solid var(--border);border-radius:12px;background:var(--white);cursor:pointer;width:100%;margin-bottom:12px;text-align:left;transition:all .2s}.opt-btn:hover{border-color:var(--primary);background:#eff6ff;transform:translateY(-2px)}.opt-icon{width:48px;height:48px;border-radius:12px;display:flex;align-items:center;justify-content:center}.opt-icon.blue{background:#eff6ff;color:var(--primary)}.opt-icon.green{background:#dcfce7;color:var(--success)}.opt-icon.purple{background:#ede9fe;color:var(--purple)}.opt-text h3{font-size:14px;font-weight:600;margin-bottom:3px}.opt-text p{font-size:12px;color:var(--gray)}.camera-box{width:100%;border-radius:12px;overflow:hidden;background:#000;margin-bottom:15px}.camera-video{width:100%;height:280px;object-fit:cover}.face-status{padding:12px;border-radius:8px;text-align:center;font-size:13px;margin-bottom:15px;font-weight:500}.face-status.loading{background:#fef3c7;color:#b45309}.face-status.success{background:#dcfce7;color:#16a34a}.face-status.error{background:#fee2e2;color:#dc2626}.loc-info{background:var(--bg);padding:12px 14px;border-radius:8px;font-size:12px;margin-bottom:15px;display:flex;align-items:center;gap:8px}.success-screen{text-align:center;padding:20px}.success-icon{width:80px;height:80px;border-radius:50%;color:#fff;display:flex;align-items:center;justify-content:center;margin:0 auto 20px;font-size:40px}.rekap-table{font-size:11px}.rekap-table th,.rekap-table td{padding:8px 4px;text-align:center;min-width:28px}.rekap-table .day-h{background:#dcfce7}.rekap-table .day-t{background:#fef3c7}.rekap-table .day-s{background:#ede9fe}.rekap-table .day-i{background:#cffafe}.rekap-table .day-a{background:#fee2e2}@media(max-width:768px){.sidebar{display:none}.main{margin-left:0}.stats-grid,.feed-grid{grid-template-columns:1fr 1fr}}";

}

function getAdminHTML() {

  return "<div class='app'><aside class='sidebar'><div class='sidebar-header'><div class='logo-icon'><i data-lucide='graduation-cap' style='width:22px;height:22px'></i></div><div class='logo-text'><h1>Admin MTsN 1 Medan</h1><p>Admin Portal</p></div></div><nav class='nav'><div class='nav-item active' data-page='dashboard' onclick='showPage(\"dashboard\")'><i data-lucide='home'></i><span>Dashboard</span></div><div class='nav-item' data-page='laporan' onclick='showPage(\"laporan\")'><i data-lucide='clipboard-check'></i><span>Laporan Kehadiran</span></div><div class='nav-item' data-page='rekap' onclick='showPage(\"rekap\")'><i data-lucide='calendar-range'></i><span>Rekap Bulanan</span></div><div class='nav-item' data-page='siswa' onclick='showPage(\"siswa\")'><i data-lucide='users'></i><span>Daftar Siswa</span></div><div class='nav-item' data-page='pengaturan' onclick='showPage(\"pengaturan\")'><i data-lucide='settings'></i><span>Pengaturan</span></div></nav><div class='sidebar-footer'><div class='user'><div class='user-avatar'>BS</div><div class='user-info'>Urip Darianto<p>Administrator</p></div></div></div></aside><main class='main'><div class='topbar'><h2 id='page-title'>Dashboard</h2><div class='topbar-right'><div class='search-box'><i data-lucide='search' style='width:16px;color:var(--gray)'></i><input type='text' placeholder='Cari nama siswa...'></div><div class='topbar-icon'><i data-lucide='bell' style='width:20px'></i></div><div class='topbar-icon'><i data-lucide='message-square' style='width:20px'></i></div></div></div><div class='content' id='main-content'></div></main></div><div class='modal-overlay' id='modal'></div><div class='toast-container' id='toasts'></div>";

}

function getStudentHTML() {

  return "<div class='student-page'><div class='att-box'><div class='att-header'><h1>Admin MTsN 1 Medan</h1><p>Sistem Absensi Digital</p></div><div class='att-body' id='student-content'></div></div></div><div class='toast-container' id='toasts'></div>";

}

function getAdminJS() {

  return "var currentPage='dashboard';var students=[];var attendance=[];var stats={};var rekapData=null;var filterKelas='Semua';var colors=['blue','green','orange','purple','cyan'];document.addEventListener('DOMContentLoaded',function(){lucide.createIcons();loadData();});function loadData(){google.script.run.withSuccessHandler(function(r){if(r.success)stats=r.stats;renderPage();}).getStats();google.script.run.withSuccessHandler(function(r){if(r.success)attendance=r.attendance;renderPage();}).getAttendanceToday();google.script.run.withSuccessHandler(function(r){if(r.success)students=r.students;renderPage();}).getStudentList();}function getColor(i){return colors[i%colors.length];}function showPage(p){currentPage=p;document.querySelectorAll('.nav-item').forEach(function(n){n.classList.remove('active');});document.querySelector('[data-page=\"'+p+'\"]').classList.add('active');var t={'dashboard':'Dashboard','laporan':'Laporan Kehadiran','rekap':'Rekap Bulanan','siswa':'Daftar Siswa','pengaturan':'Pengaturan'};document.getElementById('page-title').textContent=t[p];renderPage();}function renderPage(){if(currentPage==='dashboard')renderDashboard();else if(currentPage==='laporan')renderLaporan();else if(currentPage==='rekap')renderRekap();else if(currentPage==='siswa')renderSiswa();else if(currentPage==='pengaturan')renderPengaturan();}function renderDashboard(){var c=document.getElementById('main-content');var verified=0;var total=attendance.length;for(var i=0;i<attendance.length;i++){if(attendance[i].faceMatch>0.5)verified++;}var pct=total>0?((verified/total)*100).toFixed(1):'0';var feed='';if(attendance.length>0){for(var j=0;j<attendance.length;j++){var a=attendance[j];var badge=a.faceMatch>0.5?(a.status==='Terlambat'?'<span class=\"feed-badge late\">TERLAMBAT</span>':'<span class=\"feed-badge verified\">TERVERIFIKASI</span>'):'';var photoSrc=a.fotoAbsen?a.fotoAbsen:'';var photoHtml=photoSrc?'<img class=\"feed-photo\" src=\"'+photoSrc+'\">':'<div class=\"feed-photo\" style=\"display:flex;align-items:center;justify-content:center;font-size:48px;font-weight:700;color:var(--primary)\">'+getInit(a.nama)+'</div>';var timeClass=a.status==='Terlambat'?' late':'';feed+='<div class=\"feed-card\"><div class=\"feed-img\">'+photoHtml+badge+'<div class=\"feed-check\"><i data-lucide=\"check\" style=\"width:14px;height:14px\"></i></div></div><div class=\"feed-info\"><div class=\"feed-name\">'+a.nama+'</div><div class=\"feed-class\">'+a.kelas+'</div><div class=\"feed-time'+timeClass+'\"><i data-lucide=\"clock\" style=\"width:12px\"></i> '+a.waktu+' WIB</div></div></div>';}}else{feed='<div style=\"text-align:center;padding:80px 20px;color:var(--gray);grid-column:1/-1\"><i data-lucide=\"users\" style=\"width:64px;height:64px;margin-bottom:20px;opacity:.3\"></i><h3 style=\"margin-bottom:8px\">Belum Ada Absensi Hari Ini</h3><p style=\"font-size:13px\">Siswa yang absen akan muncul di sini secara real-time</p></div>';}c.innerHTML='<div class=\"stats-grid\"><div class=\"stat-card\"><div class=\"stat-info\"><h3>Total Hadir</h3><div class=\"value\">'+(stats.totalHadir||0)+'</div><div class=\"trend up\">+5% vs Kemarin</div></div><div class=\"stat-icon green\"><i data-lucide=\"users\"></i></div></div><div class=\"stat-card\"><div class=\"stat-info\"><h3>Siswa Terlambat</h3><div class=\"value\">'+(stats.totalTerlambat||0)+'</div><div class=\"trend down\">-2% Membaik</div></div><div class=\"stat-icon orange\"><i data-lucide=\"clock\"></i></div></div><div class=\"stat-card\"><div class=\"stat-info\"><h3>Belum Absen</h3><div class=\"value\">'+(stats.belumAbsen||0)+'</div><div class=\"trend down\">Perlu Cek Manual</div></div><div class=\"stat-icon red\"><i data-lucide=\"user-x\"></i></div></div><div class=\"stat-card\"><div class=\"stat-info\"><h3>Akurasi Selfie</h3><div class=\"value\">'+pct+'%</div><div class=\"trend up\">AI Verification</div></div><div class=\"stat-icon blue\"><i data-lucide=\"scan-face\"></i></div></div></div><div class=\"feed-header\"><div class=\"feed-title\">Feed Absensi Real-time <span class=\"live-badge\"><span class=\"live-dot\"></span> LIVE</span></div><div style=\"display:flex;gap:10px\"><button class=\"btn btn-outline\" onclick=\"loadData()\"><i data-lucide=\"filter\" style=\"width:14px\"></i> Semua Kelas</button><button class=\"btn btn-primary\" onclick=\"exportExcel()\"><i data-lucide=\"download\" style=\"width:14px\"></i> Export Log</button></div></div><div class=\"feed-grid\">'+feed+'</div><div class=\"pagination\"><div class=\"pagination-info\">Menampilkan 1 - '+attendance.length+' dari '+attendance.length+' siswa</div><div class=\"pagination-btns\"><button class=\"page-btn\">&lt;</button><button class=\"page-btn active\">1</button><button class=\"page-btn\">2</button><button class=\"page-btn\">3</button><button class=\"page-btn\">...</button><button class=\"page-btn\">12</button><button class=\"page-btn\">&gt;</button></div></div>';lucide.createIcons();}";

}

function getAdminJS2() {

  return "function renderLaporan(){var c=document.getElementById('main-content');c.innerHTML='<div class=\"page-header\"><h1>Laporan Kehadiran Siswa</h1><p>Kelola dan pantau data kehadiran harian siswa dengan verifikasi selfie.</p><div class=\"header-actions\"><button class=\"btn btn-danger\" onclick=\"exportPDF()\"><i data-lucide=\"file-text\" style=\"width:16px\"></i> Export PDF</button><button class=\"btn btn-success\" onclick=\"exportExcel()\"><i data-lucide=\"file-spreadsheet\" style=\"width:16px\"></i> Export Excel</button></div></div><div class=\"card\"><div class=\"card-body\"><div class=\"filters\"><div class=\"filter-group\"><div class=\"filter-label\">Tingkat Kelas</div><div class=\"filter-tabs\"><button class=\"filter-tab active\" onclick=\"setFilter(this,\\'Semua\\')\">Semua</button><button class=\"filter-tab\" onclick=\"setFilter(this,\\'VII\\')\">Kelas VII</button><button class=\"filter-tab\" onclick=\"setFilter(this,\\'VIII\\')\">Kelas VIII</button><button class=\"filter-tab\" onclick=\"setFilter(this,\\'IX\\')\">Kelas IX</button></div></div><div class=\"filter-group\"><div class=\"filter-label\">Rentang Tanggal</div><div class=\"date-picker\"><i data-lucide=\"calendar\" style=\"width:16px;color:var(--gray)\"></i><span>1 Januari 2026 - 31 Juli 2026</span><i data-lucide=\"chevron-down\" style=\"width:16px;color:var(--gray)\"></i></div></div></div><table class=\"table\"><thead><tr><th>Siswa</th><th>Kelas</th><th>Tanggal</th><th>Jam Masuk</th><th>Status</th><th>Verifikasi</th><th>Aksi</th></tr></thead><tbody id=\"lap-table\"></tbody></table><div class=\"pagination\"><div class=\"pagination-info\">Menampilkan 1 - '+(attendance.length||0)+' dari '+(attendance.length||0)+' data</div><div class=\"pagination-btns\"><button class=\"page-btn\">&lt;</button><button class=\"page-btn active\">1</button><button class=\"page-btn\">2</button><button class=\"page-btn\">3</button><button class=\"page-btn\">&gt;</button></div></div></div></div>';lucide.createIcons();renderLapTable();}function setFilter(el,f){filterKelas=f;document.querySelectorAll('.filter-tab').forEach(function(t){t.classList.remove('active');});el.classList.add('active');renderLapTable();}function renderLapTable(){var tb=document.getElementById('lap-table');if(!tb)return;var data=attendance.filter(function(a){return filterKelas==='Semua'||a.kelas.indexOf(filterKelas)>-1;});if(data.length===0){tb.innerHTML='<tr><td colspan=\"7\" style=\"text-align:center;padding:40px;color:var(--gray)\">Tidak ada data</td></tr>';return;}var h='';for(var i=0;i<data.length;i++){var a=data[i];var bc=a.status==='Hadir'?'success':a.status==='Terlambat'?'warning':a.status==='Izin'?'info':'danger';var tc=a.status==='Terlambat'?' class=\"time-late\"':'';var v=a.faceMatch>0.5?'<button class=\"verify-btn verified\"><i data-lucide=\"camera\" style=\"width:16px\"></i></button>':'<button class=\"verify-btn not-verified\"><i data-lucide=\"camera-off\" style=\"width:16px\"></i></button>';var thumb=a.fotoAbsen?'<img src=\"'+a.fotoAbsen+'\" style=\"width:36px;height:36px;border-radius:50%;object-fit:cover\">':'<div class=\"avatar '+getColor(i)+'\">'+getInit(a.nama)+'</div>';h+='<tr><td><div class=\"student-cell\">'+thumb+'<span style=\"font-weight:500\">'+a.nama+'</span></div></td><td>'+a.kelas+'</td><td>'+formatDate(a.tanggal)+'</td><td'+tc+'>'+(a.waktu||'--:--')+'</td><td><span class=\"badge badge-'+bc+'\">'+a.status+'</span></td><td>'+v+'</td><td><button class=\"action-btn\"><i data-lucide=\"more-vertical\" style=\"width:18px\"></i></button></td></tr>';}tb.innerHTML=h;lucide.createIcons();}function formatDate(d){if(!d)return'-';var p=String(d).split('-');var m=['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Agt','Sep','Okt','Nov','Des'];return p.length===3?p[2]+' '+m[parseInt(p[1])-1]+' '+p[0]:d;}function renderRekap(){var c=document.getElementById('main-content');c.innerHTML='<div class=\"page-header\"><h1>Rekap Bulanan</h1><p>Rekap kehadiran siswa per bulan dengan status harian.</p></div><div class=\"card\"><div class=\"card-body\" id=\"rekap-content\"><p style=\"text-align:center;padding:30px;color:var(--gray)\">Memuat data...</p></div></div>';google.script.run.withSuccessHandler(function(r){if(r.success){rekapData=r;showRekapTable();}}).getMonthlyRecap(new Date().getMonth()+1,new Date().getFullYear());}function showRekapTable(){var rc=document.getElementById('rekap-content');if(!rekapData||!rekapData.students||rekapData.students.length===0){rc.innerHTML='<p style=\"text-align:center;padding:30px;color:var(--gray)\">Tidak ada data rekap</p>';return;}var h='<div style=\"overflow-x:auto\"><table class=\"table rekap-table\"><thead><tr><th style=\"text-align:left;min-width:150px\">Siswa</th>';for(var d=1;d<=rekapData.daysInMonth;d++)h+='<th>'+d+'</th>';h+='<th style=\"background:#dcfce7\">H</th><th style=\"background:#fef3c7\">T</th><th style=\"background:#ede9fe\">S</th><th style=\"background:#cffafe\">I</th><th style=\"background:#fee2e2\">A</th></tr></thead><tbody>';for(var k=0;k<rekapData.students.length;k++){var s=rekapData.students[k];h+='<tr><td style=\"text-align:left\"><div class=\"student-cell\"><div class=\"avatar '+getColor(k)+'\" style=\"width:28px;height:28px;font-size:10px\">'+getInit(s.nama)+'</div><div><strong style=\"font-size:12px\">'+s.nama+'</strong><br><small style=\"color:var(--gray)\">'+s.kelas+'</small></div></div></td>';for(var d=1;d<=rekapData.daysInMonth;d++){var code=s.days[d]||'-';var cls=code==='H'?'day-h':code==='T'?'day-t':code==='S'?'day-s':code==='I'?'day-i':code==='A'?'day-a':'';h+='<td class=\"'+cls+'\"><strong>'+code+'</strong></td>';}h+='<td style=\"font-weight:700;color:var(--success)\">'+(s.totals.H||0)+'</td><td style=\"font-weight:700;color:var(--warning)\">'+(s.totals.T||0)+'</td><td style=\"font-weight:700;color:var(--purple)\">'+(s.totals.S||0)+'</td><td style=\"font-weight:700;color:var(--info)\">'+(s.totals.I||0)+'</td><td style=\"font-weight:700;color:var(--danger)\">'+(s.totals.A||0)+'</td></tr>';}h+='</tbody></table></div>';rc.innerHTML=h;}";

}

function getAdminJS3() {

  return "function renderSiswa(){var c=document.getElementById('main-content');c.innerHTML='<div class=\"page-header\"><h1>Daftar Siswa</h1><p>Kelola data siswa dan registrasi wajah untuk verifikasi.</p><div class=\"header-actions\"><button class=\"btn btn-primary\" onclick=\"showAddModal()\"><i data-lucide=\"plus\" style=\"width:16px\"></i> Tambah Siswa</button><button class=\"btn btn-success\" onclick=\"runSetup()\"><i data-lucide=\"database\" style=\"width:16px\"></i> Setup Sheet</button></div></div><div class=\"card\"><div class=\"card-body\" style=\"padding:0\"><table class=\"table\"><thead><tr><th>Siswa</th><th>Barcode</th><th>Kelas</th><th>Verifikasi</th><th>Status</th><th>Aksi</th></tr></thead><tbody id=\"siswa-table\"></tbody></table></div></div>';lucide.createIcons();renderSiswaTable();}function renderSiswaTable(){var tb=document.getElementById('siswa-table');if(!tb)return;if(students.length===0){tb.innerHTML='<tr><td colspan=\"6\" style=\"text-align:center;padding:40px;color:var(--gray)\">Belum ada data siswa. Klik Setup Sheet lalu Tambah Siswa.</td></tr>';return;}var h='';for(var i=0;i<students.length;i++){var s=students[i];var fb=s.hasFace?'<button class=\"verify-btn verified\"><i data-lucide=\"check\" style=\"width:16px\"></i></button>':'<button class=\"btn btn-outline\" style=\"padding:6px 12px;font-size:12px\" onclick=\"regFace(\\''+s.id+'\\',\\''+s.nama+'\\')\" ><i data-lucide=\"camera\" style=\"width:14px\"></i> Register</button>';var stBadge=s.status==='Aktif'?'success':'danger';h+='<tr><td><div class=\"student-cell\"><div class=\"avatar '+getColor(i)+'\">'+getInit(s.nama)+'</div><span style=\"font-weight:500\">'+s.nama+'</span></div></td><td>'+s.barcode+'</td><td>'+s.kelas+'</td><td>'+fb+'</td><td><span class=\"badge badge-'+stBadge+'\">'+(s.status||'Aktif')+'</span></td><td><button class=\"action-btn\" onclick=\"deleteSiswa(\\''+s.id+'\\')\" title=\"Hapus\"><i data-lucide=\"trash-2\" style=\"width:18px\"></i></button></td></tr>';}tb.innerHTML=h;lucide.createIcons();}function renderPengaturan(){var c=document.getElementById('main-content');c.innerHTML='<div class=\"page-header\"><h1>Pengaturan</h1><p>Konfigurasi jam terlambat dan parameter sistem.</p></div><div class=\"card\"><div class=\"card-body\"><div style=\"max-width:400px\"><div class=\"form-group\"><label class=\"form-label\">Jam Batas Terlambat</label><input type=\"time\" class=\"form-input\" id=\"set-jam\" value=\"07:15\"></div><div class=\"form-group\"><label class=\"form-label\">Threshold Face Match (0-1)</label><input type=\"number\" class=\"form-input\" id=\"set-th\" value=\"0.5\" min=\"0\" max=\"1\" step=\"0.1\"></div><button class=\"btn btn-primary\" onclick=\"saveSettings()\"><i data-lucide=\"save\" style=\"width:16px\"></i> Simpan Pengaturan</button></div></div></div>';lucide.createIcons();}function showAddModal(){var m=document.getElementById('modal');m.innerHTML='<div class=\"modal\"><div class=\"modal-header\"><span class=\"modal-title\">Tambah Siswa Baru</span><button class=\"modal-close\" onclick=\"closeModal()\">&times;</button></div><div class=\"modal-body\"><div class=\"form-group\"><label class=\"form-label\">Barcode / NIS</label><input type=\"text\" class=\"form-input\" id=\"add-bc\" placeholder=\"Masukkan barcode\"></div><div class=\"form-group\"><label class=\"form-label\">Nama Lengkap</label><input type=\"text\" class=\"form-input\" id=\"add-nama\" placeholder=\"Nama siswa\"></div><div class=\"form-group\"><label class=\"form-label\">Kelas</label><input type=\"text\" class=\"form-input\" id=\"add-kelas\" placeholder=\"X-IPA 1\"></div></div><div class=\"modal-footer\"><button class=\"btn btn-outline\" onclick=\"closeModal()\">Batal</button><button class=\"btn btn-primary\" onclick=\"submitAdd()\">Simpan</button></div></div>';m.classList.add('active');}function regFace(id,nm){var m=document.getElementById('modal');m.innerHTML='<div class=\"modal\"><div class=\"modal-header\"><span class=\"modal-title\">Register Wajah - '+nm+'</span><button class=\"modal-close\" onclick=\"closeModal()\">&times;</button></div><div class=\"modal-body\"><div style=\"text-align:center;padding:20px;border:2px dashed var(--border);border-radius:12px;margin-bottom:15px;cursor:pointer\" onclick=\"document.getElementById(\\'foto-input\\').click()\"><i data-lucide=\"upload\" style=\"width:48px;height:48px;color:var(--gray);margin-bottom:10px\"></i><p style=\"color:var(--gray);font-size:13px\">Klik untuk upload foto wajah</p><input type=\"file\" id=\"foto-input\" accept=\"image/*\" style=\"display:none\" onchange=\"previewFoto(this)\"></div><div id=\"preview-box\" style=\"display:none;margin-bottom:15px\"><img id=\"preview-img\" style=\"width:100%;max-height:300px;object-fit:contain;border-radius:8px\"></div><div class=\"face-status loading\" id=\"face-status\" style=\"display:none\">Memproses wajah...</div><button class=\"btn btn-primary\" style=\"width:100%\" id=\"save-btn\" disabled onclick=\"saveFace(\\''+id+'\\')\"><i data-lucide=\"save\" style=\"width:16px\"></i> Simpan Wajah</button></div></div>';m.classList.add('active');lucide.createIcons();loadFaceApi();}";

}

function getAdminJS4() {

  return "var uploadedImg=null;function previewFoto(input){if(input.files&&input.files[0]){var reader=new FileReader();reader.onload=function(e){uploadedImg=new Image();uploadedImg.onload=function(){document.getElementById('preview-box').style.display='block';document.getElementById('preview-img').src=e.target.result;document.getElementById('save-btn').disabled=false;};uploadedImg.src=e.target.result;};reader.readAsDataURL(input.files[0]);}}function loadFaceApi(){Promise.all([faceapi.nets.tinyFaceDetector.loadFromUri('https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model'),faceapi.nets.faceLandmark68Net.loadFromUri('https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model'),faceapi.nets.faceRecognitionNet.loadFromUri('https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model')]).then(function(){console.log('Face API loaded');}).catch(function(e){console.log('Face API error',e);});}function saveFace(id){if(!uploadedImg){showToast('Pilih foto dulu','error');return;}document.getElementById('face-status').style.display='block';document.getElementById('face-status').className='face-status loading';document.getElementById('face-status').textContent='Mendeteksi wajah...';document.getElementById('save-btn').disabled=true;var cv=document.createElement('canvas');cv.width=uploadedImg.width;cv.height=uploadedImg.height;cv.getContext('2d').drawImage(uploadedImg,0,0);faceapi.detectSingleFace(cv,new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceDescriptor().then(function(d){if(d){var desc=Array.from(d.descriptor);var small=document.createElement('canvas');small.width=200;small.height=200;small.getContext('2d').drawImage(uploadedImg,0,0,200,200);var url=small.toDataURL('image/jpeg',0.3);document.getElementById('face-status').textContent='Menyimpan...';google.script.run.withSuccessHandler(function(r){closeModal();if(r.success){showToast('Wajah berhasil didaftarkan!','success');loadData();}else{showToast('Gagal menyimpan: '+(r.error||''),'error');}}).withFailureHandler(function(e){document.getElementById('face-status').className='face-status error';document.getElementById('face-status').textContent='Error: '+e.message;document.getElementById('save-btn').disabled=false;}).registerFace({studentId:id,faceDescriptor:desc,fotoURL:url});}else{document.getElementById('face-status').className='face-status error';document.getElementById('face-status').textContent='Wajah tidak terdeteksi dalam foto';document.getElementById('save-btn').disabled=false;}}).catch(function(e){document.getElementById('face-status').className='face-status error';document.getElementById('face-status').textContent='Error deteksi: '+e.message;document.getElementById('save-btn').disabled=false;});}function stopCam(){if(window.regStream){window.regStream.getTracks().forEach(function(t){t.stop();});}}function closeModal(){document.getElementById('modal').classList.remove('active');uploadedImg=null;}function submitAdd(){var bc=document.getElementById('add-bc').value;var nm=document.getElementById('add-nama').value;var kl=document.getElementById('add-kelas').value;if(!bc||!nm||!kl){showToast('Lengkapi semua field','error');return;}google.script.run.withSuccessHandler(function(r){if(r.success){showToast('Siswa berhasil ditambahkan','success');closeModal();loadData();}}).addStudent({barcode:bc,nama:nm,kelas:kl});}function deleteSiswa(id){if(confirm('Yakin ingin menghapus?')){google.script.run.withSuccessHandler(function(r){if(r.success){showToast('Siswa dihapus','success');loadData();}}).deleteStudent(id);}}function runSetup(){google.script.run.withSuccessHandler(function(r){showToast(r,'success');loadData();}).setupSpreadsheet();}function saveSettings(){var j=document.getElementById('set-jam').value;var t=document.getElementById('set-th').value;google.script.run.withSuccessHandler(function(r){if(r.success)showToast('Pengaturan disimpan','success');}).updateSettings({jamTerlambat:j,faceMatchThreshold:t});}function exportPDF(){var doc=new jspdf.jsPDF();doc.text('Laporan Kehadiran Siswa',20,20);var y=35;for(var i=0;i<attendance.length;i++){var a=attendance[i];doc.text((i+1)+'. '+a.nama+' - '+a.kelas+' - '+a.waktu+' - '+a.status,20,y);y+=8;}doc.save('laporan.pdf');showToast('PDF diunduh','success');}function exportExcel(){var wb=XLSX.utils.book_new();var data=[['No','Nama','Kelas','Tanggal','Waktu','Status','Verifikasi']];for(var i=0;i<attendance.length;i++){var a=attendance[i];data.push([i+1,a.nama,a.kelas,a.tanggal||'-',a.waktu,a.status,a.faceMatch>0.5?'Ya':'Tidak']);}var ws=XLSX.utils.aoa_to_sheet(data);XLSX.utils.book_append_sheet(wb,ws,'Kehadiran');XLSX.writeFile(wb,'laporan.xlsx');showToast('Excel diunduh','success');}function showToast(m,t){var tc=document.getElementById('toasts');var d=document.createElement('div');d.className='toast '+(t==='error'?'error':'');d.textContent=m;tc.appendChild(d);setTimeout(function(){d.remove();},3000);}function getInit(n){if(!n)return'?';var p=n.split(' ');return p.length>=2?(p[0][0]+p[1][0]).toUpperCase():n.substring(0,2).toUpperCase();}";

}

function getStudentJS() {

  return "var currentStudent=null;var videoStream=null;var locationData={lat:'',lng:'',alamat:''};var faceLoaded=false;document.addEventListener('DOMContentLoaded',function(){lucide.createIcons();showOptions();});function showOptions(){stopCam();var c=document.getElementById('student-content');c.innerHTML='<button class=\"opt-btn\" onclick=\"startScanner()\"><div class=\"opt-icon blue\"><i data-lucide=\"scan-barcode\"></i></div><div class=\"opt-text\"><h3>Scan Barcode</h3><p>Pindai kartu pelajar</p></div></button><button class=\"opt-btn\" onclick=\"showManual()\"><div class=\"opt-icon green\"><i data-lucide=\"keyboard\"></i></div><div class=\"opt-text\"><h3>Input Manual</h3><p>Ketik barcode</p></div></button><button class=\"opt-btn\" onclick=\"showSick()\"><div class=\"opt-icon purple\"><i data-lucide=\"thermometer\"></i></div><div class=\"opt-text\"><h3>Sakit / Izin</h3><p>Form tidak hadir</p></div></button>';lucide.createIcons();}function showManual(){var c=document.getElementById('student-content');c.innerHTML='<div class=\"form-group\"><label class=\"form-label\">Barcode / NIS</label><input type=\"text\" class=\"form-input\" id=\"barcode\"></div><button class=\"btn btn-primary\" style=\"width:100%\" onclick=\"findStudent()\">Cari Siswa</button><button class=\"btn btn-outline\" style=\"width:100%;margin-top:10px\" onclick=\"showOptions()\">Kembali</button>';}function startScanner(){var c=document.getElementById('student-content');c.innerHTML='<div id=\"scanner\" style=\"width:100%;height:260px;border-radius:12px;overflow:hidden;margin-bottom:15px\"></div><button class=\"btn btn-outline\" style=\"width:100%\" onclick=\"stopScan();showOptions()\">Batal</button>';try{window.scanner=new Html5Qrcode('scanner');window.scanner.start({facingMode:'environment'},{fps:10,qrbox:{width:250,height:100}},function(t){stopScan();findByCode(t);},function(){});}catch(e){showToast('Error','error');showOptions();}}function stopScan(){if(window.scanner){try{window.scanner.stop();}catch(e){}}}function findStudent(){var bc=document.getElementById('barcode').value.trim();if(!bc){showToast('Masukkan barcode','error');return;}findByCode(bc);}function findByCode(bc){google.script.run.withSuccessHandler(function(r){if(r.success){currentStudent=r.student;showSelfie();}else{showToast('Siswa tidak ditemukan','error');showOptions();}}).getStudentByBarcode(bc);}function showSelfie(){locationData={lat:'',lng:'',alamat:''};getLoc();var c=document.getElementById('student-content');c.innerHTML='<div style=\"text-align:center;margin-bottom:20px\"><div class=\"avatar blue\" style=\"width:60px;height:60px;font-size:20px;margin:0 auto 10px\">'+getInit(currentStudent.nama)+'</div><h3>'+currentStudent.nama+'</h3><p style=\"color:var(--gray)\">'+currentStudent.kelas+'</p></div><div class=\"camera-box\"><video id=\"selfie-video\" class=\"camera-video\" autoplay playsinline></video></div><div class=\"face-status loading\" id=\"fs\">Memuat...</div><div class=\"loc-info\" id=\"li\"><i data-lucide=\"map-pin\" style=\"width:14px\"></i>Mendeteksi lokasi...</div><button class=\"btn btn-success\" style=\"width:100%\" id=\"sbtn\" disabled onclick=\"captureSubmit()\"><i data-lucide=\"camera\" style=\"width:16px\"></i> Absen Sekarang</button><button class=\"btn btn-outline\" style=\"width:100%;margin-top:10px\" onclick=\"stopCam();showOptions()\">Batal</button>';lucide.createIcons();startSelfie();}function startSelfie(){navigator.mediaDevices.getUserMedia({video:{facingMode:'user',width:640,height:480}}).then(function(s){videoStream=s;document.getElementById('selfie-video').srcObject=s;loadFace();}).catch(function(){document.getElementById('fs').className='face-status error';document.getElementById('fs').textContent='Gagal kamera';});}function stopCam(){if(videoStream){videoStream.getTracks().forEach(function(t){t.stop();});videoStream=null;}}function loadFace(){if(faceLoaded){document.getElementById('fs').className='face-status success';document.getElementById('fs').textContent='Siap!';document.getElementById('sbtn').disabled=false;return;}Promise.all([faceapi.nets.tinyFaceDetector.loadFromUri('https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model'),faceapi.nets.faceLandmark68Net.loadFromUri('https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model'),faceapi.nets.faceRecognitionNet.loadFromUri('https://cdn.jsdelivr.net/npm/@vladmandic/face-api/model')]).then(function(){faceLoaded=true;document.getElementById('fs').className='face-status success';document.getElementById('fs').textContent='Siap!';document.getElementById('sbtn').disabled=false;}).catch(function(){document.getElementById('fs').className='face-status error';document.getElementById('fs').textContent='Gagal model';});}function getLoc(){if(navigator.geolocation){navigator.geolocation.getCurrentPosition(function(p){locationData.lat=p.coords.latitude;locationData.lng=p.coords.longitude;fetch('https://nominatim.openstreetmap.org/reverse?format=json&lat='+p.coords.latitude+'&lon='+p.coords.longitude).then(function(r){return r.json();}).then(function(d){locationData.alamat=d.display_name||'';document.getElementById('li').innerHTML='<i data-lucide=\"map-pin\" style=\"width:14px;color:var(--success)\"></i>'+locationData.alamat.substring(0,40)+'...';lucide.createIcons();});},function(){document.getElementById('li').innerHTML='<i data-lucide=\"map-pin\" style=\"width:14px;color:var(--danger)\"></i>Lokasi tidak tersedia';lucide.createIcons();});}}function captureSubmit(){var v=document.getElementById('selfie-video');var cv=document.createElement('canvas');cv.width=v.videoWidth;cv.height=v.videoHeight;cv.getContext('2d').drawImage(v,0,0);document.getElementById('fs').textContent='Memproses...';document.getElementById('sbtn').disabled=true;faceapi.detectSingleFace(cv,new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceDescriptor().then(function(d){if(!d){document.getElementById('fs').className='face-status error';document.getElementById('fs').textContent='Wajah tidak terdeteksi';document.getElementById('sbtn').disabled=false;return;}var desc=d.descriptor;var fm=0;if(currentStudent.faceDescriptor){var dist=faceapi.euclideanDistance(desc,currentStudent.faceDescriptor);fm=Math.max(0,1-dist);}var photo=cv.toDataURL('image/jpeg',0.3);stopCam();google.script.run.withSuccessHandler(function(r){if(r.success){showSuccess(r.status,r.time,fm);}else{showToast('Gagal','error');showOptions();}}).recordAttendance({studentId:currentStudent.id,nama:currentStudent.nama,kelas:currentStudent.kelas,fotoAbsen:photo,faceMatch:fm,latitude:locationData.lat,longitude:locationData.lng,alamat:locationData.alamat});});}function showSick(){var c=document.getElementById('student-content');c.innerHTML='<div class=\"form-group\"><label class=\"form-label\">Barcode</label><input type=\"text\" class=\"form-input\" id=\"sbc\"></div><div class=\"form-group\"><label class=\"form-label\">Status</label><select class=\"form-select\" id=\"sst\"><option value=\"Sakit\">Sakit</option><option value=\"Izin\">Izin</option></select></div><div class=\"form-group\"><label class=\"form-label\">Keterangan</label><textarea class=\"form-input\" id=\"skt\" rows=\"3\"></textarea></div><button class=\"btn btn-primary\" style=\"width:100%\" onclick=\"submitSick()\">Kirim</button><button class=\"btn btn-outline\" style=\"width:100%;margin-top:10px\" onclick=\"showOptions()\">Batal</button>';}function submitSick(){var bc=document.getElementById('sbc').value.trim();var st=document.getElementById('sst').value;var kt=document.getElementById('skt').value;if(!bc){showToast('Isi barcode','error');return;}google.script.run.withSuccessHandler(function(r){if(r.success){currentStudent=r.student;google.script.run.withSuccessHandler(function(r2){if(r2.success)showSuccess(r2.status,r2.time,0);}).submitSickLeave({studentId:currentStudent.id,nama:currentStudent.nama,kelas:currentStudent.kelas,status:st,keterangan:kt});}else showToast('Tidak ditemukan','error');}).getStudentByBarcode(bc);}function showSuccess(st,tm,fm){var c=document.getElementById('student-content');var col=st==='Terlambat'?'var(--warning)':'var(--success)';c.innerHTML='<div class=\"success-screen\"><div class=\"success-icon\" style=\"background:'+col+'\"><i data-lucide=\"check\" style=\"width:40px;height:40px\"></i></div><h2 style=\"color:'+col+';margin-bottom:10px\">Berhasil!</h2><strong>'+currentStudent.nama+'</strong><p style=\"color:var(--gray)\">'+currentStudent.kelas+'</p><div style=\"background:var(--bg);padding:15px;border-radius:8px;margin:20px 0;text-align:left\"><p><strong>Status:</strong> '+st+'</p><p><strong>Waktu:</strong> '+tm+'</p><p><strong>Verifikasi:</strong> '+(fm>0.5?'Cocok':'Tidak')+'</p></div><button class=\"btn btn-primary\" style=\"width:100%\" onclick=\"showOptions()\">Selesai</button></div>';lucide.createIcons();}function showToast(m,t){var tc=document.getElementById('toasts');var d=document.createElement('div');d.className='toast '+(t==='error'?'error':'');d.textContent=m;tc.appendChild(d);setTimeout(function(){d.remove();},3000);}function getInit(n){if(!n)return'?';var p=n.split(' ');return p.length>=2?(p[0][0]+p[1][0]).toUpperCase():n.substring(0,2).toUpperCase();}";

}

function getSpreadsheet(){if(SPREADSHEET_ID)return SpreadsheetApp.openById(SPREADSHEET_ID);return SpreadsheetApp.getActiveSpreadsheet();}

function getSheet(n){var ss=getSpreadsheet();var s=ss.getSheetByName(n);if(!s)s=ss.insertSheet(n);return s;}

function setupSpreadsheet(){var ss=getSpreadsheet();var s1=ss.getSheetByName(SHEET_SISWA);if(!s1)s1=ss.insertSheet(SHEET_SISWA);if(s1.getLastRow()===0)s1.appendRow(["ID","Barcode","Nama","Kelas","JK","Foto","Status","FaceDescriptor"]);var s2=ss.getSheetByName(SHEET_ABSENSI);if(!s2)s2=ss.insertSheet(SHEET_ABSENSI);if(s2.getLastRow()===0)s2.appendRow(["ID","StudentID","Nama","Kelas","Tanggal","Waktu","Status","FotoAbsen","FaceMatch","Lat","Lng","Alamat","Device"]);var s3=ss.getSheetByName(SHEET_SETTINGS);if(!s3)s3=ss.insertSheet(SHEET_SETTINGS);if(s3.getLastRow()===0){s3.appendRow(["Key","Value"]);s3.appendRow(["jamTerlambat","07:15"]);s3.appendRow(["faceMatchThreshold","0.5"]);}return "Setup berhasil!";}

function getSettings(){var s=getSheet(SHEET_SETTINGS);var d=s.getDataRange().getValues();var r={};for(var i=1;i<d.length;i++)r[d[i][0]]=d[i][1];return {success:true,settings:r};}

function updateSettings(ns){var s=getSheet(SHEET_SETTINGS);for(var k in ns){var found=false;var rows=s.getDataRange().getValues();for(var i=1;i<rows.length;i++){if(rows[i][0]===k){s.getRange(i+1,2).setValue(ns[k]);found=true;break;}}if(!found)s.appendRow([k,ns[k]]);}return {success:true};}

function getStudentList(){var s=getSheet(SHEET_SISWA);var d=s.getDataRange().getValues();var arr=[];for(var i=1;i<d.length;i++)arr.push({id:String(d[i][0]),barcode:String(d[i][1]),nama:String(d[i][2]),kelas:String(d[i][3]),foto:String(d[i][5]||""),status:String(d[i][6]||"Aktif"),hasFace:d[i][7]?true:false});return {success:true,students:arr};}

function getStudentByBarcode(bc){var s=getSheet(SHEET_SISWA);var d=s.getDataRange().getValues();for(var i=1;i<d.length;i++){if(String(d[i][1])===String(bc)){var desc=null;if(d[i][7]){try{desc=JSON.parse(d[i][7]);}catch(e){}}return {success:true,student:{id:String(d[i][0]),barcode:String(d[i][1]),nama:String(d[i][2]),kelas:String(d[i][3]),foto:String(d[i][5]||""),faceDescriptor:desc}};}}return {success:false};}

function addStudent(d){var s=getSheet(SHEET_SISWA);var id="STD"+Date.now();s.appendRow([id,d.barcode,d.nama,d.kelas,"","","Aktif",""]);return {success:true,id:id};}

function deleteStudent(id){var s=getSheet(SHEET_SISWA);var rows=s.getDataRange().getValues();for(var i=1;i<rows.length;i++){if(rows[i][0]===id){s.deleteRow(i+1);return {success:true};}}return {success:false};}

function registerFace(d){var s=getSheet(SHEET_SISWA);var rows=s.getDataRange().getValues();for(var i=1;i<rows.length;i++){if(rows[i][0]===d.studentId){if(d.fotoURL)s.getRange(i+1,6).setValue(d.fotoURL);s.getRange(i+1,8).setValue(JSON.stringify(d.faceDescriptor));return {success:true};}}return {success:false};}

function recordAttendance(d){var s=getSheet(SHEET_ABSENSI);var set=getSettings().settings||{};var now=new Date();var today=Utilities.formatDate(now,"Asia/Jakarta","yyyy-MM-dd");var time=Utilities.formatDate(now,"Asia/Jakarta","HH:mm:ss");var jt=set.jamTerlambat||"07:15";var ct=Utilities.formatDate(now,"Asia/Jakarta","HH:mm");var st=ct>jt?"Terlambat":"Hadir";var id="ATT"+Date.now();s.appendRow([id,d.studentId,d.nama,d.kelas,today,time,st,d.fotoAbsen||"",d.faceMatch||0,d.latitude||"",d.longitude||"",d.alamat||"",""]);return {success:true,id:id,status:st,time:time};}

function submitSickLeave(d){var s=getSheet(SHEET_ABSENSI);var now=new Date();var today=Utilities.formatDate(now,"Asia/Jakarta","yyyy-MM-dd");var time=Utilities.formatDate(now,"Asia/Jakarta","HH:mm:ss");var id="ATT"+Date.now();s.appendRow([id,d.studentId,d.nama,d.kelas,today,time,d.status,"",0,"","",d.keterangan||"",""]);return {success:true,id:id,status:d.status,time:time};}

function getAttendanceToday(){var s=getSheet(SHEET_ABSENSI);var d=s.getDataRange().getDisplayValues();var today=Utilities.formatDate(new Date(),"Asia/Jakarta","yyyy-MM-dd");var map={};for(var i=1;i<d.length;i++){if(String(d[i][4]).trim()===today){var sid=String(d[i][1]);map[sid]={id:d[i][0],nama:d[i][2],kelas:d[i][3],tanggal:d[i][4],waktu:d[i][5],status:d[i][6],fotoAbsen:d[i][7]||"",faceMatch:parseFloat(d[i][8])||0,alamat:d[i][11]||""};}}var arr=[];for(var k in map)arr.push(map[k]);return {success:true,attendance:arr};}

function getStats(){var today=Utilities.formatDate(new Date(),"Asia/Jakarta","yyyy-MM-dd");var siswa=getSheet(SHEET_SISWA).getDataRange().getValues();var absen=getSheet(SHEET_ABSENSI).getDataRange().getDisplayValues();var total=0;for(var i=1;i<siswa.length;i++)if(siswa[i][6]==="Aktif")total++;var map={};for(var j=1;j<absen.length;j++)if(String(absen[j][4]).trim()===today)map[absen[j][1]]=absen[j][6];var h=0,t=0,sk=0,iz=0;for(var k in map){if(map[k]==="Hadir")h++;if(map[k]==="Terlambat")t++;if(map[k]==="Sakit")sk++;if(map[k]==="Izin")iz++;}return {success:true,stats:{totalHadir:h,totalTerlambat:t,totalSakit:sk,totalIzin:iz,belumAbsen:total-(h+t+sk+iz),totalSiswa:total}};}

function getMonthlyRecap(m,y){var siswa=getSheet(SHEET_SISWA).getDataRange().getValues();var absen=getSheet(SHEET_ABSENSI).getDataRange().getDisplayValues();var arr=[];for(var i=1;i<siswa.length;i++)if(siswa[i][6]==="Aktif")arr.push({id:String(siswa[i][0]),nama:String(siswa[i][2]),kelas:String(siswa[i][3])});var days=new Date(y,m,0).getDate();var map={};for(var s=0;s<arr.length;s++)map[arr[s].id]={};for(var j=1;j<absen.length;j++){var tgl=String(absen[j][4]).trim();var p=tgl.split("-");if(p.length===3&&parseInt(p[0])===y&&parseInt(p[1])===m){var sid=String(absen[j][1]);var day=parseInt(p[2]);var st=absen[j][6];var c=st==="Hadir"?"H":st==="Terlambat"?"T":st==="Sakit"?"S":st==="Izin"?"I":"A";if(map[sid])map[sid][day]=c;}}var recap=[];for(var k=0;k<arr.length;k++){var stu=arr[k];var r={id:stu.id,nama:stu.nama,kelas:stu.kelas,days:{},totals:{H:0,T:0,S:0,I:0,A:0}};for(var d=1;d<=days;d++){var code=map[stu.id][d]||"-";if(new Date(y,m-1,d)>new Date())code="-";r.days[d]=code;if(code!=="-")r.totals[code]++;}recap.push(r);}return {success:true,month:m,year:y,daysInMonth:days,students:recap};}

Komentar

Postingan populer dari blog ini

Ekspresi dan Operasi Logika