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

214 lines
11 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" id="attendance-report-app">
<div class="flex flex-wrap items-center justify-between gap-4">
<h1 class="text-xl font-semibold">Schedule Attendance Report</h1>
<div class="flex items-center gap-2">
<label for="report-date" class="text-sm text-gray-600 dark:text-gray-400">Date</label>
<input type="date" id="report-date" class="rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm focus:ring-2 focus:ring-primary"
value="<?= date('Y-m-d') ?>">
</div>
</div>
<div id="report-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 report…
</div>
<div id="report-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="report-content" class="hidden space-y-6">
<!-- SECTION 1: Schedule Info -->
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/30">
<h2 class="text-lg font-semibold">Schedule Info</h2>
</div>
<div class="p-6 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Subject</p>
<p id="info-subject" class="mt-1 font-medium"></p>
</div>
<div>
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Class</p>
<p id="info-class" class="mt-1 font-medium"></p>
</div>
<div>
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Teacher</p>
<p id="info-teacher" class="mt-1 font-medium"></p>
</div>
<div>
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Time</p>
<p id="info-time" class="mt-1 font-medium"></p>
</div>
</div>
</div>
<!-- SECTION 2: Summary Cards -->
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-6 shadow-sm">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Expected</p>
<p id="summary-expected" class="mt-2 text-2xl font-bold text-gray-900 dark:text-white">0</p>
</div>
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-6 shadow-sm">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Present</p>
<p id="summary-present" class="mt-2 text-2xl font-bold text-green-600 dark:text-green-400">0</p>
</div>
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-6 shadow-sm">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Late</p>
<p id="summary-late" class="mt-2 text-2xl font-bold text-yellow-600 dark:text-yellow-400">0</p>
</div>
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-6 shadow-sm">
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Absent</p>
<p id="summary-absent" class="mt-2 text-2xl font-bold text-red-600 dark:text-red-400">0</p>
</div>
</div>
<!-- SECTION 3: Present List -->
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/30">
<h2 class="text-lg font-semibold">Present</h2>
</div>
<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">NIS</th>
<th class="px-6 py-3 font-medium">Name</th>
<th class="px-6 py-3 font-medium">Status</th>
<th class="px-6 py-3 font-medium">Check-in time</th>
</tr>
</thead>
<tbody id="present-tbody" class="divide-y divide-gray-200 dark:divide-gray-700"></tbody>
</table>
</div>
</div>
<!-- SECTION 4: Absent List -->
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/30">
<h2 class="text-lg font-semibold">Absent</h2>
</div>
<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">NIS</th>
<th class="px-6 py-3 font-medium">Name</th>
</tr>
</thead>
<tbody id="absent-tbody" class="divide-y divide-gray-200 dark:divide-gray-700"></tbody>
</table>
</div>
</div>
</div>
</div>
<script>
(function() {
var scheduleId = <?= (int) $scheduleId ?>;
var baseUrl = '<?= base_url() ?>';
var apiUrl = baseUrl.replace(/\/$/, '') + '/api/attendance/report/schedule/' + scheduleId;
var reportLoading = document.getElementById('report-loading');
var reportError = document.getElementById('report-error');
var reportContent = document.getElementById('report-content');
var reportDate = document.getElementById('report-date');
function showLoading() {
reportLoading.classList.remove('hidden');
reportError.classList.add('hidden');
reportContent.classList.add('hidden');
}
function showError(msg) {
reportLoading.classList.add('hidden');
reportContent.classList.add('hidden');
reportError.classList.remove('hidden');
reportError.textContent = msg;
}
function showContent() {
reportLoading.classList.add('hidden');
reportError.classList.add('hidden');
reportContent.classList.remove('hidden');
}
function escapeHtml(str) {
if (str == null) return '';
var div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function formatTime(iso) {
if (!iso) return '';
var d = new Date(iso);
return d.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}
function statusBadgeClass(status) {
return status === 'LATE' ? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400' : 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400';
}
function renderReport(data) {
var schedule = data.schedule || {};
var summary = data.summary || {};
var present = data.present || [];
var absent = data.absent || [];
document.getElementById('info-subject').textContent = schedule.subject || '';
document.getElementById('info-class').textContent = schedule.class_name || ('Class #' + (schedule.class_id || ''));
document.getElementById('info-teacher').textContent = schedule.teacher || '';
document.getElementById('info-time').textContent = (schedule.start_time && schedule.end_time) ? (schedule.start_time + ' ' + schedule.end_time) : '';
document.getElementById('summary-expected').textContent = summary.expected_total != null ? summary.expected_total : 0;
document.getElementById('summary-present').textContent = summary.present_total != null ? summary.present_total : 0;
document.getElementById('summary-late').textContent = summary.late_total != null ? summary.late_total : 0;
document.getElementById('summary-absent').textContent = summary.absent_total != null ? summary.absent_total : 0;
var presentTbody = document.getElementById('present-tbody');
presentTbody.innerHTML = '';
present.forEach(function(row) {
var tr = document.createElement('tr');
tr.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/30';
var statusClass = statusBadgeClass(row.status);
tr.innerHTML = '<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + escapeHtml(row.nisn) + '</td>' +
'<td class="px-6 py-3 font-medium">' + escapeHtml(row.name) + '</td>' +
'<td class="px-6 py-3"><span class="inline-flex px-2.5 py-0.5 rounded-full text-xs font-medium ' + statusClass + '">' + escapeHtml(row.status) + '</span></td>' +
'<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + formatTime(row.checkin_at) + '</td>';
presentTbody.appendChild(tr);
});
var absentTbody = document.getElementById('absent-tbody');
absentTbody.innerHTML = '';
absent.forEach(function(row) {
var tr = document.createElement('tr');
tr.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/30';
tr.innerHTML = '<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + escapeHtml(row.nisn) + '</td>' +
'<td class="px-6 py-3 font-medium">' + escapeHtml(row.name) + '</td>';
absentTbody.appendChild(tr);
});
showContent();
}
function loadReport() {
showLoading();
var date = reportDate.value || new Date().toISOString().slice(0, 10);
var url = apiUrl + '?date=' + encodeURIComponent(date);
fetch(url, { 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 : 'Failed to load report');
return;
}
var data = r.data && r.data.data ? r.data.data : r.data;
if (!data) {
showError('Invalid response');
return;
}
renderReport(data);
})
.catch(function() {
showError('Network error');
});
}
reportDate.addEventListener('change', loadReport);
loadReport();
})();
</script>