Sindbad~EG File Manager
<?php
require_once '../../config/config.php';
checkLogin();
$pageTitle = "Live QR Attendance - " . APP_NAME;
$db = Database::getInstance()->getConnection();
$success = '';
$error = '';
// Handle QR code scan check-in (AJAX)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['qr_checkin'])) {
header('Content-Type: application/json');
try {
$qrData = json_decode($_POST['qr_data'], true);
$memberId = $qrData['member_id'] ?? null;
$programId = $qrData['program_id'] ?? null;
if (!$memberId || !$programId) {
echo json_encode(['success' => false, 'message' => 'Invalid QR code']);
exit();
}
// Check if member exists
$memberStmt = $db->prepare("SELECT m.*, a.assembly_name, d.district_name
FROM members m
LEFT JOIN assemblies a ON m.assembly_id = a.id
LEFT JOIN districts d ON m.district_id = d.id
WHERE m.id = :id");
$memberStmt->execute(['id' => $memberId]);
$member = $memberStmt->fetch();
if (!$member) {
echo json_encode(['success' => false, 'message' => 'Member not found']);
exit();
}
// Get program details
$progStmt = $db->prepare("SELECT program_name FROM programs WHERE id = :id");
$progStmt->execute(['id' => $programId]);
$program = $progStmt->fetch();
if (!$program) {
echo json_encode(['success' => false, 'message' => 'Program not found']);
exit();
}
$selectedDate = date('Y-m-d');
// Check if already checked in
$checkStmt = $db->prepare("
SELECT id, check_in_time, check_out_time FROM program_attendance
WHERE program_id = :program_id AND member_id = :member_id AND attendance_date = :date
");
$checkStmt->execute([
'program_id' => $programId,
'member_id' => $memberId,
'date' => $selectedDate
]);
$attendance = $checkStmt->fetch();
if ($attendance) {
// Already has attendance record
if ($attendance['check_out_time']) {
// Already checked out - error
echo json_encode([
'success' => false,
'message' => $member['first_name'] . ' ' . $member['last_name'] . ' already checked out from ' . $program['program_name']
]);
exit();
} else {
// Checked in but not out - perform check-out
$stmt = $db->prepare("
UPDATE program_attendance
SET check_out_time = NOW(), marked_by = :marked_by
WHERE id = :id
");
$stmt->execute([
'id' => $attendance['id'],
'marked_by' => $_SESSION['user_id']
]);
echo json_encode([
'success' => true,
'message' => 'Check-out successful!',
'action' => 'checkout',
'member_name' => $member['first_name'] . ' ' . $member['last_name'],
'program_name' => $program['program_name'],
'district' => $member['district_name'] ?? 'N/A',
'assembly' => $member['assembly_name'] ?? 'N/A',
'time' => date('g:i A')
]);
exit();
}
}
// Insert new check-in
$stmt = $db->prepare("
INSERT INTO program_attendance (program_id, member_id, attendance_date, status, marked_by, check_in_time)
VALUES (:program_id, :member_id, :attendance_date, 'present', :marked_by, NOW())
");
$stmt->execute([
'program_id' => $programId,
'member_id' => $memberId,
'attendance_date' => $selectedDate,
'marked_by' => $_SESSION['user_id']
]);
echo json_encode([
'success' => true,
'message' => 'Check-in successful!',
'action' => 'checkin',
'member_name' => $member['first_name'] . ' ' . $member['last_name'],
'program_name' => $program['program_name'],
'district' => $member['district_name'] ?? 'N/A',
'assembly' => $member['assembly_name'] ?? 'N/A',
'time' => date('g:i A')
]);
exit();
} catch (Exception $e) {
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
exit();
}
}
// Get today's attendance stats across all programs
$statsStmt = $db->prepare("
SELECT
COUNT(DISTINCT pa.member_id) as checked_in_today,
COUNT(DISTINCT pa.program_id) as active_programs,
(SELECT COUNT(*) FROM members WHERE is_active = 1) as total_members
FROM program_attendance pa
WHERE pa.attendance_date = :date
");
$statsStmt->execute(['date' => date('Y-m-d')]);
$stats = $statsStmt->fetch();
// Get recent check-ins across all programs today
$recentStmt = $db->prepare("
SELECT m.id, m.first_name, m.last_name, m.membershipcard_id,
p.program_name, pa.check_in_time, pa.check_out_time,
a.assembly_name, d.district_name
FROM program_attendance pa
JOIN members m ON pa.member_id = m.id
JOIN programs p ON pa.program_id = p.id
LEFT JOIN assemblies a ON m.assembly_id = a.id
LEFT JOIN districts d ON m.district_id = d.id
WHERE pa.attendance_date = :date
ORDER BY COALESCE(pa.check_out_time, pa.check_in_time) DESC
LIMIT 100
");
$recentStmt->execute(['date' => date('Y-m-d')]);
$recentCheckIns = $recentStmt->fetchAll();
include '../../includes/header.php';
?>
<?php include '../../includes/sidebar.php'; ?>
<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">
<!-- Header -->
<div class="mb-6">
<h1 class="text-3xl font-bold text-gray-800">
<i class="fas fa-qrcode mr-2 text-blue-500"></i>Live QR Attendance
</h1>
<p class="text-gray-600 mt-2">Real-time attendance tracking via QR code scanning - <?php echo date('F j, Y'); ?></p>
</div>
<!-- Today's Stats -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<div class="bg-gradient-to-r from-blue-500 to-blue-600 rounded-xl shadow-lg p-6 text-white">
<div class="flex items-center justify-between">
<div>
<p class="text-sm opacity-90">Checked In Today</p>
<p class="text-4xl font-bold" id="checkedInCount"><?php echo $stats['checked_in_today']; ?></p>
</div>
<i class="fas fa-user-check text-5xl opacity-20"></i>
</div>
</div>
<div class="bg-gradient-to-r from-green-500 to-green-600 rounded-xl shadow-lg p-6 text-white">
<div class="flex items-center justify-between">
<div>
<p class="text-sm opacity-90">Active Programs</p>
<p class="text-4xl font-bold" id="activeProgramsCount"><?php echo $stats['active_programs']; ?></p>
</div>
<i class="fas fa-calendar text-5xl opacity-20"></i>
</div>
</div>
<div class="bg-gradient-to-r from-purple-500 to-purple-600 rounded-xl shadow-lg p-6 text-white">
<div class="flex items-center justify-between">
<div>
<p class="text-sm opacity-90">Total Members</p>
<p class="text-4xl font-bold"><?php echo $stats['total_members']; ?></p>
</div>
<i class="fas fa-users text-5xl opacity-20"></i>
</div>
</div>
<div class="bg-gradient-to-r from-orange-500 to-red-500 rounded-xl shadow-lg p-6 text-white">
<div class="flex items-center justify-between">
<div>
<p class="text-sm opacity-90">Attendance Rate</p>
<p class="text-4xl font-bold" id="attendanceRate">
<?php echo $stats['total_members'] > 0 ? round(($stats['checked_in_today'] / $stats['total_members']) * 100) : 0; ?>%
</p>
</div>
<i class="fas fa-chart-pie text-5xl opacity-20"></i>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- QR Scanner -->
<div class="lg:col-span-1">
<div class="bg-white rounded-xl shadow-lg p-6 sticky top-24">
<h3 class="text-xl font-bold text-gray-800 mb-4 text-center">
<i class="fas fa-camera mr-2 text-blue-500"></i>Scan QR Code
</h3>
<div class="bg-gray-100 p-4 rounded-lg mb-4">
<div id="reader" class="w-full"></div>
</div>
<div id="scanStatus" class="text-center text-sm text-gray-600 mb-4">
Ready to scan...
</div>
<button id="startScanBtn" onclick="startScanner()"
class="w-full bg-gradient-to-r from-blue-500 to-blue-600 text-white px-6 py-3 rounded-lg hover:shadow-lg transition">
<i class="fas fa-camera mr-2"></i>Start Scanner
</button>
<button id="stopScanBtn" onclick="stopScanner()"
class="w-full bg-gradient-to-r from-red-500 to-red-600 text-white px-6 py-3 rounded-lg hover:shadow-lg transition hidden">
<i class="fas fa-stop mr-2"></i>Stop Scanner
</button>
<!-- Last Scanned Info -->
<div id="lastScanned" class="mt-4 p-4 bg-green-50 border border-green-200 rounded-lg hidden">
<h4 class="font-semibold text-green-800 mb-2">
<i class="fas fa-check-circle mr-2"></i>Last Check-In
</h4>
<div id="lastScannedInfo" class="text-sm text-green-700"></div>
</div>
</div>
</div>
<!-- Live Feed -->
<div class="lg:col-span-2">
<div class="bg-white rounded-xl shadow-lg p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-800">
<i class="fas fa-stream mr-2 text-green-500"></i>Live Feed
<span class="text-sm font-normal text-gray-500">(Auto-updates)</span>
</h3>
<button onclick="location.reload()" class="text-blue-600 hover:text-blue-800">
<i class="fas fa-sync-alt mr-1"></i>Refresh
</button>
</div>
<div id="recentCheckIns" class="space-y-3 max-h-[650px] overflow-y-auto">
<?php if (empty($recentCheckIns)): ?>
<p class="text-gray-500 text-center py-12">No check-ins today yet</p>
<?php else: ?>
<?php foreach ($recentCheckIns as $checkin): ?>
<div class="flex items-center justify-between p-4 bg-gradient-to-r from-gray-50 to-white rounded-lg border border-gray-200 hover:shadow-md transition checkin-item">
<div class="flex items-center flex-1">
<div class="h-12 w-12 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center text-white font-bold mr-4">
<?php echo strtoupper(substr($checkin['first_name'], 0, 1) . substr($checkin['last_name'], 0, 1)); ?>
</div>
<div class="flex-1">
<p class="font-semibold text-gray-900">
<?php echo htmlspecialchars($checkin['first_name'] . ' ' . $checkin['last_name']); ?>
</p>
<p class="text-xs text-gray-500">
<i class="fas fa-id-card mr-1"></i><?php echo htmlspecialchars($checkin['membershipcard_id']); ?> |
<i class="fas fa-church mr-1"></i><?php echo htmlspecialchars($checkin['assembly_name'] ?? 'N/A'); ?>
</p>
<p class="text-xs text-blue-600 mt-1">
<i class="fas fa-calendar mr-1"></i><?php echo htmlspecialchars($checkin['program_name']); ?>
</p>
</div>
</div>
<div class="text-right">
<?php if ($checkin['check_out_time']): ?>
<p class="text-sm text-blue-600 font-semibold">
<i class="fas fa-sign-out-alt mr-1"></i>Checked Out
</p>
<p class="text-xs text-gray-500">
<i class="fas fa-clock mr-1"></i>In: <?php echo date('g:i A', strtotime($checkin['check_in_time'])); ?>
</p>
<p class="text-xs text-gray-500">
<i class="fas fa-clock mr-1"></i>Out: <?php echo date('g:i A', strtotime($checkin['check_out_time'])); ?>
</p>
<?php else: ?>
<p class="text-sm text-green-600 font-semibold">
<i class="fas fa-check-circle mr-1"></i>Checked In
</p>
<p class="text-xs text-gray-500">
<i class="fas fa-clock mr-1"></i><?php echo date('g:i A', strtotime($checkin['check_in_time'])); ?>
</p>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- QR Scanner Library -->
<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
<script>
let html5Qrcode;
let isScanning = false;
function startScanner() {
if (isScanning) return;
document.getElementById('startScanBtn').classList.add('hidden');
document.getElementById('stopScanBtn').classList.remove('hidden');
document.getElementById('scanStatus').innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Initializing camera...';
html5Qrcode = new Html5Qrcode("reader");
html5Qrcode.start(
{ facingMode: "environment" },
{
fps: 10,
qrbox: { width: 250, height: 250 }
},
onScanSuccess,
onScanError
).then(() => {
isScanning = true;
document.getElementById('scanStatus').innerHTML = '<i class="fas fa-camera mr-2 text-green-600"></i>Scanning... Point camera at QR code';
}).catch(err => {
document.getElementById('scanStatus').innerHTML = '<i class="fas fa-exclamation-triangle mr-2 text-red-600"></i>Camera error: ' + err;
document.getElementById('startScanBtn').classList.remove('hidden');
document.getElementById('stopScanBtn').classList.add('hidden');
});
}
function stopScanner() {
if (!isScanning || !html5Qrcode) return;
html5Qrcode.stop().then(() => {
isScanning = false;
document.getElementById('startScanBtn').classList.remove('hidden');
document.getElementById('stopScanBtn').classList.add('hidden');
document.getElementById('scanStatus').innerHTML = 'Scanner stopped';
}).catch(err => {
console.error('Error stopping scanner:', err);
});
}
function onScanSuccess(decodedText) {
// Prevent multiple scans
if (!isScanning) return;
document.getElementById('scanStatus').innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Processing...';
try {
const qrData = JSON.parse(decodedText);
if (qrData.member_id && qrData.program_id) {
// Send check-in request
const formData = new FormData();
formData.append('qr_checkin', '1');
formData.append('qr_data', decodedText);
fetch('', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Determine icon and color based on action
const isCheckout = data.action === 'checkout';
const iconClass = isCheckout ? 'fa-sign-out-alt' : 'fa-check-circle';
const colorClass = isCheckout ? 'text-blue-600' : 'text-green-600';
const actionText = isCheckout ? 'Checked Out' : 'Checked In';
// Show success message
document.getElementById('scanStatus').innerHTML = `<i class="fas ${iconClass} mr-2 ${colorClass}"></i>${data.message}`;
// Update last scanned info
const lastScanned = document.getElementById('lastScanned');
const lastScannedInfo = document.getElementById('lastScannedInfo');
// Change box color based on action
if (isCheckout) {
lastScanned.className = 'mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg';
lastScannedInfo.innerHTML = `
<h4 class="font-semibold text-blue-800 mb-2">
<i class="fas fa-sign-out-alt mr-2"></i>Last Check-Out
</h4>
`;
} else {
lastScanned.className = 'mt-4 p-4 bg-green-50 border border-green-200 rounded-lg';
lastScannedInfo.innerHTML = `
<h4 class="font-semibold text-green-800 mb-2">
<i class="fas fa-check-circle mr-2"></i>Last Check-In
</h4>
`;
}
lastScannedInfo.innerHTML += `
<div class="text-sm ${isCheckout ? 'text-blue-700' : 'text-green-700'}">
<p><strong>${data.member_name}</strong></p>
<p class="mt-1"><i class="fas fa-calendar mr-1"></i>${data.program_name}</p>
<p class="mt-1"><i class="fas fa-church mr-1"></i>${data.assembly}</p>
<p class="mt-1"><i class="fas fa-clock mr-1"></i>${data.time}</p>
</div>
`;
lastScanned.classList.remove('hidden');
// Update stats only for check-ins
if (!isCheckout) {
document.getElementById('checkedInCount').textContent = parseInt(document.getElementById('checkedInCount').textContent) + 1;
}
// Reload after 2 seconds to show in feed
setTimeout(() => {
location.reload();
}, 2000);
} else {
document.getElementById('scanStatus').innerHTML = `<i class="fas fa-exclamation-triangle mr-2 text-red-600"></i>${data.message}`;
// Resume scanning after error
setTimeout(() => {
if (isScanning) {
document.getElementById('scanStatus').innerHTML = '<i class="fas fa-camera mr-2 text-green-600"></i>Scanning...';
}
}, 3000);
}
})
.catch(err => {
document.getElementById('scanStatus').innerHTML = '<i class="fas fa-exclamation-triangle mr-2 text-red-600"></i>Error: ' + err;
});
} else {
document.getElementById('scanStatus').innerHTML = '<i class="fas fa-exclamation-triangle mr-2 text-red-600"></i>Invalid QR code format';
}
} catch (e) {
document.getElementById('scanStatus').innerHTML = '<i class="fas fa-exclamation-triangle mr-2 text-red-600"></i>Invalid QR code';
}
}
function onScanError(errorMessage) {
// Silent - continuous scanning
}
// Auto-refresh every 30 seconds
setInterval(() => {
if (!isScanning) {
location.reload();
}
}, 30000);
</script>
<?php include '../../includes/footer.php'; ?>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists