diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..02c9192 --- /dev/null +++ b/admin.php @@ -0,0 +1,148 @@ +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'; +?> + +
+
+
+

School Admin Dashboard

+

Overview of school operations

+
+
+ + +
+
+
+

Total Learners

+

+
+
+
+
+

Today's Presence

+

%

+
+
+
+
+

Total Staff

+

12

+
+
+
+
+

SGB Meetings

+

1

+
+
+
+ +
+
+
+
+
Attendance Analytics by Grade (Today)
+
+
+ + 0 ? round(($stat['present'] / $stat['total']) * 100) : 0; + $bar_color = $rate > 80 ? 'bg-success' : ($rate > 50 ? 'bg-warning' : 'bg-danger'); + ?> +
+
+ + % (/) +
+
+
+
+
+ +
+
+ +
+
+
Recent Activity
+
+
+
    +
  • +
    +
    +
    Daily Register Completed
    + Grade 10A - Mrs. Mdluli +
    + 10 mins ago +
    +
  • +
  • +
    +
    +
    New Learner Registered
    + Sipho Zulu - Grade 8B +
    + 1 hour ago +
    +
  • +
+
+
+
+
+
+
+
Quick Actions
+
+
+
+ + Register New Learner + +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index b1aae36..8cecb87 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -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 = ' 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 = ' 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); }); }); }); -}); +}); \ No newline at end of file diff --git a/includes/footer.php b/includes/footer.php new file mode 100644 index 0000000..1af3deb --- /dev/null +++ b/includes/footer.php @@ -0,0 +1,14 @@ + + + + + + diff --git a/includes/header.php b/includes/header.php new file mode 100644 index 0000000..979d878 --- /dev/null +++ b/includes/header.php @@ -0,0 +1,80 @@ + + + + + + + <?= $pageTitle ?? 'Township Schools Platform' ?> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.php b/index.php index a55506e..bfe9452 100644 --- a/index.php +++ b/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'; ?> - - - - - - Teacher Dashboard | Township Schools Platform - - - - - - - - - - - - - - -
@@ -185,18 +144,6 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
- - - - - - \ No newline at end of file + + diff --git a/learners.php b/learners.php new file mode 100644 index 0000000..309c87e --- /dev/null +++ b/learners.php @@ -0,0 +1,134 @@ +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'; +?> + +
+
+
+

Learner Management

+

Total: learners

+
+
+ +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
Full NameGradeStudent IDActions
+ + +
+ + + +
+
+
+
+
+ + + + + diff --git a/parent.php b/parent.php new file mode 100644 index 0000000..bf7ff9f --- /dev/null +++ b/parent.php @@ -0,0 +1,134 @@ +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'; +?> + +
+
+
+

Parent Engagement Portal

+

Stay updated on your child's progress with minimal data usage.

+
+
+ +
+
+
+
Find My Child
+
+
+ + +
+ +
+
+ + +
+ No learner found with ID . Please contact the school office. +
+ +
+ +
+ +
+
+
Learner Profile:
+
+
+
+
+

Grade

+
+
+
+

Attendance

+ 0 ? round(($present / $total) * 100) : 0; + ?> +
%
+
+
+

Status

+ Enrolled +
+
+
+
+ +
+
+
Recent Attendance
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
DateStatus
No attendance records found for the last 30 days.
+ + Present + + Absent + +
+
+
+
+ +
+ +

Please enter a Student ID to view details.

+
+ +
+
+
+ + diff --git a/super-admin.php b/super-admin.php new file mode 100644 index 0000000..4e970b0 --- /dev/null +++ b/super-admin.php @@ -0,0 +1,154 @@ +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'; +?> + +
+
+
+

Super Admin Dashboard

+

Platform-wide oversight and school management

+
+
+ +
+
+ + + + + + +
+
+
+

Total Schools

+

+
+
+
+
+

Total Learners

+

+
+
+
+
+

System Uptime

+

99.9%

+
+
+
+
+

Storage Used

+

12 MB

+
+
+
+ +
+
+
Managed Schools
+
+
+ + + + + + + + + + + + + + + + + + + + + +
School NameProvinceDistrictStatusActions
+ + Active + +
+
+
+
+ + + + + diff --git a/sw.js b/sw.js index 114e217..ad17f29 100644 --- a/sw.js +++ b/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; + }); + }) + ); +}); \ No newline at end of file