TKK_E32222685/WEB-playground/assets/js/dashboard.js

438 lines
20 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function() {
// Global variable for countdown interval
let countdownInterval = null;
// Elements for RFID modal
const rfidStatusModal = document.getElementById('rfidStatusModal');
const closeRfidModal = document.getElementById('closeRfidModal');
const modalTitle = document.getElementById('modalTitle'); // Ini untuk RFID Modal
const modalMessage = document.getElementById('modalMessage'); // Ini untuk RFID Modal
const modalGelangCode = document.getElementById('modalGelangCode');
const modalUserData = document.getElementById('modalUserData');
const modalActionButton = document.getElementById('modalActionButton');
const modalCancelButton = document.getElementById('modalCancelButton');
// Elements for Logout Confirmation Modal
const logoutConfirmModal = document.getElementById('logoutConfirmModal');
const closeLogoutModal = document.getElementById('closeLogoutModal');
const logoutModalTitle = document.getElementById('logoutModalTitle'); // <<< New ID
const logoutModalMessage = document.getElementById('logoutModalMessage'); // <<< New ID
const confirmLogoutBtn = document.getElementById('confirmLogoutBtn');
const cancelLogoutBtn = document.getElementById('cancelLogoutBtn');
// Store scanned RFID code globally for actions
let currentScannedRfid = null;
let currentKunjunganId = null; // To store kunjungan_id for ending session
// Function to show a specific modal
function showSpecificModal(modalElement) {
modalElement.classList.add('show');
}
// Function to hide a specific modal
function hideSpecificModal(modalElement) {
modalElement.classList.remove('show');
}
// Event listeners for RFID modal close
closeRfidModal.addEventListener('click', () => {
hideSpecificModal(rfidStatusModal);
clearRfidTemp(); // Clear temp RFID when RFID modal is closed by close button
});
modalCancelButton.addEventListener('click', () => {
hideSpecificModal(rfidStatusModal);
clearRfidTemp(); // Clear temp RFID when RFID modal is closed by cancel button
});
// Event listeners for Logout modal close
closeLogoutModal.addEventListener('click', () => hideSpecificModal(logoutConfirmModal));
cancelLogoutBtn.addEventListener('click', () => hideSpecificModal(logoutConfirmModal));
// Handle clicks outside the modal content for both modals
window.addEventListener('click', function(event) {
if (event.target == rfidStatusModal) {
hideSpecificModal(rfidStatusModal);
// Clear temp RFID if the RFID modal is closed by clicking outside
clearRfidTemp();
}
if (event.target == logoutConfirmModal) {
hideSpecificModal(logoutConfirmModal);
}
});
// Check authentication first
checkAuthentication();
// Handle Logout Button
const logoutButton = document.getElementById('logoutButton');
if (logoutButton) {
logoutButton.addEventListener('click', function() {
// Show the logout confirmation modal instead of direct logout
showSpecificModal(logoutConfirmModal);
});
}
// Handle confirmation inside the logout modal
confirmLogoutBtn.addEventListener('click', async function() {
const originalHtml = confirmLogoutBtn.innerHTML; // Simpan HTML asli
confirmLogoutBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Keluar...'; // Ikon spinner Font Awesome
confirmLogoutBtn.disabled = true;
cancelLogoutBtn.disabled = true; // Disable cancel button too
try {
const response = await fetch('../api/logout.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
const result = await response.json();
if (result.status === 'success') {
window.location.href = 'login.html';
} else {
alert('Gagal logout: ' + (result.message || 'Pesan tidak diketahui.'));
// Re-enable and reset button on failure
confirmLogoutBtn.innerHTML = originalHtml;
confirmLogoutBtn.disabled = false;
cancelLogoutBtn.disabled = false;
hideSpecificModal(logoutConfirmModal); // Hide modal on failure
}
} catch (error) {
console.error('Error during logout:', error);
alert('Terjadi kesalahan saat logout. Silakan coba lagi.');
// Re-enable and reset button on failure
confirmLogoutBtn.innerHTML = originalHtml;
confirmLogoutBtn.disabled = false;
cancelLogoutBtn.disabled = false;
hideSpecificModal(logoutConfirmModal); // Hide modal on failure
// Redirect anyway in case of serious network error
window.location.href = 'login.html';
}
});
// Function to check authentication and get user info
async function checkAuthentication() {
try {
const response = await fetch('../api/dashboard.php');
const result = await response.json();
if (result.status === 'error') {
// Redirect to login if not authenticated
window.location.href = result.redirect || 'login.html';
return;
}
if (result.status === 'success') {
// Update username in the header
const usernameElement = document.getElementById('adminUsername');
if (usernameElement && result.data.username) {
usernameElement.textContent = result.data.username;
}
// Load active visitors data
fetchActiveVisitors();
// Set up periodic refresh for active visitors
if (window.activeVisitorsInterval) {
clearInterval(window.activeVisitorsInterval);
}
window.activeVisitorsInterval = setInterval(fetchActiveVisitors, 30000); // Refresh every 30 seconds
// Start RFID polling
startRfidPolling();
}
} catch (error) {
console.error('Auth check error:', error);
// Redirect to login on error
window.location.href = 'login.html';
}
}
// Function to format seconds into HH:MM (and handle negative for overdue)
function formatTime(seconds) {
let prefix = '';
if (seconds < 0) {
prefix = 'Telat ';
seconds = Math.abs(seconds);
}
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
let formatted = '';
if (h > 0) {
formatted += h + ' jam ';
}
formatted += m + ' menit';
return prefix + formatted.trim();
}
// Function to update countdowns for all visitors
function updateAllCountdowns() {
document.querySelectorAll('.countdown-timer').forEach(timerElement => {
const waktuMasukTimestamp = parseInt(timerElement.dataset.waktuMasuk);
const totalDurasiDetik = parseInt(timerElement.dataset.totalDurasi);
const waktuSekarangMs = Date.now();
const waktuMasukMs = waktuMasukTimestamp * 1000;
const waktuSelesaiMs = waktuMasukMs + (totalDurasiDetik * 1000);
const sisaDetik = Math.floor((waktuSelesaiMs - waktuSekarangMs) / 1000);
timerElement.textContent = formatTime(sisaDetik);
if (sisaDetik <= 0) {
timerElement.classList.remove('warning-time');
timerElement.classList.add('time-over');
timerElement.textContent = formatTime(sisaDetik);
} else if (sisaDetik <= (10 * 60)) {
timerElement.classList.add('warning-time');
timerElement.classList.remove('time-over');
} else {
timerElement.classList.remove('warning-time', 'time-over');
}
});
}
// Function to fetch and display active visitors
async function fetchActiveVisitors() {
const activeVisitorsList = document.getElementById('activeVisitorsList');
activeVisitorsList.innerHTML = `
<div class="loading-state">
<div class="loading-spinner">⏳</div>
<p>Memuat data pengunjung aktif...</p>
</div>
`;
try {
const response = await fetch('../api/get_active_visitors.php');
const result = await response.json();
if (result.status === 'success') {
if (result.data.length > 0) {
let tableHtml = `
<table class="visitors-table">
<thead>
<tr>
<th>Nama Pengunjung</th>
<th>No. HP</th>
<th>Kode Gelang</th>
<th>Durasi Tersisa</th>
<th>Wahana Terakhir</th>
<th>Waktu Masuk Kunjungan</th>
</tr>
</thead>
<tbody>
`;
result.data.forEach(visitor => {
const displayNoHp = visitor.no_hp || '-';
const displayWaktuMasuk = visitor.waktu_masuk_kunjungan_string || 'N/A';
tableHtml += `
<tr>
<td>${visitor.nama_anak}</td>
<td>${displayNoHp}</td>
<td>${visitor.kode_gelang}</td>
<td>
<span
class="countdown-timer"
data-waktu-masuk="${visitor.waktu_masuk_timestamp}"
data-total-durasi="${visitor.total_durasi_detik}">
</span>
</td>
<td>${visitor.wahana_terakhir}</td>
<td>${displayWaktuMasuk}</td>
</tr>
`;
});
tableHtml += `
</tbody>
</table>
`;
activeVisitorsList.innerHTML = tableHtml;
updateAllCountdowns();
if (countdownInterval) { // Clear previous interval
clearInterval(countdownInterval);
}
countdownInterval = setInterval(updateAllCountdowns, 1000);
} else {
activeVisitorsList.innerHTML = `
<div class="empty-state">
<div class="empty-icon">🎉</div>
<h2>Belum Ada Pengunjung Aktif</h2>
<p>Tidak ada pengunjung yang sedang bermain di wahana saat ini.</p>
</div>
`;
if (countdownInterval) { // Clear interval if no active visitors
clearInterval(countdownInterval);
countdownInterval = null;
}
}
} else {
activeVisitorsList.innerHTML = `<p class="error-message-inline">Gagal memuat data: ${result.message || 'Terjadi kesalahan'}</p>`;
console.error('Error fetching active visitors:', result.message);
}
} catch (error) {
activeVisitorsList.innerHTML = `<p class="error-message-inline">Terjadi kesalahan saat mengambil data pengunjung.</p>`;
console.error('Network or parsing error:', error);
}
}
// --- RFID Scan Polling and Modal Logic ---
let rfidPollingInterval = null;
function startRfidPolling() {
if (rfidPollingInterval) {
clearInterval(rfidPollingInterval);
}
rfidPollingInterval = setInterval(async () => {
try {
const response = await fetch('../api/tambah_pengunjung.php?action=get_scanned_rfid');
const result = await response.json();
if (result.status === 'success' && result.data.scanned && result.data.kode_gelang !== currentScannedRfid) {
// Stop polling temporarily to process the scan
clearInterval(rfidPollingInterval);
rfidPollingInterval = null;
currentScannedRfid = result.data.kode_gelang; // Update current scanned RFID
// Immediately check RFID status via server
await checkRfidStatusOnServer(result.data.kode_gelang); // Pass the scanned code
} else if (result.status === 'success' && !result.data.scanned && currentScannedRfid !== null) {
// If rfid_temp is cleared by another action or manual clear
currentScannedRfid = null;
}
} catch (error) {
console.error('Error during RFID polling:', error);
}
}, 1000); // Poll every 1 second for RFID scan
}
async function checkRfidStatusOnServer(kodeGelang) {
try {
const response = await fetch(`../api/tambah_pengunjung.php?action=check_rfid&kode_gelang=${kodeGelang}`); // Ensure kode_gelang is sent
const result = await response.json();
// Clear previous modal content
modalUserData.innerHTML = '';
modalActionButton.onclick = null; // Clear previous event listener
modalActionButton.style.display = 'block'; // Show button by default
if (result.status === 'available') {
modalTitle.textContent = 'Gelang Siap Digunakan';
modalMessage.innerHTML = `Gelang dengan kode <strong>${result.data.kode_gelang}</strong> berstatus: <span class="text-green-500">TERSEDIA</span>.`;
modalGelangCode.textContent = 'Silakan gunakan gelang ini untuk mendaftarkan pengunjung baru.';
modalUserData.innerHTML = `<p>Arahkan ke halaman <a href="tambah_pengunjung.html" class="text-blue-500">Tambah Pengunjung</a> untuk proses pendaftaran.</p>`;
modalActionButton.textContent = 'Daftarkan Pengunjung';
modalActionButton.className = 'modal-action-btn'; // Reset class
modalActionButton.dataset.status = 'available'; // Set status for action
modalActionButton.onclick = async () => { // Make async to await clearRfidTemp
await clearRfidTemp(); // <<< Call clearRfidTemp HERE before redirect
window.location.href = `tambah_pengunjung.html?kode_gelang=${result.data.kode_gelang}`;
};
showSpecificModal(rfidStatusModal); // Use showSpecificModal
} else if (result.status === 'in_use') {
modalTitle.textContent = 'Gelang Sedang Digunakan';
modalMessage.innerHTML = `Gelang dengan kode <strong>${result.data.kode_gelang}</strong> berstatus: <span class="text-red-500">DIGUNAKAN</span>.`;
modalGelangCode.textContent = ``; // Clear this line
const userData = result.data;
modalUserData.innerHTML = `
<p>Nama Pengunjung: <strong>${userData.nama}</strong></p>
<p>Kode Gelang: <strong>${userData.kode_gelang}</strong></p>
<p>Sisa Waktu: <strong class="${userData.color_class}">${userData.sisa_waktu}</strong></p>
`;
modalActionButton.textContent = 'Akhiri Sesi Bermain';
modalActionButton.className = 'modal-action-btn logout-btn'; // Use red style
modalActionButton.dataset.status = 'in_use'; // Set status for action
currentKunjunganId = userData.id_kunjungan; // Store kunjungan_id
modalActionButton.onclick = async () => {
await endSession(userData.kode_gelang);
await clearRfidTemp(); // <<< Call clearRfidTemp HERE after ending session
};
showSpecificModal(rfidStatusModal); // Use showSpecificModal
} else if (result.status === 'error' && result.message === 'Gelang tidak dikenali') {
modalTitle.textContent = 'Gelang Tidak Terdaftar';
modalMessage.innerHTML = `Kode gelang <strong>${kodeGelang}</strong> <span class="text-red-500">TIDAK TERDAFTAR</span> di sistem.`;
modalGelangCode.textContent = 'Silakan daftarkan gelang ini terlebih dahulu.';
modalUserData.innerHTML = `<p>Arahkan ke halaman <a href="tambah_pengunjung.html" class="text-blue-500">Tambah Pengunjung</a> untuk mendaftarkan gelang baru.</p>`;
modalActionButton.style.display = 'none'; // Hide action button
showSpecificModal(rfidStatusModal); // Use showSpecificModal
} else {
// This case handles 'Belum ada gelang yang di-scan' or other generic errors
modalTitle.textContent = 'Status Gelang RFID';
modalMessage.innerHTML = `Terjadi kesalahan saat memeriksa status gelang: ${result.message || 'Pesan tidak diketahui.'}`;
modalGelangCode.textContent = `Kode Gelang: <strong>${kodeGelang}</strong>`;
modalUserData.innerHTML = '';
modalActionButton.style.display = 'none'; // Hide action button
showSpecificModal(rfidStatusModal); // Use showSpecificModal
}
} catch (error) {
console.error('Error checking RFID status:', error);
modalTitle.textContent = 'Error Jaringan/Server';
modalMessage.innerHTML = `Terjadi kesalahan saat berkomunikasi dengan server. Coba lagi.`;
modalGelangCode.textContent = `Kode Gelang: <strong>${kodeGelang}</strong>`;
modalUserData.innerHTML = '';
modalActionButton.style.display = 'none';
showSpecificModal(rfidStatusModal); // Use showSpecificModal
} finally {
// If the modal is shown due to 'unknown' or generic error, ensure polling restarts after close
if (rfidPollingInterval === null) { // Only restart if it was stopped
setTimeout(() => {
startRfidPolling();
}, 1000); // Wait a bit before restarting polling
}
}
}
async function endSession(kodeGelang) {
try {
const response = await fetch('../api/tambah_pengunjung.php?action=end_session', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `kode_gelang=${encodeURIComponent(kodeGelang)}`
});
const result = await response.json();
if (result.status === 'success') {
alert(result.message);
hideSpecificModal(rfidStatusModal); // Hide RFID modal
fetchActiveVisitors(); // Refresh dashboard data
} else {
alert('Gagal mengakhiri sesi: ' + (result.message || 'Pesan tidak diketahui.'));
}
} catch (error) {
console.error('Error ending session:', error);
alert('Terjadi kesalahan saat mengakhiri sesi.');
} finally {
currentScannedRfid = null; // Reset scanned RFID state
// clearRfidTemp(); // Moved this call to modalActionButton.onclick
}
}
// Function to clear rfid_temp table via API
async function clearRfidTemp() {
try {
const response = await fetch('../api/tambah_pengunjung.php?action=clear_temp', { method: 'POST' });
const result = await response.json();
if (result.status === 'success') {
console.log('RFID temp cleared successfully.');
} else {
console.error('Failed to clear RFID temp:', result.message);
}
} catch (error) {
console.error('Network error clearing RFID temp:', error);
}
}
});