init backend presensi

This commit is contained in:
mwpn
2026-03-05 14:37:36 +07:00
commit b4fda6b9c9
319 changed files with 27261 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
<div class="space-y-6">
<div>
<h1 class="text-xl font-semibold">Pengaturan Presensi</h1>
<p class="text-gray-600 dark:text-gray-400 mt-1">Satu pengaturan terpusat: <strong>koordinat sekolah</strong> (untuk semua absen) dan <strong>jadwal jam masuk &amp; jam pulang</strong>.</p>
</div>
<div id="presence-loading" class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-12 text-center text-gray-500 dark:text-gray-400">
Memuat…
</div>
<div id="presence-error" class="hidden rounded-2xl border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 p-6 text-red-700 dark:text-red-300"></div>
<div id="presence-form-wrap" class="hidden space-y-6">
<!-- Zona sekolah -->
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm p-6">
<h2 class="text-base font-semibold text-gray-900 dark:text-gray-100 mb-1">Koordinat Sekolah (Zona Presensi)</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">Semua absen (mobile &amp; device) hanya valid jika siswa berada di dalam radius ini.</p>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label class="block mb-1 text-sm text-gray-600 dark:text-gray-300">Latitude</label>
<input type="number" step="0.00000001" id="zone-lat" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
<div>
<label class="block mb-1 text-sm text-gray-600 dark:text-gray-300">Longitude</label>
<input type="number" step="0.00000001" id="zone-lng" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
<div>
<label class="block mb-1 text-sm text-gray-600 dark:text-gray-300">Radius (meter)</label>
<input type="number" min="1" id="zone-radius" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
</div>
</div>
<!-- Jam masuk & pulang -->
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm p-6">
<h2 class="text-base font-semibold text-gray-900 dark:text-gray-100 mb-1">Jadwal Masuk &amp; Pulang</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">Window waktu untuk absen masuk dan absen pulang (format 24 jam).</p>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<label class="block mb-1 text-sm text-gray-600 dark:text-gray-300">Jam masuk (mulai)</label>
<input type="time" id="time-masuk-start" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
<div>
<label class="block mb-1 text-sm text-gray-600 dark:text-gray-300">Jam masuk (akhir)</label>
<input type="time" id="time-masuk-end" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
<div>
<label class="block mb-1 text-sm text-gray-600 dark:text-gray-300">Jam pulang (mulai)</label>
<input type="time" id="time-pulang-start" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
<div>
<label class="block mb-1 text-sm text-gray-600 dark:text-gray-300">Jam pulang (akhir)</label>
<input type="time" id="time-pulang-end" class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
</div>
</div>
<div class="flex justify-end">
<button type="button" id="btn-save" class="inline-flex items-center justify-center px-5 py-2.5 rounded-lg bg-primary text-white text-sm font-medium hover:bg-primary-hover">
Simpan Pengaturan
</button>
</div>
</div>
</div>
<script>
(function() {
var baseUrl = '<?= base_url() ?>'.replace(/\/$/, '');
var apiUrl = baseUrl + '/api/dashboard/presence-settings';
var loading = document.getElementById('presence-loading');
var errorEl = document.getElementById('presence-error');
var formWrap = document.getElementById('presence-form-wrap');
var zoneLat = document.getElementById('zone-lat');
var zoneLng = document.getElementById('zone-lng');
var zoneRadius = document.getElementById('zone-radius');
var timeMasukStart = document.getElementById('time-masuk-start');
var timeMasukEnd = document.getElementById('time-masuk-end');
var timePulangStart = document.getElementById('time-pulang-start');
var timePulangEnd = document.getElementById('time-pulang-end');
var btnSave = document.getElementById('btn-save');
function showError(msg) {
errorEl.textContent = msg || 'Terjadi kesalahan';
errorEl.classList.remove('hidden');
formWrap.classList.add('hidden');
}
function timeToHhMm(val) {
if (!val) return '';
if (val.length === 5 && val.indexOf(':') !== -1) return val;
var parts = (val + '').trim().split(/[:\s]/);
if (parts.length >= 2) return parts[0].padStart(2, '0') + ':' + parts[1].padStart(2, '0');
return val.substring(0, 5);
}
function hhMmToTimeInput(hhmmss) {
if (!hhmmss) return '';
var s = (hhmmss + '').trim();
if (s.length >= 5) return s.substring(0, 5);
return s;
}
function load() {
loading.classList.remove('hidden');
errorEl.classList.add('hidden');
formWrap.classList.add('hidden');
fetch(apiUrl, { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' })
.then(function(r) { return r.json(); })
.then(function(res) {
loading.classList.add('hidden');
if (res && res.success && res.data) {
var zone = res.data.zone || {};
var times = res.data.times || {};
zoneLat.value = zone.latitude != null ? zone.latitude : '';
zoneLng.value = zone.longitude != null ? zone.longitude : '';
zoneRadius.value = zone.radius_meters != null ? zone.radius_meters : '150';
timeMasukStart.value = hhMmToTimeInput(times.time_masuk_start);
timeMasukEnd.value = hhMmToTimeInput(times.time_masuk_end);
timePulangStart.value = hhMmToTimeInput(times.time_pulang_start);
timePulangEnd.value = hhMmToTimeInput(times.time_pulang_end);
formWrap.classList.remove('hidden');
} else {
showError(res && res.message ? res.message : 'Gagal memuat pengaturan');
}
})
.catch(function() {
loading.classList.add('hidden');
showError('Gagal memuat pengaturan (jaringan).');
});
}
btnSave.addEventListener('click', function() {
var lat = parseFloat(zoneLat.value);
var lng = parseFloat(zoneLng.value);
var radius = parseInt(zoneRadius.value, 10);
if (isNaN(lat) || isNaN(lng) || isNaN(radius) || radius < 1) {
alert('Isi koordinat sekolah (Latitude, Longitude, Radius) dengan benar.');
return;
}
var payload = {
zone: {
latitude: lat,
longitude: lng,
radius_meters: radius,
zone_name: 'Zona Sekolah'
},
times: {
time_masuk_start: timeToHhMm(timeMasukStart.value) + ':00',
time_masuk_end: timeToHhMm(timeMasukEnd.value) + ':00',
time_pulang_start: timeToHhMm(timePulangStart.value) + ':00',
time_pulang_end: timeToHhMm(timePulangEnd.value) + ':00'
}
};
btnSave.disabled = true;
fetch(apiUrl, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
credentials: 'same-origin',
body: JSON.stringify(payload)
})
.then(function(r) { return r.json(); })
.then(function(res) {
btnSave.disabled = false;
if (res && res.success) {
if (typeof showToast === 'function') showToast('Pengaturan presensi berhasil disimpan', 'success');
else alert('Pengaturan berhasil disimpan.');
} else {
alert(res && res.message ? res.message : 'Gagal menyimpan');
}
})
.catch(function() {
btnSave.disabled = false;
alert('Gagal menyimpan (jaringan).');
});
});
load();
})();
</script>