This commit is contained in:
Flatlogic Bot 2026-01-30 14:21:32 +00:00
parent 14d9c5e808
commit e29ef2dff4
9 changed files with 781 additions and 77 deletions

148
admin.php Normal file
View File

@ -0,0 +1,148 @@
<?php
require_once __DIR__ . '/db/config.php';
$db = db();
$current_role = 'Admin';
$pageTitle = 'Admin Dashboard | Township Schools Platform';
// Fetch Some Stats
$total_learners = $db->query("SELECT COUNT(*) FROM learners")->fetchColumn();
// Attendance for today
$today = date('Y-m-d');
$present_today = $db->prepare("SELECT COUNT(*) FROM attendance WHERE date = ? AND status = 'present'");
$present_today->execute([$today]);
$present_today_count = $present_today->fetchColumn();
$presence_rate = $total_learners > 0 ? round(($present_today_count / $total_learners) * 100) : 0;
// Analytics: Attendance by Grade
$grade_stats = $db->query("
SELECT l.grade,
COUNT(l.id) as total,
SUM(CASE WHEN a.status = 'present' THEN 1 ELSE 0 END) as present
FROM learners l
LEFT JOIN attendance a ON l.id = a.learner_id AND a.date = '$today'
GROUP BY l.grade
ORDER BY l.grade
")->fetchAll();
include 'includes/header.php';
?>
<div class="container pb-5">
<div class="row mb-4">
<div class="col-12">
<h2 class="h4 mb-1">School Admin Dashboard</h2>
<p class="text-muted small">Overview of school operations</p>
</div>
</div>
<!-- Stats Row -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card p-3 stats-card">
<p>Total Learners</p>
<h3><?= $total_learners ?></h3>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 stats-card" style="border-left-color: #2E7D32;">
<p>Today's Presence</p>
<h3><?= $presence_rate ?>%</h3>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 stats-card" style="border-left-color: var(--secondary-color);">
<p>Total Staff</p>
<h3>12</h3>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 stats-card" style="border-left-color: #0288D1;">
<p>SGB Meetings</p>
<h3>1</h3>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-md-8">
<div class="card mb-4">
<div class="card-header bg-white py-3">
<h5 class="mb-0">Attendance Analytics by Grade (Today)</h5>
</div>
<div class="card-body">
<?php foreach ($grade_stats as $stat): ?>
<?php
$rate = $stat['total'] > 0 ? round(($stat['present'] / $stat['total']) * 100) : 0;
$bar_color = $rate > 80 ? 'bg-success' : ($rate > 50 ? 'bg-warning' : 'bg-danger');
?>
<div class="mb-3">
<div class="d-flex justify-content-between mb-1">
<span class="small fw-semibold"><?= htmlspecialchars($stat['grade']) ?></span>
<span class="small text-muted"><?= $rate ?>% (<?= $stat['present'] ?>/<?= $stat['total'] ?>)</span>
</div>
<div class="progress" style="height: 8px;">
<div class="progress-bar <?= $bar_color ?>" role="progressbar" style="width: <?= $rate ?>%"></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="card">
<div class="card-header bg-white py-3">
<h5 class="mb-0">Recent Activity</h5>
</div>
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="list-group-item px-0">
<div class="d-flex justify-content-between">
<div>
<h6 class="mb-0">Daily Register Completed</h6>
<small class="text-muted">Grade 10A - Mrs. Mdluli</small>
</div>
<span class="text-muted small">10 mins ago</span>
</div>
</li>
<li class="list-group-item px-0">
<div class="d-flex justify-content-between">
<div>
<h6 class="mb-0">New Learner Registered</h6>
<small class="text-muted">Sipho Zulu - Grade 8B</small>
</div>
<span class="text-muted small">1 hour ago</span>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-header bg-white py-3">
<h5 class="mb-0">Quick Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="learners.php" class="btn btn-outline-primary text-start">
<i class="bi bi-person-plus me-2"></i> Register New Learner
</a>
<button class="btn btn-outline-primary text-start">
<i class="bi bi-file-earmark-bar-graph me-2"></i> Generate Reports
</a>
<button class="btn btn-outline-primary text-start">
<i class="bi bi-megaphone me-2"></i> Send Parent Notice
</a>
<button class="btn btn-outline-primary text-start">
<i class="bi bi-calendar-event me-2"></i> Schedule Meeting
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<?php include 'includes/footer.php'; ?>

View File

@ -2,26 +2,86 @@
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Service Worker registered', reg))
.then(reg => {
console.log('Service Worker registered');
// Check for updates
reg.onupdatefound = () => {
const installingWorker = reg.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed' && navigator.serviceWorker.controller) {
console.log('New content is available; please refresh.');
}
};
};
})
.catch(err => console.log('Service Worker registration failed', err));
});
}
// Interactivity for Attendance Toggles
document.addEventListener('DOMContentLoaded', () => {
const tableRows = document.querySelectorAll('#learnersTable tbody tr');
// Online/Offline Status
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
function updateOnlineStatus() {
const status = navigator.onLine ? 'online' : 'offline';
console.log('Status changed to:', status);
// Create/Update UI indicator if it doesn't exist
let indicator = document.getElementById('offline-indicator');
if (!navigator.onLine) {
if (!indicator) {
indicator = document.createElement('div');
indicator.id = 'offline-indicator';
indicator.className = 'alert alert-warning fixed-bottom m-0 text-center rounded-0';
indicator.style.zIndex = '9999';
indicator.innerHTML = '<i class="bi bi-wifi-off me-2"></i> You are currently offline. Changes will be synced when you reconnect.';
document.body.appendChild(indicator);
}
} else {
if (indicator) {
indicator.remove();
// Show brief "Back online" message
const onlineToast = document.createElement('div');
onlineToast.className = 'alert alert-success fixed-bottom m-0 text-center rounded-0';
onlineToast.style.zIndex = '9999';
onlineToast.innerHTML = '<i class="bi bi-wifi me-2"></i> Back online! Syncing data...';
document.body.appendChild(onlineToast);
setTimeout(() => onlineToast.remove(), 3000);
}
}
}
// Initial check
document.addEventListener('DOMContentLoaded', () => {
updateOnlineStatus();
// Attendance Table Search (Re-initialize if present)
const learnerSearch = document.getElementById('learnerSearch');
if (learnerSearch) {
learnerSearch.addEventListener('keyup', function() {
let filter = this.value.toUpperCase();
let rows = document.querySelector("#learnersTable tbody").rows;
for (let i = 0; i < rows.length; i++) {
let nameCol = rows[i].cells[0].textContent.toUpperCase();
let idCol = rows[i].cells[2].textContent.toUpperCase();
if (nameCol.indexOf(filter) > -1 || idCol.indexOf(filter) > -1) {
rows[i].style.display = "";
} else {
rows[i].style.display = "none";
}
}
});
}
// Attendance Toggle Feedback
const tableRows = document.querySelectorAll('#learnersTable tbody tr');
tableRows.forEach(row => {
const presentBtn = row.querySelector('label[for^="pres_"]');
const absentBtn = row.querySelector('label[for^="abs_"]');
// Add subtle feedback on change
const inputs = row.querySelectorAll('input[type="radio"]');
inputs.forEach(input => {
input.addEventListener('change', () => {
row.classList.add('table-primary');
setTimeout(() => row.classList.remove('table-primary'), 500);
row.classList.add('table-light');
setTimeout(() => row.classList.remove('table-light'), 500);
});
});
});
});
});

14
includes/footer.php Normal file
View File

@ -0,0 +1,14 @@
<footer class="bg-white border-top py-4 mt-5">
<div class="container text-center">
<p class="text-muted small mb-0">&copy; <?= date('Y') ?> Township Schools Platform. Optimized for low-bandwidth.</p>
<div class="mt-2">
<span class="badge rounded-pill bg-light text-dark border"><i class="bi bi-wifi-off me-1"></i> Offline Capable</span>
<span class="badge rounded-pill bg-light text-dark border ms-2"><i class="bi bi-shield-check me-1"></i> POPIA Compliant</span>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?= time() ?>"></script>
</body>
</html>

80
includes/header.php Normal file
View File

@ -0,0 +1,80 @@
<?php
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Education Ecosystem Platform for South African Schools';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
$current_role = $current_role ?? 'Teacher';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $pageTitle ?? 'Township Schools Platform' ?></title>
<?php if ($projectDescription): ?>
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark mb-4">
<div class="container">
<a class="navbar-brand" href="index.php">
<i class="bi bi-book-half me-2"></i>
Township Schools
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<?php if ($current_role === 'Super Admin'): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'super-admin.php' ? 'active' : '' ?>" href="super-admin.php">Platform Stats</a>
</li>
<?php endif; ?>
<?php if ($current_role === 'Teacher'): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'index.php' ? 'active' : '' ?>" href="index.php">Attendance</a>
</li>
<?php endif; ?>
<?php if ($current_role === 'Admin'): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'admin.php' ? 'active' : '' ?>" href="admin.php">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'learners.php' ? 'active' : '' ?>" href="learners.php">Learners</a>
</li>
<?php endif; ?>
<?php if ($current_role === 'Parent'): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'parent.php' ? 'active' : '' ?>" href="parent.php">My Child</a>
</li>
<?php endif; ?>
</ul>
<div class="d-flex align-items-center">
<span class="text-white me-3 d-none d-md-inline small">Role: <strong><?= $current_role ?></strong></span>
<div class="dropdown">
<button class="btn btn-outline-light btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
Switch Role
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item <?= $current_role == 'Super Admin' ? 'active' : '' ?>" href="super-admin.php">Super Admin</a></li>
<li><a class="dropdown-item <?= $current_role == 'Admin' ? 'active' : '' ?>" href="admin.php">School Admin</a></li>
<li><a class="dropdown-item <?= $current_role == 'Teacher' ? 'active' : '' ?>" href="index.php">Teacher</a></li>
<li><a class="dropdown-item <?= $current_role == 'Parent' ? 'active' : '' ?>" href="parent.php">Parent</a></li>
</ul>
</div>
</div>
</div>
</div>
</nav>

View File

@ -4,6 +4,8 @@ require_once __DIR__ . '/db/config.php';
$db = db();
$date = date('Y-m-d');
$message = '';
$current_role = 'Teacher';
$pageTitle = 'Teacher Dashboard | Township Schools Platform';
// Handle Attendance Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['attendance'])) {
@ -45,51 +47,8 @@ foreach ($learners as $l) {
}
$presence_rate = $total_learners > 0 ? round(($present_count / $total_learners) * 100) : 0;
// Project Info
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Education Ecosystem Platform for South African Schools';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
include 'includes/header.php';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Teacher Dashboard | Township Schools Platform</title>
<?php if ($projectDescription): ?>
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
</head>
<body>
<nav class="navbar navbar-dark mb-4">
<div class="container">
<a class="navbar-brand" href="#">
<i class="bi bi-book-half me-2"></i>
Township Schools Platform
</a>
<div class="d-flex align-items-center">
<span class="text-white me-3 d-none d-md-inline">Role: <strong>Teacher</strong></span>
<div class="dropdown">
<button class="btn btn-outline-light btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
Switch Role
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item active" href="#">Teacher</a></li>
<li><a class="dropdown-item" href="#">Admin (Soon)</a></li>
<li><a class="dropdown-item" href="#">Parent (Soon)</a></li>
</ul>
</div>
</div>
</div>
</nav>
<div class="container pb-5">
<div class="row mb-4">
@ -185,18 +144,6 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
</div>
</div>
<footer class="bg-white border-top py-4 mt-5">
<div class="container text-center">
<p class="text-muted small mb-0">&copy; <?= date('Y') ?> Township Schools Platform. Optimized for low-bandwidth.</p>
<div class="mt-2">
<span class="badge rounded-pill bg-light text-dark border"><i class="bi bi-wifi-off me-1"></i> Offline Capable</span>
<span class="badge rounded-pill bg-light text-dark border ms-2"><i class="bi bi-shield-check me-1"></i> POPIA Compliant</span>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?= time() ?>"></script>
<script>
// Simple Search Filter
document.getElementById('learnerSearch').addEventListener('keyup', function() {
@ -213,5 +160,5 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
}
});
</script>
</body>
</html>
<?php include 'includes/footer.php'; ?>

