1063 lines
50 KiB
PHP
1063 lines
50 KiB
PHP
<?php
|
|
session_start();
|
|
require_once 'config.php';
|
|
|
|
// Check if user is logged in
|
|
if (!isset($_SESSION['token'])) {
|
|
header('Location: login.php');
|
|
exit;
|
|
}
|
|
|
|
$token = $_SESSION['token'];
|
|
$user_data = $_SESSION['user_data'] ?? [];
|
|
|
|
// Get user profile data
|
|
$profile_result = api_get_profil($token);
|
|
$profile_data = $profile_result['success'] ? $profile_result['data'] : [];
|
|
|
|
// Get today's attendance data
|
|
$presensi_today_result = api_get_presensi_today($token);
|
|
$presensi_today = $presensi_today_result['success'] ? $presensi_today_result['data'] : [];
|
|
|
|
// Extract user info - sama seperti di dashboard
|
|
$pegawai = $profile_data['pegawai'] ?? $profile_data['data']['pegawai'] ?? $profile_data['data'] ?? [];
|
|
$user_name = $pegawai['nama_lengkap'] ?? $pegawai['nama'] ?? $user_data['nama'] ?? $user_data['nama_lengkap'] ?? 'User';
|
|
$user_jabatan = $pegawai['jabatan']['nama_jabatan'] ?? $pegawai['jabatan'] ?? $user_data['jabatan'] ?? $user_data['nama_jabatan'] ?? 'Karyawan';
|
|
$user_id = $pegawai['nip'] ?? $pegawai['id'] ?? $user_data['id'] ?? $user_data['nip'] ?? '';
|
|
$user_photo = uploads_pengguna_url((string) ($pegawai['photo'] ?? $pegawai['foto'] ?? $user_data['foto'] ?? $user_data['photo'] ?? ''));
|
|
|
|
// Extract work schedule info
|
|
$jam_masuk = $pegawai['jam_masuk'] ?? '08:00';
|
|
$jam_pulang = $pegawai['jam_pulang'] ?? '17:00';
|
|
$jam_istirahat = $pegawai['jam_istirahat'] ?? '12:00';
|
|
$kantor_nama = $pegawai['kantor']['nama_kantor'] ?? $user_data['kantor'] ?? 'Kantor Pusat';
|
|
$alamat_kantor = $pegawai['kantor']['alamat_kantor'] ?? $pegawai['alamat_kantor'] ?? $user_data['alamat_kantor'] ?? '';
|
|
|
|
// Extract kantor coordinates for distance validation - cek berbagai kemungkinan field
|
|
$kantor_lat = null;
|
|
$kantor_lng = null;
|
|
$toleransi_meter = 100; // Default 100 meter
|
|
|
|
// Cek di berbagai level struktur data
|
|
if (isset($pegawai['kantor'])) {
|
|
$kantor_data = $pegawai['kantor'];
|
|
$kantor_lat = $kantor_data['latitude'] ?? $kantor_data['lat'] ?? $kantor_data['koordinat_lat'] ?? $kantor_data['lat_kantor'] ?? null;
|
|
$kantor_lng = $kantor_data['longitude'] ?? $kantor_data['lng'] ?? $kantor_data['koordinat_lng'] ?? $kantor_data['lng_kantor'] ?? null;
|
|
$toleransi_meter = $kantor_data['jarak_rekam_presensi'] ?? $kantor_data['toleransi'] ?? $kantor_data['radius'] ?? $kantor_data['jarak_toleransi'] ?? 100;
|
|
} elseif (isset($profile_data['kantor'])) {
|
|
$kantor_data = $profile_data['kantor'];
|
|
$kantor_lat = $kantor_data['latitude'] ?? $kantor_data['lat'] ?? $kantor_data['koordinat_lat'] ?? $kantor_data['lat_kantor'] ?? null;
|
|
$kantor_lng = $kantor_data['longitude'] ?? $kantor_data['lng'] ?? $kantor_data['koordinat_lng'] ?? $kantor_data['lng_kantor'] ?? null;
|
|
$toleransi_meter = $kantor_data['jarak_rekam_presensi'] ?? $kantor_data['toleransi'] ?? $kantor_data['radius'] ?? $kantor_data['jarak_toleransi'] ?? 100;
|
|
} elseif (isset($profile_data['data']['kantor'])) {
|
|
$kantor_data = $profile_data['data']['kantor'];
|
|
$kantor_lat = $kantor_data['latitude'] ?? $kantor_data['lat'] ?? $kantor_data['koordinat_lat'] ?? $kantor_data['lat_kantor'] ?? null;
|
|
$kantor_lng = $kantor_data['longitude'] ?? $kantor_data['lng'] ?? $kantor_data['koordinat_lng'] ?? $kantor_data['lng_kantor'] ?? null;
|
|
$toleransi_meter = $kantor_data['jarak_rekam_presensi'] ?? $kantor_data['toleransi'] ?? $kantor_data['radius'] ?? $kantor_data['jarak_toleransi'] ?? 100;
|
|
}
|
|
|
|
// Fallback ke default jika tidak ditemukan
|
|
if ($kantor_lat === null) {
|
|
$kantor_lat = -7.212239323216; // Koordinat kantor sebenarnya dari API
|
|
$kantor_lng = 107.902314730224; // Koordinat kantor sebenarnya dari API
|
|
$toleransi_meter = 20; // Toleransi sebenarnya dari API (jarak_rekam_presensi)
|
|
}
|
|
|
|
// Debug log untuk troubleshooting
|
|
error_log("Kantor coordinates - Lat: $kantor_lat, Lng: $kantor_lng, Toleransi: $toleransi_meter");
|
|
|
|
// Extract today's attendance status (API/DB bisa kirim 00:00:00 walau belum rekam — pakai presensi_waktu_terisi)
|
|
$presensi_data_today = $presensi_today['data'] ?? [];
|
|
$masuk_time = $presensi_data_today['jam_masuk'] ?? null;
|
|
$istirahat_mulai = $presensi_data_today['mulai_istirahat'] ?? null;
|
|
$istirahat_selesai = $presensi_data_today['beres_istirahat'] ?? null;
|
|
$pulang_time = $presensi_data_today['jam_pulang'] ?? null;
|
|
|
|
$masuk_disabled = presensi_waktu_terisi($masuk_time);
|
|
$istirahat_disabled = presensi_waktu_terisi($istirahat_mulai);
|
|
$pulang_disabled = presensi_waktu_terisi($pulang_time);
|
|
|
|
// Helper function untuk format tanggal Indonesia
|
|
function formatTanggalIndonesia($tanggal = null, $format = 'l, d F Y')
|
|
{
|
|
$hari = ['Sunday' => 'Minggu', 'Monday' => 'Senin', 'Tuesday' => 'Selasa', 'Wednesday' => 'Rabu', 'Thursday' => 'Kamis', 'Friday' => 'Jumat', 'Saturday' => 'Sabtu'];
|
|
$bulan = ['January' => 'Januari', 'February' => 'Februari', 'March' => 'Maret', 'April' => 'April', 'May' => 'Mei', 'June' => 'Juni', 'July' => 'Juli', 'August' => 'Agustus', 'September' => 'September', 'October' => 'Oktober', 'November' => 'November', 'December' => 'Desember'];
|
|
|
|
if ($tanggal) {
|
|
$tanggal_inggris = date($format, strtotime($tanggal));
|
|
} else {
|
|
$tanggal_inggris = date($format);
|
|
}
|
|
|
|
$tanggal_indonesia = str_replace(array_keys($hari), array_values($hari), str_replace(array_keys($bulan), array_values($bulan), $tanggal_inggris));
|
|
return $tanggal_indonesia;
|
|
}
|
|
|
|
// Handle form submission
|
|
$success_message = '';
|
|
$error_message = '';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = $_POST['action'] ?? '';
|
|
$latitude = $_POST['latitude'] ?? '';
|
|
$longitude = $_POST['longitude'] ?? '';
|
|
$photo_base64 = $_POST['photo_base64'] ?? '';
|
|
|
|
$data = [
|
|
'latitude' => $latitude,
|
|
'longitude' => $longitude,
|
|
'waktu' => date('Y-m-d H:i:s')
|
|
];
|
|
|
|
// Handle photo dari kamera (base64) jika tersedia
|
|
if (!empty($photo_base64)) {
|
|
$data['photo'] = $photo_base64;
|
|
}
|
|
|
|
$result = null;
|
|
switch ($action) {
|
|
case 'masuk':
|
|
$result = api_save_masuk($token, $data);
|
|
break;
|
|
case 'pulang':
|
|
$result = api_save_pulang($token, $data);
|
|
break;
|
|
case 'istirahat':
|
|
$result = api_save_istirahat($token, $data);
|
|
break;
|
|
}
|
|
|
|
if ($result && $result['success']) {
|
|
$success_message = 'Presensi berhasil direkam!';
|
|
} else {
|
|
// Handle berbagai jenis error dari API
|
|
$api_message = '';
|
|
if (isset($result['data']['pesan'])) {
|
|
$api_message = $result['data']['pesan'];
|
|
} elseif (isset($result['data']['message'])) {
|
|
$api_message = $result['data']['message'];
|
|
} elseif (isset($result['data']['error'])) {
|
|
$api_message = $result['data']['error'];
|
|
}
|
|
|
|
// Debug info untuk troubleshooting
|
|
error_log("Presensi API Error - Action: $action, Message: $api_message, Full Response: " . json_encode($result));
|
|
|
|
$error_message = $api_message ?: 'Gagal merekam presensi.';
|
|
}
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="id">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, maximum-scale=1">
|
|
<meta name="theme-color" content="#F2F2F7">
|
|
<meta name="color-scheme" content="light">
|
|
<!-- ✅ PATCH: Izinkan lokasi & kamera di mobile browser -->
|
|
<meta name="permissions-policy" content="geolocation=(self), camera=(self)">
|
|
<meta name="referrer" content="no-referrer">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
<title>Bank BIJ — Rekam presensi</title>
|
|
<link rel="icon" type="image/png" href="<?php echo htmlspecialchars(site_public_asset('assets/images/bij_logo.png')); ?>">
|
|
<link rel="stylesheet" href="<?php echo asset('css/style.css'); ?>">
|
|
<link rel="stylesheet" href="<?php echo asset('css/tailwind.css'); ?>">
|
|
<link rel="stylesheet" href="<?php echo asset('css/ios-app.css'); ?>">
|
|
<link rel="stylesheet" href="<?php echo asset('fa-7.1.0-web/css/all.min.css'); ?>">
|
|
|
|
</head>
|
|
|
|
<body class="ios-app">
|
|
<div class="mobile-frame p-4">
|
|
<header class="ios-screen-head">
|
|
<div class="ios-screen-head__row">
|
|
<div class="bank-logo flex-shrink-0">
|
|
<img src="<?php echo asset('logo_bij3.png'); ?>" alt="Logo Bank BIJ">
|
|
</div>
|
|
<div class="ios-head-meta">
|
|
<h1 class="ios-head-brand">BPR <span class="ios-accent">Intan Jabar</span></h1>
|
|
<p class="ios-head-page">Rekam presensi</p>
|
|
</div>
|
|
<div class="ios-head-user">
|
|
<span class="ios-head-name"><?php echo htmlspecialchars($user_name); ?></span>
|
|
<span class="ios-head-role"><?php echo htmlspecialchars($user_jabatan); ?></span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<nav class="ios-nav-back" aria-label="Navigasi">
|
|
<a href="dashboard.php"><i class="fas fa-chevron-left" aria-hidden="true"></i> Dashboard</a>
|
|
</nav>
|
|
|
|
<!-- Error/Success Messages -->
|
|
<?php if (!empty($error_message)): ?>
|
|
<div class="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded-lg">
|
|
<div class="flex items-center">
|
|
<i class="fas fa-exclamation-circle mr-2"></i>
|
|
<?php echo htmlspecialchars($error_message); ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($success_message)): ?>
|
|
<div class="mb-4 p-3 bg-green-100 border border-green-400 text-green-700 rounded-lg">
|
|
<div class="flex items-center">
|
|
<i class="fas fa-check-circle mr-2"></i>
|
|
<?php echo htmlspecialchars($success_message); ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Informasi Rekam Masuk -->
|
|
<div class="dashboard-card p-5 mb-4">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-4 text-center">Rekam Kehadiran</h2>
|
|
|
|
<!-- Informasi Karyawan -->
|
|
<div class="mb-5 p-3 bg-blue-50 rounded-lg">
|
|
<div class="flex items-center">
|
|
<?php if ($user_photo !== ''): ?>
|
|
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-3 overflow-hidden">
|
|
<img src="<?php echo htmlspecialchars($user_photo); ?>" alt="Foto Profil" class="w-full h-full object-cover">
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-3">
|
|
<i class="fas fa-user text-blue-700"></i>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div>
|
|
<p class="font-medium text-gray-800"><?php echo htmlspecialchars($user_name); ?></p>
|
|
<p class="text-sm text-gray-600"><?php echo htmlspecialchars($user_jabatan); ?></p>
|
|
<p class="text-xs text-gray-500">NIP: <?php echo htmlspecialchars($user_id); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tanggal dan Waktu -->
|
|
<div class="mb-5">
|
|
<div class="flex justify-between items-center mb-2">
|
|
<p class="text-gray-700 font-medium">Tanggal</p>
|
|
<p class="text-gray-800" id="current-date"><?php echo formatTanggalIndonesia(); ?></p>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<p class="text-gray-700 font-medium">Waktu Sekarang</p>
|
|
<p class="text-xl font-bold text-blue-700" id="current-time">08:45:32</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pilihan Presensi -->
|
|
<div class="mb-5">
|
|
<h3 class="text-gray-700 font-medium mb-4 text-center" id="label-jenis-presensi">Pilih Jenis Presensi</h3>
|
|
<div class="grid grid-cols-3 gap-2 sm:gap-4" role="radiogroup" aria-labelledby="label-jenis-presensi">
|
|
<?php
|
|
if (!$masuk_disabled) {
|
|
$default_action = 'masuk';
|
|
} elseif (!$istirahat_disabled) {
|
|
$default_action = 'istirahat';
|
|
} elseif (!$pulang_disabled) {
|
|
$default_action = 'pulang';
|
|
} else {
|
|
$default_action = 'masuk';
|
|
}
|
|
$rekam_label = ['masuk' => 'Masuk', 'istirahat' => 'Istirahat', 'pulang' => 'Pulang'];
|
|
?>
|
|
<button type="button" role="radio" aria-checked="<?php echo ($default_action === 'masuk' && !$masuk_disabled) ? 'true' : 'false'; ?>" class="presensi-btn presensi-btn--masuk py-4 px-4 rounded-xl font-medium flex flex-col items-center justify-center transition-all duration-200 <?php echo $masuk_disabled ? 'presensi-btn--done' : ($default_action === 'masuk' ? 'presensi-btn--selected' : 'presensi-btn--idle'); ?>" data-action="masuk" <?php echo $masuk_disabled ? 'disabled' : ''; ?>>
|
|
<span class="presensi-pick-badge" aria-hidden="true">✓</span>
|
|
<i class="fas fa-sign-in-alt text-2xl mb-2"></i>
|
|
<span class="text-sm font-semibold"><?php echo $masuk_disabled ? 'Sudah' : 'Masuk'; ?></span>
|
|
<?php if ($masuk_disabled): ?>
|
|
<span class="text-xs opacity-75 mt-1"><?php echo htmlspecialchars((string) $masuk_time); ?></span>
|
|
<?php endif; ?>
|
|
</button>
|
|
<button type="button" role="radio" aria-checked="<?php echo ($default_action === 'istirahat' && !$istirahat_disabled) ? 'true' : 'false'; ?>" class="presensi-btn presensi-btn--istirahat py-4 px-4 rounded-xl font-medium flex flex-col items-center justify-center transition-all duration-200 <?php echo $istirahat_disabled ? 'presensi-btn--done' : ($default_action === 'istirahat' ? 'presensi-btn--selected' : 'presensi-btn--idle'); ?>" data-action="istirahat" <?php echo $istirahat_disabled ? 'disabled' : ''; ?>>
|
|
<span class="presensi-pick-badge" aria-hidden="true">✓</span>
|
|
<i class="fas fa-coffee text-2xl mb-2"></i>
|
|
<span class="text-sm font-semibold"><?php echo $istirahat_disabled ? 'Sudah' : 'Istirahat'; ?></span>
|
|
<?php if ($istirahat_disabled): ?>
|
|
<span class="text-xs opacity-75 mt-1"><?php echo htmlspecialchars((string) $istirahat_mulai); ?></span>
|
|
<?php endif; ?>
|
|
</button>
|
|
<button type="button" role="radio" aria-checked="<?php echo ($default_action === 'pulang' && !$pulang_disabled) ? 'true' : 'false'; ?>" class="presensi-btn presensi-btn--pulang py-4 px-4 rounded-xl font-medium flex flex-col items-center justify-center transition-all duration-200 <?php echo $pulang_disabled ? 'presensi-btn--done' : ($default_action === 'pulang' ? 'presensi-btn--selected' : 'presensi-btn--idle'); ?>" data-action="pulang" <?php echo $pulang_disabled ? 'disabled' : ''; ?>>
|
|
<span class="presensi-pick-badge" aria-hidden="true">✓</span>
|
|
<i class="fas fa-sign-out-alt text-2xl mb-2"></i>
|
|
<span class="text-sm font-semibold"><?php echo $pulang_disabled ? 'Sudah' : 'Pulang'; ?></span>
|
|
<?php if ($pulang_disabled): ?>
|
|
<span class="text-xs opacity-75 mt-1"><?php echo htmlspecialchars((string) $pulang_time); ?></span>
|
|
<?php endif; ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lokasi Perekaman -->
|
|
<div class="mb-5">
|
|
<h3 class="text-gray-700 font-medium mb-3 text-sm">Lokasi Perekaman</h3>
|
|
<div class="bg-white border border-gray-200 rounded-lg p-3 shadow-sm">
|
|
<!-- GPS Status -->
|
|
<div class="mb-3">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-gray-600 text-sm font-medium">Status GPS:</span>
|
|
<div id="gps-status" class="flex items-center">
|
|
<div class="w-2 h-2 bg-yellow-500 rounded-full mr-2 pulse-animation"></div>
|
|
<span class="text-yellow-700 text-xs">Mendeteksi lokasi...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Koordinat Kantor -->
|
|
<div class="mb-3 p-2 bg-blue-50 rounded">
|
|
<div class="flex items-center mb-1">
|
|
<i class="fas fa-building text-blue-600 mr-2 text-xs"></i>
|
|
<span class="text-gray-600 text-xs font-medium">Lokasi Kerja:</span>
|
|
</div>
|
|
<p class="text-gray-800 text-xs font-medium mb-1"><?php echo htmlspecialchars($kantor_nama); ?></p>
|
|
<p class="text-gray-800 text-xs font-mono" id="kantor-coords"><?php echo $kantor_lat . ', ' . $kantor_lng; ?></p>
|
|
</div>
|
|
|
|
<!-- Koordinat Presensi -->
|
|
<div class="mb-3 p-2 bg-green-50 rounded">
|
|
<div class="flex items-center mb-1">
|
|
<i class="fas fa-map-marker-alt text-green-600 mr-2 text-xs"></i>
|
|
<span class="text-gray-600 text-xs font-medium">Koordinat Presensi:</span>
|
|
</div>
|
|
<p class="text-gray-800 text-xs font-mono" id="presensi-coords">-</p>
|
|
</div>
|
|
|
|
<!-- Alamat (reverse geocode; id dipakai skrip) -->
|
|
<div class="mb-3 p-2 bg-gray-50 rounded">
|
|
<div class="flex items-center mb-1">
|
|
<i class="fas fa-road text-gray-500 mr-2 text-xs"></i>
|
|
<span class="text-gray-600 text-xs font-medium">Alamat perkiraan</span>
|
|
</div>
|
|
<p class="text-gray-800 text-xs leading-snug" id="location-address">—</p>
|
|
</div>
|
|
|
|
<!-- Jarak Presensi -->
|
|
<div class="mb-3 p-2 bg-amber-50 rounded">
|
|
<div class="flex items-center mb-1">
|
|
<i class="fas fa-ruler text-amber-600 mr-2 text-xs"></i>
|
|
<span class="text-gray-600 text-xs font-medium">Jarak Presensi:</span>
|
|
</div>
|
|
<p class="text-gray-800 text-xs font-mono" id="jarak-presensi">-</p>
|
|
</div>
|
|
|
|
<!-- Tombol Retry -->
|
|
<div class="text-center">
|
|
<button id="retryLocationBtn" class="hidden text-blue-600 hover:text-blue-800 font-medium transition-colors text-xs" onclick="getCurrentLocation()">
|
|
<i class="fas fa-redo mr-1"></i> Coba Lagi
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Upload Foto -->
|
|
<div class="mb-6">
|
|
<h3 class="text-gray-700 font-medium mb-2">Foto Selfie</h3>
|
|
<div class="camera-preview" id="cameraPreview">
|
|
<i class="fas fa-camera text-gray-400 text-4xl mb-2"></i>
|
|
<p class="text-gray-500 text-center">Ambil foto selfie untuk presensi</p>
|
|
<p class="text-gray-400 text-sm text-center mt-1">Pastikan wajah terlihat jelas</p>
|
|
</div>
|
|
<div class="mt-3">
|
|
<button id="takePhotoBtn" class="action-btn w-full py-2 px-4 bg-blue-700 text-white rounded-lg flex items-center justify-center">
|
|
<i class="fas fa-camera mr-2"></i> Ambil Foto
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Rekam Presensi -->
|
|
<form method="POST" enctype="multipart/form-data" id="presensiForm">
|
|
<input type="hidden" name="action" id="actionInput" value="<?php echo htmlspecialchars($default_action); ?>">
|
|
<input type="hidden" name="latitude" id="latitudeInput" value="">
|
|
<input type="hidden" name="longitude" id="longitudeInput" value="">
|
|
<input type="hidden" name="photo_base64" id="photoBase64Input" value="">
|
|
|
|
|
|
<!-- Tombol Submit -->
|
|
<button type="submit" id="recordAttendanceBtn" class="w-full py-3 bg-blue-700 text-white rounded-lg font-medium flex items-center justify-center action-btn">
|
|
<i class="fas fa-fingerprint mr-2"></i> Rekam <?php echo htmlspecialchars($rekam_label[$default_action] ?? 'Kehadiran'); ?>
|
|
</button>
|
|
|
|
<!-- Status Lokasi untuk Submit -->
|
|
<div id="locationSubmitStatus" class="mt-2 text-center text-sm hidden">
|
|
<p class="text-red-600">
|
|
<i class="fas fa-exclamation-triangle mr-1"></i>
|
|
Lokasi harus terdeteksi untuk merekam presensi
|
|
</p>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="text-center mt-6 ios-foot">
|
|
<p>© 2024 Bank BIJ • v2.4.1</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Konfirmasi -->
|
|
<div id="successModal" class="fixed inset-0 flex items-center justify-center z-50 hidden">
|
|
<div class="absolute inset-0 bg-black opacity-50"></div>
|
|
<div class="dashboard-card p-6 mx-4 z-10 w-full max-w-sm">
|
|
<div class="text-center">
|
|
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<i class="fas fa-check text-green-600 text-2xl"></i>
|
|
</div>
|
|
<h3 class="text-xl font-bold text-gray-800 mb-2">Berhasil!</h3>
|
|
<p class="text-gray-600 mb-4">Kehadiran masuk Anda telah berhasil direkam.</p>
|
|
<button id="closeModalBtn" class="w-full py-2 bg-blue-700 text-white rounded-lg font-medium">
|
|
Oke
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Peringatan Lokasi -->
|
|
<div id="locationModal" class="fixed inset-0 flex items-center justify-center z-50 hidden">
|
|
<div class="absolute inset-0 bg-black opacity-50"></div>
|
|
<div class="dashboard-card p-6 mx-4 z-10 w-full max-w-sm relative">
|
|
<button id="closeLocationModalBtn" class="absolute top-3 right-3 text-gray-400 hover:text-gray-600 transition-colors">
|
|
<i class="fas fa-times text-xl"></i>
|
|
</button>
|
|
<div class="text-center">
|
|
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<i class="fas fa-map-marker-alt text-red-600 text-2xl"></i>
|
|
</div>
|
|
<h3 class="text-xl font-bold text-gray-800 mb-2">Lokasi Diperlukan!</h3>
|
|
<p class="text-gray-600 mb-4">Untuk merekam presensi, Anda harus mengaktifkan akses lokasi. Silakan:</p>
|
|
<div class="text-left text-sm text-gray-600 mb-4">
|
|
<p>1. Klik ikon lokasi di address bar browser</p>
|
|
<p>2. Pilih "Izinkan" atau "Allow"</p>
|
|
<p>3. Refresh halaman ini</p>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button id="retryLocationModalBtn" class="flex-1 py-2 bg-blue-700 text-white rounded-lg font-medium">
|
|
<i class="fas fa-redo mr-1"></i> Coba Lagi
|
|
</button>
|
|
<button id="refreshPageBtn" class="flex-1 py-2 bg-gray-500 text-white rounded-lg font-medium">
|
|
<i class="fas fa-refresh mr-1"></i> Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Update waktu dan tanggal secara real-time
|
|
function updateDateTime() {
|
|
const now = new Date();
|
|
|
|
// Format tanggal
|
|
const optionsDate = {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
};
|
|
const formattedDate = now.toLocaleDateString('id-ID', optionsDate);
|
|
document.getElementById('current-date').textContent = formattedDate;
|
|
|
|
// Format waktu
|
|
const hours = String(now.getHours()).padStart(2, '0');
|
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
document.getElementById('current-time').textContent = `${hours}:${minutes}:${seconds}`;
|
|
}
|
|
|
|
// Update waktu setiap detik
|
|
setInterval(updateDateTime, 1000);
|
|
updateDateTime(); // Panggil sekali saat pertama kali load
|
|
|
|
function setLocationAddress(text) {
|
|
const el = document.getElementById('location-address');
|
|
if (el) {
|
|
el.textContent = text;
|
|
}
|
|
}
|
|
|
|
// Function untuk reverse geocoding (koordinat ke alamat)
|
|
function reverseGeocode(lat, lng) {
|
|
const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&addressdetails=1&accept-language=id`;
|
|
|
|
fetch(url)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('HTTP ' + response.status);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data && data.display_name) {
|
|
let address = data.display_name;
|
|
const parts = address.split(', ');
|
|
const shortAddress = parts.length >= 3 ? parts.slice(-3).join(', ') : address;
|
|
|
|
setLocationAddress(shortAddress);
|
|
|
|
const distanceValidation = validateDistance(lat, lng);
|
|
document.getElementById('jarak-presensi').textContent =
|
|
`${distanceValidation.distance.toFixed(0)} meter (${distanceValidation.valid ? 'Dalam jangkauan' : 'Di luar jangkauan'})`;
|
|
} else {
|
|
throw new Error('Tidak dapat mendapatkan alamat');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error reverse geocoding:', error);
|
|
setLocationAddress('Alamat tidak dapat dideteksi');
|
|
});
|
|
}
|
|
|
|
// Function untuk mendapatkan lokasi saat ini
|
|
function getCurrentLocation() {
|
|
console.log('=== GEOLOCATION DEBUG ===');
|
|
console.log('Navigator geolocation:', !!navigator.geolocation);
|
|
console.log('Protocol:', location.protocol);
|
|
console.log('Hostname:', location.hostname);
|
|
console.log('User Agent:', navigator.userAgent);
|
|
|
|
if (navigator.geolocation) {
|
|
const retryBtn = document.getElementById('retryLocationBtn');
|
|
|
|
// Reset status
|
|
locationDetected = false;
|
|
locationError = false;
|
|
retryBtn.classList.add('hidden');
|
|
|
|
// Update GPS status
|
|
const gpsStatus = document.getElementById('gps-status');
|
|
gpsStatus.innerHTML = `
|
|
<div class="w-2 h-2 bg-yellow-500 rounded-full mr-2 pulse-animation"></div>
|
|
<span class="text-yellow-700 text-xs">Mendeteksi lokasi...</span>
|
|
`;
|
|
|
|
// Cek permission status terlebih dahulu
|
|
if (navigator.permissions) {
|
|
navigator.permissions.query({
|
|
name: 'geolocation'
|
|
}).then(function(result) {
|
|
console.log('Geolocation permission:', result.state);
|
|
|
|
if (result.state === 'denied') {
|
|
showLocationError('Akses lokasi ditolak. Silakan aktifkan di pengaturan browser.');
|
|
return;
|
|
}
|
|
|
|
// Lanjutkan dengan getCurrentPosition
|
|
requestLocation();
|
|
}).catch(function(error) {
|
|
console.log('Permission query failed:', error);
|
|
// Lanjutkan dengan getCurrentPosition jika permission query gagal
|
|
requestLocation();
|
|
});
|
|
} else {
|
|
// Browser tidak support permission API, langsung coba
|
|
console.log('Permission API not supported, trying direct geolocation...');
|
|
requestLocation();
|
|
}
|
|
|
|
function requestLocation() {
|
|
navigator.geolocation.getCurrentPosition(
|
|
function(position) {
|
|
const lat = position.coords.latitude;
|
|
const lng = position.coords.longitude;
|
|
const accuracy = position.coords.accuracy;
|
|
|
|
console.log('✅ Location obtained successfully!');
|
|
console.log('Latitude:', lat);
|
|
console.log('Longitude:', lng);
|
|
console.log('Accuracy:', accuracy, 'meters');
|
|
|
|
// Simpan koordinat ke hidden input
|
|
document.getElementById('latitudeInput').value = lat;
|
|
document.getElementById('longitudeInput').value = lng;
|
|
|
|
// Set status lokasi berhasil
|
|
locationDetected = true;
|
|
locationError = false;
|
|
|
|
// Update koordinat presensi
|
|
document.getElementById('presensi-coords').textContent = `${lat.toFixed(6)}, ${lng.toFixed(6)}`;
|
|
|
|
// Hitung dan tampilkan jarak
|
|
const distanceValidation = validateDistance(lat, lng);
|
|
document.getElementById('jarak-presensi').textContent = `${distanceValidation.distance.toFixed(0)} meter (${distanceValidation.valid ? 'Dalam jangkauan' : 'Di luar jangkauan'})`;
|
|
|
|
// Update GPS status
|
|
const gpsStatus = document.getElementById('gps-status');
|
|
gpsStatus.innerHTML = `
|
|
<div class="w-2 h-2 bg-green-500 rounded-full mr-2"></div>
|
|
<span class="text-green-700 text-xs">GPS Aktif</span>
|
|
`;
|
|
|
|
// Reverse geocoding untuk mendapatkan alamat
|
|
reverseGeocode(lat, lng);
|
|
},
|
|
function(error) {
|
|
console.error('❌ Geolocation Error:', error);
|
|
console.error('Error code:', error.code);
|
|
console.error('Error message:', error.message);
|
|
showLocationError(getErrorMessage(error));
|
|
}, {
|
|
enableHighAccuracy: true,
|
|
timeout: 30000, // 30 detik timeout untuk mobile
|
|
maximumAge: 0 // Jangan gunakan cache
|
|
}
|
|
);
|
|
}
|
|
|
|
function showLocationError(message) {
|
|
// Set status lokasi error
|
|
locationDetected = false;
|
|
locationError = true;
|
|
|
|
setLocationAddress(message);
|
|
document.getElementById('presensi-coords').textContent = '-';
|
|
document.getElementById('jarak-presensi').textContent = '-';
|
|
|
|
// Update GPS status
|
|
const gpsStatus = document.getElementById('gps-status');
|
|
gpsStatus.innerHTML = `
|
|
<div class="w-2 h-2 bg-red-500 rounded-full mr-2"></div>
|
|
<span class="text-red-700 text-xs">GPS Tidak Aktif</span>
|
|
`;
|
|
|
|
// Tampilkan tombol retry
|
|
retryBtn.classList.remove('hidden');
|
|
|
|
// Tampilkan modal peringatan
|
|
document.getElementById('locationModal').classList.remove('hidden');
|
|
}
|
|
|
|
function getErrorMessage(error) {
|
|
switch (error.code) {
|
|
case error.PERMISSION_DENIED:
|
|
return 'Akses lokasi ditolak. Silakan aktifkan di pengaturan browser.';
|
|
case error.POSITION_UNAVAILABLE:
|
|
return 'Lokasi tidak tersedia. Pastikan GPS aktif.';
|
|
case error.TIMEOUT:
|
|
return 'Timeout mendeteksi lokasi. Coba lagi.';
|
|
default:
|
|
return 'Tidak dapat mengakses lokasi.';
|
|
}
|
|
}
|
|
} else {
|
|
// Set status lokasi error
|
|
locationDetected = false;
|
|
locationError = true;
|
|
|
|
setLocationAddress('Geolocation tidak didukung');
|
|
document.getElementById('presensi-coords').textContent = '-';
|
|
document.getElementById('jarak-presensi').textContent = '-';
|
|
|
|
// Update GPS status
|
|
const gpsStatus = document.getElementById('gps-status');
|
|
gpsStatus.innerHTML = `
|
|
<div class="w-2 h-2 bg-red-500 rounded-full mr-2"></div>
|
|
<span class="text-red-700 text-xs">GPS Tidak Didukung</span>
|
|
`;
|
|
|
|
// Tampilkan tombol retry
|
|
document.getElementById('retryLocationBtn').classList.remove('hidden');
|
|
|
|
// Tampilkan modal peringatan
|
|
document.getElementById('locationModal').classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
// Global variable untuk tracking lokasi
|
|
let locationDetected = false;
|
|
let locationError = false;
|
|
|
|
// Kantor coordinates dan toleransi dari PHP
|
|
const kantorLat = <?php echo $kantor_lat; ?>;
|
|
const kantorLng = <?php echo $kantor_lng; ?>;
|
|
const toleransiMeter = <?php echo $toleransi_meter; ?>;
|
|
|
|
// Function untuk menghitung jarak antara dua koordinat (Haversine formula)
|
|
function calculateDistance(lat1, lng1, lat2, lng2) {
|
|
const R = 6371000; // Radius bumi dalam meter
|
|
const dLat = (lat2 - lat1) * Math.PI / 180;
|
|
const dLng = (lng2 - lng1) * Math.PI / 180;
|
|
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
|
Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
return R * c; // Jarak dalam meter
|
|
}
|
|
|
|
// Function untuk validasi jarak dari kantor
|
|
function validateDistance(userLat, userLng) {
|
|
const distance = calculateDistance(kantorLat, kantorLng, userLat, userLng);
|
|
console.log(`Jarak dari kantor: ${distance.toFixed(0)} meter (toleransi: ${toleransiMeter} meter)`);
|
|
|
|
if (distance <= toleransiMeter) {
|
|
return {
|
|
valid: true,
|
|
distance: distance,
|
|
message: `Jarak: ${distance.toFixed(0)}m (dalam toleransi)`
|
|
};
|
|
} else {
|
|
return {
|
|
valid: false,
|
|
distance: distance,
|
|
message: `Jarak: ${distance.toFixed(0)}m (melebihi toleransi ${toleransiMeter}m)`
|
|
};
|
|
}
|
|
}
|
|
|
|
// Debug info untuk troubleshooting
|
|
console.log('Browser geolocation support:', !!navigator.geolocation);
|
|
console.log('Browser permissions API support:', !!navigator.permissions);
|
|
console.log('HTTPS protocol:', location.protocol === 'https:');
|
|
console.log('Kantor coordinates:', kantorLat, kantorLng);
|
|
console.log('Toleransi:', toleransiMeter, 'meter');
|
|
|
|
// Panggil getCurrentLocation saat halaman load
|
|
getCurrentLocation();
|
|
|
|
// Cleanup camera saat halaman di-unload
|
|
window.addEventListener('beforeunload', function() {
|
|
if (stream) {
|
|
stream.getTracks().forEach(track => track.stop());
|
|
}
|
|
});
|
|
|
|
|
|
// Global variables untuk camera
|
|
let stream = null;
|
|
let video = null;
|
|
let canvas = null;
|
|
let isCameraActive = false;
|
|
|
|
// Handle tombol ambil foto
|
|
document.getElementById('takePhotoBtn').addEventListener('click', function() {
|
|
if (!isCameraActive) {
|
|
startCamera();
|
|
} else {
|
|
capturePhoto();
|
|
}
|
|
});
|
|
|
|
// Function untuk memulai camera
|
|
async function startCamera() {
|
|
try {
|
|
const cameraPreview = document.getElementById('cameraPreview');
|
|
|
|
// Tampilkan loading
|
|
cameraPreview.innerHTML = `
|
|
<div class="w-full h-full bg-gray-800 flex items-center justify-center">
|
|
<div class="text-white text-center">
|
|
<div class="w-16 h-16 border-2 border-white rounded-full mx-auto mb-2 flex items-center justify-center">
|
|
<i class="fas fa-spinner fa-spin text-white text-xl"></i>
|
|
</div>
|
|
<p>Mengakses Camera...</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Request camera access (mobile-friendly constraints + fallbacks)
|
|
const constraintsList = [{
|
|
video: {
|
|
facingMode: {
|
|
exact: 'user'
|
|
},
|
|
width: {
|
|
ideal: 640
|
|
},
|
|
height: {
|
|
ideal: 480
|
|
}
|
|
},
|
|
audio: false
|
|
},
|
|
{
|
|
video: {
|
|
facingMode: 'user',
|
|
width: {
|
|
ideal: 640
|
|
},
|
|
height: {
|
|
ideal: 480
|
|
}
|
|
},
|
|
audio: false
|
|
},
|
|
{
|
|
video: true,
|
|
audio: false
|
|
}
|
|
];
|
|
|
|
let lastError = null;
|
|
for (const constraints of constraintsList) {
|
|
try {
|
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
break;
|
|
} catch (err) {
|
|
lastError = err;
|
|
console.warn('getUserMedia failed with constraints', constraints, err);
|
|
}
|
|
}
|
|
|
|
if (!stream) {
|
|
throw lastError || new Error('Camera stream not available');
|
|
}
|
|
|
|
// Create video element
|
|
video = document.createElement('video');
|
|
video.srcObject = stream;
|
|
video.autoplay = true;
|
|
video.muted = true;
|
|
// iOS Safari perlu playsinline agar tidak masuk fullscreen otomatis
|
|
video.setAttribute('playsinline', '');
|
|
video.style.width = '100%';
|
|
video.style.height = '100%';
|
|
video.style.objectFit = 'cover';
|
|
|
|
// Create canvas untuk capture
|
|
canvas = document.createElement('canvas');
|
|
canvas.width = 640;
|
|
canvas.height = 480;
|
|
|
|
// Update preview
|
|
cameraPreview.innerHTML = '';
|
|
cameraPreview.appendChild(video);
|
|
|
|
// Update button
|
|
const takePhotoBtn = document.getElementById('takePhotoBtn');
|
|
takePhotoBtn.innerHTML = '<i class="fas fa-camera mr-2"></i> Ambil Foto';
|
|
takePhotoBtn.classList.remove('bg-blue-700');
|
|
takePhotoBtn.classList.add('bg-green-700');
|
|
|
|
isCameraActive = true;
|
|
|
|
} catch (error) {
|
|
console.error('Error accessing camera:', error);
|
|
const cameraPreview = document.getElementById('cameraPreview');
|
|
cameraPreview.innerHTML = `
|
|
<div class="w-full h-full bg-red-100 flex items-center justify-center">
|
|
<div class="text-red-600 text-center">
|
|
<i class="fas fa-exclamation-triangle text-4xl mb-2"></i>
|
|
<p class="font-medium">Camera tidak dapat diakses</p>
|
|
<p class="text-sm">Pastikan:
|
|
<br>- Akses camera sudah diizinkan pada browser
|
|
<br>- Situs ini diakses via HTTPS (bukan HTTP)
|
|
<br>- Tidak ada aplikasi lain yang sedang memakai camera
|
|
</p>
|
|
<p class="text-sm mt-2">Alternatif: gunakan tombol "Unggah" untuk memilih foto dari galeri</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Function untuk capture foto
|
|
function capturePhoto() {
|
|
if (!video || !canvas) return;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Resize canvas untuk kompresi (max width 400px, maintain aspect ratio)
|
|
const maxWidth = 400;
|
|
const videoAspectRatio = video.videoWidth / video.videoHeight;
|
|
const newWidth = Math.min(maxWidth, video.videoWidth);
|
|
const newHeight = newWidth / videoAspectRatio;
|
|
|
|
canvas.width = newWidth;
|
|
canvas.height = newHeight;
|
|
|
|
// Draw video dengan ukuran yang sudah di-resize
|
|
ctx.drawImage(video, 0, 0, newWidth, newHeight);
|
|
|
|
// Convert ke data URL dengan kompresi tinggi (kualitas 60%)
|
|
const photoDataUrl = canvas.toDataURL('image/jpeg', 0.6);
|
|
|
|
// Update preview dengan foto yang diambil
|
|
const cameraPreview = document.getElementById('cameraPreview');
|
|
cameraPreview.innerHTML = `<img src="${photoDataUrl}" alt="Foto Selfie" style="width: 100%; height: 100%; object-fit: cover;">`;
|
|
|
|
// Simpan foto ke hidden input base64 untuk dikirim ke server
|
|
const photoBase64Input = document.getElementById('photoBase64Input');
|
|
// Hapus prefix data URL agar hanya kirim base64 murni
|
|
const base64Data = photoDataUrl.split(',')[1] || '';
|
|
photoBase64Input.value = base64Data;
|
|
|
|
// Tampilkan info ukuran (estimasi) untuk debug
|
|
const approxSizeKB = Math.round((base64Data.length * 3 / 4) / 1024);
|
|
console.log(`Foto terkompres ~${approxSizeKB}KB (${newWidth}x${newHeight})`);
|
|
|
|
// Stop camera
|
|
if (stream) {
|
|
stream.getTracks().forEach(track => track.stop());
|
|
stream = null;
|
|
}
|
|
|
|
// Update button
|
|
const takePhotoBtn = document.getElementById('takePhotoBtn');
|
|
takePhotoBtn.innerHTML = '<i class="fas fa-camera mr-2"></i> Ambil Foto';
|
|
takePhotoBtn.classList.remove('bg-green-700');
|
|
takePhotoBtn.classList.add('bg-blue-700');
|
|
|
|
// Tampilkan pesan sukses di area foto
|
|
const successMsg = document.createElement('div');
|
|
successMsg.className = 'absolute bottom-2 left-0 right-0 text-center';
|
|
successMsg.innerHTML = '<span class="bg-green-500 text-white text-xs py-1 px-2 rounded">Foto berhasil diambil</span>';
|
|
cameraPreview.appendChild(successMsg);
|
|
|
|
isCameraActive = false;
|
|
}
|
|
|
|
// Function untuk convert data URL ke File
|
|
function dataURLtoFile(dataurl, filename) {
|
|
return new Promise((resolve) => {
|
|
const arr = dataurl.split(',');
|
|
const mime = arr[0].match(/:(.*?);/)[1];
|
|
const bstr = atob(arr[1]);
|
|
let n = bstr.length;
|
|
const u8arr = new Uint8Array(n);
|
|
while (n--) {
|
|
u8arr[n] = bstr.charCodeAt(n);
|
|
}
|
|
resolve(new File([u8arr], filename, {
|
|
type: mime
|
|
}));
|
|
});
|
|
}
|
|
|
|
// Function untuk kompres gambar
|
|
function compressImage(dataUrl, callback) {
|
|
const img = new Image();
|
|
img.onload = function() {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Resize untuk kompresi (max width 400px, maintain aspect ratio)
|
|
const maxWidth = 400;
|
|
const aspectRatio = img.width / img.height;
|
|
const newWidth = Math.min(maxWidth, img.width);
|
|
const newHeight = newWidth / aspectRatio;
|
|
|
|
canvas.width = newWidth;
|
|
canvas.height = newHeight;
|
|
|
|
// Draw image dengan ukuran yang sudah di-resize
|
|
ctx.drawImage(img, 0, 0, newWidth, newHeight);
|
|
|
|
// Convert ke data URL dengan kompresi tinggi (kualitas 60%)
|
|
const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.6);
|
|
callback(compressedDataUrl);
|
|
};
|
|
img.src = dataUrl;
|
|
}
|
|
|
|
// Upload via galeri di-nonaktifkan sesuai permintaan; hanya kamera yang digunakan
|
|
|
|
const rekamLabelMap = {
|
|
masuk: 'Masuk',
|
|
istirahat: 'Istirahat',
|
|
pulang: 'Pulang'
|
|
};
|
|
|
|
function syncPresensiToggle(selectedBtn) {
|
|
document.querySelectorAll('.presensi-btn').forEach(b => {
|
|
if (b.disabled) {
|
|
b.setAttribute('aria-checked', 'false');
|
|
return;
|
|
}
|
|
const on = b === selectedBtn;
|
|
b.classList.toggle('presensi-btn--selected', on);
|
|
b.classList.toggle('presensi-btn--idle', !on);
|
|
b.setAttribute('aria-checked', on ? 'true' : 'false');
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll('.presensi-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
if (this.disabled) {
|
|
return;
|
|
}
|
|
syncPresensiToggle(this);
|
|
const action = this.getAttribute('data-action');
|
|
document.getElementById('actionInput').value = action;
|
|
const submitBtn = document.getElementById('recordAttendanceBtn');
|
|
const label = rekamLabelMap[action] || action;
|
|
submitBtn.innerHTML = `<i class="fas fa-fingerprint mr-2"></i> Rekam ${label}`;
|
|
});
|
|
});
|
|
|
|
// Handle form submission
|
|
document.getElementById('presensiForm').addEventListener('submit', function(e) {
|
|
// Validasi lokasi sebelum submit
|
|
if (!locationDetected) {
|
|
e.preventDefault(); // Block submit
|
|
|
|
// Tampilkan status submit
|
|
document.getElementById('locationSubmitStatus').classList.remove('hidden');
|
|
|
|
// Tampilkan modal peringatan
|
|
document.getElementById('locationModal').classList.remove('hidden');
|
|
|
|
// Focus ke tombol retry
|
|
setTimeout(() => {
|
|
document.getElementById('retryLocationModalBtn').focus();
|
|
}, 100);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Validasi jarak dari kantor
|
|
const userLat = parseFloat(document.getElementById('latitudeInput').value);
|
|
const userLng = parseFloat(document.getElementById('longitudeInput').value);
|
|
|
|
if (!isNaN(userLat) && !isNaN(userLng)) {
|
|
const distanceValidation = validateDistance(userLat, userLng);
|
|
|
|
if (!distanceValidation.valid) {
|
|
e.preventDefault(); // Block submit
|
|
|
|
// Tampilkan alert untuk jarak terlalu jauh
|
|
alert(`Presensi tidak dapat dilakukan!\n\n${distanceValidation.message}\n\nSilakan mendekat ke kantor untuk merekam presensi.`);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Sembunyikan status submit jika lokasi OK
|
|
document.getElementById('locationSubmitStatus').classList.add('hidden');
|
|
|
|
const btn = document.getElementById('recordAttendanceBtn');
|
|
const originalText = btn.innerHTML;
|
|
|
|
// Tampilkan loading
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Memproses...';
|
|
btn.disabled = true;
|
|
|
|
// Koordinat sudah diambil saat halaman load, langsung submit
|
|
// Form akan submit dengan koordinat yang sudah ada di hidden input
|
|
});
|
|
|
|
// Handle tombol tutup modal
|
|
document.getElementById('closeModalBtn').addEventListener('click', function() {
|
|
document.getElementById('successModal').classList.add('hidden');
|
|
// Redirect ke dashboard (dalam implementasi nyata)
|
|
// window.location.href = 'dashboard.html';
|
|
});
|
|
|
|
// Handle tombol retry di modal lokasi
|
|
document.getElementById('retryLocationModalBtn').addEventListener('click', function() {
|
|
document.getElementById('locationModal').classList.add('hidden');
|
|
getCurrentLocation();
|
|
});
|
|
|
|
// Handle tombol refresh di modal lokasi
|
|
document.getElementById('refreshPageBtn').addEventListener('click', function() {
|
|
window.location.reload();
|
|
});
|
|
|
|
// Handle tombol close di modal lokasi
|
|
document.getElementById('closeLocationModalBtn').addEventListener('click', function() {
|
|
document.getElementById('locationModal').classList.add('hidden');
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|