Initial commit
This commit is contained in:
363
src-backup/admin/attendance.php
Normal file
363
src-backup/admin/attendance.php
Normal file
@@ -0,0 +1,363 @@
|
||||
<?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';
|
||||
?>
|
||||
Reference in New Issue
Block a user