v2
This commit is contained in:
parent
14d9c5e808
commit
e29ef2dff4
148
admin.php
Normal file
148
admin.php
Normal 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'; ?>
|
||||
@ -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
14
includes/footer.php
Normal 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">© <?= 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
80
includes/header.php
Normal 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>
|
||||
63
index.php
63
index.php
@ -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">© <?= 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
134
learners.php
Normal 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
134
parent.php
Normal 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
154
super-admin.php
Normal 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
49
sw.js
@ -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;
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user