134
learners.php Normal file
View File

@ -0,0 +1,134 @@
<?php
require_once __DIR__ . '/db/config.php';
$db = db();
$current_role = 'Admin';
$pageTitle = 'Manage Learners | Township Schools Platform';
$message = '';
// Handle Add Learner
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'add') {
try {
$stmt = $db->prepare("INSERT INTO learners (full_name, grade, student_id) VALUES (?, ?, ?)");
$stmt->execute([
$_POST['full_name'],
$_POST['grade'],
$_POST['student_id']
]);
$message = "Learner registered successfully.";
} catch (Exception $e) {
$message = "Error: " . $e->getMessage();
}
} elseif ($_POST['action'] === 'delete') {
try {
$stmt = $db->prepare("DELETE FROM learners WHERE id = ?");
$stmt->execute([$_POST['id']]);
$message = "Learner record deleted.";
} catch (Exception $e) {
$message = "Error: " . $e->getMessage();
}
}
}
// Fetch Learners
$learners = $db->query("SELECT * FROM learners ORDER BY full_name ASC")->fetchAll();
include 'includes/header.php';
?>
<div class="container pb-5">
<div class="row mb-4 align-items-center">
<div class="col">
<h2 class="h4 mb-1">Learner Management</h2>
<p class="text-muted small">Total: <strong><?= count($learners) ?> learners</strong></p>
</div>
<div class="col-auto">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addLearnerModal">
<i class="bi bi-person-plus me-2"></i> Register New Learner
</button>
</div>
</div>
<?php if ($message): ?>
<div class="alert alert-info alert-dismissible fade show" role="alert">
<?= htmlspecialchars($message) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<div class="card">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th class="ps-4">Full Name</th>
<th>Grade</th>
<th>Student ID</th>
<th class="text-end pe-4">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($learners as $l): ?>
<tr>
<td class="ps-4">
<strong><?= htmlspecialchars($l['full_name']) ?></strong>
</td>
<td><?= htmlspecialchars($l['grade']) ?></td>
<td><span class="badge bg-light text-dark"><?= htmlspecialchars($l['student_id']) ?></span></td>
<td class="text-end pe-4">
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this learner?');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $l['id'] ?>">
<button type="submit" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Add Learner Modal -->
<div class="modal fade" id="addLearnerModal" tabindex="-1">
<div class="modal-dialog">
<form method="POST" class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Register New Learner</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="action" value="add">
<div class="mb-3">
<label class="form-label">Full Name</label>
<input type="text" name="full_name" class="form-control" required placeholder="e.g. Sipho Zulu">
</div>
<div class="mb-3">
<label class="form-label">Grade</label>
<select name="grade" class="form-select" required>
<option value="">Select Grade</option>
<option value="Grade 8">Grade 8</option>
<option value="Grade 9">Grade 9</option>
<option value="Grade 10">Grade 10</option>
<option value="Grade 11">Grade 11</option>
<option value="Grade 12">Grade 12</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Student ID / National ID</label>
<input type="text" name="student_id" class="form-control" required placeholder="e.g. STU10023">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Register Learner</button>
</div>
</form>
</div>
</div>
<?php include 'includes/footer.php'; ?>

134
parent.php Normal file
View File

