Files
presensi_app/index.html

351 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>SMAN 1 Garut - Presensi Siswa</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<!-- Peringatan: jangan buka dari file:// (browser blokir koneksi ke server) -->
<div id="file-protocol-warning" class="file-protocol-warning hidden">
<div class="file-protocol-warning-inner">
<strong>⚠️ Buka aplikasi lewat HTTP</strong>
<p>Aplikasi ini dibuka dari <strong>file://</strong>. Browser memblokir koneksi ke server. Agar bisa terhubung:</p>
<ol>
<li>Buka CMD / Terminal.</li>
<li>Masuk ke folder <code>mobile</code>: <code>cd c:\laragon\www\sman1\mobile</code></li>
<li>Jalankan: <code>php -S localhost:8001</code></li>
<li>Buka di browser: <a href="http://localhost:8001" target="_blank" rel="noopener">http://localhost:8001</a></li>
</ol>
</div>
</div>
<!-- Toast container (untuk notifikasi dari app.js) -->
<div id="toast-container" class="toast-container"></div>
<!-- Modal Settings (Backend URL) -->
<div id="settings-modal" class="modal hidden">
<div class="modal-backdrop" id="modal-backdrop"></div>
<div class="modal-content">
<div class="modal-header">
<h2>Pengaturan Server</h2>
<button type="button" id="btn-close-settings" class="modal-close" aria-label="Tutup">&times;</button>
</div>
<div class="modal-body">
<p class="modal-hint">Pastikan aplikasi dibuka lewat <strong>http://</strong> (bukan file://), misalnya <code>http://localhost:8001</code> setelah menjalankan <code>php -S localhost:8001</code> di folder mobile.</p>
<div class="form-group">
<label class="input-label" for="backend-url">URL Backend (SMAN 1)</label>
<input id="backend-url" type="text" class="input-android input-full" placeholder="http://localhost/sman1/backend/public" autocomplete="off">
</div>
<p id="config-status" class="config-status"></p>
<div class="modal-actions">
<button type="button" id="btn-test-connection" class="secondary-button">Cek koneksi</button>
<button type="button" id="btn-save-config" class="login-button">Simpan</button>
</div>
</div>
</div>
</div>
<div class="container">
<!-- Header (tombol settings untuk buka Backend URL) -->
<div class="app-header">
<button type="button" id="btn-open-settings" class="back-button btn-settings" aria-label="Pengaturan">
<svg viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
</button>
<span class="app-title">Presensi Online</span>
</div>
<!-- ========== SCREEN WELCOME ========== -->
<div id="screen-welcome" class="screen">
<div class="android-illustration">
<div class="android-logo">
<img src="./assets/img/logo_sman1garut.png" alt="Logo SMAN 1 Garut">
</div>
<h1 class="welcome-text">Presensi Online</h1>
<p class="sub-text">SMAN 1 Garut</p>
</div>
<button type="button" id="btn-go-login" class="login-button ripple">Masuk</button>
<div class="signup-link">
Belum punya akun? <a href="#" id="btn-go-register">Daftar</a>
</div>
</div>
<!-- ========== SCREEN LOGIN ========== -->
<div id="screen-login" class="screen hidden">
<div class="android-illustration android-illustration-small">
<h1 class="welcome-text">Masuk</h1>
<p class="sub-text">NISN &amp; PIN</p>
</div>
<div class="form-group">
<div class="input-label">NISN</div>
<div class="input-android">
<svg viewBox="0 0 24 24"><path d="M20 3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H4V5h16v14z"/><path d="M9 8c-1.38 0-2.5 1.12-2.5 2.5S7.62 13 9 13s2.5-1.12 2.5-2.5S10.38 8 9 8z"/></svg>
<input id="login-nisn" type="text" inputmode="numeric" placeholder="Masukkan NISN">
</div>
</div>
<div class="form-group">
<div class="input-label">PIN</div>
<div class="input-android">
<svg viewBox="0 0 24 24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/></svg>
<input id="login-pin" type="password" inputmode="numeric" maxlength="6" placeholder="Masukkan PIN">
</div>
</div>
<p id="login-status" class="form-status"></p>
<div class="forgot-section">
<a href="#" id="link-forgot-pin" class="forgot-link">Lupa PIN?</a>
</div>
<button type="button" id="btn-login" class="login-button ripple">MASUK</button>
<div class="signup-link">
Belum punya akun? <a href="#" id="link-go-register">Daftar</a>
</div>
</div>
<!-- ========== SCREEN LUPA PIN ========== -->
<div id="screen-forgot-pin" class="screen hidden">
<div class="android-illustration android-illustration-small">
<h1 class="welcome-text">Lupa PIN</h1>
<p class="sub-text">Reset PIN dengan NISN Anda</p>
</div>
<div class="form-group">
<div class="input-label">NISN</div>
<div class="input-android">
<input id="forgot-nisn" type="text" inputmode="numeric" placeholder="Masukkan NISN">
</div>
</div>
<div class="form-group">
<div class="input-label">PIN Baru (min 4 digit)</div>
<div class="input-android">
<input id="forgot-new-pin" type="password" inputmode="numeric" maxlength="6" placeholder="PIN baru">
</div>
</div>
<div class="form-group">
<div class="input-label">Ulangi PIN Baru</div>
<div class="input-android">
<input id="forgot-new-pin2" type="password" inputmode="numeric" maxlength="6" placeholder="Ulangi PIN baru">
</div>
</div>
<p id="forgot-pin-status" class="form-status"></p>
<button type="button" id="btn-reset-pin" class="login-button ripple">Reset PIN</button>
<div class="signup-link">
<a href="#" id="link-back-to-login">Kembali ke Masuk</a>
</div>
</div>
<!-- ========== SCREEN REGISTER NISN ========== -->
<div id="screen-register-nisn" class="screen hidden">
<div class="android-illustration android-illustration-small">
<h1 class="welcome-text">Daftar</h1>
<p class="sub-text">Cek NISN dulu</p>
</div>
<div class="form-group">
<div class="input-label">NISN</div>
<div class="input-android">
<input id="reg-nisn" type="text" inputmode="numeric" placeholder="Masukkan NISN">
</div>
</div>
<p id="reg-nisn-status" class="form-status"></p>
<button type="button" id="btn-check-nisn" class="login-button ripple">Lanjutkan</button>
<div class="signup-link">
Sudah punya akun? <a href="#" id="link-back-login-1">Masuk</a>
</div>
</div>
<!-- ========== SCREEN REGISTER PIN ========== -->
<div id="screen-register-pin" class="screen hidden">
<div class="android-illustration android-illustration-small">
<h1 class="welcome-text">Lengkapi Data</h1>
<p class="sub-text">Pilih kelas &amp; buat PIN</p>
</div>
<div id="reg-student-summary" class="student-summary"></div>
<div class="form-group">
<div class="input-label">Kelas</div>
<select id="reg-class" class="input-android input-full">
<option value="">-- Pilih kelas --</option>
</select>
</div>
<div class="form-group">
<div class="input-label">PIN (min 4 digit)</div>
<div class="input-android">
<input id="reg-pin" type="password" inputmode="numeric" maxlength="6" placeholder="PIN">
</div>
</div>
<div class="form-group">
<div class="input-label">Ulangi PIN</div>
<div class="input-android">
<input id="reg-pin2" type="password" inputmode="numeric" maxlength="6" placeholder="Ulangi PIN">
</div>
</div>
<p id="reg-pin-status" class="form-status"></p>
<button type="button" id="btn-complete-register" class="login-button ripple">Simpan &amp; Mulai</button>
<div class="signup-link">
<a href="#" id="btn-register-pin-batal">Batal</a>
</div>
</div>
<!-- ========== SCREEN HOME (setelah login) ========== -->
<div id="screen-home" class="screen hidden">
<div class="home-greeting">
<h2 id="home-greeting-text" class="home-greeting-title">Presensi</h2>
<p class="home-greeting-sub" id="home-date-label"></p>
</div>
<div id="home-student-summary" class="student-summary student-summary-compact"></div>
<!-- Kartu status / aksi presensi (enterprise style) -->
<div class="home-presence-grid">
<!-- Masuk -->
<div class="presence-card" id="card-masuk">
<div class="presence-card-icon presence-card-icon-masuk">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v18M5 12l7 7 7-7"/></svg>
</div>
<div class="presence-card-body">
<div class="presence-card-label">Absen Masuk</div>
<div id="masuk-status" class="presence-card-status hidden">Sudah masuk</div>
<button type="button" id="btn-absen-masuk" class="presence-card-btn presence-card-btn-primary ripple">Absen Masuk</button>
</div>
</div>
<!-- Check-in Mapel -->
<div class="presence-card presence-card-featured" id="card-mapel">
<div class="presence-card-icon presence-card-icon-mapel">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
</div>
<div class="presence-card-body">
<div class="presence-card-label">Jam Belajar</div>
<p class="presence-card-hint">Scan QR dari guru untuk absen mapel</p>
<button type="button" id="btn-scan-qr" class="presence-card-btn presence-card-btn-featured ripple">Check-in Mapel</button>
</div>
</div>
<!-- Pulang -->
<div class="presence-card" id="card-pulang">
<div class="presence-card-icon presence-card-icon-pulang">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 21V3M5 12l7-7 7 7"/></svg>
</div>
<div class="presence-card-body">
<div class="presence-card-label">Absen Pulang</div>
<div id="pulang-status" class="presence-card-status hidden">Sudah pulang</div>
<button type="button" id="btn-absen-pulang" class="presence-card-btn presence-card-btn-secondary ripple">Absen Pulang</button>
</div>
</div>
</div>
<!-- Lokasi ringkas -->
<div class="home-location-bar">
<span id="location-status" class="location-status-inline">Mengambil lokasi…</span>
<button type="button" id="btn-refresh-location" class="link-button" aria-label="Refresh"></button>
</div>
<!-- Hasil absen (popup rapi, bisa ditutup) -->
<div id="result-box" class="result-popup hidden">
<div class="result-popup-inner">
<div id="result-icon" class="result-popup-icon"></div>
<div id="result-status" class="result-popup-status">-</div>
<p id="result-message" class="result-popup-message"></p>
<button type="button" id="result-close-btn" class="btn-outline btn-sm">Tutup</button>
</div>
</div>
<div class="home-footer">
<a href="#" id="btn-logout" class="logout-link">Keluar</a>
</div>
</div>
</div>
<!-- Modal Scan QR Absen Mapel -->
<div id="scan-qr-modal" class="modal modal-overlay hidden">
<div class="modal-backdrop" id="scan-qr-backdrop"></div>
<div class="modal-panel">
<div class="modal-panel-header">
<h2 class="modal-panel-title">Check-in Mapel</h2>
<button type="button" id="scan-qr-close" class="modal-panel-close" aria-label="Tutup">&times;</button>
</div>
<div class="modal-panel-body">
<p class="modal-panel-hint">Arahkan kamera ke QR yang ditampilkan guru di kelas.</p>
<div class="qr-reader-wrap">
<div id="qr-reader"></div>
</div>
<p id="scan-qr-status" class="modal-panel-status"></p>
</div>
</div>
</div>
<!-- Modal Konfirmasi PIN (setelah scan QR) -->
<div id="qr-pin-modal" class="modal hidden">
<div class="modal-backdrop" id="qr-pin-backdrop"></div>
<div class="modal-panel">
<div class="modal-panel-header">
<h2 class="modal-panel-title">Konfirmasi PIN</h2>
<button type="button" id="qr-pin-close" class="modal-panel-close">&times;</button>
</div>
<div class="modal-panel-body">
<p class="modal-panel-hint">Masukkan PIN untuk mengirim absen mapel.</p>
<div class="form-group">
<label class="input-label" for="qr-pin-input">PIN</label>
<input id="qr-pin-input" type="password" inputmode="numeric" maxlength="6" placeholder="Masukkan PIN" class="input-android input-full">
</div>
<div class="modal-panel-actions modal-panel-actions-row">
<button type="button" id="qr-pin-cancel" class="btn-outline">Batal</button>
<button type="button" id="qr-pin-submit" class="btn-primary">Kirim Absen</button>
</div>
</div>
</div>
</div>
<!-- Modal Kamera (verifikasi wajah sebelum check-in) -->
<div id="camera-modal" class="modal modal-overlay hidden">
<div class="camera-modal-backdrop" id="camera-backdrop"></div>
<div class="modal-panel modal-panel-camera">
<div class="modal-panel-header">
<h2 class="modal-panel-title">Verifikasi Wajah - Smart Presensi</h2>
<button type="button" id="camera-modal-close" class="modal-panel-close" aria-label="Tutup">&times;</button>
</div>
<div class="modal-panel-body">
<p class="modal-panel-hint">Smart Presensi: arahkan wajah ke kamera. Setelah terverifikasi, Anda akan diminta PIN untuk menyelesaikan absen.</p>
<p id="camera-face-status" class="camera-face-status hidden"></p>
<div class="camera-frame">
<video id="camera-video" class="camera-video" playsinline autoplay muted></video>
<div id="camera-error" class="camera-frame-error hidden">Kamera tidak tersedia atau izin ditolak.</div>
</div>
<div class="modal-panel-actions">
<button type="button" id="camera-btn-cancel" class="btn-outline">Batal</button>
</div>
</div>
</div>
</div>
<!-- Modal Rekam Wajah (saat daftar pertama) -->
<div id="enroll-face-modal" class="modal modal-overlay hidden">
<div class="modal-backdrop" id="enroll-face-backdrop"></div>
<div class="modal-panel modal-panel-camera">
<div class="modal-panel-header">
<h2 class="modal-panel-title">Rekam Wajah (Wajib)</h2>
</div>
<div class="modal-panel-body">
<p class="modal-panel-hint">Arahkan wajah ke kamera lalu tekan <strong>Rekam</strong>. Data wajah dari HP dipakai untuk verifikasi saat absen masuk/pulang.</p>
<p id="enroll-face-status" class="camera-face-status hidden"></p>
<div class="camera-frame">
<video id="enroll-face-video" class="camera-video" playsinline autoplay muted></video>
<div id="enroll-face-error" class="camera-frame-error hidden">Kamera tidak tersedia atau izin ditolak.</div>
</div>
<div class="modal-panel-actions">
<button type="button" id="enroll-face-start" class="btn-primary btn-full">Rekam (35 foto)</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js"></script>
<script src="./app.js"></script>
<script>
(function () {
if (window.location.protocol === 'file:') {
var el = document.getElementById('file-protocol-warning');
if (el) el.classList.remove('hidden');
}
})();
</script>
</body>
</html>