Files
QrCode-Attendance-System/src-backup/admin/attendance.php
2026-01-07 14:09:59 +08:00

363 lines
14 KiB
PHP

<?php
require_once '../includes/config.php';
// Check if user is logged in and is admin/teacher
if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true ||
($_SESSION['role'] !== 'admin' && $_SESSION['role'] !== 'teacher')) {
header('Location: ../auth/login.php');
exit();
}
$title = "QR Code Scanner";
// Get active activities for today
$activities = [];
$sql = "SELECT * FROM activities WHERE status = 1 AND date = CURDATE() ORDER BY time_in ASC";
$result = query($conn, $sql);
while ($row = mysqli_fetch_assoc($result)) {
$activities[] = $row;
}
include '../includes/header.php';
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">QR Code Scanner</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button class="btn btn-sm btn-outline-secondary" onclick="switchCamera()">
<i class="bi bi-camera-video"></i> Switch Camera
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="toggleScanner()">
<i class="bi bi-power"></i> Toggle Scanner
</button>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Scanner</h6>
</div>
<div class="card-body text-center">
<!-- Scanner Container -->
<div id="qr-reader" style="width: 100%; max-width: 500px; margin: 0 auto;"></div>
<!-- Scan Result -->
<div id="scan-result" class="mt-4"></div>
<!-- Manual Entry Form -->
<div class="mt-4">
<h6 class="text-muted mb-3">Or enter student ID manually:</h6>
<form id="manual-entry-form" class="row g-3">
<div class="col-md-8">
<input type="text" class="form-control" id="student_id"
placeholder="Enter Student ID" required>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-check-circle"></i> Submit
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Today's Activities</h6>
</div>
<div class="card-body">
<?php if (empty($activities)): ?>
<div class="alert alert-warning">
<i class="bi bi-info-circle"></i> No activities scheduled for today.
</div>
<?php else: ?>
<div class="list-group">
<?php foreach ($activities as $activity): ?>
<div class="list-group-item">
<h6 class="mb-1"><?php echo $activity['name']; ?></h6>
<small class="text-muted">
<i class="bi bi-clock"></i>
<?php echo date('h:i A', strtotime($activity['time_in'])); ?> -
<?php echo date('h:i A', strtotime($activity['time_out'])); ?>
</small><br>
<small class="text-muted">
<i class="bi bi-geo-alt"></i> <?php echo $activity['location']; ?>
</small>
<div class="mt-2">
<span class="badge bg-<?php echo time() >= strtotime($activity['time_in']) && time() <= strtotime($activity['time_out']) ? 'success' : 'secondary'; ?>">
<?php echo time() >= strtotime($activity['time_in']) && time() <= strtotime($activity['time_out']) ? 'Active Now' : 'Scheduled'; ?>
</span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Quick Stats</h6>
</div>
<div class="card-body">
<?php
$sql = "SELECT COUNT(*) as total FROM attendance WHERE DATE(created_at) = CURDATE()";
$result = query($conn, $sql);
$today_attendance = mysqli_fetch_assoc($result)['total'];
$sql = "SELECT COUNT(*) as total FROM students WHERE status = 1";
$result = query($conn, $sql);
$total_students = mysqli_fetch_assoc($result)['total'];
?>
<div class="d-flex justify-content-between mb-2">
<span>Today's Attendance:</span>
<strong><?php echo $today_attendance; ?></strong>
</div>
<div class="d-flex justify-content-between mb-2">
<span>Total Students:</span>
<strong><?php echo $total_students; ?></strong>
</div>
<div class="progress mb-3" style="height: 10px;">
<div class="progress-bar" role="progressbar"
style="width: <?php echo $total_students > 0 ? ($today_attendance / $total_students * 100) : 0; ?>%">
</div>
</div>
<small class="text-muted">
<?php echo number_format($total_students > 0 ? ($today_attendance / $total_students * 100) : 0, 1); ?>% attendance rate today
</small>
</div>
</div>
</div>
</div>
<?php
$page_scripts = '
<script src="https://unpkg.com/html5-qrcode"></script>
<script>
let html5QrCode;
let isScanning = false;
let cameraId = null;
// Initialize scanner
function initScanner() {
html5QrCode = new Html5Qrcode("qr-reader");
}
// Start scanner
async function startScanner() {
if (isScanning) return;
const devices = await Html5Qrcode.getCameras();
if (devices && devices.length > 0) {
const facingMode = cameraId ? { exact: cameraId } : { facingMode: "environment" };
const config = { fps: 10, qrbox: { width: 250, height: 250 } };
try {
await html5QrCode.start(facingMode, config, onScanSuccess, onScanError);
isScanning = true;
document.getElementById("scan-result").innerHTML = \'<div class="alert alert-info"><i class="bi bi-info-circle"></i> Scanner started. Point camera at QR code.</div>\';
} catch (error) {
console.error("Scanner start error:", error);
document.getElementById("scan-result").innerHTML = \'<div class="alert alert-danger"><i class="bi bi-exclamation-triangle"></i> Failed to start camera: \' + error + \'</div>\';
}
} else {
document.getElementById("scan-result").innerHTML = \'<div class="alert alert-warning"><i class="bi bi-camera-video-off"></i> No camera found.</div>\';
}
}
// Stop scanner
function stopScanner() {
if (!isScanning) return;
html5QrCode.stop().then(() => {
isScanning = false;
document.getElementById("scan-result").innerHTML = \'<div class="alert alert-secondary"><i class="bi bi-info-circle"></i> Scanner stopped.</div>\';
}).catch(error => {
console.error("Scanner stop error:", error);
});
}
// Toggle scanner
function toggleScanner() {
if (isScanning) {
stopScanner();
} else {
startScanner();
}
}
// Switch camera
async function switchCamera() {
const devices = await Html5Qrcode.getCameras();
if (devices.length > 1) {
stopScanner();
// Switch to next camera
const currentIndex = devices.findIndex(device => device.id === cameraId);
const nextIndex = (currentIndex + 1) % devices.length;
cameraId = devices[nextIndex].id;
startScanner();
}
}
// Scan success callback
function onScanSuccess(decodedText, decodedResult) {
// Show scanning indicator
document.getElementById("scan-result").innerHTML = \'<div class="alert alert-info"><div class="spinner-border spinner-border-sm me-2"></div>Processing QR code...</div>\';
// Send to server
fetch(\'../api/scan_qr.php\', {
method: \'POST\',
headers: {
\'Content-Type\': \'application/json\',
},
body: JSON.stringify({ qr_code: decodedText })
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById("scan-result").innerHTML = `
<div class="alert alert-success">
<div class="d-flex align-items-center">
<i class="bi bi-check-circle-fill me-3" style="font-size: 2rem;"></i>
<div>
<h5 class="mb-1">✓ Attendance Recorded!</h5>
<p class="mb-1"><strong>Student:</strong> \${data.data.student_name}</p>
<p class="mb-1"><strong>Activity:</strong> \${data.data.activity_name}</p>
<p class="mb-1"><strong>Time:</strong> \${data.data.time}</p>
<p class="mb-0"><strong>Status:</strong> <span class="badge bg-success">\${data.data.status}</span></p>
</div>
</div>
</div>
`;
// Play success sound
playSound(\'success\');
} else {
document.getElementById("scan-result").innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
\${data.message}
</div>
`;
// Play error sound
playSound(\'error\');
}
})
.catch(error => {
document.getElementById("scan-result").innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
Network error: \${error}
</div>
`;
});
}
// Scan error callback
function onScanError(error) {
// Don\'t show all errors to user
console.warn("QR Scan Error:", error);
}
// Manual entry form
document.getElementById("manual-entry-form").addEventListener("submit", function(e) {
e.preventDefault();
const studentId = document.getElementById("student_id").value.trim();
if (!studentId) return;
// Show processing indicator
document.getElementById("scan-result").innerHTML = \'<div class="alert alert-info"><div class="spinner-border spinner-border-sm me-2"></div>Processing manual entry...</div>\';
// Send to server
fetch(\'../api/manual_entry.php\', {
method: \'POST\',
headers: {
\'Content-Type\': \'application/json\',
},
body: JSON.stringify({ student_id: studentId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById("scan-result").innerHTML = `
<div class="alert alert-success">
<div class="d-flex align-items-center">
<i class="bi bi-check-circle-fill me-3" style="font-size: 2rem;"></i>
<div>
<h5 class="mb-1">✓ Manual Entry Recorded!</h5>
<p class="mb-1"><strong>Student:</strong> \${data.data.student_name}</p>
<p class="mb-1"><strong>Activity:</strong> \${data.data.activity_name}</p>
<p class="mb-0"><strong>Time:</strong> \${data.data.time}</p>
</div>
</div>
</div>
`;
// Clear input
document.getElementById("student_id").value = \'\';
playSound(\'success\');
} else {
document.getElementById("scan-result").innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
\${data.message}
</div>
`;
playSound(\'error\');
}
})
.catch(error => {
document.getElementById("scan-result").innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
Network error: \${error}
</div>
`;
});
});
// Play sound
function playSound(type) {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
if (type === \'success\') {
oscillator.frequency.setValueAtTime(523.25, audioContext.currentTime);
oscillator.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.1);
oscillator.frequency.setValueAtTime(783.99, audioContext.currentTime + 0.2);
} else {
oscillator.frequency.setValueAtTime(200, audioContext.currentTime);
}
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.3);
}
// Initialize scanner when page loads
document.addEventListener("DOMContentLoaded", function() {
initScanner();
startScanner();
});
</script>
';
include '../includes/footer.php';
?>