@ -0,0 +1,134 @@
<?php
require_once __DIR__ . '/db/config.php';
$db = db();
$current_role = 'Parent';
$pageTitle = 'Parent Portal | Township Schools Platform';
$learner = null;
$attendance_history = [];
$search_id = $_GET['student_id'] ?? '';
if ($search_id) {
// Fetch Learner
$stmt = $db->prepare("SELECT * FROM learners WHERE student_id = ?");
$stmt->execute([$search_id]);
$learner = $stmt->fetch();
if ($learner) {
// Fetch Attendance History (Last 30 days)
$stmt = $db->prepare("SELECT * FROM attendance WHERE learner_id = ? ORDER BY date DESC LIMIT 30");
$stmt->execute([$learner['id']]);
$attendance_history = $stmt->fetchAll();
}
}
include 'includes/header.php';
?>
<div class="container pb-5">
<div class="row mb-4">
<div class="col-12">
<h2 class="h4 mb-1">Parent Engagement Portal</h2>
<p class="text-muted small">Stay updated on your child's progress with minimal data usage.</p>
</div>
</div>
<div class="row g-4">
<div class="col-md-4">
<div class="card p-4">
<h5 class="mb-3">Find My Child</h5>
<form method="GET">
<div class="mb-3">
<label class="form-label small">Student ID / National ID</label>
<input type="text" name="student_id" class="form-control" placeholder="e.g. STU10023" value="<?= htmlspecialchars($search_id) ?>" required>
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-search me-2"></i> View Progress
</button>
</form>
</div>
<?php if ($search_id && !$learner): ?>
<div class="alert alert-warning mt-3">
<i class="bi bi-exclamation-triangle me-2"></i> No learner found with ID <strong><?= htmlspecialchars($search_id) ?></strong>. Please contact the school office.
</div>
<?php endif; ?>
</div>
<div class="col-md-8">
<?php if ($learner): ?>
<div class="card mb-4">
<div class="card-header bg-white py-3">
<h5 class="mb-0">Learner Profile: <strong><?= htmlspecialchars($learner['full_name']) ?></strong></h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-4">
<p class="text-muted small mb-1">Grade</p>
<h6><?= htmlspecialchars($learner['grade']) ?></h6>
</div>
<div class="col-4">
<p class="text-muted small mb-1">Attendance</p>
<?php
$total = count($attendance_history);
$present = 0;
foreach ($attendance_history as $a) if ($a['status'] === 'present') $present++;
$rate = $total > 0 ? round(($present / $total) * 100) : 0;
?>
<h6><?= $rate ?>%</h6>
</div>
<div class="col-4">
<p class="text-muted small mb-1">Status</p>
<span class="badge bg-success">Enrolled</span>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header bg-white py-3">
<h5 class="mb-0">Recent Attendance</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th class="ps-4">Date</th>
<th class="text-end pe-4">Status</th>
</tr>
</thead>
<tbody>
<?php if (empty($attendance_history)): ?>
<tr>
<td colspan="2" class="text-center py-4 text-muted">No attendance records found for the last 30 days.</td>
</tr>
<?php endif; ?>
<?php foreach ($attendance_history as $record): ?>
<tr>
<td class="ps-4"><?= date('l, d M Y', strtotime($record['date'])) ?></td>
<td class="text-end pe-4">
<?php if ($record['status'] === 'present'): ?>
<span class="badge bg-success">Present</span>
<?php else: ?>
<span class="badge bg-danger">Absent</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php else: ?>
<div class="h-100 d-flex flex-column align-items-center justify-content-center text-muted py-5 border rounded bg-white">
<i class="bi bi-person-badge mb-3" style="font-size: 3rem; opacity: 0.3;"></i>
<p>Please enter a Student ID to view details.</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php include 'includes/footer.php'; ?>

154
super-admin.php Normal file
View File

