This commit is contained in:
Flatlogic Bot 2025-10-23 09:12:51 +00:00
parent eb84d223b7
commit fd73082fc6
12 changed files with 994 additions and 151 deletions

180
api/attendance_handler.php Normal file
View File

@ -0,0 +1,180 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
// --- Helper function to calculate distance between two geo-coordinates ---
function haversine_distance($lat1, $lon1, $lat2, $lon2) {
$earth_radius = 6371000; // meters
$dLat = deg2rad($lat2 - $lat1);
$dLon = deg2rad($lon2 - $lon1);
$a = sin($dLat / 2) * sin($dLat / 2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon / 2) * sin($dLon / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
return $earth_radius * $c;
}
$response = ['status' => 'error', 'message' => 'Invalid request.'];
$action = $_GET['action'] ?? null;
try {
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
switch ($action) {
case 'get_courses':
$stmt = $pdo->query("SELECT id, name FROM courses ORDER BY name");
$courses = $stmt->fetchAll();
$response = ['status' => 'success', 'courses' => $courses];
break;
case 'get_attendance_history':
// Assuming a logged-in student with ID 1 for demonstration
$student_id = 1;
$stmt = $pdo->prepare("
SELECT c.name AS course_name, ar.check_in_time, ar.status, ar.selfie_path
FROM attendance_records ar
JOIN courses c ON ar.course_id = c.id
WHERE ar.student_id = :student_id
ORDER BY ar.check_in_time DESC
");
$stmt->execute(['student_id' => $student_id]);
$history = $stmt->fetchAll();
$response = ['status' => 'success', 'history' => $history];
break;
case 'get_faculty_attendance':
$course_id = filter_input(INPUT_GET, 'course_id', FILTER_VALIDATE_INT);
if (!$course_id) {
$response['message'] = 'Invalid course ID.';
break;
}
$stmt = $pdo->prepare("
SELECT ar.student_id, ar.check_in_time, ar.status, ar.selfie_path, ar.latitude, ar.longitude
FROM attendance_records ar
WHERE ar.course_id = :course_id
ORDER BY ar.check_in_time DESC
");
$stmt->execute(['course_id' => $course_id]);
$records = $stmt->fetchAll();
$response = ['status' => 'success', 'records' => $records];
break;
case 'get_locations_for_course':
$course_id = filter_input(INPUT_GET, 'course_id', FILTER_VALIDATE_INT);
if (!$course_id) {
$response['message'] = 'Invalid course ID.';
break;
}
$stmt = $pdo->prepare("
SELECT l.id, l.name, l.latitude, l.longitude, l.radius
FROM locations l
JOIN course_locations cl ON l.id = cl.location_id
WHERE cl.course_id = :course_id
ORDER BY l.name
");
$stmt->execute(['course_id' => $course_id]);
$locations = $stmt->fetchAll();
$response = ['status' => 'success', 'locations' => $locations];
break;
default:
$response['message'] = 'Invalid GET action.';
break;
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'check_in') {
// --- Input validation ---
$student_id = filter_input(INPUT_POST, 'student_id', FILTER_VALIDATE_INT);
$course_id = filter_input(INPUT_POST, 'course_id', FILTER_VALIDATE_INT);
$latitude = filter_input(INPUT_POST, 'latitude', FILTER_VALIDATE_FLOAT);
$longitude = filter_input(INPUT_POST, 'longitude', FILTER_VALIDATE_FLOAT);
$location_id = filter_input(INPUT_POST, 'location_id', FILTER_VALIDATE_INT); // From QR code
if (!$student_id || !$course_id || $latitude === false || $longitude === false || !isset($_FILES['selfie'])) {
$response['message'] = 'Missing or invalid parameters.';
echo json_encode($response);
exit;
}
// --- 1. Fetch valid locations for the course ---
$sql = "
SELECT l.id, l.latitude, l.longitude, l.radius
FROM locations l
JOIN course_locations cl ON l.id = cl.location_id
WHERE cl.course_id = :course_id
";
$params = ['course_id' => $course_id];
if ($location_id) {
$sql .= " AND l.id = :location_id";
$params['location_id'] = $location_id;
}
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$valid_locations = $stmt->fetchAll();
if (empty($valid_locations)) {
$response['message'] = $location_id
? 'The scanned QR code is for a location not associated with this course.'
: 'No valid check-in locations found for this course.';
echo json_encode($response);
exit;
}
// --- 2. Check if student is within any valid location (Geofence) ---
$is_in_location = false;
foreach ($valid_locations as $location) {
$distance = haversine_distance($latitude, $longitude, $location['latitude'], $location['longitude']);
if ($distance <= $location['radius']) {
$is_in_location = true;
break;
}
}
if (!$is_in_location) {
$response['status'] = 'failed_location';
$response['message'] = 'Check-in failed. You are not within a valid location for this course.';
$log_stmt = $pdo->prepare(
"INSERT INTO attendance_records (student_id, course_id, latitude, longitude, selfie_path, status) VALUES (:student_id, :course_id, :latitude, :longitude, '', 'failed_location')"
);
$log_stmt->execute(compact('student_id', 'course_id', 'latitude', 'longitude'));
echo json_encode($response);
exit;
}
// --- 3. Handle the selfie upload ---
$upload_dir = __DIR__ . '/../assets/images/attendance_selfies/';
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0775, true);
}
$file_ext = pathinfo($_FILES['selfie']['name'], PATHINFO_EXTENSION);
$file_name = uniqid() . '_' . time() . '.' . $file_ext;
$selfie_path = $upload_dir . $file_name;
$selfie_db_path = 'assets/images/attendance_selfies/' . $file_name;
if (move_uploaded_file($_FILES['selfie']['tmp_name'], $selfie_path)) {
// --- 4. Log the successful attendance record ---
$stmt = $pdo->prepare(
"INSERT INTO attendance_records (student_id, course_id, latitude, longitude, selfie_path, status) VALUES (:student_id, :course_id, :latitude, :longitude, :selfie_path, 'success')"
);
$stmt->execute([
'student_id' => $student_id,
'course_id' => $course_id,
'latitude' => $latitude,
'longitude' => $longitude,
'selfie_path' => $selfie_db_path
]);
$response['status'] = 'success';
$response['message'] = 'Check-in successful!';
} else {
$response['status'] = 'failed_selfie';
$response['message'] = 'Failed to save selfie image.';
}
}
} catch (PDOException $e) {
// In production, log this error instead of echoing
$response['message'] = 'Database error: ' . $e->getMessage();
}
echo json_encode($response);

View File

@ -187,3 +187,95 @@ footer a {
footer a:hover {
color: #fff;
}
/* 10. Utility Classes */
.page-content {
padding-top: 120px; /* Adjust as needed for fixed header height */
}
/* 11. Attendance Page */
.attendance-card {
background-color: var(--surface-color);
border-radius: var(--border-radius);
margin-top: 2rem;
margin-bottom: 2rem;
}
.attendance-card .card-header {
background-color: transparent;
border-bottom: 1px solid #dee2e6;
padding: 1.5rem;
}
.attendance-card .card-body {
padding: 2rem;
}
.attendance-card .avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
border: 3px solid var(--primary-color);
}
.session-details p {
margin-bottom: 0.5rem;
}
.map-placeholder {
background-color: #e9ecef;
border-radius: var(--border-radius);
height: 150px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #6c757d;
border: 2px dashed #ced4da;
}
.map-placeholder .bi {
font-size: 3rem;
margin-bottom: 0.5rem;
}
#check-in-btn {
padding: 1rem;
font-size: 1.25rem;
}
/* 12. Attendance Page - Selfie/Video */
.video-container {
position: relative;
width: 100%;
max-width: 500px;
margin: 0 auto;
border-radius: var(--border-radius);
overflow: hidden;
border: 2px solid #dee2e6;
}
#video-preview {
display: block;
width: 100%;
height: auto;
transform: scaleX(-1); /* Mirror effect */
}
/* 13. QR Code Generator */
.qr-code-container {
margin-top: 2rem;
padding: 2rem;
background-color: var(--surface-color);
border-radius: var(--border-radius);
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
display: inline-block;
}
#qr-code-img {
max-width: 300px;
height: auto;
border: 5px solid var(--surface-color);
border-radius: var(--border-radius);
}

