351 lines
19 KiB
HTML
351 lines
19 KiB
HTML
<!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">×</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 & 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 & 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 & 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">×</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">×</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">×</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 (3–5 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>
|