@ -0,0 +1,154 @@
<?php
require_once __DIR__ . '/db/config.php';
$db = db();
$current_role = 'Super Admin';
$pageTitle = 'Super Admin | Township Schools Platform';
$message = '';
// Handle School Addition
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'add_school') {
try {
$stmt = $db->prepare("INSERT INTO schools (name, province, district) VALUES (?, ?, ?)");
$stmt->execute([$_POST['name'], $_POST['province'], $_POST['district']]);
$message = "New school onboarded successfully.";
} catch (Exception $e) {
$message = "Error: " . $e->getMessage();
}
}
}
// Stats
$total_schools = $db->query("SELECT COUNT(*) FROM schools")->fetchColumn();
$total_learners = $db->query("SELECT COUNT(*) FROM learners")->fetchColumn();
$schools = $db->query("SELECT * FROM schools ORDER BY name ASC")->fetchAll();
include 'includes/header.php';
?>
<div class="container pb-5">
<div class="row mb-4">
<div class="col">
<h2 class="h4 mb-1">Super Admin Dashboard</h2>
<p class="text-muted small">Platform-wide oversight and school management</p>
</div>
<div class="col-auto">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addSchoolModal">
<i class="bi bi-plus-circle me-2"></i> Onboard New School
</button>
</div>
</div>
<?php if ($message): ?>
<div class="alert alert-info alert-dismissible fade show" role="alert">
<?= htmlspecialchars($message) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<!-- Platform Stats -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card p-3 stats-card">
<p>Total Schools</p>
<h3><?= $total_schools ?></h3>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 stats-card" style="border-left-color: #0288D1;">
<p>Total Learners</p>
<h3><?= $total_learners ?></h3>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 stats-card" style="border-left-color: #2E7D32;">
<p>System Uptime</p>
<h3>99.9%</h3>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 stats-card" style="border-left-color: #FFA000;">
<p>Storage Used</p>
<h3>12 MB</h3>
</div>
</div>
</div>
<div class="card">
<div class="card-header bg-white py-3">
<h5 class="mb-0">Managed Schools</h5>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th class="ps-4">School Name</th>
<th>Province</th>
<th>District</th>
<th>Status</th>
<th class="text-end pe-4">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($schools as $school): ?>
<tr>
<td class="ps-4">
<strong><?= htmlspecialchars($school['name']) ?></strong>
</td>
<td><?= htmlspecialchars($school['province']) ?></td>
<td><?= htmlspecialchars($school['district']) ?></td>
<td><span class="badge bg-success-subtle text-success">Active</span></td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-outline-primary">Manage</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Add School Modal -->
<div class="modal fade" id="addSchoolModal" tabindex="-1">
<div class="modal-dialog">
<form method="POST" class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Onboard New School</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" name="action" value="add_school">
<div class="mb-3">
<label class="form-label">School Name</label>
<input type="text" name="name" class="form-control" required placeholder="e.g. Luthuli High School">
</div>
<div class="mb-3">
<label class="form-label">Province</label>
<select name="province" class="form-select" required>
<option value="Gauteng">Gauteng</option>
<option value="Western Cape">Western Cape</option>
<option value="Eastern Cape">Eastern Cape</option>
<option value="KwaZulu-Natal">KwaZulu-Natal</option>
<option value="Free State">Free State</option>
<option value="Limpopo">Limpopo</option>
<option value="Mpumalanga">Mpumalanga</option>
<option value="North West">North West</option>
<option value="Northern Cape">Northern Cape</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">District</label>
<input type="text" name="district" class="form-control" required placeholder="e.g. Sedibeng East">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Onboard School</button>
</div>
</form>
</div>
</div>
<?php include 'includes/footer.php'; ?>

49
sw.js
View File

@ -1,25 +1,58 @@
const CACHE_NAME = 'township-schools-v1';
const ASSETS_TO_CACHE = [
const CACHE_NAME = 'township-schools-v2';
const STATIC_ASSETS = [
'/',
'/index.php',
'/admin.php',
'/learners.php',
'/assets/css/custom.css',
'/assets/js/main.js',
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css',
'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css'
'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css',
'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'
];
// Install Event
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(ASSETS_TO_CACHE);
console.log('Caching static assets');
return cache.addAll(STATIC_ASSETS);
})
);
self.skipWaiting();
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
// Activate Event
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => {
return Promise.all(
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
);
})
);
self.clients.claim();
});
// Fetch Event (Stale-While-Revalidate)
self.addEventListener('fetch', (event) => {
// Only handle GET requests
if (event.request.method !== 'GET') return;
event.respondWith(
caches.open(CACHE_NAME).then((cache) => {
return cache.match(event.request).then((cachedResponse) => {
const fetchedResponse = fetch(event.request).then((networkResponse) => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
}).catch(() => {
// If network fails and no cache, maybe return a fallback page
return cachedResponse;
});
return cachedResponse || fetchedResponse;
});
})
);
});