chore: matikan realtime stream di dashboard
Ganti SSE /api/dashboard/stream dengan fetch sekali ke /api/dashboard/realtime supaya halaman dashboard lebih ringan dan cepat dibuka.
This commit is contained in:
@@ -43,8 +43,8 @@
|
|||||||
|
|
||||||
<div class="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm overflow-hidden">
|
<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">
|
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h2 class="text-lg font-semibold">Realtime Attendance</h2>
|
<h2 class="text-lg font-semibold">Riwayat Absensi Terbaru</h2>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Live stream via Server-Sent Events. Newest at top.</p>
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">Daftar absen terakhir (tanpa live stream), paling baru di atas.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full text-left">
|
<table class="w-full text-left">
|
||||||
@@ -57,11 +57,7 @@
|
|||||||
<th class="px-6 py-3 font-medium">Status</th>
|
<th class="px-6 py-3 font-medium">Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="attendance-tbody" class="divide-y divide-gray-200 dark:divide-gray-700">
|
<tbody id="attendance-tbody" class="divide-y divide-gray-200 dark:divide-gray-700"></tbody>
|
||||||
<tr>
|
|
||||||
<td colspan="5" class="px-6 py-8 text-center text-gray-500 dark:text-gray-400">Connecting to stream…</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,10 +127,7 @@
|
|||||||
.catch(function() {});
|
.catch(function() {});
|
||||||
|
|
||||||
var tbody = document.getElementById('attendance-tbody');
|
var tbody = document.getElementById('attendance-tbody');
|
||||||
var streamUrl = baseUrl + '/api/dashboard/stream';
|
var realtimeApiUrl = baseUrl + '/api/dashboard/realtime';
|
||||||
var afterId = 0;
|
|
||||||
var placeholderRow = null;
|
|
||||||
var connected = false;
|
|
||||||
|
|
||||||
function badgeClass(status) {
|
function badgeClass(status) {
|
||||||
var s = (status || '').toUpperCase();
|
var s = (status || '').toUpperCase();
|
||||||
@@ -152,10 +145,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addRow(data) {
|
function addRow(data) {
|
||||||
if (placeholderRow && placeholderRow.parentNode) {
|
|
||||||
placeholderRow.remove();
|
|
||||||
placeholderRow = null;
|
|
||||||
}
|
|
||||||
var tr = document.createElement('tr');
|
var tr = document.createElement('tr');
|
||||||
tr.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/30';
|
tr.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/30';
|
||||||
tr.setAttribute('data-id', data.id);
|
tr.setAttribute('data-id', data.id);
|
||||||
@@ -166,8 +155,7 @@
|
|||||||
'<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + escapeHtml(data.class_name) + '</td>' +
|
'<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + escapeHtml(data.class_name) + '</td>' +
|
||||||
'<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + escapeHtml(data.subject) + '</td>' +
|
'<td class="px-6 py-3 text-gray-600 dark:text-gray-400">' + escapeHtml(data.subject) + '</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(data.status) + '</span></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(data.status) + '</span></td>';
|
||||||
tbody.insertBefore(tr, tbody.firstChild);
|
tbody.appendChild(tr);
|
||||||
if (data.id > afterId) afterId = data.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
@@ -177,43 +165,29 @@
|
|||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPlaceholder(msg) {
|
function loadRealtimeOnce() {
|
||||||
if (placeholderRow && placeholderRow.parentNode) placeholderRow.remove();
|
fetch(realtimeApiUrl, { method: 'GET', credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } })
|
||||||
placeholderRow = document.createElement('tr');
|
.then(function(res) { return res.json().then(function(j) { return { ok: res.ok, data: j }; }); })
|
||||||
placeholderRow.innerHTML = '<td colspan="5" class="px-6 py-8 text-center text-gray-500 dark:text-gray-400">' + escapeHtml(msg) + '</td>';
|
.then(function(r) {
|
||||||
tbody.appendChild(placeholderRow);
|
if (!r.ok) return;
|
||||||
|
var list = (r.data && r.data.data) ? r.data.data : (Array.isArray(r.data) ? r.data : []);
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
if (!list.length) {
|
||||||
|
var tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = '<td colspan="5" class="px-6 py-8 text-center text-gray-500 dark:text-gray-400">Belum ada data absensi hari ini.</td>';
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list.forEach(function(row) { addRow(row); });
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
var tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = '<td colspan="5" class="px-6 py-8 text-center text-red-500 dark:text-red-400">Gagal memuat data absensi.</td>';
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect() {
|
loadRealtimeOnce();
|
||||||
var url = streamUrl + (afterId ? '?after_id=' + afterId : '');
|
|
||||||
var es = new EventSource(url);
|
|
||||||
es.addEventListener('attendance', function(e) {
|
|
||||||
try {
|
|
||||||
var data = JSON.parse(e.data);
|
|
||||||
if (data && data.id) addRow(data);
|
|
||||||
} catch (err) {}
|
|
||||||
});
|
|
||||||
es.addEventListener('heartbeat', function() {
|
|
||||||
if (!connected) {
|
|
||||||
connected = true;
|
|
||||||
setPlaceholder('No attendance records yet. Waiting for new check-ins…');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
es.addEventListener('timeout', function() {
|
|
||||||
es.close();
|
|
||||||
connected = false;
|
|
||||||
setPlaceholder('Stream ended. Reconnecting…');
|
|
||||||
setTimeout(connect, 3000);
|
|
||||||
});
|
|
||||||
es.onerror = function() {
|
|
||||||
es.close();
|
|
||||||
connected = false;
|
|
||||||
setPlaceholder('Connection lost. Reconnecting…');
|
|
||||||
setTimeout(connect, 3000);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
connect();
|
|
||||||
|
|
||||||
if (typeof window.addEventListener === 'function') {
|
if (typeof window.addEventListener === 'function') {
|
||||||
window.addEventListener('beforeunload', function() {
|
window.addEventListener('beforeunload', function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user