438 lines
20 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
}); |