Sindbad~EG File Manager
<?php
require_once '../../config/config.php';
checkLogin();
// Only superusers and area/district/assembly level users can access
if (!isSuperuser() && !in_array(getAccessLevel(), ['area', 'district', 'assembly'])) {
redirect('../../dashboard.php');
}
$pageTitle = "Event Attendance - " . APP_NAME;
$db = Database::getInstance()->getConnection();
$success = '';
$error = '';
require_once '../../classes/EventManager.php';
$eventManager = new EventManager();
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['record_attendance'])) {
try {
$result = $eventManager->recordAttendance(
$_POST['event_id'],
$_POST['registration_id'] ?? null,
[
'member_id' => $_POST['member_id'] ?? null,
'user_id' => $_POST['user_id'] ?? null,
'first_name' => $_POST['first_name'],
'last_name' => $_POST['last_name'],
'attendance_type' => $_POST['attendance_type'] ?? 'walk_in',
'notes' => $_POST['notes'] ?? null
]
);
if ($result['success']) {
$success = $result['message'];
} else {
$error = $result['message'];
}
} catch (Exception $e) {
$error = "Error recording attendance: " . $e->getMessage();
}
}
if (isset($_POST['bulk_attendance'])) {
try {
$eventId = $_POST['event_id'];
$registrationIds = $_POST['registration_ids'] ?? [];
$successCount = 0;
foreach ($registrationIds as $regId) {
// Get registration details
$stmt = $db->prepare("SELECT * FROM event_registrations WHERE id = :id");
$stmt->execute(['id' => $regId]);
$registration = $stmt->fetch();
if ($registration) {
$result = $eventManager->recordAttendance(
$eventId,
$regId,
[
'member_id' => $registration['member_id'],
'user_id' => $registration['user_id'],
'first_name' => $registration['first_name'],
'last_name' => $registration['last_name'],
'attendance_type' => 'registered'
]
);
if ($result['success']) {
$successCount++;
}
}
}
$success = "Recorded attendance for $successCount registrations";
} catch (Exception $e) {
$error = "Error recording bulk attendance: " . $e->getMessage();
}
}
}
// Get events
$events = $eventManager->getEvents(['active' => true]);
$selectedEventId = $_GET['event_id'] ?? null;
$selectedEvent = null;
$registrations = [];
$attendance = [];
if ($selectedEventId) {
$selectedEvent = $eventManager->getEventById($selectedEventId);
$registrations = $eventManager->getEventRegistrations($selectedEventId);
$attendance = $eventManager->getEventAttendance($selectedEventId);
}
include '../../includes/header.php';
?>
<?php include '../../includes/sidebar.php'; ?>
<!-- Main Content -->
<main class="flex-1 md:ml-64 mt-16">
<div class="container mx-auto px-4 py-8">
<div class="max-w-7xl mx-auto">
<div class="mb-6">
<h1 class="text-3xl font-bold text-gray-800">
<i class="fas fa-calendar-check mr-2 text-blue-500"></i>Event Attendance
</h1>
<p class="text-gray-600 mt-2">Manage event attendance and check-ins</p>
</div>
<?php if ($success): ?>
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
<i class="fas fa-check-circle mr-2"></i><?php echo $success; ?>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
<i class="fas fa-exclamation-circle mr-2"></i><?php echo $error; ?>
</div>
<?php endif; ?>
<!-- Event Selection -->
<div class="bg-white rounded-xl shadow-lg p-6 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Select Event</h3>
<form method="GET" class="flex items-center space-x-4">
<select name="event_id" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" onchange="this.form.submit()">
<option value="">Choose an event...</option>
<?php foreach ($events as $event): ?>
<option value="<?php echo $event['id']; ?>" <?php echo $selectedEventId == $event['id'] ? 'selected' : ''; ?>>
<?php echo htmlspecialchars($event['name']) . ' - ' . date('M j, Y', strtotime($event['start_date'])); ?>
</option>
<?php endforeach; ?>
</select>
</form>
</div>
<?php if ($selectedEvent): ?>
<!-- Event Details -->
<div class="bg-white rounded-xl shadow-lg p-6 mb-8">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-800"><?php echo htmlspecialchars($selectedEvent['name']); ?></h3>
<div class="flex space-x-4 text-sm text-gray-600">
<span><i class="fas fa-users mr-1"></i><?php echo $selectedEvent['registration_count']; ?> Registered</span>
<span><i class="fas fa-check mr-1"></i><?php echo $selectedEvent['attendance_count']; ?> Attended</span>
</div>
</div>
<p class="text-gray-600 mb-2"><?php echo htmlspecialchars($selectedEvent['description']); ?></p>
<div class="flex items-center space-x-6 text-sm text-gray-600">
<span><i class="fas fa-calendar mr-1"></i><?php echo date('F j, Y', strtotime($selectedEvent['start_date'])); ?></span>
<span><i class="fas fa-clock mr-1"></i><?php echo date('g:i A', strtotime($selectedEvent['start_date'])); ?></span>
<span><i class="fas fa-map-marker-alt mr-1"></i><?php echo ucfirst($selectedEvent['location_type']); ?></span>
</div>
</div>
<!-- Tabs -->
<div class="bg-white rounded-xl shadow-lg">
<div class="border-b border-gray-200">
<nav class="flex">
<button onclick="switchAttendanceTab('registrations')" class="attendance-tab px-6 py-4 font-medium border-b-2 border-blue-500 text-blue-600">
<i class="fas fa-list mr-2"></i>Registrations (<?php echo count($registrations); ?>)
</button>
<button onclick="switchAttendanceTab('attendance')" class="attendance-tab px-6 py-4 font-medium text-gray-600 hover:text-blue-600 border-b-2 border-transparent">
<i class="fas fa-check-circle mr-2"></i>Attendance (<?php echo count($attendance); ?>)
</button>
<button onclick="switchAttendanceTab('checkin')" class="attendance-tab px-6 py-4 font-medium text-gray-600 hover:text-blue-600 border-b-2 border-transparent">
<i class="fas fa-user-plus mr-2"></i>Manual Check-in
</button>
<button onclick="switchAttendanceTab('realtime')" class="attendance-tab px-6 py-4 font-medium text-gray-600 hover:text-blue-600 border-b-2 border-transparent">
<i class="fas fa-qrcode mr-2"></i>Real-time Attendance
</button>
</nav>
</div>
<!-- Registrations Tab -->
<div id="registrationsTab" class="attendance-tab-content p-6">
<div class="flex justify-between items-center mb-4">
<h4 class="text-lg font-semibold">Event Registrations</h4>
<button onclick="toggleBulkActions()" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
<i class="fas fa-check-double mr-2"></i>Bulk Check-in
</button>
</div>
<form method="POST" id="bulkAttendanceForm" style="display: none;">
<input type="hidden" name="event_id" value="<?php echo $selectedEventId; ?>">
<div class="bg-blue-50 p-4 rounded-lg mb-4">
<div class="flex justify-between items-center">
<span class="text-blue-800">Select registrations to mark as attended:</span>
<div class="space-x-2">
<button type="button" onclick="selectAllRegistrations()" class="text-blue-600 hover:text-blue-800">Select All</button>
<button type="submit" name="bulk_attendance" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
Mark Selected as Attended
</button>
<button type="button" onclick="toggleBulkActions()" class="text-gray-600 hover:text-gray-800">Cancel</button>
</div>
</div>
</div>
</form>
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200 rounded-lg">
<thead class="bg-gray-50">
<tr>
<th class="bulk-checkbox px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" style="display: none;">
<input type="checkbox" id="selectAll" onchange="toggleAllRegistrations()">
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Registered</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<?php if (empty($registrations)): ?>
<tr>
<td colspan="7" class="px-6 py-8 text-center text-gray-500">
<i class="fas fa-users text-3xl mb-2"></i>
<p>No registrations found for this event</p>
</td>
</tr>
<?php else: ?>
<?php foreach ($registrations as $registration): ?>
<tr>
<td class="bulk-checkbox px-6 py-4" style="display: none;">
<input type="checkbox" name="registration_ids[]" value="<?php echo $registration['id']; ?>" form="bulkAttendanceForm">
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">
<?php echo htmlspecialchars($registration['first_name'] . ' ' . $registration['last_name']); ?>
</div>
<div class="text-sm text-gray-500">
<?php echo htmlspecialchars($registration['tracking_code']); ?>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<?php echo htmlspecialchars($registration['email']); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded-full <?php echo $registration['registration_type'] === 'member' ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800'; ?>">
<?php echo ucfirst($registration['registration_type']); ?>
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<?php
$statusColors = [
'pending' => 'bg-yellow-100 text-yellow-800',
'confirmed' => 'bg-green-100 text-green-800',
'attended' => 'bg-blue-100 text-blue-800',
'cancelled' => 'bg-red-100 text-red-800'
];
?>
<span class="px-2 py-1 text-xs font-medium rounded-full <?php echo $statusColors[$registration['status']] ?? 'bg-gray-100 text-gray-800'; ?>">
<?php echo ucfirst($registration['status']); ?>
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<?php echo date('M j, Y H:i', strtotime($registration['registered_at'])); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<?php if ($registration['status'] !== 'attended'): ?>
<button onclick="markAttended(<?php echo $registration['id']; ?>, '<?php echo htmlspecialchars($registration['first_name'] . ' ' . $registration['last_name']); ?>')"
class="text-blue-600 hover:text-blue-900">
<i class="fas fa-check"></i> Mark Attended
</button>
<?php else: ?>
<span class="text-green-600"><i class="fas fa-check-circle"></i> Attended</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Attendance Tab -->
<div id="attendanceTab" class="attendance-tab-content hidden p-6">
<h4 class="text-lg font-semibold mb-4">Event Attendance Records</h4>
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200 rounded-lg">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Check-in Time</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tracking Code</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Notes</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<?php if (empty($attendance)): ?>
<tr>
<td colspan="5" class="px-6 py-8 text-center text-gray-500">
<i class="fas fa-calendar-times text-3xl mb-2"></i>
<p>No attendance records found</p>
</td>
</tr>
<?php else: ?>
<?php foreach ($attendance as $record): ?>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
<?php echo htmlspecialchars($record['first_name'] . ' ' . $record['last_name']); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs font-medium rounded-full <?php echo $record['attendance_type'] === 'registered' ? 'bg-green-100 text-green-800' : 'bg-blue-100 text-blue-800'; ?>">
<?php echo ucfirst(str_replace('_', ' ', $record['attendance_type'])); ?>
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<?php echo date('M j, Y H:i:s', strtotime($record['check_in_time'])); ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<?php echo htmlspecialchars($record['tracking_code'] ?? 'N/A'); ?>
</td>
<td class="px-6 py-4 text-sm text-gray-500">
<?php echo htmlspecialchars($record['notes'] ?? ''); ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Manual Check-in Tab -->
<div id="checkinTab" class="attendance-tab-content hidden p-6">
<h4 class="text-lg font-semibold mb-4">Manual Check-in</h4>
<p class="text-gray-600 mb-6">Use this form to manually check-in attendees who didn't pre-register.</p>
<form method="POST" class="max-w-md space-y-4">
<input type="hidden" name="event_id" value="<?php echo $selectedEventId; ?>">
<input type="hidden" name="attendance_type" value="walk_in">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">First Name</label>
<input type="text" name="first_name" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Last Name</label>
<input type="text" name="last_name" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Notes (Optional)</label>
<textarea name="notes" rows="3"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"></textarea>
</div>
<button type="submit" name="record_attendance" class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600">
<i class="fas fa-user-check mr-2"></i>Record Attendance
</button>
</form>
</div>
<!-- Real-time Attendance Tab -->
<div id="realtimeTab" class="attendance-tab-content hidden p-6">
<!-- QR Scanner Input Section -->
<div class="mb-6 bg-gradient-to-r from-blue-50 to-purple-50 border-2 border-blue-300 rounded-lg p-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center space-x-3">
<div class="w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center">
<i class="fas fa-qrcode text-white text-xl"></i>
</div>
<div>
<h4 class="text-lg font-semibold text-gray-800">QR Code Scanner Ready</h4>
<p class="text-sm text-gray-600">Scan member QR codes to check in automatically</p>
</div>
</div>
<div class="flex items-center space-x-2">
<span id="scannerStatus" class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
<span class="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></span>
Ready
</span>
</div>
</div>
<div class="relative">
<input
type="text"
id="qrScannerInput"
placeholder="Focus here and scan QR code with scanner device..."
class="w-full px-4 py-3 text-lg border-2 border-blue-400 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono"
autocomplete="off"
autofocus>
<div id="scannerFeedback" class="mt-2 text-sm hidden"></div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Left: QR Code Section -->
<div class="bg-gray-50 rounded-lg p-6">
<h4 class="text-lg font-semibold mb-4 text-center">Event Check-in QR Code</h4>
<div class="flex flex-col items-center space-y-4">
<div id="eventQRCode" class="bg-white p-6 rounded-lg shadow-md">
<!-- QR Code will be generated here -->
</div>
<div class="text-center">
<p class="text-sm text-gray-600 mb-2">Scan to check in to this event</p>
<p class="text-xs text-gray-500 font-mono bg-white px-4 py-2 rounded">
Event ID: <?php echo $selectedEventId; ?>
</p>
</div>
<button onclick="downloadQRCode()" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
<i class="fas fa-download mr-2"></i>Download QR Code
</button>
</div>
</div>
<!-- Right: Live Check-ins -->
<div>
<div class="flex justify-between items-center mb-4">
<h4 class="text-lg font-semibold">Live Check-ins</h4>
<div class="flex items-center space-x-2">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
<span class="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></span>
Live
</span>
<span id="totalAttendance" class="text-sm text-gray-600">Total: <?php echo count($attendance); ?></span>
</div>
</div>
<div id="liveAttendanceList" class="space-y-2 max-h-96 overflow-y-auto">
<!-- Live attendance will be populated here -->
<?php if (empty($attendance)): ?>
<div class="text-center text-gray-500 py-8">
<i class="fas fa-users text-3xl mb-2"></i>
<p>Waiting for check-ins...</p>
</div>
<?php else: ?>
<?php foreach (array_slice($attendance, 0, 20) as $record): ?>
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200 hover:border-blue-300 transition">
<div class="flex justify-between items-start">
<div class="flex-1">
<div class="font-medium text-gray-900">
<?php echo htmlspecialchars($record['first_name'] . ' ' . $record['last_name']); ?>
</div>
<div class="text-xs text-gray-500 mt-1">
<?php if (!empty($record['membershipcard_id'])): ?>
<span class="inline-flex items-center">
<i class="fas fa-id-card mr-1"></i>
<?php echo htmlspecialchars($record['membershipcard_id']); ?>
</span>
<?php endif; ?>
</div>
</div>
<div class="text-right">
<div class="text-xs text-gray-500">
<i class="fas fa-clock mr-1"></i>
<?php echo date('H:i:s', strtotime($record['check_in_time'])); ?>
</div>
<span class="inline-flex mt-1 px-2 py-1 text-xs rounded-full bg-green-100 text-green-800">
<?php echo ucfirst(str_replace('_', ' ', $record['attendance_type'])); ?>
</span>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
</main>
<script>
function switchAttendanceTab(tabName) {
// Hide all tab contents
document.querySelectorAll('.attendance-tab-content').forEach(content => {
content.classList.add('hidden');
});
// Reset all tabs to inactive state
document.querySelectorAll('.attendance-tab').forEach(tab => {
tab.classList.remove('border-blue-500', 'text-blue-600');
tab.classList.add('border-transparent', 'text-gray-600', 'hover:text-blue-600');
});
// Show selected tab content
document.getElementById(tabName + 'Tab').classList.remove('hidden');
// Activate selected tab
event.target.classList.remove('border-transparent', 'text-gray-600', 'hover:text-blue-600');
event.target.classList.add('border-blue-500', 'text-blue-600');
}
function toggleBulkActions() {
const form = document.getElementById('bulkAttendanceForm');
const checkboxes = document.querySelectorAll('.bulk-checkbox');
if (form.style.display === 'none') {
form.style.display = 'block';
checkboxes.forEach(cb => cb.style.display = 'table-cell');
} else {
form.style.display = 'none';
checkboxes.forEach(cb => cb.style.display = 'none');
document.getElementById('selectAll').checked = false;
document.querySelectorAll('input[name="registration_ids[]"]').forEach(cb => cb.checked = false);
}
}
function toggleAllRegistrations() {
const selectAll = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('input[name="registration_ids[]"]');
checkboxes.forEach(cb => cb.checked = selectAll.checked);
}
function selectAllRegistrations() {
document.getElementById('selectAll').checked = true;
toggleAllRegistrations();
}
function markAttended(registrationId, name) {
if (confirm(`Mark ${name} as attended?`)) {
const form = document.createElement('form');
form.method = 'POST';
form.innerHTML = `
<input type="hidden" name="event_id" value="<?php echo $selectedEventId; ?>">
<input type="hidden" name="registration_id" value="${registrationId}">
<input type="hidden" name="record_attendance" value="1">
`;
document.body.appendChild(form);
form.submit();
}
}
// Real-time attendance functionality
let realtimePollingInterval = null;
let lastAttendanceId = 0;
function generateEventQRCode() {
const eventId = <?php echo $selectedEventId ?? 'null'; ?>;
if (!eventId) return;
const checkInUrl = '<?php echo BASE_URL; ?>/event-checkin.php?event_id=' + eventId;
const qrContainer = document.getElementById('eventQRCode');
// Generate QR code using qrcodejs or similar library
// For now, using a simple approach with Google Charts API
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(checkInUrl)}`;
qrContainer.innerHTML = `<img src="${qrUrl}" alt="Event QR Code" class="w-48 h-48">`;
}
function downloadQRCode() {
const eventId = <?php echo $selectedEventId ?? 'null'; ?>;
if (!eventId) return;
const checkInUrl = '<?php echo BASE_URL; ?>/event-checkin.php?event_id=' + eventId;
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=${encodeURIComponent(checkInUrl)}`;
const link = document.createElement('a');
link.href = qrUrl;
link.download = `event-${eventId}-qrcode.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function startRealtimePolling() {
if (realtimePollingInterval) return;
fetchLiveAttendance();
realtimePollingInterval = setInterval(fetchLiveAttendance, 3000); // Poll every 3 seconds
}
function stopRealtimePolling() {
if (realtimePollingInterval) {
clearInterval(realtimePollingInterval);
realtimePollingInterval = null;
}
}
async function fetchLiveAttendance() {
const eventId = <?php echo $selectedEventId ?? 'null'; ?>;
if (!eventId) return;
try {
const response = await fetch(`<?php echo BASE_URL; ?>/api/event_live_attendance.php?event_id=${eventId}&last_id=${lastAttendanceId}`);
const data = await response.json();
if (data.success) {
// Update total count
document.getElementById('totalAttendance').textContent = `Total: ${data.total_count}`;
// Update last ID
if (data.last_id) {
lastAttendanceId = data.last_id;
}
// Add new attendance to list (prepend for most recent first)
if (data.new_attendance && data.new_attendance.length > 0) {
const listContainer = document.getElementById('liveAttendanceList');
data.new_attendance.reverse().forEach(record => {
const checkinTime = new Date(record.check_in_time);
const timeStr = checkinTime.toLocaleTimeString('en-US', {hour12: false});
const recordHtml = `
<div class="bg-white p-4 rounded-lg shadow-sm border border-green-300 hover:border-blue-300 transition animate-pulse">
<div class="flex justify-between items-start">
<div class="flex-1">
<div class="font-medium text-gray-900">
${escapeHtml(record.first_name + ' ' + record.last_name)}
</div>
<div class="text-xs text-gray-500 mt-1">
${record.membershipcard_id ? `<span class="inline-flex items-center"><i class="fas fa-id-card mr-1"></i>${escapeHtml(record.membershipcard_id)}</span>` : ''}
</div>
</div>
<div class="text-right">
<div class="text-xs text-gray-500">
<i class="fas fa-clock mr-1"></i>${timeStr}
</div>
<span class="inline-flex mt-1 px-2 py-1 text-xs rounded-full bg-green-100 text-green-800">
${record.attendance_type.replace('_', ' ')}
</span>
</div>
</div>
</div>
`;
listContainer.insertAdjacentHTML('afterbegin', recordHtml);
});
// Play sound notification
playCheckinSound();
// Remove animation after 2 seconds
setTimeout(() => {
document.querySelectorAll('.animate-pulse').forEach(el => {
el.classList.remove('animate-pulse', 'border-green-300');
});
}, 2000);
}
}
} catch (error) {
console.error('Error fetching live attendance:', error);
}
}
function playCheckinSound() {
// Simple beep sound using Web Audio API
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = 800;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.2);
} catch (error) {
// Silently fail if audio not supported
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// QR Scanner functionality
let scannerTimeout = null;
let isProcessingQR = false;
function initializeQRScanner() {
const scannerInput = document.getElementById('qrScannerInput');
if (!scannerInput) return;
// Focus the input when tab is active
scannerInput.focus();
// Handle input from QR scanner
scannerInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
processQRCode(this.value.trim());
this.value = ''; // Clear input for next scan
}
});
// Auto-process after brief pause (for scanners that don't send Enter)
scannerInput.addEventListener('input', function() {
clearTimeout(scannerTimeout);
scannerTimeout = setTimeout(() => {
if (this.value.trim().length > 0 && !isProcessingQR) {
processQRCode(this.value.trim());
this.value = '';
}
}, 300);
});
// Keep focus on scanner input
document.addEventListener('click', function(e) {
if (document.getElementById('realtimeTab').classList.contains('hidden') === false) {
setTimeout(() => scannerInput.focus(), 100);
}
});
}
async function processQRCode(qrData) {
if (!qrData || isProcessingQR) return;
isProcessingQR = true;
updateScannerStatus('processing', 'Processing...');
const eventId = <?php echo $selectedEventId ?? 'null'; ?>;
if (!eventId) {
showScannerFeedback('error', 'No event selected');
isProcessingQR = false;
return;
}
try {
// Extract tracking code from QR data
// QR format could be: "CHECKIN:eventId:MEMBER:code" or just "code"
let trackingCode = qrData;
if (qrData.includes('CHECKIN:')) {
const parts = qrData.split(':');
trackingCode = parts[parts.length - 1]; // Get last part
}
// Call check-in API
const response = await fetch('<?php echo BASE_URL; ?>/api/event_checkin.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `event_id=${eventId}&tracking_code=${encodeURIComponent(trackingCode)}`
});
const result = await response.json();
if (result.success) {
if (result.already_checked_in) {
updateScannerStatus('warning', 'Already Checked In');
showScannerFeedback('warning', `${result.attendee.first_name} ${result.attendee.last_name} - Already checked in`);
playWarningSound();
} else {
updateScannerStatus('success', 'Success!');
showScannerFeedback('success', `✓ ${result.attendee.first_name} ${result.attendee.last_name} - Checked in successfully`);
playCheckinSound();
// Refresh attendance list
fetchLiveAttendance();
}
} else {
updateScannerStatus('error', 'Error');
showScannerFeedback('error', result.message || 'Check-in failed');
playErrorSound();
}
} catch (error) {
updateScannerStatus('error', 'Error');
showScannerFeedback('error', 'Network error: ' + error.message);
playErrorSound();
}
// Reset status after 2 seconds
setTimeout(() => {
updateScannerStatus('ready', 'Ready');
isProcessingQR = false;
document.getElementById('qrScannerInput').focus();
}, 2000);
}
function updateScannerStatus(type, text) {
const statusEl = document.getElementById('scannerStatus');
if (!statusEl) return;
// Remove all status classes
statusEl.className = 'inline-flex items-center px-3 py-1 rounded-full text-sm font-medium';
switch(type) {
case 'ready':
statusEl.classList.add('bg-green-100', 'text-green-800');
statusEl.innerHTML = '<span class="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></span>' + text;
break;
case 'processing':
statusEl.classList.add('bg-blue-100', 'text-blue-800');
statusEl.innerHTML = '<span class="w-2 h-2 bg-blue-500 rounded-full mr-2 animate-pulse"></span>' + text;
break;
case 'success':
statusEl.classList.add('bg-green-100', 'text-green-800');
statusEl.innerHTML = '<i class="fas fa-check mr-1"></i>' + text;
break;
case 'warning':
statusEl.classList.add('bg-yellow-100', 'text-yellow-800');
statusEl.innerHTML = '<i class="fas fa-exclamation-triangle mr-1"></i>' + text;
break;
case 'error':
statusEl.classList.add('bg-red-100', 'text-red-800');
statusEl.innerHTML = '<i class="fas fa-times mr-1"></i>' + text;
break;
}
}
function showScannerFeedback(type, message) {
const feedbackEl = document.getElementById('scannerFeedback');
if (!feedbackEl) return;
feedbackEl.className = 'mt-2 text-sm p-3 rounded-lg';
switch(type) {
case 'success':
feedbackEl.classList.add('bg-green-100', 'text-green-800', 'border', 'border-green-300');
break;
case 'warning':
feedbackEl.classList.add('bg-yellow-100', 'text-yellow-800', 'border', 'border-yellow-300');
break;
case 'error':
feedbackEl.classList.add('bg-red-100', 'text-red-800', 'border', 'border-red-300');
break;
}
feedbackEl.textContent = message;
feedbackEl.classList.remove('hidden');
// Hide after 3 seconds
setTimeout(() => {
feedbackEl.classList.add('hidden');
}, 3000);
}
function playWarningSound() {
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = 600;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.2, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.15);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.15);
} catch (error) {}
}
function playErrorSound() {
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = 400;
oscillator.type = 'square';
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.2);
} catch (error) {}
}
// Initialize when realtime tab is opened
const originalSwitchTab = switchAttendanceTab;
switchAttendanceTab = function(tabName) {
originalSwitchTab(tabName);
if (tabName === 'realtime') {
generateEventQRCode();
startRealtimePolling();
initializeQRScanner();
} else {
stopRealtimePolling();
}
};
</script>
<?php include '../../includes/footer.php'; ?>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists