This commit is contained in:
Flatlogic Bot 2026-01-30 14:44:46 +00:00
parent ef3a1cc12c
commit d987612018
4 changed files with 383 additions and 17 deletions

View File

@ -27,10 +27,17 @@ $is_logged_in = isset($_SESSION['user_id']);
<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() ?>">
<style>
@media print {
.no-print { display: none !important; }
.card { border: none !important; box-shadow: none !important; }
body { background: white !important; }
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark mb-4 shadow-sm">
<nav class="navbar navbar-expand-lg navbar-dark mb-4 shadow-sm no-print">
<div class="container">
<a class="navbar-brand fw-bold" href="index.php">
<i class="bi bi-book-half me-2"></i>
@ -54,6 +61,9 @@ $is_logged_in = isset($_SESSION['user_id']);
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'assessments.php' ? 'active' : '' ?>" href="assessments.php">Assessments</a>
</li>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'reports.php' ? 'active' : '' ?>" href="reports.php">Reports</a>
</li>
<?php endif; ?>
<?php if ($user_role === 'Admin'): ?>
@ -63,6 +73,9 @@ $is_logged_in = isset($_SESSION['user_id']);
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'learners.php' ? 'active' : '' ?>" href="learners.php">Learners</a>
</li>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'reports.php' ? 'active' : '' ?>" href="reports.php">Reports</a>
</li>
<?php endif; ?>
<?php if ($user_role === 'Parent' || $user_role === 'Guest'): ?>
@ -76,12 +89,12 @@ $is_logged_in = isset($_SESSION['user_id']);
<span class="text-white me-3 d-none d-md-inline small">
Logged in as: <strong><?= htmlspecialchars($_SESSION['email']) ?></strong> (<?= $user_role ?>)
</span>
<a href="logout.php" class="btn btn-outline-light btn-sm">Logout</a>
<a href="logout.php" class="btn btn-outline-light btn-sm no-print">Logout</a>
<?php else: ?>
<a href="login.php" class="btn btn-outline-light btn-sm me-2">Login</a>
<a href="register.php" class="btn btn-warning btn-sm text-dark fw-bold">Register</a>
<a href="login.php" class="btn btn-outline-light btn-sm me-2 no-print">Login</a>
<a href="register.php" class="btn btn-warning btn-sm text-dark fw-bold no-print">Register</a>
<?php endif; ?>
</div>
</div>
</div>
</nav>
</nav>

View File

@ -7,6 +7,7 @@ $pageTitle = 'Parent Portal | SOMS';
$learner = null;
$school = null;
$attendance_history = [];
$assessment_results = [];
$search_id = $_GET['student_id'] ?? '';
if ($search_id) {
@ -23,6 +24,17 @@ if ($search_id) {
$stmt = $db->prepare("SELECT * FROM attendance WHERE learner_id = ? ORDER BY date DESC LIMIT 30");
$stmt->execute([$learner['id']]);
$attendance_history = $stmt->fetchAll();
// Fetch Assessment Results
$stmt = $db->prepare("
SELECT a.name, a.type, a.total_marks, m.marks_obtained, a.created_at
FROM marks m
JOIN assessments a ON m.assessment_id = a.id
WHERE m.learner_id = ?
ORDER BY a.created_at DESC
");
$stmt->execute([$learner['id']]);
$assessment_results = $stmt->fetchAll();
}
}
@ -68,11 +80,11 @@ include 'includes/header.php';
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-4">
<div class="col-4 border-end">
<p class="text-muted small mb-1">Grade</p>
<h6 class="fw-bold"><?= htmlspecialchars($learner['grade']) ?></h6>
</div>
<div class="col-4">
<div class="col-4 border-end">
<p class="text-muted small mb-1">Attendance (30d)</p>
<?php
$total = count($attendance_history);
@ -83,18 +95,34 @@ include 'includes/header.php';
<h6 class="fw-bold"><?= $rate ?>%</h6>
</div>
<div class="col-4">
<p class="text-muted small mb-1">Status</p>
<span class="badge bg-success">Enrolled</span>
<p class="text-muted small mb-1">Academic Avg</p>
<?php
$total_perc = 0;
$count = 0;
foreach ($assessment_results as $res) {
$total_perc += ($res['marks_obtained'] / $res['total_marks']) * 100;
$count++;
}
$avg = $count > 0 ? round($total_perc / $count, 1) : 0;
?>
<h6 class="fw-bold text-primary"><?= $avg ?>%</h6>
</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-header bg-white py-3">
<h5 class="mb-0 fw-bold">Recent Attendance</h5>
</div>
<div class="card-body p-0">
<!-- Tabs for Attendance vs Results -->
<ul class="nav nav-tabs border-0 mb-3" id="parentTabs" role="tablist">
<li class="nav-item">
<button class="nav-link active border-0 fw-bold" id="attendance-tab" data-bs-toggle="tab" data-bs-target="#attendance" type="button">Attendance</button>
</li>
<li class="nav-item">
<button class="nav-link border-0 fw-bold" id="results-tab" data-bs-toggle="tab" data-bs-target="#results" type="button">Academic Results</button>
</li>
</ul>
<div class="tab-content bg-white rounded shadow-sm">
<div class="tab-pane fade show active" id="attendance">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="bg-light">
@ -125,6 +153,40 @@ include 'includes/header.php';
</table>
</div>
</div>
<div class="tab-pane fade" id="results">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">Assessment</th>
<th>Score</th>
<th class="text-end pe-4">Percentage</th>
</tr>
</thead>
<tbody>
<?php if (empty($assessment_results)): ?>
<tr>
<td colspan="3" class="text-center py-4 text-muted">No assessment results recorded yet.</td>
</tr>
<?php endif; ?>
<?php foreach ($assessment_results as $res):
$p = round(($res['marks_obtained'] / $res['total_marks']) * 100, 1);
?>
<tr>
<td class="ps-4">
<strong><?= htmlspecialchars($res['name']) ?></strong>
<br><small class="text-muted"><?= htmlspecialchars($res['type']) ?> (<?= date('d M Y', strtotime($res['created_at'])) ?>)</small>
</td>
<td><?= $res['marks_obtained'] ?> / <?= $res['total_marks'] ?></td>
<td class="text-end pe-4 fw-bold <?= $p >= 50 ? 'text-success' : 'text-danger' ?>">
<?= $p ?>%
</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 shadow-sm">
@ -136,4 +198,4 @@ include 'includes/header.php';
</div>
</div>
<?php include 'includes/footer.php'; ?>
<?php include 'includes/footer.php'; ?>

290
reports.php Normal file
View File

@ -0,0 +1,290 @@
<?php
require_once __DIR__ . '/db/config.php';
session_start();
// Auth Check
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['role'], ['Teacher', 'Admin'])) {
header('Location: login.php');
exit;
}
$db = db();
$school_id = $_SESSION['school_id'];
$pageTitle = 'Reporting Center | SOMS';
// Fetch school info for report header
$stmt = $db->prepare("SELECT * FROM schools WHERE id = ?");
$stmt->execute([$school_id]);
$school = $stmt->fetch();
$report_type = $_GET['type'] ?? null;
$learner_id = $_GET['learner_id'] ?? null;
$assessment_id = $_GET['assessment_id'] ?? null;
$report_data = null;
$learner_info = null;
$assessment_info = null;
if ($report_type === 'learner' && $learner_id) {
// Fetch learner transcript
$stmt = $db->prepare("SELECT * FROM learners WHERE id = ? AND school_id = ?");
$stmt->execute([$learner_id, $school_id]);
$learner_info = $stmt->fetch();
if ($learner_info) {
$stmt = $db->prepare("
SELECT a.name, a.type, a.total_marks, m.marks_obtained, a.created_at
FROM marks m
JOIN assessments a ON m.assessment_id = a.id
WHERE m.learner_id = ?
ORDER BY a.created_at DESC
");
$stmt->execute([$learner_id]);
$report_data = $stmt->fetchAll();
}
} elseif ($report_type === 'class' && $assessment_id) {
// Fetch class report for an assessment
$stmt = $db->prepare("SELECT * FROM assessments WHERE id = ? AND school_id = ?");
$stmt->execute([$assessment_id, $school_id]);
$assessment_info = $stmt->fetch();
if ($assessment_info) {
$stmt = $db->prepare("
SELECT l.full_name, l.student_id, m.marks_obtained
FROM learners l
LEFT JOIN marks m ON l.id = m.learner_id AND m.assessment_id = ?
WHERE l.school_id = ? AND l.grade = ?
ORDER BY l.full_name ASC
");
$stmt->execute([$assessment_id, $school_id, $assessment_info['grade']]);
$report_data = $stmt->fetchAll();
}
}
// Fetch lists for selection
$stmt = $db->prepare("SELECT id, full_name, grade, student_id FROM learners WHERE school_id = ? ORDER BY grade, full_name");
$stmt->execute([$school_id]);
$all_learners = $stmt->fetchAll();
$stmt = $db->prepare("SELECT id, name, grade, type FROM assessments WHERE school_id = ? ORDER BY created_at DESC");
$stmt->execute([$school_id]);
$all_assessments = $stmt->fetchAll();
include 'includes/header.php';
?>
<div class="container pb-5">
<div class="row mb-4 no-print">
<div class="col-12">
<h2 class="h4 mb-1">Reporting Center</h2>
<p class="text-muted small">Generate academic transcripts and class performance reports</p>
</div>
</div>
<div class="row g-4 no-print">
<!-- Learner Report Selection -->
<div class="col-md-6">
<div class="card shadow-sm border-0">
<div class="card-body p-4 text-center">
<i class="bi bi-person-badge text-primary mb-3" style="font-size: 2.5rem;"></i>
<h5 class="fw-bold">Learner Transcript</h5>
<p class="text-muted small">Academic history for a specific student</p>
<form method="GET" class="mt-3">
<input type="hidden" name="type" value="learner">
<div class="mb-3">
<select name="learner_id" class="form-select" required>
<option value="">Select Learner</option>
<?php foreach ($all_learners as $l): ?>
<option value="<?= $l['id'] ?>" <?= $learner_id == $l['id'] ? 'selected' : '' ?>>
Grade <?= $l['grade'] ?> - <?= htmlspecialchars($l['full_name']) ?> (<?= $l['student_id'] ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary w-100">Generate Report</button>
</form>
</div>
</div>
</div>
<!-- Class Report Selection -->
<div class="col-md-6">
<div class="card shadow-sm border-0">
<div class="card-body p-4 text-center">
<i class="bi bi-people text-secondary mb-3" style="font-size: 2.5rem;"></i>
<h5 class="fw-bold">Class Performance</h5>
<p class="text-muted small">Full class results for a specific assessment</p>
<form method="GET" class="mt-3">
<input type="hidden" name="type" value="class">
<div class="mb-3">
<select name="assessment_id" class="form-select" required>
<option value="">Select Assessment</option>
<?php foreach ($all_assessments as $a): ?>
<option value="<?= $a['id'] ?>" <?= $assessment_id == $a['id'] ? 'selected' : '' ?>>
Grade <?= $a['grade'] ?> - <?= htmlspecialchars($a['name']) ?> (<?= $a['type'] ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-secondary w-100">Generate Report</button>
</form>
</div>
</div>
</div>
</div>
<?php if ($report_type && $report_data): ?>
<div class="mt-5 report-container">
<div class="card shadow-sm border-0">
<div class="card-body p-5">
<!-- Report Header -->
<div class="d-flex justify-content-between align-items-start mb-5 border-bottom pb-4">
<div>
<h1 class="h3 fw-bold mb-1"><?= htmlspecialchars($school['name'] ?? 'School Report') ?></h1>
<p class="text-muted mb-0"><?= htmlspecialchars($school['location'] ?? 'South Africa') ?></p>
<p class="text-muted small">Generated on: <?= date('d F Y, H:i') ?></p>
</div>
<div class="text-end no-print">
<button onclick="window.print()" class="btn btn-dark shadow-sm">
<i class="bi bi-printer me-2"></i> Print to PDF
</button>
</div>
</div>
<?php if ($report_type === 'learner' && $learner_info): ?>
<div class="mb-4">
<h4 class="fw-bold text-center mb-4">ACADEMIC TRANSCRIPT</h4>
<div class="row">
<div class="col-6">
<p class="mb-1"><strong>Name:</strong> <?= htmlspecialchars($learner_info['full_name']) ?></p>
<p class="mb-1"><strong>Student ID:</strong> <?= htmlspecialchars($learner_info['student_id']) ?></p>
</div>
<div class="col-6 text-end">
<p class="mb-1"><strong>Grade:</strong> <?= htmlspecialchars($learner_info['grade']) ?></p>
<p class="mb-1"><strong>Status:</strong> Active</p>
</div>
</div>
</div>
<table class="table table-bordered mt-4">
<thead class="bg-light">
<tr>
<th>Date</th>
<th>Assessment</th>
<th>Type</th>
<th class="text-center">Score</th>
<th class="text-center">Percentage</th>
</tr>
</thead>
<tbody>
<?php
$total_perc = 0;
$count = 0;
foreach ($report_data as $row):
$perc = ($row['marks_obtained'] / $row['total_marks']) * 100;
$total_perc += $perc;
$count++;
?>
<tr>
<td><?= date('d M Y', strtotime($row['created_at'])) ?></td>
<td><?= htmlspecialchars($row['name']) ?></td>
<td><?= htmlspecialchars($row['type']) ?></td>
<td class="text-center"><?= $row['marks_obtained'] ?> / <?= $row['total_marks'] ?></td>
<td class="text-center"><strong><?= round($perc, 1) ?>%</strong></td>
</tr>
<?php endforeach; ?>
</tbody>
<?php if ($count > 0): ?>
<tfoot class="bg-light fw-bold">
<tr>
<td colspan="4" class="text-end">Average Performance:</td>
<td class="text-center"><?= round($total_perc / $count, 1) ?>%</td>
</tr>
</tfoot>
<?php endif; ?>
</table>
<?php endif; ?>
<?php if ($report_type === 'class' && $assessment_info): ?>
<div class="mb-4">
<h4 class="fw-bold text-center mb-4">CLASS PERFORMANCE REPORT</h4>
<div class="text-center mb-4">
<h5 class="mb-1"><?= htmlspecialchars($assessment_info['name']) ?></h5>
<p class="text-muted">Grade <?= htmlspecialchars($assessment_info['grade']) ?> | <?= htmlspecialchars($assessment_info['type']) ?> | Max Marks: <?= $assessment_info['total_marks'] ?></p>
</div>
</div>
<table class="table table-bordered mt-4">
<thead class="bg-light">
<tr>
<th>Student ID</th>
<th>Learner Name</th>
<th class="text-center">Marks Obtained</th>
<th class="text-center">Percentage</th>
<th class="text-center">Status</th>
</tr>
</thead>
<tbody>
<?php
$total_marks = 0;
$present_count = 0;
foreach ($report_data as $row):
$has_marks = $row['marks_obtained'] !== null;
if ($has_marks) {
$perc = ($row['marks_obtained'] / $assessment_info['total_marks']) * 100;
$total_marks += $perc;
$present_count++;
}
?>
<tr>
<td><?= htmlspecialchars($row['student_id']) ?></td>
<td><?= htmlspecialchars($row['full_name']) ?></td>
<td class="text-center"><?= $has_marks ? $row['marks_obtained'] : '<span class="text-muted">N/A</span>' ?></td>
<td class="text-center"><?= $has_marks ? '<strong>'.round($perc, 1).'%</strong>' : '-' ?></td>
<td class="text-center">
<?php if ($has_marks): ?>
<span class="badge <?= $perc >= 50 ? 'bg-success' : 'bg-danger' ?> no-print">
<?= $perc >= 50 ? 'Pass' : 'Fail' ?>
</span>
<span class="d-none d-print-inline">
<?= $perc >= 50 ? 'Pass' : 'Fail' ?>
</span>
<?php else: ?>
<span class="text-muted">Absent/Pending</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<?php if ($present_count > 0): ?>
<tfoot class="bg-light fw-bold">
<tr>
<td colspan="3" class="text-end">Class Average:</td>
<td class="text-center"><?= round($total_marks / $present_count, 1) ?>%</td>
<td></td>
</tr>
</tfoot>
<?php endif; ?>
</table>
<?php endif; ?>
<div class="mt-5 pt-4 border-top">
<div class="row">
<div class="col-4 text-center">
<div class="border-top pt-2 mt-4" style="border-top-style: dashed !important;">
<small class="text-muted">Teacher Signature</small>
</div>
</div>
<div class="col-4"></div>
<div class="col-4 text-center">
<div class="border-top pt-2 mt-4" style="border-top-style: dashed !important;">
<small class="text-muted">Principal Signature</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php include 'includes/footer.php'; ?>

5
sw.js
View File

@ -1,4 +1,4 @@
const CACHE_NAME = 'township-schools-v3';
const CACHE_NAME = 'township-schools-v4';
const STATIC_ASSETS = [
'/',
'/index.php',
@ -8,6 +8,7 @@ const STATIC_ASSETS = [
'/admin.php',
'/learners.php',
'/assessments.php',
'/reports.php',
'/parent.php',
'/assets/css/custom.css',
'/assets/js/main.js',
@ -62,4 +63,4 @@ self.addEventListener('fetch', (event) => {
});
})
);
});
});