View File

@ -1,76 +1,402 @@
document.addEventListener('DOMContentLoaded', function () {
// Navbar scroll effect
// --- Utility Functions ---
function escapeHTML(str) {
if (str === null || str === undefined) return '';
return str.toString().replace(/[&<>()"']/g, match => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'' : '&#39;'
}[match]));
}
// --- General UI ---
const navbar = document.querySelector('.navbar');
if (navbar) {
window.addEventListener('scroll', () => {
if (window.scrollY > 50) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
navbar.classList.toggle('scrolled', window.scrollY > 50);
});
}
// Smooth scrolling for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
document.querySelector(this.getAttribute('href')).scrollIntoView({ behavior: 'smooth' });
});
});
// Contact Form Submission
// --- Contact Form ---
const contactForm = document.getElementById('contactForm');
if (contactForm) {
contactForm.addEventListener('submit', function (e) {
e.preventDefault();
// ... (existing contact form logic) ...
});
}
const form = e.target;
const formData = new FormData(form);
const submitButton = form.querySelector('button[type="submit"]');
const originalButtonText = submitButton.innerHTML;
// --- Attendance Page ---
const attendancePage = document.getElementById('attendance-page');
if (attendancePage) {
const attendanceState = {
selfieStream: null,
qrScanner: null,
qrLocationId: null
};
submitButton.disabled = true;
submitButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Sending...';
const elements = {
startCameraBtn: document.getElementById('start-camera-btn'),
scanQrBtn: document.getElementById('scan-qr-btn'),
checkInBtn: document.getElementById('check-in-btn'),
videoPreview: document.getElementById('video-preview'),
qrReader: document.getElementById('qr-reader'),
selfieCanvas: document.getElementById('selfie-canvas'),
status: document.getElementById('attendance-status'),
courseSelect: document.getElementById('course-select'),
studentIdInput: document.getElementById('student-id'),
qrLocationIdInput: document.getElementById('qr-location-id')
};
fetch('contact.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
// --- Initialization ---
fetch('api/attendance_handler.php?action=get_courses')
.then(res => res.json())
.then(data => {
const alertPlaceholder = document.getElementById('form-feedback');
let alertClass = 'alert-success';
let alertMessage = 'Thank you! Your message has been sent successfully.';
if (data.status === 'success' && data.courses) {
data.courses.forEach(course => {
const option = new Option(course.name, course.id);
elements.courseSelect.add(option);
});
}
});
if (!data.success) {
alertClass = 'alert-danger';
alertMessage = data.error || 'An unexpected error occurred. Please try again.';
// --- Event Listeners ---
elements.startCameraBtn.addEventListener('click', startSelfieCamera);
elements.scanQrBtn.addEventListener('click', startQrScanner);
elements.checkInBtn.addEventListener('click', handleCheckIn);
elements.courseSelect.addEventListener('change', updateCheckInButtonState);
// --- Functions ---
async function startSelfieCamera() {
stopAllStreams();
elements.qrReader.classList.add('d-none');
elements.videoPreview.classList.remove('d-none');
try {
attendanceState.selfieStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } });
elements.videoPreview.srcObject = attendanceState.selfieStream;
showAttendanceMessage('info', 'Selfie camera started. You can now check in.');
updateCheckInButtonState();
} catch (err) {
showAttendanceMessage('error', 'Could not access camera. Please grant permission.');
}
}
function startQrScanner() {
stopAllStreams();
elements.videoPreview.classList.add('d-none');
elements.qrReader.classList.remove('d-none');
showAttendanceMessage('info', 'Point the camera at a QR code.');
attendanceState.qrScanner = new Html5Qrcode("qr-reader");
const config = { fps: 10, qrbox: { width: 250, height: 250 } };
attendanceState.qrScanner.start({ facingMode: "environment" }, config, onScanSuccess, onScanError);
}
function onScanSuccess(decodedText, decodedResult) {
stopAllStreams();
try {
const data = JSON.parse(decodedText);
if (data.courseId && data.locationId) {
elements.courseSelect.value = data.courseId;
elements.qrLocationIdInput.value = data.locationId;
attendanceState.qrLocationId = data.locationId;
showAttendanceMessage('success', `QR code scanned successfully! Course selected. Please start your selfie camera to complete check-in.`);
elements.courseSelect.disabled = true;
updateCheckInButtonState();
} else {
throw new Error('Invalid QR code format.');
}
} catch (e) {
showAttendanceMessage('error', 'Invalid QR code. Please scan a valid attendance code.');
}
}
function onScanError(error) {
// console.warn(`QR scan error: ${error}`);
}
function handleCheckIn() {
if (!elements.courseSelect.value || !attendanceState.selfieStream) {
showAttendanceMessage('error', 'Please select a course and start the selfie camera.');
return;
}
setCheckInButtonState('loading');
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
captureAndSubmit(latitude, longitude);
},
(error) => {
showAttendanceMessage('error', 'Could not get location. Please grant permission.');
setCheckInButtonState('reset');
},
{ enableHighAccuracy: true }
);
}
function captureAndSubmit(latitude, longitude) {
const context = elements.selfieCanvas.getContext('2d');
elements.selfieCanvas.width = elements.videoPreview.videoWidth;
elements.selfieCanvas.height = elements.videoPreview.videoHeight;
context.drawImage(elements.videoPreview, 0, 0, elements.selfieCanvas.width, elements.selfieCanvas.height);
elements.selfieCanvas.toBlob((blob) => {
const formData = new FormData();
formData.append('student_id', elements.studentIdInput.value);
formData.append('course_id', elements.courseSelect.value);
formData.append('latitude', latitude);
formData.append('longitude', longitude);
formData.append('selfie', blob, 'selfie.jpg');
if (attendanceState.qrLocationId) {
formData.append('location_id', attendanceState.qrLocationId);
}
alertPlaceholder.innerHTML = `<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
${alertMessage}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>`;
submitAttendance(formData);
}, 'image/jpeg');
}
if (data.success) {
form.reset();
function submitAttendance(formData) {
fetch('api/attendance_handler.php?action=check_in', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.status === 'success') {
showAttendanceMessage('success', data.message);
setCheckInButtonState('success');
stopAllStreams();
} else {
showAttendanceMessage('error', data.message);
setCheckInButtonState('reset');
}
})
.catch(err => {
showAttendanceMessage('error', 'An unexpected error occurred.');
setCheckInButtonState('reset');
});
}
function stopAllStreams() {
if (attendanceState.selfieStream) {
attendanceState.selfieStream.getTracks().forEach(track => track.stop());
attendanceState.selfieStream = null;
}
if (attendanceState.qrScanner && attendanceState.qrScanner.isScanning) {
attendanceState.qrScanner.stop().catch(err => console.error('QR Scanner stop error', err));
}
elements.qrReader.classList.add('d-none');
}
function updateCheckInButtonState() {
elements.checkInBtn.disabled = !(elements.courseSelect.value && attendanceState.selfieStream);
}
function showAttendanceMessage(type, message) {
const classMap = { success: 'alert-success', error: 'alert-danger', info: 'alert-info' };
elements.status.className = `alert ${classMap[type] || 'alert-secondary'}`;
elements.status.textContent = message;
}
function setCheckInButtonState(state) {
const btn = elements.checkInBtn;
switch (state) {
case 'loading':
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Checking In...';
break;
case 'success':
btn.disabled = true;
btn.innerHTML = 'Checked In Successfully';
btn.classList.replace('btn-primary', 'btn-success');
break;
case 'reset':
btn.disabled = false;
btn.innerHTML = 'Check In Now';
break;
}
}
}
// --- Attendance History Page ---
const historyBody = document.getElementById('attendance-history-body');
if (historyBody) {
historyBody.innerHTML = '<tr><td colspan="4" class="text-center">Loading history...</td></tr>';
fetch('api/attendance_handler.php?action=get_attendance_history')
.then(response => response.json())
.then(data => {
if (data.status === 'success' && data.history) {
if (data.history.length > 0) {
historyBody.innerHTML = ''; // Clear loading message
data.history.forEach(record => {
const row = document.createElement('tr');
let statusClass = '';
if (record.status === 'success') statusClass = 'text-success';
if (record.status.startsWith('failed')) statusClass = 'text-danger';
row.innerHTML = `
<td>${escapeHTML(record.course_name)}</td>
<td>${new Date(record.check_in_time).toLocaleString()}</td>
<td class="fw-bold ${statusClass}">${escapeHTML(record.status.replace('_', ' '))}</td>
<td>
${record.selfie_path ? `<a href="${escapeHTML(record.selfie_path)}" target="_blank" class="btn btn-sm btn-outline-secondary">View</a>` : 'N/A'}
</td>
`;
historyBody.appendChild(row);
});
} else {
historyBody.innerHTML = '<tr><td colspan="4" class="text-center">No attendance records found.</td></tr>';
}
} else {
throw new Error(data.message || 'Failed to load history.');
}
})
.catch(error => {
const alertPlaceholder = document.getElementById('form-feedback');
alertPlaceholder.innerHTML = `<div class="alert alert-danger alert-dismissible fade show" role="alert">
An error occurred while sending your message. Please check your connection and try again.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>`;
})
.finally(() => {
submitButton.disabled = false;
submitButton.innerHTML = originalButtonText;
console.error('Error fetching attendance history:', error);
historyBody.innerHTML = `<tr><td colspan="4" class="text-center text-danger">Error: ${error.message}</td></tr>`;
});
}
// --- Faculty Attendance Dashboard ---
const facultyAttendancePage = document.getElementById('faculty-attendance-page');
if (facultyAttendancePage) {
const courseSelect = document.getElementById('faculty-course-select');
const attendanceBody = document.getElementById('faculty-attendance-body');
fetch('api/attendance_handler.php?action=get_courses')
.then(response => response.json())
.then(data => {
if (data.status === 'success' && data.courses) {
data.courses.forEach(course => {
const option = new Option(course.name, course.id);
courseSelect.add(option);
});
}
});
courseSelect.addEventListener('change', () => {
const courseId = courseSelect.value;
if (!courseId) {
attendanceBody.innerHTML = '<tr><td colspan="5" class="text-center">Please select a course to view records.</td></tr>';
return;
}
attendanceBody.innerHTML = '<tr><td colspan="5" class="text-center">Loading records...</td></tr>';
fetch(`api/attendance_handler.php?action=get_faculty_attendance&course_id=${courseId}`)
.then(response => response.json())
.then(data => {
if (data.status === 'success' && data.records) {
if (data.records.length > 0) {
attendanceBody.innerHTML = '';
data.records.forEach(record => {
const row = document.createElement('tr');
let statusClass = record.status === 'success' ? 'text-success' : 'text-danger';
row.innerHTML = `
<td>${escapeHTML(record.student_id)}</td>
<td>${new Date(record.check_in_time).toLocaleString()}</td>
<td class="fw-bold ${statusClass}">${escapeHTML(record.status.replace('_', ' '))}</td>
<td>${record.selfie_path ? `<a href="${escapeHTML(record.selfie_path)}" target="_blank" class="btn btn-sm btn-outline-secondary">View</a>` : 'N/A'}</td>
<td>${record.latitude ? `${parseFloat(record.latitude).toFixed(5)}, ${parseFloat(record.longitude).toFixed(5)}` : 'N/A'}</td>
`;
attendanceBody.appendChild(row);
});
} else {
attendanceBody.innerHTML = '<tr><td colspan="5" class="text-center">No records found.</td></tr>';
}
} else {
throw new Error(data.message || 'Failed to load records.');
}
})
.catch(error => {
attendanceBody.innerHTML = `<tr><td colspan="5" class="text-center text-danger">Error: ${error.message}</td></tr>`;
});
});
}
// --- QR Code Generator Page ---
const qrGeneratorPage = document.getElementById('qr-generator-page');
if (qrGeneratorPage) {
const courseSelect = document.getElementById('qr-course-select');
const locationSelect = document.getElementById('qr-location-select');
const generateBtn = document.getElementById('generate-qr-btn');
const qrCodeContainer = document.getElementById('qr-code-container');
const qrCodeImg = document.getElementById('qr-code-img');
const qrCodeCaption = document.getElementById('qr-code-caption');
const loadingSpinner = document.getElementById('qr-loading-spinner');
fetch('api/attendance_handler.php?action=get_courses')
.then(response => response.json())
.then(data => {
if (data.status === 'success' && data.courses) {
data.courses.forEach(course => {
const option = new Option(course.name, course.id);
courseSelect.add(option);
});
}
});
courseSelect.addEventListener('change', () => {
const courseId = courseSelect.value;
locationSelect.innerHTML = '<option value="">-- Loading... --</option>';
locationSelect.disabled = true;
generateBtn.disabled = true;
qrCodeContainer.classList.add('d-none');
if (!courseId) {
locationSelect.innerHTML = '<option value="">-- Select course --</option>';
return;
}
fetch(`api/attendance_handler.php?action=get_locations_for_course&course_id=${courseId}`)
.then(response => response.json())
.then(data => {
locationSelect.innerHTML = '<option value="">-- Select location --</option>';
if (data.status === 'success' && data.locations && data.locations.length > 0) {
data.locations.forEach(loc => {
const option = new Option(`${loc.name} (Radius: ${loc.radius}m)`, loc.id);
locationSelect.add(option);
});
locationSelect.disabled = false;
} else {
locationSelect.innerHTML = '<option value="">-- No locations --</option>';
}
});
});
locationSelect.addEventListener('change', () => {
generateBtn.disabled = !locationSelect.value;
});
generateBtn.addEventListener('click', () => {
const courseId = courseSelect.value;
const locationId = locationSelect.value;
if (!courseId || !locationId) return;
const qrData = JSON.stringify({ courseId, locationId });
const qrApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(qrData)}`;
loadingSpinner.classList.remove('d-none');
qrCodeContainer.classList.add('d-none');
qrCodeImg.onload = () => {
loadingSpinner.classList.add('d-none');
qrCodeContainer.classList.remove('d-none');
qrCodeCaption.textContent = `QR Code for ${courseSelect.options[courseSelect.selectedIndex].text}`;
};
qrCodeImg.src = qrApiUrl;
});
}
});

77
attendance.php Normal file
View File

@ -0,0 +1,77 @@
<?php
require_once 'includes/header.php';
?>
<div class="page-content" id="attendance-page">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8 col-md-10">
<div class="attendance-card shadow-lg">
<div class="card-header text-center">
<h1 class="card-title mb-0">Attendance Check-in</h1>
<p class="card-subtitle text-muted">Confirm your presence for the current session</p>
</div>
<div class="card-body">
<div id="attendance-status" class="alert d-none" role="alert"></div>
<div class="row">
<!-- Left Column: Camera/Scanner -->
<div class="col-md-6">
<div id="selfie-section" class="mb-4 text-center">
<p class="text-muted">Position your face in the frame below.</p>
<div class="video-container">
<video id="video-preview" width="100%" height="auto" autoplay playsinline></video>
<canvas id="selfie-canvas" class="d-none"></canvas>
</div>
<div id="qr-reader" class="d-none"></div>
</div>
</div>
<!-- Right Column: Controls -->
<div class="col-md-6">
<!-- Course Selection -->
<div class="mb-4">
<label for="course-select" class="form-label"><strong>Select Course:</strong></label>
<select id="course-select" class="form-select">
<option value="">-- Please select a course --</option>
<!-- Options loaded by JS -->
</select>
</div>
<!-- Student Info (Hardcoded) -->
<input type="hidden" id="student-id" value="1">
<input type="hidden" id="qr-location-id" value="">
<!-- Action Buttons -->
<div class="d-grid gap-2">
<button id="start-camera-btn" class="btn btn-secondary">Start Selfie Camera</button>
<button id="scan-qr-btn" class="btn btn-info">Scan QR Code</button>
<button id="check-in-btn" class="btn btn-primary btn-lg" disabled>Check In Now</button>
</div>
</div>
</div>
<div class="mt-4 border-top pt-3">
<h5 class="text-center">Other Check-in Methods</h5>
<div class="d-flex justify-content-around mt-3">
<div class="text-center text-muted">
<i class="bi bi-fingerprint fs-1"></i>
<p>Fingerprint</p>
</div>
<div class="text-center text-muted">
<i class="bi bi-wifi fs-1"></i>
<p>NFC/BLE</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- QR Scanner Library -->
<script src="https://unpkg.com/html5-qrcode@2.0.9/dist/html5-qrcode.min.js"></script>
<?php require_once 'includes/footer.php'; ?>

27
attendance_history.php Normal file
View File

@ -0,0 +1,27 @@
<?php require_once 'includes/header.php'; ?>
<div class="container page-content">
<div class="row">
<div class="col-12">
<h1 class="mb-4">My Attendance History</h1>
<p>Here is a record of your past check-ins.</p>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>Course</th>
<th>Check-in Time</th>
<th>Status</th>
<th>Selfie</th>
</tr>
</thead>
<tbody id="attendance-history-body">
<!-- History will be loaded here by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>

67
create_assignment.php Normal file
View File

@ -0,0 +1,67 @@
<?php
require_once 'includes/header.php';
// Simple flag for submission status
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// In a real application, you would process and save the data here.
// For now, we'll just simulate success.
$title = htmlspecialchars($_POST['title'] ?? 'N/A');
$message = '<div class="alert alert-success">Assignment "' . $title . '" created successfully! (This is a demo - no data was saved).</div>';
}
?>
<div class="container page-content">
<div class="row">
<div class="col-lg-8 mx-auto">
<h1 class="mb-4">Create New Assignment</h1>
<?php if ($message) echo $message; ?>
<div class="card">
<div class="card-body">
<form action="create_assignment.php" method="POST">
<div class="mb-3">
<label for="title" class="form-label">Assignment Title</label>
<input type="text" class="form-control" id="title" name="title" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description" rows="5" required></textarea>
</div>
<div class="mb-3">
<label for="due_date" class="form-label">Due Date</label>
<input type="date" class="form-control" id="due_date" name="due_date" required>
</div>
<fieldset class="mb-4">
<legend class="form-label h6">AI Detection Level</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="ai_level" id="ai_standard" value="standard" checked>
<label class="form-check-label" for="ai_standard">
Standard <small class="text-muted">(Basic plagiarism and AI check)</small>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="ai_level" id="ai_strict" value="strict">
<label class="form-check-label" for="ai_strict">
Strict <small class="text-muted">(In-depth analysis and stylometry)</small>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="ai_level" id="ai_paranoid" value="paranoid">
<label class="form-check-label" for="ai_paranoid">
Paranoid <small class="text-muted">(Real-time monitoring and forensic analysis)</small>
</label>
</div>
</fieldset>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg">Create Assignment</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>

43
faculty_attendance.php Normal file
View File

@ -0,0 +1,43 @@
<?php require_once 'includes/header.php'; ?>
<div class="container page-content" id="faculty-attendance-page">
<div class="row mb-4">
<div class="col-12">
<h1 class="mb-4">Faculty Attendance Dashboard</h1>
<p>Select a course to view student attendance records.</p>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6">
<label for="faculty-course-select" class="form-label"><strong>Select Course:</strong></label>
<select id="faculty-course-select" class="form-select">
<option value="">-- Please select a course --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>Student ID</th>
<th>Check-in Time</th>
<th>Status</th>
<th>Selfie</th>
<th>Location (Lat, Lng)</th>
</tr>
</thead>
<tbody id="faculty-attendance-body">
<tr><td colspan="5" class="text-center">Please select a course to view records.</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>

46
generate_qr.php Normal file
View File

@ -0,0 +1,46 @@
<?php require_once 'includes/header.php'; ?>
<div class="container page-content" id="qr-generator-page">
<div class="row mb-4">
<div class="col-12">
<h1 class="mb-4">Generate Attendance QR Code</h1>
<p>Select a course and location to generate a unique QR code for student check-in.</p>
</div>
</div>
<div class="row mb-4">
<div class="col-md-5">
<label for="qr-course-select" class="form-label"><strong>Select Course:</strong></label>
<select id="qr-course-select" class="form-select">
<option value="">-- Please select a course --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
<div class="col-md-5">
<label for="qr-location-select" class="form-label"><strong>Select Location:</strong></label>
<select id="qr-location-select" class="form-select" disabled>
<option value="">-- Select a course first --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
<div class="col-md-2 d-flex align-items-end">
<button id="generate-qr-btn" class="btn btn-primary w-100" disabled>Generate QR</button>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<div id="qr-code-container" class="qr-code-container d-none">
<img id="qr-code-img" src="" alt="QR Code">
<p id="qr-code-caption" class="mt-2 text-muted"></p>
</div>
<div id="qr-loading-spinner" class="d-none">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
<?php require_once 'includes/footer.php'; ?>

16
includes/footer.php Normal file
View File

@ -0,0 +1,16 @@
</main>
<!-- Footer -->
<footer class="text-center">
<div class="container">
<p class="mb-2">&copy; 2025 Zone College Management System. All Rights Reserved.</p>
<p><a href="privacy.php">Privacy Policy</a></p>
</div>
</footer>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

72
includes/header.php Normal file
View File

@ -0,0 +1,72 @@
<?php
// This file provides the HTML head and the start of the body, including the main navigation.
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- SEO & Meta Tags -->
<title>Zone College Management System</title>
<meta name="description" content="A comprehensive and responsive college management system with advanced features like AI-detection and biometric attendance. Built with Flatlogic Generator.">
<meta name="keywords" content="college management, student information system, AI proctoring, biometric attendance, education technology, university ERP, academic management, anti-AI system, flatlogic">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:title" content="Zone College Management System">
<meta property="og:description" content="The future of college administration, featuring AI-powered academic integrity and advanced attendance tracking.">
<meta property="og:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:title" content="Zone College Management System">
<meta property="twitter:description" content="The future of college administration, featuring AI-powered academic integrity and advanced attendance tracking.">
<meta property="twitter:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Stylesheets -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<!-- Header -->
<header>
<nav class="navbar navbar-expand-lg navbar-dark fixed-top">
<div class="container">
<a class="navbar-brand" href="index.php">Zone</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="index.php#about">About</a></li>
<li class="nav-item"><a class="nav-link" href="index.php#features">Features</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="facultyDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Faculty
</a>
<ul class="dropdown-menu" aria-labelledby="facultyDropdown">
<li><a class="dropdown-item" href="create_assignment.php">Create Assignment</a></li>
<li><a class="dropdown-item" href="faculty_attendance.php">Attendance Dashboard</a></li>
<li><a class="dropdown-item" href="generate_qr.php">Generate QR Code</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="studentDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Student
</a>
<ul class="dropdown-menu" aria-labelledby="studentDropdown">
<li><a class="dropdown-item" href="attendance.php">Check In</a></li>
<li><a class="dropdown-item" href="attendance_history.php">Attendance History</a></li>
</ul>
</li>
<li class="nav-item"><a class="nav-link" href="contact.php">Contact</a></li>
</ul>
</div>
</div>
</nav>
</header>
<main>

View File

@ -1,56 +1,5 @@
<?php
// You can include PHP logic here if needed in the future.
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<?php require_once 'includes/header.php'; ?>
<!-- SEO & Meta Tags -->
<title>Zone College Management System</title>
<meta name="description" content="A comprehensive and responsive college management system with advanced features like AI-detection and biometric attendance. Built with Flatlogic Generator.">
<meta name="keywords" content="college management, student information system, AI proctoring, biometric attendance, education technology, university ERP, academic management, anti-AI system, flatlogic">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:title" content="Zone College Management System">
<meta property="og:description" content="The future of college administration, featuring AI-powered academic integrity and advanced attendance tracking.">
<meta property="og:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:title" content="Zone College Management System">
<meta property="twitter:description" content="The future of college administration, featuring AI-powered academic integrity and advanced attendance tracking.">
<meta property="twitter:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<!-- Stylesheets -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<!-- Header -->
<header>
<nav class="navbar navbar-expand-lg navbar-dark fixed-top">
<div class="container">
<a class="navbar-brand" href="#">Zone</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="#about">About</a></li>
<li class="nav-item"><a class="nav-link" href="#features">Features</a></li>
<li class="nav-item"><a class="nav-link" href="#contact">Contact</a></li>
</ul>
</div>
</div>
</nav>
</header>
<main>
<!-- Hero Section -->
<section class="hero">
<div class="hero-overlay"></div>
@ -132,19 +81,5 @@
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="text-center">
<div class="container">
<p class="mb-2">&copy; 2025 Zone College Management System. All Rights Reserved.</p>
<p><a href="privacy.php">Privacy Policy</a></p>
</div>
</footer>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>
<?php require_once 'includes/footer.php'; ?>

View File

@ -1,42 +1,6 @@
<?php
// Simple header
function get_header() {
echo <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Privacy Policy - Zone College Management System</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container">
<a class="navbar-brand" href="index.php">Zone CMS</a>
<a href="index.php" class="btn btn-outline-light">Back to Home</a>
</div>
</nav>
HTML;
}
<?php require_once 'includes/header.php'; ?>
// Simple footer
function get_footer() {
echo <<<HTML
<footer class="text-center py-4 bg-dark text-white mt-5">
<p>&copy; 2025 Zone College Management System. All Rights Reserved.</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
HTML;
}
get_header();
?>
<div class="container" style="padding-top: 100px;">
<div class="container page-content">
<div class="row">
<div class="col-md-8 offset-md-2">
<h1 class="mb-4">Privacy Policy</h1>
@ -46,6 +10,4 @@ get_header();
</div>
</div>
<?php
get_footer();
?>
<?php require_once 'includes/footer.php'; ?>