init backend presensi
This commit is contained in:
279
app/Views/dashboard/schedule_today.php
Normal file
279
app/Views/dashboard/schedule_today.php
Normal file
@@ -0,0 +1,279 @@
|
||||
<div class="space-y-6">
|
||||
<h1 class="text-xl font-semibold">Jadwal Hari Ini</h1>
|
||||
|
||||
<!-- Banner: saat ini guru harus di kelas mana -->
|
||||
<div id="current-schedule-banner" class="hidden rounded-2xl border border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-900/20 p-4">
|
||||
<p class="text-sm font-medium text-blue-800 dark:text-blue-200 mb-1">📍 Anda seharusnya di:</p>
|
||||
<p id="current-schedule-text" class="text-base font-semibold text-blue-900 dark:text-blue-100"></p>
|
||||
<p id="current-schedule-hint" class="text-xs text-blue-600 dark:text-blue-300 mt-1"></p>
|
||||
</div>
|
||||
<div id="current-schedule-empty" class="hidden rounded-2xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 p-4 text-gray-600 dark:text-gray-400 text-sm">
|
||||
Tidak ada jadwal mengajar Anda hari ini, atau tidak ada jam aktif saat ini.
|
||||
</div>
|
||||
|
||||
<div id="schedule-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">
|
||||
Loading schedules…
|
||||
</div>
|
||||
<div id="schedule-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="schedule-empty" class="hidden 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">
|
||||
Tidak ada jadwal untuk hari ini.
|
||||
</div>
|
||||
<div id="schedule-content" class="hidden rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700/50 text-sm text-gray-600 dark:text-gray-400">
|
||||
<tr>
|
||||
<th class="px-6 py-3 font-medium">Mapel</th>
|
||||
<th class="px-6 py-3 font-medium">Kelas</th>
|
||||
<th class="px-6 py-3 font-medium">Guru</th>
|
||||
<th class="px-6 py-3 font-medium">Jam</th>
|
||||
<th class="px-6 py-3 font-medium w-52">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="schedule-tbody" class="divide-y divide-gray-200 dark:divide-gray-700"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal QR Absen Mapel -->
|
||||
<div id="qr-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-black/50" id="qr-modal-backdrop"></div>
|
||||
<div class="relative bg-white dark:bg-gray-800 rounded-2xl shadow-xl max-w-sm w-full mx-4 p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100">QR Absen Mapel</h2>
|
||||
<button type="button" id="qr-modal-close" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400">×</button>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1"><strong id="qr-subject">-</strong> — <span id="qr-class">-</span></p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-4" id="qr-expires"></p>
|
||||
<div id="qr-code-container" class="flex justify-center my-4"></div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 text-center">Siswa scan QR ini dengan aplikasi presensi untuk absen mapel.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
|
||||
<script>
|
||||
(function() {
|
||||
var baseUrl = '<?= base_url() ?>'.replace(/\/$/, '');
|
||||
var reportBaseUrl = baseUrl + '/dashboard/attendance/report/';
|
||||
var apiUrl = baseUrl + '/api/dashboard/schedules/today';
|
||||
var currentApiUrl = baseUrl + '/api/dashboard/schedules/current';
|
||||
|
||||
var currentBanner = document.getElementById('current-schedule-banner');
|
||||
var currentText = document.getElementById('current-schedule-text');
|
||||
var currentHint = document.getElementById('current-schedule-hint');
|
||||
var currentEmpty = document.getElementById('current-schedule-empty');
|
||||
|
||||
var loading = document.getElementById('schedule-loading');
|
||||
var errorEl = document.getElementById('schedule-error');
|
||||
var emptyEl = document.getElementById('schedule-empty');
|
||||
var content = document.getElementById('schedule-content');
|
||||
var tbody = document.getElementById('schedule-tbody');
|
||||
|
||||
function fetchCurrentAndHighlight() {
|
||||
fetch(currentApiUrl, { method: 'GET', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
||||
.then(function(res) { return res.json().then(function(j) { return { ok: res.ok, data: j }; }); })
|
||||
.then(function(r) {
|
||||
if (!r.ok || !r.data) {
|
||||
if (currentEmpty) currentEmpty.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
var d = r.data.data || r.data;
|
||||
var scheduleIdToHighlight = null;
|
||||
var isActiveNow = d.is_active_now === true;
|
||||
|
||||
if (isActiveNow && d.schedule_id) {
|
||||
scheduleIdToHighlight = d.schedule_id;
|
||||
if (currentBanner) {
|
||||
currentBanner.classList.remove('hidden');
|
||||
currentBanner.classList.remove('border-amber-200', 'dark:border-amber-800', 'bg-amber-50', 'dark:bg-amber-900/20');
|
||||
currentBanner.classList.add('border-blue-200', 'dark:border-blue-800', 'bg-blue-50', 'dark:bg-blue-900/20');
|
||||
}
|
||||
if (currentText) currentText.textContent = (d.subject_name || '-') + ' — Kelas ' + (d.class_name || '-');
|
||||
if (currentHint) currentHint.textContent = 'Jam ' + (d.start_time || '') + ' – ' + (d.end_time || '') + '. Tampilkan QR untuk jadwal ini di bawah.';
|
||||
if (currentEmpty) currentEmpty.classList.add('hidden');
|
||||
} else if (d.next_schedule && d.next_schedule.schedule_id) {
|
||||
var next = d.next_schedule;
|
||||
scheduleIdToHighlight = next.schedule_id;
|
||||
if (currentBanner) {
|
||||
currentBanner.classList.remove('hidden');
|
||||
currentBanner.classList.remove('border-blue-200', 'dark:border-blue-800', 'bg-blue-50', 'dark:bg-blue-900/20');
|
||||
currentBanner.classList.add('border-amber-200', 'dark:border-amber-800', 'bg-amber-50', 'dark:bg-amber-900/20');
|
||||
}
|
||||
if (currentText) currentText.textContent = 'Berikutnya: ' + (next.subject_name || '-') + ' — Kelas ' + (next.class_name || '-') + ' (mulai ' + (next.start_time || '') + ')';
|
||||
if (currentHint) currentHint.textContent = 'Belum jam mengajar. Tampilkan QR saat jam tersebut tiba.';
|
||||
if (currentEmpty) currentEmpty.classList.add('hidden');
|
||||
} else {
|
||||
if (currentBanner) currentBanner.classList.add('hidden');
|
||||
if (currentEmpty) currentEmpty.classList.remove('hidden');
|
||||
}
|
||||
|
||||
if (scheduleIdToHighlight && tbody) {
|
||||
var rows = tbody.querySelectorAll('tr[data-schedule-id]');
|
||||
rows.forEach(function(tr) {
|
||||
var id = tr.getAttribute('data-schedule-id');
|
||||
if (id && parseInt(id, 10) === scheduleIdToHighlight) {
|
||||
tr.classList.add('bg-blue-50', 'dark:bg-blue-900/20', 'ring-2', 'ring-inset', 'ring-blue-400', 'dark:ring-blue-500');
|
||||
var firstCell = tr.querySelector('td');
|
||||
if (firstCell && !firstCell.querySelector('.badge-current')) {
|
||||
var badge = document.createElement('span');
|
||||
badge.className = 'badge-current inline-block ml-1 px-2 py-0.5 text-xs font-medium rounded-full ' + (isActiveNow ? 'bg-blue-600 text-white' : 'bg-amber-500 text-white');
|
||||
badge.textContent = isActiveNow ? 'Saat ini' : 'Berikutnya';
|
||||
firstCell.appendChild(badge);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
if (currentEmpty) currentEmpty.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
var errorEl = document.getElementById('schedule-error');
|
||||
var emptyEl = document.getElementById('schedule-empty');
|
||||
var content = document.getElementById('schedule-content');
|
||||
var tbody = document.getElementById('schedule-tbody');
|
||||
|
||||
function showLoading() {
|
||||
loading.classList.remove('hidden');
|
||||
errorEl.classList.add('hidden');
|
||||
emptyEl.classList.add('hidden');
|
||||
content.classList.add('hidden');
|
||||
}
|
||||
function showError(msg) {
|
||||
loading.classList.add('hidden');
|
||||
emptyEl.classList.add('hidden');
|
||||
content.classList.add('hidden');
|
||||
errorEl.classList.remove('hidden');
|
||||
errorEl.textContent = msg;
|
||||
}
|
||||
function showEmpty() {
|
||||
loading.classList.add('hidden');
|
||||
errorEl.classList.add('hidden');
|
||||
content.classList.add('hidden');
|
||||
emptyEl.classList.remove('hidden');
|
||||
}
|
||||
function showContent() {
|
||||
loading.classList.add('hidden');
|
||||
errorEl.classList.add('hidden');
|
||||
emptyEl.classList.add('hidden');
|
||||
content.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (str == null) return '';
|
||||
var div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
fetch(apiUrl, { method: 'GET', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
||||
.then(function(res) { return res.json().then(function(j) { return { ok: res.ok, data: j }; }); })
|
||||
.then(function(r) {
|
||||
if (!r.ok) {
|
||||
showError(r.data && r.data.message ? r.data.message : 'Gagal memuat jadwal');
|
||||
return;
|
||||
}
|
||||
var list = r.data && r.data.data ? r.data.data : r.data;
|
||||
if (!Array.isArray(list)) {
|
||||
showError('Data jadwal tidak valid');
|
||||
return;
|
||||
}
|
||||
if (list.length === 0) {
|
||||
showEmpty();
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = '';
|
||||
list.forEach(function(row) {
|
||||
var tr = document.createElement('tr');
|
||||
tr.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/30';
|
||||
tr.setAttribute('data-schedule-id', row.schedule_id);
|
||||
var timeStr = (row.start_time || '') + ' – ' + (row.end_time || '');
|
||||
var reportUrl = reportBaseUrl + row.schedule_id;
|
||||
tr.innerHTML =
|
||||
'<td class="px-6 py-3 font-medium">' + escapeHtml(row.subject_name) + '</td>' +
|
||||
'<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + escapeHtml(row.class_name) + '</td>' +
|
||||
'<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + escapeHtml(row.teacher_name) + '</td>' +
|
||||
'<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + escapeHtml(timeStr) + '</td>' +
|
||||
'<td class="px-6 py-3 flex flex-wrap gap-2">' +
|
||||
'<a href="' + escapeHtml(reportUrl) + '" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-primary rounded-lg hover:opacity-90">Laporan</a>' +
|
||||
'<button type="button" class="btn-qr inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-200 bg-gray-100 dark:bg-gray-600 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-500" data-schedule-id="' + row.schedule_id + '" data-subject="' + escapeHtml(row.subject_name) + '" data-class="' + escapeHtml(row.class_name) + '">Tampilkan QR</button>' +
|
||||
'</td>';
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
showContent();
|
||||
fetchCurrentAndHighlight();
|
||||
})
|
||||
.catch(function() {
|
||||
showError('Koneksi gagal');
|
||||
});
|
||||
|
||||
var qrModal = document.getElementById('qr-modal');
|
||||
var qrContainer = document.getElementById('qr-code-container');
|
||||
var qrSubject = document.getElementById('qr-subject');
|
||||
var qrClass = document.getElementById('qr-class');
|
||||
var qrExpires = document.getElementById('qr-expires');
|
||||
var qrCloseBtn = document.getElementById('qr-modal-close');
|
||||
var qrApiUrl = baseUrl + '/api/dashboard/qr-attendance/generate';
|
||||
|
||||
function closeQrModal() {
|
||||
if (qrModal) qrModal.classList.add('hidden');
|
||||
if (qrContainer) {
|
||||
qrContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
tbody.addEventListener('click', function(e) {
|
||||
var btn = e.target.closest('.btn-qr');
|
||||
if (!btn) return;
|
||||
var scheduleId = btn.getAttribute('data-schedule-id');
|
||||
var subject = btn.getAttribute('data-subject') || '-';
|
||||
var classNm = btn.getAttribute('data-class') || '-';
|
||||
if (!scheduleId) return;
|
||||
|
||||
if (qrSubject) qrSubject.textContent = subject;
|
||||
if (qrClass) qrClass.textContent = classNm;
|
||||
if (qrExpires) qrExpires.textContent = 'Memuat…';
|
||||
if (qrContainer) qrContainer.innerHTML = '<p class="text-gray-500">Memuat…</p>';
|
||||
|
||||
fetch(qrApiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({ schedule_id: parseInt(scheduleId, 10) })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(res) {
|
||||
if (!res.success || !res.data || !res.data.token) {
|
||||
if (qrContainer) qrContainer.innerHTML = '<p class="text-red-500">' + (res.message || 'Gagal generate QR') + '</p>';
|
||||
if (qrExpires) qrExpires.textContent = '';
|
||||
return;
|
||||
}
|
||||
var token = res.data.token;
|
||||
var expiresAt = res.data.expires_at || '';
|
||||
if (qrExpires) qrExpires.textContent = 'Berlaku sampai: ' + expiresAt;
|
||||
|
||||
qrContainer.innerHTML = '';
|
||||
var qrDiv = document.createElement('div');
|
||||
qrDiv.id = 'qrcode';
|
||||
qrDiv.className = 'inline-block p-2 bg-white rounded-lg';
|
||||
qrContainer.appendChild(qrDiv);
|
||||
if (typeof QRCode !== 'undefined') {
|
||||
new QRCode(qrDiv, { text: token, width: 220, height: 220 });
|
||||
} else {
|
||||
qrDiv.innerHTML = '<p class="text-gray-600">Token: ' + token.substring(0, 16) + '…<br><small>Pasang library QRCode untuk tampil QR.</small></p>';
|
||||
}
|
||||
|
||||
if (qrModal) qrModal.classList.remove('hidden');
|
||||
})
|
||||
.catch(function() {
|
||||
if (qrContainer) qrContainer.innerHTML = '<p class="text-red-500">Gagal memuat (jaringan).</p>';
|
||||
if (qrExpires) qrExpires.textContent = '';
|
||||
});
|
||||
});
|
||||
|
||||
if (qrCloseBtn) qrCloseBtn.addEventListener('click', closeQrModal);
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user