Files
presensi/app/Views/dashboard/schedule_today.php
2026-03-05 14:37:36 +07:00

280 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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">&times;</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>