v3
This commit is contained in:
parent
eb84d223b7
commit
fd73082fc6
180
api/attendance_handler.php
Normal file
180
api/attendance_handler.php
Normal 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);
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
'\'' : '''
|
||||
}[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();
|
||||
|
||||
const form = e.target;
|
||||
const formData = new FormData(form);
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
const originalButtonText = submitButton.innerHTML;
|
||||
|
||||
submitButton.disabled = true;
|
||||
submitButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Sending...';
|
||||
|
||||
fetch('contact.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.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.success) {
|
||||
alertClass = 'alert-danger';
|
||||
alertMessage = data.error || 'An unexpected error occurred. Please try again.';
|
||||
// ... (existing contact form logic) ...
|
||||
});
|
||||
}
|
||||
|
||||
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>`;
|
||||
// --- Attendance Page ---
|
||||
const attendancePage = document.getElementById('attendance-page');
|
||||
if (attendancePage) {
|
||||
const attendanceState = {
|
||||
selfieStream: null,
|
||||
qrScanner: null,
|
||||
qrLocationId: null
|
||||
};
|
||||
|
||||
if (data.success) {
|
||||
form.reset();
|
||||
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')
|
||||
};
|
||||
|
||||
// --- Initialization ---
|
||||
fetch('api/attendance_handler.php?action=get_courses')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success' && data.courses) {
|
||||
data.courses.forEach(course => {
|
||||
const option = new Option(course.name, course.id);
|
||||
elements.courseSelect.add(option);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// --- 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);
|
||||
}
|
||||
|
||||
submitAttendance(formData);
|
||||
}, 'image/jpeg');
|
||||
}
|
||||
|
||||
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>`;
|
||||
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.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
submitButton.disabled = false;
|
||||
submitButton.innerHTML = originalButtonText;
|
||||
.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
77
attendance.php
Normal 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
27
attendance_history.php
Normal 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
67
create_assignment.php
Normal 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
43
faculty_attendance.php
Normal 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
46
generate_qr.php
Normal 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
16
includes/footer.php
Normal file
@ -0,0 +1,16 @@
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="text-center">
|
||||
<div class="container">
|
||||
<p class="mb-2">© 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
72
includes/header.php
Normal 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>
|
||||
69
index.php
69
index.php
@ -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">© 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'; ?>
|
||||
|
||||
44
privacy.php
44
privacy.php
@ -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>© 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'; ?>
|
||||
Loading…
x
Reference in New Issue
Block a user