|
27 | 27 | .geo-status .ic { width:38px; height:38px; border-radius:10px; display:grid; place-items:center; background:#94a3b8; color:#fff; flex:none; font-size:20px; } |
28 | 28 | .geo-status.in .ic { background:#22c55e; } |
29 | 29 | .geo-status.out .ic { background:#ef4444; } |
| 30 | + .rm-icon { width:72px; height:72px; border-radius:50%; display:grid; place-items:center; margin:0 auto; font-size:40px; color:#fff; background:#22c55e; } |
| 31 | + .rm-icon.is-warn { background:#f59e0b; } |
| 32 | + .rm-icon.is-error { background:#ef4444; } |
30 | 33 | </style> |
31 | 34 |
|
32 | 35 | <div class="row justify-content-center g-3"> |
|
73 | 76 |
|
74 | 77 | <div id="result" class="alert d-none mt-3" role="alert"></div> |
75 | 78 |
|
| 79 | +{{-- Modal konfirmasi hasil absen — feedback yang jelas & di tengah layar, |
| 80 | + supaya karyawan langsung tahu absennya berhasil atau perlu tindakan. --}} |
| 81 | +<div class="modal fade" id="resultModal" tabindex="-1" data-bs-backdrop="static" aria-hidden="true"> |
| 82 | + <div class="modal-dialog modal-dialog-centered"> |
| 83 | + <div class="modal-content text-center"> |
| 84 | + <div class="modal-body p-4"> |
| 85 | + <div id="rm-icon" class="rm-icon mb-3"><i class="la la-check"></i></div> |
| 86 | + <h4 id="rm-title" class="mb-1">Absen tercatat</h4> |
| 87 | + <div id="rm-time" class="text-muted mb-2"></div> |
| 88 | + <div id="rm-note" class="alert alert-warning small py-2 mb-3 d-none"></div> |
| 89 | + <a href="{{ route('portal.dashboard') }}" class="btn btn-primary w-100 btn-lg"> |
| 90 | + <i class="la la-home"></i> Kembali ke Beranda |
| 91 | + </a> |
| 92 | + <a href="{{ route('portal.attendance') }}" class="btn btn-link w-100 mt-1">Lihat Riwayat Kehadiran</a> |
| 93 | + </div> |
| 94 | + </div> |
| 95 | + </div> |
| 96 | +</div> |
| 97 | + |
76 | 98 | <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> |
77 | 99 | <script> |
78 | 100 | (function () { |
@@ -193,25 +215,49 @@ function maybeEnable() { |
193 | 215 | const data = await res.json().catch(() => ({})); |
194 | 216 |
|
195 | 217 | if (!res.ok) { |
196 | | - resultBox.className = 'alert alert-danger mt-3'; |
197 | | - resultBox.textContent = data.message || 'Gagal menyimpan absensi.'; |
198 | | - resultBox.classList.remove('d-none'); |
| 218 | + showResult('error', 'Gagal menyimpan', data.message || 'Gagal menyimpan absensi.', null); |
199 | 219 | btn.disabled = false; |
200 | 220 | return; |
201 | 221 | } |
202 | 222 |
|
203 | | - resultBox.className = 'alert ' + (data.outside ? 'alert-warning' : 'alert-success') + ' mt-3'; |
204 | | - resultBox.innerHTML = '<b>' + data.message + '</b>' + |
205 | | - (data.outside ? '<br>Di luar radius — menunggu persetujuan manajer.' : ''); |
206 | | - resultBox.classList.remove('d-none'); |
| 223 | + // Stop kamera setelah sukses supaya lampu kamera mati & hemat baterai. |
| 224 | + try { (video.srcObject?.getTracks() || []).forEach(t => t.stop()); } catch (e) {} |
| 225 | +
|
| 226 | + const timeStr = data.time ? new Date(data.time.replace(' ', 'T')).toLocaleTimeString('id-ID', { hour:'2-digit', minute:'2-digit' }) : ''; |
| 227 | + showResult( |
| 228 | + data.outside ? 'warn' : 'success', |
| 229 | + data.message || 'Absen tercatat.', |
| 230 | + timeStr ? ('Pukul ' + timeStr + ' WIB') : '', |
| 231 | + data.outside ? 'Lokasi di luar radius kantor — menunggu persetujuan manajer.' : null |
| 232 | + ); |
207 | 233 | hint.textContent = 'Selesai.'; |
208 | 234 | } catch (e) { |
209 | | - resultBox.className = 'alert alert-danger mt-3'; |
210 | | - resultBox.textContent = 'Gagal terhubung — periksa koneksi.'; |
211 | | - resultBox.classList.remove('d-none'); |
| 235 | + showResult('error', 'Gagal terhubung', 'Periksa koneksi lalu coba lagi.', null); |
212 | 236 | btn.disabled = false; |
213 | 237 | } |
214 | 238 | }); |
| 239 | +
|
| 240 | + // Tampilkan modal hasil di tengah layar + fallback ke alert box. |
| 241 | + const resultModalEl = document.getElementById('resultModal'); |
| 242 | + const resultModal = resultModalEl ? new bootstrap.Modal(resultModalEl) : null; |
| 243 | +
|
| 244 | + function showResult(kind, title, time, note) { |
| 245 | + // Alert box (fallback bila modal gagal). |
| 246 | + resultBox.className = 'alert mt-3 ' + (kind === 'success' ? 'alert-success' : kind === 'warn' ? 'alert-warning' : 'alert-danger'); |
| 247 | + resultBox.innerHTML = '<b>' + title + '</b>' + (note ? '<br>' + note : ''); |
| 248 | + resultBox.classList.remove('d-none'); |
| 249 | +
|
| 250 | + if (!resultModal) return; |
| 251 | + const icon = document.getElementById('rm-icon'); |
| 252 | + icon.className = 'rm-icon mb-3' + (kind === 'warn' ? ' is-warn' : kind === 'error' ? ' is-error' : ''); |
| 253 | + icon.querySelector('i').className = 'la ' + (kind === 'error' ? 'la-times' : kind === 'warn' ? 'la-exclamation' : 'la-check'); |
| 254 | + document.getElementById('rm-title').textContent = title; |
| 255 | + document.getElementById('rm-time').textContent = time || ''; |
| 256 | + const noteEl = document.getElementById('rm-note'); |
| 257 | + if (note) { noteEl.textContent = note; noteEl.classList.remove('d-none'); } |
| 258 | + else { noteEl.classList.add('d-none'); } |
| 259 | + resultModal.show(); |
| 260 | + } |
215 | 261 | })(); |
216 | 262 | </script> |
217 | 263 | @endsection |
0 commit comments