recreate project

This commit is contained in:
root
2026-02-10 22:11:06 -05:00
commit 663c0cdbda
10149 changed files with 1379710 additions and 0 deletions
+327
View File
@@ -0,0 +1,327 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Al Rahma Sunday School</title>
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<meta content="" name="keywords" />
<meta content="" name="description" />
<!-- Favicon -->
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
<!-- Google Web Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;600&family=Inter:wght@600&family=Lobster+Two:wght@700&display=swap"
rel="stylesheet"
/>
<!-- Icon Font Stylesheet -->
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.0/css/all.min.css"
rel="stylesheet"
/>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css"
rel="stylesheet"
/>
<!-- Libraries Stylesheet -->
<link href="lib/animate/animate.min.css" rel="stylesheet" />
<link href="lib/owlcarousel/assets/owl.carousel.min.css" rel="stylesheet" />
<!-- Customized Bootstrap Stylesheet -->
<link href="css/bootstrap.min.css" rel="stylesheet" />
<!-- Template Stylesheet -->
<link href="assets/css/style.css" rel="stylesheet" />
</head>
<body>
<div class="container-xxl bg-white p-0">
<!-- Spinner Start -->
<div
id="spinner"
class="show bg-white position-fixed translate-middle w-100 vh-100 top-50 start-50 d-flex align-items-center justify-content-center"
>
<div
class="spinner-border text-primary"
style="width: 3rem; height: 3rem"
role="status"
>
<span class="sr-only">Loading...</span>
</div>
</div>
<!-- Spinner End -->
<!-- Navbar Start -->
<nav
class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0"
>
<a
href="<?= base_url('/') ?>"
class="navbar-brand d-flex align-items-center"
>
<img
src="<?= base_url('images/logo.png') ?>"
alt="Al Rahma Sunday School"
style="height: 40px"
/>
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button
type="button"
class="navbar-toggler"
data-bs-toggle="collapse"
data-bs-target="#navbarCollapse"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav mx-auto">
<a href="index.html" class="nav-item nav-link">Home</a>
<a href="about.html" class="nav-item nav-link">About Us</a>
<a href="classes.html" class="nav-item nav-link">Classes</a>
<div class="nav-item dropdown">
<a
href="#"
class="nav-link dropdown-toggle active"
data-bs-toggle="dropdown"
>Pages</a
>
<div class="dropdown-menu rounded-0 rounded-bottom border-0 m-0">
<a href="facility.html" class="dropdown-item"
>School Facilities</a
>
<a href="team.html" class="dropdown-item">Popular Teachers</a>
<a href="call-to-action.html" class="dropdown-item"
>Become A Teacher or Admins</a
>
<a href="appointment.html" class="dropdown-item"
>Make Appointment</a
>
<a href="testimonial.html" class="dropdown-item">Testimonial</a>
<a href="404.html" class="dropdown-item active">404 Error</a>
</div>
</div>
<a href="contact.html" class="nav-item nav-link">Contact Us</a>
</div>
<a
href="/register"
class="btn btn-primary rounded-pill px-3 d-none d-lg-block"
>Register <i class="fa fa-arrow-right ms-3"></i
></a>
<a
href="login-link"
class="btn btn-primary rounded-pill px-3 d-none d-lg-block"
>Login<i class="fa fa-arrow-right ms-3"></i
></a>
</div>
</nav>
<!-- Navbar End -->
<!-- Page Header End -->
<div class="container-xxl py-5 page-header position-relative mb-5">
<div class="container py-5">
<h1 class="display-2 text-white animated slideInDown mb-4">
404 Error
</h1>
<nav aria-label="breadcrumb animated slideInDown">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="#">Pages</a></li>
<li class="breadcrumb-item text-white active" aria-current="page">
404 Error
</li>
</ol>
</nav>
</div>
</div>
<!-- Page Header End -->
<!-- 404 Start -->
<div class="container-xxl py-5 wow fadeInUp" data-wow-delay="0.1s">
<div class="container text-center">
<div class="row justify-content-center">
<div class="col-lg-6">
<i class="bi bi-exclamation-triangle display-1 text-primary"></i>
<h1 class="display-1">404</h1>
<h1 class="mb-4">Page Not Found</h1>
<p class="mb-4">
Were sorry, the page you have looked for does not exist in our
website! Maybe go to our home page or try to use a search?
</p>
<a class="btn btn-primary rounded-pill py-3 px-5" href=""
>Go Back To Home</a
>
</div>
</div>
</div>
</div>
<!-- 404 End -->
<!-- Footer Start -->
<div
class="container-fluid bg-dark text-white-50 footer pt-5 mt-5 wow fadeIn"
data-wow-delay="0.1s"
>
<div class="container py-5">
<div class="row g-5">
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Get In Touch</h3>
<p class="mb-2">
<i class="fa fa-map-marker-alt me-3"></i>5 Courthouse Lane,
Chelmsford, MA 01824
</p>
<p class="mb-2">
<i class="fa fa-phone-alt me-3"></i>+1 978-364-0219 0
</p>
<p class="mb-2">
<i class="fa fa-envelope me-3"></i>alrahma.isgl@gmail.com
</p>
<div class="d-flex pt-2">
<a class="btn btn-outline-light btn-social" href=""
><i class="fab fa-twitter"></i
></a>
<a class="btn btn-outline-light btn-social" href=""
><i class="fab fa-facebook-f"></i
></a>
<a class="btn btn-outline-light btn-social" href=""
><i class="fab fa-youtube"></i
></a>
<a class="btn btn-outline-light btn-social" href=""
><i class="fab fa-linkedin-in"></i
></a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Quick Links</h3>
<a class="btn btn-link text-white-50" href="">About Us</a>
<a class="btn btn-link text-white-50" href="">Contact Us</a>
<a class="btn btn-link text-white-50" href="">Our Services</a>
<a class="btn btn-link text-white-50" href="">Privacy Policy</a>
<a class="btn btn-link text-white-50" href=""
>Terms & Condition</a
>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Photo Gallery</h3>
<div class="row g-2 pt-2">
<div class="col-4">
<img
class="img-fluid rounded bg-light p-1"
src=""assets/images/classes-1.jpg"
alt=""
/>
</div>
<div class="col-4">
<img
class="img-fluid rounded bg-light p-1"
src=""assets/images/classes-2.jpg"
alt=""
/>
</div>
<div class="col-4">
<img
class="img-fluid rounded bg-light p-1"
src=""assets/images/classes-3.jpg"
alt=""
/>
</div>
<div class="col-4">
<img
class="img-fluid rounded bg-light p-1"
src=""assets/images/classes-4.jpg"
alt=""
/>
</div>
<div class="col-4">
<img
class="img-fluid rounded bg-light p-1"
src=""assets/images/classes-5.jpg"
alt=""
/>
</div>
<div class="col-4">
<img
class="img-fluid rounded bg-light p-1"
src=""assets/images/classes-6.jpg"
alt=""
/>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Newsletter</h3>
<p>
Our newsletter is your gateway to staying informed and connected
with our Sunday school community. Each issue includes insightful
articles, updates on our programs, and highlights of upcoming
events. Subscribe today to keep up with all the exciting things
happening and to be a part of our vibrant community.
</p>
<div class="position-relative mx-auto" style="max-width: 400px">
<input
class="form-control bg-transparent w-100 py-3 ps-4 pe-5"
type="text"
placeholder="Your email"
/>
<button
type="button"
class="btn btn-primary py-2 position-absolute top-0 end-0 mt-2 me-2"
>
SignUp
</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="copyright">
<div class="row">
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
&copy;
<a class="border-bottom" href="#"
>Al Rahma Sunday School by ISGL</a
>, All Right Reserved.
<!--/*** This template is free as long as you keep the footer authors credit link/attribution link/backlink. If you'd like to use the template without the footer authors credit link/attribution link/backlink, you can purchase the Credit Removal License from "https://htmlcodex.com/credit-removal". Thank you for your support. ***/-->
Designed By
<a class="border-bottom" href="https://htmlcodex.com"
>HTML Codex</a
>
</div>
<div class="col-md-6 text-center text-md-end">
<div class="footer-menu">
<a href="">Home</a>
<a href="">Cookies</a>
<a href="">Help</a>
<a href="">FQAs</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer End -->
<!-- Back to Top -->
<a href="#" class="btn btn-lg btn-primary btn-lg-square back-to-top"
><i class="bi bi-arrow-up"></i
></a>
</div>
<!-- JavaScript Libraries -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="lib/wow/wow.min.js"></script>
<script src="lib/easing/easing.min.js"></script>
<script src="lib/waypoints/waypoints.min.js"></script>
<script src="lib/owlcarousel/owl.carousel.min.js"></script>
<!-- Template Javascript -->
<script src="js/main.js"></script>
</body>
</html>
+239
View File
@@ -0,0 +1,239 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Al Rahma Sunday School</title>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="" name="keywords">
<meta content="" name="description">
<!-- Favicon -->
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
<!-- Google Web Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;600&family=Inter:wght@600&family=Lobster+Two:wght@700&display=swap" rel="stylesheet">
<!-- Icon Font Stylesheet -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css" rel="stylesheet">
<!-- Libraries Stylesheet -->
<link href="<?= base_url('lib/animate/animate.min.css') ?>" rel="stylesheet">
<link href="<?= base_url('lib/owlcarousel/assets/owl.carousel.min.css') ?>" rel="stylesheet">
<!-- Customized Bootstrap Stylesheet -->
<link href="<?= base_url('css/bootstrap.min.css') ?>" rel="stylesheet">
<!-- Template Stylesheet -->
<link href="<?= base_url('css/style.css') ?>" rel="stylesheet">
</head>
<body>
<div class="container-xxl bg-white p-0">
<!-- Spinner Start -->
<div id="spinner" class="show bg-white position-fixed translate-middle w-100 vh-100 top-50 start-50 d-flex align-items-center justify-content-center">
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<!-- Spinner End -->
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav mx-auto">
<a href="<?= base_url('/') ?>" class="nav-item nav-link">Home</a>
<a href="<?= base_url('/about') ?>" class="nav-item nav-link">About Us</a>
<a href="<?= base_url('/classes') ?>" class="nav-item nav-link">Classes</a>
<a href="<?= base_url('/contact') ?>" class="nav-item nav-link">Contact Us</a>
</div>
<div class="d-flex">
<a href="/user/login" class="btn btn-primary rounded-pill px-3">Login<i class="fa fa-arrow-right ms-3"></i></a>
<a href="/register" class="btn btn-primary rounded-pill px-3 me-2">Register<i class="fa fa-arrow-right ms-3"></i></a>
</div>
</div>
</nav>
<!-- Navbar End -->
<!-- Page Header Start -->
<!-- Page Header Start -->
<div class="container-xxl py-5 page-header position-relative mb-5">
<div class="container py-5">
<h1 class="display-2 text-white animated slideInDown mb-4">About Us</h1>
<nav aria-label="breadcrumb animated slideInDown">
<ol class="breadcrumb">
<li class="breadcrumb-item text-green"><a href="/">Home</a></li>
<li class="breadcrumb-item text-green"><a href="#">Pages</a></li>
<li class="breadcrumb-item text-green active" aria-current="page">About Us</li>
</ol>
</nav>
</div>
</div>
<!-- Page Header End -->
<!-- Page Header End -->
<!-- About Start -->
<div class="container-xxl py-5">
<div class="container">
<div class="row g-5 align-items-center">
<div class="col-lg-6 wow fadeInUp" data-wow-delay="0.1s">
<h1 class="mb-4">Learn More About Our Work And Our Cultural Activities</h1>
<p>Discover the impactful work we do and immerse yourself in our vibrant cultural activities.
</p>
<p class="mb-4">Our programs are designed to enrich the community, fostering a deep appreciation for diverse traditions and values.
Join us to experience firsthand the creativity and passion that drive our initiatives.
</p>
<div class="row g-4 align-items-center">
<div class="col-sm-6">
<a class="btn btn-primary rounded-pill py-3 px-5" href="#">Read More</a>
</div>
</div>
</div>
<div class="col-lg-6 about-img wow fadeInUp" data-wow-delay="0.5s">
<div class="row">
<div class="col-12 text-center">
<img class="img-fluid w-75 rounded-circle bg-light p-3" src="<?= base_url('images/about-1.jpg') ?>" alt="">
</div>
<div class="col-6 text-start" style="margin-top: -150px;">
<img class="img-fluid w-100 rounded-circle bg-light p-3" src="<?= base_url('images/about-2.jpg') ?>" alt="">
</div>
<div class="col-6 text-end" style="margin-top: -150px;">
<img class="img-fluid w-100 rounded-circle bg-light p-3" src="<?= base_url('images/about-3.jpg') ?>" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- About End -->
<!-- Call To Action Start -->
<div class="container-xxl py-5">
<div class="container">
<div class="bg-light rounded">
<div class="row g-0">
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.1s" style="min-height: 400px;">
<div class="position-relative h-100">
<img class="position-absolute w-100 h-100 rounded" src="<?= base_url('images/call-to-action.jpg') ?>" style="object-fit: cover;">
</div>
</div>
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.5s">
<div class="h-100 d-flex flex-column justify-content-center p-5">
<h1 class="mb-4">Become A Teacher or Admin</h1>
<p class="mb-4">Becoming a teacher or admin at our Sunday school is a rewarding opportunity to make a meaningful impact on young lives. As a teacher, you will inspire and guide children on their spiritual journey, fostering their growth and understanding of faith. As an admin, you will play a crucial role in supporting the school's operations and ensuring a smooth and effective learning environment. Join our dedicated team and contribute to a nurturing environment that shapes the future of our community.</p>
<a class="btn btn-primary py-3 px-5" href="#">Get Started Now<i class="fa fa-arrow-right ms-2"></i></a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Call To Action End -->
<!-- Footer Start -->
<div class="container-fluid bg-dark text-white-50 footer pt-5 mt-5 wow fadeIn" data-wow-delay="0.1s">
<div class="container py-5">
<div class="row g-5">
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Get In Touch</h3>
<p class="mb-2"><i class="fa fa-map-marker-alt me-3"></i>5 Courthouse Lane, Chelmsford, MA 01824</p>
<p class="mb-2"><i class="fa fa-phone-alt me-3"></i>+1 978-364-0219</p>
<p class="mb-2"><i class="fa fa-envelope me-3"></i>alrahma.isgl@gmail.com</p>
<div class="d-flex pt-2">
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-twitter"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-facebook-f"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-youtube"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-linkedin-in"></i></a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Quick Links</h3>
<a class="btn btn-link text-white-50" href="<?= base_url('/about') ?>">About Us</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/contact') ?>">Contact Us</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/services') ?>">Our Services</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/privacy') ?>">Privacy Policy</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/terms') ?>">Terms & Condition</a>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Photo Gallery</h3>
<div class="row g-2 pt-2">
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-1.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-2.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-3.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-4.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-5.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-6.jpg') ?>" alt="">
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Newsletter</h3>
<p>Our newsletter is your gateway to staying informed and connected with our Sunday school community. </p>
<div class="position-relative mx-auto" style="max-width: 400px;">
<input class="form-control bg-transparent w-100 py-3 ps-4 pe-5" type="text" placeholder="Your email">
<button type="button" class="btn btn-primary py-2 position-absolute top-0 end-0 mt-2 me-2">SignUp</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="copyright">
<div class="row">
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
&copy; <a class="border-bottom" href="#">Al Rahma Sunday School by ISGL</a>, All Right Reserved.
Designed By <a class="border-bottom" href="https://htmlcodex.com">HTML Codex</a>
</div>
<div class="col-md-6 text-center text-md-end">
<div class="footer-menu">
<a href="#">Home</a>
<a href="#">Cookies</a>
<a href="#">Help</a>
<a href="#">FQAs</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer End -->
<!-- Back to Top -->
<a href="#" class="btn btn-lg btn-primary btn-lg-square back-to-top"><i class="bi bi-arrow-up"></i></a>
</div>
<!-- JavaScript Libraries -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="<?= base_url('lib/wow/wow.min.js') ?>"></script>
<script src="<?= base_url('lib/easing/easing.min.js') ?>"></script>
<script src="<?= base_url('lib/waypoints/waypoints.min.js') ?>"></script>
<script src="<?= base_url('lib/owlcarousel/owl.carousel.min.js') ?>"></script>
<!-- Template Javascript -->
<script src="<?= base_url('js/main.js') ?>"></script>
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Choose Account</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="icon" href="<?= base_url('assets/images/favicon.ico') ?>">
</head>
<body class="bg-light">
<div class="container mt-5">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Select Account Type</h4>
</div>
<div class="card-body">
<p class="mb-4">You have multiple roles. Please select the account you'd like to log into:</p>
<div class="d-flex flex-wrap gap-3">
<?php foreach ($roles as $role): ?>
<a href="<?= base_url('roleswitcher/switchTo/' . urlencode($role)) ?>"
class="btn btn-outline-primary">
<?= ucfirst(str_replace('_', ' ', $role)) ?>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
</body>
</html>
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Activate Your Account</title>
<link rel="stylesheet" href="/css/styles.css"> <!-- Adjust the path accordingly -->
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
</head>
<body>
<div class="container">
<div class="left-side">
Al Rahma Sunday School
<img src="<?= base_url('assets/images/logo.png') ?>" alt="School Logo" class="logo"> <!-- Adjust the path accordingly -->
</div>
<div class="right-side">
<div class="activate-account-container">
<h2>Activate your account</h2>
<p>You account is not active yet. We sent you an email during the account registration. Please click the
confirmation link in that email.</p>
<p>If the link is expired, please use the button below to resend the confirmation email</p>
<div class="form-actions">
<button type="submit">Resend Confirmation email</button>
</div>
<p>If you don't remember the email or have access to the email address you used to register, please </p>
<div class="form-links">
<a href="/register">Register a new account</a>
<br>
<br>
<a href="/">Login page</a>
</div>
</div>
</div>
</div>
</body>
</html>
+135
View File
@@ -0,0 +1,135 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<style>
#adminProgressTable thead th {
position: static !important;
}
</style>
<div class="container py-4">
<div class="mb-4 pb-3 border-bottom">
<div class="d-flex align-items-center justify-content-between">
<div>
<h3 class="mb-0">Class Progress Reports</h3>
<div class="text-muted">Filter by week, class, and status</div>
</div>
</div>
</div>
<form class="card shadow-sm mb-3" method="get" action="<?= base_url('admin/progress') ?>">
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">From</label>
<input type="date" name="from" class="form-control" value="<?= esc($filters['from'] ?? '') ?>">
</div>
<div class="col-md-3">
<label class="form-label">To</label>
<input type="date" name="to" class="form-control" value="<?= esc($filters['to'] ?? '') ?>">
</div>
<div class="col-md-3">
<label class="form-label">Class/Section</label>
<select name="class_section_id" class="form-select">
<option value="">All</option>
<?php foreach ($classSections as $cs): ?>
<option value="<?= esc($cs['class_section_id']) ?>" <?= (isset($filters['class_section_id']) && $filters['class_section_id'] === (string) $cs['class_section_id']) ? 'selected' : '' ?>>
<?= esc($cs['class_section_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Status</label>
<select name="status" class="form-select">
<option value="">All</option>
<?php foreach ($statusOptions as $value => $label): ?>
<option value="<?= esc($value) ?>" <?= (isset($filters['status']) && $filters['status'] === $value) ? 'selected' : '' ?>><?= esc($label) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="d-flex gap-2 mt-3">
<button class="btn btn-primary" type="submit">Apply</button>
<a class="btn btn-outline-secondary" href="<?= base_url('admin/progress') ?>">Reset</a>
</div>
</div>
</form>
<div class="card shadow-sm mt-5">
<div class="card-body pt-4 px-0">
<div class="table-responsive px-4" style="margin-top: 1.5rem;">
<table id="adminProgressTable" class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Week</th>
<th>Subjects</th>
<th>Teacher</th>
<th class="text-end">Action</th>
</tr>
</thead>
<tbody>
<?php
$reportGroups = $reportGroups ?? [];
$subjectSections = $subjectSections ?? [];
?>
<?php if (empty($reportGroups)): ?>
<tr><td colspan="4" class="text-center text-muted py-4">No reports found.</td></tr>
<?php else: ?>
<?php foreach ($reportGroups as $group): ?>
<?php
$weekLabel = $group['week_start'] ? date('M d, Y', strtotime($group['week_start'])) : '-';
if (!empty($group['week_end'])) {
$weekLabel .= ' ' . date('M d, Y', strtotime($group['week_end']));
}
$reports = $group['reports'] ?? [];
$teacherName = '';
foreach ($reports as $report) {
if (!empty($report['teacher_name'])) {
$teacherName = $report['teacher_name'];
break;
}
}
$exampleId = $reports ? reset($reports)['id'] : null;
?>
<tr>
<td>
<div class="fw-semibold"><?= esc($weekLabel) ?></div>
<?php if (!empty($group['class_section_name'])): ?>
<div class="text-muted small">Class: <?= esc($group['class_section_name']) ?></div>
<?php endif; ?>
</td>
<td>
<div class="d-flex flex-column gap-2">
<?php foreach ($subjectSections as $slug => $section): ?>
<?php
$subjectName = $section['db_subject'] ?? $section['label'] ?? $slug;
$report = $reports[$subjectName] ?? null;
$statusTag = $report ? ($report['status_label'] ?? 'Unknown') : 'No entry';
$badgeClass = $report ? 'bg-secondary' : 'bg-light text-muted';
?>
<div class="border rounded-3 p-2">
<div class="d-flex justify-content-between align-items-center">
<strong class="small mb-0"><?= esc($section['label'] ?? $subjectName) ?></strong>
<span class="badge <?= $badgeClass ?>"><?= esc($statusTag) ?></span>
</div>
<div class="small text-muted"><?= $report ? esc($report['unit_title'] ?: '-') : 'No submission' ?></div>
</div>
<?php endforeach; ?>
</div>
</td>
<td><?= esc($teacherName ?: '-') ?></td>
<td class="text-end">
<?php if ($exampleId): ?>
<a class="btn btn-sm btn-outline-primary" href="<?= base_url('admin/progress/view/' . $exampleId) ?>">View</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
+139
View File
@@ -0,0 +1,139 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container py-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<div>
<h3 class="mb-0">Progress Report</h3>
<div class="text-muted">
<?= esc($row['class_section_name'] ?? '-') ?> • <?= esc($row['week_start']) ?> → <?= esc($row['week_end']) ?>
</div>
</div>
<a href="<?= base_url('admin/progress') ?>" class="btn btn-outline-secondary">Back</a>
</div>
<?php
$flagLabels = [
'homework_assigned' => 'Homework assigned',
'quiz_done' => 'Quiz / check-in completed',
'materials_missing' => 'Materials needed',
];
$weeklyReports = $weeklyReports ?? [];
$subjectSections = $subjectSections ?? [];
$reportsBySubject = [];
$attachments = [];
foreach ($weeklyReports as $report) {
$reportsBySubject[$report['subject']] = $report;
foreach (($report['attachments'] ?? []) as $attachment) {
$attachments[] = [
'subject' => $report['subject'] ?? 'Attachment',
'attachment' => $attachment,
'report_id' => $report['id'] ?? null,
];
}
}
?>
<div class="row g-3">
<div class="col-lg-8">
<?php foreach ($subjectSections as $slug => $section): ?>
<?php
$subjectName = $section['db_subject'] ?? $section['label'] ?? $slug;
$isQuran = $subjectName === 'Quran/Arabic';
$unitLabel = $isQuran ? 'Surah / Custom Arabic' : 'Unit / Chapter';
$homeworkLabel = $isQuran ? 'Arabic Practice / Homework' : 'Assigned Homework';
$report = $reportsBySubject[$subjectName] ?? null;
?>
<div class="card shadow-sm mb-3">
<div class="card-header bg-white d-flex align-items-center justify-content-between">
<strong class="mb-0"><?= esc($section['label'] ?? $subjectName) ?></strong>
</div>
<div class="card-body">
<?php if (! $report): ?>
<div class="text-muted">No entry submitted for this subject this week.</div>
<?php else: ?>
<div class="mb-2"><strong><?= $unitLabel ?>:</strong> <?= esc($report['unit_title'] ?: '-') ?></div>
<?php if (!empty($report['materials'])): ?>
<div class="mb-2"><strong>Materials:</strong> <?= esc($report['materials']) ?></div>
<?php endif; ?>
<div class="mb-3">
<strong>Activities</strong>
<div class="mt-1"><?= nl2br(esc($report['covered'] ?? '-')) ?></div>
</div>
<div class="mb-3">
<strong><?= $homeworkLabel ?>:</strong>
<div class="mt-1"><?= nl2br(esc($report['homework'] ?: '-')) ?></div>
</div>
<?php if (!empty($report['attachments'])): ?>
<div class="mt-3">
<strong>Attachments:</strong>
<div class="d-flex flex-column gap-2 mt-2">
<?php foreach ($report['attachments'] as $attachment): ?>
<?php
$linkId = $attachment['id'] ?? $report['id'];
$link = !empty($attachment['legacy'])
? base_url('admin/progress/attachment/' . $linkId)
: base_url('admin/progress/attachment-file/' . $linkId);
?>
<a class="btn btn-sm btn-outline-secondary text-start" href="<?= $link ?>" target="_blank" rel="noreferrer">
<?= esc($attachment['name'] ?? 'Attachment') ?>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="col-lg-4">
<div class="card shadow-sm mb-3">
<div class="card-header bg-white"><strong>Summary</strong></div>
<div class="card-body">
<div class="mb-2"><strong>Teacher:</strong> <?= esc(trim($row['teacher_name'] ?? '')) ?: '-' ?></div>
<div class="mb-2"><strong>Submitted:</strong> <?= esc($row['created_at'] ? date('M d, Y g:i A', strtotime($row['created_at'])) : '-') ?></div>
<div><strong>Status:</strong> <?= esc($row['status_label'] ?? 'Unknown') ?></div>
</div>
</div>
<?php if (!empty($row['flags'])): ?>
<div class="card shadow-sm mb-3">
<div class="card-header bg-white"><strong>Checklist</strong></div>
<div class="card-body">
<div class="d-flex flex-wrap gap-2">
<?php foreach ($row['flags'] as $flag): ?>
<span class="badge bg-light text-dark border"><?= esc($flagLabels[$flag] ?? str_replace('_', ' ', $flag)) ?></span>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if (!empty($attachments)): ?>
<div class="card shadow-sm">
<div class="card-header bg-white"><strong>Attachments</strong></div>
<div class="card-body">
<?php foreach ($attachments as $entry): ?>
<?php
$attachment = $entry['attachment'] ?? [];
$linkId = $attachment['id'] ?? ($entry['report_id'] ?? null);
if (!$linkId) {
continue;
}
$link = !empty($attachment['legacy'])
? base_url('admin/progress/attachment/' . $linkId)
: base_url('admin/progress/attachment-file/' . $linkId);
$label = trim(($entry['subject'] ?? 'Attachment') . ' • ' . ($attachment['name'] ?? 'Attachment'));
?>
<a class="btn btn-outline-primary w-100 text-start mb-2" href="<?= $link ?>" target="_blank" rel="noreferrer">
<?= esc($label) ?>
</a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,157 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<?php $isEdit = ($mode ?? 'create') === 'edit'; ?>
<?php $isLocked = $isEdit && !empty($competition['is_locked']); ?>
<?php $lockedAttr = $isLocked ? 'disabled' : ''; ?>
<h3><?= esc($isEdit ? 'Edit Competition' : 'Create Competition') ?></h3>
<?php if ($isLocked): ?>
<div class="alert alert-warning">This competition is locked. Unlock it to make changes.</div>
<?php endif; ?>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach ($errors as $e): ?><li><?= esc($e) ?></li><?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form method="post" action="<?= $isEdit ? '/admin/competition-winners/' . $competition['id'] . '/update' : '/admin/competition-winners/store' ?>" class="row g-3">
<?= csrf_field() ?>
<div class="col-md-8">
<label class="form-label">Title *</label>
<input class="form-control" name="title" value="<?= esc(old('title', $competition['title'] ?? '')) ?>" required <?= $lockedAttr ?>>
</div>
<div class="col-md-6">
<label class="form-label">Class Section (optional)</label>
<?php $oldSection = (string) old('class_section_id', (string) ($competition['class_section_id'] ?? '')); ?>
<select class="form-select" name="class_section_id" <?= $lockedAttr ?>>
<option value="">All classes</option>
<?php foreach ($classSections as $section): ?>
<?php
$value = (string) ($section['class_section_id'] ?? '');
$label = $section['class_section_name'] ?? $value;
$selected = $value !== '' && $value === $oldSection ? 'selected' : '';
?>
<option value="<?= esc($value) ?>" <?= $selected ?>><?= esc($label) ?></option>
<?php endforeach; ?>
</select>
<div class="form-text">Leave empty to manage winners for all classes.</div>
</div>
<div class="col-md-3">
<label class="form-label">Semester</label>
<input class="form-control" name="semester" value="<?= esc(old('semester', $competition['semester'] ?? $defaultSemester ?? '')) ?>" <?= $lockedAttr ?>>
</div>
<div class="col-md-3">
<label class="form-label">School Year</label>
<input class="form-control" name="school_year" value="<?= esc(old('school_year', $competition['school_year'] ?? $defaultSchoolYear ?? '')) ?>" placeholder="2025-2026" <?= $lockedAttr ?>>
<div class="form-text">Counts use the school year above.</div>
</div>
<div class="col-md-4">
<label class="form-label">Start Date</label>
<input class="form-control" name="start_date" type="date" value="<?= esc(old('start_date', $competition['start_date'] ?? '')) ?>" <?= $lockedAttr ?>>
</div>
<div class="col-md-4">
<label class="form-label">End Date</label>
<input class="form-control" name="end_date" type="date" value="<?= esc(old('end_date', $competition['end_date'] ?? '')) ?>" <?= $lockedAttr ?>>
</div>
<div class="col-12">
<h5 class="mt-2">Winners per Class</h5>
<div class="table-responsive">
<table class="table table-bordered table-sm" data-no-mgmt-sticky>
<thead class="table-light">
<tr>
<th>Class</th>
<th style="width:140px;">Students</th>
<th style="width:140px;">Auto Winners</th>
<th style="width:150px;">Questions</th>
<th style="width:120px;">1st</th>
<th style="width:120px;">2nd</th>
<th style="width:120px;">3rd</th>
<th style="width:120px;">4th</th>
<th style="width:120px;">5th</th>
<th style="width:120px;">6th</th>
<th style="width:160px;">Override</th>
<th style="width:140px;">Final Winners</th>
</tr>
</thead>
<tbody>
<?php foreach ($classRows as $row): ?>
<tr>
<td><?= esc($row['class_section_name']) ?></td>
<td><?= esc($row['student_count']) ?></td>
<td><?= esc($row['auto_winners']) ?></td>
<td>
<input class="form-control form-control-sm" type="number" min="1"
name="question_counts[<?= esc($row['class_section_id']) ?>]"
value="<?= esc($row['question_count'] ?? '') ?>"
placeholder="Questions" <?= $lockedAttr ?>>
</td>
<td>
<input class="form-control form-control-sm" type="number" step="0.01" min="0"
name="prizes[<?= esc($row['class_section_id']) ?>][1]"
value="<?= esc($row['prize_1'] ?? '') ?>"
placeholder="$" <?= $lockedAttr ?>>
</td>
<td>
<input class="form-control form-control-sm" type="number" step="0.01" min="0"
name="prizes[<?= esc($row['class_section_id']) ?>][2]"
value="<?= esc($row['prize_2'] ?? '') ?>"
placeholder="$" <?= $lockedAttr ?>>
</td>
<td>
<input class="form-control form-control-sm" type="number" step="0.01" min="0"
name="prizes[<?= esc($row['class_section_id']) ?>][3]"
value="<?= esc($row['prize_3'] ?? '') ?>"
placeholder="$" <?= $lockedAttr ?>>
</td>
<td>
<input class="form-control form-control-sm" type="number" step="0.01" min="0"
name="prizes[<?= esc($row['class_section_id']) ?>][4]"
value="<?= esc($row['prize_4'] ?? '') ?>"
placeholder="$" <?= $lockedAttr ?>>
</td>
<td>
<input class="form-control form-control-sm" type="number" step="0.01" min="0"
name="prizes[<?= esc($row['class_section_id']) ?>][5]"
value="<?= esc($row['prize_5'] ?? '') ?>"
placeholder="$" <?= $lockedAttr ?>>
</td>
<td>
<input class="form-control form-control-sm" type="number" step="0.01" min="0"
name="prizes[<?= esc($row['class_section_id']) ?>][6]"
value="<?= esc($row['prize_6'] ?? '') ?>"
placeholder="$" <?= $lockedAttr ?>>
</td>
<td>
<input class="form-control form-control-sm" type="number" min="1"
name="winner_overrides[<?= esc($row['class_section_id']) ?>]"
value="<?= esc($row['override_winners'] ?? '') ?>"
placeholder="Auto" <?= $lockedAttr ?>>
</td>
<td><?= esc($row['final_winners']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="col-12">
<button class="btn btn-primary" <?= $lockedAttr ?>><?= esc($isEdit ? 'Update' : 'Create') ?></button>
<a class="btn btn-outline-secondary" href="/admin/competition-winners">Cancel</a>
</div>
</form>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,70 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="mb-0">Competition Winners</h3>
<a class="btn btn-primary" href="/admin/competition-winners/create">+ New Competition</a>
</div>
<?php if (session('success')): ?>
<div class="alert alert-success"><?= esc(session('success')) ?></div>
<?php endif; ?>
<?php if (session('error')): ?>
<div class="alert alert-danger"><?= esc(session('error')) ?></div>
<?php endif; ?>
<table class="table table-bordered table-sm" data-no-mgmt-sticky>
<thead class="table-light">
<tr>
<th>ID</th>
<th>Title</th>
<th>Class Section</th>
<th>Winners Rule</th>
<th>Published</th>
<th>Locked</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($competitions as $c): ?>
<?php
$sectionId = (int) ($c['class_section_id'] ?? 0);
$sectionName = $sectionId > 0 ? ($sectionMap[$sectionId] ?? $sectionId) : 'All';
$isLocked = !empty($c['is_locked']);
?>
<tr>
<td><?= esc($c['id']) ?></td>
<td><?= esc($c['title']) ?></td>
<td><?= esc($sectionName) ?></td>
<td>Tiered per class</td>
<td><?= $c['is_published'] ? 'Yes' : 'No' ?></td>
<td><?= $isLocked ? 'Yes' : 'No' ?></td>
<td>
<a class="btn btn-sm btn-outline-secondary" href="/admin/competition-winners/<?= $c['id'] ?>/settings">Settings</a>
<a class="btn btn-sm btn-outline-primary" href="/admin/competition-winners/<?= $c['id'] ?>">Enter Scores</a>
<a class="btn btn-sm btn-outline-secondary" href="/admin/competition-winners/<?= $c['id'] ?>/preview">Preview</a>
<a class="btn btn-sm btn-outline-secondary" href="/admin/competition-winners/<?= $c['id'] ?>/winners">Winners</a>
<form method="post" action="/admin/competition-winners/<?= $c['id'] ?>/export-quiz" class="d-inline">
<?= csrf_field() ?>
<button class="btn btn-sm btn-outline-success" type="submit">Export Scores to Quiz</button>
</form>
<?php if ($isLocked): ?>
<form method="post" action="/admin/competition-winners/<?= $c['id'] ?>/unlock" class="d-inline">
<?= csrf_field() ?>
<button class="btn btn-sm btn-outline-warning" type="submit" onclick="return confirm('Unlock this competition to allow edits?')">Unlock</button>
</form>
<?php else: ?>
<form method="post" action="/admin/competition-winners/<?= $c['id'] ?>/lock" class="d-inline">
<?= csrf_field() ?>
<button class="btn btn-sm btn-outline-danger" type="submit" onclick="return confirm('Lock this competition to prevent edits?')">Lock</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,86 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h3 class="mb-0">Preview Winners</h3>
<div class="d-flex gap-2">
<a class="btn btn-outline-secondary" href="/admin/competition-winners/<?= $competition['id'] ?>">Edit Scores</a>
<a class="btn btn-outline-secondary" href="/admin/competition-winners/<?= $competition['id'] ?>/settings">Settings</a>
<a class="btn btn-outline-secondary" href="/admin/competition-winners">Back</a>
</div>
</div>
<div class="mb-3">
<div><strong>Competition:</strong> <?= esc($competition['title']) ?></div>
<div><strong>School Year:</strong> <?= esc($competition['school_year'] ?? '-') ?></div>
</div>
<?php if (empty($rankedByClass)): ?>
<div class="alert alert-warning">No scores entered yet.</div>
<?php else: ?>
<?php foreach ($rankedByClass as $classId => $rows): ?>
<?php
$classLabel = $classMap[$classId] ?? ('Class ' . $classId);
$topRows = $topByClass[$classId] ?? [];
$winnerPrizeByStudent = [];
foreach ($topRows as $winnerRow) {
$studentId = $winnerRow['student_id'] ?? null;
if ($studentId !== null) {
$winnerPrizeByStudent[(int) $studentId] = $winnerRow['prize_amount'] ?? null;
}
}
$studentCount = $classCounts[$classId] ?? count($rows);
$override = $overrideMap[$classId] ?? null;
$questions = $questionCounts[$classId] ?? null;
$prizes = $prizeMap[$classId] ?? [];
$finalWinners = $winnersCountByClass[$classId] ?? 0;
?>
<h5 class="mt-4">Top Winners - <?= esc($classLabel) ?></h5>
<div class="text-muted mb-2">
Students: <?= esc($studentCount) ?> | Questions: <?= esc($questions ?? '-') ?> | Override: <?= esc($override ?? '-') ?> | Final Winners: <?= esc($finalWinners) ?>
</div>
<table class="table table-bordered table-sm" data-no-mgmt-sticky>
<thead class="table-light">
<tr><th>Rank</th><th>Student</th><th>Score</th><th>Prize</th></tr>
</thead>
<tbody>
<?php foreach ($topRows as $row): ?>
<tr>
<td><?= esc($row['rank']) ?></td>
<td><?= esc($row['name']) ?></td>
<td><?= esc($row['score']) ?></td>
<td><?= esc(!empty($row['prize_amount']) ? $row['prize_amount'] : '-') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<h6>All Ranked Scores - <?= esc($classLabel) ?></h6>
<table class="table table-striped table-sm" data-no-mgmt-sticky>
<thead><tr><th>#</th><th>Student</th><th>Score</th><th>Prize</th></tr></thead>
<tbody>
<?php $i = 1; foreach ($rows as $row): ?>
<?php
$studentId = (int) ($row['student_id'] ?? 0);
$prizeVal = $winnerPrizeByStudent[$studentId] ?? null;
?>
<tr>
<td><?= $i++ ?></td>
<td><?= esc($row['name']) ?></td>
<td><?= esc($row['score']) ?></td>
<td><?= esc(!empty($prizeVal) ? $prizeVal : '-') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach; ?>
<?php endif; ?>
<form method="post" action="/admin/competition-winners/<?= $competition['id'] ?>/publish" class="mt-3">
<?= csrf_field() ?>
<button class="btn btn-success">Publish Winners</button>
</form>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,135 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h3 class="mb-0">Enter Scores</h3>
<a class="btn btn-outline-secondary" href="/admin/competition-winners">Back</a>
</div>
<?php $isLocked = !empty($competition['is_locked']); ?>
<?php $lockedAttr = $isLocked ? 'disabled' : ''; ?>
<?php if ($isLocked): ?>
<div class="alert alert-warning">This competition is locked. Unlock it to update scores.</div>
<?php endif; ?>
<div class="mb-3">
<div><strong>Competition:</strong> <?= esc($competition['title']) ?></div>
<?php if (!empty($classSectionName)): ?>
<div><strong>Class Section:</strong> <?= esc($classSectionName) ?></div>
<div>
<strong>Students:</strong> <?= esc($classStudentCount ?? 0) ?> |
<strong>Auto Winners:</strong> <?= esc($classAutoWinners ?? 0) ?> |
<strong>Override:</strong> <?= esc($classOverrideWinners ?? '-') ?> |
<strong>Final Winners:</strong> <?= esc($classFinalWinners ?? 0) ?> |
<strong>Questions:</strong> <?= esc($classQuestionCount ?? '-') ?>
</div>
<?php endif; ?>
</div>
<?php if (!$classSelectionLocked): ?>
<form method="get" class="row g-2 align-items-end mb-3">
<div class="col-md-6">
<label class="form-label">Select Class Section</label>
<select class="form-select" name="class_section_id" onchange="this.form.submit()" <?= $lockedAttr ?>>
<option value="">Choose a class</option>
<?php foreach ($classSections as $section): ?>
<?php
$value = (string) ($section['class_section_id'] ?? '');
$label = $section['class_section_name'] ?? $value;
$selected = $value !== '' && (int) $value === (int) $activeClassSectionId ? 'selected' : '';
?>
<option value="<?= esc($value) ?>" <?= $selected ?>><?= esc($label) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<div class="form-text">Pick a class to load its students for score entry.</div>
</div>
</form>
<?php endif; ?>
<?php if (session('success')): ?>
<div class="alert alert-success"><?= esc(session('success')) ?></div>
<?php endif; ?>
<form method="post" action="/admin/competition-winners/<?= $competition['id'] ?>/scores">
<?= csrf_field() ?>
<input type="hidden" name="class_section_id" value="<?= esc($activeClassSectionId ?? '') ?>">
<?php if (empty($activeClassSectionId)): ?>
<div class="alert alert-warning">Select a class section to load students.</div>
<?php else: ?>
<link rel="stylesheet" href="https://cdn.datatables.net/v/bs5/dt-2.1.8/r-3.0.3/datatables.min.css">
<?php $scoreMax = ($classQuestionCount !== null && (int) $classQuestionCount > 0) ? (int) $classQuestionCount : null; ?>
<table class="table table-bordered table-sm" data-no-mgmt-sticky id="scoresTable">
<thead class="table-light">
<tr>
<th style="width:140px;">School ID</th>
<th>Student</th>
<th style="width:180px;">
Score<?= $scoreMax !== null ? ' (max ' . esc($scoreMax) . ')' : '' ?>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $s):
$name = trim(($s['firstname'] ?? '') . ' ' . ($s['lastname'] ?? ''));
$studentId = $s['id'] ?? $s['student_id'] ?? null;
$schoolId = $s['school_id'] ?? null;
$val = $studentId !== null ? ($scoreMap[$studentId] ?? '') : '';
?>
<tr>
<td><?= esc($schoolId ?: $studentId) ?></td>
<td><?= esc($name !== '' ? $name : ('Student #' . $studentId)) ?></td>
<td data-order="<?= esc($val) ?>">
<input class="form-control form-control-sm"
name="scores[<?= esc($studentId) ?>]"
type="number" step="0.01" min="0"
value="<?= esc($val) ?>"
<?= $scoreMax !== null ? 'max="' . esc($scoreMax) . '"' : '' ?>
<?= $lockedAttr ?>>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<div class="d-flex gap-2">
<button class="btn btn-primary" <?= $lockedAttr ?>>Save Scores</button>
<a class="btn btn-outline-secondary" href="/admin/competition-winners/<?= $competition['id'] ?>/preview">Preview Winners</a>
</div>
</form>
</div>
<script src="https://cdn.datatables.net/v/bs5/dt-2.1.8/r-3.0.3/datatables.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const tableEl = document.getElementById('scoresTable');
if (!tableEl) {
return;
}
new DataTable(tableEl, {
responsive: true,
paging: false,
searching: false,
info: false,
order: [[1, 'asc']],
});
tableEl.addEventListener('input', function (event) {
const target = event.target;
if (target && target.matches('input')) {
const cell = target.closest('td');
if (cell) {
cell.setAttribute('data-order', target.value.trim());
}
}
});
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,110 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h3 class="mb-0">School Winners</h3>
<div class="d-flex gap-2">
<a class="btn btn-outline-secondary" href="/admin/competition-winners/<?= $competition['id'] ?>/preview">Preview</a>
<a class="btn btn-outline-secondary" href="/admin/competition-winners">Back</a>
</div>
</div>
<div class="mb-3">
<div><strong>Competition:</strong> <?= esc($competition['title']) ?></div>
<div><strong>School Year:</strong> <?= esc($competition['school_year'] ?? '-') ?></div>
<div><strong>Published:</strong> <?= !empty($competition['is_published']) ? 'Yes' : 'No' ?></div>
</div>
<?php
$palette = [
['bg' => '#fde2c2', 'hover' => '#f8c996'],
['bg' => '#cfe6ff', 'hover' => '#b5d6ff'],
['bg' => '#c9f2e7', 'hover' => '#a6e6d4'],
['bg' => '#f7c8c7', 'hover' => '#f1a6a3'],
['bg' => '#fff1a8', 'hover' => '#ffe38a'],
['bg' => '#e4c7f2', 'hover' => '#d1a9e8'],
['bg' => '#d6d9de', 'hover' => '#c2c8cf'],
['bg' => '#c9efc5', 'hover' => '#aee3a9'],
];
$paletteCount = count($palette);
?>
<style>
<?php foreach ($palette as $index => $colors): ?>
.class-color-<?= $index ?> td { background-color: <?= $colors['bg'] ?>; }
.class-color-<?= $index ?>:hover td { background-color: <?= $colors['hover'] ?>; }
<?php endforeach; ?>
</style>
<?php if (empty($rows)): ?>
<div class="alert alert-warning">No published winners yet. Publish winners to see the list.</div>
<?php else: ?>
<table class="table table-bordered table-sm" data-no-mgmt-sticky>
<thead class="table-light">
<tr>
<th>Class</th>
<th>School ID</th>
<th>Student</th>
<th>Photo Consent</th>
<th>Rank</th>
<th>Score</th>
<th>Prize</th>
</tr>
</thead>
<tbody>
<?php $totalPrize = 0.0; ?>
<?php foreach ($rows as $row): ?>
<?php
$classId = (int) ($row['class_section_id'] ?? 0);
$classLabel = $row['class_section_name'] ?? ($classMap[$classId] ?? ('Class ' . $classId));
$name = trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? ''));
$studentLabel = $name !== '' ? $name : ('Student #' . ($row['student_id'] ?? ''));
$colorIndex = $paletteCount > 0 ? ($classId % $paletteCount) : 0;
$questionCount = $questionCounts[$classId] ?? null;
$scoreValue = $row['score'] ?? null;
$scoreLabel = '-';
$prizeValue = null;
$photoConsentLabel = '-';
if (array_key_exists('photo_consent', $row)) {
$photoConsentLabel = ((int) $row['photo_consent'] === 1) ? 'Yes' : 'No';
}
if (isset($row['prize_amount']) && $row['prize_amount'] !== '' && is_numeric($row['prize_amount'])) {
$prizeValue = (float) $row['prize_amount'];
$totalPrize += $prizeValue;
}
if ($scoreValue !== null && $scoreValue !== '' && is_numeric($scoreValue)) {
$scoreDisplay = number_format((float) $scoreValue, 0, '.', '');
$scoreLabel = $scoreDisplay;
if ($questionCount !== null) {
$scoreLabel = $scoreDisplay . '/' . $questionCount;
}
}
?>
<tr class="class-color-<?= esc((string) $colorIndex) ?>">
<td><?= esc($classLabel) ?></td>
<td><?= esc($row['school_id'] ?? $row['student_id'] ?? '-') ?></td>
<td><?= esc($studentLabel) ?></td>
<td><?= esc($photoConsentLabel) ?></td>
<td><?= esc($row['rank'] ?? '-') ?></td>
<td><?= esc($scoreLabel) ?></td>
<td><?= esc($prizeValue !== null ? $prizeValue : '-') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<th colspan="6" class="text-end">Total Prizes</th>
<th><?= esc(number_format($totalPrize, 2, '.', '')) ?></th>
</tr>
</tfoot>
</table>
<div class="d-flex justify-content-end mt-3">
<a class="btn btn-primary external-link" href="/admin/competition-winners/<?= $competition['id'] ?>/recognition-pdf" target="_blank" rel="noopener">
Generate Recognition PDF
</a>
</div>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
+145
View File
@@ -0,0 +1,145 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid py-4">
<style>
.admin-score-table { margin-top: 4px; }
.admin-score-table thead th { position: static; }
</style>
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<div>
<h2 class="mb-0">Student Score Card (Admin)</h2>
<div class="text-muted small">Search for a student, then open their score card.</div>
</div>
</div>
<div class="card shadow-sm mb-3">
<div class="card-body">
<form id="studentSearchForm" method="get" action="<?= site_url('administrator/student-score-card') ?>">
<div class="row g-2 justify-content-center">
<div class="col-12 col-md-8 col-lg-6">
<div class="input-group">
<span class="input-group-text">Search Student</span>
<input id="globalStudentSearch"
class="form-control"
type="text"
name="q"
placeholder="Type School ID or Student Name"
list="studentSuggestions"
autocomplete="off"
value="<?= esc($query ?? '') ?>" />
<button id="globalStudentSearchClear" class="btn btn-outline-secondary" type="button" title="Clear search">Clear</button>
</div>
<small class="text-muted">Searches all grades.</small>
</div>
</div>
<button type="submit" class="d-none">Search</button>
</form>
</div>
</div>
<datalist id="studentSuggestions">
<?php
$seen = [];
foreach (($suggestions ?? []) as $stu):
$sid = (int)($stu['id'] ?? 0);
if ($sid <= 0) continue;
$schoolId = trim((string)($stu['school_id'] ?? ''));
$label = trim(($stu['firstname'] ?? '') . ' ' . ($stu['lastname'] ?? ''));
$value = trim(($schoolId !== '' ? $schoolId . ' - ' : '') . $label);
if ($value === '' || isset($seen[$value])) continue;
$seen[$value] = true;
?>
<option value="<?= esc($value) ?>"></option>
<?php endforeach; ?>
</datalist>
<?php if (!empty($query) && empty($students)): ?>
<div class="alert alert-warning">No students found.</div>
<?php endif; ?>
<?php if (!empty($students)): ?>
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>Results</strong>
<span class="text-muted small">Showing up to 50</span>
</div>
<div class="card-body table-responsive pt-2">
<table class="table table-striped table-hover align-middle admin-score-table no-mgmt-sticky" data-no-mgmt-sticky="1">
<thead class="table-light">
<tr>
<th>School ID</th>
<th>Student</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $s): ?>
<tr>
<td><?= esc($s['school_id'] ?? '') ?></td>
<td><?= esc(($s['firstname'] ?? '') . ' ' . ($s['lastname'] ?? '')) ?></td>
<td>
<?php if ((int)($s['is_active'] ?? 0) === 1): ?>
<span class="badge text-bg-success">Active</span>
<?php else: ?>
<span class="badge text-bg-secondary">Inactive</span>
<?php endif; ?>
</td>
<td class="text-end">
<form method="post" action="<?= site_url('student/score-card') ?>" class="d-inline">
<?= csrf_field() ?>
<input type="hidden" name="student_id" value="<?= (int)($s['id'] ?? 0) ?>">
<button type="submit" class="btn btn-sm btn-outline-primary">Open Score Card</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
<script>
(function () {
const form = document.getElementById('studentSearchForm');
const input = document.getElementById('globalStudentSearch');
const clr = document.getElementById('globalStudentSearchClear');
const dlist = document.getElementById('studentSuggestions');
const suggestionSet = new Set(Array.from(dlist?.options || []).map(o => (o.value || '').trim()));
let lastAutoQuery = '';
function submitSearch() {
if (!form) return;
form.submit();
}
input?.addEventListener('keydown', e => {
if (e.key === 'Enter') { e.preventDefault(); submitSearch(); }
});
clr?.addEventListener('click', () => {
if (input) input.value = '';
input?.focus();
});
function tryAutoRun() {
const val = (input?.value || '').trim();
if (!val) return;
if (suggestionSet.has(val) && val !== lastAutoQuery) {
lastAutoQuery = val;
submitSearch();
}
}
input?.addEventListener('change', tryAutoRun);
input?.addEventListener('input', () => {
clearTimeout(input.__autoTimer);
input.__autoTimer = setTimeout(tryAutoRun, 50);
});
})();
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,161 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-4">
<div class="container">
<div class="text-center mx-auto mb-4" style="max-width:700px;">
<h4 class="text-dark" style="font-family:Arial,sans-serif;">TimeOff Request</h4>
<div class="text-muted">Logged in as: <strong><?= esc($admin_name ?? 'Administrator') ?></strong></div>
<?php if (!empty($semester) && !empty($schoolYear)): ?>
<div class="small text-muted">Term: <?= esc($semester) ?> — <?= esc($schoolYear) ?></div>
<?php endif; ?>
</div>
<?php if (session()->getFlashdata('message')): ?>
<?php $status = session()->getFlashdata('status') ?? 'info';
$cls = $status === 'success' ? 'alert-success' : ($status === 'error' ? 'alert-danger' : 'alert-info'); ?>
<div class="alert <?= $cls ?> text-center" role="alert">
<?= esc(session()->getFlashdata('message')) ?>
</div>
<?php endif; ?>
<div class="card shadow-sm mb-4">
<div class="card-header bg-light"><strong>Submit Upcoming Absences</strong></div>
<div class="card-body">
<form action="<?= site_url('/administrator/absence') ?>" method="post" id="absenceForm">
<?= csrf_field() ?>
<?php $hasDates = !empty($availableDates) && is_array($availableDates); ?>
<div class="mb-3">
<label class="form-label">Select Sunday Date(s)</label>
<div id="datesContainer">
<div class="d-flex align-items-center gap-2 mb-2 date-row">
<select name="dates[]" class="form-select" style="max-width:260px;" <?= $hasDates ? '' : 'disabled' ?> required>
<option value="" disabled selected>Select Sunday date</option>
<?php foreach (($availableDates ?? []) as $d): ?>
<option value="<?= esc($d) ?>"><?= esc($d) ?></option>
<?php endforeach; ?>
</select>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="addDateRow()" <?= $hasDates ? '' : 'disabled' ?>>
<i class="bi bi-plus-lg"></i> Add another
</button>
</div>
</div>
<div class="form-text">Future Sundays from September to May are available only.</div>
<?php if (!$hasDates): ?>
<div class="alert alert-info mt-2">No future Sundays are available in the current term.</div>
<?php endif; ?>
</div>
<div class="row g-3 mb-3">
<div class="col-sm-6">
<label class="form-label" for="reason_type">Reason Type</label>
<select id="reason_type" name="reason_type" class="form-select">
<option value="vacation">Vacation</option>
<option value="sick">Sick</option>
<option value="personal">Personal</option>
<option value="other">Other</option>
</select>
</div>
<div class="col-sm-6">
<label class="form-label" for="reason">Reason <span class="text-danger">*</span></label>
<input type="text" id="reason" name="reason" class="form-control" placeholder="Provide a reason" required>
</div>
</div>
<div class="d-flex justify-content-center">
<button type="submit" class="btn btn-primary px-5" <?= $hasDates ? '' : 'disabled' ?> >
Submit Absence
</button>
</div>
</form>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header bg-light"><strong>Your Reported Status (This Term)</strong></div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-bordered table-striped m-0 align-middle no-mgmt-sticky wrap-headers">
<thead class="table-light">
<tr>
<th class="text-center" style="width:80px;">#</th>
<th>Date</th>
<th>Status</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
<?php if (!empty($existing)): ?>
<?php $i=1; foreach ($existing as $row): ?>
<tr>
<td class="text-center"><?= $i++ ?></td>
<td><?= esc(!empty($row['date']) ? local_date($row['date'], 'm-d-Y') : '') ?></td>
<td>
<?php $st = strtolower((string)($row['status'] ?? ''));
$badge = $st==='absent'?'danger':($st==='late'?'warning text-dark':'success'); ?>
<span class="badge bg-<?= $badge ?>"><?= ucfirst($st ?: 'n/a') ?></span>
</td>
<td><?= esc($row['reason'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="4" class="text-center">No reported entries yet.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('styles') ?>
<style>
/* Allow header labels to wrap if needed on smaller screens */
.wrap-headers thead th { white-space: normal !important; }
</style>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
const AVAILABLE_DATES = <?= json_encode(array_values($availableDates ?? [])) ?>;
function addDateRow() {
if (!AVAILABLE_DATES || AVAILABLE_DATES.length === 0) return;
const container = document.getElementById('datesContainer');
const row = document.createElement('div');
row.className = 'd-flex align-items-center gap-2 mb-2 date-row';
const select = document.createElement('select');
select.name = 'dates[]';
select.className = 'form-select';
select.style.maxWidth = '260px';
select.required = true;
const def = document.createElement('option');
def.value = '';
def.disabled = true;
def.selected = true;
def.textContent = 'Select Sunday date';
select.appendChild(def);
AVAILABLE_DATES.forEach(function(d) {
const opt = document.createElement('option');
opt.value = d;
opt.textContent = d;
select.appendChild(opt);
});
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-outline-danger btn-sm';
btn.innerHTML = '<i class="bi bi-x-lg"></i> Remove';
btn.addEventListener('click', function(){ row.remove(); });
row.appendChild(select);
row.appendChild(btn);
container.appendChild(row);
}
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,45 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
< class="wrapper">
<div class="content"></div>
<h1>Academic Performance</h1>
<?= $this->include('partials/academic_filter') ?>
<div class="table-responsive">
<table class="table table-bordered">
<thead></thead>
<tr>
<th>Student ID</th>
<th>Name</th>
<th>Subject</th>
<th>Grade</th>
<th>Term</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$academicPerformance = [
['student_id' => 'S001', 'name' => 'John Doe', 'subject' => 'Mathematics', 'grade' => 'A', 'term' => '2024 Q1'],
['student_id' => 'S002', 'name' => 'Jane Smith', 'subject' => 'English', 'grade' => 'B+', 'term' => '2024 Q1'],
['student_id' => 'S003', 'name' => 'Michael Johnson', 'subject' => 'Science', 'grade' => 'A-', 'term' => '2024 Q1'],
// Add more records as needed
];
foreach ($academicPerformance as $performance) {
echo "<tr>
<td>{$performance['student_id']}</td>
<td>{$performance['name']}</td>
<td>{$performance['subject']}</td>
<td>{$performance['grade']}</td>
<td>{$performance['term']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,42 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>administrator Attendance Records</h1>
<table class="table table-striped">
<thead>
<tr>
<th>administrator Name</th>
<th>Date</th>
<th>Time In</th>
<th>Time Out</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in real scenario, this will be fetched from the database
$attendanceRecords = [
['name' => 'John Doe', 'date' => '2024-06-01', 'time_in' => '09:00 AM', 'time_out' => '05:00 PM', 'status' => 'Present'],
['name' => 'Jane Smith', 'date' => '2024-06-01', 'time_in' => '09:15 AM', 'time_out' => '05:15 PM', 'status' => 'Present'],
['name' => 'Alice Johnson', 'date' => '2024-06-01', 'time_in' => '09:30 AM', 'time_out' => '04:45 PM', 'status' => 'Late'],
// Add more records as needed
];
foreach ($attendanceRecords as $record) {
$dateDisp = !empty($record['date']) ? local_date($record['date'], 'm-d-Y') : '';
echo "<tr>
<td>{$record['name']}</td>
<td>" . esc($dateDisp) . "</td>
<td>{$record['time_in']}</td>
<td>{$record['time_out']}</td>
<td>{$record['status']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,44 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>administrator Profiles</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$administratorProfiles = [
['name' => 'John Doe', 'email' => 'john.doe@example.com', 'role' => 'Super administrator', 'status' => 'Active'],
['name' => 'Jane Smith', 'email' => 'jane.smith@example.com', 'role' => 'administrator', 'status' => 'Inactive'],
['name' => 'Emily Johnson', 'email' => 'emily.johnson@example.com', 'role' => 'administrator', 'status' => 'Active'],
// Add more profiles as needed
];
foreach ($administratorProfiles as $profile) {
echo "<tr>
<td>{$profile['name']}</td>
<td>{$profile['email']}</td>
<td>{$profile['role']}</td>
<td>{$profile['status']}</td>
<td>
<a href='/administrator/administrator_profiles/edit/{$profile['email']}' class='btn btn-primary btn-sm'>Edit</a>
<a href='/administrator/administrator_profiles/delete/{$profile['email']}' class='btn btn-danger btn-sm'>Delete</a>
</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,706 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
helper('url');
$dashboardEndpoint = $dashboardEndpoint ?? site_url('api/administrator/dashboard');
?>
<style>
/* --- Circular Stats --- */
.stat-circle {
position: relative;
width: 320px;
height: 320px;
border-radius: 50%;
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #dff0cfff;
font-weight: 700;
box-shadow: 0 6px 18px rgba(0, 0, 0, .15);
transition: transform .2s ease, box-shadow .2s ease;
}
.stat-circle:hover {
transform: scale(1.05);
box-shadow: 0 10px 28px rgba(0, 0, 0, .2);
}
.stat-value {
font-size: 2rem;
line-height: 1;
}
.stat-icon {
font-size: 2.5rem;
margin-bottom: .25rem;
opacity: .85;
}
.stat-title {
font-size: 1.6rem;
margin-top: .25rem;
font-weight: 500;
opacity: .9;
text-align: center;
line-height: 1.2;
word-break: break-word;
}
.circle-primary {
background: radial-gradient(circle at 30% 30%, #0d6efd, #0044aa);
}
.circle-success {
background: radial-gradient(circle at 30% 30%, #198754, #0c4f2c);
}
.circle-warning {
background: radial-gradient(circle at 30% 30%, #daa544ff, #a66f00);
}
.circle-info {
background: radial-gradient(circle at 30% 30%, #0dcaf0, #047e9c);
}
.circle-light {
background: radial-gradient(circle at 30% 30%, #63af4cff, #00eb7dff);
}
.stats-row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
justify-content: center;
}
/* Search results */
.sr-badge {
font-size: .825rem;
}
.small-muted {
font-size: .875rem;
color: #6c757d;
}
.admin-dashboard table.mgmt-sticky > thead > tr > th,
.admin-dashboard table.mgmt-sticky > thead > tr > td,
.admin-dashboard table thead th,
.admin-dashboard table thead td {
position: static !important;
top: auto !important;
z-index: auto !important;
box-shadow: none !important;
}
</style>
<div class="container-fluid admin-dashboard" data-dashboard-endpoint="<?= esc($dashboardEndpoint) ?>">
<div class="wrapper">
<div class="content"></div>
<main>
<!-- === Welcome Section === -->
<section class="mb-4">
<h2>Welcome, <?= esc((string)session()->get('firstname')) . ' ' . esc((string)session()->get('lastname')); ?></h2>
<p>Here is an overview of your site's activity.</p>
</section>
<!-- === Search Section === -->
<section class="mb-5">
<h2 class="mb-3">Search</h2>
<div id="dashboardAlert" class="alert alert-danger d-none" role="alert"></div>
<form action="<?= site_url('administrator/administratordashboard') ?>" method="get" class="mb-3">
<div class="input-group">
<input type="text"
class="form-control"
placeholder="Search name, email, phone, school ID, RFID…"
name="query"
value="<?= esc((string)($query ?? '')) ?>"
required>
<button class="btn btn-outline-secondary" type="submit">Search</button>
</div>
<div class="form-text">Supports Users, Parents, Staff, Students, and Emergency Contacts.</div>
</form>
<?php
// Convenience flags
$hasResults = !empty($results);
$isMerged = $hasResults && array_is_list($results); // new bundle format
$isLegacy = $hasResults && !$isMerged; // old flat arrays
?>
<?php if (!empty($query)): ?>
<div class="mb-2 small-muted">
Showing results for: <strong><?= esc($query) ?></strong>
</div>
<?php endif; ?>
<?php if ($hasResults): ?>
<?php if ($isMerged): ?>
<!-- ===== New merged bundle format ===== -->
<div class="accordion" id="searchResultsAccordion">
<?php foreach ($results as $idx => $bundle): ?>
<?php
$u = $bundle['user'] ?? null;
$fullName = trim(($u['firstname'] ?? '') . ' ' . ($u['lastname'] ?? ''));
$title = $fullName !== '' ? $fullName : 'User ID ' . (int)($u['id'] ?? 0);
$badgeCount =
count($bundle['students'] ?? []) +
count($bundle['emergency_contacts'] ?? []) +
count($bundle['parents'] ?? []) +
count($bundle['staff'] ?? []);
$headerId = 'sr-head-' . $idx;
$bodyId = 'sr-body-' . $idx;
?>
<div class="accordion-item mb-2">
<h2 class="accordion-header" id="<?= esc($headerId) ?>">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#<?= esc($bodyId) ?>" aria-expanded="false"
aria-controls="<?= esc($bodyId) ?>">
<div class="d-flex flex-column w-100">
<div class="d-flex align-items-center justify-content-between w-100">
<span>
<i class="bi bi-person-circle me-2"></i>
<?= esc($title) ?>
</span>
<span class="badge bg-primary sr-badge"><?= (int)$badgeCount ?></span>
</div>
<div class="small text-muted">
<?php if ($u): ?>
<?= esc($u['email'] ?? '') ?>
<?php if (!empty($u['cellphone'])): ?>
· <?= esc($u['cellphone']) ?>
<?php endif; ?>
<?php if (!empty($u['city']) || !empty($u['state'])): ?>
· <?= esc(trim(($u['city'] ?? '') . ', ' . ($u['state'] ?? ''), ', ')) ?>
<?php endif; ?>
<?php else: ?>
<em>No user profile row; matched via family/students.</em>
<?php endif; ?>
</div>
</div>
</button>
</h2>
<div id="<?= esc($bodyId) ?>" class="accordion-collapse collapse" aria-labelledby="<?= esc($headerId) ?>" data-bs-parent="#searchResultsAccordion">
<div class="accordion-body">
<!-- Parents / Family -->
<?php if (!empty($bundle['parents'])): ?>
<h6 class="mb-2"><i class="bi bi-people me-1"></i> Family</h6>
<div class="table-responsive mb-3">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>#</th>
<th>First Parent ID</th>
<th>Second Parent</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<?php foreach ($bundle['parents'] as $i => $p): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($p['firstparent_id']) ?></td>
<td><?= esc(trim(($p['secondparent_firstname'] ?? '') . ' ' . ($p['secondparent_lastname'] ?? ''))) ?></td>
<td><?= esc($p['secondparent_email'] ?? '') ?></td>
<td><?= esc($p['secondparent_phone'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<!-- Students -->
<?php if (!empty($bundle['students'])): ?>
<h6 class="mb-2"><i class="bi bi-mortarboard me-1"></i> Students</h6>
<div class="table-responsive mb-3">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>#</th>
<th>School ID</th>
<th>Name</th>
<th>Gender</th>
<th>DOB</th>
<th>RFID</th>
</tr>
</thead>
<tbody>
<?php foreach ($bundle['students'] as $i => $s): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($s['school_id']) ?></td>
<td><?= esc(trim(($s['firstname'] ?? '') . ' ' . ($s['lastname'] ?? ''))) ?></td>
<td><?= esc($s['gender'] ?? '') ?></td>
<td><?= esc($s['dob'] ?? '') ?></td>
<td><?= esc($s['rfid_tag'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<!-- Emergency Contacts -->
<?php if (!empty($bundle['emergency_contacts'])): ?>
<h6 class="mb-2"><i class="bi bi-telephone-outbound me-1"></i> Emergency Contacts</h6>
<div class="table-responsive mb-3">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Relation</th>
<th>Phone</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<?php foreach ($bundle['emergency_contacts'] as $i => $e): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($e['emergency_contact_name'] ?? '') ?></td>
<td><?= esc($e['relation'] ?? '') ?></td>
<td><?= esc($e['cellphone'] ?? '') ?></td>
<td><?= esc($e['email'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<!-- Staff roles -->
<?php if (!empty($bundle['staff'])): ?>
<h6 class="mb-2"><i class="bi bi-person-badge me-1"></i> Staff</h6>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>#</th>
<th>Role</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($bundle['staff'] as $i => $st): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($st['role_name'] ?? '') ?></td>
<td><?= esc(trim(($st['firstname'] ?? '') . ' ' . ($st['lastname'] ?? ''))) ?></td>
<td><?= esc($st['email'] ?? '') ?></td>
<td><?= esc($st['phone'] ?? '') ?></td>
<td><?= esc($st['active_role'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php if (empty($bundle['parents']) && empty($bundle['students']) && empty($bundle['emergency_contacts']) && empty($bundle['staff'])): ?>
<div class="text-muted">No related records.</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php elseif ($isLegacy): ?>
<!-- ===== Legacy flat format fallback ===== -->
<div class="row g-3">
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header"><i class="bi bi-people"></i> Users</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['users'])): ?>
<?php foreach ($results['users'] as $i => $r): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc(trim(($r['firstname'] ?? '') . ' ' . ($r['lastname'] ?? ''))) ?></td>
<td><?= esc($r['email'] ?? '') ?></td>
<td><?= esc($r['cellphone'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4" class="text-muted">No users found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header"><i class="bi bi-mortarboard"></i> Students</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>School ID</th>
<th>Name</th>
<th>DOB</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['students'])): ?>
<?php foreach ($results['students'] as $i => $s): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($s['school_id'] ?? '') ?></td>
<td><?= esc(trim(($s['firstname'] ?? '') . ' ' . ($s['lastname'] ?? ''))) ?></td>
<td><?= esc($s['dob'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4" class="text-muted">No students found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header"><i class="bi bi-people"></i> Parents</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>First Parent ID</th>
<th>Second Parent</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['parents'])): ?>
<?php foreach ($results['parents'] as $i => $p): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($p['firstparent_id'] ?? '') ?></td>
<td><?= esc(trim(($p['secondparent_firstname'] ?? '') . ' ' . ($p['secondparent_lastname'] ?? ''))) ?></td>
<td><?= esc($p['secondparent_email'] ?? '') ?></td>
<td><?= esc($p['secondparent_phone'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="5" class="text-muted">No parents found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header"><i class="bi bi-person-badge"></i> Staff</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>Role</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['staff'])): ?>
<?php foreach ($results['staff'] as $i => $st): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($st['role_name'] ?? '') ?></td>
<td><?= esc(trim(($st['firstname'] ?? '') . ' ' . ($st['lastname'] ?? ''))) ?></td>
<td><?= esc($st['email'] ?? '') ?></td>
<td><?= esc($st['phone'] ?? '') ?></td>
<td><?= esc($st['active_role'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="text-muted">No staff found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header"><i class="bi bi-telephone-outbound"></i> Emergency Contacts</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Relation</th>
<th>Phone</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<?php if (!empty($results['emergency_contacts'])): ?>
<?php foreach ($results['emergency_contacts'] as $i => $e): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= esc($e['emergency_contact_name'] ?? '') ?></td>
<td><?= esc($e['relation'] ?? '') ?></td>
<td><?= esc($e['cellphone'] ?? '') ?></td>
<td><?= esc($e['email'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="5" class="text-muted">No emergency contacts found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php elseif (!empty($query)): ?>
<div class="alert alert-warning mb-0">No matches found.</div>
<?php endif; ?>
</section>
<!-- === Statistics Section (circular design) === -->
<section class="mb-5">
<h2 class="mb-4 text-center">Statistics</h2>
<div class="stats-row">
<!-- Students -->
<div class="stat-circle circle-primary">
<div class="stat-icon"><i class="bi bi-people-fill"></i></div>
<div class="stat-value" data-stat="students">—</div>
<div class="stat-title">Students</div>
</div>
<!-- Teachers -->
<div class="stat-circle circle-info">
<div class="stat-icon"><i class="bi bi-person-video2"></i></div>
<div class="stat-value" data-stat="teachers">—</div>
<div class="stat-title">Teachers</div>
</div>
<!-- Teacher Assistants -->
<div class="stat-circle circle-success">
<div class="stat-icon"><i class="bi bi-person-badge-fill"></i></div>
<div class="stat-value" data-stat="teacherAssistants">—</div>
<div class="stat-title">Teachers' Assistants</div>
</div>
<!-- Admins -->
<div class="stat-circle circle-warning">
<div class="stat-icon"><i class="bi bi-shield-lock-fill"></i></div>
<div class="stat-value" data-stat="admins">—</div>
<div class="stat-title">Admins</div>
</div>
<!-- Parents -->
<div class="stat-circle circle-light">
<div class="stat-icon"><i class="bi bi-people"></i></div>
<div class="stat-value" data-stat="parents">—</div>
<div class="stat-title">Parents</div>
</div>
</div>
</section>
<!-- === Recent Activities Section === -->
<section>
<h2 class="mb-3">Recent Activities</h2>
<table class="table mt-2" data-no-mgmt-sticky>
<thead>
<tr>
<th>Date</th>
<th>Activity</th>
<th>User</th>
</tr>
</thead>
<tbody data-activities-body>
<tr data-activities-placeholder>
<td colspan="3" class="text-muted">Loading recent activity…</td>
</tr>
</tbody>
</table>
</section>
</main>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('[data-dashboard-endpoint]');
if (!container) {
return;
}
const endpoint = container.dataset.dashboardEndpoint;
const alertBox = document.getElementById('dashboardAlert');
const numberFormatter = new Intl.NumberFormat();
const statElements = container.querySelectorAll('[data-stat]');
const activitiesBody = container.querySelector('[data-activities-body]');
const showError = (message) => {
if (!alertBox) {
return;
}
alertBox.textContent = message;
alertBox.classList.remove('d-none');
};
fetch(endpoint, {
headers: {
'Accept': 'application/json',
},
credentials: 'same-origin',
})
.then((response) => {
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return response.json();
})
.then((payload) => {
const counts = payload && typeof payload === 'object' && payload.counts
? payload.counts
: {};
statElements.forEach((element) => {
const key = element.dataset.stat;
if (!key) {
return;
}
const value = counts[key];
element.textContent = Number.isFinite(value)
? numberFormatter.format(value)
: '—';
});
if (!activitiesBody) {
return;
}
activitiesBody.innerHTML = '';
const activities = payload && Array.isArray(payload.recentActivities)
? payload.recentActivities
: [];
if (activities.length === 0) {
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 3;
cell.classList.add('text-muted');
cell.textContent = 'No recent activities found.';
row.appendChild(cell);
activitiesBody.appendChild(row);
return;
}
const formatDateTime = (value) => {
if (!value) {
return '—';
}
const date = new Date(value);
if (Number.isNaN(date.valueOf())) {
return String(value);
}
const pad = (num) => String(num).padStart(2, '0');
const datePart = `${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${date.getFullYear()}`;
const timePart = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
return `${datePart} ${timePart}`;
};
activities.forEach((activity) => {
const row = document.createElement('tr');
const toDisplayString = (value) => {
if (value === null || value === undefined || value === '') {
return '—';
}
return String(value);
};
const formattedTime = formatDateTime(activity && activity.login_time);
const timeCell = document.createElement('td');
timeCell.textContent = formattedTime;
const activityCell = document.createElement('td');
activityCell.textContent = 'Logged in';
const userCell = document.createElement('td');
userCell.textContent = toDisplayString(activity && activity.email);
row.appendChild(timeCell);
row.appendChild(activityCell);
row.appendChild(userCell);
activitiesBody.appendChild(row);
});
})
.catch((error) => {
if (activitiesBody) {
activitiesBody.innerHTML = '';
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 3;
cell.classList.add('text-danger');
cell.textContent = 'Failed to load recent activities.';
row.appendChild(cell);
activitiesBody.appendChild(row);
}
showError(error.message || 'Unable to load dashboard metrics.');
});
});
</script>
<?= $this->endSection() ?>
+37
View File
@@ -0,0 +1,37 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Attendance</h1>
<a href="/administrator/attendance/create" class="btn btn-primary">Add New Attendance Record</a>
</div>
<table class="table table-striped mt-3">
<thead>
<tr>
<th>ID</th>
<th>Student Name</th>
<th>Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($attendances)): ?>
<?php foreach ($attendances as $attendance): ?>
<tr>
<td><?php echo $attendance['id']; ?></td>
<td><?php echo $attendance['student_name']; ?></td>
<td><?= !empty($attendance['date']) ? esc(local_date($attendance['date'], 'm-d-Y')) : '' ?></td>
<td><?php echo $attendance['status']; ?></td>
<td>
<a href="/administrator/attendance/edit/<?php echo $attendance['id']; ?>" class="btn btn-warning">Edit</a>
<a href="/administrator/attendance/delete/<?php echo $attendance['id']; ?>" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this attendance record?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="5">No attendance records found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</main>
@@ -0,0 +1,45 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Attendance Records</h1>
<?= $this->include('partials/academic_filter') ?>
<table class="table table-striped">
<thead>
<tr>
<th>Student ID</th>
<th>Name</th>
<th>Date</th>
<th>Status</th>
<th>Remarks</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$attendanceRecords = [
['student_id' => 'S001', 'name' => 'John Doe', 'date' => '2024-07-01', 'status' => 'Present', 'remarks' => ''],
['student_id' => 'S002', 'name' => 'Jane Smith', 'date' => '2024-07-01', 'status' => 'Absent', 'remarks' => 'Sick'],
['student_id' => 'S003', 'name' => 'Michael Johnson', 'date' => '2024-07-01', 'status' => 'Present', 'remarks' => ''],
// Add more records as needed
];
foreach ($attendanceRecords as $record) {
$dateDisp = !empty($record['date']) ? local_date($record['date'], 'm-d-Y') : '';
echo "<tr>
<td>{$record['student_id']}</td>
<td>{$record['name']}</td>
<td>" . esc($dateDisp) . "</td>
<td>{$record['status']}</td>
<td>{$record['remarks']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,45 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Behavior Reports</h1>
<?= $this->include('partials/academic_filter') ?>
<table class="table table-striped">
<thead>
<tr>
<th>Report ID</th>
<th>Student ID</th>
<th>Name</th>
<th>Incident</th>
<th>Date</th>
<th>Action Taken</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$behaviorReports = [
['report_id' => 'B001', 'student_id' => 'S001', 'name' => 'John Doe', 'incident' => 'Disruptive in class', 'date' => '2024-07-01', 'action' => 'Parent meeting'],
['report_id' => 'B002', 'student_id' => 'S002', 'name' => 'Jane Smith', 'incident' => 'Fighting', 'date' => '2024-07-02', 'action' => 'Detention'],
['report_id' => 'B003', 'student_id' => 'S003', 'name' => 'Michael Johnson', 'incident' => 'Bullying', 'date' => '2024-07-03', 'action' => 'Suspension'],
// Add more records as needed
];
foreach ($behaviorReports as $report) {
$dateDisp = !empty($report['date']) ? local_date($report['date'], 'm-d-Y') : '';
echo "<tr>
<td>{$report['report_id']}</td>
<td>{$report['student_id']}</td>
<td>{$report['name']}</td>
<td>{$report['incident']}</td>
<td>" . esc($dateDisp) . "</td>
<td>{$report['action']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
+255
View File
@@ -0,0 +1,255 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<h2 class="text-center mb-3">Broadcast Email to Parents</h2>
<?php if ($msg = session()->getFlashdata('message')): ?>
<div class="alert alert-success"><?= esc($msg) ?></div>
<?php endif; ?>
<?php if ($err = session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc($err) ?></div>
<?php endif; ?>
<form method="post" action="<?= site_url('admin/broadcast-email/send') ?>" id="broadcastForm">
<?= csrf_field() ?>
<div class="card mb-3">
<div class="card-header">Email Details</div>
<div class="card-body row g-3">
<div class="col-md-6">
<label class="form-label">From (configured sender)</label>
<select class="form-select" name="from_key" required>
<?php foreach (($fromOptions ?? []) as $opt): ?>
<option value="<?= esc($opt['key']) ?>"><?= esc($opt['label']) ?></option>
<?php endforeach; ?>
</select>
<small class="text-muted">Senders come from MAIL_SENDERS (.env).</small>
</div>
<div class="col-md-6">
<label class="form-label">Send Mode</label>
<select name="mode" class="form-select" id="modeSelect">
<option value="personalized">Personalized (replaces {{name}})</option>
<option value="standard">Standard (no personalization)</option>
</select>
</div>
<div class="col-md-8">
<label class="form-label">Subject</label>
<input type="text" name="subject" class="form-control" placeholder="Subject..." required>
</div>
<div class="col-md-4">
<div class="form-check mt-4">
<input class="form-check-input" type="checkbox" value="1" id="wrap_layout" name="wrap_layout" checked>
<label class="form-check-label" for="wrap_layout">
Wrap with email layout
</label>
</div>
</div>
<div class="col-12">
<label class="form-label">Message (HTML). Use {{name}} in Personalized mode.</label>
<textarea id="body_html" name="body_html" class="form-control" rows="10" placeholder="Dear {{name}}, ..."></textarea>
</div>
<!-- Optional fields your layout can use -->
<div class="col-md-6">
<label class="form-label">Preheader (optional)</label>
<input type="text" name="preheader" class="form-control" placeholder="Short preview text shown by mail clients">
</div>
<div class="col-md-3">
<label class="form-label">CTA Text (optional)</label>
<input type="text" name="cta_text" class="form-control" placeholder="e.g., View Details">
</div>
<div class="col-md-3">
<label class="form-label">CTA URL (optional)</label>
<input type="url" name="cta_url" class="form-control" placeholder="https://...">
</div>
</div>
<div class="card-footer d-flex gap-2 align-items-center flex-wrap">
<div class="input-group" style="max-width: 420px;">
<span class="input-group-text">Test email</span>
<input type="email" name="test_email" class="form-control" placeholder="you@example.com">
<button class="btn btn-outline-primary" name="send_test_only" value="1" type="submit">Send Test Only</button>
</div>
<span class="ms-auto fw-semibold">Selected: <span id="selectedCount">0</span></span>
<button type="submit" class="btn btn-primary">Send Broadcast</button>
</div>
</div>
<div class="card">
<div class="card-header d-flex justify-content-between">
<span>Parents</span>
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-secondary" id="selectAll">Select All</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="clearAll">Clear</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="parentsTable" class="table table-striped table-hover align-middle w-100">
<thead class="table-dark">
<tr>
<th style="width:40px;">#</th>
<th>Name</th>
<th>Email</th>
<th>School ID</th>
</tr>
</thead>
<tbody>
<?php foreach (($parents ?? []) as $p): ?>
<tr>
<td><input type="checkbox" class="row-check" value="<?= (int)$p['id'] ?>"></td>
<td><?= esc(($p['firstname'] ?? '') . ' ' . ($p['lastname'] ?? '')) ?></td>
<td><?= esc($p['email'] ?? '') ?></td>
<td><?= esc($p['school_id'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<input type="hidden" id="selectedIdsCarrier" name="parent_ids[]" value="">
</div>
</div>
</form>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/dataTables.bootstrap5.min.css">
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.8/js/dataTables.bootstrap5.min.js"></script>
<!-- Summernote Lite (no Bootstrap dep) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-lite.min.css">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-lite.min.js"></script>
<script>
(function() {
const editor = $('#body_html');
const csrfName = '<?= csrf_token() ?>'; // e.g. "csrf_test_name"
let csrfHash = '<?= csrf_hash() ?>';
const CSRF_COOKIE_NAME = <?= json_encode(config('Security')->csrfCookieName ?? (config('Security')->cookieName ?? 'csrf_cookie_name')) ?>;
function getCookie(n){
return document.cookie.split(';').map(v=>v.trim()).find(v=>v.startsWith(n+'='))?.split('=')[1] || '';
}
function uploadImageFile(file) {
if (!file || !file.type || !file.type.startsWith('image/')) return;
if (file.size > 5 * 1024 * 1024) { alert('Image too large (max 5MB).'); return; }
const fd = new FormData();
fd.append('image', file);
fd.append(csrfName, csrfHash); // 👈 CSRF in body
fetch('<?= site_url('admin/broadcast-email/upload-image') ?>', {
method: 'POST',
body: fd,
credentials: 'same-origin', // 👈 send cookies
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': csrfHash, // 👈 CSRF in header (CodeIgniter accepts this too)
}
})
.then(async (r) => {
// Grab fresh token if CI rotated it
const newHash = r.headers.get('X-CSRF-HASH');
if (newHash) csrfHash = newHash; else {
// Fallback to cookie (CI rotates token after POST)
try {
const c = decodeURIComponent(getCookie(CSRF_COOKIE_NAME) || '');
if (c) csrfHash = c;
} catch(_) {}
}
// If server returned HTML error (CSRF page), show it
const text = await r.text();
try { return JSON.parse(text); } catch { throw new Error(text || 'Upload failed'); }
})
.then(json => {
if (!json.success) throw new Error(json.error || 'Upload failed');
if (json.csrf_hash) csrfHash = json.csrf_hash; // another place to refresh
editor.summernote('insertImage', json.url, file.name || 'image');
})
.catch(err => {
console.error(err);
alert('Image upload failed: ' + err.message);
});
}
editor.summernote({
height: 260,
toolbar: [
['style', ['bold','italic','underline','clear']],
['para', ['ul','ol','paragraph']],
['insert',['link','table','hr']],
['font', ['superscript','subscript']],
['view', ['undo','redo','codeview']]
],
callbacks: {
onImageUpload: function(files) { for (let f of files) uploadImageFile(f); },
onPaste: function(e) {
const cd = (e.originalEvent || e).clipboardData;
if (!cd || !cd.items) return;
for (let it of cd.items) {
if (it.kind === 'file' && it.type.startsWith('image/')) {
e.preventDefault();
uploadImageFile(it.getAsFile());
}
}
}
}
});
})();
</script>
<script>
(function() {
const selected = new Set();
const table = $('#parentsTable').DataTable({
pageLength: 100,
lengthMenu: [10, 25, 50, 100, 250],
order: [[1, 'asc']]
});
function refreshSelected() {
$('#selectedCount').text(selected.size);
document.getElementById('selectedIdsCarrier').value = Array.from(selected).join(',');
}
$('#parentsTable').on('draw.dt', function() {
document.querySelectorAll('#parentsTable .row-check').forEach(chk => {
const id = parseInt(chk.value, 10);
chk.checked = selected.has(id);
});
});
$('#parentsTable').on('change', '.row-check', function() {
const id = parseInt(this.value, 10);
if (this.checked) selected.add(id); else selected.delete(id);
refreshSelected();
});
document.getElementById('selectAll').addEventListener('click', function() {
table.rows({page:'current'}).nodes().to$().find('.row-check').each(function() {
this.checked = true;
selected.add(parseInt(this.value, 10));
});
refreshSelected();
});
document.getElementById('clearAll').addEventListener('click', function() {
table.rows({page:'current'}).nodes().to$().find('.row-check').each(function() {
this.checked = false;
selected.delete(parseInt(this.value, 10));
});
refreshSelected();
});
})();
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,44 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Budget Reports</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Report ID</th>
<th>Period</th>
<th>Total Income</th>
<th>Total Expenses</th>
<th>Net Balance</th>
<th>Date Generated</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$budgetReports = [
['report_id' => 'BR001', 'period' => 'Q1 2024', 'total_income' => '$5000', 'total_expenses' => '$3000', 'net_balance' => '$2000', 'date_generated' => '2024-07-01'],
['report_id' => 'BR002', 'period' => 'Q2 2024', 'total_income' => '$7000', 'total_expenses' => '$4000', 'net_balance' => '$3000', 'date_generated' => '2024-07-02'],
['report_id' => 'BR003', 'period' => 'Q3 2024', 'total_income' => '$8000', 'total_expenses' => '$5000', 'net_balance' => '$3000', 'date_generated' => '2024-07-03'],
// Add more records as needed
];
foreach ($budgetReports as $report) {
$dateDisp = !empty($report['date_generated']) ? local_date($report['date_generated'], 'm-d-Y') : '';
echo "<tr>
<td>{$report['report_id']}</td>
<td>{$report['period']}</td>
<td>{$report['total_income']}</td>
<td>{$report['total_expenses']}</td>
<td>{$report['net_balance']}</td>
<td>" . esc($dateDisp) . "</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
+69
View File
@@ -0,0 +1,69 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-5">
<div class="container">
<div class="main-content">
<div id="calendar-container">
<div id="calendar"></div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const calendarEl = document.getElementById('calendar');
const isMobile = window.innerWidth <= 576;
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: isMobile ? 'listWeek' : 'dayGridMonth',
height: 'auto',
contentHeight: 'auto',
expandRows: true,
events: <?= json_encode($events) ?>,
eventDidMount: function(info) {
if (info.event.extendedProps.description && window.bootstrap && bootstrap.Tooltip) {
try {
new bootstrap.Tooltip(info.el, {
title: info.event.extendedProps.description,
placement: 'top',
trigger: 'hover',
container: 'body'
});
} catch (e) { /* no-op */ }
}
},
eventContent: function(arg) {
const event = arg.event;
const bg = event.extendedProps.backgroundColor || '#3788d8';
return {
html: `
<div style="
background-color: ${bg};
padding: 4px;
border-radius: 4px;
color: #fff;
font-size: 0.8rem;
text-align: center;">
${event.title}
</div>`
};
},
windowResize: function(view) {
if (window.innerWidth <= 576 && calendar.view.type !== 'listWeek') {
calendar.changeView('listWeek');
} else if (window.innerWidth > 576 && calendar.view.type !== 'dayGridMonth') {
calendar.changeView('dayGridMonth');
}
calendar.updateSize();
}
});
calendar.render();
window.calendar = calendar;
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,127 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">Add Event</h2>
<?php if (isset($validation)): ?>
<div class="alert alert-danger">
<?= $validation->listErrors(); ?>
</div>
<?php endif; ?>
<?php
$request = service('request');
$prefilledSchoolYear = set_value('school_year');
if ($prefilledSchoolYear === '') {
$prefilledSchoolYear = $request->getGet('school_year') ?: (function_exists('getSchoolYear') ? (string) getSchoolYear() : '');
}
$prefilledSemester = set_value('semester');
if ($prefilledSemester === '') {
$prefilledSemester = $request->getGet('semester') ?: (function_exists('getSemester') ? (string) getSemester() : '');
}
?>
<form action="/administrator/calendar_add_event" method="post">
<?= csrf_field() ?>
<div class="form-group mb-3">
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="no_school" name="no_school" value="1"
<?= set_checkbox('no_school', '1') ?>>
<label class="form-check-label" for="no_school">No School Day</label>
</div>
<label for="title">Title<span class="text-danger">*</span></label>
<input type="text" id="title" name="title" class="form-control"
value="<?= set_value('title'); ?>" required>
</div>
<div class="form-group mb-3">
<label for="description">Description:</label>
<textarea id="description" name="description" class="form-control"><?= set_value('description'); ?></textarea>
</div>
<div class="form-group mb-3">
<label for="event_type">Event Type:</label>
<select id="event_type" name="event_type" class="form-select"
<?= empty($eventTypes) ? 'disabled' : '' ?>>
<option value="">— Select event type —</option>
<?php foreach ($eventTypes ?? [] as $type): ?>
<option value="<?= esc($type) ?>" <?= set_select('event_type', $type) ?>>
<?= esc($type) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group mb-3">
<label for="start">Event Date <span class="text-danger">*</span></label>
<input type="date" id="start" name="start" class="form-control"
value="<?= set_value('start'); ?>" required>
</div>
<div class="form-group mt-3">
<label>Calendar Display:</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="notify_parent" name="notify_parent" value="1"
<?= set_checkbox('notify_parent', '1') ?>>
<label class="form-check-label" for="notify_parent">Parents</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="notify_teacher" name="notify_teacher" value="1"
<?= set_checkbox('notify_teacher', '1') ?>>
<label class="form-check-label" for="notify_teacher">Teachers</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="notify_admin" name="notify_admin" value="1"
<?= set_checkbox('notify_admin', '1') ?>>
<label class="form-check-label" for="notify_admin">Admins</label>
</div>
</div>
<div class="form-group mt-3">
<label>Email notifications (optional):</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="send_email_parent" name="send_email_parent" value="1"
<?= set_checkbox('send_email_parent', '1') ?>>
<label class="form-check-label" for="send_email_parent">Send to Parents</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="send_email_teacher" name="send_email_teacher" value="1"
<?= set_checkbox('send_email_teacher', '1') ?>>
<label class="form-check-label" for="send_email_teacher">Send to Teachers</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="send_email_admin" name="send_email_admin" value="1"
<?= set_checkbox('send_email_admin', '1') ?>>
<label class="form-check-label" for="send_email_admin">Send to Admins</label>
</div>
<small class="form-text text-muted">Emails are dispatched immediately upon saving the event.</small>
</div>
<div class="row g-3 mt-2">
<div class="col-md-6">
<label for="school_year" class="form-label">School Year</label>
<input type="text" id="school_year" name="school_year" class="form-control" value="<?= esc($prefilledSchoolYear) ?>" placeholder="e.g. 2025-2026">
</div>
<div class="col-md-6">
<label for="semester" class="form-label">Semester</label>
<select id="semester" name="semester" class="form-select">
<option value="">—</option>
<option value="Fall" <?= (strcasecmp($prefilledSemester, 'Fall') === 0 ? 'selected' : '') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($prefilledSemester, 'Spring') === 0 ? 'selected' : '') ?>>Spring</option>
</select>
</div>
</div>
<br>
<div class="d-flex gap-2 mb-3 justify-content-end">
<button type="submit" class="btn btn-primary">Add Event</button>
<a href="/administrator/calendar_view" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
<?= $this->endSection() ?>
+113
View File
@@ -0,0 +1,113 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">Edit Event</h2>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger">
<?= session()->getFlashdata('error'); ?>
</div>
<?php endif; ?>
<form action="/administrator/update/<?= $event['id']; ?>" method="post">
<?= csrf_field(); ?>
<div class="form-group mb-3">
<label>Calendar Display:</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="notify_parent" value="1"
<?= $event['notify_parent'] ? 'checked' : '' ?>>
<label class="form-check-label">Parents</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="notify_teacher" value="1"
<?= $event['notify_teacher'] ? 'checked' : '' ?>>
<label class="form-check-label">Teachers</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="notify_admin" value="1"
<?= $event['notify_admin'] ? 'checked' : '' ?>>
<label class="form-check-label">Admins</label>
</div>
</div>
<div class="form-group mb-3">
<label>Email notifications (optional):</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="send_email_parent" name="send_email_parent" value="1"
<?= set_checkbox('send_email_parent', '1') ?>>
<label class="form-check-label" for="send_email_parent">Send to Parents</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="send_email_teacher" name="send_email_teacher" value="1"
<?= set_checkbox('send_email_teacher', '1') ?>>
<label class="form-check-label" for="send_email_teacher">Send to Teachers</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="send_email_admin" name="send_email_admin" value="1"
<?= set_checkbox('send_email_admin', '1') ?>>
<label class="form-check-label" for="send_email_admin">Send to Admins</label>
</div>
<small class="form-text text-muted">Emails are dispatched immediately upon saving the event.</small>
</div>
<div class="form-group mb-3">
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="no_school" name="no_school" value="1"
<?= !empty($event['no_school']) ? 'checked' : '' ?>>
<label class="form-check-label" for="no_school">No School Day</label>
</div>
<label for="title">Title:</label>
<input type="text" id="title" name="title" class="form-control"
value="<?= esc($event['title']); ?>" required>
</div>
<div class="form-group mb-3">
<label for="description">Description:</label>
<textarea id="description" name="description" class="form-control"><?= esc($event['description']); ?></textarea>
</div>
<div class="form-group mb-3">
<label for="event_type">Event Type:</label>
<?php $currentEventType = (string)($event['event_type'] ?? ''); ?>
<select id="event_type" name="event_type" class="form-select">
<option value="">— Select event type —</option>
<?php foreach ($eventTypes ?? [] as $type): ?>
<option value="<?= esc($type) ?>" <?= $currentEventType === $type ? 'selected' : '' ?>>
<?= esc($type) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group mb-3">
<label for="start">Event Date:</label>
<input type="date" id="start" name="start" class="form-control"
value="<?= esc($event['date']); ?>" required>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label for="school_year" class="form-label">School Year</label>
<input type="text" id="school_year" name="school_year" class="form-control" value="<?= esc($event['school_year'] ?? (function_exists('getSchoolYear')?getSchoolYear():'')) ?>" placeholder="e.g. 2025-2026">
</div>
<div class="col-md-6">
<label for="semester" class="form-label">Semester</label>
<?php $semVal = (string)($event['semester'] ?? (function_exists('getSemester')?getSemester():'')); ?>
<select id="semester" name="semester" class="form-select">
<option value="">—</option>
<option value="Fall" <?= (strcasecmp($semVal,'Fall')===0?'selected':'') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal,'Spring')===0?'selected':'') ?>>Spring</option>
</select>
</div>
</div>
<div class="d-flex gap-2 mb-3 justify-content-end">
<button type="submit" class="btn btn-success">Update Event</button>
<a href="/administrator/calendar_view" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
<?= $this->endSection() ?>
+146
View File
@@ -0,0 +1,146 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<br>
<h2 class="text-center mt-4 mb-3">School Calendar</h2>
<?php
$addParams = array_filter([
'school_year' => $schoolYear ?? '',
'semester' => $semester ?? '',
], fn($value) => $value !== '');
$addQuery = $addParams ? ('?' . http_build_query($addParams)) : '';
?>
<div class="d-flex gap-2 mb-3 justify-content-end">
<a href="/administrator/calendar_add_event<?= $addQuery ?>" class="btn btn-success">Add New Event</a>
</div>
<br>
<!-- School Year / Semester Filter Form -->
<form method="get" action="<?= base_url('administrator/calendar_view') ?>" class="mb-3 text-center d-flex gap-2 justify-content-center align-items-end flex-wrap">
<div>
<label for="school_year" class="form-label">School Year:</label>
<select name="school_year" id="school_year" class="form-select w-auto d-inline-block ms-2">
<?php foreach (range(date('Y') + 1, 2023) as $y):
$sy = ($y - 1) . '-' . $y; ?>
<option value="<?= $sy ?>" <?= $sy === ($schoolYear ?? '') ? 'selected' : '' ?>>
<?= $sy ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label for="semester" class="form-label">Semester:</label>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<select name="semester" id="semester" class="form-select w-auto d-inline-block ms-2">
<option value="">—</option>
<option value="Fall" <?= (strcasecmp($semVal,'Fall')===0?'selected':'') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal,'Spring')===0?'selected':'') ?>>Spring</option>
</select>
</div>
<div>
<button type="submit" class="btn btn-secondary">Apply</button>
<a href="<?= base_url('administrator/calendar_view') ?>" class="btn btn-outline-secondary">Reset</a>
</div>
</form>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success">
<?= session()->getFlashdata('success') ?>
</div>
<?php endif; ?>
<?php
$emailStatus = session()->getFlashdata('email_status');
$emailStatusClass = session()->getFlashdata('email_status_class') ?? 'info';
?>
<?php if ($emailStatus): ?>
<div class="alert alert-<?= esc($emailStatusClass) ?>">
<?= esc($emailStatus) ?>
</div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger">
<?= session()->getFlashdata('error') ?>
</div>
<?php endif; ?>
<div class="table-responsive mb-3">
<table id="myTable" class="display">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Type</th>
<th>Description</th>
<th>Date</th>
<th>Notify Parent</th>
<th>Notify Teacher</th>
<th>Notify Admin</th>
<th>No School</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($events)): ?>
<?php if ($emailStatus): ?>
<tr class="table-warning">
<td colspan="10" class="text-center small">
<?= esc($emailStatus) ?>
</td>
</tr>
<?php endif; ?>
<?php foreach ($events as $event): ?>
<tr>
<td><?= esc($event['id']) ?></td>
<td><?= esc($event['title']) ?></td>
<td><?= esc($event['event_type'] ?? '—') ?></td>
<td><?= esc($event['description']) ?></td>
<td><?= esc(!empty($event['date']) ? date('m-d-Y', strtotime($event['date'])) : '') ?></td>
<td>
<input type="checkbox" disabled <?= $event['notify_parent'] ? 'checked' : '' ?>>
</td>
<td>
<input type="checkbox" disabled <?= $event['notify_teacher'] ? 'checked' : '' ?>>
</td>
<td>
<input type="checkbox" disabled <?= $event['notify_admin'] ? 'checked' : '' ?>>
</td>
<td>
<input type="checkbox" disabled <?= !empty($event['no_school']) ? 'checked' : '' ?>>
</td>
<td>
<a href="/administrator/calendar_edit/<?= $event['id'] ?>" class="btn btn-primary btn-sm">Edit</a>
<form action="/administrator/calendar_delete/<?= $event['id'] ?>" method="post" style="display:inline;">
<?= csrf_field() ?>
<button type="submit" class="btn btn-danger btn-sm"
onclick="return confirm('Are you sure you want to delete this event?')">Delete</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="10" class="text-center">No events found</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
$(document).ready(function() {
$('#myTable').DataTable();
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,159 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">Classes Lists</h2>
<form method="get" class="row g-2 align-items-center justify-content-center mb-3">
<div class="col-auto">
<label class="form-label mb-0">School year</label>
</div>
<div class="col-auto">
<select name="school_year" class="form-select form-select-sm" style="min-width: 200px;">
<?php if (!empty($schoolYears)): ?>
<?php foreach ($schoolYears as $yearOption): ?>
<option value="<?= esc($yearOption) ?>" <?= ($selectedYear === $yearOption ? 'selected' : '') ?>>
<?= esc($yearOption) ?>
</option>
<?php endforeach; ?>
<?php else: ?>
<option value="<?= esc($schoolYear ?? '') ?>" <?= ($selectedYear === ($schoolYear ?? '') ? 'selected' : '') ?>>
<?= esc($schoolYear ?? 'Current') ?>
</option>
<?php endif; ?>
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-secondary btn-sm">Apply</button>
<a class="btn btn-outline-secondary btn-sm" href="<?= base_url('administrator/class_assignment') ?>">Reset</a>
</div>
</form>
<?php if (session()->getFlashdata('message')): ?>
<div class="alert alert-success">
<?= session()->getFlashdata('message') ?>
</div>
<?php endif; ?>
<?php if (!empty($classSections)): ?>
<div id="classAccordion">
<?php foreach ($classSections as $index => $classSection): ?>
<div class="card">
<div class="card-header" id="heading<?= $index ?>">
<h2 class="mb-0">
<button class="btn btn-link w-100 text-start" type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse<?= $index ?>"
aria-expanded="<?= $index === 0 ? 'true' : 'false' ?>"
aria-controls="collapse<?= $index ?>">
Grade : <?= esc($classSection['class_section_name']) ?>
</button>
</h2>
</div>
<div id="collapse<?= $index ?>" class="collapse" aria-labelledby="heading<?= $index ?>" data-bs-parent="#classAccordion">
<div class="card-body">
<h5>Main Teacher:
<?= !empty($classSection['main_teachers'])
? esc(implode(', ', $classSection['main_teachers']))
: 'None' ?>
</h5>
<h5>Teacher-Assistant:
<?= !empty($classSection['teacher_assistants'])
? esc(implode(', ', $classSection['teacher_assistants']))
: 'None' ?>
</h5>
<?php $displaySemester = $selectedSemester ?? ''; ?>
<?php if ($displaySemester === '' && isset($classSection['semester'])): ?>
<?php $displaySemester = $classSection['semester']; ?>
<?php endif; ?>
<h5>Semester: <?= esc($displaySemester ?: 'N/A') ?></h5>
<h5>School Year: <?= esc($classSection['school_year']) ?></h5>
<p><?= esc($classSection['description']) ?></p>
<?php if (!empty($classSection['students'])): ?>
<table id="myTable<?= $index ?>" class="display">
<thead>
<tr>
<th>School ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Age</th>
<th>Gender</th>
<th>Photo Consent</th>
<th>Tuition Paid</th>
</tr>
</thead>
<tbody>
<?php foreach ($classSection['students'] as $student): ?>
<tr>
<td><?= esc($student['school_id']) ?></td>
<td>
<?php $sid = (int)($student['id'] ?? 0); ?>
<?php if ($sid): ?>
<a href="#" class="text-decoration-none" data-family-student-id="<?= $sid ?>">
<?= esc($student['firstname']) ?>
</a>
<?php else: ?>
<?= esc($student['firstname']) ?>
<?php endif; ?>
</td>
<td>
<?php if (!empty($sid)): ?>
<a href="#" class="text-decoration-none" data-family-student-id="<?= $sid ?>">
<?= esc($student['lastname']) ?>
</a>
<?php else: ?>
<?= esc($student['lastname']) ?>
<?php endif; ?>
</td>
<td><?= esc($student['age']) ?></td>
<td><?= esc($student['gender']) ?></td>
<td><?= esc($student['photo_consent']) ?></td>
<td><?= esc($student['tuition_paid']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p>No students registered in this class section.</p>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<p>No class sections available.</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
$(document).ready(function() {
<?php foreach ($classSections as $index => $section): ?>
$('#myTable<?= $index ?>').DataTable({
"paging": true, // enable pagination
"lengthChange": true, // allow changing number of rows per page
"pageLength": 50, // default rows per page
"info": true, // show "Showing X to Y of Z entries"
"language": {
"info": "Showing _START_ to _END_ of _TOTAL_ entries (Page _PAGE_ of _PAGES_)",
"infoEmpty": "No entries to show",
"infoFiltered": "(filtered from _MAX_ total entries)",
"lengthMenu": "Show _MENU_ entries per page"
}
});
<?php endforeach; ?>
});
</script>
<?= $this->endSection() ?>
+38
View File
@@ -0,0 +1,38 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Classes</h1>
<a href="/administrator/classsection/create" class="btn btn-primary">Add New Class</a>
</div>
<table class="table table-striped mt-3">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($classsection)): ?>
<?php foreach ($classsection as $classsec): ?>
<tr>
<td><?php echo $classsec['id']; ?></td>
<td><?php echo $classsec['name']; ?></td>
<td><?php echo $classsec['description']; ?></td>
<td>
<a href="/administrator/classsection/edit/<?php echo $classsec['id']; ?>"
class="btn btn-warning">Edit</a>
<a href="/administrator/classsection/delete/<?php echo $classsec['id']; ?>" class="btn btn-danger"
onclick="return confirm('Are you sure you want to delete this class?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4">No classsection found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</main>
@@ -0,0 +1,40 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="modal fade" id="classWarningModal" tabindex="-1" aria-labelledby="classWarningLabel" aria-hidden="true" role="alertdialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-warning">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title" id="classWarningLabel">
<i class="bi bi-exclamation-triangle-fill me-2"></i> Warning
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-dark fs-6">
<?= esc($warningMessage ?? 'An unexpected issue occurred.') ?>
</div>
<div class="modal-footer justify-content-between">
<a href="<?= base_url('/administrator/student_class_assignment') ?>" class="btn btn-secondary">
<i class="bi bi-arrow-left-circle me-1"></i> Back to Class Assignment
</a>
<!-- Optional dynamic action (add if needed) -->
<!-- <a href="<?= base_url('/some-action') ?>" class="btn btn-warning">Proceed</a> -->
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
const classWarningModalEl = document.getElementById('classWarningModal');
if (classWarningModalEl) {
const modal = new bootstrap.Modal(classWarningModalEl);
modal.show();
}
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,44 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Communication Logs</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Date</th>
<th>Sender</th>
<th>Recipient</th>
<th>Message</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$communicationLogs = [
['date' => '2024-07-01', 'sender' => 'Teacher A', 'recipient' => 'Parent B', 'message' => 'Discussed student performance.', 'status' => 'Sent'],
['date' => '2024-07-02', 'sender' => 'Teacher C', 'recipient' => 'Parent D', 'message' => 'Meeting request.', 'status' => 'Received'],
['date' => '2024-07-03', 'sender' => 'administrator', 'recipient' => 'Teacher A', 'message' => 'Policy update.', 'status' => 'Sent'],
// Add more logs as needed
];
foreach ($communicationLogs as $log) {
$dateDisp = !empty($log['date']) ? local_date($log['date'], 'm-d-Y') : '';
echo "<tr>
<td>" . esc($dateDisp) . "</td>
<td>{$log['sender']}</td>
<td>{$log['recipient']}</td>
<td>{$log['message']}</td>
<td>{$log['status']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,56 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Course Materials</h1>
<div class="mb-3">
<form action="/administrator/upload_material" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="materialTitle">Material Title</label>
<input type="text" class="form-control" id="materialTitle" name="title" required>
</div>
<div class="form-group">
<label for="materialFile">Upload File</label>
<input type="file" class="form-control" id="materialFile" name="file" required>
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</form>
</div>
<h2>Uploaded Materials</h2>
<table class="table table-striped">
<thead>
<tr>
<th>Title</th>
<th>File</th>
<th>Uploaded At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$courseMaterials = [
['title' => 'Lecture Notes 1', 'file' => 'lecture_notes_1.pdf', 'uploaded_at' => '2023-01-10'],
['title' => 'Assignment 1', 'file' => 'assignment_1.pdf', 'uploaded_at' => '2023-01-12'],
// Add more materials as needed
];
foreach ($courseMaterials as $material) {
echo "<tr>
<td>{$material['title']}</td>
<td><a href='/uploads/{$material['file']}' target='_blank'>{$material['file']}</a></td>
<td>{$material['uploaded_at']}</td>
<td>
<a href='/administrator/delete_material/{$material['file']}' class='btn btn-danger btn-sm'>Delete</a>
</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
+25
View File
@@ -0,0 +1,25 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Add New Parent</h1>
</div>
<form action="/administrator/parent/store" method="post">
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" class="form-control" id="firstname" name="firstname" required>
</div>
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" id="lastname" name="lastname" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</main>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,471 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?= csrf_meta() ?>
<?php
// Exclude unwanted grade buckets globally (e.g., legacy Grade 10/11) unless the bucket is the Arabic program.
$hiddenGrades = [10, 11];
$hasArabic = static function (array $sections): bool {
foreach ($sections as $section) {
$name = strtolower((string)($section['class_section_name'] ?? ''));
$code = strtolower((string)($section['class_section_id'] ?? ''));
if (strpos($name, 'arabic') !== false || strpos($code, 'arabic') !== false) {
return true;
}
}
return false;
};
foreach ($hiddenGrades as $hid) {
if (isset($grades[$hid]) && $hasArabic($grades[$hid])) {
continue; // keep Arabic sections even if the class_id matches a hidden grade bucket
}
unset($grades[$hid]);
}
// Prune out sections with no students (extra safety so empty Arabic-2/3 never render)
$filteredGrades = [];
foreach ($grades as $cid => $sections) {
$kept = [];
foreach ($sections as $section) {
$secKey = (string)($section['class_section_id'] ?? ($section['id'] ?? ''));
if ($secKey !== '' && !empty($studentsBySection[$secKey])) {
$kept[] = $section;
}
}
if (!empty($kept)) {
$filteredGrades[$cid] = $kept;
}
}
$grades = $filteredGrades;
?>
<div class="container-fluid">
<div class="wrapper">
<div class="content mb-3"></div>
<h2 class="text-center mt-4 mb-3">Attendance Analysis</h2>
<?= $this->include('partials/academic_filter') ?>
<?= $this->section('styles') ?>
<style>
.attn-analysis-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1rem;
}
.attn-analysis-card {
background: #fff;
border: 1px solid rgba(0,0,0,0.08);
border-radius: .5rem;
padding: 1rem;
}
.attn-analysis-card h6 { margin-bottom: .75rem; }
.attn-analysis-wide { grid-column: span 12; }
.attn-analysis-half { grid-column: span 6; }
.attn-analysis-table {
width: 100%;
font-size: .9rem;
}
.attn-analysis-table th,
.attn-analysis-table td { padding: .35rem .5rem; }
.attn-analysis-scroll {
max-height: none;
overflow: visible;
}
@media (max-width: 1200px) {
.attn-analysis-half { grid-column: span 12; }
}
</style>
<?= $this->endSection() ?>
<?php
$req = request();
$startDateRaw = trim((string)($req->getGet('start_date') ?? ''));
$endDateRaw = trim((string)($req->getGet('end_date') ?? ''));
$isValidDate = static function (string $d): bool {
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $d)) return false;
[$y, $m, $day] = array_map('intval', explode('-', $d));
return checkdate($m, $day, $y);
};
$filterStart = ($startDateRaw !== '' && $isValidDate($startDateRaw)) ? $startDateRaw : '';
$filterEnd = ($endDateRaw !== '' && $isValidDate($endDateRaw)) ? $endDateRaw : '';
// ---- Build labels and a stable order: KG -> Grade 1..12 -> Youth ----
$labelsByClassId = [];
$gradeNumberById = [];
$kgIds = [];
$arabicIds = [];
$youthIds = [];
foreach ($grades as $classId => $sections) {
$cid = (int)$classId;
$sampleName = '';
$isArabicProgram = false;
foreach ($sections as $s) {
$sampleName = (string)($s['class_section_name'] ?? '');
$lowName = strtolower($sampleName);
$lowCode = strtolower((string)($s['class_section_id'] ?? ''));
if (strpos($lowName, 'arabic') !== false || strpos($lowCode, 'arabic') !== false) {
$isArabicProgram = true;
}
if ($sampleName !== '') break;
}
if ($isArabicProgram || $cid === 14) {
$labelsByClassId[$cid] = 'Arabic';
$arabicIds[] = $cid;
} elseif (preg_match('/\bKG\b/i', $sampleName)) {
$labelsByClassId[$cid] = 'KG';
$kgIds[] = $cid;
} elseif (preg_match('/\bYouth\b/i', $sampleName)) {
$labelsByClassId[$cid] = 'Youth';
$youthIds[] = $cid;
} elseif (preg_match('/Grade\s*(\d+)/i', $sampleName, $m)) {
$labelsByClassId[$cid] = 'Grade ' . (int)$m[1];
$gradeNumberById[$cid] = (int)$m[1];
} else {
if ($cid === 0) {
$labelsByClassId[$cid] = 'KG';
$kgIds[] = $cid;
} elseif ($cid === 13) {
$labelsByClassId[$cid] = 'Youth';
$youthIds[] = $cid;
} else {
$labelsByClassId[$cid] = 'Grade ' . $cid;
$gradeNumberById[$cid] = $cid;
}
}
}
$sortedClassIds = [];
if ($kgIds) {
sort($kgIds, SORT_NUMERIC);
$sortedClassIds = array_merge($sortedClassIds, $kgIds);
}
if ($gradeNumberById) {
asort($gradeNumberById, SORT_NUMERIC);
$sortedClassIds = array_merge($sortedClassIds, array_keys($gradeNumberById));
}
if ($youthIds) {
sort($youthIds, SORT_NUMERIC);
$sortedClassIds = array_merge($sortedClassIds, $youthIds);
}
if ($arabicIds) {
sort($arabicIds, SORT_NUMERIC);
$sortedClassIds = array_merge($sortedClassIds, $arabicIds);
}
$presentIds = array_map('intval', array_keys($grades));
$missingIds = array_values(array_diff($presentIds, $sortedClassIds));
if (!empty($missingIds)) {
$sortedClassIds = array_merge($sortedClassIds, $missingIds);
}
$labelFor = function (int $id) use ($labelsByClassId): string {
return $labelsByClassId[$id] ?? ('Class ' . $id);
};
// ---- Attendance analysis ----
$analysisOverall = ['present' => 0, 'late' => 0, 'absent' => 0];
$analysisByGrade = [];
$analysisBySection = [];
$analysisTotal = 0;
foreach ($grades as $classId => $sections) {
$gradeLabel = $labelFor((int)$classId);
if (!isset($analysisByGrade[$gradeLabel])) {
$analysisByGrade[$gradeLabel] = ['present' => 0, 'late' => 0, 'absent' => 0];
}
foreach ($sections as $section) {
$sectionKey = (string)($section['class_section_id'] ?? ($section['id'] ?? ''));
if ($sectionKey === '' || empty($studentsBySection[$sectionKey])) continue;
$secNameRaw = trim((string)($section['class_section_name'] ?? ''));
$secLabel = $secNameRaw !== '' ? $secNameRaw : ('Section ' . $sectionKey);
if (!isset($analysisBySection[$secLabel])) {
$analysisBySection[$secLabel] = ['present' => 0, 'late' => 0, 'absent' => 0, 'total_students' => 0];
}
$sectionTotalStudents = count($studentsBySection[$sectionKey]);
if ($sectionTotalStudents > ($analysisBySection[$secLabel]['total_students'] ?? 0)) {
$analysisBySection[$secLabel]['total_students'] = $sectionTotalStudents;
}
foreach ($studentsBySection[$sectionKey] as $stu) {
$sid = (int)($stu['id'] ?? 0);
if ($sid <= 0) continue;
$entries = $attendanceData[$sectionKey][$sid] ?? [];
if (!is_array($entries)) continue;
foreach ($entries as $e) {
$d = substr((string)($e['date'] ?? ''), 0, 10);
if ($d === '') continue;
if ($filterStart !== '' && $d < $filterStart) continue;
if ($filterEnd !== '' && $d > $filterEnd) continue;
$st = strtolower(trim((string)($e['status'] ?? '')));
if (!in_array($st, ['present', 'late', 'absent'], true)) continue;
$analysisOverall[$st]++;
$analysisByGrade[$gradeLabel][$st]++;
$analysisBySection[$secLabel][$st]++;
$analysisTotal++;
}
}
}
}
$overallPercent = [];
foreach (['present', 'late', 'absent'] as $st) {
$overallPercent[$st] = $analysisTotal > 0 ? round(($analysisOverall[$st] * 100) / $analysisTotal, 1) : 0;
}
$analysisGradeLabels = [];
$analysisGradeAbsent = [];
$analysisGradeLate = [];
$analysisGradeTotals = [];
foreach ($sortedClassIds as $cid) {
$lbl = $labelFor((int)$cid);
$g = $analysisByGrade[$lbl] ?? ['present' => 0, 'late' => 0, 'absent' => 0];
$analysisGradeLabels[] = $lbl;
$analysisGradeAbsent[] = (int)($g['absent'] ?? 0);
$analysisGradeLate[] = (int)($g['late'] ?? 0);
$analysisGradeTotals[] = (int)($g['present'] ?? 0) + (int)($g['late'] ?? 0) + (int)($g['absent'] ?? 0);
}
$analysisSectionLabels = array_keys($analysisBySection);
$analysisSectionAbsent = [];
$analysisSectionLate = [];
$analysisSectionTotals = [];
foreach ($analysisSectionLabels as $lbl) {
$s = $analysisBySection[$lbl] ?? ['present' => 0, 'late' => 0, 'absent' => 0];
$analysisSectionAbsent[] = (int)($s['absent'] ?? 0);
$analysisSectionLate[] = (int)($s['late'] ?? 0);
$analysisSectionTotals[] = (int)($s['total_students'] ?? 0);
}
$totalDaysForPercent = 0;
if ($filterStart === '' && $filterEnd === '' && !empty($totalPassedDays)) {
$totalDaysForPercent = (int)$totalPassedDays;
} elseif (!empty($dateList) && is_array($dateList)) {
foreach ($dateList as $d) {
$d = (string)$d;
if ($d === '') continue;
if ($filterStart !== '' && $d < $filterStart) continue;
if ($filterEnd !== '' && $d > $filterEnd) continue;
if (!empty($noSchoolDays[$d])) continue;
$totalDaysForPercent++;
}
}
$backUrl = site_url('administrator/daily_attendance');
$queryParts = [];
if (!empty($semester)) $queryParts[] = 'semester=' . urlencode((string)$semester);
if (!empty($schoolYear)) $queryParts[] = 'school_year=' . urlencode((string)$schoolYear);
if ($queryParts) $backUrl .= '?' . implode('&', $queryParts);
?>
<div class="row justify-content-center mb-3">
<div class="col-auto">
<a class="btn btn-outline-secondary" href="<?= esc($backUrl) ?>">
Back to Daily Attendance
</a>
</div>
</div>
<form class="row g-2 justify-content-center mb-4" method="get" action="<?= esc(current_url()) ?>">
<?php if (!empty($semester)): ?>
<input type="hidden" name="semester" value="<?= esc((string)$semester) ?>">
<?php endif; ?>
<?php if (!empty($schoolYear)): ?>
<input type="hidden" name="school_year" value="<?= esc((string)$schoolYear) ?>">
<?php endif; ?>
<div class="col-12 col-md-3">
<label class="form-label">Start date</label>
<input type="date" class="form-control" name="start_date" value="<?= esc($filterStart) ?>">
</div>
<div class="col-12 col-md-3">
<label class="form-label">End date</label>
<input type="date" class="form-control" name="end_date" value="<?= esc($filterEnd) ?>">
</div>
<div class="col-12 col-md-auto d-flex align-items-end gap-2">
<button type="submit" class="btn btn-primary">Apply</button>
<a class="btn btn-outline-secondary" href="<?= esc(current_url()) ?>">Clear</a>
</div>
</form>
<div class="attn-analysis-grid">
<div class="attn-analysis-card attn-analysis-half">
<h6>Overall Distribution</h6>
<canvas id="attnPieChart" height="50" aria-label="Overall attendance pie chart"></canvas>
<table class="attn-analysis-table mt-3 no-mgmt-sticky" data-no-mgmt-sticky>
<thead>
<tr>
<th title="Attendance status for the selected date range">Status</th>
<th class="text-end" title="Number of attendance entries with this status">Count</th>
<th class="text-end" title="Percent of total attendance entries">Percent</th>
</tr>
</thead>
<tbody>
<tr>
<td>Present</td>
<td class="text-end"><?= (int)$analysisOverall['present'] ?></td>
<td class="text-end"><?= number_format($overallPercent['present'], 1) ?>%</td>
</tr>
<tr>
<td>Late</td>
<td class="text-end"><?= (int)$analysisOverall['late'] ?></td>
<td class="text-end"><?= number_format($overallPercent['late'], 1) ?>%</td>
</tr>
<tr>
<td>Absent</td>
<td class="text-end"><?= (int)$analysisOverall['absent'] ?></td>
<td class="text-end"><?= number_format($overallPercent['absent'], 1) ?>%</td>
</tr>
</tbody>
</table>
</div>
<div class="attn-analysis-card attn-analysis-half">
<h6>By Section (Absent / Late)</h6>
<canvas id="attnSectionChart" height="200" aria-label="Attendance by section bar chart"></canvas>
<div class="attn-analysis-scroll mt-3">
<table id="sectionAnalysisTable" class="attn-analysis-table no-mgmt-sticky" data-no-mgmt-sticky>
<thead>
<tr>
<th title="Class section name">Section</th>
<th class="text-end" title="Total semester days in range (excluding no-school days)">Semester Days</th>
<th class="text-end" title="Total students in the class section">Total Students</th>
<th class="text-end" title="Number of absent entries in range">Absent</th>
<th class="text-end" title="Absent entries as a percent of total possible attendance entries (total students × total days in range)">Absent%</th>
<th class="text-end" title="Number of late entries in range">Late</th>
<th class="text-end" title="Late entries as a percent of total possible attendance entries (total students × total days in range)">Late%</th>
</tr>
</thead>
<tbody>
<?php foreach ($analysisSectionLabels as $i => $lbl): ?>
<?php
$tot = (int)($analysisSectionTotals[$i] ?? 0);
$abs = (int)($analysisSectionAbsent[$i] ?? 0);
$late = (int)($analysisSectionLate[$i] ?? 0);
$denom = $totalDaysForPercent > 0 ? ($totalDaysForPercent * $tot) : 0;
$absPct = $denom > 0 ? round(($abs * 100) / $denom, 1) : 0;
$latePct = $denom > 0 ? round(($late * 100) / $denom, 1) : 0;
?>
<tr>
<td><?= esc($lbl) ?></td>
<td class="text-end"><?= (int)$totalDaysForPercent ?></td>
<td class="text-end"><?= $tot ?></td>
<td class="text-end"><?= $abs ?></td>
<td class="text-end"><?= number_format($absPct, 1) ?>%</td>
<td class="text-end"><?= $late ?></td>
<td class="text-end"><?= number_format($latePct, 1) ?>%</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ATTN_ANALYSIS = <?= json_encode([
'overall' => [
'labels' => ['Present', 'Late', 'Absent'],
'counts' => [
(int)($analysisOverall['present'] ?? 0),
(int)($analysisOverall['late'] ?? 0),
(int)($analysisOverall['absent'] ?? 0),
],
],
'byGrade' => [
'labels' => $analysisGradeLabels,
'absent' => $analysisGradeAbsent,
'late' => $analysisGradeLate,
],
'bySection' => [
'labels' => $analysisSectionLabels,
'absent' => $analysisSectionAbsent,
'late' => $analysisSectionLate,
],
]) ?>;
function percentOfTotal(value, total) {
if (!total) return '0%';
return (Math.round((value * 1000) / total) / 10).toFixed(1) + '%';
}
function buildAttendanceCharts() {
if (!window.Chart) return;
const pieEl = document.getElementById('attnPieChart');
const sectionEl = document.getElementById('attnSectionChart');
if (!pieEl || !sectionEl) return;
const overall = ATTN_ANALYSIS.overall || { labels: [], counts: [] };
const total = (overall.counts || []).reduce((a, b) => a + (parseInt(b, 10) || 0), 0);
new Chart(pieEl.getContext('2d'), {
type: 'pie',
data: {
labels: overall.labels,
datasets: [{
data: overall.counts,
backgroundColor: ['#198754', '#f0ad4e', '#dc3545'],
borderWidth: 1,
}]
},
options: {
plugins: {
tooltip: {
callbacks: {
label: function(ctx) {
const val = parseInt(ctx.parsed, 10) || 0;
return ctx.label + ': ' + val + ' (' + percentOfTotal(val, total) + ')';
}
}
},
legend: { position: 'bottom' }
}
}
});
new Chart(sectionEl.getContext('2d'), {
type: 'bar',
data: {
labels: ATTN_ANALYSIS.bySection?.labels || [],
datasets: [
{
label: 'Absent',
data: ATTN_ANALYSIS.bySection?.absent || [],
backgroundColor: '#dc3545',
},
{
label: 'Late',
data: ATTN_ANALYSIS.bySection?.late || [],
backgroundColor: '#f0ad4e',
}
]
},
options: {
plugins: { legend: { position: 'bottom' } },
scales: {
y: { beginAtZero: true, ticks: { precision: 0 } }
}
}
});
}
document.addEventListener('DOMContentLoaded', buildAttendanceCharts);
document.addEventListener('DOMContentLoaded', function () {
if (window.jQuery && $.fn.DataTable) {
$('#sectionAnalysisTable').DataTable({
paging: false,
searching: false,
info: false,
order: [[0, 'asc']]
});
}
});
</script>
<?= $this->endSection() ?>
+28
View File
@@ -0,0 +1,28 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Edit Parent</h1>
</div>
<form action="/administrator/parent/update/<?php echo $parent['id']; ?>" method="post">
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" class="form-control" id="firstname" name="firstname"
value="<?php echo $parent['firstname']; ?>" required>
</div>
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" id="lastname" name="lastname"
value="<?php echo $parent['lastname']; ?>" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" name="email" value="<?php echo $parent['email']; ?>"
required>
</div>
<div class="form-group">
<label for="password">Password (leave blank to keep current password)</label>
<input type="password" class="form-control" id="password" name="password">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</main>
+23
View File
@@ -0,0 +1,23 @@
<div class="content-wrapper">
<div class="container">
<h1>Edit Teacher</h1>
<form action="teacher/update/<?php echo $teacher['id']; ?>" method="POST">
<div class="form-group">
<label for="firstname">First Name:</label>
<input type="text" class="form-control" id="firstname" name="firstname"
value="<?php echo $teacher['firstname']; ?>" required>
</div>
<div class="form-group">
<label for="lastname">Last Name:</label>
<input type="text" class="form-control" id="lastname" name="lastname"
value="<?php echo $teacher['lastname']; ?>" required>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" class="form-control" id="email" name="email"
value="<?php echo $teacher['email']; ?>" required>
</div>
<button type="submit" class="btn btn-primary">Update</button>
</form>
</div>
</div>
@@ -0,0 +1,27 @@
<div class="container mt-5">
<h2>Edit Emergency Contact</h2>
<form action="<?= base_url('administrator/emergency_contact/update/' . $contact['id']) ?>" method="post">
<?= csrf_field() ?>
<div class="form-group">
<label>Name:</label>
<input type="text" class="form-control" name="emergency_contact_name"
value="<?= esc($contact['emergency_contact_name']) ?>" required>
</div>
<div class="form-group">
<label>Phone:</label>
<input type="text" class="form-control" name="cellphone" value="<?= esc($contact['cellphone']) ?>"
required>
</div>
<div class="form-group">
<label>Relation:</label>
<input type="text" class="form-control" name="relation" value="<?= esc($contact['relation']) ?>"
required>
</div>
<button type="submit" class="btn btn-primary">Update</button>
<a href="<?= base_url('administrator/emergency_contact') ?>" class="btn btn-secondary">Cancel</a>
</form>
</div>
@@ -0,0 +1,148 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-3">Emergency Contact Information</h2>
<?= $this->include('partials/academic_filter') ?>
<div class="table-responsive">
<table id="emergencyTable" class="display table table-bordered table-striped align-middle">
<thead class="table-dark">
<tr>
<th>Parent Name</th>
<th>Students</th>
<th>Contact Name</th>
<th>Relation</th>
<th>Phone</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php $modals = ''; ?>
<?php foreach ($groups as $group): ?>
<?php
// Build clickable student names (open Family Card)
$studentNames = array_map(function($s) {
$sid = (int)($s['id'] ?? $s['student_id'] ?? 0);
$label = esc(($s['firstname'] ?? '') . ' ' . ($s['lastname'] ?? ''));
if ($sid > 0) {
return '<a href="#" class="text-decoration-none" data-family-student-id="' . $sid . '">' . $label . '</a>';
}
return $label;
}, $group['students']);
$studentsCell = implode('<br>', $studentNames);
?>
<?php foreach ($group['contacts'] as $contact): ?>
<tr>
<td>
<?php $pid = (int)($group['parent_id'] ?? 0); ?>
<?php if ($pid): ?>
<a href="#" class="text-decoration-none" data-family-guardian-id="<?= $pid ?>">
<?= esc($group['parent_name']) ?>
</a>
<?php else: ?>
<?= esc($group['parent_name']) ?>
<?php endif; ?>
</td>
<td><?= $studentsCell ?></td>
<td><?= esc($contact['emergency_contact_name']) ?></td>
<td><?= esc($contact['relation']) ?></td>
<td>
<?php
$phone = trim((string)($contact['cellphone'] ?? ''));
if ($phone === '' && !empty($group['parent_phones']) && is_array($group['parent_phones'])) {
$phone = implode(' / ', array_map('esc', $group['parent_phones']));
}
echo $phone !== '' ? esc($phone) : 'N/A';
?>
</td>
<td>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal"
data-bs-target="#editContactModal<?= (int)$contact['id'] ?>">
Edit
</button>
<a href="<?= base_url('administrator/emergency_contact/delete/' . (int)$contact['id']) ?>"
class="btn btn-sm btn-danger"
onclick="return confirm('Are you sure?')">Delete</a>
</td>
</tr>
<?php
$modalId = (int)$contact['id'];
$modals .= '
<div class="modal fade"
id="editContactModal' . $modalId . '"
tabindex="-1"
aria-labelledby="editContactLabel' . $modalId . '"
aria-hidden="true"
data-bs-backdrop="static"
data-bs-keyboard="false">
<div class="modal-dialog" role="document">
<form action="' . base_url('administrator/emergency_contact/update/' . $modalId) . '"
method="post"
class="modal-content">
' . csrf_field() . '
<div class="modal-header">
<h5 class="modal-title" id="editContactLabel' . $modalId . '">Edit Emergency Contact</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="name_' . $modalId . '" class="form-label">Name</label>
<input type="text" class="form-control"
name="emergency_contact_name"
id="name_' . $modalId . '"
value="' . esc($contact['emergency_contact_name']) . '"
required>
</div>
<div class="mb-3">
<label for="phone_' . $modalId . '" class="form-label">Phone</label>
<input type="text" class="form-control"
name="cellphone"
id="phone_' . $modalId . '"
value="' . esc($contact['cellphone']) . '"
required>
</div>
<div class="mb-3">
<label for="relation_' . $modalId . '" class="form-label">Relation</label>
<input type="text" class="form-control"
name="relation"
id="relation_' . $modalId . '"
value="' . esc($contact['relation']) . '"
required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Update</button>
</div>
</form>
</div>
</div>';
?>
<?php endforeach; ?>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- All modals rendered outside the table -->
<?= $modals ?>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
$(document).ready(function() {
$('#emergencyTable').DataTable({
pageLength: 100,
lengthMenu: [5, 10, 25, 50, 100],
columnDefs: [
{ orderable: false, targets: [1, 5] } // Students + Actions columns not sortable
]
});
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,59 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<h2>Create Event</h2>
<form method="post" action="<?= site_url('administrator/events/create') ?>" enctype="multipart/form-data">
<?= csrf_field() ?>
<div class="mb-3">
<label class="form-label">Event Name</label>
<input type="text" name="event_name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Category</label>
<select name="event_category" class="form-control" required>
<option value="" selected disabled>Select category</option>
<?php foreach (($categories ?? []) as $category): ?>
<option value="<?= esc($category) ?>"><?= esc(ucwords($category)) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea name="description" class="form-control"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Amount</label>
<input type="number" step="0.01" name="amount" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Flyer (optional)</label>
<input type="file" name="flyer" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Expiration Date</label>
<input type="date" name="expiration_date" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Semester</label>
<input type="text" name="semester" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">School Year</label>
<input type="text" name="school_year" class="form-control" required>
</div>
<button type="submit" class="btn btn-success">Create Event</button>
<a href="<?= site_url('administrator/events') ?>" class="btn btn-secondary">Cancel</a>
</form>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,78 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<h2>Edit Event</h2>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
<?php endif; ?>
<form method="post" action="<?= site_url('administrator/events/edit/' . $event['id']) ?>" enctype="multipart/form-data">
<?= csrf_field() ?>
<div class="mb-3">
<label class="form-label">Event Name</label>
<input type="text" name="event_name" class="form-control" value="<?= esc($event['event_name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Category</label>
<select name="event_category" class="form-control" required>
<option value="" disabled <?= empty($event['event_category']) ? 'selected' : '' ?>>Select category</option>
<?php foreach (($categories ?? []) as $category): ?>
<option value="<?= esc($category) ?>" <?= (string)($event['event_category'] ?? '') === (string)$category ? 'selected' : '' ?>>
<?= esc(ucwords($category)) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea name="description" class="form-control"><?= esc($event['description']) ?></textarea>
</div>
<div class="mb-3">
<label class="form-label">Amount</label>
<input type="number" step="0.01" name="amount" class="form-control" value="<?= esc($event['amount']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Current Flyer</label><br>
<?php if ($event['flyer']): ?>
<img src="<?= base_url('writable/uploads/' . $event['flyer']) ?>" width="150" class="mb-2">
<?php else: ?>
<div class="text-muted">No flyer uploaded.</div>
<?php endif; ?>
</div>
<div class="mb-3">
<label class="form-label">New Flyer (optional)</label>
<input type="file" name="flyer" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Expiration Date</label>
<input type="date" name="expiration_date" class="form-control" value="<?= esc($event['expiration_date']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Semester</label>
<input type="text" name="semester" class="form-control" value="<?= esc($event['semester']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">School Year</label>
<input type="text" name="school_year" class="form-control" value="<?= esc($event['school_year']) ?>" required>
</div>
<button type="submit" class="btn btn-primary">Update Event</button>
<a href="<?= site_url('administrator/events') ?>" class="btn btn-secondary">Cancel</a>
</form>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,162 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-5">
<h3>Event Charges</h3>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
<?php endif; ?>
<!-- Add New Charge Form -->
<form action="<?= site_url('payment/event_charges') ?>" method="post">
<?= csrf_field() ?>
<div class="row">
<div class="col-md-3 mt-3">
<label for="event_id" class="form-label">Select Event</label>
<select name="event_id" id="event_id" class="form-select" required>
<option value="">-- Select Event --</option>
<?php foreach ($events as $event): ?>
<option value="<?= esc($event['id']) ?>">
<?= esc($event['event_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3 mt-3">
<label for="parent_id" class="form-label">Parent</label>
<select id="parent_id" name="parent_id" class="form-select" required>
<option value="">-- Select Parent --</option>
<?php foreach ($parents as $parent): ?>
<option value="<?= $parent['id'] ?>">
<?= esc($parent['firstname'] . ' ' . $parent['lastname']) ?> (<?= esc($parent['school_id']) ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 mt-3" id="studentCheckboxContainer" style="display: none;">
<label>Select Students:</label>
<div id="studentList" class="row"></div>
</div>
</div>
<button type="submit" class="btn btn-primary mt-3">Submit</button>
<a href="<?= site_url('administrator/events') ?>" class="btn btn-success mt-3">Event List</a>
</form>
<br>
<!-- Charge Tables grouped by event -->
<?php
$grouped = [];
foreach ($charges as $charge) {
$label = $charge['event_name'] ?? 'N/A';
$grouped[$label][] = $charge;
}
?>
<?php if (empty($grouped)): ?>
<div class="alert alert-info">No charges found.</div>
<?php else: ?>
<?php foreach ($grouped as $eventLabel => $rows): ?>
<div class="card mb-4">
<div class="card-header bg-light fw-semibold">
<?= esc($eventLabel) ?>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-bordered table-striped mb-0 no-mgmt-sticky">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Parent Name</th>
<th>Student Name</th>
<th>Charged Amount</th>
<th>Is Participating</th>
<th>Semester</th>
<th>Year</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $charge): ?>
<tr>
<td><?= esc($charge['id']) ?></td>
<td><?= esc($charge['parent_firstname'] . ' ' . $charge['parent_lastname']) ?></td>
<td>
<?= $charge['student_firstname']
? esc($charge['student_firstname'] . ' ' . $charge['student_lastname'])
: '-' ?>
</td>
<td>$<?= esc(number_format($charge['charged'] ?? 0, 2)) ?></td>
<td>
<?= $charge['participation']
? '<span class="badge bg-success">Yes</span>'
: '<span class="badge bg-warning">No</span>' ?>
</td>
<td><?= esc($charge['semester'] ?? '-') ?></td>
<td><?= esc($charge['school_year'] ?? '-') ?></td>
<td><?= esc(!empty($charge['created_at']) ? local_datetime($charge['created_at'], 'm-d-Y H:i') : '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
function loadStudentsWithCharges() {
let parentId = $('#parent_id').val();
let eventId = $('#event_id').val();
let semester = '<?= esc($semester) ?>';
let schoolYear = '<?= esc($school_year) ?>';
if (parentId && eventId) {
$.get('<?= base_url('administrator/get-students-with-charges') ?>', {
parent_id: parentId,
event_id: eventId,
semester: semester,
school_year: schoolYear
}, function(data) {
let container = $('#studentList');
container.empty();
if (data.length > 0) {
$('#studentCheckboxContainer').show();
data.forEach(student => {
container.append(`
<div class="col-md-4">
<div class="form-check">
<input type="hidden" name="participation[${student.id}]" value="no">
<input class="form-check-input" type="checkbox" name="participation[${student.id}]" value="yes" id="student_${student.id}" ${student.charged ? 'checked' : ''}>
<label class="form-check-label" for="student_${student.id}">
${student.name}
</label>
</div>
</div>
`);
});
} else {
$('#studentCheckboxContainer').hide();
}
}, 'json');
} else {
$('#studentCheckboxContainer').hide();
$('#studentList').empty();
}
}
$('#parent_id').on('change', loadStudentsWithCharges);
$('#event_id').on('change', loadStudentsWithCharges);
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,60 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<h2 class="text-center mt-4 mb-3">Event Managment</h2>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
<?php endif; ?>
<?php if (empty($events)): ?>
<div class="alert alert-info mb-3 d-inline-block">No events found.</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-bordered no-mgmt-sticky">
<thead>
<tr>
<th>Event Name</th>
<th>Category</th>
<th>Amount</th>
<th>Expiration Date</th>
<th>Semester</th>
<th>School Year</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($events as $event): ?>
<tr>
<td><?= esc($event['event_name']) ?></td>
<td><?= esc($event['event_category'] ?? '—') ?></td>
<td><?= esc($event['amount']) ?></td>
<td><?= esc(!empty($event['expiration_date']) ? local_date($event['expiration_date'], 'm-d-Y') : '') ?></td>
<td><?= esc($event['semester']) ?></td>
<td><?= esc($event['school_year']) ?></td>
<td>
<a href="<?= site_url('administrator/events/edit/' . $event['id']) ?>" class="btn btn-sm btn-warning">Edit</a>
<form action="<?= site_url('administrator/events/delete/' . $event['id']) ?>" method="post" style="display:inline;">
<?= csrf_field() ?>
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete this event?')">Delete</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<div class="d-flex gap-2 mb-3 justify-content-end">
<a href="<?= site_url('administrator/events/create') ?>" class="btn btn-primary mb-3">Create New Event</a>
<a href="<?= site_url('administrator/event-charges') ?>" class="btn btn-secondary mb-3">List Event Participants</a>
</div>
</div>
<?= $this->endSection() ?>
+290
View File
@@ -0,0 +1,290 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid px-4 py-4">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3 mb-4">
<div>
<h1 class="h3 mb-1">Exam Draft Submissions</h1>
<p class="text-muted mb-0 small">
<?= esc($semester ?: 'Semester') ?> <?= esc($schoolYear ?: '') ?>
</p>
</div>
<div class="text-muted small d-flex flex-column align-items-lg-end">
<span>Allowed files: <?= esc(implode(', ', $allowedExtensions)) ?></span>
<span>Max size: <?= number_format($maxUploadBytes / 1024 / 1024, 0) ?> MB</span>
</div>
</div>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success">
<?= esc(session()->getFlashdata('success')) ?>
</div>
<?php elseif (session()->getFlashdata('error')): ?>
<div class="alert alert-danger">
<?= esc(session()->getFlashdata('error')) ?>
</div>
<?php endif; ?>
<ul class="nav nav-pills mb-3" id="examDraftTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="submissions-tab" data-bs-toggle="pill" data-bs-target="#submissions" type="button" role="tab" aria-controls="submissions" aria-selected="true">
Submissions
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="legacy-tab" data-bs-toggle="pill" data-bs-target="#legacy" type="button" role="tab" aria-controls="legacy" aria-selected="false">
Legacy Exams
</button>
</li>
</ul>
<div class="tab-content" id="examDraftTabsContent">
<div class="tab-pane fade show active" id="submissions" role="tabpanel" aria-labelledby="submissions-tab">
<div class="card mb-4">
<div class="card-body">
<?php if (empty($drafts)): ?>
<p class="text-muted mb-0">No exam drafts have been submitted yet.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-striped table-bordered align-middle mb-0 exam-drafts-table no-mgmt-sticky">
<thead class="table-light">
<tr>
<th>Teacher</th>
<th>Class</th>
<th>Title / Type</th>
<th>Version</th>
<th>Submitted</th>
<th>Status</th>
<th>Files</th>
<th>PDF</th>
<th>Review</th>
</tr>
</thead>
<tbody>
<?php foreach ($drafts as $draft): ?>
<?php
$teacherName = trim(($draft['teacher_first'] ?? '') . ' ' . ($draft['teacher_last'] ?? ''));
if ($teacherName === '') {
$teacherName = ($draft['admin_id'] ?? null) === ($draft['teacher_id'] ?? null)
? 'Admin Upload'
: 'User #' . ($draft['teacher_id'] ?? 'N/A');
}
$statusInfo = $statusBadges[$draft['status'] ?? 'pending'] ?? ['label' => 'Unknown', 'class' => 'bg-secondary text-white'];
$adminName = trim(($draft['admin_first'] ?? '') . ' ' . ($draft['admin_last'] ?? ''));
?>
<tr>
<td><?= esc($teacherName) ?></td>
<td><?= esc($draft['class_section_name'] ?? 'Unknown') ?></td>
<td>
<strong><?= esc($draft['draft_title'] ?? 'Untitled') ?></strong>
<div class="small text-muted"><?= esc($draft['exam_type'] ?? 'N/A') ?></div>
<?php if (!empty($draft['description'])): ?>
<div class="small text-muted"><?= esc($draft['description']) ?></div>
<?php endif; ?>
</td>
<td class="text-nowrap">v<?= esc($draft['version'] ?? 1) ?></td>
<td class="text-nowrap"><?= esc((!empty($draft['created_at']) ? local_datetime($draft['created_at'], 'M j, Y g:i A') : '—')) ?></td>
<td>
<span class="badge <?= esc($statusInfo['class']) ?>">
<?= esc($statusInfo['label']) ?>
</span>
<?php if (!empty($draft['reviewed_at'])): ?>
<div class="small text-muted mt-1">
Reviewed <?= esc(local_datetime($draft['reviewed_at'], 'M j, Y g:i A')) ?>
<?php if ($adminName !== ''): ?>
by <?= esc($adminName) ?>
<?php endif; ?>
</div>
<?php endif; ?>
</td>
<td>
<?php if (!empty($draft['teacher_file'])): ?>
<div>
<a href="<?= base_url('exam-drafts/files/teacher/' . $draft['teacher_file']) ?>" target="_blank">
<?= esc($draft['teacher_filename'] ?? 'Submitted draft') ?>
</a>
</div>
<?php else: ?>
<span class="text-muted small">No teacher file</span>
<?php endif; ?>
<?php if (!empty($draft['final_file'])): ?>
<div class="mt-2">
<a href="<?= base_url('exam-drafts/files/final/' . $draft['final_file']) ?>" target="_blank" class="link-success small">
<?= esc($draft['final_filename'] ?? 'Final draft') ?>
</a>
</div>
<?php endif; ?>
</td>
<td class="text-nowrap">
<?php
$pdfFile = $draft['final_pdf_file'] ?? null;
// Fallback: if final_file itself is a pdf, use it
if (empty($pdfFile) && !empty($draft['final_file']) && strtolower(pathinfo($draft['final_file'], PATHINFO_EXTENSION)) === 'pdf') {
$pdfFile = $draft['final_file'];
}
?>
<?php if (!empty($pdfFile)): ?>
<a href="<?= base_url('exam-drafts/files/final/' . $pdfFile) ?>" target="_blank" class="link-success small">
PDF
</a>
<?php else: ?>
<span class="text-muted small">—</span>
<?php endif; ?>
</td>
<td>
<form method="post" action="<?= base_url('/administrator/exam-drafts/review') ?>" enctype="multipart/form-data">
<?= csrf_field() ?>
<input type="hidden" name="draft_id" value="<?= esc($draft['id']) ?>">
<div class="mb-2">
<textarea
name="admin_comments"
rows="2"
class="form-control form-control-sm"
placeholder="Optional note for the teacher"
><?= esc($draft['admin_comments'] ?? '') ?></textarea>
</div>
<div class="mb-2">
<label class="form-label small mb-1">Status</label>
<select name="review_status" class="form-select form-select-sm">
<?php foreach ($statusOptions as $option): ?>
<option value="<?= esc($option) ?>" <?= ($option === ($draft['status'] ?? '')) ? 'selected' : '' ?>>
<?= esc(ucfirst($option)) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-2">
<label class="form-label small mb-1">Upload final draft</label>
<input type="file" name="final_file" class="form-control form-control-sm">
</div>
<button type="submit" class="btn btn-sm btn-primary w-100">
Save review
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="tab-pane fade" id="legacy" role="tabpanel" aria-labelledby="legacy-tab">
<div class="card border-primary-subtle mb-3">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<div>
<strong>Upload Old / Legacy Exam</strong>
<div class="small text-muted">Store historic exams as finalized records.</div>
</div>
<span class="badge bg-primary-subtle text-primary">Admin only</span>
</div>
<div class="card-body">
<form method="post" action="<?= base_url('/administrator/exam-drafts/upload-legacy') ?>" enctype="multipart/form-data" class="row g-3">
<?= csrf_field() ?>
<div class="col-md-4">
<label class="form-label small">Class Section</label>
<select name="class_section_id" class="form-select" required>
<option value="">Select class</option>
<?php foreach ($classSections as $cs): ?>
<option value="<?= esc($cs['class_section_id']) ?>"><?= esc($cs['class_section_name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small">School Year</label>
<input type="text" name="school_year" class="form-control" value="<?= esc($schoolYear) ?>" placeholder="2025-2026" required>
</div>
<div class="col-md-2">
<label class="form-label small">Semester</label>
<select name="semester" class="form-select" required>
<option value="Fall" <?= ($semester === 'Fall') ? 'selected' : '' ?>>Fall</option>
<option value="Spring" <?= ($semester === 'Spring') ? 'selected' : '' ?>>Spring</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label small">Exam Type</label>
<select name="exam_type" class="form-select">
<option value="">— Select type —</option>
<?php foreach ($examTypes as $type): ?>
<option value="<?= esc($type) ?>"><?= esc($type) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12">
<label class="form-label small">Upload File</label>
<input type="file" name="old_exam_file" class="form-control" accept=".doc,.docx,.pdf" required>
<div class="form-text">Allowed: <?= esc(implode(', ', $allowedExtensions)) ?> • Max <?= number_format($maxUploadBytes / 1024 / 1024, 0) ?> MB</div>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">Upload Legacy Exam</button>
</div>
</form>
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<?php if (empty($legacyByClass)): ?>
<p class="text-muted mb-0">No legacy exams uploaded yet.</p>
<?php else: ?>
<?php foreach ($legacyByClass as $group): ?>
<div class="mb-4">
<h6 class="mb-2"><?= esc($group['class_section_name'] ?? 'Class') ?></h6>
<div class="list-group">
<?php foreach ($group['items'] as $item): ?>
<div class="list-group-item d-flex justify-content-between align-items-start">
<div class="me-3">
<div class="fw-semibold"><?= esc($item['draft_title'] ?? 'Legacy Exam') ?></div>
<div class="small text-muted">
<?= esc($item['exam_type'] ?? 'N/A') ?> •
<?= esc($item['semester'] ?? '') ?> <?= esc($item['school_year'] ?? '') ?>
</div>
</div>
<div class="text-end">
<?php if (!empty($item['final_file'])): ?>
<a href="<?= base_url('exam-drafts/files/final/' . $item['final_file']) ?>" target="_blank" class="btn btn-sm btn-outline-primary">
Download
</a>
<?php else: ?>
<span class="text-muted small">File missing</span>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('styles') ?>
<style>
.exam-drafts-table th,
.exam-drafts-table td {
white-space: normal;
word-break: break-word;
}
.exam-drafts-table th {
min-width: 120px;
}
.exam-drafts-table td {
max-width: 220px;
}
.exam-drafts-table {
table-layout: auto;
}
.exam-drafts-table thead th {
background-color: #f8f9fa;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
padding-bottom: 0.75rem;
}
.exam-drafts-table tbody tr {
background-color: #fff;
}
</style>
<?= $this->endSection() ?>
@@ -0,0 +1,42 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Expense Management</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Expense ID</th>
<th>Description</th>
<th>Amount</th>
<th>Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$expenses = [
['expense_id' => 'E001', 'description' => 'Purchase of textbooks', 'amount' => '$500', 'date' => '2024-07-01', 'status' => 'Approved'],
['expense_id' => 'E002', 'description' => 'Lab equipment maintenance', 'amount' => '$300', 'date' => '2024-07-02', 'status' => 'Pending'],
['expense_id' => 'E003', 'description' => 'Sports equipment', 'amount' => '$700', 'date' => '2024-07-03', 'status' => 'Approved'],
// Add more records as needed
];
foreach ($expenses as $expense) {
$dateDisp = !empty($expense['date']) ? local_date($expense['date'], 'm-d-Y') : '';
echo "<tr>
<td>{$expense['expense_id']}</td>
<td>{$expense['description']}</td>
<td>{$expense['amount']}</td>
<td>" . esc($dateDisp) . "</td>
<td>{$expense['status']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,44 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Fee Collection</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Payment ID</th>
<th>Student Name</th>
<th>Amount</th>
<th>Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$feeCollections = [
['payment_id' => 'F001', 'student_name' => 'John Doe', 'amount' => '$200', 'date' => '2024-07-01', 'status' => 'Paid'],
['payment_id' => 'F002', 'student_name' => 'Jane Smith', 'amount' => '$250', 'date' => '2024-07-02', 'status' => 'Pending'],
['payment_id' => 'F003', 'student_name' => 'Michael Johnson', 'amount' => '$300', 'date' => '2024-07-03', 'status' => 'Paid'],
// Add more records as needed
];
foreach ($feeCollections as $collection) {
$dateDisp = !empty($collection['date']) ? local_date($collection['date'], 'm-d-Y') : '';
echo "<tr>
<td>{$collection['payment_id']}</td>
<td>{$collection['student_name']}</td>
<td>{$collection['amount']}</td>
<td>" . esc($dateDisp) . "</td>
<td>{$collection['status']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,44 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Fee Payment Records</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Payment ID</th>
<th>Student Name</th>
<th>Amount</th>
<th>Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$feePayments = [
['payment_id' => 'F001', 'student_name' => 'John Doe', 'amount' => '$200', 'date' => '2024-07-01', 'status' => 'Paid'],
['payment_id' => 'F002', 'student_name' => 'Jane Smith', 'amount' => '$250', 'date' => '2024-07-02', 'status' => 'Pending'],
['payment_id' => 'F003', 'student_name' => 'Michael Johnson', 'amount' => '$300', 'date' => '2024-07-03', 'status' => 'Paid'],
// Add more records as needed
];
foreach ($feePayments as $payment) {
$dateDisp = !empty($payment['date']) ? local_date($payment['date'], 'm-d-Y') : '';
echo "<tr>
<td>{$payment['payment_id']}</td>
<td>{$payment['student_name']}</td>
<td>{$payment['amount']}</td>
<td>" . esc($dateDisp) . "</td>
<td>{$payment['status']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,44 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Feedback and Complaints</h1>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Feedback/Complaint</th>
<th>Status</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$feedbackComplaints = [
['id' => '1', 'name' => 'John Doe', 'email' => 'john.doe@example.com', 'message' => 'Great school!', 'status' => 'Resolved', 'date' => '2024-07-01'],
['id' => '2', 'name' => 'Jane Smith', 'email' => 'jane.smith@example.com', 'message' => 'Issue with teacher', 'status' => 'Pending', 'date' => '2024-07-02'],
['id' => '3', 'name' => 'Michael Johnson', 'email' => 'michael.johnson@example.com', 'message' => 'Need more facilities', 'status' => 'In Progress', 'date' => '2024-07-03'],
// Add more records as needed
];
foreach ($feedbackComplaints as $fc) {
$dateDisp = !empty($fc['date']) ? local_date($fc['date'], 'm-d-Y') : '';
echo "<tr>
<td>{$fc['id']}</td>
<td>{$fc['name']}</td>
<td>{$fc['email']}</td>
<td>{$fc['message']}</td>
<td>{$fc['status']}</td>
<td>" . esc($dateDisp) . "</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
+39
View File
@@ -0,0 +1,39 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Scores</h1>
<a href="/administrator/scores/create" class="btn btn-primary">Add New Score</a>
</div>
<table class="table table-striped mt-3">
<thead>
<tr>
<th>ID</th>
<th>Student Name</th>
<th>Course</th>
<th>Score</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($scores)): ?>
<?php foreach ($scores as $score): ?>
<tr>
<td><?php echo $score['id']; ?></td>
<td><?php echo $score['student_name']; ?></td>
<td><?php echo $score['course']; ?></td>
<td><?php echo $score['score']; ?></td>
<td>
<a href="/administrator/scores/edit/<?php echo $score['id']; ?>" class="btn btn-warning">Edit</a>
<a href="/administrator/scores/delete/<?php echo $score['id']; ?>" class="btn btn-danger"
onclick="return confirm('Are you sure you want to delete this score record?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="5">No score records found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</main>
@@ -0,0 +1,72 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1 class="text-center">Score Management</h1>
<div class="d-flex justify-content-center">
<div class="col-12 col-lg-10">
<?= $this->include('partials/academic_filter') ?>
</div>
</div>
<br>
<!-- Tabs navigation -->
<ul class="nav nav-tabs justify-content-center" id="gradingTabs" role="tablist">
<?php foreach ($grades as $classId => $sections): ?>
<li class="nav-item">
<a class="nav-link <?= ($classId === 1) ? 'active' : '' ?>"
id="grade<?= $classId ?>-tab"
data-bs-toggle="tab"
href="#grade<?= $classId ?>"
role="tab"
aria-controls="grade<?= $classId ?>"
aria-selected="<?= ($classId === 1) ? 'true' : 'false' ?>">
<?= $classId === 12 ? 'Youth' : "Grade $classId" ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<!-- Tabs content -->
<div class="tab-content mt-4">
<?php foreach ($grades as $classId => $sections): ?>
<div class="tab-pane fade <?= ($classId === 1) ? 'show active' : '' ?>" id="grade<?= $classId ?>" role="tabpanel">
<?php foreach ($sections as $section): ?>
<?php
$sectionId = $section['class_section_id'];
if (empty($studentsBySection[$sectionId])) continue;
?>
<h4 class="text-center mt-4"><?= esc($section['class_section_name']) ?></h4>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Student Name</th>
<th>School ID</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($studentsBySection[$sectionId] as $student): ?>
<tr>
<td><?= esc($student['firstname'] . ' ' . $student['lastname']) ?></td>
<td><?= esc($student['school_id']) ?></td>
<td>
<?php
$types = ['homework', 'quiz', 'project', 'test', 'midterm', 'final', 'comments'];
foreach ($types as $type):
?>
<a href="<?= base_url("grading/$type/{$sectionId}/{$student['id']}") ?>" class="btn btn-sm btn-outline-primary m-1">
<?= ucfirst($type) ?>
</a>
<?php endforeach; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,43 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Health Records</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Student ID</th>
<th>Name</th>
<th>Date of Birth</th>
<th>Allergies</th>
<th>Medical Conditions</th>
<th>Emergency Contact</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$healthRecords = [
['student_id' => 'S001', 'name' => 'John Doe', 'dob' => '2008-05-01', 'allergies' => 'Peanuts', 'conditions' => 'Asthma', 'emergency_contact' => 'john.doe@example.com'],
['student_id' => 'S002', 'name' => 'Jane Smith', 'dob' => '2007-03-15', 'allergies' => 'None', 'conditions' => 'None', 'emergency_contact' => 'jane.smith@example.com'],
['student_id' => 'S003', 'name' => 'Michael Johnson', 'dob' => '2006-09-20', 'allergies' => 'Latex', 'conditions' => 'Diabetes', 'emergency_contact' => 'michael.johnson@example.com'],
// Add more records as needed
];
foreach ($healthRecords as $record) {
echo "<tr>
<td>{$record['student_id']}</td>
<td>{$record['name']}</td>
<td>{$record['dob']}</td>
<td>{$record['allergies']}</td>
<td>{$record['conditions']}</td>
<td>{$record['emergency_contact']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
+43
View File
@@ -0,0 +1,43 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4" id="main-content">
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
</div>
<div id="content">
<!-- Initial content goes here -->
<p>Welcome to the administrator dashboard.</p>
</div>
</main>
<?php include(__DIR__ . '/../partials/footer_back.php'); ?>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-cVj0DXe9C1fYM2oK5RD7T0I5vBnv3tntntGiTfP1F3e5Sf5fp3E3Ql5h0QQjxyV" crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
$('.nav-link').click(function(e) {
var target = $(this).attr('href');
if (target.startsWith('#')) {
// Handle collapse toggle for menu items
return;
}
e.preventDefault();
$.ajax({
url: target,
method: 'GET',
success: function(response) {
$('#content').html(response);
},
error: function() {
$('#content').html(
'<p>Sorry, an error occurred while loading the page.</p>');
}
});
});
});
</script>
+112
View File
@@ -0,0 +1,112 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-3">IP Bans</h2>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<div class="d-flex justify-content-between align-items-center mb-3">
<form method="get" action="<?= site_url('administrator/ip_bans') ?>" class="d-flex align-items-center gap-2">
<label class="me-2">Show</label>
<select name="status" class="form-select form-select-sm" style="min-width: 160px;">
<?php $cur = strtolower((string)($status ?? 'all')); ?>
<option value="all" <?= $cur==='all'?'selected':'' ?>>All entries</option>
<option value="active" <?= $cur==='active'?'selected':'' ?>>Active bans only</option>
</select>
<button type="submit" class="btn btn-secondary btn-sm">Apply</button>
</form>
<form method="post" action="<?= site_url('administrator/ip_bans/unban') ?>">
<?= csrf_field() ?>
<input type="hidden" name="all" value="1" />
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Unban all currently blocked IPs?')">Unban All</button>
</form>
</div>
<div class="table-responsive">
<table id="ipBansTable" class="table table-striped table-hover align-middle" style="width:100%">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>IP Address</th>
<th>Attempts</th>
<th>Last Attempt</th>
<th>Blocked Until</th>
<th>Status</th>
<th>Created</th>
<th>Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($banned)): ?>
<?php foreach ($banned as $row): ?>
<?php $isBlocked = !empty($row['blocked_until']) && strtotime($row['blocked_until']) > time(); ?>
<tr>
<td><?= (int)$row['id'] ?></td>
<td><?= esc($row['ip_address']) ?></td>
<td><?= (int)$row['attempts'] ?></td>
<td><?= esc(!empty($row['last_attempt_at']) ? local_datetime($row['last_attempt_at'], 'm-d-Y H:i') : '') ?></td>
<td><?= esc(!empty($row['blocked_until']) ? local_datetime($row['blocked_until'], 'm-d-Y H:i') : '') ?></td>
<td>
<?php if ($isBlocked): ?>
<span class="badge bg-danger">Active</span>
<?php else: ?>
<span class="badge bg-secondary">Not Active</span>
<?php endif; ?>
</td>
<td><?= esc(!empty($row['created_at']) ? local_datetime($row['created_at'], 'm-d-Y H:i') : '') ?></td>
<td><?= esc(!empty($row['updated_at']) ? local_datetime($row['updated_at'], 'm-d-Y H:i') : '') ?></td>
<td>
<?php if ($isBlocked): ?>
<form method="post" action="<?= site_url('administrator/ip_bans/unban') ?>" class="d-inline">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= (int)$row['id'] ?>" />
<button type="submit" class="btn btn-sm btn-primary" title="Unban" onclick="return confirm('Unban IP <?= esc($row['ip_address']) ?>?')">Unban</button>
</form>
<?php else: ?>
<form method="post" action="<?= site_url('administrator/ip_bans/ban-now') ?>" class="d-inline me-1">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= (int)$row['id'] ?>" />
<input type="hidden" name="hours" value="24" />
<button type="submit" class="btn btn-sm btn-warning" title="Ban for 24h" onclick="return confirm('Ban IP <?= esc($row['ip_address']) ?> for 24 hours?')">Ban 24h</button>
</form>
<form method="post" action="<?= site_url('administrator/ip_bans/unban') ?>" class="d-inline">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= (int)$row['id'] ?>" />
<button type="submit" class="btn btn-sm btn-secondary" title="Clear Attempts" onclick="return confirm('Clear attempts for IP <?= esc($row['ip_address']) ?>?')">Clear Attempts</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
(function(){
if (typeof $ === 'undefined' || !$.fn.DataTable) return;
const table = $('#ipBansTable').DataTable({
responsive: true,
pageLength: 50,
lengthMenu: [10, 25, 50, 100],
order: [[4, 'desc']],
stateSave: true,
language: { emptyTable: 'No active IP bans.' }
});
})();
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,99 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid mt-3">
<h2 class="mb-3">Late Slip Logs</h2>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<form class="row g-2 align-items-end mb-3" method="get" action="<?= site_url('administrator/late_slips') ?>">
<div class="col-auto">
<label for="school_year" class="form-label">School Year</label>
<input type="text" name="school_year" id="school_year" class="form-control" value="<?= esc($schoolYear ?? ($_GET['school_year'] ?? '')) ?>" placeholder="e.g. 2025-2026">
</div>
<div class="col-auto">
<label for="semester" class="form-label">Semester</label>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<select name="semester" id="semester" class="form-select">
<option value="">—</option>
<option value="Fall" <?= (strcasecmp($semVal, 'Fall') === 0 ? 'selected' : '') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal, 'Spring') === 0 ? 'selected' : '') ?>>Spring</option>
</select>
</div>
<div class="col-auto">
<label for="q" class="form-label">Student</label>
<input type="text" name="q" id="q" class="form-control" value="<?= esc($filters['q'] ?? '') ?>" placeholder="search name">
</div>
<div class="col-auto">
<label for="date_from" class="form-label">From</label>
<input type="date" name="date_from" id="date_from" class="form-control" value="<?= esc($filters['date_from'] ?? '') ?>">
</div>
<div class="col-auto">
<label for="date_to" class="form-label">To</label>
<input type="date" name="date_to" id="date_to" class="form-control" value="<?= esc($filters['date_to'] ?? '') ?>">
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">Filter</button>
<a class="btn btn-secondary" href="<?= site_url('administrator/late_slips') ?>">Reset</a>
</div>
</form>
<div class="table-responsive">
<table class="table table-striped" id="lateSlipLogsTable">
<thead>
<tr>
<th>ID</th>
<th>Printed At</th>
<th>Student</th>
<th>Grade</th>
<th>Date</th>
<th>Time In</th>
<th>Reason</th>
<th>School Year</th>
<th>Admin</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php foreach (($logs ?? []) as $r): ?>
<tr>
<td><?= (int)($r['id'] ?? 0) ?></td>
<td><?= esc(!empty($r['printed_at']) ? local_datetime($r['printed_at'], 'm-d-Y H:i') : '') ?></td>
<td><?= esc($r['student_name'] ?? '') ?></td>
<td><?= esc($r['grade'] ?? '') ?></td>
<td><?= esc(!empty($r['slip_date']) ? local_date($r['slip_date'], 'm-d-Y') : '') ?></td>
<td><?= esc($r['time_in'] ?? '') ?></td>
<td><?= esc($r['reason'] ?? '') ?></td>
<td><?= esc($r['school_year'] ?? '') ?></td>
<td><?= esc($r['admin_name'] ?? '') ?></td>
<td>
<form action="<?= site_url('administrator/late_slips/reprint/' . (int)($r['id'] ?? 0)) ?>" method="post" class="d-inline">
<?= csrf_field() ?>
<button type="submit" class="btn btn-sm btn-primary" title="Reprint">
<i class="bi bi-printer"></i> Reprint
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
$(function(){
$('#lateSlipLogsTable').DataTable({
pageLength: 25,
order: [[0, 'desc']]
});
});
</script>
<?= $this->endSection() ?>
+37
View File
@@ -0,0 +1,37 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Manage Users</h1>
<a href="/administrator/add-user" class="btn btn-success">Add User</a>
<table class="table table-bordered mt-3">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?= $user['id']; ?></td>
<td><?= $user['name']; ?></td>
<td><?= $user['email']; ?></td>
<td><?= $user['role']; ?></td>
<td>
<a href="/administrator/edit-user/<?= $user['id']; ?>" class="btn btn-warning">Edit</a>
<a href="/administrator/delete-user/<?= $user['id']; ?>"
class="btn btn-danger">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</main>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,98 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid py-3">
<div class="d-flex align-items-center justify-content-between mb-2">
<div>
<h1 class="h4 mb-1">Admin Notification Subjects</h1>
<div class="text-muted">Choose the subject each admin should receive notifications for.</div>
</div>
</div>
<?= view('partials/flash_messages') ?>
<?php if (empty($tableReady)): ?>
<div class="alert alert-warning" role="alert">
Notification subject storage is not available. Please run migrations to enable saving.
</div>
<?php endif; ?>
<form method="post" action="<?= site_url('administrator/notifications_alerts/save') ?>">
<?= csrf_field() ?>
<div class="card">
<div class="card-header d-flex align-items-center justify-content-between">
<span>Admins</span>
<button type="submit" class="btn btn-primary btn-sm" <?= empty($tableReady) ? 'disabled' : '' ?>>Save</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="adminNotificationsTable" class="table table-striped align-middle no-mgmt-sticky no-dt-fixedheader">
<thead class="table-dark">
<tr>
<th style="width: 60px;">#</th>
<th>Name</th>
<th>Email</th>
<?php foreach (($subjects ?? []) as $label): ?>
<th class="text-center" style="width: 140px;"><?= esc($label) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php if (empty($admins)): ?>
<tr>
<td colspan="<?= 3 + count($subjects ?? []) ?>" class="text-center text-muted">No admins found.</td>
</tr>
<?php else: ?>
<?php foreach ($admins as $index => $admin): ?>
<?php
$adminId = (int) ($admin['id'] ?? 0);
$selected = $assignedSubjects[$adminId] ?? [];
$fullName = trim(($admin['firstname'] ?? '') . ' ' . ($admin['lastname'] ?? ''));
?>
<tr>
<td><?= (int) $index + 1 ?></td>
<td><?= esc($fullName !== '' ? $fullName : ('Admin #' . $adminId)) ?></td>
<td><?= esc($admin['email'] ?? '') ?></td>
<?php foreach (($subjects ?? []) as $key => $label): ?>
<td class="text-center">
<input
type="checkbox"
class="form-check-input"
name="subjects[<?= $adminId ?>][]"
value="<?= esc($key) ?>"
<?= !empty($selected[$key]) ? 'checked' : '' ?>
<?= empty($tableReady) ? 'disabled' : '' ?>
>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<div class="card-footer text-end">
<button type="submit" class="btn btn-primary" <?= empty($tableReady) ? 'disabled' : '' ?>>Save Changes</button>
</div>
</div>
</form>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
if (!window.jQuery || !window.jQuery.fn || !window.jQuery.fn.DataTable) {
return;
}
const selector = '#adminNotificationsTable';
if (window.jQuery.fn.DataTable.isDataTable(selector)) {
return;
}
window.jQuery(selector).DataTable({
pageLength: 25,
order: [[1, 'asc']]
});
});
</script>
<?= $this->endSection() ?>
+37
View File
@@ -0,0 +1,37 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h2 class="text-center mt-4 mb-3">Parents</h2>
<a href="/administrator/parent/create" class="btn btn-primary">Add New Parent</a>
</div>
<table class="table table-striped mt-3">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($parents)): ?>
<?php foreach ($parents as $parent): ?>
<tr>
<td><?php echo $parent['id']; ?></td>
<td><?php echo $parent['firstname'] . ' ' . $parent['lastname']; ?></td>
<td><?php echo $parent['email']; ?></td>
<td>
<a href="/administrator/parent/edit/<?php echo $parent['id']; ?>" class="btn btn-warning">Edit</a>
<a href="/administrator/parent/delete/<?php echo $parent['id']; ?>" class="btn btn-danger"
onclick="return confirm('Are you sure you want to delete this parent?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4">No parents found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</main>
+143
View File
@@ -0,0 +1,143 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-3">Parent Profiles</h2>
<?= $this->include('partials/academic_filter') ?>
<table id="myTable" class="display table table-striped table-bordered align-middle" style="width:100%">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email Address</th>
<th>Phone Number</th>
<th>Gender</th>
<th>Registration Date</th>
<th>Students</th>
</tr>
</thead>
<tbody>
<?php if (!empty($parents)): ?>
<?php foreach ($parents as $parent): ?>
<?php
// 1. Check if the students array is empty or doesn't exist
// If it is empty, 'continue' skips the entire <tr> for this parent
if (empty($parent['students'])) {
continue;
}
?>
<tr>
<td><?= esc($parent['school_id']) ?></td>
<td>
<a href="#" class="text-decoration-none" data-family-guardian-id="<?= (int)($parent['id'] ?? 0) ?>">
<?= esc($parent['firstname']) ?>
</a>
</td>
<td>
<a href="#" class="text-decoration-none" data-family-guardian-id="<?= (int)($parent['id'] ?? 0) ?>">
<?= esc($parent['lastname']) ?>
</a>
</td>
<td><?= esc($parent['email']) ?></td>
<td><?= esc($parent['cellphone']) ?></td>
<td><?= esc($parent['gender']) ?></td>
<td><?= esc(local_date($parent['created_at'], 'm-d-Y')) ?></td>
<td>
<ul class="list-unstyled mb-0">
<?php foreach ($parent['students'] as $student): ?>
<li><?= esc($student['name']) ?> (<?= esc($student['class_section']) ?>)</li>
<?php endforeach; ?>
</ul>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="8" class="text-center">No parent profiles found.</td></tr>
<?php endif; ?>
</tbody>
<tfoot>
<tr>
<th colspan="8" class="text-end"></th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
$(document).ready(function() {
function getFixedHeaderOffset(){
let total = 0;
const stack = [];
const header = document.querySelector('header.navbar.sticky-top, header.navbar.fixed-top');
if (header) stack.push(header);
const mgmt = document.getElementById('navbarManagement');
if (mgmt && (mgmt.classList.contains('sticky-top') || mgmt.classList.contains('fixed-top'))) stack.push(mgmt);
document.querySelectorAll('.navbar.sticky-top, .navbar.fixed-top').forEach(el => { if (!stack.includes(el)) stack.push(el); });
stack.forEach(el => { const h = el.offsetHeight || el.getBoundingClientRect().height || 0; total += Math.max(0, Math.round(h)); });
return total;
}
function loadScript(src, id){
return new Promise((resolve, reject)=>{
if (id && document.getElementById(id)) return resolve();
const s = document.createElement('script');
if (id) s.id = id;
s.src = src;
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
});
}
function loadCss(href, id){
return new Promise((resolve)=>{
if (id && document.getElementById(id)) return resolve();
const l = document.createElement('link');
if (id) l.id = id;
l.rel = 'stylesheet';
l.href = href;
l.onload = resolve;
document.head.appendChild(l);
});
}
function initTable(){
$('#myTable').DataTable({
responsive: true,
pageLength: 100,
lengthMenu: [5, 10, 25, 50, 100],
order: [[6, 'desc']], // Registration Date desc
dom: 'Bfrtip',
buttons: [
{ extend: 'csv', title: 'parent_profiles' },
{ extend: 'excel',title: 'parent_profiles' },
{ extend: 'pdf', title: 'parent_profiles', orientation: 'landscape', pageSize: 'A4' },
{ extend: 'print',title: 'Parent Profiles' }
],
columnDefs: [
{ targets: [7], className: 'text-center' }
],
fixedHeader: {
header: true,
headerOffset: getFixedHeaderOffset()
}
});
}
const hasFH = $.fn.dataTable && $.fn.dataTable.FixedHeader;
if (hasFH) {
initTable();
} else {
Promise.all([
loadScript('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader@3.4.0/js/dataTables.fixedHeader.min.js', 'dt-fixedheader'),
loadCss('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader-bs5@3.4.0/css/fixedHeader.bootstrap5.min.css', 'dt-fixedheader-css')
]).then(()=>initTable()).catch(()=>initTable());
}
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,82 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">PayPal Transactions</h2>
<?= $this->include('partials/academic_filter') ?>
<!-- Export Button -->
<div class="d-flex gap-2 mb-3 justify-content-end">
<a href="<?= base_url('admin/paypal-transactions/export') . ($keyword ? '?q=' . urlencode($keyword) : '') ?>"
class="btn btn-success">
Export CSV
</a>
</div>
<!-- Search Form -->
<!--form method="get" action="<--?= base_url('admin/paypal-transactions') ?>" class="mb-3">
<--?= csrf_field(); ?>
<div class="input-group">
<input type="text" name="q" value="<--?= esc($keyword) ?>" class="form-control"
placeholder="Search by transaction ID, email, order ID, or event type">
<button type="submit" class="btn btn-primary">Search</button>
<--?php if ($keyword): ?>
<a href="<--?= base_url('admin/paypal-transactions') ?>" class="btn btn-secondary">Clear</a>
<--?php endif; ?>
</div>
</form-->
<!-- Transactions Table -->
<table id="myTable" class="display table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Transaction ID</th>
<th>Order ID</th>
<th>Parent School ID</th>
<th>Email</th>
<th>Amount</th>
<th>Net Amount</th>
<th>Currency</th>
<th>Status</th>
<th>Event Type</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<?php foreach ($transactions as $t): ?>
<tr>
<td><?= esc($t['id']) ?></td>
<td><?= esc($t['transaction_id']) ?></td>
<td><?= esc($t['order_id']) ?></td>
<td><?= esc($t['parent_school_id']) ?></td>
<td><?= esc($t['payer_email']) ?></td>
<td>$<?= esc(number_format($t['amount'], 2)) ?></td>
<td>$<?= esc(number_format($t['net_amount'], 2)) ?></td>
<td><?= esc($t['currency']) ?></td>
<td><?= esc($t['status']) ?></td>
<td><?= esc($t['event_type']) ?></td>
<td><?= esc(local_datetime($t['created_at'], 'm-d-Y H:i')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- Pagination -->
<div class="d-flex justify-content-center">
<?= $pager->links() ?>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
$(document).ready(function() {
$('#myTable').DataTable({
"order": [
[0, "desc"]
],
"pageLength": 10
});
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,42 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Payroll Management</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Employee Name</th>
<th>Position</th>
<th>Salary</th>
<th>Pay Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$payrolls = [
['name' => 'John Doe', 'position' => 'Teacher', 'salary' => '$3000', 'pay_date' => '2024-07-01', 'status' => 'Paid'],
['name' => 'Jane Smith', 'position' => 'administrator Assistant', 'salary' => '$2500', 'pay_date' => '2024-07-01', 'status' => 'Paid'],
['name' => 'Sam Brown', 'position' => 'Principal', 'salary' => '$5000', 'pay_date' => '2024-07-01', 'status' => 'Pending'],
// Add more payroll records as needed
];
foreach ($payrolls as $payroll) {
$dateDisp = !empty($payroll['pay_date']) ? local_date($payroll['pay_date'], 'm-d-Y') : '';
echo "<tr>
<td>{$payroll['name']}</td>
<td>{$payroll['position']}</td>
<td>{$payroll['salary']}</td>
<td>" . esc($dateDisp) . "</td>
<td>{$payroll['status']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,42 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Performance Reviews</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Employee</th>
<th>Position</th>
<th>Review Date</th>
<th>Rating</th>
<th>Comments</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in real scenario, this will be fetched from the database
$performanceReviews = [
['employee' => 'John Doe', 'position' => 'Teacher', 'review_date' => '2024-01-15', 'rating' => 'Excellent', 'comments' => 'Great performance throughout the year.'],
['employee' => 'Jane Smith', 'position' => 'administrator', 'review_date' => '2024-01-20', 'rating' => 'Good', 'comments' => 'Consistently meets expectations.'],
['employee' => 'Emily Johnson', 'position' => 'Teacher', 'review_date' => '2024-02-10', 'rating' => 'Satisfactory', 'comments' => 'Room for improvement in classroom management.'],
// Add more reviews as needed
];
foreach ($performanceReviews as $review) {
$dateDisp = !empty($review['review_date']) ? local_date($review['review_date'], 'm-d-Y') : '';
echo "<tr>
<td>{$review['employee']}</td>
<td>{$review['position']}</td>
<td>" . esc($dateDisp) . "</td>
<td>{$review['rating']}</td>
<td>{$review['comments']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,80 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid py-3">
<div class="d-flex align-items-center justify-content-between mb-3">
<div>
<h1 class="h4 mb-1">Print Notification Recipients</h1>
<div class="text-muted">Select which administrators should receive alerts whenever a print request changes.</div>
</div>
<a href="<?= site_url('administrator/notifications_alerts') ?>" class="btn btn-outline-secondary btn-sm">
General Notification Subjects
</a>
</div>
<?= view('partials/flash_messages') ?>
<?php if (empty($tableReady)): ?>
<div class="alert alert-warning" role="alert">
Notification subject storage is not available. Please run migrations to enable this feature.
</div>
<?php endif; ?>
<form method="post" action="<?= site_url('administrator/print-notifications/save') ?>">
<?= csrf_field() ?>
<div class="card">
<div class="card-header d-flex align-items-center justify-content-between">
<span>Administrators</span>
<button type="submit" class="btn btn-primary btn-sm" <?= empty($tableReady) ? 'disabled' : '' ?>>Save</button>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped align-middle mb-0">
<thead class="table-dark">
<tr>
<th style="width: 60px;">#</th>
<th>Name</th>
<th>Email</th>
<th class="text-center">Send Print Alerts</th>
</tr>
</thead>
<tbody>
<?php if (empty($admins)): ?>
<tr>
<td colspan="4" class="text-center text-muted py-4">No administrators found.</td>
</tr>
<?php else: ?>
<?php foreach ($admins as $index => $admin): ?>
<?php
$adminId = (int) ($admin['id'] ?? 0);
$fullName = trim(($admin['firstname'] ?? '') . ' ' . ($admin['lastname'] ?? ''));
?>
<tr>
<td><?= $index + 1 ?></td>
<td><?= esc($fullName !== '' ? $fullName : ('Admin #' . $adminId)) ?></td>
<td><?= esc($admin['email'] ?? '') ?></td>
<td class="text-center">
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
name="notify[<?= $adminId ?>]"
value="1"
<?= !empty($assigned[$adminId]) ? 'checked' : '' ?>
<?= empty($tableReady) ? 'disabled' : '' ?>
>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<div class="card-footer text-end">
<button type="submit" class="btn btn-primary" <?= empty($tableReady) ? 'disabled' : '' ?>>Save Changes</button>
</div>
</div>
</form>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,134 @@
<?php
$activeStudents = $active_students ?? [];
$removedStudents = $removed_students ?? [];
$schoolYear = $school_year ?? '';
?>
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-2">Student Removal</h2>
<p class="text-center text-muted mb-4">
Removed students keep their data but do not appear in classes or attendance.
<?= $schoolYear !== '' ? 'Current year: ' . esc($schoolYear) : '' ?>
</p>
<?= $this->include('partials/flash_messages') ?>
<div class="card mb-4 shadow-sm">
<div class="card-header d-flex align-items-center justify-content-between">
<span>Active Students</span>
<span class="badge bg-primary"><?= count($activeStudents) ?></span>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="activeStudentsTable" class="table table-striped table-hover align-middle">
<thead class="table-dark">
<tr>
<th>School ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Class</th>
<th style="min-width: 140px;">Action</th>
</tr>
</thead>
<tbody>
<?php if (!empty($activeStudents)): ?>
<?php foreach ($activeStudents as $student): ?>
<tr>
<td><?= esc($student['school_id'] ?? '') ?></td>
<td><?= esc($student['firstname'] ?? '') ?></td>
<td><?= esc($student['lastname'] ?? '') ?></td>
<td><?= esc($student['class_section_name'] ?? 'No class assigned') ?></td>
<td>
<form method="post" action="<?= site_url('administrator/students/set-active') ?>" class="d-inline">
<?= csrf_field() ?>
<input type="hidden" name="student_id" value="<?= (int)($student['id'] ?? 0) ?>">
<input type="hidden" name="is_active" value="0">
<button type="submit" class="btn btn-outline-danger btn-sm" onclick="return confirm('Remove this student from classes and attendance?');">
Remove
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="5" class="text-center">No active students found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header d-flex align-items-center justify-content-between">
<span>Removed Students</span>
<span class="badge bg-secondary"><?= count($removedStudents) ?></span>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="removedStudentsTable" class="table table-striped table-hover align-middle">
<thead class="table-dark">
<tr>
<th>School ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Last Class</th>
<th style="min-width: 140px;">Action</th>
</tr>
</thead>
<tbody>
<?php if (!empty($removedStudents)): ?>
<?php foreach ($removedStudents as $student): ?>
<tr>
<td><?= esc($student['school_id'] ?? '') ?></td>
<td><?= esc($student['firstname'] ?? '') ?></td>
<td><?= esc($student['lastname'] ?? '') ?></td>
<td><?= esc($student['class_section_name'] ?? 'No class assigned') ?></td>
<td>
<form method="post" action="<?= site_url('administrator/students/set-active') ?>" class="d-inline">
<?= csrf_field() ?>
<input type="hidden" name="student_id" value="<?= (int)($student['id'] ?? 0) ?>">
<input type="hidden" name="is_active" value="1">
<button type="submit" class="btn btn-outline-success btn-sm" onclick="return confirm('Restore this student to classes and attendance?');">
Restore
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="5" class="text-center">No removed students found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
if (window.jQuery && typeof jQuery.fn.DataTable === 'function') {
if (document.getElementById('activeStudentsTable')) {
jQuery('#activeStudentsTable').DataTable();
}
if (document.getElementById('removedStudentsTable')) {
jQuery('#removedStudentsTable').DataTable();
}
}
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,41 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h1>Scholarship Information</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Scholarship ID</th>
<th>Name</th>
<th>Amount</th>
<th>Eligibility Criteria</th>
<th>Application Deadline</th>
</tr>
</thead>
<tbody>
<?php
// Example data - in a real scenario, this will be fetched from the database
$scholarships = [
['scholarship_id' => 'S001', 'name' => 'Academic Excellence', 'amount' => '$1000', 'criteria' => 'Top 10% of class', 'deadline' => '2024-09-01'],
['scholarship_id' => 'S002', 'name' => 'Sports Scholarship', 'amount' => '$800', 'criteria' => 'Outstanding performance in sports', 'deadline' => '2024-10-01'],
['scholarship_id' => 'S003', 'name' => 'Need-Based Scholarship', 'amount' => '$1200', 'criteria' => 'Financial need', 'deadline' => '2024-08-15'],
// Add more records as needed
];
foreach ($scholarships as $scholarship) {
echo "<tr>
<td>{$scholarship['scholarship_id']}</td>
<td>{$scholarship['name']}</td>
<td>{$scholarship['amount']}</td>
<td>{$scholarship['criteria']}</td>
<td>{$scholarship['deadline']}</td>
</tr>";
}
?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
+163
View File
@@ -0,0 +1,163 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<br>
<main>
<section>
<form action="<?= site_url('administrator/userSearch') ?>" method="get" class="form-inline mb-4">
<div class="form-group">
<input type="text" name="query" class="form-control"
value="<?= htmlspecialchars($query, ENT_QUOTES, 'UTF-8'); ?>"
placeholder="Enter search term" required>
<button type="submit" class="btn btn-primary ms-2">Search</button>
</div>
</form>
<h2>Search Results for "<?= htmlspecialchars($query, ENT_QUOTES, 'UTF-8'); ?>"</h2>
<?php if (empty($results) || array_filter($results, fn($group) => empty($group)) === $results): ?>
<p>No results found.</p>
<?php else: ?>
<!-- Users Table -->
<?php if (!empty($results['users'])): ?>
<h4>Users</h4>
<table class="table table-bordered table-sm">
<thead>
<tr>
<th>School ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<?php foreach ($results['users'] as $user): ?>
<tr>
<td><?= esc($user['school_id'] ?? 'N/A') ?></td>
<td><?= esc($user['firstname'] ?? 'N/A') ?></td>
<td><?= esc($user['lastname'] ?? 'N/A') ?></td>
<td><?= esc($user['email'] ?? 'N/A') ?></td>
<td><?= esc($user['cellphone'] ?? 'N/A') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<!-- Students Table -->
<?php if (!empty($results['students'])): ?>
<h4>Students</h4>
<table class="table table-bordered table-sm">
<thead>
<tr>
<th>School ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Date of Birth</th>
</tr>
</thead>
<tbody>
<?php foreach ($results['students'] as $student): ?>
<tr>
<td><?= esc($student['school_id'] ?? 'N/A') ?></td>
<td><?= esc($student['firstname'] ?? 'N/A') ?></td>
<td><?= esc($student['lastname'] ?? 'N/A') ?></td>
<td><?= esc($student['dob'] ?? 'N/A') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<!-- Parents Table -->
<?php if (!empty($results['parents'])): ?>
<h4>Parents</h4>
<table class="table table-bordered table-sm">
<thead>
<tr>
<th>First Parent ID</th>
<th>Second Parent</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
<?php foreach ($results['parents'] as $parent): ?>
<tr>
<td><?= esc($parent['firstparent_id'] ?? 'N/A') ?></td>
<td><?= esc(trim(($parent['secondparent_firstname'] ?? '') . ' ' . ($parent['secondparent_lastname'] ?? ''))) ?></td>
<td><?= esc($parent['secondparent_email'] ?? 'N/A') ?></td>
<td><?= esc($parent['secondparent_phone'] ?? 'N/A') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<!-- Staff Table -->
<?php if (!empty($results['staff'])): ?>
<h4>Staff</h4>
<table class="table table-bordered table-sm">
<thead>
<tr>
<th>Role</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($results['staff'] as $staff): ?>
<tr>
<td><?= esc($staff['role_name'] ?? 'N/A') ?></td>
<td><?= esc(trim(($staff['firstname'] ?? '') . ' ' . ($staff['lastname'] ?? ''))) ?></td>
<td><?= esc($staff['email'] ?? 'N/A') ?></td>
<td><?= esc($staff['phone'] ?? 'N/A') ?></td>
<td><?= esc($staff['active_role'] ?? 'N/A') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<!-- Emergency Contacts Table -->
<?php if (!empty($results['emergency_contacts'])): ?>
<h4>Emergency Contacts</h4>
<table class="table table-bordered table-sm">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Relation</th>
<th>Phone</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<?php foreach ($results['emergency_contacts'] as $contact): ?>
<tr>
<td><?= esc($contact['id'] ?? 'N/A') ?></td>
<td><?= esc($contact['emergency_contact_name'] ?? 'N/A') ?></td>
<td><?= esc($contact['relation'] ?? 'N/A') ?></td>
<td><?= esc($contact['cellphone'] ?? 'N/A') ?></td>
<td><?= esc($contact['email'] ?? 'N/A') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php endif; ?>
</section>
</main>
</div>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,239 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-3">Auto-Distribute Students into Sections</h2>
<div class="row g-2 align-items-center justify-content-center mb-3">
<div class="col-auto"><label for="adYearSelect" class="col-form-label">School year</label></div>
<div class="col-auto">
<form method="get" action="<?= site_url('administrator/sections/auto-distribute') ?>" class="d-flex align-items-center gap-2">
<select id="adYearSelect" name="schoolYear" class="form-select form-select-sm" style="min-width: 180px;">
<?php
$years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : [];
if (empty($years) && !empty($selectedYear)) $years = [$selectedYear];
foreach ($years as $y): $val = is_array($y) && isset($y['school_year']) ? $y['school_year'] : (string)$y; ?>
<option value="<?= esc($val) ?>" <?= ((string)($selectedYear ?? '') === (string)$val) ? 'selected' : '' ?>>
<?= esc($val) ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-secondary btn-sm">Apply</button>
</form>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="row g-3 align-items-end mb-2">
<div class="col-md-3">
<label for="globalPer" class="form-label">Students per section</label>
<input type="number" min="1" id="globalPer" class="form-control" placeholder="e.g. 20" />
</div>
<div class="col-md-4 d-flex gap-2">
<button type="button" id="refreshTotalsBtn" class="btn btn-outline-secondary">Refresh Totals</button>
<button type="button" id="generateAllBtn" class="btn btn-primary">Generate All</button>
</div>
<div class="col-md-7 text-end">
<span id="pageMsg" class="small text-muted"></span>
</div>
</div>
<div class="table-responsive">
<table class="table table-sm table-striped align-middle no-mgmt-sticky" id="classesTable">
<thead class="table-light">
<tr id="tblHeader">
<th>Class</th>
<th>Total</th>
<th>Sections Needed</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="tblBody"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<input type="hidden" id="csrfName" value="<?= esc(csrf_token()) ?>" />
<input type="hidden" id="csrfValue" value="<?= esc(csrf_hash()) ?>" />
<script>
(function(){
const selectedYear = '<?= esc($selectedYear ?? '') ?>';
const totalsUrl = '<?= site_url('administrator/sections/promotion-totals') ?>?school_year=' + encodeURIComponent(selectedYear);
const distUrl = '<?= site_url('administrator/sections/auto-distribute') ?>';
const tblBody = document.getElementById('tblBody');
const tblHeader = document.getElementById('tblHeader');
const perInput = document.getElementById('globalPer');
const msgEl = document.getElementById('pageMsg');
const refreshBtn= document.getElementById('refreshTotalsBtn');
let headerSections = []; // list of generated section names as columns
let rowIndexByClassId = {}; // mapping to locate rows
function calcNeeded(total) {
const per = parseInt(perInput.value || '0', 10);
if (!per || per <= 0) return '';
return Math.ceil(total / per);
}
function ensureSectionColumns(sectionNames) {
// Add any new section name as a column if not already present
sectionNames.forEach(function(name){
if (!name) return;
if (headerSections.indexOf(name) >= 0) return;
headerSections.push(name);
const th = document.createElement('th');
th.textContent = name;
tblHeader.appendChild(th);
// Also add empty cell to each existing row
document.querySelectorAll('#tblBody tr').forEach(function(tr){
const td = document.createElement('td');
td.dataset.section = name;
td.textContent = '';
tr.appendChild(td);
});
});
}
function buildInitialTable(rows) {
tblBody.innerHTML = '';
rowIndexByClassId = {};
headerSections = [];
// Remove dynamically-added section cols, keep the first four fixed
while (tblHeader.children.length > 4) tblHeader.removeChild(tblHeader.lastElementChild);
rows.forEach(function(r){
const tr = document.createElement('tr');
tr.dataset.classId = r.class_id;
tr.dataset.classSectionId = r.class_section_id;
tr.dataset.className = r.class_section_name || '';
const tdName = document.createElement('td');
tdName.textContent = r.class_section_name || '';
tr.appendChild(tdName);
const tdTotal = document.createElement('td');
tdTotal.className = 'text-end';
tdTotal.textContent = r.total;
tr.appendChild(tdTotal);
const tdNeed = document.createElement('td');
tdNeed.className = 'text-end need-cell';
tdNeed.textContent = calcNeeded(r.total);
tr.appendChild(tdNeed);
const tdAct = document.createElement('td');
const btn = document.createElement('button');
btn.className = 'btn btn-sm btn-primary';
btn.textContent = 'Generate Sections';
btn.addEventListener('click', function(){ runDistribution(r.class_section_id, r.class_section_name); });
tdAct.appendChild(btn);
tr.appendChild(tdAct);
tblBody.appendChild(tr);
rowIndexByClassId[r.class_id] = tr;
});
}
function updateNeeds() {
document.querySelectorAll('#tblBody tr').forEach(function(tr){
const total = parseInt(tr.children[1].textContent || '0', 10);
const needCell = tr.querySelector('.need-cell');
if (needCell) needCell.textContent = calcNeeded(total);
});
}
function runDistribution(baseSectionId, baseName) {
const per = parseInt(perInput.value || '0', 10);
if (!per || per <= 0) { msgEl.textContent = 'Enter students per section first.'; return; }
msgEl.textContent = 'Distributing ' + (baseName || '') + '...';
const fd = new FormData();
fd.append('class_section_id', String(baseSectionId));
fd.append('students_per_section', String(per));
fd.append('school_year', selectedYear);
// CSRF
const csrfNameEl = document.getElementById('csrfName');
const csrfValueEl= document.getElementById('csrfValue');
if (csrfNameEl && csrfValueEl) {
fd.append(csrfNameEl.value, csrfValueEl.value);
}
return fetch(distUrl, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, body: fd })
.then(r => r.json())
.then(res => {
// refresh CSRF if provided
if (res && res.csrfTokenName && res.csrfHash) {
if (csrfNameEl) csrfNameEl.value = res.csrfTokenName;
if (csrfValueEl) csrfValueEl.value = res.csrfHash;
}
if (!res || !res.ok || !Array.isArray(res.sections)) {
msgEl.textContent = res && res.message ? res.message : 'Distribution finished with no sections.';
return;
}
msgEl.textContent = res && res.message ? res.message : 'Completed.';
// Collect section names, then ensure header columns
const names = res.sections.map(s => s.class_section_name).filter(Boolean);
ensureSectionColumns(names);
// Fill row cells for this class
const tr = Array.from(document.querySelectorAll('#tblBody tr')).find(function(_tr){
return (_tr.children[0].textContent || '') === (baseName || '');
});
if (!tr) return;
res.sections.forEach(function(s){
const td = tr.querySelector('td[data-section="'+ s.class_section_name +'"]');
if (td) td.textContent = (s.total ?? 0) + ' (M'+ (s.male ?? 0) + '/F'+ (s.female ?? 0) +')';
});
})
.catch(() => { msgEl.textContent = 'Failed to distribute. Please try again.'; });
}
function loadTotals() {
msgEl.textContent = 'Loading totals...';
fetch(totalsUrl, { headers: { 'X-Requested-With': 'XMLHttpRequest' }})
.then(r => r.json())
.then(res => {
if (!res || !res.ok) { msgEl.textContent = res && res.message ? res.message : 'Failed to load totals.'; return; }
buildInitialTable(res.rows || []);
updateNeeds();
msgEl.textContent = '';
})
.catch(() => { msgEl.textContent = 'Failed to load totals.'; });
}
refreshBtn.addEventListener('click', function(){ updateNeeds(); });
document.getElementById('generateAllBtn').addEventListener('click', async function(){
const per = parseInt(perInput.value || '0', 10);
if (!per || per <= 0) { msgEl.textContent = 'Enter students per section first.'; return; }
// Collect base sections from rows
const rows = Array.from(document.querySelectorAll('#tblBody tr'))
.map(tr => ({ id: tr.dataset.classSectionId, name: tr.dataset.className }))
.filter(r => r.id);
for (let i = 0; i < rows.length; i++) {
const r = rows[i];
msgEl.textContent = 'Distributing ' + (r.name || r.id) + ' (' + (i+1) + '/' + rows.length + ')...';
// eslint-disable-next-line no-await-in-loop
await runDistribution(r.id, r.name);
}
msgEl.textContent = 'All distributions completed.';
});
perInput.addEventListener('input', function(){ updateNeeds(); });
loadTotals();
})();
</script>
<?= $this->endSection() ?>
+30
View File
@@ -0,0 +1,30 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Settings</h1>
</div>
<form action="/administrator/settings/update" method="post">
<div class="form-group">
<label for="site_name">Site Name</label>
<input type="text" class="form-control" id="site_name" name="site_name"
value="<?php echo $settings['site_name']; ?>" required>
</div>
<div class="form-group">
<label for="administrator_email">administrator Email</label>
<input type="email" class="form-control" id="administrator_email" name="administrator_email"
value="<?php echo $settings['administrator_email']; ?>" required>
</div>
<div class="form-group">
<label for="timezone">Timezone</label>
<select class="form-control" id="timezone" name="timezone" required>
<?php foreach ($timezones as $timezone): ?>
<option value="<?php echo $timezone; ?>"
<?php echo $timezone == $settings['timezone'] ? 'selected' : ''; ?>>
<?php echo $timezone; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary">Save Settings</button>
</form>
</main>
@@ -0,0 +1,643 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-3">Student Class Assignment</h2>
<div class="row g-2 align-items-center justify-content-center mb-3">
<div class="col-auto"><label for="scaYearSelect" class="col-form-label">School year</label></div>
<div class="col-auto">
<form method="get" action="<?= site_url('administrator/student_class_assignment') ?>" class="d-flex align-items-center gap-2">
<select id="scaYearSelect" name="schoolYear" class="form-select form-select-sm" style="min-width: 180px;">
<?php
$years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : [];
if (empty($years) && !empty($selectedYear)) $years = [$selectedYear];
foreach ($years as $y): $val = is_array($y) && isset($y['school_year']) ? $y['school_year'] : (string)$y; ?>
<option value="<?= esc($val) ?>" <?= ((string)($selectedYear ?? '') === (string)$val) ? 'selected' : '' ?>>
<?= esc($val) ?>
</option>
<?php endforeach; ?>
</select>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<label for="scaSemesterSelect" class="col-form-label">Semester</label>
<select id="scaSemesterSelect" name="semester" class="form-select form-select-sm" style="min-width: 140px;">
<option value="">—</option>
<option value="Fall" <?= (strcasecmp($semVal,'Fall')===0?'selected':'') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal,'Spring')===0?'selected':'') ?>>Spring</option>
</select>
<button type="submit" class="btn btn-secondary btn-sm">Apply</button>
</form>
</div>
<?php if (isset($isCurrentYear) && !$isCurrentYear): ?>
<div class="col-auto"><span class="badge bg-secondary">Read-only (Past Year)</span></div>
<?php endif; ?>
</div>
<div class="table-responsive">
<!-- Auto-distribute panel -->
<div class="card mb-3">
<div class="card-body">
<form id="autoDistForm" action="<?= site_url('administrator/sections/auto-distribute') ?>" method="post" class="row g-2 align-items-end">
<?= csrf_field() ?>
<div class="col-md-4">
<label for="autoDistSection" class="form-label">Class (base section)</label>
<select id="autoDistSection" name="class_section_id" class="form-select" required>
<option value="">Select class…</option>
<?php if (!empty($classes)): ?>
<?php foreach ($classes as $c): ?>
<?php $nm = (string)($c['class_section_name'] ?? '');
$isBase = (strpos($nm, '-') === false);
if (!$isBase) continue; ?>
<option value="<?= (int)($c['class_section_id'] ?? 0) ?>">
<?= esc($nm) ?>
</option>
<?php endforeach; ?>
<?php endif; ?>
</select>
</div>
<div class="col-md-3">
<label for="autoDistPer" class="form-label">Students per section</label>
<input type="number" min="1" id="autoDistPer" name="students_per_section" class="form-control" required />
</div>
<div class="col-md-3">
<label for="autoDistYear" class="form-label">School year</label>
<input type="text" id="autoDistYear" name="school_year" value="<?= esc($selectedYear ?? '') ?>" class="form-control" />
</div>
<div class="col-md-2 d-grid">
<button type="submit" class="btn btn-outline-primary">Auto-Distribute</button>
</div>
<div class="col-12">
<div id="autoDistMsg" class="small text-muted"></div>
</div>
</form>
</div>
</div>
<table id="studentsTable" class="display table table-striped table-hover align-middle" style="width:100%">
<thead class="table-dark">
<tr>
<th>Student Name</th>
<th>Age</th>
<th>Registration Grade</th>
<th>New Student</th>
<th>Current Assigned Grade</th>
<th>Registration Date</th>
<th>School Year</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($students)): ?>
<?php foreach ($students as $student): ?>
<tr data-student-id="<?= (int)($student['student_id'] ?? 0) ?>"
data-student-name="<?= esc($student['name'] ?? '') ?>"
data-class-ids='<?= esc(json_encode($student['class_section_ids'] ?? [])) ?>'
data-class-names='<?= esc(json_encode($student['class_section_names'] ?? [])) ?>'>
<td>
<a href="#" class="text-decoration-none" data-family-student-id="<?= (int)($student['student_id'] ?? 0) ?>">
<?= esc($student['name']) ?>
</a>
</td>
<td><?= esc($student['age']) ?></td>
<td><?= esc($student['registration_grade']) ?></td>
<td><?= esc($student['new_student']) ?></td>
<?php
$sIds = $student['class_section_ids'] ?? [];
$sNames = $student['class_section_names'] ?? [];
$sidVal = (int)($student['student_id'] ?? 0);
$badgeHtml = '';
if (!empty($sIds) && is_array($sIds)) {
foreach ($sIds as $idx => $cid) {
$nm = $sNames[$idx] ?? ('Class #'.$cid);
$badgeHtml .= '<span class="badge bg-success me-1 mb-1 d-inline-flex align-items-center gap-1">';
$badgeHtml .= '<span>'.esc($nm).'</span>';
$badgeHtml .= '<button type="button" class="btn-close btn-close-white btn-close-sm remove-class-chip" data-student-id="'.$sidVal.'" data-class-id="'.(int)$cid.'" aria-label="Remove"></button>';
$badgeHtml .= '</span>';
}
} else {
$badgeHtml = '<span class="text-muted">No class assigned</span>';
}
?>
<td class="assigned-classes" data-class-ids='<?= esc(json_encode($sIds)) ?>' data-class-names='<?= esc(json_encode($sNames)) ?>'>
<?= $badgeHtml ?>
</td>
<td data-order="<?= esc($student['registration_date']) ?>">
<?php if (!empty($student['registration_date'])): ?>
<?= local_date($student['registration_date'], 'm-d-Y') ?>
<?php else: ?>
-
<?php endif; ?>
</td>
<td><?= esc($student['school_year']) ?></td>
<td>
<button
type="button"
class="btn btn-primary btn-sm assign-student-btn" <?= (isset($isCurrentYear) && !$isCurrentYear) ? 'disabled title="Editing disabled for non-current year"' : '' ?>
data-bs-toggle="modal"
data-bs-target="#assignStudentClassModal"
data-student-id="<?= esc($student['student_id']) ?>"
data-student-name="<?= esc($student['name']) ?>"
data-current-classes="<?= esc($student['class_section_name'] ?? '') ?>"
data-class-ids="<?= esc(json_encode($student['class_section_ids'] ?? [])) ?>"
data-class-names="<?= esc(json_encode($student['class_section_names'] ?? [])) ?>">
Assign Class
</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Single Modal (do NOT include another modal partial) -->
<div class="modal fade" id="assignStudentClassModal" tabindex="-1" aria-labelledby="assignStudentClassModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="assignStudentClassModalLabel">Assign Class to Student</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="assignError" class="alert alert-danger d-none"></div>
<div class="mb-2 small text-muted" id="currentAssignments"></div>
<form id="assignStudentClassForm" action="<?= base_url('assign_class_student') ?>" method="post">
<?= csrf_field() ?>
<input type="hidden" name="student_id" id="studentId">
<div class="mb-3">
<label for="studentName" class="form-label">Student Name</label>
<input type="text" class="form-control" id="studentName" readonly>
</div>
<div class="mb-3">
<label for="student_class_section_id" class="form-label">Select Class(es)</label>
<select name="class_section_id[]" id="student_class_section_id" class="form-control" multiple required size="8">
<option value="" disabled>Select one or more classes</option>
<?php if (!empty($classes) && is_array($classes)): ?>
<?php foreach ($classes as $class_): ?>
<option value="<?= htmlspecialchars($class_['class_section_id']) ?>">
<?= htmlspecialchars($class_['class_section_name']) ?>
</option>
<?php endforeach; ?>
<?php else: ?>
<option value="" disabled>No classes available</option>
<?php endif; ?>
</select>
<div class="form-text">Use Ctrl/Cmd + click to select multiple classes. Existing assignments are kept.</div>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" value="1" id="is_event_only" name="is_event_only">
<label class="form-check-label" for="is_event_only">
Event class only (no tuition)
</label>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="assignStudentClassButton">Assign Class</button>
</div>
</div>
</div>
</div>
</div>
<!-- Remove Modal -->
<div class="modal fade" id="removeStudentClassModal" tabindex="-1" aria-labelledby="removeStudentClassModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="removeStudentClassModalLabel">Remove Class from Student</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="removeError" class="alert alert-danger d-none"></div>
<form id="removeStudentClassForm" action="<?= site_url('remove_class_student') ?>" method="post">
<?= csrf_field() ?>
<input type="hidden" name="student_id" id="removeStudentId">
<div class="mb-3">
<label for="removeStudentName" class="form-label">Student Name</label>
<input type="text" class="form-control" id="removeStudentName" readonly>
</div>
<div class="mb-3">
<label for="remove_class_section_id" class="form-label">Select Class to Remove</label>
<select name="class_section_id" id="remove_class_section_id" class="form-control" required>
<option value="" disabled selected>Select a class</option>
</select>
<div class="form-text">Only currently assigned classes are shown.</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="removeStudentClassButton">Remove Class</button>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
// Helpers to render assigned class chips
let CSRF_NAME = '<?= csrf_token() ?>';
let CSRF_HASH = '<?= csrf_hash() ?>';
function applyCsrfToForms(name, hash) {
if (!name || !hash) return;
$('form').each(function(){
const $f = $(this);
let $fld = $f.find('input[name="'+name+'"]');
if (!$fld.length) {
$f.find('input[type="hidden"]').filter(function(){ return this.name && this.name.startsWith('csrf_'); }).remove();
$fld = $('<input>', {type:'hidden', name});
$f.prepend($fld);
}
$fld.val(hash);
});
}
function updateCsrf($form, res){
if (!$form || !res) return;
let name = res.csrfTokenName || null;
let hash = res.csrfHash || null;
if (!hash) {
$form.find('input[type="hidden"]').each(function(){
const nm = this.name;
if (nm && res[nm]) { name = nm; hash = res[nm]; return false; }
});
}
if (name && hash) {
CSRF_NAME = name;
CSRF_HASH = hash;
applyCsrfToForms(CSRF_NAME, CSRF_HASH);
}
}
function findColIndexByHeader(text){
let idx = -1;
$('#studentsTable thead th').each(function(i){
if ($(this).text().trim() === text) idx = i;
});
return idx >= 0 ? idx : 4; // fallback
}
const CLASS_COL_INDEX = findColIndexByHeader('Current Assigned Grade');
function renderBadgesHtml(ids, names, studentId){
if (!Array.isArray(ids) || ids.length === 0) return '<span class="text-muted">No class assigned</span>';
if (!Array.isArray(names)) names = [];
return ids.map((id, idx) => {
const nm = names[idx] || ('Class #' + id);
return `<span class="badge bg-success me-1 mb-1 d-inline-flex align-items-center gap-1">
<span>${nm}</span>
<button type="button" class="btn-close btn-close-white btn-close-sm remove-class-chip" data-student-id="${studentId}" data-class-id="${id}" aria-label="Remove"></button>
</span>`;
}).join('');
}
function renderAssignedBadges() {
const decodeAttr = (raw) => {
if (!raw) return '';
return raw.replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
};
$('#studentsTable tbody tr').each(function(){
const $row = $(this);
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
if (!$cell.length) return;
if (!$cell.attr('data-class-ids') && $row.attr('data-class-ids')) {
$cell.attr('data-class-ids', $row.attr('data-class-ids'));
}
if (!$cell.attr('data-class-names') && $row.attr('data-class-names')) {
$cell.attr('data-class-names', $row.attr('data-class-names'));
}
const idsRaw = decodeAttr($cell.attr('data-class-ids') || $row.attr('data-class-ids') || '[]');
const namesRaw = decodeAttr($cell.attr('data-class-names') || $row.attr('data-class-names') || '[]');
let ids = [], names = [];
try { ids = JSON.parse(idsRaw) || []; } catch(_) {}
try { names = JSON.parse(namesRaw) || []; } catch(_) {}
const studentId = parseInt($row.attr('data-student-id') || '0', 10);
const badgeHtml = renderBadgesHtml(ids, names, studentId);
$cell.html(badgeHtml || $cell.html());
});
}
window.renderAssignedBadges = renderAssignedBadges;
window.renderBadgesHtml = renderBadgesHtml;
// Keep table row + button data in sync after add/remove so modals stay up-to-date without refresh.
function syncAssignButtonData($row, ids, names, displayVal){
const idsJson = JSON.stringify(ids || []);
const namesJson = JSON.stringify(names || []);
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
$row.attr('data-class-ids', idsJson).attr('data-class-names', namesJson);
$cell.attr('data-class-ids', idsJson).attr('data-class-names', namesJson);
$row.find('.assign-student-btn')
.attr('data-class-ids', idsJson)
.attr('data-class-names', namesJson)
.attr('data-current-classes', displayVal || '')
.data('class-ids', ids || [])
.data('class-names', names || [])
.data('current-classes', displayVal || '');
}
window.syncAssignButtonData = syncAssignButtonData;
function getAssignmentsFromRow($row){
const res = { ids: [], names: [] };
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
const $chips = $cell.find('.remove-class-chip');
if ($chips.length) {
$chips.each(function(){
const cid = parseInt($(this).data('class-id') || 0, 10);
if (cid) res.ids.push(cid);
const label = $(this).closest('.badge').clone().children('button').remove().end().text().trim();
res.names.push(label || ('Class #' + cid));
});
return res;
}
const decodeAttr = (raw) => {
if (!raw) return '';
return raw.replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
};
try { res.ids = JSON.parse(decodeAttr($cell.attr('data-class-ids') || $row.attr('data-class-ids') || '[]')) || []; } catch(_) {}
try { res.names = JSON.parse(decodeAttr($cell.attr('data-class-names') || $row.attr('data-class-names') || '[]')) || []; } catch(_) {}
return res;
}
// Track the rows used by modals across handlers
let rowNode = null;
let removeRowNode = null;
(function () {
if (typeof $ === 'undefined' || !$.fn.DataTable) return;
window.studentsTable = $.fn.DataTable.isDataTable('#studentsTable')
? $('#studentsTable').DataTable()
: $('#studentsTable').DataTable({
responsive: true,
pageLength: 100,
lengthMenu: [5, 10, 25, 50, 100],
order: [[0, 'asc']],
stateSave: true,
columnDefs: [{ orderable: false, targets: [7] }],
language: { emptyTable: 'No students found.' }
});
window.studentsTable.on('draw', function(){ renderAssignedBadges(); });
window.getStudentsTable = function(){
return window.studentsTable || ($.fn.DataTable.isDataTable('#studentsTable') ? $('#studentsTable').DataTable() : null);
};
window.table = window.studentsTable; // legacy reference for existing handlers
// Open modal and set values
$(document).on('click', '.assign-student-btn', function(){
const $row = $(this).closest('tr');
rowNode = $row.get(0);
$('#studentId').val($(this).data('student-id'));
$('#studentName').val($(this).data('student-name'));
// Pre-select currently assigned classes using live badges, fallback to data attrs.
const fromRow = getAssignmentsFromRow($row);
const ids = Array.isArray(fromRow.ids) ? fromRow.ids : [];
const names = Array.isArray(fromRow.names) ? fromRow.names : [];
$('#student_class_section_id').val(ids.map(String)).trigger('change');
let current = $(this).attr('data-current-classes') || $(this).data('current-classes') || '';
if (!current && names.length) current = names.join(', ');
$('#currentAssignments').text(current ? `Currently assigned: ${current}` : 'Currently assigned: none');
$('#assignError').addClass('d-none').text('');
});
// Open remove modal and populate options
$(document).on('click', '.remove-class-btn', function(){
removeRowNode = $(this).closest('tr').get(0);
$('#removeStudentId').val($(this).data('student-id'));
$('#removeStudentName').val($(this).data('student-name'));
$('#removeError').addClass('d-none').text('');
const select = $('#remove_class_section_id');
select.empty();
select.append('<option value="" disabled selected>Select a class</option>');
try {
const ids = JSON.parse($(this).attr('data-class-ids') || '[]') || [];
const names = JSON.parse($(this).attr('data-class-names') || '[]') || [];
ids.forEach((id, idx) => {
const label = names[idx] || `Class #${id}`;
select.append(`<option value="${id}">${label}</option>`);
});
} catch (_) {
select.append('<option value="" disabled>Error loading classes</option>');
}
});
// Button click → trigger AJAX submit
$('#assignStudentClassButton').on('click', function(){
const form = document.getElementById('assignStudentClassForm');
if (form.checkValidity()) $('#assignStudentClassForm').trigger('submit');
else alert('Please select a class before submitting.');
});
// AJAX submit for assign
$(document).on('submit', '#assignStudentClassForm', function(e){
e.preventDefault();
const $form = $(this);
const url = $form.attr('action');
applyCsrfToForms(CSRF_NAME, CSRF_HASH);
const $btn = $('#assignStudentClassButton');
const orig = $btn.text();
$btn.prop('disabled', true).text('Assigning...');
const csrfField = $form.find('input[name="'+CSRF_NAME+'"]');
const csrfHeaderVal = csrfField.length ? csrfField.val() : CSRF_HASH;
$.ajax({
type: 'POST',
url: url,
data: $form.serialize(),
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest',
...(csrfHeaderVal ? {'X-CSRF-TOKEN': csrfHeaderVal} : {})
}
})
.done(function(res){
updateCsrf($form, res);
if (!res || !res.ok) {
$('#assignError').removeClass('d-none').text(res?.message || 'Unable to assign class.');
return;
}
// Update the table cell
if (rowNode) {
const tbl = window.getStudentsTable ? window.getStudentsTable() : null;
if (!tbl) return;
const displayVal = Array.isArray(res.class_section_names)
? res.class_section_names.join(', ')
: (res.class_section_name || '-');
const badgeHtml = renderBadgesHtml(res.class_section_ids || [], res.class_section_names || [], parseInt($(rowNode).attr('data-student-id') || '0', 10));
const rowIdx = tbl.row(rowNode).index();
tbl.cell(rowIdx, CLASS_COL_INDEX).data(badgeHtml);
const $row = $(tbl.row(rowIdx).node());
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
$cell.html(badgeHtml);
syncAssignButtonData($row, res.class_section_ids || [], res.class_section_names || [], displayVal);
tbl.draw(false);
renderAssignedBadges();
$(rowNode).addClass('table-success');
setTimeout(()=>$(rowNode).removeClass('table-success'), 1200);
}
// Close modal
const modalEl = document.getElementById('assignStudentClassModal');
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
})
.fail(function(xhr){
try {
const res = JSON.parse(xhr.responseText);
updateCsrf($form, res);
$('#assignError').removeClass('d-none').text(res?.message || 'Server error.');
} catch (_) {
$('#assignError').removeClass('d-none').text('Network or server error.');
}
})
.always(function(){
$btn.prop('disabled', false).text(orig);
});
});
})();
</script>
<script>
// Handle auto-distribute submit via AJAX
(function(){
const form = document.getElementById('autoDistForm');
if (!form) return;
form.addEventListener('submit', function(e){
e.preventDefault();
const fd = new FormData(form);
const msg = document.getElementById('autoDistMsg');
msg.textContent = 'Running distribution...';
fetch(form.action, { method: 'POST', body: fd, headers: { 'X-Requested-With':'XMLHttpRequest' } })
.then(r => r.json())
.then(res => {
msg.textContent = (res && res.message) ? res.message : 'Done.';
if (res && res.sections) {
console.log('Section summary', res.sections);
}
})
.catch(() => { msg.textContent = 'Failed to distribute.'; });
});
})();
// Handle remove class submit
(function(){
$(document).on('click', '#removeStudentClassButton', function(){
const form = document.getElementById('removeStudentClassForm');
if (form.checkValidity()) {
$('#removeStudentClassForm').trigger('submit');
} else {
alert('Please select a class to remove.');
}
});
$(document).on('submit', '#removeStudentClassForm', function(e){
e.preventDefault();
const $form = $(this);
const url = $form.attr('action');
const $btn = $('#removeStudentClassButton');
const orig = $btn.text();
$btn.prop('disabled', true).text('Removing...');
applyCsrfToForms(CSRF_NAME, CSRF_HASH);
const csrfField = $form.find('input[name="'+CSRF_NAME+'"]');
const csrfHeaderVal = csrfField.length ? csrfField.val() : CSRF_HASH;
$.ajax({
type: 'POST',
url: url,
data: $form.serialize(),
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest',
...(csrfHeaderVal ? {'X-CSRF-TOKEN': csrfHeaderVal} : {})
}
})
.done(function(res){
updateCsrf($form, res);
if (!res || !res.ok) {
$('#removeError').removeClass('d-none').text(res?.message || 'Unable to remove class.');
return;
}
// Update the table cell + data attributes
const tbl = window.getStudentsTable ? window.getStudentsTable() : null;
if (tbl) {
const targetRowNode = removeRowNode || $('#studentsTable tbody tr[data-student-id="'+(res.student_id || '')+'"]').get(0);
if (targetRowNode) {
const badgeHtml = renderBadgesHtml(res.remaining_ids || [], res.remaining_names || [], parseInt($(targetRowNode).attr('data-student-id') || '0', 10));
const rowIdx = tbl.row(targetRowNode).index();
tbl.cell(rowIdx, CLASS_COL_INDEX).data(badgeHtml);
const $row = $(tbl.row(rowIdx).node());
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
$cell.html(badgeHtml); // ensure DOM shows latest without refresh
if (typeof syncAssignButtonData === 'function') {
syncAssignButtonData($row, res.remaining_ids || [], res.remaining_names || [], res.remaining_display || '');
}
tbl.draw(false); // redraw to keep ordering but stay on page
}
}
renderAssignedBadges();
const rowEl = removeRowNode || $('#studentsTable tbody tr[data-student-id="'+(res.student_id || '')+'"]').get(0);
if (rowEl) {
$(rowEl).addClass('table-warning');
setTimeout(()=>$(rowEl).removeClass('table-warning'), 1200);
}
const modalEl = document.getElementById('removeStudentClassModal');
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
})
.fail(function(xhr){
try {
const res = JSON.parse(xhr.responseText);
updateCsrf($form, res);
$('#removeError').removeClass('d-none').text(res?.message || 'Server error.');
} catch (_) {
$('#removeError').removeClass('d-none').text('Network or server error.');
}
})
.always(function(){
$btn.prop('disabled', false).text(orig);
});
});
})();
// Inline chip removal (no modal)
$(document).on('click', '.remove-class-chip', function(e){
e.preventDefault();
const cid = parseInt($(this).data('class-id') || 0, 10);
const sid = parseInt($(this).data('student-id') || 0, 10);
if (!cid || !sid) return;
removeRowNode = $(this).closest('tr').get(0);
$('#removeStudentId').val(sid);
const label = $(this).closest('.badge').clone().children('button').remove().end().text().trim();
$('#remove_class_section_id').empty().append(`<option value="${cid}" selected>${label || ('Class #' + cid)}</option>`).val(cid);
$('#removeStudentClassForm').trigger('submit');
});
// Initial render of badges on page load
renderAssignedBadges();
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,670 @@
<?php
// Options (can be moved to config or controller)
$medicalOptions = $medicalOptions ?? [
"None",
"ADHD (Attention-Deficit/Hyperactivity Disorder)",
"Anxiety or Emotional Disorders",
"Asthma",
"Autism Spectrum Disorder (ASD)",
"Behavioral or Conduct Disorders",
"Blindness / Vision Impairment",
"Celiac Disease (Gluten Intolerance)",
"Cerebral Palsy",
"Cystic Fibrosis",
"Depression",
"Diabetes (Type 1 or Type 2)",
"Down Syndrome",
"Dyslexia or Learning Disabilities",
"Eating Disorders",
"Eczema / Severe Skin Conditions",
"Epilepsy / Seizure Disorders",
"Hearing Impairments / Deafness",
"Heart Conditions (congenital or acquired)",
"Hemophilia / Bleeding Disorders",
"Kidney Disease",
"Migraines / Chronic Headaches",
"Obsessive-Compulsive Disorder (OCD)",
"Physical Disabilities / Mobility Impairments",
"PTSD (Post-Traumatic Stress Disorder)",
"Sickle Cell Anemia",
"Speech and Language Disorders",
"Thyroid Disorders",
"Tourette Syndrome",
"Traumatic Brain Injury (TBI)",
"Rheumatic diseases",
"Ulcerative Colitis / Crohns Disease",
"Other"
];
$allergyOptions = $allergyOptions ?? [
"None",
"Animal Dander (cats, dogs, etc.)",
"Antibiotics",
"Bee stings",
"Cockroach",
"Corn",
"Dust Mites",
"Egg",
"Fire ant stings",
"Fish",
"Fragrances / Perfumes",
"Latex",
"Milk / Dairy",
"Mold",
"Mosquito bites",
"Peanut",
"Pollen (grass, tree, weed)",
"Sesame",
"Shellfish (shrimp, crab, lobster, etc.)",
"Soy",
"Tree Nuts (almond, cashew, walnut, etc.)",
"Wasp stings",
"Wheat / Gluten",
"Other"
];
$gradeOptions = $gradeOptions ?? ['KG', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', 'Youth'];
?>
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-3">Student Profiles</h2>
<div class="table-responsive">
<table id="myTable" class="table table-striped table-hover align-middle">
<thead class="table-dark">
<tr>
<th>School ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>DOB</th>
<th>Age</th>
<th>Gender</th>
<th>Assigned Grade</th>
<th>Active</th>
<th>Medical Conditions</th>
<th>Allergies</th>
<th>Photo Consent</th>
<th>Registration Date</th>
<th style="min-width: 220px;">Action</th>
</tr>
</thead>
<tbody>
<?php if (!empty($students)): ?>
<?php foreach ($students as $student): ?>
<?php
$modalIdContact = 'contactModal' . (int)$student['id'];
$modalIdEdit = 'editStudentModal' . (int)$student['id'];
$updateUrl = base_url('administrator/students/update'); // POST target
// Format DOB for display
$dobDisp = '';
if (!empty($student['dob'])) {
try {
$dobDisp = (new DateTime($student['dob']))->format('m-d-Y');
} catch (\Throwable $e) {
$dobDisp = esc($student['dob']);
}
}
// Format Reg Date for display (as local mm-dd-YYYY)
$regDisp = '';
if (!empty($student['registration_date'])) {
try {
$dt = new \DateTime($student['registration_date'], new \DateTimeZone('UTC'));
$dt->setTimezone(new \DateTimeZone(user_timezone()));
$regDisp = local_datetime($student['registration_date'], 'm-d-Y H:i');
} catch (\Throwable $e) {
$regDisp = esc($student['registration_date']);
}
}
// Input-safe values
$dobVal = $dobDisp;
$regDateLocal = '';
if (!empty($student['registration_date'])) {
try {
$dt2 = new \DateTime($student['registration_date'], new \DateTimeZone('UTC'));
$dt2->setTimezone(new \DateTimeZone(user_timezone()));
$regDateLocal = local_datetime($student['registration_date'], 'Y-m-d\TH:i');
} catch (\Throwable $e) {
$regDateLocal = '';
}
}
$isActive = (int)($student['is_active'] ?? 1);
// Preselected lists (server-computed strings or joined lists)
$medSelected = array_filter(array_map('trim', preg_split('/[,\n;]+/', (string)($student['medical_conditions'] ?? ''), -1, PREG_SPLIT_NO_EMPTY)));
$allSelected = array_filter(array_map('trim', preg_split('/[,\n;]+/', (string)($student['allergies'] ?? ''), -1, PREG_SPLIT_NO_EMPTY)));
$medOther = implode(', ', array_diff($medSelected, $medicalOptions));
$allOther = implode(', ', array_diff($allSelected, $allergyOptions));
$medId = 'medical_conditions_' . (int)$student['id'];
$allId = 'allergies_' . (int)$student['id'];
$medOtherId = 'medical_other_' . (int)$student['id'];
$allOtherId = 'allergy_other_' . (int)$student['id'];
?>
<tr>
<td><?= esc($student['school_id']) ?></td>
<td>
<a href="#" class="text-decoration-none" data-family-student-id="<?= (int)($student['id'] ?? 0) ?>">
<?= esc($student['firstname']) ?>
</a>
</td>
<td>
<a href="#" class="text-decoration-none" data-family-student-id="<?= (int)($student['id'] ?? 0) ?>">
<?= esc($student['lastname']) ?>
</a>
</td>
<td><?= esc($dobDisp) ?></td>
<td><?= esc((string)($student['age'] ?? '')) ?></td>
<td><?= esc($student['gender']) ?></td>
<td><?= esc($student['class_section_name']) ?></td>
<td><?= $isActive ? 'Yes' : 'No' ?></td>
<td><?= esc($student['medical_conditions'] ?? '') ?></td>
<td><?= esc($student['allergies'] ?? '') ?></td>
<td><?= !empty($student['photo_consent']) ? 'Yes' : 'No' ?></td>
<td><?= esc($regDisp) ?></td>
<td class="d-flex flex-wrap gap-2">
<!-- Contact Modal Trigger -->
<button class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#<?= $modalIdContact ?>">
Contact Information
</button>
<!-- Edit Modal Trigger -->
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#<?= $modalIdEdit ?>">
Edit Student
</button>
<!-- Contact Modal -->
<div class="modal fade" id="<?= $modalIdContact ?>" tabindex="-1" aria-labelledby="<?= $modalIdContact ?>Label" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="<?= $modalIdContact ?>Label">
Contact Info - <?= esc($student['firstname']) ?> <?= esc($student['lastname']) ?>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h5 class="mb-3 border-bottom pb-1">👨‍👩‍👧 Parent Information</h5>
<div class="row mb-3">
<div class="col-md-6">
<p><strong>Name:</strong> <?= esc(($student['parent_firstname'] ?? '') . ' ' . ($student['parent_lastname'] ?? '')) ?></p>
<p><strong>Phone:</strong> <?= esc($student['parent_phone'] ?? 'N/A') ?></p>
</div>
<div class="col-md-6">
<p><strong>Email:</strong> <?= esc($student['parent_email'] ?? 'N/A') ?></p>
</div>
</div>
<h5 class="mb-3 border-bottom pb-1">🚨 Emergency Contact</h5>
<div class="row">
<div class="col-md-6">
<p><strong>Name:</strong> <?= esc($student['emergency_name'] ?? 'N/A') ?></p>
<p><strong>Relationship:</strong> <?= esc($student['emergency_relationship'] ?? 'N/A') ?></p>
<p><strong>Phone:</strong> <?= esc($student['emergency_phone'] ?? 'N/A') ?></p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Edit Student Modal -->
<div class="modal fade" id="<?= $modalIdEdit ?>" tabindex="-1" aria-labelledby="<?= $modalIdEdit ?>Label" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content shadow">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="<?= $modalIdEdit ?>Label">
Edit Student - <?= esc($student['firstname']) ?> <?= esc($student['lastname']) ?>
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="post" action="<?= $updateUrl ?>" class="needs-validation" novalidate>
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= (int)$student['id'] ?>">
<div class="modal-body">
<div class="row g-3">
<!-- school_id -->
<div class="col-md-3">
<label class="form-label">School ID</label>
<input type="text" name="school_id" class="form-control" value="<?= esc($student['school_id'] ?? '') ?>" maxlength="100">
</div>
<!-- firstname -->
<div class="col-md-3">
<label class="form-label">First Name</label>
<input type="text" name="firstname" class="form-control"
value="<?= esc($student['firstname'] ?? '') ?>"
pattern="^[A-Za-z\s\-]{2,30}$" required>
<div class="invalid-feedback">Only letters, spaces, or dashes (230 chars).</div>
</div>
<!-- lastname -->
<div class="col-md-3">
<label class="form-label">Last Name</label>
<input type="text" name="lastname" class="form-control"
value="<?= esc($student['lastname'] ?? '') ?>"
pattern="^[A-Za-z\s\-]{2,30}$" required>
<div class="invalid-feedback">Only letters, spaces, or dashes (230 chars).</div>
</div>
<!-- gender -->
<div class="col-md-3">
<label class="form-label">Gender</label>
<select name="gender" class="form-select" required>
<option value="" disabled <?= empty($student['gender']) ? 'selected' : '' ?>>Select…</option>
<option value="Male" <?= (strcasecmp($student['gender'] ?? '', 'Male') === 0) ? 'selected' : '' ?>>Male</option>
<option value="Female" <?= (strcasecmp($student['gender'] ?? '', 'Female') === 0) ? 'selected' : '' ?>>Female</option>
</select>
<div class="invalid-feedback">Please choose a gender.</div>
</div>
<!-- dob -->
<div class="col-md-3">
<label class="form-label">DOB</label>
<input type="date" name="dob" class="form-control js-dob" value="<?= esc($dobVal) ?>" required>
<div class="invalid-feedback">Please provide a valid date of birth.</div>
</div>
<!-- age (readonly) -->
<div class="col-md-3">
<label class="form-label">Age</label>
<input type="number" name="age" class="form-control js-age" value="<?= (int)($student['age'] ?? 0) ?>" min="0" step="1" readonly>
<div class="form-text">Auto-calculated from DOB.</div>
</div>
<!-- registration_grade -->
<div class="col-md-3">
<label class="form-label">Grade</label>
<select name="registration_grade" class="form-select" required>
<option value="" disabled <?= empty($student['registration_grade']) ? 'selected' : '' ?>>Select…</option>
<?php foreach ($gradeOptions as $opt): ?>
<option value="<?= esc($opt) ?>" <?= (strcasecmp((string)($student['registration_grade'] ?? ''), (string)$opt) === 0) ? 'selected' : '' ?>>
<?= esc($opt) ?>
</option>
<?php endforeach; ?>
</select>
<div class="invalid-feedback">Please choose a grade.</div>
</div>
<!-- registration_date (datetime-local) -->
<div class="col-md-3">
<label class="form-label">Registration Date & Time</label>
<input type="datetime-local" name="registration_date" class="form-control" value="<?= esc($regDateLocal) ?>">
<div class="form-text">Local time (<?= esc(user_timezone()) ?>).</div>
</div>
<!-- parent_id -->
<div class="col-md-3">
<label class="form-label">Parent ID</label>
<input type="number" name="parent_id" class="form-control" value="<?= (int)($student['parent_id'] ?? 0) ?>" min="1" step="1" required>
<div class="invalid-feedback">Parent ID is required.</div>
</div>
<!-- year_of_registration -->
<div class="col-md-3">
<label class="form-label">Year of Registration</label>
<input type="text" name="year_of_registration" class="form-control"
value="<?= esc($student['year_of_registration'] ?? '') ?>"
pattern="^\d{4}(-\d{4})?$" maxlength="25" placeholder="e.g., 2025 or 2025-2026">
<div class="invalid-feedback">Use YYYY or YYYY-YYYY.</div>
</div>
<!-- school_year -->
<?php
$syDatalistId = 'datalist-school-years-' . (int)$student['id'];
$nowY = (int)date('Y');
$syOptions = [];
for ($y = $nowY - 2; $y <= $nowY + 3; $y++) {
$syOptions[] = $y . '-' . ($y + 1);
}
?>
<div class="col-md-3">
<label class="form-label">School Year</label>
<input list="<?= $syDatalistId ?>" name="school_year" class="form-control"
value="<?= esc($student['school_year'] ?? '') ?>"
pattern="^\d{4}-\d{4}$" maxlength="9" placeholder="YYYY-YYYY">
<datalist id="<?= $syDatalistId ?>">
<?php foreach ($syOptions as $sy): ?>
<option value="<?= esc($sy) ?>"></option>
<?php endforeach; ?>
</datalist>
<div class="invalid-feedback">Format: YYYY-YYYY.</div>
</div>
<!-- rfid_tag -->
<div class="col-md-3">
<label class="form-label">RFID Tag</label>
<input type="text" name="rfid_tag" class="form-control" value="<?= esc($student['rfid_tag'] ?? '') ?>" maxlength="100" placeholder="Optional">
</div>
<!-- semester -->
<div class="col-md-3">
<label class="form-label">Semester</label>
<?php $sem = trim((string)($student['semester'] ?? '')); ?>
<select name="semester" class="form-select">
<option value="" <?= $sem === '' ? 'selected' : '' ?>>—</option>
<option value="Fall" <?= strcasecmp($sem, 'Fall') === 0 ? 'selected' : '' ?>>Fall</option>
<option value="Spring" <?= strcasecmp($sem, 'Spring') === 0 ? 'selected' : '' ?>>Spring</option>
<option value="Summer" <?= strcasecmp($sem, 'Summer') === 0 ? 'selected' : '' ?>>Summer</option>
</select>
</div>
<!-- tuition_paid -->
<div class="col-md-3">
<label class="form-label d-block">Tuition Paid</label>
<input type="hidden" name="tuition_paid" value="0">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" value="1" id="tuition_paid_<?= (int)$student['id'] ?>" name="tuition_paid" <?= !empty($student['tuition_paid']) ? 'checked' : '' ?>>
<label class="form-check-label" for="tuition_paid_<?= (int)$student['id'] ?>">Mark as paid</label>
</div>
</div>
<!-- is_new -->
<div class="col-md-3">
<label class="form-label">Is New Student?</label>
<?php $isNewVal = (string)($student['is_new'] ?? '1'); ?>
<select name="is_new" class="form-select" required>
<option value="1" <?= $isNewVal === '1' ? 'selected' : '' ?>>Yes (New)</option>
<option value="0" <?= $isNewVal === '0' ? 'selected' : '' ?>>No (Returning)</option>
</select>
</div>
<!-- is_active -->
<div class="col-md-3">
<label class="form-label d-block">Active Status</label>
<input type="hidden" name="is_active" value="0">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" value="1" id="is_active_<?= (int)$student['id'] ?>" name="is_active" <?= $isActive ? 'checked' : '' ?>>
<label class="form-check-label" for="is_active_<?= (int)$student['id'] ?>">Show in classes/attendance</label>
</div>
</div>
<!-- photo_consent -->
<div class="col-md-12">
<div class="form-check mt-2">
<input type="hidden" name="photo_consent" value="0">
<input class="form-check-input" type="checkbox" value="1" id="photo_consent_<?= (int)$student['id'] ?>" name="photo_consent" <?= !empty($student['photo_consent']) ? 'checked' : '' ?>>
<label class="form-check-label" for="photo_consent_<?= (int)$student['id'] ?>">Photo Consent</label>
</div>
</div>
<!-- Medical Conditions -->
<div class="col-md-6">
<label class="form-label">Medical Conditions</label>
<!-- Hidden payload + touched flag -->
<input type="hidden" name="medical_conditions" id="<?= $medId ?>_hidden">
<input type="hidden" name="medical_touched" id="<?= $medId ?>_touched" value="0">
<select class="form-select js-multi"
id="<?= $medId ?>"
multiple
data-none="None"
data-hidden="#<?= $medId ?>_hidden"
data-other="#<?= $medOtherId ?>"
data-touched="#<?= $medId ?>_touched">
<?php foreach ($medicalOptions as $opt): ?>
<option value="<?= esc($opt) ?>" <?= in_array($opt, $medSelected, true) ? 'selected' : '' ?>>
<?= esc($opt) ?>
</option>
<?php endforeach; ?>
</select>
<div class="form-text">Hold Ctrl/⌘ to select multiple. Choose <b>None</b> if no conditions.</div>
<label class="form-label mt-2">Other (free text, comma-separated)</label>
<input type="text" class="form-control" id="<?= $medOtherId ?>" placeholder="e.g., Seasonal rhinitis" value="<?= esc($medOther) ?>">
</div>
<!-- Allergies -->
<div class="col-md-6">
<label class="form-label">Allergies</label>
<!-- Hidden payload + touched flag -->
<input type="hidden" name="allergies" id="<?= $allId ?>_hidden">
<input type="hidden" name="allergies_touched" id="<?= $allId ?>_touched" value="0">
<select class="form-select js-multi"
id="<?= $allId ?>"
multiple
data-none="None"
data-hidden="#<?= $allId ?>_hidden"
data-other="#<?= $allOtherId ?>"
data-touched="#<?= $allId ?>_touched">
<?php foreach ($allergyOptions as $opt): ?>
<option value="<?= esc($opt) ?>" <?= in_array($opt, $allSelected, true) ? 'selected' : '' ?>>
<?= esc($opt) ?>
</option>
<?php endforeach; ?>
</select>
<div class="form-text">Hold Ctrl/⌘ to select multiple. Choose <b>None</b> if no allergies.</div>
<label class="form-label mt-2">Other (free text, comma-separated)</label>
<input type="text" class="form-control" id="<?= $allOtherId ?>" placeholder="e.g., Penicillin" value="<?= esc($allOther) ?>">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
<!-- /Edit Student Modal -->
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="13" class="text-center">No students found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
/* ============== DataTables (guarded) ============== */
if (window.jQuery && typeof jQuery.fn.DataTable === 'function' && document.getElementById('myTable')) {
jQuery('#myTable').DataTable(); // Use your own options if needed
}
/* ============== Bootstrap 5 validation ============== */
(function() {
const forms = document.querySelectorAll('.needs-validation');
Array.prototype.forEach.call(forms, (form) => {
form.addEventListener('submit', (evt) => {
if (!form.checkValidity()) {
evt.preventDefault();
evt.stopPropagation();
}
form.classList.add('was-validated');
}, false);
});
})();
/* ============== Helpers ============== */
function splitCSV(s) {
if (!s) return [];
return s.split(',').map(x => x.trim()).filter(Boolean);
}
function uniq(arr) {
const seen = new Set();
const out = [];
for (const v of arr) {
const k = v.toLowerCase();
if (!seen.has(k)) {
seen.add(k);
out.push(v);
}
}
return out;
}
function setTouched(sel) {
const tSel = sel.dataset.touched ? document.querySelector(sel.dataset.touched) : null;
if (tSel) tSel.value = '1';
}
function updateHiddenFromSelect(sel) {
const hiddenSel = sel.dataset.hidden ? document.querySelector(sel.dataset.hidden) : null;
if (!hiddenSel) return;
const noneLabel = (sel.dataset.none || 'None').toLowerCase();
const otherInp = sel.dataset.other ? document.querySelector(sel.dataset.other) : null;
const selected = Array.from(sel.selectedOptions).map(o => o.value).filter(Boolean);
const others = otherInp && !otherInp.disabled ? splitCSV(otherInp.value) : [];
let final = uniq([...selected, ...others]);
// Remove "None" token from payload; exclusivity handled elsewhere
final = final.filter(v => v.toLowerCase() !== noneLabel);
hiddenSel.value = final.length ? final.join(', ') : '';
}
function enforceNone(sel) {
const noneLabel = sel.dataset.none || 'None';
const values = Array.from(sel.selectedOptions).map(o => o.value);
const otherInp = sel.dataset.other ? document.querySelector(sel.dataset.other) : null;
if (values.includes(noneLabel)) {
// Deselect everything except "None"
Array.from(sel.options).forEach(opt => {
if (opt.value !== noneLabel) opt.selected = false;
});
if (otherInp) {
otherInp.value = '';
otherInp.disabled = true;
}
} else {
// Ensure "None" is off if other items chosen
Array.from(sel.options).forEach(opt => {
if (opt.value === noneLabel) opt.selected = false;
});
if (otherInp) {
otherInp.disabled = false;
}
}
updateHiddenFromSelect(sel);
}
function bindSelect(sel) {
if (sel.__bound) return;
sel.__bound = true;
// Initial normalize + pack based on server preselects
enforceNone(sel);
sel.addEventListener('change', () => {
enforceNone(sel);
setTouched(sel);
});
const otherInp = sel.dataset.other ? document.querySelector(sel.dataset.other) : null;
if (otherInp) {
otherInp.addEventListener('input', () => {
updateHiddenFromSelect(sel);
setTouched(sel);
});
}
}
// Bind all multi-selects present on the page (all modals rendered server-side)
document.querySelectorAll('select.js-multi').forEach(bindSelect);
// Modal lifecycle: wire age + (re)bind selects inside shown modal
document.querySelectorAll('.modal').forEach((modal) => {
modal.addEventListener('shown.bs.modal', () => {
// Age from DOB
const dob = modal.querySelector('.js-dob');
const age = modal.querySelector('.js-age');
if (dob && age) {
const update = () => {
if (!dob.value) {
age.value = '';
return;
}
const d = new Date(dob.value + 'T00:00:00');
if (isNaN(d.getTime())) {
age.value = '';
return;
}
const t = new Date();
let a = t.getFullYear() - d.getFullYear();
const m = t.getMonth() - d.getMonth();
if (m < 0 || (m === 0 && t.getDate() < d.getDate())) a--;
age.value = a >= 0 ? a : '';
};
update();
dob.addEventListener('change', update);
dob.addEventListener('input', update);
}
modal.querySelectorAll('select.js-multi').forEach(bindSelect);
// Ensure hidden fields packed and flags set on submit
modal.querySelectorAll('form').forEach((form) => {
if (form.__syncBound) return;
form.__syncBound = true;
form.addEventListener('submit', () => {
form.querySelectorAll('select.js-multi').forEach((sel) => {
enforceNone(sel);
updateHiddenFromSelect(sel);
setTouched(sel);
});
});
});
});
});
// --- CSRF sync: ensure latest token is sent on submit ---
(function attachCsrfSync() {
const TOKEN_NAME = '<?= csrf_token() ?>';
const COOKIE_NAME = <?= json_encode(config('Security')->csrfCookieName ?? (config('Security')->cookieName ?? 'csrf_cookie_name')) ?>;
function getCookie(name) {
const parts = document.cookie.split(';');
for (let i = 0; i < parts.length; i++) {
const p = parts[i].trim();
if (p.startsWith(name + '=')) return decodeURIComponent(p.substring(name.length + 1));
}
return '';
}
function syncFormToken(form) {
try {
const cookieVal = getCookie(COOKIE_NAME);
if (!cookieVal) return; // nothing to sync
const hidden = form.querySelector('input[name="' + TOKEN_NAME + '"]');
if (hidden) hidden.value = cookieVal;
} catch (_) {
// no-op
}
}
// Bind to all edit forms on this page
document.querySelectorAll('form').forEach((form) => {
if (form.__csrfSyncBound) return;
form.__csrfSyncBound = true;
form.addEventListener('submit', () => syncFormToken(form));
});
})();
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,205 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
$classes = $classes ?? [];
$entries = $entries ?? [];
$subjectLabels = $subjectLabels ?? [];
$editEntry = $editEntry ?? null;
?>
<div class="container-fluid">
<div class="d-flex flex-wrap align-items-center justify-content-between mb-3 gap-2">
<div>
<h2 class="mb-1">Subject Curriculum</h2>
<p class="text-muted mb-0">Add, update, or remove the unit/surah targets that teachers use when reporting class progress.</p>
</div>
<a href="<?= site_url('administrator/class_assignment') ?>" class="btn btn-outline-secondary btn-sm">Back to class list</a>
</div>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('errors')): ?>
<div class="alert alert-danger">
<ul class="mb-0">
<?php foreach (session()->getFlashdata('errors') as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="row g-4">
<div class="col-lg-5">
<div class="card shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0">Add new curriculum entry</h5>
</div>
<div class="card-body">
<form action="<?= site_url('administrator/subject-curriculum/save') ?>" method="post">
<?= csrf_field() ?>
<div class="mb-3">
<label class="form-label">Class</label>
<select name="class_id" class="form-select" required>
<option value="">Select a class</option>
<?php foreach ($classes as $class): ?>
<option value="<?= esc($class['id']) ?>"
<?= set_select('class_id', $class['id'], false) ?>>
<?= esc($class['class_name'] ?? 'Unnamed') ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Subject</label>
<select name="subject" class="form-select" required>
<?php foreach ($subjectLabels as $key => $label): ?>
<option value="<?= esc($key) ?>" <?= set_select('subject', $key, $key === 'islamic') ?>>
<?= esc($label) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Unit / Term number</label>
<input type="number" name="unit_number" class="form-control" placeholder="Optional" value="<?= set_value('unit_number') ?>">
<div class="form-text text-muted small">Leave blank for Quran/Arabic entries that only need a chapter/surah.</div>
</div>
<div class="mb-3">
<label class="form-label">Unit title</label>
<input type="text" name="unit_title" class="form-control" placeholder="Optional summary" value="<?= set_value('unit_title') ?>">
</div>
<div class="mb-3">
<label class="form-label">Chapter / Surah</label>
<input type="text" name="chapter_name" class="form-control" required value="<?= set_value('chapter_name') ?>">
</div>
<button type="submit" class="btn btn-primary w-100">Save curriculum entry</button>
</form>
</div>
</div>
<?php if ($editEntry): ?>
<div class="card shadow-sm mt-4">
<div class="card-header bg-white">
<h5 class="mb-0">Edit entry</h5>
</div>
<div class="card-body">
<form action="<?= site_url('administrator/subject-curriculum/update/' . $editEntry['id']) ?>" method="post">
<?= csrf_field() ?>
<div class="mb-3">
<label class="form-label">Class</label>
<select name="class_id" class="form-select" required>
<?php foreach ($classes as $class): ?>
<?php
$selected = set_select('class_id', $class['id'], ((int)($editEntry['class_id'] ?? 0)) === (int)$class['id']);
?>
<option value="<?= esc($class['id']) ?>" <?= $selected ?>>
<?= esc($class['class_name'] ?? 'Unnamed') ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Subject</label>
<select name="subject" class="form-select" required>
<?php foreach ($subjectLabels as $key => $label): ?>
<?php
$isSelected = set_select('subject', $key, ($editEntry['subject'] ?? '') === $key);
?>
<option value="<?= esc($key) ?>" <?= $isSelected ?>>
<?= esc($label) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Unit / Term number</label>
<input type="number" name="unit_number" class="form-control" placeholder="Optional" value="<?= set_value('unit_number', $editEntry['unit_number'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label">Unit title</label>
<input type="text" name="unit_title" class="form-control" placeholder="Optional summary" value="<?= set_value('unit_title', $editEntry['unit_title'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label">Chapter / Surah</label>
<input type="text" name="chapter_name" class="form-control" required value="<?= set_value('chapter_name', $editEntry['chapter_name'] ?? '') ?>">
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-success w-100">Apply changes</button>
<a href="<?= site_url('administrator/subject-curriculum') ?>" class="btn btn-outline-secondary w-100">Cancel</a>
</div>
</form>
</div>
</div>
<?php endif; ?>
</div>
<div class="col-lg-7">
<div class="card shadow-sm">
<div class="card-header bg-white d-flex align-items-center justify-content-between">
<h5 class="mb-0">Current curriculum entries</h5>
<span class="text-muted small"><?= count($entries) ?> record<?= count($entries) === 1 ? '' : 's' ?></span>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-hover align-middle mb-0" data-no-mgmt-sticky>
<thead class="table-light">
<tr>
<th>Class</th>
<th>Subject</th>
<th>Unit</th>
<th>Unit title</th>
<th>Chapter / Surah</th>
<th>Updated</th>
<th style="min-width: 170px;">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($entries)): ?>
<tr>
<td colspan="7" class="text-center text-muted">No curriculum records yet.</td>
</tr>
<?php else: ?>
<?php foreach ($entries as $entry): ?>
<?php
$subjectLabel = $subjectLabels[$entry['subject']] ?? ucfirst($entry['subject'] ?? '');
$updatedAt = $entry['updated_at'] ?? $entry['created_at'] ?? '';
$updatedDisplay = '';
if ($updatedAt) {
try {
$updatedDisplay = (new \DateTime($updatedAt))->format('M d, Y H:i');
} catch (\Exception $e) {
$updatedDisplay = $updatedAt;
}
}
?>
<tr>
<td><?= esc(($entry['class_name'] ?? '') ?: '—') ?></td>
<td><?= esc($subjectLabel) ?></td>
<td><?= $entry['unit_number'] ? esc((string)$entry['unit_number']) : '—' ?></td>
<td><?= esc(($entry['unit_title'] ?? '') ?: '—') ?></td>
<td><?= esc(($entry['chapter_name'] ?? '') ?: '—') ?></td>
<td><?= esc($updatedDisplay ?: '—') ?></td>
<td class="d-flex gap-2 flex-wrap">
<a href="<?= site_url('administrator/subject-curriculum/edit/' . $entry['id']) ?>" class="btn btn-sm btn-outline-primary">Edit</a>
<form action="<?= site_url('administrator/subject-curriculum/delete/' . $entry['id']) ?>" method="post" onsubmit="return confirm('Remove this curriculum entry?');">
<?= csrf_field() ?>
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,514 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div
class="container-fluid"
data-api-list="<?= esc($apiListUrl ?? '') ?>"
data-api-assign="<?= esc($apiAssignUrl ?? '') ?>"
data-api-delete="<?= esc($apiDeleteUrl ?? '') ?>"
data-csrf-name="<?= esc($csrfTokenName ?? csrf_token()) ?>"
data-csrf-value="<?= esc($csrfTokenValue ?? csrf_hash()) ?>"
>
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-2">Teacher Class Assignments</h2>
<?php if (!empty($missingYear)): ?>
<div class="alert alert-warning" role="alert">
Current school year is not configured. This page is read-only until configured in
<a href="<?= site_url('configuration/configuration_view') ?>" class="alert-link">Add/Edit Configuration</a> (key: <code>school_year</code>).
</div>
<?php endif; ?>
<div class="d-flex justify-content-center align-items-center gap-2 mb-3">
<label for="tcaYearSelect" class="col-form-label">School year</label>
<form method="get" action="<?= site_url('administrator/teacher_class_assignment') ?>" class="d-flex align-items-center gap-2">
<select id="tcaYearSelect" name="schoolYear" class="form-select form-select-sm" style="min-width: 180px;">
<?php
$years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : [];
if (empty($years) && !empty($schoolYear)) $years = [$schoolYear];
foreach ($years as $y): $val = is_array($y) && isset($y['school_year']) ? $y['school_year'] : (string)$y; ?>
<option value="<?= esc($val) ?>" <?= ((string)($schoolYear ?? '') === (string)$val) ? 'selected' : '' ?>>
<?= esc($val) ?>
</option>
<?php endforeach; ?>
</select>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<label for="tcaSemesterSelect" class="col-form-label">Semester</label>
<select id="tcaSemesterSelect" name="semester" class="form-select form-select-sm" style="min-width: 140px;">
<option value="">—</option>
<option value="Fall" <?= (strcasecmp($semVal,'Fall')===0?'selected':'') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal,'Spring')===0?'selected':'') ?>>Spring</option>
</select>
<button type="submit" class="btn btn-secondary btn-sm">Apply</button>
</form>
<?php if (isset($isCurrentYear) && !$isCurrentYear): ?>
<span class="badge bg-secondary">Read-only (Past Year)</span>
<?php endif; ?>
</div>
<?= view('partials/flash_messages') ?>
<div id="assignmentMessages"></div>
<table id="teachersTable" class="display table table-striped table-hover align-middle">
<thead class="table-dark">
<tr>
<th>#</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Phone Number</th>
<th>Role</th>
<th>Assigned Classes</th>
<th>School Year</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="9" class="text-center text-muted">Loading...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<?= $this->include('partials/assign_class_teacher_modal') ?>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
var container = document.querySelector('.container-fluid[data-api-list]');
if (!container) {
return;
}
var apiList = container.getAttribute('data-api-list') || '';
var apiAssign = container.getAttribute('data-api-assign') || '';
var apiDelete = container.getAttribute('data-api-delete') || '';
var csrfName = container.getAttribute('data-csrf-name') || '';
var csrfValue = container.getAttribute('data-csrf-value') || '';
var tableEl = document.getElementById('teachersTable');
var tbody = tableEl ? tableEl.querySelector('tbody') : null;
var messages = document.getElementById('assignmentMessages');
var selectedYear = <?= json_encode((string)($schoolYear ?? '')) ?>;
var isCurrentYear = <?= json_encode((bool)($isCurrentYear ?? true)) ?>;
var classSelect = document.getElementById('class_section_id');
var assignForm = document.getElementById('assignClassForm');
var assignButton = document.getElementById('assignClassButton');
var teacherRoleHidden = document.getElementById('teacherRoleHidden');
var teacherRoleDisplay = document.getElementById('teacherRoleDisplay');
var assignModalEl = document.getElementById('assignClassModal');
function updateCsrfFromResponse(data) {
if (!data) return;
if (data.csrf_token && data.csrf_hash) {
csrfName = data.csrf_token;
csrfValue = data.csrf_hash;
container.setAttribute('data-csrf-name', csrfName);
container.setAttribute('data-csrf-value', csrfValue);
var hiddenField = document.getElementById('assignCsrfField');
if (hiddenField) {
hiddenField.setAttribute('name', csrfName);
hiddenField.value = csrfValue;
}
}
}
function applyCsrf(payload) {
var data = Object.assign({}, payload || {});
if (csrfName) {
data[csrfName] = csrfValue;
}
return data;
}
function bootstrapAlertClass(type) {
if (type === 'success') return 'alert-success';
if (type === 'info') return 'alert-info';
if (type === 'warning') return 'alert-warning';
return 'alert-danger';
}
function showMessage(type, text) {
if (!messages || !text) return;
var alert = document.createElement('div');
alert.className = 'alert ' + bootstrapAlertClass(type) + ' alert-dismissible fade show';
alert.setAttribute('role', 'alert');
alert.innerHTML = '<span>' + text + '</span>'
+ '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>';
messages.appendChild(alert);
setTimeout(function () {
if (alert && alert.parentNode) {
alert.classList.remove('show');
alert.classList.add('fade');
setTimeout(function () {
if (alert.parentNode) {
alert.parentNode.removeChild(alert);
}
}, 200);
}
}, 6000);
}
function renderAssignedList(assignments, label, teacher) {
var fragment = document.createDocumentFragment();
assignments.forEach(function (assignment) {
var li = document.createElement('li');
li.className = 'd-flex align-items-center justify-content-between gap-2 mb-1';
var text = document.createElement('span');
text.innerHTML = '<strong>' + (assignment.class_section_name || 'Class #' + assignment.section_id) + '</strong> (' + label + ')';
li.appendChild(text);
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-outline-danger btn-sm remove-assignment-btn';
btn.textContent = 'Remove';
btn.setAttribute('data-teacher-id', teacher.teacher_id);
btn.setAttribute('data-teacher-name', teacher.name || '');
btn.setAttribute('data-class-section-id', assignment.section_id);
btn.setAttribute('data-class-name', assignment.class_section_name || 'Class #' + assignment.section_id);
var position = (assignment.role || '').toLowerCase();
if (position !== 'main' && position !== 'ta') {
position = label.toLowerCase() === 'ta' ? 'ta' : 'main';
}
btn.setAttribute('data-position', position);
var assignmentSemester = assignment.semester || '';
var assignmentSchoolYear = assignment.school_year || selectedYear || '';
btn.setAttribute('data-semester', assignmentSemester);
btn.setAttribute('data-school-year', assignmentSchoolYear);
li.appendChild(btn);
fragment.appendChild(li);
});
return fragment;
}
function renderTeachersTable(teachers, schoolYear) {
if (!tbody) return;
if (window.jQuery && $.fn.DataTable && $.fn.DataTable.isDataTable('#teachersTable')) {
$('#teachersTable').DataTable().destroy();
}
tbody.innerHTML = '';
if (!teachers || teachers.length === 0) {
var emptyRow = document.createElement('tr');
var emptyCell = document.createElement('td');
emptyCell.colSpan = 9;
emptyCell.className = 'text-center text-muted';
emptyCell.textContent = 'No teachers found.';
emptyRow.appendChild(emptyCell);
tbody.appendChild(emptyRow);
} else {
teachers.forEach(function (teacher, index) {
var row = document.createElement('tr');
var orderCell = document.createElement('td');
orderCell.className = 'text-center';
orderCell.textContent = index + 1;
row.appendChild(orderCell);
var firstCell = document.createElement('td');
firstCell.textContent = teacher.firstname || '';
row.appendChild(firstCell);
var lastCell = document.createElement('td');
lastCell.textContent = teacher.lastname || '';
row.appendChild(lastCell);
var emailCell = document.createElement('td');
emailCell.textContent = teacher.email || '';
row.appendChild(emailCell);
var phoneCell = document.createElement('td');
phoneCell.textContent = teacher.cellphone || '';
row.appendChild(phoneCell);
var roleCell = document.createElement('td');
var role = (teacher.role || '').replace(/_/g, ' ');
roleCell.textContent = role ? role.charAt(0).toUpperCase() + role.slice(1) : 'N/A';
row.appendChild(roleCell);
var assignedCell = document.createElement('td');
if ((teacher.main_assignments && teacher.main_assignments.length) ||
(teacher.ta_assignments && teacher.ta_assignments.length)) {
var list = document.createElement('ul');
list.className = 'list-unstyled mb-0';
if (teacher.main_assignments && teacher.main_assignments.length) {
list.appendChild(renderAssignedList(teacher.main_assignments, 'Main', teacher));
}
if (teacher.ta_assignments && teacher.ta_assignments.length) {
list.appendChild(renderAssignedList(teacher.ta_assignments, 'TA', teacher));
}
assignedCell.appendChild(list);
} else {
var empty = document.createElement('em');
empty.textContent = 'No classes assigned';
assignedCell.appendChild(empty);
}
row.appendChild(assignedCell);
var schoolYearCell = document.createElement('td');
schoolYearCell.textContent = teacher.school_year || schoolYear || '';
row.appendChild(schoolYearCell);
var actionsCell = document.createElement('td');
var assignBtn = document.createElement('button');
assignBtn.type = 'button';
assignBtn.className = 'btn btn-success btn-sm assign-class-btn';
if (!isCurrentYear) { assignBtn.disabled = true; assignBtn.title = 'Editing disabled for non-current year'; }
assignBtn.setAttribute('data-bs-toggle', 'modal');
assignBtn.setAttribute('data-bs-target', '#assignClassModal');
assignBtn.setAttribute('data-teacher-id', teacher.teacher_id);
assignBtn.setAttribute('data-teacher-name', teacher.name || '');
assignBtn.setAttribute('data-teacher-role', teacher.role || '');
assignBtn.textContent = 'Assign Class';
actionsCell.appendChild(assignBtn);
row.appendChild(actionsCell);
tbody.appendChild(row);
});
}
if (window.jQuery && $.fn.DataTable) {
$('#teachersTable').DataTable({
pageLength: 100,
lengthMenu: [5, 10, 25, 50, 100],
order: [[0, 'asc']],
columnDefs: [
{ orderable: false, targets: [6, 8] }
]
});
}
}
function populateClassOptions(classes) {
if (!classSelect) {
return;
}
classSelect.innerHTML = '';
var placeholder = document.createElement('option');
placeholder.value = '';
placeholder.disabled = true;
placeholder.selected = true;
placeholder.textContent = 'Select a class';
classSelect.appendChild(placeholder);
(classes || []).forEach(function (cls) {
var option = document.createElement('option');
option.value = cls.class_section_id;
option.textContent = cls.class_section_name || ('Class #' + cls.class_section_id);
classSelect.appendChild(option);
});
}
function loadAssignments() {
if (!apiList || !tbody) {
return;
}
tbody.innerHTML = '<tr><td colspan="9" class="text-center text-muted">Loading...</td></tr>';
var url = apiList + (selectedYear ? ('?schoolYear=' + encodeURIComponent(selectedYear)) : '');
fetch(url, {
headers: { 'Accept': 'application/json' },
credentials: 'same-origin'
})
.then(function (response) {
return response.json()
.catch(function () { return {}; })
.then(function (data) {
return { ok: response.ok, data: data };
});
})
.then(function (result) {
if (!result) {
throw new Error('No response received.');
}
var data = result.data || {};
updateCsrfFromResponse(data);
if (!result.ok || data.ok === false) {
showMessage('danger', data.message || 'Unable to load teacher assignments.');
return;
}
renderTeachersTable(data.teachers || [], data.school_year || '');
populateClassOptions(data.classes || []);
})
.catch(function (error) {
showMessage('danger', error.message || 'Failed to load assignments.');
});
}
function submitAssignment(payload) {
if (!apiAssign || !isCurrentYear) {
showMessage('warning', 'Editing disabled for non-current year.');
return;
}
fetch(apiAssign, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify(applyCsrf(payload))
})
.then(function (response) {
return response.json()
.catch(function () { return {}; })
.then(function (data) {
return { ok: response.ok, data: data };
});
})
.then(function (result) {
if (!result) {
throw new Error('No response received.');
}
var data = result.data || {};
updateCsrfFromResponse(data);
if (!result.ok || data.ok === false) {
showMessage('danger', data.message || 'Unable to assign class.');
return;
}
showMessage('success', data.message || 'Class assigned successfully.');
if (assignForm) {
assignForm.reset();
}
if (assignModalEl && window.bootstrap) {
var modalInstance = bootstrap.Modal.getInstance(assignModalEl);
if (modalInstance) {
modalInstance.hide();
}
}
loadAssignments();
})
.catch(function (error) {
showMessage('danger', error.message || 'Unable to assign class.');
});
}
function removeAssignment(payload) {
if (!apiDelete || !isCurrentYear) {
showMessage('warning', 'Editing disabled for non-current year.');
return;
}
fetch(apiDelete, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify(applyCsrf(payload))
})
.then(function (response) {
return response.json()
.catch(function () { return {}; })
.then(function (data) {
return { ok: response.ok, data: data };
});
})
.then(function (result) {
if (!result) {
throw new Error('No response received.');
}
var data = result.data || {};
updateCsrfFromResponse(data);
if (!result.ok || data.ok === false) {
showMessage('danger', data.message || 'Unable to remove assignment.');
return;
}
showMessage('success', data.message || 'Assignment removed successfully.');
loadAssignments();
})
.catch(function (error) {
showMessage('danger', error.message || 'Unable to remove assignment.');
});
}
document.addEventListener('click', function (event) {
var assignBtn = event.target.closest('.assign-class-btn');
if (assignBtn) {
var teacherId = assignBtn.getAttribute('data-teacher-id') || '';
var teacherName = assignBtn.getAttribute('data-teacher-name') || '';
var teacherRole = assignBtn.getAttribute('data-teacher-role') || '';
var nameInput = document.getElementById('teacherName');
var idInput = document.getElementById('teacherId');
if (idInput) {
idInput.value = teacherId;
}
if (nameInput) {
nameInput.value = teacherName;
}
if (teacherRoleHidden) {
teacherRoleHidden.value = teacherRole;
}
if (teacherRoleDisplay) {
teacherRoleDisplay.value = teacherRole === 'teacher_assistant' ? 'Teaching Assistant' : 'Teacher';
}
}
var removeBtn = event.target.closest('.remove-assignment-btn');
if (removeBtn) {
event.preventDefault();
var teacherId = removeBtn.getAttribute('data-teacher-id');
var classSectionId = removeBtn.getAttribute('data-class-section-id');
var position = removeBtn.getAttribute('data-position');
var semester = removeBtn.getAttribute('data-semester') || '';
var schoolYear = removeBtn.getAttribute('data-school-year') || '';
var className = removeBtn.getAttribute('data-class-name') || 'this class';
var teacherName = removeBtn.getAttribute('data-teacher-name') || 'this teacher';
if (confirm('Remove ' + className + ' (' + position.toUpperCase() + ') from ' + teacherName + '?')) {
removeAssignment({
teacher_id: teacherId,
class_section_id: classSectionId,
position: position,
semester: semester,
school_year: schoolYear
});
}
}
});
if (assignButton && assignForm) {
assignButton.addEventListener('click', function (event) {
event.preventDefault();
if (!assignForm.checkValidity()) {
assignForm.reportValidity ? assignForm.reportValidity() : alert('Please fill out all required fields.');
return;
}
submitAssignment({
teacher_id: assignForm.teacher_id.value,
teacher_role: assignForm.teacher_role ? assignForm.teacher_role.value : (teacherRoleHidden ? teacherRoleHidden.value : ''),
class_section_id: assignForm.class_section_id.value
});
});
}
loadAssignments();
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,190 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid px-4 py-4 teacher-submissions-page">
<div class="card shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<div>
<strong>Teacher Submission Dashboard</strong>
</div>
<?php if (!empty($semester) || !empty($schoolYear)): ?>
<span class="small text-muted">
<?= esc(ucfirst($semester)) ?> <?= esc($schoolYear) ?>
</span>
<?php endif; ?>
</div>
<?php
$notificationHistory = $notificationHistory ?? [];
$summary = $summary ?? [];
$completionPercent = max(0, min(100, (int)($summary['submission_percentage'] ?? 0)));
$missingItemsCount = max(0, (int)($summary['missing_items'] ?? 0));
$submittedItems = max(0, (int)($summary['submitted_items'] ?? 0));
$totalItems = max(0, (int)($summary['total_items'] ?? 0));
?>
<div class="card-body">
<div class="border rounded-3 p-3 mb-4 bg-light">
<div class="d-flex flex-wrap gap-4 align-items-center">
<div>
<div class="text-uppercase small text-muted">Submission completion</div>
<div class="h4 fw-semibold mb-1"><?= esc($completionPercent) ?>%</div>
<div class="progress" style="height:6px;">
<div
class="progress-bar bg-primary"
role="progressbar"
style="width: <?= esc($completionPercent) ?>%;"
aria-valuenow="<?= esc($completionPercent) ?>"
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
</div>
<div>
<div class="text-uppercase small text-muted">Missing items</div>
<div class="h4 fw-semibold text-danger mb-0"><?= esc($missingItemsCount) ?></div>
</div>
<div class="text-muted small">
<?= esc($submittedItems) ?> submitted / <?= esc($totalItems) ?> total items
</div>
</div>
</div>
<form method="post" action="<?= site_url('administrator/teacher-submissions/notify') ?>">
<?= csrf_field() ?>
<div class="table-responsive">
<table
class="table table-striped table-bordered m-0 align-middle teacher-submissions-table"
data-no-mgmt-sticky
data-no-dt-fixedheader
>
<thead class="table-light">
<tr>
<th>Class Section</th>
<th>Teacher</th>
<th class="text-center">Midterm Score</th>
<th class="text-center">Midterm Comment</th>
<th class="text-center">Participation</th>
<th class="text-center">PTAP Comment</th>
<th class="text-center">Notifications</th>
</tr>
</thead>
<tbody>
<?php if (!empty($rows)): ?>
<?php foreach ($rows as $row): ?>
<tr>
<td><?= esc($row['class_section']) ?></td>
<td>
<?php if (!empty($row['teachers'])): ?>
<?php foreach ($row['teachers'] as $teacher): ?>
<div><?= esc($teacher['label'] ?? 'Teacher') ?></div>
<?php endforeach; ?>
<?php else: ?>
<span class="text-muted small">Unassigned</span>
<?php endif; ?>
</td>
<?php foreach ([
'midterm_score_status',
'midterm_comment_status',
'participation_status',
'ptap_comment_status'
] as $statusKey): ?>
<?php $status = $row[$statusKey] ?? ['label' => 'N/A', 'badge' => 'bg-secondary']; ?>
<td class="text-center">
<span class="badge <?= esc($status['badge'] ?? 'bg-secondary') ?>">
<?= esc($status['label'] ?? 'N/A') ?>
</span>
<?php if (!empty($status['detail'])): ?>
<div class="small text-muted"><?= esc($status['detail']) ?></div>
<?php endif; ?>
</td>
<?php endforeach; ?>
<td>
<?php if (!empty($row['teachers'])): ?>
<?php $missingPayload = base64_encode(json_encode($row['missing_items'] ?? [])); ?>
<?php foreach ($row['teachers'] as $teacher): ?>
<?php $history = $notificationHistory[$row['class_section_id']][$teacher['id']] ?? []; ?>
<?php $lastEntry = $history[0] ?? null; ?>
<div class="mb-2">
<label class="d-flex align-items-center gap-2 mb-1">
<input
type="checkbox"
class="form-check-input"
name="notify[<?= esc($row['class_section_id']) ?>][<?= esc($teacher['id']) ?>]"
value="1"
/>
<span class="fw-semibold"><?= esc($teacher['label'] ?? 'Teacher') ?></span>
</label>
<input
type="hidden"
name="missing_items[<?= esc($row['class_section_id']) ?>][<?= esc($teacher['id']) ?>]"
value="<?= esc($missingPayload) ?>"
/>
<div class="small text-muted">
<?php if ($lastEntry !== null): ?>
<span>
Last <?= esc($lastEntry['status'] === 'sent' ? 'sent' : 'attempted') ?> on <?= esc($lastEntry['sent_at_text'] ?: 'N/A') ?>
by <?= esc($lastEntry['admin_name'] ?? '') ?>
</span>
<?php if (count($history) > 1): ?>
<div>History: <?= count($history) ?> entries</div>
<?php endif; ?>
<?php else: ?>
<span>No notifications sent yet</span>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<span class="text-muted small">No teacher assigned</span>
<?php endif; ?>
<?php if (!empty($row['missing_items'])): ?>
<div class="small text-danger mt-1">
Outstanding: <?= esc(implode(', ', $row['missing_items'])) ?>
</div>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="7" class="text-center">No teacher-class assignments found for this term.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="mt-3 text-end">
<button type="submit" class="btn btn-primary" <?= empty($rows) ? 'disabled' : '' ?>>
Send notifications to selected teachers
</button>
</div>
</form>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('styles') ?>
<style>
.teacher-submissions-page {
width: 100%;
max-width: 100%;
}
.teacher-submissions-page .card {
width: 100%;
}
.teacher-submissions-page .table-responsive {
max-width: none;
}
.card-body .table-responsive table thead th {
position: static !important;
top: auto !important;
}
.card-body .table-responsive .teacher-submissions-table {
width: 100% !important;
min-width: 100%;
table-layout: auto;
}
.card-body .table-responsive .teacher-submissions-table th,
.card-body .table-responsive .teacher-submissions-table td {
white-space: normal;
}
</style>
<?= $this->endSection() ?>
+43
View File
@@ -0,0 +1,43 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Teachers</h1>
<a href="/register" class="btn btn-primary">Add New Teacher</a>
</div>
<table id="myTable" class="display">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Phone</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($teachers as $teacher): ?>
<tr>
<td><?php echo $teacher['id']; ?></td>
<td><?php echo $teacher['firstname']; ?></td>
<td><?php echo $teacher['lastname']; ?></td>
<td><?php echo $teacher['cellphone']; ?></td>
<td><?php echo $teacher['email']; ?></td>
<td>
<a href="teacher/edit/<?php echo $teacher['id']; ?>" class="btn btn-warning">Edit</a>
<a href="teacher/delete/<?php echo $teacher['id']; ?>" class="btn btn-danger"
onclick="return confirm('Are you sure you want to delete this teacher?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</main>
<?= $this->section('scripts') ?>
<script>
$(document).ready(function() {
$('#myTable').DataTable();
});
</script>
<?= $this->endSection() ?>
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manage Students</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/css/administrator.css">
</head>
<body>
<div class="content-wrapper">
<div class="container">
<?php include('partials/header.php'); ?>
<h1>Teachers</h1>
<a href="teacher/create" class="btn btn-primary">Add New Teacher</a>
<table class="table table-striped mt-3">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($teachers as $teacher): ?>
<tr>
<td><?php echo $teacher['id']; ?></td>
<td><?php echo $teacher['firstname'] . ' ' . $teacher['lastname']; ?></td>
<td><?php echo $teacher['email']; ?></td>
<td>
<a href="teacher/edit/<?php echo $teacher['id']; ?>" class="btn btn-warning">Edit</a>
<a href="teacher/delete/<?php echo $teacher['id']; ?>" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this teacher?');">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
+270
View File
@@ -0,0 +1,270 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Al Rahma Sunday School</title>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="" name="keywords">
<meta content="" name="description">
<!-- Favicon -->
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
<!-- Google Web Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;600&family=Inter:wght@600&family=Lobster+Two:wght@700&display=swap"
rel="stylesheet">
<!-- Icon Font Stylesheet -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css" rel="stylesheet">
<!-- Libraries Stylesheet -->
<link href="lib/animate/animate.min.css" rel="stylesheet">
<link href="lib/owlcarousel/assets/owl.carousel.min.css" rel="stylesheet">
<!-- Customized Bootstrap Stylesheet -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- Template Stylesheet -->
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body>
<div class="container-xxl bg-white p-0">
<!-- Spinner Start -->
<div id="spinner"
class="show bg-white position-fixed translate-middle w-100 vh-100 top-50 start-50 d-flex align-items-center justify-content-center">
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<!-- Spinner End -->
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav mx-auto">
<a href="<?= base_url('/') ?>" class="nav-item nav-link">Home</a>
<a href="<?= base_url('/about') ?>" class="nav-item nav-link">About Us</a>
<a href="<?= base_url('/classes') ?>" class="nav-item nav-link">Classes</a>
<div class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle active" data-bs-toggle="dropdown">Pages</a>
<div class="dropdown-menu rounded-0 rounded-bottom border-0 shadow-sm m-0">
<a href="<?= base_url('/facility') ?>" class="dropdown-item">School Facilities</a>
<a href="team.html" class="dropdown-item">Popular Teachers</a>
<a href="<?= base_url('/call-to-action') ?>" class="dropdown-item">Become A Teacher or Admins</a>
<a href="<?= base_url('/appointment') ?>" class="dropdown-item active">Make Appointment</a>
<a href="<?= base_url('/testimonial') ?>" class="dropdown-item">Testimonial</a>
<a href="<?= base_url('/notFound') ?>" class="dropdown-item">notFound Error</a>
</div>
</div>
<a href="<?= base_url('/contact') ?>" class="nav-item nav-link">Contact Us</a>
</div>
<a href="/register" class="btn btn-primary rounded-pill px-3 d-none d-lg-block">Register
<i class="fa fa-arrow-right ms-3"></i></a>
<a href="/login" class="btn btn-primary rounded-pill px-3 d-none d-lg-block">Login<i
class="fa fa-arrow-right ms-3"></i></a>
</div>
</nav>
<!-- Navbar End -->
<!-- Page Header End -->
<div class="container-xxl py-5 page-header position-relative mb-5">
<div class="container py-5">
<h1 class="display-2 text-white animated slideInDown mb-4">Appointment</h1>
<nav aria-label="breadcrumb animated slideInDown">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="#">Pages</a></li>
<li class="breadcrumb-item text-white active" aria-current="page">Appointment</li>
</ol>
</nav>
</div>
</div>
<!-- Page Header End -->
<!-- Appointment Start -->
<div class="container-xxl py-5">
<div class="container">
<div class="bg-light rounded">
<div class="row g-0">
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.1s">
<div class="h-100 d-flex flex-column justify-content-center p-5">
<h1 class="mb-4">Make Appointment</h1>
<form>
<div class="row g-3">
<div class="col-sm-6">
<div class="form-floating">
<input type="text" class="form-control border-0" id="gname"
placeholder="Gurdian Name">
<label for="gname">Gurdian Name</label>
</div>
</div>
<div class="col-sm-6">
<div class="form-floating">
<input type="email" class="form-control border-0" id="gmail"
placeholder="Gurdian Email">
<label for="gmail">Gurdian Email</label>
</div>
</div>
<div class="col-sm-6">
<div class="form-floating">
<input type="text" class="form-control border-0" id="cname"
placeholder="Child Name">
<label for="cname">Child Name</label>
</div>
</div>
<div class="col-sm-6">
<div class="form-floating">
<input type="text" class="form-control border-0" id="cage"
placeholder="Child Age">
<label for="cage">Child Age</label>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<textarea class="form-control border-0"
placeholder="Leave a message here" id="message"
style="height: 100px"></textarea>
<label for="message">Message</label>
</div>
</div>
<div class="col-12">
<button class="btn btn-primary w-100 py-3" type="submit">Submit</button>
</div>
</div>
</form>
</div>
</div>
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.5s" style="min-height: 400px;">
<div class="position-relative h-100">
<img class="position-absolute w-100 h-100 rounded" src="images/appointment.jpg"
style="object-fit: cover;">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Appointment End -->
<!-- Footer Start -->
<div class="container-fluid bg-dark text-white-50 footer pt-5 mt-5 wow fadeIn" data-wow-delay="0.1s">
<div class="container py-5">
<div class="row g-5">
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Get In Touch</h3>
<p class="mb-2"><i class="fa fa-map-marker-alt me-3"></i>5 Courthouse Lane, Chelmsford, MA 01824
</p>
<p class="mb-2"><i class="fa fa-phone-alt me-3"></i>+1 978-364-0219
0</p>
<p class="mb-2"><i class="fa fa-envelope me-3"></i>alrahma.isgl@gmail.com
</p>
<div class="d-flex pt-2">
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-twitter"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-facebook-f"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-youtube"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-linkedin-in"></i></a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Quick Links</h3>
<a class="btn btn-link text-white-50" href="">About Us</a>
<a class="btn btn-link text-white-50" href="">Contact Us</a>
<a class="btn btn-link text-white-50" href="">Our Services</a>
<a class="btn btn-link text-white-50" href="">Privacy Policy</a>
<a class="btn btn-link text-white-50" href="">Terms & Condition</a>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Photo Gallery</h3>
<div class="row g-2 pt-2">
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-1.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-2.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-3.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-4.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-5.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-6.jpg" alt="">
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Newsletter</h3>
<p>Our newsletter is your gateway to staying informed and connected with our Sunday school community. </p>
<div class="position-relative mx-auto" style="max-width: 400px;">
<input class="form-control bg-transparent w-100 py-3 ps-4 pe-5" type="text"
placeholder="Your email">
<button type="button"
class="btn btn-primary py-2 position-absolute top-0 end-0 mt-2 me-2">SignUp</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="copyright">
<div class="row">
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
&copy; <a class="border-bottom" href="#">Al Rahma Sunday School by ISGL</a>, All Right
Reserved.
<!--/*** This template is free as long as you keep the footer authors credit link/attribution link/backlink. If you'd like to use the template without the footer authors credit link/attribution link/backlink, you can purchase the Credit Removal License from "https://htmlcodex.com/credit-removal". Thank you for your support. ***/-->
Designed By <a class="border-bottom" href="https://htmlcodex.com">HTML Codex</a>
</div>
<div class="col-md-6 text-center text-md-end">
<div class="footer-menu">
<a href="">Home</a>
<a href="">Cookies</a>
<a href="">Help</a>
<a href="">FQAs</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer End -->
<!-- Back to Top -->
<a href="#" class="btn btn-lg btn-primary btn-lg-square back-to-top"><i class="bi bi-arrow-up"></i></a>
</div>
<!-- JavaScript Libraries -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="lib/wow/wow.min.js"></script>
<script src="lib/easing/easing.min.js"></script>
<script src="lib/waypoints/waypoints.min.js"></script>
<script src="lib/owlcarousel/owl.carousel.min.js"></script>
<!-- Template Javascript -->
<script src="js/main.js"></script>
</body>
</html>
@@ -0,0 +1,154 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php $dateLabel = !empty($date) ? local_date($date, 'm-d-Y') : ''; ?>
<div class="container-xxl py-4">
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
<h2 class="mb-0">Admin Attendance</h2>
<div class="d-flex gap-2">
<a class="btn btn-outline-secondary"
href="<?= site_url('admin/teacher-attendance/month?date='.urlencode($date).'&semester='.urlencode($semester).'&school_year='.urlencode($schoolYear)) ?>">
← Back to Monthly Attendance
</a>
</div>
</div>
<?php if (session()->getFlashdata('status')): ?>
<div class="alert alert-success"><?= session()->getFlashdata('status') ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
<?php endif; ?>
<!-- Filters -->
<form class="row gy-2 gx-3 align-items-end mb-3" method="get" action="<?= site_url('admin/admins-attendance') ?>">
<div class="col-sm-3">
<label class="form-label">School Year</label>
<input type="text" name="school_year" class="form-control" value="<?= esc($schoolYear) ?>" placeholder="e.g. 2025-2026">
</div>
<div class="col-sm-2">
<label class="form-label">Semester</label>
<input type="text" name="semester" class="form-control" value="<?= esc($semester) ?>" placeholder="Fall">
</div>
<div class="col-sm-3">
<label class="form-label">Date</label>
<input type="date" name="date" class="form-control" value="<?= esc($date) ?>">
</div>
<div class="col-sm-4 d-flex gap-2">
<button type="submit" class="btn btn-secondary">Load</button>
<a class="btn btn-outline-secondary" href="<?= site_url('admin/admins-attendance') ?>">Reset</a>
</div>
</form>
<!-- Admins grid -->
<form method="post" action="<?= site_url('admin/admins-attendance/save') ?>" id="adminsAttForm">
<?= csrf_field() ?>
<input type="hidden" name="date" value="<?= esc($date) ?>">
<input type="hidden" name="semester" value="<?= esc($semester) ?>">
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<strong>Admins on <?= esc($dateLabel) ?></strong>
<span class="badge bg-secondary ms-2"><?= is_array($adminsGrid) ? count($adminsGrid) : 0 ?></span>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-success" id="markAllPresentAdmins">Mark All Present</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="clearAllAdmins">Clear All</button>
</div>
</div>
<div class="card-body table-responsive">
<?php if (empty($adminsGrid)): ?>
<div class="alert alert-info mb-0">No Admin users found for this day/term.</div>
<?php else: ?>
<table id="adminsAttTable" class="table table-bordered table-striped align-middle">
<thead class="table-light">
<tr>
<th class="text-center" style="width:70px;">#</th>
<th>Name</th>
<th class="text-center" style="width:180px;">Role</th>
<th class="text-center" style="width:220px;">Status</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
<?php $i=1; foreach ($adminsGrid as $row): ?>
<?php
$uid = (int)($row['user_id'] ?? 0);
$name = trim(($row['firstname'] ?? '').' '.($row['lastname'] ?? '')) ?: ('User#'.$uid);
$role = (string)($row['role_name'] ?? 'Admin');
$stat = strtolower((string)($row['status'] ?? ''));
$reason = (string)($row['reason'] ?? '');
?>
<tr>
<td class="text-center"><?= $i++ ?></td>
<td><?= esc($name) ?></td>
<td class="text-center"><?= esc($role) ?></td>
<td class="text-center">
<input type="hidden" name="admins[<?= $uid ?>][user_id]" value="<?= $uid ?>">
<input type="hidden" name="admins[<?= $uid ?>][role_name]" value="<?= esc($role) ?>">
<select name="admins[<?= $uid ?>][status]" class="form-select form-select-sm admin-status-select">
<option value="">—</option>
<option value="present" <?= $stat === 'present' ? 'selected' : '' ?>>Present</option>
<option value="absent" <?= $stat === 'absent' ? 'selected' : '' ?>>Absent</option>
<option value="late" <?= $stat === 'late' ? 'selected' : '' ?>>Late</option>
</select>
</td>
<td>
<input type="text" name="admins[<?= $uid ?>][reason]" class="form-control form-control-sm" placeholder="Optional reason" value="<?= esc($reason) ?>">
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<div class="card-footer d-flex justify-content-end">
<button type="submit" class="btn btn-primary" <?= empty($adminsGrid) ? 'disabled' : '' ?>>Save Admins Attendance</button>
</div>
</div>
</form>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
const markAll = document.getElementById('markAllPresentAdmins');
const clearAll = document.getElementById('clearAllAdmins');
if (markAll) {
markAll.addEventListener('click', () => {
document.querySelectorAll('#adminsAttTable .admin-status-select')
.forEach(sel => { sel.value = 'present'; });
});
}
if (clearAll) {
clearAll.addEventListener('click', () => {
document.querySelectorAll('#adminsAttTable .admin-status-select')
.forEach(sel => { sel.value = ''; });
document.querySelectorAll('#adminsAttTable input[name^="admins"][name$="[reason]"]')
.forEach(inp => { inp.value = ''; });
});
}
});
</script>
<script>
$(function(){
if ($('#adminsAttTable').length) {
$('#adminsAttTable').DataTable({
paging: false,
searching: false,
info: false,
order: []
});
}
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,139 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container">
<h2 class="text-center mt-4 mb-3"><?= $title ?></h1>
<div class="d-flex justify-content-center mb-2">
<div class="col-12 col-lg-10">
<?= $this->include('partials/academic_filter') ?>
</div>
</div>
<?php if (session()->getFlashdata('message')): ?>
<div class="alert alert-success">
<?= session()->getFlashdata('message') ?>
</div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger">
<?= session()->getFlashdata('error') ?>
</div>
<?php endif; ?>
<div class="card mb-4">
<div class="card-header">
<h5>Record Attendance</h5>
</div>
<div class="card-body">
<form action="<?= base_url('attendance/record') ?>" method="post">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label for="student_id">Student</label>
<select class="form-control" id="student_id" name="student_id" required>
<option value="">Select Student</option>
<?php foreach ($students as $student): ?>
<option value="<?= $student['id'] ?>"><?= $student['name'] ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="date">Date</label>
<input type="date" class="form-control" id="date" name="date" required value="<?= local_date(utc_now(), 'Y-m-d') ?>">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="is_present">Status</label>
<select class="form-control" id="is_present" name="is_present" required>
<option value="1">Present</option>
<option value="0">Absent</option>
</select>
</div>
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" class="btn btn-primary">Record</button>
</div>
</div>
<div class="row mt-3" id="reasonRow" style="display: none;">
<div class="col-md-6">
<div class="form-group">
<label for="reason">Reason for Absence</label>
<textarea class="form-control" id="reason" name="reason" rows="2"></textarea>
</div>
</div>
<div class="col-md-6 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_reported" name="is_reported" value="1">
<label class="form-check-label" for="is_reported">
Reported Absence
</label>
</div>
<div class="form-check ml-3">
<input class="form-check-input" type="checkbox" id="notified" name="notified" value="1">
<label class="form-check-label" for="notified">
Parent Notified
</label>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5>Student List</h5>
</div>
<div class="card-body">
<table class="table table-striped">
<thead>
<tr>
<th>Student Name</th>
<th>Total Absences</th>
<th>Last Absence Date</th>
<th>Last Absence Reason</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $student): ?>
<tr>
<td><?= $student['name'] ?></td>
<td><?= $student['total_absences'] ?></td>
<td>
<?= $student['last_absence'] ? local_date($student['last_absence']['date'], 'm-d-Y') : 'N/A' ?>
</td>
<td>
<?= $student['last_absence']['reason'] ?? 'N/A' ?>
</td>
<td>
<a href="<?= base_url('attendance/view/' . $student['id']) ?>" class="btn btn-sm btn-info">
View Details
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
// Show reason field when absent is selected
document.getElementById('is_present').addEventListener('change', function() {
const reasonRow = document.getElementById('reasonRow');
if (this.value === '0') {
reasonRow.style.display = 'flex';
} else {
reasonRow.style.display = 'none';
}
});
</script>
<?= $this->endSection() ?>
+151
View File
@@ -0,0 +1,151 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
// Prepare initial editor content:
// Prefer $body_html if provided; else fall back to $body_text (escaped)
$initialEditorContent = (string)(
isset($body_html) && $body_html !== ''
? $body_html
: (isset($body_text) ? htmlspecialchars((string)$body_text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') : '')
);
?>
<div class="container my-4">
<h2 class="text-center mb-3">Compose Attendance Email</h2>
<?php if ($err = session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc($err) ?></div>
<?php endif; ?>
<?php if ($msg = session()->getFlashdata('message')): ?>
<div class="alert alert-success"><?= esc($msg) ?></div>
<?php endif; ?>
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<strong><?= esc($student_name ?? '') ?></strong>
<span class="badge bg-secondary ms-2">
<?= esc($code) ?><?= $variant ? ' • ' . esc($variant) : '' ?>
</span>
</div>
<div>
<span class="badge bg-primary">School Year: <?= esc($school_year ?? '') ?></span>
<?php if (!empty($semester)): ?>
<span class="badge bg-info text-dark ms-1">Semester: <?= esc($semester) ?></span>
<?php endif; ?>
</div>
</div>
<div class="card-body">
<form method="post" action="<?= site_url('attendance/send-email-manual') ?>">
<?= csrf_field() ?>
<input type="hidden" name="student_id" value="<?= esc($student_id) ?>">
<input type="hidden" name="code" value="<?= esc($code) ?>">
<input type="hidden" name="variant" value="<?= esc($variant) ?>">
<input type="hidden" name="incident_date" value="<?= esc($incident_date ?? local_date(utc_now(), 'Y-m-d')) ?>">
<!-- This hidden field is what your controller validates (body_html) -->
<input type="hidden" name="body_html" id="body_html" value="<?= htmlspecialchars($initialEditorContent, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>">
<div class="mb-3">
<label class="form-label">To</label>
<input type="email" name="to" class="form-control" value="<?= esc($parent_email) ?>" required>
<div class="form-text">Primary parent email (you can change it here).</div>
</div>
<?php
$secEmail = (string)($secondary_email ?? '');
$secName = (string)($secondary_name ?? '');
?>
<?php if ($secEmail !== ''): ?>
<div class="alert alert-light border d-flex align-items-start">
<div class="me-2"><i class="fas fa-copy"></i></div>
<div>
<div><strong>Second Parent (CC):</strong> <?= esc($secEmail) ?><?= $secName ? ' — ' . esc($secName) : '' ?></div>
</div>
</div>
<?php endif; ?>
<div class="mb-3">
<label class="form-label">Subject</label>
<input type="text" name="subject" class="form-control" value="<?= esc($subject) ?>" required>
</div>
<!-- Rich Text Editor -->
<div class="mb-3">
<label class="form-label">Body (Rich Text)</label>
<textarea id="editor" class="form-control" rows="12"><?= $initialEditorContent ?></textarea>
<div class="form-text">
Use the toolbar to format your message. Links/images/tables are supported.
</div>
<noscript>
<div class="alert alert-warning mt-2">
JavaScript is disabled. The message will be sent as-is from the hidden <code>body_html</code> field.
</div>
</noscript>
</div>
<div class="d-flex gap-2">
<a href="<?= site_url('attendance/violations') ?>" class="btn btn-outline-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane me-1"></i> Send Email
</button>
</div>
</form>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<!-- TinyMCE self-hosted -->
<script src="<?= base_url('assets/tinymce/tinymce.min.js') ?>"></script>
<script>
(function () {
const form = document.querySelector('form[action$="attendance/send-email-manual"]');
const hiddenHtml = document.getElementById('body_html');
tinymce.init({
selector: '#editor',
// CRUCIAL for self-host:
base_url: '<?= base_url('assets/tinymce') ?>',
suffix: '.min',
// Optional: explicitly mark GPL mode (suppresses license nags in some builds)
license_key: 'gpl',
height: 420,
menubar: true,
branding: false,
promotion: false,
// ONLY use OSS plugins here. Remove any premium/cloud ones (e.g., powerpaste, a11ychecker, tinycomments, spellchecker, linkchecker, checklist, premium table plugins).
plugins: 'advlist autolink lists link image charmap preview anchor ' +
'searchreplace visualblocks code fullscreen insertdatetime media table ' +
'help wordcount emoticons codesample',
toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough forecolor backcolor | ' +
'alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | ' +
'link image media table | emoticons codesample | removeformat | preview code',
convert_urls: false,
paste_data_images: true,
image_caption: true,
content_style: 'body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; }',
setup(editor) {
editor.on('keyup change undo redo SetContent', function () {
if (hiddenHtml) hiddenHtml.value = editor.getContent({ format: 'html' });
});
if (form) {
form.addEventListener('submit', function () {
if (hiddenHtml) hiddenHtml.value = editor.getContent({ format: 'html' });
});
}
}
});
})();
</script>
<?= $this->endSection() ?>
+150
View File
@@ -0,0 +1,150 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?= csrf_meta() ?>
<?php
use App\Libraries\AttendancePolicy;
$roleRaw = session()->get('role');
$roleList = is_array($roleRaw) ? $roleRaw : [$roleRaw];
$canManage = AttendancePolicy::canManageEarlyDismissals($roleList);
$signatureByDate = $signatureByDate ?? [];
$query = $_SERVER['QUERY_STRING'] ?? '';
$returnUrl = current_url();
if ($query && strpos($returnUrl, '?') === false) {
$returnUrl .= '?' . $query;
}
?>
<div class="container-xxl py-4">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<div>
<h2 class="mb-0">Early Dismissals</h2>
<small class="text-muted">Parent-submitted early dismissal requests</small>
</div>
<?php if ($canManage): ?>
<a href="<?= site_url('attendance/early-dismissals/new') ?>" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Add Early Dismissal
</a>
<?php endif; ?>
</div>
<form method="get" class="row gy-2 gx-3 align-items-end mb-4">
<div class="col-md-4">
<label class="form-label">School Year</label>
<input type="text" class="form-control"
name="school_year"
value="<?= esc($schoolYear ?? ($_GET['school_year'] ?? '')) ?>"
placeholder="e.g. 2025-2026">
</div>
<div class="col-md-3">
<label class="form-label">Semester</label>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<select name="semester" class="form-select">
<option value="">All Semesters</option>
<option value="Fall" <?= (strcasecmp($semVal, 'Fall') === 0 ? 'selected' : '') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal, 'Spring') === 0 ? 'selected' : '') ?>>Spring</option>
</select>
</div>
<div class="col-md-3 d-flex gap-2">
<button type="submit" class="btn btn-secondary mt-4">Apply</button>
<a class="btn btn-outline-secondary mt-4" href="<?= site_url('attendance/early-dismissals') ?>">Reset</a>
</div>
</form>
<?php if ($msg = session()->getFlashdata('message')): ?>
<div class="alert alert-success"><?= $msg ?></div>
<?php endif; ?>
<?php if ($err = session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= $err ?></div>
<?php endif; ?>
<?php if (empty($groups)): ?>
<div class="alert alert-info">No early dismissals found for the selected school year<?= $semester ? ' and semester' : '' ?>.</div>
<?php else: ?>
<?php foreach ($groups as $date => $rows): ?>
<?php
$dateLabel = $date;
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $date)) {
$dateLabel = date('m-d-Y', strtotime($date));
}
?>
<div class="card shadow-sm mb-3">
<div class="card-body">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
<strong><?= esc($dateLabel) ?></strong>
<?php if ($date !== 'Unknown'): ?>
<?php $sig = $signatureByDate[$date] ?? null; ?>
<div class="d-flex flex-wrap align-items-center gap-2">
<?php if (!empty($sig['filename'])): ?>
<a class="btn btn-sm btn-outline-primary" href="<?= site_url('early-dismissal-signatures/' . $sig['filename']) ?>" target="_blank" rel="noopener">
View Signature
</a>
<?php if (!empty($sig['original_name'])): ?>
<span class="small text-muted"><?= esc($sig['original_name']) ?></span>
<?php endif; ?>
<?php else: ?>
<span class="small text-muted">No signature uploaded</span>
<?php endif; ?>
<?php if ($canManage): ?>
<form method="post"
action="<?= site_url('attendance/early-dismissals/signature') ?>"
enctype="multipart/form-data"
class="d-flex flex-wrap align-items-center gap-2">
<?= csrf_field() ?>
<input type="hidden" name="report_date" value="<?= esc($date) ?>">
<input type="hidden" name="school_year" value="<?= esc($schoolYear ?? '') ?>">
<input type="hidden" name="semester" value="<?= esc($semester ?? '') ?>">
<input type="hidden" name="return_url" value="<?= esc($returnUrl) ?>">
<input type="file"
name="signature_file"
class="form-control form-control-sm"
accept=".jpg,.jpeg,.png,.webp,.gif,.pdf"
required>
<button type="submit" class="btn btn-sm btn-primary">
<?= !empty($sig['filename']) ? 'Replace' : 'Upload' ?>
</button>
</form>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<div class="table-responsive">
<table class="table table-striped align-middle mb-0 no-mgmt-sticky" data-no-mgmt-sticky="1">
<thead>
<tr>
<th>Student</th>
<th>Class/Section</th>
<th>Dismissal Time</th>
<th>Reason</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $r): ?>
<tr>
<td><?= esc(($r['firstname'] ?? '') . ' ' . ($r['lastname'] ?? '')) ?></td>
<td><?= esc($r['class_section_name'] ?? '—') ?></td>
<td><?= !empty($r['dismiss_time']) ? esc(substr($r['dismiss_time'], 0, 5)) : '—' ?></td>
<td style="max-width: 420px; white-space: normal;"><?= esc($r['reason'] ?? '') ?></td>
<td>
<span class="badge bg-<?= ($r['status'] === 'processed'
? 'success'
: ($r['status'] === 'seen' ? 'info' : 'secondary')) ?>">
<?= esc(ucfirst($r['status'] ?? 'new')) ?>
</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,204 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?= csrf_meta() ?>
<div class="container-xxl py-4">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<h2 class="mb-0">Add Early Dismissal</h2>
<a href="<?= site_url('attendance/early-dismissals') ?>" class="btn btn-outline-secondary">Back to List</a>
</div>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('message')): ?>
<div class="alert alert-success"><?= session()->getFlashdata('message') ?></div>
<?php endif; ?>
<div class="card shadow-sm">
<div class="card-body">
<form method="post" action="<?= site_url('attendance/early-dismissals') ?>">
<?= csrf_field() ?>
<div class="row gy-3">
<div class="col-md-5 position-relative">
<label class="form-label">Student</label>
<input type="text" id="studentSearch" class="form-control" autocomplete="off" placeholder="Type student name to search…" value="<?= esc(old('student_label', '')) ?>">
<input type="hidden" name="student_id" id="studentId" value="<?= esc(old('student_id', '')) ?>">
<div id="studentSuggestions" class="list-group position-absolute w-100" style="z-index:1000; max-height: 240px; overflow:auto;"></div>
<div class="form-text">Start typing first or last name, then pick from the list.</div>
</div>
<div class="col-md-3">
<label class="form-label">Date (Sunday)</label>
<input type="date" name="date" class="form-control" value="<?= esc(old('date', $defaultDate ?? local_date(utc_now(), 'Y-m-d'))) ?>" required>
</div>
<div class="col-md-2">
<label class="form-label">Dismissal Time</label>
<input type="time" name="dismiss_time" class="form-control" value="<?= esc(old('dismiss_time', '')) ?>" required>
</div>
<div class="col-12">
<label class="form-label">Reason (optional)</label>
<textarea name="reason" class="form-control" rows="2" placeholder="e.g., Family appointment"><?= esc(old('reason', '')) ?></textarea>
</div>
<div class="col-12 d-flex gap-2">
<button type="submit" class="btn btn-primary">Save Early Dismissal</button>
<a href="<?= site_url('attendance/early-dismissals') ?>" class="btn btn-outline-secondary">Cancel</a>
</div>
</div>
</form>
</div>
</div>
<div class="mt-3 small text-muted">
Note: Early dismissals do not change daily present/absent status; they are shown for staff awareness and pickup coordination.
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<?php
// Prepare a minimal dataset for client-side search
$jsStudents = array_map(static function($s) {
return [
'id' => (int)($s['id'] ?? 0),
'firstname'=> (string)($s['firstname'] ?? ''),
'lastname' => (string)($s['lastname'] ?? ''),
'section' => (string)($s['class_section_name'] ?? ''),
];
}, (array)($students ?? []));
?>
<script>
(function() {
const STUDENTS = <?= json_encode($jsStudents, JSON_HEX_TAG|JSON_HEX_APOS|JSON_HEX_AMP|JSON_HEX_QUOT) ?>;
const input = document.getElementById('studentSearch');
const hiddenId = document.getElementById('studentId');
const list = document.getElementById('studentSuggestions');
if (!input || !hiddenId || !list) return;
const norm = (s) => (s || '').toLowerCase().trim();
const buildKey = (o) => {
const first = norm(o.firstname);
const last = norm(o.lastname);
const sec = norm(o.section);
return `${first} ${last} ${last} ${first} ${sec}`;
};
// Build enriched search dataset
const DATA = STUDENTS.map(s => ({
...s,
key: buildKey(s),
label: `${s.lastname}, ${s.firstname}${s.section ? ' — ' + s.section : ''}`,
display: `${s.firstname} ${s.lastname}${s.section ? ' — ' + s.section : ''}`
}));
let activeIndex = -1;
function render(q) {
const query = norm(q);
list.innerHTML = '';
list.style.display = 'none';
activeIndex = -1;
if (query.length === 0) {
// If user clears, clear selected student
hiddenId.value = '';
return;
}
// Clear selection whenever user types
hiddenId.value = '';
const matches = DATA.filter(s => s.key.includes(query)).slice(0, 20);
if (matches.length === 0) return;
for (let i = 0; i < matches.length; i++) {
const m = matches[i];
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'list-group-item list-group-item-action';
btn.textContent = m.display;
btn.dataset.id = String(m.id);
btn.addEventListener('click', () => selectStudent(m));
list.appendChild(btn);
}
list.style.display = '';
}
function selectStudent(s) {
hiddenId.value = s.id;
input.value = s.display;
list.innerHTML = '';
list.style.display = 'none';
}
input.addEventListener('input', () => render(input.value));
// Keyboard navigation
input.addEventListener('keydown', (e) => {
const items = Array.from(list.querySelectorAll('.list-group-item'));
if (e.key === 'ArrowDown') {
e.preventDefault();
if (items.length === 0) return;
activeIndex = (activeIndex + 1) % items.length;
updateActive(items);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (items.length === 0) return;
activeIndex = (activeIndex - 1 + items.length) % items.length;
updateActive(items);
} else if (e.key === 'Enter') {
if (activeIndex >= 0 && items[activeIndex]) {
e.preventDefault();
const id = parseInt(items[activeIndex].dataset.id, 10);
const s = DATA.find(x => x.id === id);
if (s) selectStudent(s);
}
} else if (e.key === 'Escape') {
list.innerHTML = '';
list.style.display = 'none';
}
});
function updateActive(items) {
items.forEach((el, idx) => {
if (idx === activeIndex) {
el.classList.add('active');
el.scrollIntoView({ block: 'nearest' });
} else {
el.classList.remove('active');
}
});
}
// Hide suggestions on outside click
document.addEventListener('click', (e) => {
if (!list.contains(e.target) && e.target !== input) {
list.innerHTML = '';
list.style.display = 'none';
}
});
// Client-side submit validation: ensure a student was selected
const form = input.closest('form');
if (form) {
form.addEventListener('submit', (e) => {
if (!hiddenId.value) {
e.preventDefault();
render(input.value);
input.classList.add('is-invalid');
if (!list.style.display || list.innerHTML === '') {
// No matches; show simple feedback
alert('Please select a student from the list.');
}
}
});
input.addEventListener('input', () => input.classList.remove('is-invalid'));
}
})();
</script>
<?= $this->endSection() ?>
+104
View File
@@ -0,0 +1,104 @@
<?php
/**
* Attendance Edit Modal
*
* @param array $student Student data
* @param int $classId Class ID
* @param int $sectionId Section ID
* @param string $sid Student ID
*/
?>
<!-- Modal -->
<div class="modal fade" id="editAttendanceModal-<?= $sid ?>" tabindex="-1"
aria-labelledby="editAttendanceModalLabel-<?= $sid ?>" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editAttendanceModalLabel-<?= $sid ?>">
Edit Attendance for <?= esc($student['firstname'] . ' ' . $student['lastname']) ?>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form action="<?= base_url('attendance/update') ?>" method="post">
<?= csrf_field() ?>
<input type="hidden" name="student_id" value="<?= $sid ?>">
<input type="hidden" name="class_id" value="<?= $classId ?>">
<input type="hidden" name="class_section_id" value="<?= $sectionId ?>">
<div class="mb-3">
<label for="status-<?= $sid ?>" class="form-label">Status</label>
<select name="status" class="form-select attendance-status" id="status-<?= $sid ?>" required>
<option value="present">Present</option>
<option value="late">Late</option>
<option value="absent">Absent</option>
</select>
</div>
<div class="mb-3">
<label for="date-<?= $sid ?>" class="form-label">Date</label>
<input type="date" class="form-control" id="date-<?= $sid ?>" name="date"
value="<?= local_date(utc_now(), 'Y-m-d') ?>" required>
</div>
<div class="mb-3">
<label for="reason-<?= $sid ?>" class="form-label">Reason</label>
<textarea name="reason" id="reason-<?= $sid ?>" class="form-control attendance-reason"
rows="3" placeholder="Enter reason for absence or lateness"></textarea>
</div>
<!-- Absence Reported? (shown only when Status = Absent) -->
<div class="mb-3 d-none" id="reported-group-<?= $sid ?>">
<label for="reported-<?= $sid ?>" class="form-label">Absence Reported?</label>
<select name="reported" id="reported-<?= $sid ?>" class="form-select">
<option value="0" selected>No</option>
<option value="1">Yes</option>
</select>
<div class="form-text">Select “Yes” if the parent/guardian reported this absence.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
(function () {
const statusEl = document.getElementById('status-<?= $sid ?>');
const reasonEl = document.getElementById('reason-<?= $sid ?>');
const reportedGp = document.getElementById('reported-group-<?= $sid ?>');
const reportedEl = document.getElementById('reported-<?= $sid ?>');
function syncFields() {
const v = statusEl.value;
// Reason: required for absent/late, disabled otherwise
if (v === 'absent' || v === 'late') {
reasonEl.removeAttribute('disabled');
reasonEl.setAttribute('required', 'required');
} else {
reasonEl.value = '';
reasonEl.removeAttribute('required');
reasonEl.setAttribute('disabled', 'disabled');
}
// Reported selector: visible only for absent; reset to "No" otherwise
if (v === 'absent') {
reportedGp.classList.remove('d-none');
} else {
reportedEl.value = '0';
reportedGp.classList.add('d-none');
}
}
statusEl.addEventListener('change', syncFields);
syncFields(); // init on modal load
})();
</script>
+85
View File
@@ -0,0 +1,85 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-xxl py-4">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<h2 class="mb-0">Parent Attendance Reports</h2>
<small class="text-muted">Submitted by parents (absent/late/early dismissal)</small>
</div>
<form method="get" class="row gy-2 gx-3 align-items-end mb-4">
<div class="col-md-3">
<label class="form-label">School Year</label>
<input type="text" class="form-control" name="school_year" value="<?= esc($schoolYear ?? ($_GET['school_year'] ?? '')) ?>" placeholder="e.g. 2025-2026">
</div>
<div class="col-md-2">
<label class="form-label">Semester</label>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<select name="semester" class="form-select">
<option value="">—</option>
<option value="Fall" <?= (strcasecmp($semVal,'Fall')===0?'selected':'') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal,'Spring')===0?'selected':'') ?>>Spring</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Start Date</label>
<input type="date" class="form-control" name="start" value="<?= esc($start ?? local_date(utc_now(), 'Y-m-d')) ?>">
</div>
<div class="col-md-2">
<label class="form-label">End Date</label>
<input type="date" class="form-control" name="end" value="<?= esc($end ?? '') ?>">
</div>
<div class="col-md-3 d-flex gap-2">
<button type="submit" class="btn btn-secondary mt-4">Apply</button>
<a class="btn btn-outline-secondary mt-4" href="<?= site_url('attendance/parent-reports') ?>">Reset</a>
</div>
</form>
<div class="card shadow-sm">
<div class="card-body table-responsive">
<table class="table table-striped align-middle">
<thead>
<tr>
<th>Date</th>
<th>Student</th>
<th>Class/Section</th>
<th>Type</th>
<th>Time</th>
<th>Reason</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php if (!empty($rows)): ?>
<?php foreach ($rows as $r): ?>
<tr>
<td><?= esc(!empty($r['report_date']) ? local_date($r['report_date'], 'm-d-Y') : '') ?></td>
<td><?= esc(($r['firstname'] ?? '') . ' ' . ($r['lastname'] ?? '')) ?></td>
<td><?= esc($r['class_section_name'] ?? '—') ?></td>
<td class="text-capitalize"><?= esc(str_replace('_',' ', $r['type'])) ?></td>
<td>
<?php if ($r['type'] === 'late' && !empty($r['arrival_time'])): ?>
Arrival: <?= esc(substr($r['arrival_time'], 0, 5)) ?>
<?php elseif ($r['type'] === 'early_dismissal' && !empty($r['dismiss_time'])): ?>
Dismiss: <?= esc(substr($r['dismiss_time'], 0, 5)) ?>
<?php else: ?>
<?php endif; ?>
</td>
<td style="max-width: 360px; white-space: normal;"><?= esc($r['reason'] ?? '') ?></td>
<td><span class="badge bg-<?= ($r['status'] === 'processed' ? 'success' : ($r['status'] === 'seen' ? 'info' : 'secondary')) ?>
"><?= esc(ucfirst($r['status'] ?? 'new')) ?></span></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="7" class="text-center text-muted">No reports found for the selected range.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,180 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php $dateLabel = !empty($date) ? local_date($date, 'm-d-Y') : ''; ?>
<div class="container-xxl py-4">
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
<h2 class="mb-0">Teacher Attendance</h2>
<div class="d-flex gap-2">
<a class="btn btn-outline-secondary"
href="<?= site_url('admin/teacher-attendance/month?date='.urlencode($date).'&semester='.urlencode($semester).'&school_year='.urlencode($schoolYear)) ?>">
← Back to Monthly Attendance
</a>
</div>
</div>
<?php if (session()->getFlashdata('status')): ?>
<div class="alert alert-success"><?= session()->getFlashdata('status') ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
<?php endif; ?>
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
</div>
<form class="row gy-2 gx-3 align-items-end mb-3" method="get" action="<?= site_url('admin/teacher-attendance') ?>">
<div class="col-sm-3">
<label class="form-label">School Year</label>
<input type="text" name="school_year" class="form-control" value="<?= esc($schoolYear) ?>" placeholder="e.g. 2025-2026">
</div>
<div class="col-sm-2">
<label class="form-label">Semester</label>
<input type="text" name="semester" class="form-control" value="<?= esc($semester) ?>" placeholder="Fall">
</div>
<div class="col-sm-3">
<label class="form-label">Date</label>
<input type="date" name="date" class="form-control" value="<?= esc($date) ?>">
</div>
<div class="col-sm-4">
<label class="form-label">Class Section</label>
<select name="class_section_id" class="form-select">
<option value="0">— Choose Section —</option>
<?php foreach ($sections as $id => $label): ?>
<option value="<?= (int)$id ?>" <?= (int)$id === (int)$class_section_id ? 'selected' : '' ?>>
<?= esc($label) ?> (ID: <?= (int)$id ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 d-flex gap-2">
<button type="submit" class="btn btn-secondary">Load</button>
<?php if ($class_section_id): ?>
<a class="btn btn-outline-secondary" href="<?= site_url('admin/teacher-attendance') ?>">Reset</a>
<?php endif; ?>
</div>
</form>
<?php if ($class_section_id): ?>
<?php if ($locked): ?>
<div class="alert alert-warning">
<strong>Note:</strong> Student/section attendance for this day appears to be <em>finalized</em>.
Admin edits to teacher attendance here are allowed and will not unlock the section.
</div>
<?php endif; ?>
<form method="post" action="<?= site_url('admin/teacher-attendance/save') ?>" id="adminTeacherAttForm">
<?= csrf_field() ?>
<input type="hidden" name="class_section_id" value="<?= (int)$class_section_id ?>">
<input type="hidden" name="date" value="<?= esc($date) ?>">
<input type="hidden" name="semester" value="<?= esc($semester) ?>">
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>Teachers on <?= esc($dateLabel) ?></strong>
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-success" id="markAllPresent">Mark All Present</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="clearAll">Clear All</button>
</div>
</div>
<div class="card-body table-responsive">
<table id="teacherAttTable" class="table table-bordered table-striped align-middle">
<thead class="table-light">
<tr>
<th class="text-center" style="width:70px;">#</th>
<th>Name</th>
<th class="text-center" style="width:160px;">Role</th>
<th class="text-center" style="width:200px;">Status</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
<?php if (!empty($grid)): ?>
<?php $i = 1; foreach ($grid as $row): ?>
<?php
$tid = (int)$row['teacher_id'];
$name = trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? '')) ?: ('User#' . $tid);
$pos = (string)$row['position'];
$stat = strtolower((string)($row['status'] ?? ''));
$reason= (string)($row['reason'] ?? '');
?>
<tr>
<td class="text-center"><?= $i++ ?></td>
<td><?= esc($name) ?></td>
<td class="text-center"><?= esc($pos === 'main' ? 'Main Teacher' : 'Assistant') ?></td>
<td class="text-center">
<input type="hidden" name="teachers[<?= $tid ?>][teacher_id]" value="<?= $tid ?>">
<select name="teachers[<?= $tid ?>][status]" class="form-select form-select-sm status-select">
<option value="">—</option>
<option value="present" <?= $stat === 'present' ? 'selected' : '' ?>>Present</option>
<option value="absent" <?= $stat === 'absent' ? 'selected' : '' ?>>Absent</option>
<option value="late" <?= $stat === 'late' ? 'selected' : '' ?>>Late</option>
</select>
</td>
<td>
<input type="text"
name="teachers[<?= $tid ?>][reason]"
class="form-control form-control-sm"
placeholder="Optional reason"
value="<?= esc($reason) ?>">
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="5" class="text-center">No teachers found for this section/term.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="card-footer d-flex justify-content-end">
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</form>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function(){
const markAllBtn = document.getElementById('markAllPresent');
const clearBtn = document.getElementById('clearAll');
if (markAllBtn) {
markAllBtn.addEventListener('click', () => {
document.querySelectorAll('#teacherAttTable .status-select').forEach(sel => {
sel.value = 'present';
});
});
}
if (clearBtn) {
clearBtn.addEventListener('click', () => {
document.querySelectorAll('#teacherAttTable .status-select').forEach(sel => {
sel.value = '';
});
document.querySelectorAll('#teacherAttTable input[name$="[reason]"]').forEach(inp => {
inp.value = '';
});
});
}
});
</script>
<!-- DataTables (optional) -->
<script>
$(function(){
$('#teacherAttTable').DataTable({
paging: false, // usually small set; disable paging for editing
searching: false,
info: false,
order: []
});
});
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,900 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?= csrf_meta() ?>
<?php
$queryParams = [
'semester' => $semester ?? '',
'school_year' => $schoolYear ?? '',
];
$queryString = http_build_query(array_filter($queryParams, static function ($value) {
return $value !== '';
}));
?>
<div
class="container-fluid py-4"
data-endpoint="<?= esc($endpoint ?? '') ?>"
data-save-endpoint="<?= esc($saveEndpoint ?? '') ?>"
data-csrf-name="<?= esc(csrf_token()) ?>"
data-csrf-value="<?= esc(csrf_hash()) ?>"
data-semester="<?= esc($semester ?? '') ?>"
data-school-year="<?= esc($schoolYear ?? '') ?>"
data-is-current="<?= isset($isCurrentYear) && $isCurrentYear ? '1' : '0' ?>"
>
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<h2 class="mb-0">Admin &amp; Teacher — Monthly Attendance</h2>
<div class="legend">
<span class="badge bg-success">P</span><span class="ms-1">Present</span>
<span class="badge bg-danger ms-3">A</span><span class="ms-1">Absent</span>
<span class="badge bg-warning text-dark ms-3">L</span><span class="ms-1">Late</span>
<span class="badge bg-info text-dark ms-3">NS</span><span class="ms-1">No School</span>
<span class="ms-3 cell-empty">—</span><span class="ms-1">No entry</span>
</div>
</div>
<?php if (!empty($missingYear)): ?>
<div class="alert alert-warning" role="alert">
Current school year is not configured. This page is read-only until configured in
<a href="<?= site_url('configuration/configuration_view') ?>" class="alert-link">Add/Edit Configuration</a> (key: <code>school_year</code>).
</div>
<?php endif; ?>
<form class="row gy-2 gx-3 align-items-end justify-content-center mb-4" method="get" action="<?= site_url('admin/teacher-attendance/month') ?>">
<div class="col-md-3">
<label class="form-label">School Year</label>
<select name="school_year" class="form-select">
<?php
$years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : [];
if (empty($years) && !empty($schoolYear)) $years = [$schoolYear];
foreach ($years as $y): $val = is_array($y) && isset($y['school_year']) ? $y['school_year'] : (string)$y; ?>
<option value="<?= esc($val) ?>" <?= ((string)($schoolYear ?? '') === (string)$val) ? 'selected' : '' ?>>
<?= esc($val) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Semester</label>
<select name="semester" class="form-select">
<?php $semVal = (string)($semester ?? ''); ?>
<?php foreach (['---','Fall','Spring'] as $sem): ?>
<option value="<?= esc($sem) ?>" <?= ($semVal === $sem ? 'selected' : '') ?>><?= esc($sem) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4 d-flex gap-2">
<button type="submit" class="btn btn-secondary">Load</button>
<a class="btn btn-outline-secondary" href="<?= site_url('admin/teacher-attendance/month') ?>">Reset</a>
<a class="btn btn-outline-primary"
href="<?= site_url('admin/teacher-attendance/month.csv' . ($queryString ? ('?' . $queryString) : '')) ?>">
Export CSV
</a>
</div>
</form>
<style>
.att-select.form-select.form-select-sm {
min-width: 54px;
padding-left: 6px;
padding-right: 18px;
line-height: 1.1;
}
.att-none {
background: transparent;
color: #6c757d;
}
.att-present {
background: #19875422;
}
.att-absent {
background: #dc354522;
}
.att-late {
background: #ffc10755;
}
.month-grid-wrap {
overflow-x: auto;
}
.month-grid thead th {
position: sticky;
top: 0;
z-index: 2;
background: #f8f9fa;
}
.month-grid .sticky-col {
position: sticky;
left: 0;
z-index: 3;
background: #fff;
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.month-grid .sticky-col-2 {
position: sticky;
left: 180px;
z-index: 3;
background: #fff;
}
.month-grid .sticky-col-3 {
position: sticky;
left: 460px; /* 180 (col1) + 280 (col2) */
z-index: 3;
background: #fff;
}
.badge-cell {
display: inline-block;
min-width: 18px;
text-align: center;
border-radius: 4px;
padding: 2px 0;
font-weight: 600;
}
.cell-empty {
color: #999;
}
.legend .badge {
min-width: 28px;
}
.month-grid .badge-cell {
min-width: 32px;
padding: 4px 0;
border-radius: 6px;
font-weight: 700;
border: 1px solid transparent;
}
.month-grid .badge-cell.bg-success {
background-color: #157347 !important;
border-color: #0f5132;
color: #fff !important;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .25) inset, 0 0 0 3px rgba(21, 115, 71, .18);
}
.month-grid .badge-cell.bg-danger {
background-color: #bb2d3b !important;
border-color: #842029;
color: #fff !important;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .25) inset, 0 0 0 3px rgba(187, 45, 59, .18);
}
.month-grid .badge-cell.bg-warning {
background-color: #ffca2c !important;
border-color: #997404;
color: #111 !important;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .18) inset, 0 0 0 3px rgba(255, 202, 44, .25);
}
.month-grid .badge-cell.bg-info {
background-color: #0dcaf0 !important; /* Bootstrap info */
border-color: #055160; /* darker info border */
color: #111 !important;
box-shadow: 0 0 0 1px rgba(0, 0, 0, .18) inset, 0 0 0 3px rgba(13, 202, 240, .25);
}
.month-grid .cell-empty {
color: #adb5bd;
}
.att-select.form-select.form-select-sm {
min-width: 54px;
padding-left: 6px;
padding-right: 18px;
line-height: 1.1;
border: 1px solid #adb5bd;
transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
/* No School column/day highlight */
.no-school-day {
background-color: #cff4fc !important; /* Bootstrap info-subtle */
}
.att-select.att-present {
background: #157347;
color: #fff;
border-color: #0f5132;
box-shadow: 0 0 0 2px rgba(21, 115, 71, .18);
}
.att-select.att-absent {
background: #bb2d3b;
color: #fff;
border-color: #842029;
box-shadow: 0 0 0 2px rgba(187, 45, 59, .18);
}
.att-select.att-late {
background: #ffca2c;
color: #111;
border-color: #997404;
box-shadow: 0 0 0 2px rgba(255, 202, 44, .25);
}
.att-select.att-none {
background: #fff;
color: #212529;
border-color: #adb5bd;
}
</style>
<div class="card shadow-sm mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<strong>Teachers &amp; Assistants — Sundays in <span id="teacherMonthLabel"><?= esc($rangeLabel ?? ('School Year ' . ($schoolYear ?? ''))) ?></span></strong>
<?php if (isset($isCurrentYear) && !$isCurrentYear): ?>
<span class="badge bg-secondary ms-2">Read-only (Past Year)</span>
<?php endif; ?>
</div>
<small class="text-muted">Use the dropdowns to set attendance inline</small>
</div>
<div class="card-body month-grid-wrap">
<div id="teachersGrid" class="text-center text-muted py-4">Loading...</div>
</div>
</div>
<div class="card shadow-sm mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<strong>Admins — Sundays in <span id="adminMonthLabel"><?= esc($rangeLabel ?? ('School Year ' . ($schoolYear ?? ''))) ?></span></strong>
<span class="text-muted ms-2">(All roles except Parent/Guest/Teacher/Teacher Assistant)</span>
</div>
</div>
<div class="card-body month-grid-wrap">
<div id="adminsGrid" class="text-center text-muted py-4">Loading...</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
// Bootstrap 5 Toast utility
(function(){
function ensureToastContainer() {
var c = document.getElementById('toast-container');
if (!c) {
c = document.createElement('div');
c.id = 'toast-container';
c.className = 'toast-container position-fixed top-0 end-0 p-3';
c.style.zIndex = '1080';
document.body.appendChild(c);
}
return c;
}
window.showToast = function(message, ok) {
var c = ensureToastContainer();
var el = document.createElement('div');
el.className = 'toast align-items-center text-bg-' + (ok === false ? 'danger' : 'success') + ' border-0';
el.setAttribute('role','alert');
el.setAttribute('aria-live','assertive');
el.setAttribute('aria-atomic','true');
el.innerHTML = '<div class="d-flex">'
+ '<div class="toast-body">' + (message || (ok === false ? 'Action failed' : 'Saved')) + '</div>'
+ '<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>'
+ '</div>';
c.appendChild(el);
try {
var t = new bootstrap.Toast(el, { delay: 2500, autohide: true });
el.addEventListener('hidden.bs.toast', function(){ el.remove(); });
t.show();
} catch (_) {
setTimeout(function(){ el.remove(); }, 2500);
}
}
})();
</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var container = document.querySelector('[data-endpoint]');
if (!container) {
return;
}
var endpoint = container.getAttribute('data-endpoint') || '';
var saveUrl = container.getAttribute('data-save-endpoint') || '';
var csrfName = container.getAttribute('data-csrf-name') || '';
var csrfHash = container.getAttribute('data-csrf-value') || '';
var semesterDefault = container.getAttribute('data-semester') || '';
var schoolYearDefault = container.getAttribute('data-school-year') || '';
var isCurrentYear = (container.getAttribute('data-is-current') || '1') === '1';
var teachersGrid = document.getElementById('teachersGrid');
var adminsGrid = document.getElementById('adminsGrid');
var teacherMonthLabel = document.getElementById('teacherMonthLabel');
var adminMonthLabel = document.getElementById('adminMonthLabel');
if (!endpoint) {
if (teachersGrid) {
teachersGrid.innerHTML = '<div class="alert alert-danger mb-0">Endpoint not configured.</div>';
}
if (adminsGrid) {
adminsGrid.innerHTML = '';
}
return;
}
fetch(endpoint, {
headers: { 'Accept': 'application/json' },
credentials: 'same-origin'
})
.then(function(response) {
if (!response.ok) {
throw new Error('Request failed with status ' + response.status);
}
return response.json();
})
.then(function(payload) {
renderTeachers(payload);
renderAdmins(payload);
initializeSelects();
})
.catch(function(error) {
console.error(error);
if (teachersGrid) {
teachersGrid.innerHTML = '<div class="alert alert-danger mb-0">Unable to load teacher attendance.</div>';
}
if (adminsGrid) {
adminsGrid.innerHTML = '<div class="alert alert-danger mb-0">Unable to load admin attendance.</div>';
}
if (window.showToast) showToast('Failed to load attendance data', false);
});
function clearElement(el) {
if (!el) {
return;
}
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
function renderTeachers(payload) {
if (!teachersGrid) {
return;
}
clearElement(teachersGrid);
var filters = payload && payload.filters ? payload.filters : {};
var sections = payload && payload.sections ? payload.sections : [];
var sundays = payload && payload.sundays ? payload.sundays : [];
var noSchoolDays = payload && payload.noSchoolDays ? payload.noSchoolDays : [];
var noSchoolSet = {};
for (var i = 0; i < noSchoolDays.length; i++) { noSchoolSet[noSchoolDays[i]] = true; }
var label = filters.range_label || filters.month_label || '';
if (teacherMonthLabel && label) {
teacherMonthLabel.textContent = label;
}
if (!sections.length) {
var info = document.createElement('div');
info.className = 'alert alert-info mb-0';
info.textContent = 'No sections with assigned teachers found for this term.';
teachersGrid.appendChild(info);
return;
}
var table = document.createElement('table');
table.className = 'table table-bordered table-striped month-grid align-middle';
table.setAttribute('data-no-mgmt-sticky', '1');
var thead = document.createElement('thead');
var headerRow = document.createElement('tr');
var thSection = document.createElement('th');
thSection.className = 'sticky-col';
thSection.style.minWidth = '180px';
thSection.appendChild(document.createTextNode('Class / Section'));
headerRow.appendChild(thSection);
var thTeacher = document.createElement('th');
thTeacher.className = 'sticky-col-2';
thTeacher.style.minWidth = '280px';
thTeacher.appendChild(document.createTextNode('Teacher'));
headerRow.appendChild(thTeacher);
var thRole = document.createElement('th');
thRole.className = 'sticky-col-3 text-center';
thRole.style.minWidth = '120px';
thRole.appendChild(document.createTextNode('Role'));
headerRow.appendChild(thRole);
for (var i = 0; i < sundays.length; i++) {
var day = sundays[i];
var thDay = document.createElement('th');
thDay.className = 'text-center';
thDay.style.minWidth = '56px';
if (noSchoolSet[day]) thDay.classList.add('no-school-day');
// Show date as mm-dd-yyyy
var yyyy = day.substring(0, 4);
var mm = day.substring(5, 7);
var dd = day.substring(8, 10);
thDay.title = day; // full YYYY-MM-DD tooltip
thDay.appendChild(document.createTextNode(mm + '-' + dd));
headerRow.appendChild(thDay);
}
var totals = ['P', 'A', 'L'];
for (var j = 0; j < totals.length; j++) {
var thTotal = document.createElement('th');
thTotal.className = 'text-center';
thTotal.style.minWidth = '70px';
thTotal.appendChild(document.createTextNode(totals[j]));
headerRow.appendChild(thTotal);
}
thead.appendChild(headerRow);
table.appendChild(thead);
var tbody = document.createElement('tbody');
var hasTeachers = false;
for (var s = 0; s < sections.length; s++) {
var section = sections[s];
var teacherList = section && section.teachers ? section.teachers : [];
if (!teacherList.length) {
continue;
}
hasTeachers = true;
var rowSpan = teacherList.length;
for (var t = 0; t < teacherList.length; t++) {
var teacher = teacherList[t];
var row = document.createElement('tr');
if (t === 0) {
var tdSection = document.createElement('td');
tdSection.className = 'sticky-col';
tdSection.style.minWidth = '180px';
tdSection.rowSpan = rowSpan;
tdSection.appendChild(document.createTextNode(section.label || ('Section ' + section.section_code)));
row.appendChild(tdSection);
}
var tdTeacher = document.createElement('td');
tdTeacher.className = 'sticky-col-2';
tdTeacher.appendChild(document.createTextNode(teacher.name || '—'));
row.appendChild(tdTeacher);
var tdRole = document.createElement('td');
tdRole.className = 'sticky-col-3 text-center';
var roleText = (teacher.position || '').toLowerCase() === 'main' ? 'Main' : 'Assistant';
tdRole.appendChild(document.createTextNode(roleText));
row.appendChild(tdRole);
for (var d = 0; d < sundays.length; d++) {
var dayKey = sundays[d];
var cellData = teacher.cells && teacher.cells[dayKey] ? teacher.cells[dayKey] : null;
var status = '';
if (cellData && cellData.status) {
var st = String(cellData.status).toLowerCase();
if (st === 'present' || st === 'absent' || st === 'late') {
status = st;
}
}
var tdCell = document.createElement('td');
tdCell.className = 'text-center';
if (noSchoolSet[dayKey]) {
tdCell.classList.add('no-school-day');
var ns = document.createElement('span');
ns.className = 'badge-cell bg-info text-dark';
ns.textContent = 'NS';
tdCell.appendChild(ns);
row.appendChild(tdCell);
continue;
}
var select = createAttendanceSelect(
'teacher',
section.section_code,
teacher.user_id || teacher.teacher_id || section.section_code,
dayKey,
filters,
roleText,
status
);
tdCell.appendChild(select);
row.appendChild(tdCell);
}
var totalsRow = teacher.totals || { p: 0, a: 0, l: 0 };
var totalValues = [totalsRow.p || 0, totalsRow.a || 0, totalsRow.l || 0];
for (var tv = 0; tv < totalValues.length; tv++) {
var tdTotal = document.createElement('td');
tdTotal.className = 'text-center';
var strong = document.createElement('strong');
strong.appendChild(document.createTextNode(String(totalValues[tv])));
tdTotal.appendChild(strong);
row.appendChild(tdTotal);
}
tbody.appendChild(row);
}
}
if (!hasTeachers) {
var emptyRow = document.createElement('tr');
var emptyCell = document.createElement('td');
emptyCell.colSpan = 3 + sundays.length + 3;
emptyCell.className = 'text-center';
emptyCell.appendChild(document.createTextNode('No teachers assigned.'));
emptyRow.appendChild(emptyCell);
tbody.appendChild(emptyRow);
}
table.appendChild(tbody);
teachersGrid.appendChild(table);
}
function renderAdmins(payload) {
if (!adminsGrid) {
return;
}
clearElement(adminsGrid);
var filters = payload && payload.filters ? payload.filters : {};
var admins = payload && payload.admins ? payload.admins : [];
var sundays = payload && payload.sundays ? payload.sundays : [];
var noSchoolDays = payload && payload.noSchoolDays ? payload.noSchoolDays : [];
var noSchoolSet = {};
for (var i = 0; i < noSchoolDays.length; i++) { noSchoolSet[noSchoolDays[i]] = true; }
var label = filters.range_label || filters.month_label || '';
if (adminMonthLabel && label) {
adminMonthLabel.textContent = label;
}
if (!admins.length) {
var info = document.createElement('div');
info.className = 'alert alert-info mb-0';
info.textContent = 'No admin users found for this term.';
adminsGrid.appendChild(info);
return;
}
var table = document.createElement('table');
table.className = 'table table-bordered table-striped month-grid align-middle';
table.setAttribute('data-no-mgmt-sticky', '1');
var thead = document.createElement('thead');
var headerRow = document.createElement('tr');
var thAdmin = document.createElement('th');
thAdmin.className = 'sticky-col';
thAdmin.style.minWidth = '180px';
thAdmin.appendChild(document.createTextNode('Admin'));
headerRow.appendChild(thAdmin);
var thRole = document.createElement('th');
thRole.className = 'sticky-col-2 text-center';
thRole.style.minWidth = '240px';
thRole.appendChild(document.createTextNode('Role'));
headerRow.appendChild(thRole);
for (var i = 0; i < sundays.length; i++) {
var thDay = document.createElement('th');
thDay.className = 'text-center';
thDay.style.minWidth = '56px';
if (noSchoolSet[sundays[i]]) thDay.classList.add('no-school-day');
// Show date as mm-dd-yyyy
var sday = sundays[i];
var sdd = sday.substring(8, 10);
var smm = sday.substring(5, 7);
var syyyy = sday.substring(0, 4);
thDay.title = sday; // full YYYY-MM-DD tooltip
thDay.appendChild(document.createTextNode(smm + '-' + sdd));
headerRow.appendChild(thDay);
}
var totals = ['P', 'A', 'L'];
for (var j = 0; j < totals.length; j++) {
var thTotal = document.createElement('th');
thTotal.className = 'text-center';
thTotal.style.minWidth = '70px';
thTotal.appendChild(document.createTextNode(totals[j]));
headerRow.appendChild(thTotal);
}
thead.appendChild(headerRow);
table.appendChild(thead);
var tbody = document.createElement('tbody');
for (var a = 0; a < admins.length; a++) {
var admin = admins[a];
var row = document.createElement('tr');
var tdName = document.createElement('td');
tdName.className = 'sticky-col';
tdName.appendChild(document.createTextNode(admin.name || '—'));
row.appendChild(tdName);
var tdRole = document.createElement('td');
tdRole.className = 'sticky-col-2 text-center';
tdRole.appendChild(document.createTextNode(admin.role || '—'));
row.appendChild(tdRole);
for (var d = 0; d < sundays.length; d++) {
var dayKey = sundays[d];
var cellData = admin.cells && admin.cells[dayKey] ? admin.cells[dayKey] : null;
var status = '';
if (cellData && cellData.status) {
var st = String(cellData.status).toLowerCase();
if (st === 'present' || st === 'absent' || st === 'late') {
status = st;
}
}
var tdCell = document.createElement('td');
tdCell.className = 'text-center';
if (noSchoolSet[dayKey]) {
tdCell.classList.add('no-school-day');
var ns = document.createElement('span');
ns.className = 'badge-cell bg-info text-dark';
ns.textContent = 'NS';
tdCell.appendChild(ns);
row.appendChild(tdCell);
continue;
}
var select = createAttendanceSelect(
'admin',
null,
admin.user_id,
dayKey,
filters,
admin.role || 'Admin',
status
);
tdCell.appendChild(select);
row.appendChild(tdCell);
}
var totalsRow = admin.totals || { p: 0, a: 0, l: 0 };
var totalValues = [totalsRow.p || 0, totalsRow.a || 0, totalsRow.l || 0];
for (var tv = 0; tv < totalValues.length; tv++) {
var tdTotal = document.createElement('td');
tdTotal.className = 'text-center';
var strong = document.createElement('strong');
strong.appendChild(document.createTextNode(String(totalValues[tv])));
tdTotal.appendChild(strong);
row.appendChild(tdTotal);
}
tbody.appendChild(row);
}
table.appendChild(tbody);
adminsGrid.appendChild(table);
}
function createAttendanceSelect(type, sectionCode, userId, date, filters, roleName, currentValue) {
var select = document.createElement('select');
select.className = 'form-select form-select-sm att-select';
select.setAttribute('data-type', type);
if (sectionCode !== null && sectionCode !== undefined) {
select.setAttribute('data-section-id', sectionCode);
}
select.setAttribute('data-user-id', userId);
select.setAttribute('data-date', date);
// Derive semester for saves: if filter is whole-year (---), infer by date
var semForSave = filters.semester || semesterDefault;
if (semForSave === '---') {
semForSave = inferSemesterByDate(date, (filters.school_year || schoolYearDefault));
}
select.setAttribute('data-semester', semForSave);
select.setAttribute('data-school-year', filters.school_year || schoolYearDefault);
if (roleName) {
select.setAttribute('data-role-name', roleName);
}
var statuses = ['', 'present', 'absent', 'late'];
var labels = ['—', 'P', 'A', 'L'];
for (var i = 0; i < statuses.length; i++) {
var option = document.createElement('option');
option.value = statuses[i];
option.appendChild(document.createTextNode(labels[i]));
if (statuses[i] === currentValue) {
option.selected = true;
}
select.appendChild(option);
}
if (!isCurrentYear) {
select.disabled = true;
select.title = 'Editing disabled for non-current year';
}
return select;
}
// Infer semester by date using fixed windows:
// Fall: Y1-09-21 to Y2-01-18
// Spring: Y2-01-25 to Y2-05-30
function inferSemesterByDate(dateStr, schoolYear) {
try {
if (!schoolYear || schoolYear.indexOf('-') < 0) return '';
var parts = schoolYear.split('-');
var y1 = parseInt(parts[0], 10);
var y2 = parseInt(parts[1], 10);
if (!y1 || !y2) return '';
var fallStart = new Date(y1 + '-09-21T00:00:00Z');
var fallEnd = new Date(y2 + '-01-18T00:00:00Z');
var spStart = new Date(y2 + '-01-25T00:00:00Z');
var spEnd = new Date(y2 + '-05-30T00:00:00Z');
var d = new Date(dateStr + 'T00:00:00Z');
if (d >= fallStart && d <= fallEnd) return 'Fall';
if (d >= spStart && d <= spEnd) return 'Spring';
// Fallback by month if out of bounds
var m = d.getUTCMonth() + 1; // 1..12
return (m >= 9 || m <= 1) ? 'Fall' : 'Spring';
} catch (_) {
return '';
}
}
function initializeSelects() {
var selects = document.querySelectorAll('.att-select');
for (var i = 0; i < selects.length; i++) {
applyAppearance(selects[i]);
}
}
function applyAppearance(select) {
if (!select) {
return;
}
select.classList.remove('att-none', 'att-present', 'att-absent', 'att-late');
var value = (select.value || '').toLowerCase();
if (value === 'present') {
select.classList.add('att-present');
} else if (value === 'absent') {
select.classList.add('att-absent');
} else if (value === 'late') {
select.classList.add('att-late');
} else {
select.classList.add('att-none');
}
}
function recomputeRowTotals(row) {
if (!row) {
return;
}
var totals = { p: 0, a: 0, l: 0 };
var selects = row.querySelectorAll('.att-select');
for (var i = 0; i < selects.length; i++) {
var value = selects[i].value;
if (value === 'present') {
totals.p++;
} else if (value === 'absent') {
totals.a++;
} else if (value === 'late') {
totals.l++;
}
}
var cells = row.querySelectorAll('td');
var len = cells.length;
if (len >= 3) {
cells[len - 3].innerHTML = '<strong>' + totals.p + '</strong>';
cells[len - 2].innerHTML = '<strong>' + totals.a + '</strong>';
cells[len - 1].innerHTML = '<strong>' + totals.l + '</strong>';
}
}
function toFormData(obj) {
var fd = new FormData();
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fd.append(key, obj[key]);
}
}
return fd;
}
document.addEventListener('change', function(event) {
var select = event.target;
if (!select || !select.classList || !select.classList.contains('att-select')) {
return;
}
if (!isCurrentYear) {
event.preventDefault();
if (window.showToast) showToast('Editing disabled for non-current year', false);
return;
}
var payload = {};
payload[csrfName] = csrfHash;
payload.date = select.getAttribute('data-date') || '';
payload.status = select.value || '';
payload.user_id = select.getAttribute('data-user-id') || '';
payload.semester = select.getAttribute('data-semester') || semesterDefault;
payload.school_year = select.getAttribute('data-school-year') || schoolYearDefault;
payload.role_name = select.getAttribute('data-role-name') || '';
if (!payload.date || !payload.user_id || !payload.semester || !payload.school_year) {
console.error('Missing required fields for save', payload);
return;
}
select.disabled = true;
fetch(saveUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
// Send CSRF in headers as well to satisfy strict CSRF setups
'X-CSRF-TOKEN': csrfHash,
'X-CSRF-Token': csrfHash
},
body: toFormData(payload),
credentials: 'same-origin'
})
.then(function(response) {
return response.json().catch(function() {
return {};
}).then(function(data) {
return { ok: response.ok, data: data };
});
})
.then(function(result) {
if (!result) {
throw new Error('Invalid response');
}
var data = result.data || {};
if (data.csrf_token && data.csrf_hash) {
csrfName = data.csrf_token;
csrfHash = data.csrf_hash;
} else {
// Fallback: try refresh from cookie if server rotated token
try {
var c = document.cookie.split(';').map(function(v){return v.trim();}).find(function(v){
return v.indexOf('<?= esc(config('Security')->csrfCookieName ?? (config('Security')->cookieName ?? 'csrf_cookie_name')) ?>=')===0;
});
if (c) {
csrfHash = decodeURIComponent(c.split('=')[1] || '');
}
} catch (_) {}
}
if (!result.ok || data.ok === false) {
console.error('Save failed', data);
if (window.showToast) showToast('Save failed', false);
return;
}
applyAppearance(select);
recomputeRowTotals(select.closest('tr'));
if (window.showToast) showToast('Saved');
})
.catch(function(error) {
console.error('Network/save error', error);
if (window.showToast) showToast('Network error while saving', false);
})
.finally(function() {
select.disabled = false;
});
});
});
</script>
<?= $this->endSection() ?>
+94
View File
@@ -0,0 +1,94 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container">
<h2 class="text-center mt-4 mb-3"><?= esc($title ?? 'Attendance History') ?></h2>
<div class="card mb-4">
<div class="card-body">
<a href="<?= base_url('attendance/violations') ?>" class="btn btn-secondary mb-3">Back to Attendance</a>
<p class="text-muted small mb-3">Showing absences and lates for this student within the violation window.</p>
<?php
$viCode = $violation_code ?? null;
$viDateRaw = $violation_date ?? '';
$viDateLbl = $viDateRaw !== '' ? date('m-d-Y', strtotime($viDateRaw)) : '—';
$viNotifRaw = $violation_notified ?? null;
$viNotif = (is_string($viNotifRaw) && in_array(strtolower(trim($viNotifRaw)), ['yes', '1'], true))
|| ((int)$viNotifRaw === 1);
?>
<h5 class="card-title mb-3">Attendance Violations</h5>
<table class="table table-striped attendance-history-table no-mgmt-sticky" data-no-mgmt-sticky="1">
<thead>
<tr>
<th>Date</th>
<th>Status</th>
<th>Reason</th>
<th>Notified</th>
<th>Reported</th>
</tr>
</thead>
<tbody>
<?php if (!empty($show_violation_summary) && ($viCode || $viDateRaw !== '')): ?>
<tr class="table-info">
<td><?= esc($viDateLbl) ?></td>
<td><?= esc($viCode ?: '—') ?></td>
<td>Violation summary</td>
<td><?= $viNotif ? 'Yes' : 'No' ?></td>
<td>—</td>
</tr>
<?php endif; ?>
<?php if (!empty($attendance) && is_array($attendance)): ?>
<?php foreach ($attendance as $record): ?>
<?php
$dateStr = (string)($record['date'] ?? '');
$ymd = $dateStr !== '' ? substr($dateStr, 0, 10) : '';
$dateLabel = $ymd !== '' ? date('m-d-Y', strtotime($ymd)) : '—';
$statusRaw = strtolower(trim((string)($record['status'] ?? ''))); // absent/late
$reason = trim((string)($record['reason'] ?? '')); // may come from tracking join
$notifRaw = $record['is_notified'] ?? null;
$isNotified = (is_string($notifRaw) && in_array(strtolower(trim($notifRaw)), ['yes', '1'], true))
|| ((int)$notifRaw === 1);
$reportedRaw = $record['is_reported'] ?? null;
$isReported = (is_string($reportedRaw) && in_array(strtolower(trim($reportedRaw)), ['yes', '1'], true))
|| ((int)$reportedRaw === 1);
$isAbs = $statusRaw === 'absent' || $statusRaw === 'abs' || $statusRaw === 'a' || str_starts_with($statusRaw, 'abs');
$isLate = $statusRaw === 'late' || $statusRaw === 'l' || str_starts_with($statusRaw, 'late');
$status = $statusRaw !== '' ? ucfirst($statusRaw) : 'Unknown';
if ($isAbs) {
$status = $isReported ? 'Reported Absence' : 'Unreported Absence';
} elseif ($isLate) {
$status = $isReported ? 'Reported Late' : 'Unreported Late';
}
?>
<tr>
<td><?= esc($dateLabel) ?></td>
<td><?= esc($status) ?></td>
<td><?= $reason !== '' ? esc($reason) : 'No reason provided' ?></td>
<td><?= $isNotified ? 'Yes' : 'No' ?></td>
<td><?= $isReported ? 'Yes' : 'No' ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr><td colspan="5" class="text-center text-muted">No attendance violations found for this student.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('styles') ?>
<style>
/* Ensure table header is not sticky on this view to avoid overlap */
.attendance-history-table thead th {
position: static !important;
top: auto !important;
background: #f8f9fa;
z-index: 1;
}
</style>
<?= $this->endSection() ?>
@@ -0,0 +1,544 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
$page_title = $title ?? 'Notified Attendance Violations';
$school_year = $school_year ?? '';
$semester = $semester ?? '';
$show_actions = false; // notified page hides actions by design
?>
<div class="container-fluid px-0">
<h2 class="text-center mt-4 mb-3"><?= esc($page_title) ?></h2>
<div class="d-flex justify-content-center mb-2">
<div class="col-12 col-lg-10">
<?= $this->include('partials/academic_filter') ?>
</div>
</div>
<?php if ($msg = session()->getFlashdata('message')): ?>
<div class="alert alert-success mx-3"><?= esc($msg) ?></div>
<?php endif; ?>
<?php if ($err = session()->getFlashdata('error')): ?>
<div class="alert alert-danger mx-3"><?= esc($err) ?></div>
<?php endif; ?>
<div class="row g-0">
<div class="col-12">
<div class="card border-0 rounded-3 shadow-sm">
<div class="card-header d-flex flex-wrap gap-2 justify-content-between align-items-center">
<h5 class="mt-2 mb-0">Notified Attendance Violations</h5>
<div class="d-flex flex-wrap gap-2 align-items-center">
<span class="badge bg-primary">School Year: <?= esc($school_year) ?></span>
<?php if ($semester !== ''): ?>
<span class="badge bg-secondary">Semester: <?= esc($semester) ?></span>
<?php endif; ?>
<a href="<?= site_url('attendance/violations') ?>" class="btn btn-outline-dark btn-sm">
<i class="fas fa-hourglass-half"></i> View Pending
</a>
</div>
</div>
<div class="card-body">
<?php if (empty($students)): ?>
<div class="alert alert-info mb-0">No notified violations found.</div>
<?php else: ?>
<div class="table-responsive">
<table id="notifiedTable" class="table table-striped table-hover align-middle w-100">
<thead class="table-dark">
<tr>
<th>Student</th>
<th>Grade</th>
<th>Last Incident</th>
<th>Reason</th>
<th>Note</th>
<th>Parent</th>
<th>Notified On</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $st): ?>
<?php
// ID may be under 'student_id' (from tracking) or 'id' (from students)
$studId = (int)($st['student_id'] ?? $st['id'] ?? 0);
// Robust name resolution (name, student_name, or first/last)
$studName =
($st['name'] ?? null)
?: ($st['student_name'] ?? null)
?: trim(
($st['firstname'] ?? $st['first_name'] ?? '') . ' ' .
($st['lastname'] ?? $st['last_name'] ?? '')
);
if ($studName === '') {
$studName = $studId ? ('Student #'.$studId) : '—';
}
// Grade/Class name resolution
$className = $st['class_section_name']
?? $st['class_name']
?? $st['className']
?? $st['grade']
?? '';
// Dates and notified meta
$lastDate = $st['last_date'] ?? $st['date'] ?? null; // DATETIME
$notified = !empty($st['is_notified']);
$notifCnt = (int)($st['notif_counter'] ?? 0);
$reason = trim((string)($st['reason'] ?? ''));
// Term fields (fallback to page vars if absent)
$sy = (string)($st['school_year'] ?? $school_year);
$sem = (string)($st['semester'] ?? $semester);
// Row color (optional)
$viColor = (string)($st['color'] ?? '#0d6efd');
// Parent display info (if available)
$parentEmail = $st['parent_email'] ?? '';
$parentName = $st['parent_name'] ?? '';
$parentPhone = $st['parent_phone'] ?? '';
$parentLabel = $parentName ?: ($parentEmail ?: 'View');
// Note from attendance_tracking
$note = trim((string)($st['note'] ?? ''));
$noteShort = $note !== '' ? (mb_strlen($note) > 120 ? (mb_substr($note, 0, 120) . '…') : $note) : '';
$incidentYmd = $lastDate ? substr((string)$lastDate, 0, 10) : '';
$code = (string)($st['reason'] ?? $st['violation_code'] ?? '');
$notifiedAt = $st['updated_at'] ?? $st['date'] ?? null;
?>
<tr style="border-left:6px solid <?= esc($viColor) ?>;">
<td>
<a
href="<?= site_url('family') . '?student_id=' . (int)$studId ?>"
class="text-decoration-none"
data-family-student-id="<?= (int)$studId ?>"
title="Open Family Card">
<?= esc($studName) ?>
</a>
</td>
<td><?= esc($className) ?></td>
<td data-order="<?= $incidentYmd !== '' ? esc($incidentYmd) : '' ?>">
<?= $incidentYmd !== '' ? esc(date('m-d-Y', strtotime($incidentYmd))) : '—' ?>
</td>
<td><?= $reason !== '' ? esc($reason) : '—' ?></td>
<td style="max-width:280px;">
<?php if ($note !== ''): ?>
<div class="small text-wrap mb-1">
<?= nl2br(esc($noteShort)) ?>
</div>
<?php if (mb_strlen($note) > 120): ?>
<button
type="button"
class="btn btn-sm btn-outline-secondary me-1 mb-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="<?= esc($note) ?>"
>
View full
</button>
<?php endif; ?>
<button
type="button"
class="btn btn-sm btn-outline-primary mb-1 add-note-btn"
data-student-id="<?= (int)$studId ?>"
data-student-name="<?= esc($studName) ?>"
data-code="<?= esc($code) ?>"
data-incident-date="<?= esc($incidentYmd) ?>"
data-note="<?= esc($note) ?>"
data-return-url="<?= current_url() ?>"
>
Edit note
</button>
<?php else: ?>
<button
type="button"
class="btn btn-sm btn-outline-primary add-note-btn"
data-student-id="<?= (int)$studId ?>"
data-student-name="<?= esc($studName) ?>"
data-code="<?= esc($code) ?>"
data-incident-date="<?= esc($incidentYmd) ?>"
data-note=""
data-return-url="<?= current_url() ?>"
>
Add note
</button>
<?php endif; ?>
</td>
<!-- Parent column -->
<td>
<?php if ($parentName !== ''): ?>
<a
href="<?= site_url('family') . '?student_id=' . (int)$studId ?>"
class="text-decoration-none"
data-family-student-id="<?= (int)$studId ?>"
title="Open Family Card">
<?= esc($parentName) ?>
</a>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
</td>
<td>
<?= $notifiedAt ? esc(local_date($notifiedAt, 'm-d-Y')) : '—' ?>
</td>
<td class="text-nowrap">
<a href="<?= site_url('attendance/view/' . $studId) . '?code=' . urlencode($code) . ($lastDate ? '&incident_date=' . urlencode(substr((string)$lastDate,0,10)) : '') . '&include_notified=1' ?>" class="btn btn-sm btn-info">
<i class="fas fa-eye"></i> Details
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<!-- Add/Edit Comment/Note Modal -->
<div class="modal fade" id="notifyCommentModal" tabindex="-1" aria-labelledby="notifyCommentModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form method="post" action="<?= site_url('attendance/save-note') ?>" class="modal-content">
<?= csrf_field() ?>
<div class="modal-header">
<h5 class="modal-title" id="notifyCommentModalLabel">Add Comment / Note</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-2 small text-muted" id="noteMeta">
<div><strong>Student Name:</strong> <span id="noteStudentName">—</span></div>
<div><strong>Code:</strong> <span id="noteCode">—</span> &nbsp; <strong>Date:</strong> <span id="noteDate">—</span></div>
</div>
<div class="mb-3">
<label for="note" class="form-label">Comment / Note</label>
<textarea id="note" name="note" class="form-control" rows="5" required placeholder="Type any follow-up, call outcome, or special circumstances..."></textarea>
</div>
<!-- Hidden fields to carry context back -->
<input type="hidden" name="student_id" id="noteStudentId">
<input type="hidden" name="code" id="noteCodeInput">
<input type="hidden" name="incident_date" id="noteIncidentDate">
<input type="hidden" name="to" id="noteTo">
<input type="hidden" name="subject" id="noteSubject">
<input type="hidden" name="variant" id="noteVariant">
<input type="hidden" name="return_url" id="noteReturnUrl" value="<?= current_url() ?>">
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Save Note
</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
(function(){
// simple HTML escaper for dynamic strings
function esc(s) {
return String(s ?? '')
.replaceAll('&','&amp;').replaceAll('<','&lt;')
.replaceAll('>','&gt;').replaceAll('"','&quot;').replaceAll("'", "&#039;");
}
// Initialize Bootstrap tooltips (for "View full" note)
document.addEventListener('DOMContentLoaded', function () {
if (window.bootstrap) {
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach(function (el) { new bootstrap.Tooltip(el); });
}
});
function parentCard(p, label) {
if (!p) return '';
const name = esc(p.name || '');
const email = esc(p.email || '');
const phone = esc(p.phone || '');
return `
<div class="col-12 col-md-6">
<div class="card shadow-sm mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>${esc(label)} Parent</strong>
${email ? `<a class="btn btn-sm btn-outline-primary" href="mailto:${email}">
<i class="fas fa-envelope"></i> Email</a>` : ''}
</div>
<div class="card-body">
<div class="mb-1"><span class="text-muted">Name:</span> ${name || '—'}</div>
<div class="mb-1"><span class="text-muted">Email:</span> ${email || '—'}</div>
<div class="mb-1"><span class="text-muted">Phone:</span> ${phone || '—'}</div>
</div>
</div>
</div>`;
}
function renderParentsModal(data) {
const body = document.getElementById('parentModalBody');
if (!data || (!data.primary && !data.secondary)) {
body.innerHTML = `<div class="alert alert-warning mb-0">No parent information available for this student.</div>`;
return;
}
const primary = data.primary ? {
name: (data.primary.firstname ? `${data.primary.firstname} ${data.primary.lastname||''}`.trim() : (data.primary.name || '')),
email: data.primary.email || '',
phone: data.primary.cellphone || data.primary.phone || ''
} : null;
const secondary = data.secondary ? {
name: (data.secondary.firstname ? `${data.secondary.firstname} ${data.secondary.lastname||''}`.trim() : (data.secondary.name || '')),
email: data.secondary.email || '',
phone: data.secondary.cellphone || data.secondary.phone || ''
} : null;
body.innerHTML = `<div class="row">
${primary ? parentCard(primary, 'First') : ''}
${secondary ? parentCard(secondary, 'Second') : ''}
</div>`;
}
// Wire up modal open/fetch
document.addEventListener('click', function(e) {
const parentBtn = e.target.closest('.show-parents');
if (!parentBtn) return;
e.preventDefault();
const modalEl = document.getElementById('parentModal');
const bodyEl = document.getElementById('parentModalBody');
const sid = parentBtn.dataset.studentId || '';
if (!sid) {
if (bodyEl) bodyEl.innerHTML = '<div class="alert alert-warning mb-0">Missing student id for this row.</div>';
if (window.bootstrap) new bootstrap.Modal(modalEl).show();
return;
}
bodyEl.innerHTML = `
<div class="text-center text-muted my-4">
<div class="spinner-border" role="status" aria-hidden="true"></div>
<div class="mt-2">Loading parent info…</div>
</div>`;
if (window.bootstrap) new bootstrap.Modal(modalEl).show();
let url = parentBtn.dataset.fetchUrl;
if (!url || !url.includes('student_id=')) {
url = '<?= site_url('attendance/parents-info') ?>?student_id=' + encodeURIComponent(sid);
}
fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
.then(async (r) => {
let data = null;
try { data = await r.json(); } catch (e) {}
if (!r.ok) throw new Error((data && data.error) ? data.error : ('HTTP ' + r.status));
return data;
})
.then((data) => renderParentsModal(data))
.catch((err) => {
bodyEl.innerHTML = '<div class="alert alert-danger mb-0">Unable to load parent info: ' + esc(err?.message || err) + '</div>';
});
});
// Compute offset if a fixed/sticky top navbar exists to prevent overlap
function getFixedHeaderOffset() {
let total = 0;
const stack = [];
const header = document.querySelector('header.navbar.sticky-top, header.navbar.fixed-top');
if (header) stack.push(header);
const mgmt = document.getElementById('navbarManagement');
if (mgmt && (mgmt.classList.contains('sticky-top') || mgmt.classList.contains('fixed-top'))) stack.push(mgmt);
document.querySelectorAll('.navbar.sticky-top, .navbar.fixed-top').forEach(el => { if (!stack.includes(el)) stack.push(el); });
stack.forEach(el => { const h = el.offsetHeight || el.getBoundingClientRect().height || 0; total += Math.max(0, Math.round(h)); });
return total;
}
// ===== DataTables init (with FixedHeader) =====
function initDataTable() {
if (!window.jQuery || !jQuery.fn || !jQuery.fn.DataTable) return false;
const $ = jQuery;
const table = $('#notifiedTable');
if (!table.length) return true;
// Column indexes after header changes:
// 0 Student, 1 Grade, 2 Last Incident, 3 Reason, 4 Note, 5 Notified On, 6 Parent, 7 Details
table.DataTable({
paging: true,
pageLength: 25,
lengthMenu: [[10,25,50,100,-1],[10,25,50,100,'All']],
ordering: true,
order: [[2, 'desc'], [0, 'asc']], // Last Incident desc then Student asc
searching: true,
info: true,
responsive: true,
autoWidth: false,
columnDefs: [
{ targets: [6, 7], orderable: false, searchable: false }, // Parent, Details
{ targets: [4], orderable: false } // Note not sortable
],
fixedHeader: {
header: true,
headerOffset: getFixedHeaderOffset()
},
dom: "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: {
search: "Quick search:",
lengthMenu: "Show _MENU_ rows",
info: "Showing _START__END_ of _TOTAL_",
infoEmpty: "No rows to show",
zeroRecords: "No matching records",
paginate: { previous: "", next: "" }
}
});
return true;
}
function ensureDataTablesThenInit() {
const head = document.head;
function loadScript(src, id) {
return new Promise((resolve, reject) => {
if (id && document.getElementById(id)) return resolve();
const s = document.createElement('script');
if (id) s.id = id;
s.src = src;
s.onload = resolve;
s.onerror = reject;
head.appendChild(s);
});
}
function loadCss(href, id) {
return new Promise((resolve) => {
if (id && document.getElementById(id)) return resolve();
const l = document.createElement('link');
if (id) l.id = id;
l.rel = 'stylesheet';
l.href = href;
l.onload = resolve;
head.appendChild(l);
});
}
const hasDT = !!(window.jQuery && jQuery.fn && jQuery.fn.DataTable);
const hasFH = !!(hasDT && jQuery.fn.dataTable && jQuery.fn.dataTable.FixedHeader);
if (hasDT && hasFH) {
initDataTable();
return;
}
const tasks = [];
if (!hasDT) {
if (!window.jQuery) tasks.push(loadScript('https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js', 'jq-core'));
tasks.push(loadScript('https://cdn.jsdelivr.net/npm/datatables.net@1.13.10/js/jquery.dataTables.min.js', 'dt-core'));
tasks.push(loadScript('https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.10/js/dataTables.bootstrap5.min.js', 'dt-bs5'));
tasks.push(loadCss('https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.10/css/dataTables.bootstrap5.min.css', 'dt-bs5-css'));
}
if (!hasFH) {
tasks.push(loadScript('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader@3.4.0/js/dataTables.fixedHeader.min.js', 'dt-fixedheader'));
tasks.push(loadCss('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader-bs5@3.4.0/css/fixedHeader.bootstrap5.min.css', 'dt-fixedheader-css'));
}
Promise.all(tasks)
.then(() => setTimeout(initDataTable, 0))
.catch(() => console.warn('DataTables assets failed to load; table will render without enhancements.'));
}
document.addEventListener('DOMContentLoaded', ensureDataTablesThenInit);
})();
// ---------- Add/Edit Note Modal ----------
document.addEventListener('click', function(e) {
const btn = e.target.closest('.add-note-btn');
if (!btn) return;
const modalEl = document.getElementById('notifyCommentModal');
if (!modalEl) return;
// Populate fields
document.getElementById('noteStudentId').value = btn.dataset.studentId || '';
document.getElementById('noteStudentName').textContent = btn.dataset.studentName || '—';
document.getElementById('noteCode').textContent = btn.dataset.code || '—';
document.getElementById('noteCodeInput').value = btn.dataset.code || '';
document.getElementById('noteIncidentDate').value= btn.dataset.incidentDate || '';
document.getElementById('noteDate').textContent = btn.dataset.incidentDate || '—';
document.getElementById('noteReturnUrl').value = btn.dataset.returnUrl || '<?= current_url() ?>';
document.getElementById('note').value = btn.dataset.note || '';
// Optional: clear extra meta
document.getElementById('noteTo').value = '';
document.getElementById('noteSubject').value = '';
document.getElementById('noteVariant').value = '';
if (window.bootstrap) {
new bootstrap.Modal(modalEl).show();
} else {
modalEl.classList.add('show');
}
});
(function autoOpenCommentModal(){
<?php $cm = session('open_comment_modal'); ?>
<?php if ($cm): ?>
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'd-none add-note-btn';
btn.dataset.studentId = '<?= (int)($cm['student_id'] ?? 0) ?>';
btn.dataset.studentName = '<?= esc($cm['student_name'] ?? '', 'js') ?>';
btn.dataset.code = '<?= esc($cm['code'] ?? '', 'js') ?>';
btn.dataset.incidentDate = '<?= esc(substr((string)($cm['incident_date'] ?? ''), 0, 10), 'js') ?>';
btn.dataset.note = '';
btn.dataset.returnUrl = '<?= current_url() ?>';
document.body.appendChild(btn);
btn.click();
document.body.removeChild(btn);
<?php session()->remove('open_comment_modal'); ?>
<?php endif; ?>
})();
</script>
<!-- Shared Parent Modal -->
<div class="modal fade" id="parentModal" tabindex="-1" aria-labelledby="parentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="parentModalLabel">Parent Information</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="parentModalBody">
<div class="text-center text-muted my-4">
<div class="spinner-border" role="status" aria-hidden="true"></div>
<div class="mt-2">Loading parent info…</div>
</div>
</div>
</div>
<div class="modal-footer justify-content-between">
<div class="small text-muted">First &amp; Second contacts are shown if available.</div>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
+579
View File
@@ -0,0 +1,579 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php
$page_title = $title ?? 'Pending Attendance Violations';
$school_year = $school_year ?? '';
$semester = $semester ?? '';
$show_actions = true; // pending page shows actions
?>
<div class="container-fluid px-0">
<h2 class="text-center mt-4 mb-3"><?= esc($page_title) ?></h2>
<div class="d-flex justify-content-center mb-2">
<div class="col-12 col-lg-10">
<?= $this->include('partials/academic_filter') ?>
</div>
</div>
<?php if ($msg = session()->getFlashdata('message')): ?>
<div class="alert alert-success mx-3"><?= esc($msg) ?></div>
<?php endif; ?>
<?php if ($err = session()->getFlashdata('error')): ?>
<div class="alert alert-danger mx-3"><?= esc($err) ?></div>
<?php endif; ?>
<div class="row g-0">
<div class="col-12">
<div class="card border-0 rounded-3 shadow-sm">
<div class="card-header d-flex flex-wrap gap-2 justify-content-between align-items-center">
<h5 class="mt-2 mb-0">Pending Attendance Violations</h5>
<div class="d-flex flex-wrap gap-2 align-items-center">
<span class="badge bg-primary">School Year: <?= esc($school_year) ?></span>
<?php if ($semester !== ''): ?>
<span class="badge bg-secondary">Semester: <?= esc($semester) ?></span>
<?php endif; ?>
<a href="<?= site_url('attendance/violations/notified') ?>" class="btn btn-outline-dark btn-sm">
<i class="fas fa-check-circle"></i> View Notified
</a>
</div>
</div>
<div class="card-body">
<div id="autoEmailAlert" class="alert d-none mb-3" role="alert"></div>
<!-- Legend -->
<div class="d-flex flex-wrap gap-3 mb-3 small">
<span><span class="badge rounded-pill" style="background-color:#e6f7ff;color:#000;">&nbsp;&nbsp;</span> 1 Absence — Auto Email</span>
<span><span class="badge rounded-pill" style="background-color:#fadb14;color:#000;">&nbsp;&nbsp;</span> 2 Absences (row/≤3 weeks) — Team Notify</span>
<span><span class="badge rounded-pill" style="background-color:#fa8c16;">&nbsp;&nbsp;</span> 3 Absences (row/≤4 weeks) — Team Notify</span>
<span><span class="badge rounded-pill" style="background-color:#ff4d4f;">&nbsp;&nbsp;</span> 4 Absences (row/≤5 weeks) — Expel Notify / 4 Lates — Team Notify</span>
<span><span class="badge rounded-pill" style="background-color:#bae7ff;color:#000;">&nbsp;&nbsp;</span> 2 Lates (row/≤3 weeks) — Auto Email</span>
<span><span class="badge rounded-pill" style="background-color:#002766;">&nbsp;&nbsp;</span> 3 Lates or 2L+1A (≤4 weeks) — Team Notify</span>
</div>
<?php if (empty($students)): ?>
<div class="alert alert-info mb-2">No attendance violations found.</div>
<?php if (!empty($debug ?? null)): ?>
<div class="alert alert-secondary small">
<strong>Debug</strong>
<div>School Year: <?= esc($debug['school_year_param'] ?? '') ?> | Semester: <?= esc($debug['semester_param'] ?? '') ?></div>
<div>Class students: <?= esc($debug['class_students'] ?? 0) ?> | Student IDs: <?= esc($debug['student_ids'] ?? 0) ?></div>
<div>Attendance rows (raw/filtered): <?= esc($debug['attendance_rows'] ?? 0) ?>/<?= esc($debug['filtered_rows'] ?? 0) ?></div>
<div>Violations computed: <?= esc($debug['violations'] ?? 0) ?></div>
<div>Active weeks: <?= esc($debug['active_weeks'] ?? 0) ?> | Students with attendance: <?= esc($debug['by_student'] ?? 0) ?></div>
<div>Absences last 5 weeks: <?= esc($debug['abs_last5'] ?? 0) ?> | Lates last 5 weeks: <?= esc($debug['late_last5'] ?? 0) ?></div>
</div>
<?php endif; ?>
<?php else: ?>
<div class="table-responsive">
<table id="violationsTable" class="table table-striped table-hover align-middle w-100">
<thead class="table-dark">
<tr>
<th>Student</th>
<th>Grade</th>
<th>Violation</th>
<th>Type</th>
<th class="text-center">Abs</th>
<th class="text-center">Late</th>
<th>Last Incident</th>
<th>Action</th>
<th>Notified?</th>
<th>#</th>
<th>Parent</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $st): ?>
<?php
// Robust ID + name (supports multiple shapes)
$studId = (int)($st['id'] ?? $st['student_id'] ?? 0);
$studName =
($st['name'] ?? null)
?? ($st['student_name'] ?? null)
?? trim(($st['firstname'] ?? $st['first_name'] ?? '') . ' ' . ($st['lastname'] ?? $st['last_name'] ?? ''));
if ($studName === '' || $studName === null) {
$studName = $studId ? ('Student #' . $studId) : '—';
}
$grade = $st['class_name'] ?? '';
// Violation data (always present in pending view)
$viTitle = (string)($st['violation'] ?? '');
$viCode = (string)($st['violation_code'] ?? '');
$viType = (string)($st['type'] ?? '');
$viColor = (string)($st['color'] ?? '#6c757d');
$action = (string)($st['action'] ?? '');
$absences = (array)($st['absences'] ?? []);
$lates = (array)($st['lates'] ?? []);
$cntAbs = count($absences);
$cntLate = count($lates);
$lastDate = $st['last_date'] ?? null;
$incidentYmd = $lastDate ? substr((string)$lastDate, 0, 10) : '';
$incidentParam = $incidentYmd !== ''
? '&incident_date=' . urlencode($incidentYmd)
: '';
// Notified meta (may be 0/empty on pending view)
$notified = !empty($st['is_notified']);
$notifCnt = (int)($st['notif_counter'] ?? 0);
// Parent display info (optional)
$parentEmail = $st['parent_email'] ?? '';
$parentName = $st['parent_name'] ?? '';
$parentLabel = $parentName ?: ($parentEmail ?: 'View');
// Badge contrast
$isLight = in_array(strtolower($viColor), ['#bae7ff', '#e6f7ff', '#fadb14'], true);
$badgeCls = 'badge rounded-pill ' . ($isLight ? 'text-dark' : 'text-light');
$badgeCss = 'background-color:' . esc($viColor) . ';';
$subjectType = ($viType === 'late') ? 'Late' : (($viType === 'absence') ? 'Absent' : 'Attendance');
?>
<tr style="border-left:6px solid <?= esc($viColor) ?>;">
<td>
<a
href="<?= site_url('family') . '?student_id=' . (int)$studId ?>"
class="text-decoration-none"
data-family-student-id="<?= (int)$studId ?>"
title="Open Family Card">
<?= esc($studName) ?>
</a>
</td>
<td><?= esc($grade) ?></td>
<td><span class="<?= esc($badgeCls) ?>" style="<?= $badgeCss ?>"><?= esc($viTitle) ?></span></td>
<td class="text-capitalize"><?= esc($viType ?: '—') ?></td>
<td class="text-center"><?= esc($cntAbs) ?></td>
<td class="text-center"><?= esc($cntLate) ?></td>
<td data-order="<?= $incidentYmd !== '' ? esc($incidentYmd) : '' ?>">
<?= $incidentYmd !== '' ? esc(date('m-d-Y', strtotime($incidentYmd))) : '—' ?>
</td>
<td>
<?php if ($action === 'auto_email'): ?>
<span class="badge bg-primary">Auto Email</span>
<?php elseif ($action === 'expel_notify'): ?>
<span class="badge bg-danger">Expel Notify</span>
<?php else: ?>
<span class="badge bg-warning text-dark">Team Notify</span>
<?php endif; ?>
</td>
<td>
<span class="badge <?= $notified ? 'bg-success' : 'bg-warning text-dark' ?>">
<?= $notified ? 'Yes' : 'No' ?>
</span>
</td>
<td><?= esc($notifCnt) ?></td>
<!-- Parent column -->
<td>
<a
href="<?= site_url('family') . '?student_id=' . (int)$studId ?>"
class="text-decoration-none"
data-family-student-id="<?= (int)$studId ?>"
title="Open Family Card">
<?= esc($parentLabel) ?>
</a>
<?php if (!empty($parentEmail)): ?>
<a class="ms-2" href="mailto:<?= esc($parentEmail) ?>" title="Email First parent">
<i class="fas fa-envelope"></i>
</a>
<?php endif; ?>
</td>
<td class="text-nowrap">
<a href="<?= site_url('attendance/view/' . $studId) . '?code=' . urlencode($viCode) . $incidentParam ?>" class="btn btn-sm btn-info">
<i class="fas fa-eye"></i> Details
</a>
<?php if ($action === 'auto_email'): ?>
<a href="<?= site_url(
'attendance/compose?student_id=' . $studId .
'&code=' . urlencode($viCode) .
'&variant=default' .
$incidentParam
) ?>" class="btn btn-sm btn-primary ms-1">
<i class="fas fa-paper-plane"></i> Compose Auto Email
</a>
<?php else: ?>
<div class="btn-group ms-1">
<a href="<?= site_url(
'attendance/compose?student_id=' . $studId .
'&code=' . urlencode($viCode) .
'&variant=answered' .
$incidentParam
) ?>" class="btn btn-sm btn-success">
<i class="fas fa-phone-alt"></i> Compose (Answered)
</a>
<a href="<?= site_url(
'attendance/compose?student_id=' . $studId .
'&code=' . urlencode($viCode) .
'&variant=no_answer' .
$incidentParam
) ?>" class="btn btn-sm btn-warning">
<i class="fas fa-voicemail"></i> Compose (No Answer)
</a>
</div>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Hidden POST for notify (kept for batch CSRF or future single-row actions) -->
<form id="notifyForm" action="<?= site_url('attendance/record') ?>" method="post" class="d-none">
<?= csrf_field() ?>
<input type="hidden" name="student_id">
<input type="hidden" name="date">
<input type="hidden" name="subject_type">
<input type="hidden" name="parent_email">
<input type="hidden" name="parent_name">
<input type="hidden" name="semester" value="<?= esc($semester) ?>">
<input type="hidden" name="school_year" value="<?= esc($school_year) ?>">
<input type="hidden" name="return_url" value="<?= esc(current_url()) ?>">
</form>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
(function() {
// Basic HTML escape util
function esc(s) {
return String(s ?? '')
.replaceAll('&', '&amp;').replaceAll('<', '&lt;')
.replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", "&#039;");
}
function parentCard(p, label) {
if (!p) return '';
const name = esc(p.name || '');
const email = esc(p.email || '');
const phone = esc(p.phone || '');
return `
<div class="col-12 col-md-6">
<div class="card shadow-sm mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>${esc(label)} Parent</strong>
${email ? `<a class="btn btn-sm btn-outline-primary" href="mailto:${email}">
<i class="fas fa-envelope"></i> Email</a>` : ''}
</div>
<div class="card-body">
<div class="mb-1"><span class="text-muted">Name:</span> ${name || '—'}</div>
<div class="mb-1"><span class="text-muted">Email:</span> ${email || '—'}</div>
<div class="mb-1"><span class="text-muted">Phone:</span> ${phone || '—'}</div>
</div>
</div>
</div>
`;
}
function renderParentsModal(data) {
const body = document.getElementById('parentModalBody');
if (!data || (!data.primary && !data.secondary)) {
body.innerHTML = `<div class="alert alert-warning mb-0">No parent information available for this student.</div>`;
return;
}
const primary = data.primary ? {
name: (data.primary.firstname ? `${data.primary.firstname} ${data.primary.lastname||''}`.trim() : (data.primary.name || '')),
email: data.primary.email || '',
phone: data.primary.cellphone || data.primary.phone || ''
} : null;
const secondary = data.secondary ? {
name: (data.secondary.firstname ? `${data.secondary.firstname} ${data.secondary.lastname||''}`.trim() : (data.secondary.name || '')),
email: data.secondary.email || '',
phone: data.secondary.cellphone || data.secondary.phone || ''
} : null;
body.innerHTML = `<div class="row">${primary ? parentCard(primary, 'First') : ''}${secondary ? parentCard(secondary, 'Second') : ''}</div>`;
}
// Parent modal open/fetch
document.addEventListener('click', function(e) {
const parentBtn = e.target.closest('.show-parents');
if (!parentBtn) return;
e.preventDefault();
const sid = parentBtn.dataset.studentId || '';
const modalEl = document.getElementById('parentModal');
const bodyEl = document.getElementById('parentModalBody');
if (!sid) {
if (bodyEl) bodyEl.innerHTML = '<div class="alert alert-warning mb-0">Missing student id for this row.</div>';
if (window.bootstrap) new bootstrap.Modal(modalEl).show();
return;
}
bodyEl.innerHTML = `
<div class="text-center text-muted my-4">
<div class="spinner-border" role="status" aria-hidden="true"></div>
<div class="mt-2">Loading parent info…</div>
</div>`;
if (window.bootstrap) new bootstrap.Modal(modalEl).show();
let url = parentBtn.dataset.fetchUrl;
if (!url || !url.includes('student_id=')) {
url = '<?= site_url('attendance/parents-info') ?>?student_id=' + encodeURIComponent(sid);
}
fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(async (r) => {
let data = null;
try {
data = await r.json();
} catch (e) {}
if (!r.ok) throw new Error((data && data.error) ? data.error : ('HTTP ' + r.status));
return data;
})
.then((data) => renderParentsModal(data))
.catch((err) => {
bodyEl.innerHTML = '<div class="alert alert-danger mb-0">Unable to load parent info: ' + esc(err?.message || err) + '</div>';
});
});
// ===== Helpers for FixedHeader =====
function getFixedHeaderOffset() {
let total = 0;
const stack = [];
const header = document.querySelector('header.navbar.sticky-top, header.navbar.fixed-top');
if (header) stack.push(header);
const mgmt = document.getElementById('navbarManagement');
if (mgmt && (mgmt.classList.contains('sticky-top') || mgmt.classList.contains('fixed-top'))) stack.push(mgmt);
document.querySelectorAll('.navbar.sticky-top, .navbar.fixed-top').forEach(el => { if (!stack.includes(el)) stack.push(el); });
stack.forEach(el => { const h = el.offsetHeight || el.getBoundingClientRect().height || 0; total += Math.max(0, Math.round(h)); });
return total;
}
// ===== DataTables init (with FixedHeader) =====
function initDataTable() {
if (!window.jQuery || !jQuery.fn || !jQuery.fn.DataTable) return false;
const $ = jQuery;
const table = $('#violationsTable');
if (!table.length) return true;
// Column indexes:
// 0 Student, 1 Grade, 2 Violation, 3 Type, 4 Abs, 5 Late, 6 Last Incident,
// 7 Action, 8 Notified?, 9 #, 10 Parent, 11 Actions
table.DataTable({
paging: true,
pageLength: 25,
lengthMenu: [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, 'All']
],
ordering: true,
order: [
[6, 'desc'],
[0, 'asc']
], // Last Incident desc, then Student asc
searching: true,
info: true,
responsive: true,
autoWidth: false,
columnDefs: [{
targets: [4, 5, 9],
className: 'text-center'
},
{
targets: [7, 8, 9, 10, 11],
orderable: false
}, // actions / meta not meaningful to sort
{
targets: [7, 8, 9, 10, 11],
searchable: false
} // and not useful for search
],
// Keep the Bootstrap look & feel
dom: "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
language: {
search: "Quick search:",
lengthMenu: "Show _MENU_ rows",
info: "Showing _START__END_ of _TOTAL_",
infoEmpty: "No rows to show",
zeroRecords: "No matching students found",
paginate: {
previous: "",
next: ""
}
},
fixedHeader: {
header: true,
headerOffset: getFixedHeaderOffset()
}
});
return true;
}
// Ensure DT core + FixedHeader are present; then init
function ensureDataTablesThenInit() {
const head = document.head;
function loadScript(src, id) {
return new Promise((resolve, reject) => {
if (id && document.getElementById(id)) return resolve();
const s = document.createElement('script');
if (id) s.id = id;
s.src = src;
s.onload = resolve;
s.onerror = reject;
head.appendChild(s);
});
}
function loadCss(href, id) {
return new Promise((resolve) => {
if (id && document.getElementById(id)) return resolve();
const l = document.createElement('link');
if (id) l.id = id;
l.rel = 'stylesheet';
l.href = href;
l.onload = resolve;
head.appendChild(l);
});
}
const hasDT = !!(window.jQuery && jQuery.fn && jQuery.fn.DataTable);
const hasFH = !!(hasDT && jQuery.fn.dataTable && jQuery.fn.dataTable.FixedHeader);
if (hasDT && hasFH) {
initDataTable();
return;
}
const tasks = [];
if (!hasDT) {
// Core + Bootstrap CSS
if (!window.jQuery) tasks.push(loadScript('https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js', 'jq-core'));
tasks.push(loadScript('https://cdn.jsdelivr.net/npm/datatables.net@1.13.10/js/jquery.dataTables.min.js', 'dt-core'));
tasks.push(loadScript('https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.10/js/dataTables.bootstrap5.min.js', 'dt-bs5'));
tasks.push(loadCss('https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.10/css/dataTables.bootstrap5.min.css', 'dt-bs5-css'));
}
if (!hasFH) {
tasks.push(loadScript('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader@3.4.0/js/dataTables.fixedHeader.min.js', 'dt-fixedheader'));
tasks.push(loadCss('https://cdn.jsdelivr.net/npm/datatables.net-fixedheader-bs5@3.4.0/css/fixedHeader.bootstrap5.min.css', 'dt-fixedheader-css'));
}
Promise.all(tasks)
.then(() => setTimeout(initDataTable, 0))
.catch(() => console.warn('DataTables assets failed to load; table will render without enhancements.'));
}
document.addEventListener('DOMContentLoaded', ensureDataTablesThenInit);
})();
</script>
<!-- Parent Info Modal -->
<div class="modal fade" id="parentModal" tabindex="-1" aria-labelledby="parentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="parentModalLabel">Parent Information</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="parentModalBody">
<div class="text-center text-muted my-4">
<div class="spinner-border" role="status" aria-hidden="true"></div>
<div class="mt-2">Loading parent info…</div>
</div>
</div>
</div>
<div class="modal-footer justify-content-between">
<div class="small text-muted">First &amp; Second contacts are shown if available.</div>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<?php $cm = session('open_comment_modal'); ?>
<?php if ($cm): ?>
<!-- Add Comment/Note Modal -->
<div class="modal fade" id="notifyCommentModal" tabindex="-1" aria-labelledby="notifyCommentModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form method="post" action="<?= site_url('attendance/save-note') ?>" class="modal-content">
<?= csrf_field() ?>
<div class="modal-header">
<h5 class="modal-title" id="notifyCommentModalLabel">Add Comment / Note</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<?php
$cmDateFmt = substr((string)($cm['incident_date'] ?? ''), 0, 10);
$cmName = (string)($cm['student_name'] ?? '');
if ($cmName === '' && !empty($cm['student_id'])) {
$cmName = 'Student #' . (int) $cm['student_id'];
}
?>
<div class="modal-body">
<div class="mb-2 small text-muted">
<div><strong>Student Name:</strong> <?= esc($cmName) ?></div>
<div><strong>Code:</strong> <?= esc($cm['code']) ?> &nbsp; <strong>Date:</strong> <?= esc($cmDateFmt) ?></div>
<?php if (!empty($cm['to'])): ?>
<div><strong>To:</strong> <?= esc($cm['to']) ?></div>
<?php endif; ?>
<?php if (!empty($cm['subject'])): ?>
<div><strong>Subject:</strong> <?= esc($cm['subject']) ?></div>
<?php endif; ?>
</div>
<div class="mb-3">
<label for="note" class="form-label">Comment / Note</label>
<textarea id="note" name="note" class="form-control" rows="5" required placeholder="Type any follow-up, call outcome, or special circumstances..."></textarea>
</div>
<!-- Hidden fields to carry context back -->
<input type="hidden" name="student_id" value="<?= esc($cm['student_id']) ?>">
<input type="hidden" name="code" value="<?= esc($cm['code']) ?>">
<input type="hidden" name="incident_date" value="<?= esc($cmDateFmt) ?>">
<input type="hidden" name="to" value="<?= esc($cm['to'] ?? '') ?>">
<input type="hidden" name="subject" value="<?= esc($cm['subject'] ?? '') ?>">
<input type="hidden" name="variant" value="<?= esc($cm['variant'] ?? '') ?>">
<input type="hidden" name="return_url" value="<?= current_url() ?>">
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">
<i class="bi bi-save"></i> Save Note
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var modalEl = document.getElementById('notifyCommentModal');
if (modalEl && window.bootstrap) {
new bootstrap.Modal(modalEl).show();
}
});
</script>
<?php endif; ?>
<?= $this->endSection() ?>
+232
View File
@@ -0,0 +1,232 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid"
data-template-endpoint="<?= esc($templateEndpoint ?? site_url('api/attendance-templates')) ?>"
data-csrf-name="<?= esc(csrf_token()) ?>"
data-csrf-value="<?= esc(csrf_hash()) ?>">
<div class="wrapper">
<div class="text-center mb-4">
<h2 class="text-center mt-4 mb-3">Attendance Comment Templates</h2>
</div>
<div class="mb-4">
<button id="addNewBtn" class="btn btn-success">Add New Template</button>
</div>
<div class="table-responsive">
<table id="templatesTable" class="display table table-striped align-middle">
<thead>
<tr>
<th>ID</th>
<th>Min Score</th>
<th>Max Score</th>
<th>Template Text</th>
<th>Active</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="6" class="text-center text-muted">Loading templates...</td>
</tr>
</tbody>
</table>
</div>
<div class="modal fade" id="editTemplateModal" tabindex="-1" aria-labelledby="editTemplateModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form id="editTemplateForm" class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editTemplateModalLabel">Edit Template</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id" id="template_id">
<div class="mb-3">
<label for="min_score" class="form-label">Min Score:</label>
<input type="number" name="min_score" id="min_score" class="form-control" required>
</div>
<div class="mb-3">
<label for="max_score" class="form-label">Max Score:</label>
<input type="number" name="max_score" id="max_score" class="form-control" required>
</div>
<div class="mb-3">
<label for="template_text" class="form-label">Template Text:</label>
<textarea name="template_text" id="template_text" class="form-control" required></textarea>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="is_active" id="is_active" value="on">
<label class="form-check-label" for="is_active">
Active
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('[data-template-endpoint]');
if (!container) return;
const endpoint = container.dataset.templateEndpoint;
const csrfName = container.dataset.csrfName;
let csrfValue = container.dataset.csrfValue;
const addNewBtn = document.getElementById('addNewBtn');
const tableBody = document.querySelector('#templatesTable tbody');
const editModalElement = document.getElementById('editTemplateModal');
const editForm = document.getElementById('editTemplateForm');
const editModal = new window.bootstrap.Modal(editModalElement);
let templates = [];
const safe = (value, fallback = '') =>
value === undefined || value === null ? fallback : value;
const fetchTemplates = async () => {
tableBody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">Loading templates...</td></tr>';
try {
const response = await fetch(endpoint, {
headers: { 'Accept': 'application/json' },
});
if (!response.ok) throw new Error('Failed to fetch');
const payload = await response.json();
templates = payload.templates || [];
renderTemplates();
} catch (error) {
console.error(error);
tableBody.innerHTML = '<tr><td colspan="6" class="text-center text-danger">Failed to load templates.</td></tr>';
}
};
const renderTemplates = () => {
if (window.jQuery.fn.DataTable.isDataTable('#templatesTable')) {
window.jQuery('#templatesTable').DataTable().destroy();
}
tableBody.innerHTML = '';
if (templates.length === 0) {
tableBody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">No templates found.</td></tr>';
return;
}
templates.forEach(template => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${safe(template.id)}</td>
<td>${safe(template.min_score)}</td>
<td>${safe(template.max_score)}</td>
<td>${safe(template.template_text)}</td>
<td>${template.is_active ? 'Yes' : 'No'}</td>
<td class="d-flex gap-1"></td>
`;
const actionsCell = row.querySelector('td:last-child');
const editButton = document.createElement('button');
editButton.className = 'btn btn-sm btn-warning';
editButton.textContent = 'Edit';
editButton.onclick = () => openEditModal(template);
actionsCell.appendChild(editButton);
const deleteButton = document.createElement('button');
deleteButton.className = 'btn btn-sm btn-danger';
deleteButton.textContent = 'Delete';
deleteButton.onclick = () => deleteTemplate(template.id);
actionsCell.appendChild(deleteButton);
tableBody.appendChild(row);
});
window.jQuery('#templatesTable').DataTable({
pageLength: 25,
lengthMenu: [10, 25, 50, 100],
});
};
const openEditModal = (template = {}) => {
editForm.reset();
editForm.querySelector('#template_id').value = template.id || '';
editForm.querySelector('#min_score').value = template.min_score || '';
editForm.querySelector('#max_score').value = template.max_score || '';
editForm.querySelector('#template_text').value = template.template_text || '';
editForm.querySelector('#is_active').checked = template.is_active || false;
editModal.show();
};
addNewBtn.addEventListener('click', () => openEditModal());
editForm.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(editForm);
const data = Object.fromEntries(formData.entries());
data[csrfName] = csrfValue;
try {
const response = await fetch(`${endpoint}/save`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: new URLSearchParams(data).toString()
});
const result = await response.json();
if (response.ok && result.status === 'success') {
editModal.hide();
await fetchTemplates(); // Refresh table
} else {
alert('Failed to save template.');
}
} catch (error) {
console.error('Save error:', error);
alert('An error occurred while saving.');
}
});
const deleteTemplate = async (id) => {
if (!confirm('Are you sure you want to delete this template?')) return;
const data = new URLSearchParams();
data.append(csrfName, csrfValue);
data.append('id', id);
try {
const response = await fetch(`${endpoint}/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: data
});
const result = await response.json();
if (response.ok && result.status === 'success') {
await fetchTemplates(); // Refresh table
} else {
alert('Failed to delete template.');
}
} catch (error) {
console.error('Delete error:', error);
alert('An error occurred while deleting.');
}
};
fetchTemplates();
});
</script>
<?= $this->endSection() ?>
+71
View File
@@ -0,0 +1,71 @@
<?= $this->extend('layout/register_layout') ?>
<?= $this->section('content') ?>
<div class="registration-form container mt-5 mb-5">
<div class="row justify-content-center">
<!-- Error Message -->
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger text-center">
<?= esc(session()->getFlashdata('error')) ?>
</div>
<?php endif; ?>
<form id="roleForm" method="post" action="<?= base_url('/set-role') ?>">
<?= csrf_field() ?>
<div class="text-center mb-4">
<a href="<?= base_url('/') ?>">
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 180px; height: 120px;">
</a>
</div>
<h3 class="text-center text-success" style="font-family: Arial, sans-serif;">
Select Your Role to Log In
</h3>
<br>
<input type="hidden" name="selected_role" id="selected_role">
<div class="d-flex flex-wrap justify-content-center gap-3 mt-3">
<?php foreach ($roles as $role): ?>
<div class="mb-3 d-grid">
<button type="button"
class="btn btn-lg btn-success item"
data-role="<?= esc(strtolower($role)) ?>"
data-bs-toggle="tooltip"
title="Log in as <?= esc(ucwords($role)) ?>">
<?= esc(ucwords($role)) ?>
</button>
</div>
<?php endforeach; ?>
</div>
</form>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('roleForm');
const roleInput = document.getElementById('selected_role');
const buttons = document.querySelectorAll('button[data-role]');
buttons.forEach(button => {
button.addEventListener('click', function() {
const selectedRole = this.getAttribute('data-role');
roleInput.value = selectedRole;
form.submit();
});
});
// Enable Bootstrap tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach(el => new bootstrap.Tooltip(el));
});
</script>
<?= $this->endSection() ?>
+86
View File
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calendar</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css">
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Favicon -->
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
<link rel="stylesheet" href="<?= base_url('assets/css/custom.css') ?>">
</head>
<body>
<?php include(__DIR__ . '/partials/header.php'); ?>
<?php include(__DIR__ . '/partials/navbar.php'); ?>
<div class="container-fluid">
<div class="main-content">
<div id="calendar-container">
<div id="calendar"></div>
</div>
</div>
</div>
<?php include(__DIR__ . '/partials/footer.php'); ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
const calendarEl = document.getElementById('calendar');
const isMobile = window.innerWidth <= 576;
const calendar = new FullCalendar.Calendar(calendarEl, {
initialView: isMobile ? 'listWeek' : 'dayGridMonth',
height: 'auto',
contentHeight: 'auto',
expandRows: true,
events: <?= json_encode($events) ?>,
eventDidMount: function(info) {
if (info.event.extendedProps.description) {
$(info.el).tooltip({
title: info.event.extendedProps.description,
placement: 'top',
trigger: 'hover',
container: 'body'
});
}
},
eventContent: function(arg) {
const event = arg.event;
const bg = event.extendedProps.backgroundColor || '#3788d8';
return {
html: `
<div style="
background-color: ${bg};
padding: 4px;
border-radius: 4px;
color: #fff;
font-size: 0.8rem;
text-align: center;">
${event.title}
</div>`
};
},
windowResize: function(view) {
if (window.innerWidth <= 576 && calendar.view.type !== 'listWeek') {
calendar.changeView('listWeek');
} else if (window.innerWidth > 576 && calendar.view.type !== 'dayGridMonth') {
calendar.changeView('dayGridMonth');
}
calendar.updateSize();
}
});
calendar.render();
window.calendar = calendar;
});
</script>
</body>
</html>
+230
View File
@@ -0,0 +1,230 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Al Rahma Sunday School</title>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="" name="keywords">
<meta content="" name="description">
<!-- Favicon -->
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
<!-- Google Web Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;600&family=Inter:wght@600&family=Lobster+Two:wght@700&display=swap"
rel="stylesheet">
<!-- Icon Font Stylesheet -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css" rel="stylesheet">
<!-- Libraries Stylesheet -->
<link href="lib/animate/animate.min.css" rel="stylesheet">
<link href="lib/owlcarousel/assets/owl.carousel.min.css" rel="stylesheet">
<!-- Customized Bootstrap Stylesheet -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- Template Stylesheet -->
<link href="assets/css/style.css" rel="stylesheet">
</head>
<body>
<div class="container-xxl bg-white p-0">
<!-- Spinner Start -->
<div id="spinner"
class="show bg-white position-fixed translate-middle w-100 vh-100 top-50 start-50 d-flex align-items-center justify-content-center">
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<!-- Spinner End -->
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav mx-auto">
<a href="<?= base_url('/') ?>" class="nav-item nav-link">Home</a>
<a href="<?= base_url('/about') ?>" class="nav-item nav-link">About Us</a>
<a href="<?= base_url('/classes') ?>" class="nav-item nav-link">Classes</a>
<div class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle active" data-bs-toggle="dropdown">Pages</a>
<div class="dropdown-menu rounded-0 rounded-bottom border-0 shadow-sm m-0">
<a href="<?= base_url('/facility') ?>" class="dropdown-item">School Facilities</a>
<a href="team.html" class="dropdown-item">Popular Teachers</a>
<a href="call-to-action.html" class="dropdown-item active">Become A Teacher or Admins</a>
<a href="appointment.html" class="dropdown-item">Make Appointment</a>
<a href="<?= base_url('/testimonial') ?>" class="dropdown-item">Testimonial</a>
<a href="<?= base_url('/notFound') ?>" class="dropdown-item">404 Error</a>
</div>
</div>
<a href="<?= base_url('/contact') ?>" class="nav-item nav-link">Contact Us</a>
</div>
<a href="/register" class="btn btn-primary rounded-pill px-3 d-none d-lg-block">Register
<i class="fa fa-arrow-right ms-3"></i></a>
<a href="/login" class="btn btn-primary rounded-pill px-3 d-none d-lg-block">Login<i
class="fa fa-arrow-right ms-3"></i></a>
</div>
</nav>
<!-- Navbar End -->
<!-- Page Header End -->
<div class="container-xxl py-5 page-header position-relative mb-5">
<div class="container py-5">
<h1 class="display-2 text-white animated slideInDown mb-4">Become A Teacher or Admins</h1>
<nav aria-label="breadcrumb animated slideInDown">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Home</a></li>
<li class="breadcrumb-item"><a href="#">Pages</a></li>
<li class="breadcrumb-item text-white active" aria-current="page">Become A Teacher or Admins</li>
</ol>
</nav>
</div>
</div>
<!-- Page Header End -->
<!-- Call To Action Start -->
<div class="container-xxl py-5">
<div class="container">
<div class="bg-light rounded">
<div class="row g-0">
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.1s" style="min-height: 400px;">
<div class="position-relative h-100">
<img class="position-absolute w-100 h-100 rounded" src="images/call-to-action.jpg"
style="object-fit: cover;">
</div>
</div>
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.5s">
<div class="h-100 d-flex flex-column justify-content-center p-5">
<h1 class="mb-4">Become A Teacher or Admin</h1>
<p class="mb-4">Becoming a teacher or admin at our Sunday school is a rewarding opportunity to make a meaningful impact on young lives. As a teacher, you will inspire and guide children on their spiritual journey, fostering their growth and understanding of faith. As an admin, you will play a crucial role in supporting the school's operations and ensuring a smooth and effective learning environment. Join our dedicated team and contribute to a nurturing environment that shapes the future of our community.</p>
<a class="btn btn-primary py-3 px-5" href="/register">Get Started Now<i
class="fa fa-arrow-right ms-2"></i></a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Call To Action End -->
<!-- Footer Start -->
<div class="container-fluid bg-dark text-white-50 footer pt-5 mt-5 wow fadeIn" data-wow-delay="0.1s">
<div class="container py-5">
<div class="row g-5">
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Get In Touch</h3>
<p class="mb-2"><i class="fa fa-map-marker-alt me-3"></i>5 Courthouse Lane, Chelmsford, MA 01824
</p>
<p class="mb-2"><i class="fa fa-phone-alt me-3"></i>+1 978-364-0219
0</p>
<p class="mb-2"><i class="fa fa-envelope me-3"></i>alrahma.isgl@gmail.com
</p>
<div class="d-flex pt-2">
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-twitter"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-facebook-f"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-youtube"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-linkedin-in"></i></a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Quick Links</h3>
<a class="btn btn-link text-white-50" href="">About Us</a>
<a class="btn btn-link text-white-50" href="">Contact Us</a>
<a class="btn btn-link text-white-50" href="">Our Services</a>
<a class="btn btn-link text-white-50" href="">Privacy Policy</a>
<a class="btn btn-link text-white-50" href="">Terms & Condition</a>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Photo Gallery</h3>
<div class="row g-2 pt-2">
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-1.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-2.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-3.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-4.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-5.jpg" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src=""assets/images/classes-6.jpg" alt="">
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Newsletter</h3>
<p>Our newsletter is your gateway to staying informed and connected with our Sunday school community. </p>
<div class="position-relative mx-auto" style="max-width: 400px;">
<input class="form-control bg-transparent w-100 py-3 ps-4 pe-5" type="text"
placeholder="Your email">
<button type="button"
class="btn btn-primary py-2 position-absolute top-0 end-0 mt-2 me-2">SignUp</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="copyright">
<div class="row">
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
&copy; <a class="border-bottom" href="#">Al Rahma Sunday School by ISGL</a>, All Right
Reserved.
<!--/*** This template is free as long as you keep the footer authors credit link/attribution link/backlink. If you'd like to use the template without the footer authors credit link/attribution link/backlink, you can purchase the Credit Removal License from "https://htmlcodex.com/credit-removal". Thank you for your support. ***/-->
Designed By <a class="border-bottom" href="https://htmlcodex.com">HTML Codex</a>
</div>
<div class="col-md-6 text-center text-md-end">
<div class="footer-menu">
<a href="">Home</a>
<a href="">Cookies</a>
<a href="">Help</a>
<a href="">FQAs</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer End -->
<!-- Back to Top -->
<a href="#" class="btn btn-lg btn-primary btn-lg-square back-to-top"><i class="bi bi-arrow-up"></i></a>
</div>
<!-- JavaScript Libraries -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="lib/wow/wow.min.js"></script>
<script src="lib/easing/easing.min.js"></script>
<script src="lib/waypoints/waypoints.min.js"></script>
<script src="lib/owlcarousel/owl.carousel.min.js"></script>
<!-- Template Javascript -->
<script src="js/main.js"></script>
</body>
</html>
+225
View File
@@ -0,0 +1,225 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">Class Preparation</h2>
<!-- School Year Selector + Calculate Button -->
<form method="get" action="<?= site_url('class-prep') ?>" class="mb-4 d-flex align-items-center gap-2 flex-wrap">
<label for="school_year" class="form-label mb-0">School Year:</label>
<select name="school_year" id="school_year" class="form-select form-select-sm w-auto">
<?php for ($y = date('Y'); $y >= 2020; $y--): ?>
<?php $sy = $y . '-' . ($y + 1); ?>
<option value="<?= $sy ?>" <?= $schoolYear === $sy ? 'selected' : '' ?>><?= $sy ?></option>
<?php endfor; ?>
</select>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<label for="semester" class="form-label mb-0">Semester:</label>
<select name="semester" id="semester" class="form-select form-select-sm w-auto">
<option value="">—</option>
<option value="Fall" <?= (strcasecmp($semVal,'Fall')===0?'selected':'') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal,'Spring')===0?'selected':'') ?>>Spring</option>
</select>
<button class="btn btn-sm btn-primary">Calculate Items</button>
</form>
<!-- Class Cards -->
<div class="row g-3">
<?php foreach ($prepResults as $prep): ?>
<?php $adjMap = $prep['adjustments'] ?? []; ?>
<div class="col-12 col-lg-6">
<div class="card shadow-sm border-0 h-100">
<div class="card-header d-flex justify-content-between align-items-center bg-light border-0">
<div>
<div class="fw-semibold mb-1">
<?= esc($prep['class_section']) ?>
</div>
<?php if (!empty($prep['needs_print'])): ?>
<span class="badge bg-danger">Updated since last print</span>
<?php else: ?>
<span class="badge bg-success">Up-to-date</span>
<?php endif; ?>
<?php if (!empty($prep['last_printed_at'])): ?>
<small class="text-muted ms-2">Last printed: <?= esc($prep['last_printed_at']) ?></small>
<?php endif; ?>
</div>
<div class="small text-muted text-end">
Students:<br>
<span class="badge bg-secondary"><?= (int)$prep['student_count'] ?></span>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<!-- Prep Items -->
<div class="col-12 col-md-7">
<div class="fw-semibold mb-2">Prep Items</div>
<ul class="list-group list-group-flush small border rounded overflow-hidden">
<?php foreach (($prep['prep_items'] ?? []) as $item => $qty): ?>
<li class="list-group-item d-flex justify-content-between align-items-center <?= (int)$qty === 0 ? 'text-muted-50' : '' ?>">
<span><?= esc($item) ?></span>
<span class="badge <?= (int)$qty > 0 ? 'bg-primary' : 'bg-secondary' ?> rounded-pill">
<?= (int)$qty ?>
</span>
</li>
<?php endforeach; ?>
</ul>
</div>
<!-- Adjustments -->
<div class="col-12 col-md-5">
<div class="fw-semibold mb-2">Adjustments (Tables)</div>
<form method="post" action="<?= site_url('class-prep/save-adjustment') ?>" class="d-grid gap-2">
<?= csrf_field() ?>
<input type="hidden" name="class_section_id" value="<?= esc($prep['class_section_id']) ?>">
<input type="hidden" name="school_year" value="<?= esc($schoolYear) ?>">
<?php foreach ($adjMap as $item => $value): ?>
<div>
<label class="form-label mb-1 small text-muted d-block"><?= esc($item) ?></label>
<div class="input-group input-group-sm">
<button type="button" class="btn btn-outline-secondary" onclick="adjustQty(this, -1)" aria-label="Decrease <?= esc($item) ?>"></button>
<input type="number"
class="form-control text-center"
name="adjustments[<?= esc($item) ?>]"
value="<?= (int)$value ?>"
readonly>
<button type="button" class="btn btn-outline-secondary" onclick="adjustQty(this, 1)" aria-label="Increase <?= esc($item) ?>">+</button>
</div>
</div>
<?php endforeach; ?>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-sm btn-primary">Save</button>
</div>
</form>
</div>
</div>
</div>
<div class="card-footer bg-transparent border-0 d-flex justify-content-end">
<?php
$printUrl = site_url('class-prep/print/' . urlencode($prep['class_section_id']) . '/' . $schoolYear);
if ($semVal !== '') {
$printUrl .= '?semester=' . urlencode($semVal);
}
?>
<a href="<?= $printUrl ?>"
class="btn btn-lg btn-print-class <?= !empty($prep['needs_print']) ? 'btn-danger' : 'btn-success' ?>" target="_blank" rel="noopener noreferrer"
onclick="window.open(this.href, '_blank'); return false;">
Print
</a>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- Totals Section -->
<div class="mt-5">
<h5 class="mb-3">📦 Totals for All Used Items</h5>
<div class="table-responsive">
<table class="table table-sm table-bordered align-middle no-mgmt-sticky">
<thead class="table-light">
<tr>
<th>Item</th>
<th class="text-end">Total Needed</th>
<th class="text-end">In Inventory</th>
<th class="text-end">Short By</th>
</tr>
</thead>
<tbody>
<?php foreach ($totalNeeded as $item => $needed): ?>
<?php
$have = (int)($available[$item] ?? 0);
$short = max(0, (int)$needed - $have);
$rowCls = $short > 0 ? 'table-warning' : '';
?>
<tr class="<?= $rowCls ?>">
<td><?= esc($item) ?></td>
<td class="text-end"><?= (int)$needed ?></td>
<td class="text-end"><?= $have ?></td>
<td class="text-end <?= $short > 0 ? 'text-danger fw-bold' : 'text-success' ?>">
<?= $short ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Shortage Warning -->
<?php if (!empty($shortages)): ?>
<div class="alert alert-danger mt-3">
<strong>Shortages Detected</strong>
<ul class="mb-0">
<?php foreach ($shortages as $item => $short): ?>
<li><?= esc($item) ?>: short by <?= (int)$short ?> pcs</li>
<?php endforeach; ?>
</ul>
</div>
<?php else: ?>
<div class="alert alert-success mt-3">✅ All items are available in stock.</div>
<?php endif; ?>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<style>
.text-muted-50 {
opacity: .55;
}
.card {
border-radius: 12px;
}
.card-header {
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
.card-footer {
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
}
.list-group-item {
padding-top: .5rem;
padding-bottom: .5rem;
}
@media (max-width: 575.98px) {
.card .input-group {
max-width: 100%;
}
}
</style>
<script>
function adjustQty(btn, delta) {
const input = btn.parentElement.querySelector('input[type="number"]');
const curr = parseInt(input.value, 10) || 0;
input.value = curr + delta; // clamp if you want: input.value = Math.max(0, curr + delta);
}
// Force print links to open in a new tab even if other handlers intercept
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.btn-print-class').forEach(function(el) {
el.addEventListener('click', function(ev) {
ev.preventDefault();
try {
window.open(this.href, '_blank', 'noopener');
} catch (e) {
this.setAttribute('target', '_blank');
window.location.assign(this.href);
}
}, { capture: true });
});
});
</script>
<?= $this->endSection() ?>
+107
View File
@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Class Prep Sheet</title>
<link rel="icon" href="<?= base_url('assets/images/favicon.ico') ?>">
<style>
body {
font-family: Arial, sans-serif;
padding: 5cm;
}
.prep-label {
border: 2px solid #000;
padding: 2rem;
width: 400px;
margin: auto;
text-align: center;
}
.prep-label h2 {
font-size: 5rem;
margin-bottom: 1rem;
}
.prep-label ul {
list-style: none;
padding-left: 0;
font-size: 3rem;
}
@media print {
body {
margin: 0;
padding: 0;
}
.prep-label {
border: none;
page-break-after: always;
}
}
</style>
</head>
<body onload="window.print()">
<?php
// prefer friendly name if provided by controller
$sectionTitle = isset($classSection) && $classSection ? $classSection : ($classSectionId ?? 'Class');
// make sure we have an array
$prepItems = is_array($prepItems ?? null) ? $prepItems : [];
// optional: if you want a specific order, define it here.
// otherwise well just print in the arrays order.
$preferredOrder = [
'Grade Box',
'Large Table',
'Regular Chair',
'Small Chair',
'Small Table',
'Teacher Chair',
'Trash Bin',
'White Board',
];
// reorder $prepItems by $preferredOrder when present
$ordered = [];
foreach ($preferredOrder as $k) {
if (array_key_exists($k, $prepItems)) {
$ordered[$k] = $prepItems[$k];
}
}
// include any remaining keys (if any)
foreach ($prepItems as $k => $v) {
if (!array_key_exists($k, $ordered)) {
$ordered[$k] = $v;
}
}
?>
<div class="prep-label">
<h2>Grade <?= esc($sectionTitle) ?></h2>
<ul>
<?php
$printed = 0;
foreach ($ordered as $name => $qty):
$qty = (int)$qty;
if ($qty > 0):
$printed++;
?>
<li><strong><?= $qty ?></strong> <?= esc($name) ?></li>
<?php
endif;
endforeach;
if ($printed === 0): ?>
<li>No items needed</li>
<?php endif; ?>
</ul>
</div>
</body>
</html>
+231
View File
@@ -0,0 +1,231 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Al Rahma Sunday School</title>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="" name="keywords">
<meta content="" name="description">
<!-- Favicon -->
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
<!-- Google Web Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;600&family=Inter:wght@600&family=Lobster+Two:wght@700&display=swap" rel="stylesheet">
<!-- Icon Font Stylesheet -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css" rel="stylesheet">
<!-- Libraries Stylesheet -->
<link href="<?= base_url('lib/animate/animate.min.css') ?>" rel="stylesheet">
<link href="<?= base_url('lib/owlcarousel/assets/owl.carousel.min.css') ?>" rel="stylesheet">
<!-- Customized Bootstrap Stylesheet -->
<link href="<?= base_url('css/bootstrap.min.css') ?>" rel="stylesheet">
<!-- Template Stylesheet -->
<link href="<?= base_url('css/style.css') ?>" rel="stylesheet">
</head>
<body>
<div class="container-xxl bg-white p-0">
<!-- Spinner Start -->
<div id="spinner" class="show bg-white position-fixed translate-middle w-100 vh-100 top-50 start-50 d-flex align-items-center justify-content-center">
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<!-- Spinner End -->
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav mx-auto">
<a href="<?= base_url('/') ?>" class="nav-item nav-link">Home</a>
<a href="<?= base_url('/about') ?>" class="nav-item nav-link">About Us</a>
<a href="<?= base_url('/classes') ?>" class="nav-item nav-link">Classes</a>
<a href="<?= base_url('/contact') ?>" class="nav-item nav-link">Contact Us</a>
</div>
<div class="d-flex">
<a href="/user/login" class="btn btn-primary rounded-pill px-3">Login<i class="fa fa-arrow-right ms-3"></i></a>
<a href="/register" class="btn btn-primary rounded-pill px-3 me-2">Register<i class="fa fa-arrow-right ms-3"></i></a>
</div>
</div>
</nav>
<!-- Navbar End -->
<!-- Page Header End -->
<div class="container-xxl py-5 page-header position-relative mb-5">
<div class="container py-5">
<h1 class="display-2 text-white animated slideInDown mb-4">Classes</h1>
<nav aria-label="breadcrumb animated slideInDown">
<ol class="breadcrumb">
<li class="breadcrumb-item text-green"><a href="/">Home</a></li>
<li class="breadcrumb-item text-green"><a href="#">Pages</a></li>
<li class="breadcrumb-item text-green active" aria-current="page">Classes</li>
</ol>
</nav>
</div>
</div>
<!-- Page Header End -->
<!-- Classes Start -->
<div class="container-xxl py-5">
<div class="container">
<div class="text-center mx-auto mb-5 wow fadeInUp" data-wow-delay="0.1s" style="max-width: 600px;">
<h1 class="mb-3">School Classes</h1>
<p>Our school offers classes for students from grades 1 to 10, providing a comprehensive and engaging curriculum that fosters academic and personal growth. Additionally, our youth program offers enriching activities and mentorship for older students, helping them develop leadership skills and a sense of community. Together, these programs ensure a well-rounded education and support for every stage of your child's development.</p>
</div>
<div class="row g-4">
<div class="col-lg-4 col-md-6 wow fadeInUp" data-wow-delay="0.1s">
<div class="classes-item">
<div class="bg-light rounded-circle w-75 mx-auto p-3">
<img class="img-fluid rounded-circle" src=""assets/images/classes-1.jpg" alt="">
</div>
<div class="bg-light rounded p-4 pt-5 mt-n5">
<a class="d-block text-center h3 mt-3 mb-4" href="">Quran Learning</a>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6 wow fadeInUp" data-wow-delay="0.3s">
<div class="classes-item">
<div class="bg-light rounded-circle w-75 mx-auto p-3">
<img class="img-fluid rounded-circle" src=""assets/images/classes-2.jpg" alt="">
</div>
<div class="bg-light rounded p-4 pt-5 mt-n5">
<a class="d-block text-center h3 mt-3 mb-4" href="">Islamic Studies</a>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6 wow fadeInUp" data-wow-delay="0.5s">
<div class="classes-item">
<div class="bg-light rounded-circle w-75 mx-auto p-3">
<img class="img-fluid rounded-circle" src=""assets/images/classes-3.jpg" alt="">
</div>
<div class="bg-light rounded p-4 pt-5 mt-n5">
<a class="d-block text-center h3 mt-3 mb-4" href="">Arabic Learning</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Classes End -->
<!-- Footer Start -->
<div class="container-fluid bg-dark text-white-50 footer pt-5 mt-5 wow fadeIn" data-wow-delay="0.1s">
<div class="container py-5">
<div class="row g-5">
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Get In Touch</h3>
<p class="mb-2"><i class="fa fa-map-marker-alt me-3"></i>5 Courthouse Lane, Chelmsford, MA 01824
</p>
<p class="mb-2"><i class="fa fa-phone-alt me-3"></i>+1 978-364-0219
</p>
<p class="mb-2"><i class="fa fa-envelope me-3"></i>alrahma.isgl@gmail.com
</p>
<div class="d-flex pt-2">
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-twitter"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-facebook-f"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-youtube"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-linkedin-in"></i></a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Quick Links</h3>
<a class="btn btn-link text-white-50" href="<?= base_url('about.html') ?>">About Us</a>
<a class="btn btn-link text-white-50" href="<?= base_url('contact.html') ?>">Classes</a>
<a class="btn btn-link text-white-50" href="<?= base_url('services.html') ?>">Our Services</a>
<a class="btn btn-link text-white-50" href="<?= base_url('privacy.html') ?>">Privacy Policy</a>
<a class="btn btn-link text-white-50" href="<?= base_url('terms.html') ?>">Terms & Condition</a>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Photo Gallery</h3>
<div class="row g-2 pt-2">
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-1.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-2.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-3.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-4.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-5.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-6.jpg') ?>" alt="">
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Newsletter</h3>
<p>Our newsletter is your gateway to staying informed and connected with our Sunday school community. </p>
<div class="position-relative mx-auto" style="max-width: 400px;">
<input class="form-control bg-transparent w-100 py-3 ps-4 pe-5" type="text" placeholder="Your email">
<button type="button" class="btn btn-primary py-2 position-absolute top-0 end-0 mt-2 me-2">SignUp</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="copyright">
<div class="row">
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
&copy; <a class="border-bottom" href="#">Al Rahma Sunday School by ISGL</a>, All Right Reserved.
<!--/*** This template is free as long as you keep the footer authors credit link/attribution link/backlink. If you'd like to use the template without the footer authors credit link/attribution link/backlink, you can purchase the Credit Removal License from "https://htmlcodex.com/credit-removal". Thank you for your support. ***/-->
Designed By <a class="border-bottom" href="https://htmlcodex.com">HTML Codex</a>
</div>
<div class="col-md-6 text-center text-md-end">
<div class="footer-menu">
<a href="<?= base_url('/') ?>" class="nav-item nav-link">Home</a>
<a href="#">Cookies</a>
<a href="#">Help</a>
<a href="#">FQAs</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer End -->
<!-- Back to Top -->
<a href="#" class="btn btn-lg btn-primary btn-lg-square back-to-top"><i class="bi bi-arrow-up"></i></a>
</div>
<!-- JavaScript Libraries -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="<?= base_url('lib/wow/wow.min.js') ?>"></script>
<script src="<?= base_url('lib/easing/easing.min.js') ?>"></script>
<script src="<?= base_url('lib/waypoints/waypoints.min.js') ?>"></script>
<script src="<?= base_url('lib/owlcarousel/owl.carousel.min.js') ?>"></script>
<!-- Template Javascript -->
<script src="<?= base_url('js/main.js') ?>"></script>
</body>
</html>
+213
View File
@@ -0,0 +1,213 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container my-4">
<h2 class="mb-3">Family Communications</h2>
<?= view('partials/flash_messages') ?>
<?php
$prefillStudentId = isset($_GET['student_id']) ? (int)$_GET['student_id'] : 0;
$prefillTemplate = isset($_GET['template_key']) ? trim((string)$_GET['template_key']) : '';
?>
<div class="card">
<div class="card-header">Compose</div>
<div class="card-body">
<form id="comm-form" method="post" action="<?= site_url('communications/send') ?>">
<?= csrf_field() ?>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Student</label>
<select class="form-select" id="student_id" name="student_id" required>
<option value="">Select…</option>
<?php foreach ($students as $st): ?>
<?php $sid = (int)($st['id'] ?? 0); ?>
<option value="<?= esc($sid) ?>" <?= ($prefillStudentId === $sid ? 'selected' : '') ?>>
<?= esc(($st['lastname'] ?? '').', '.($st['firstname'] ?? '')) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Family (if multiple)</label>
<select class="form-select" id="family_id" name="family_id" required>
<option value="">Select a student first…</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Subject Category</label>
<select class="form-select" id="template_key" name="template_key" required>
<option value="">Select…</option>
<?php foreach ($templates as $t): ?>
<?php
$tkey = (string)($t['template_key'] ?? '');
$tname = (string)($t['name'] ?? '');
$label = $tname !== '' ? $tname : $tkey;
?>
<option value="<?= esc($tkey) ?>" <?= ($prefillTemplate !== '' && $prefillTemplate === $tkey ? 'selected' : '') ?>>
<?= esc($label) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mt-3">
<label class="form-label">Recipients (Guardians)</label>
<div id="recipientPills" class="d-flex flex-wrap gap-2"></div>
</div>
<hr/>
<div class="row g-3">
<div class="col-md-8">
<label class="form-label">Email Subject</label>
<input type="text" class="form-control" id="subject" name="subject" required>
</div>
<div class="col-md-4 d-flex align-items-end">
<button type="button" id="btnPreview" class="btn btn-outline-secondary ms-auto">Refresh Preview</button>
</div>
<div class="col-12">
<label class="form-label">Email Body (editable)</label>
<textarea class="form-control" id="body" name="body" rows="10" required></textarea>
</div>
</div>
<input type="hidden" name="recipients" id="recipients_json">
<input type="hidden" name="cc" id="cc_json">
<input type="hidden" name="bcc" id="bcc_json">
<div class="mt-3 d-flex gap-2">
<button type="submit" class="btn btn-primary">Send</button>
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#previewModal">Preview</button>
</div>
</form>
</div>
</div>
</div>
<!-- Preview Modal -->
<div class="modal fade" id="previewModal" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Email Preview</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<h6 id="pvSubject" class="mb-3"></h6>
<div id="pvBody"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
(function(){
const $student = document.getElementById('student_id');
const $family = document.getElementById('family_id');
const $template = document.getElementById('template_key');
const $recipientPills = document.getElementById('recipientPills');
const $subject = document.getElementById('subject');
const $body = document.getElementById('body');
const $recipientsJson = document.getElementById('recipients_json');
let recipients = [];
function renderRecipients(){
$recipientPills.innerHTML = '';
recipients.forEach((em, idx) => {
const span = document.createElement('span');
span.className = 'badge bg-light text-dark border';
span.innerHTML = em + ' <button data-i="'+idx+'" type="button" class="btn btn-sm btn-link p-0 ms-1">×</button>';
$recipientPills.appendChild(span);
});
$recipientsJson.value = JSON.stringify(recipients);
}
$recipientPills.addEventListener('click', (e)=>{
if (e.target.tagName === 'BUTTON') {
recipients.splice(parseInt(e.target.getAttribute('data-i')),1);
renderRecipients();
}
});
async function loadFamilies(studentId){
$family.innerHTML = '<option value="">Loading…</option>';
recipients = []; renderRecipients();
if(!studentId){ $family.innerHTML = '<option value="">Select a student first…</option>'; return; }
const res = await fetch('<?= site_url('api/students') ?>/'+studentId+'/families');
const data = await res.json();
const list = data.data || [];
if (!list.length){ $family.innerHTML = '<option value="">No families found</option>'; return; }
let html = '';
list.forEach(f => {
const name = f.household_name || ('Family #' + f.id);
html += `<option value="${f.id}">${name}${f.is_primary_home ? ' (primary)' : ''}</option>`;
});
$family.innerHTML = '<option value="">Select…</option>' + html;
// Auto-select the first (primary) family
$family.selectedIndex = 1;
loadGuardians($family.value);
}
async function loadGuardians(familyId){
recipients = []; renderRecipients();
if(!familyId) return;
const res = await fetch('<?= site_url('api/families') ?>/'+familyId+'/guardians');
const data = await res.json();
(data.data || []).forEach(g => { if (g.receive_emails && g.email) recipients.push(g.email); });
recipients = Array.from(new Set(recipients));
renderRecipients();
}
async function refreshPreview(){
const student_id = $student.value;
const family_id = $family.value;
const template_key = $template.value;
if (!student_id || !family_id || !template_key) return;
const vars = {
school_name: 'Al Rahma Sunday School',
teacher_name: '<?= esc(session('display_name') ?? 'Teacher') ?>',
class_section: '',
student_grade: '',
attendance_message: '',
assessment_name: '',
score_obtained: '',
score_total: '',
teacher_comments: '',
behavior_summary: '',
actions_taken: '',
next_steps: ''
};
const form = new FormData();
form.append('<?= csrf_token() ?>', '<?= csrf_hash() ?>');
form.append('student_id', student_id);
form.append('family_id', family_id);
form.append('template_key', template_key);
form.append('vars', JSON.stringify(vars));
const res = await fetch('<?= site_url('communications/preview') ?>', { method: 'POST', body: form });
const data = await res.json();
if (data.subject) $subject.value = data.subject;
if (data.html) $body.value = data.html.replace(/<br\s*\/?>/g, "\n");
document.getElementById('pvSubject').textContent = $subject.value;
document.getElementById('pvBody').innerHTML = data.html || '';
}
document.getElementById('btnPreview').addEventListener('click', refreshPreview);
$student.addEventListener('change', e => loadFamilies(e.target.value));
$family.addEventListener('change', e => loadGuardians(e.target.value));
$template.addEventListener('change', refreshPreview);
// Init
if ($student.value) loadFamilies($student.value);
})();
</script>
<?= $this->endSection() ?>
@@ -0,0 +1,274 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
<?php elseif (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
<?php endif; ?>
<div class="container-fluid"
data-config-endpoint="<?= esc($configEndpoint ?? site_url('api/configuration')) ?>"
data-csrf-name="<?= esc(csrf_token()) ?>"
data-csrf-value="<?= esc(csrf_hash()) ?>">
<div class="wrapper">
<!-- Centered Title with proper spacing -->
<div class="text-center mb-4">
<h2 class="text-center mt-4 mb-3">Configuration Management</h2>
</div>
<!-- Spacer after the button -->
<div class="mb-4"></div>
<!-- Add New Configuration Form -->
<div id="config-form" style="display: none;" class="mb-4">
<form action="/configuration/addConfig" method="post" class="mt-3">
<?= csrf_field() ?>
<div class="row">
<div class="col-md-4 mb-3">
<label for="config_key">Config Key:</label>
<input type="text" name="config_key" class="form-control" required>
</div>
<div class="col-md-4 mb-3">
<label for="config_value">Config Value:</label>
<input type="text" name="config_value" class="form-control" required>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Save</button>
<a href="/configuration/configuration_view" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
<!-- Display existing configurations -->
<div class="table-responsive">
<table id="configTable" class="display table table-striped align-middle">
<thead>
<tr>
<th>ID</th>
<th>Config Key</th>
<th>Config Value</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4" class="text-center text-muted">Loading configurations...</td>
</tr>
</tbody>
</table>
<div class="mb-4">
<button id="addNewBtn" class="btn btn-success">Add New Config</button>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editConfigModal" tabindex="-1" aria-labelledby="editConfigModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form id="editConfigForm" method="post" class="modal-content">
<?= csrf_field() ?>
<div class="modal-header">
<h5 class="modal-title" id="editConfigModalLabel">Edit Configuration</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="config_id" id="config_id">
<div class="mb-3">
<label for="modal_config_key" class="form-label">Config Key:</label>
<input type="text" name="config_key" id="modal_config_key" class="form-control" required>
</div>
<div class="mb-3">
<label for="modal_config_value" class="form-label">Config Value:</label>
<input type="text" name="config_value" id="modal_config_value" class="form-control" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('[data-config-endpoint]');
if (!container) return;
const endpoint = container.dataset.configEndpoint;
const csrfName = container.dataset.csrfName;
const csrfValue = container.dataset.csrfValue;
const CSRF_COOKIE_NAME = <?= json_encode(config('Security')->csrfCookieName ?? (config('Security')->cookieName ?? 'csrf_cookie_name')) ?>;
function getCookie(n){
return document.cookie.split(';').map(v=>v.trim()).find(v=>v.startsWith(n+'='))?.split('=')[1] || '';
}
const addNewBtn = document.getElementById('addNewBtn');
const configForm = document.getElementById('config-form');
const tableBody = document.querySelector('#configTable tbody');
const editModalElement = document.getElementById('editConfigModal');
const editForm = document.getElementById('editConfigForm');
const editModal = (editModalElement && window.bootstrap && window.bootstrap.Modal)
? new window.bootstrap.Modal(editModalElement)
: null;
let configs = [];
const safe = (value, fallback = '') =>
value === undefined || value === null ? fallback : value;
if (addNewBtn && configForm) {
addNewBtn.addEventListener('click', () => {
const isVisible = configForm.style.display === 'block';
configForm.style.display = isVisible ? 'none' : 'block';
});
}
const hasDataTables = () =>
window.jQuery && window.jQuery.fn && window.jQuery.fn.DataTable;
const destroyDataTable = () => {
if (!hasDataTables()) return;
if (window.jQuery.fn.DataTable.isDataTable('#configTable')) {
window.jQuery('#configTable').DataTable().clear().destroy();
}
};
const initDataTable = () => {
if (!hasDataTables()) return;
window.jQuery('#configTable').DataTable({
pageLength: 25,
lengthMenu: [10, 25, 50, 100],
});
};
const buildDeleteForm = (id) => {
const form = document.createElement('form');
form.method = 'post';
form.action = '/configuration/deleteConfig/' + safe(id);
form.className = 'd-inline';
form.addEventListener('submit', (event) => {
if (!confirm('Are you sure you want to delete this config?')) {
event.preventDefault();
}
// Refresh CSRF token just before submit
try {
const fresh = decodeURIComponent(getCookie(CSRF_COOKIE_NAME) || '');
if (fresh) {
const hidden = form.querySelector('input[name="' + csrfName + '"]');
if (hidden) hidden.value = fresh;
}
} catch(_) {}
});
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = csrfName;
csrfInput.value = csrfValue;
form.appendChild(csrfInput);
const deleteButton = document.createElement('button');
deleteButton.type = 'submit';
deleteButton.className = 'btn btn-sm btn-danger';
deleteButton.textContent = 'Delete';
form.appendChild(deleteButton);
return form;
};
const openEditModal = (config) => {
if (!editModal || !editForm) return;
editForm.action = '/configuration/editConfig/' + safe(config.id);
editForm.querySelector('#config_id').value = safe(config.id);
editForm.querySelector('#modal_config_key').value = safe(config.config_key);
editForm.querySelector('#modal_config_value').value = safe(config.config_value);
editModal.show();
};
const renderConfigs = function() {
destroyDataTable();
tableBody.innerHTML = '';
if (!Array.isArray(configs) || configs.length === 0) {
const emptyRow = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 4;
cell.classList.add('text-center', 'text-muted');
cell.textContent = 'No configurations found.';
emptyRow.appendChild(cell);
tableBody.appendChild(emptyRow);
return;
}
configs.forEach(function(config) {
const row = document.createElement('tr');
const idCell = document.createElement('td');
idCell.textContent = safe(config.id);
row.appendChild(idCell);
const keyCell = document.createElement('td');
keyCell.textContent = safe(config.config_key);
row.appendChild(keyCell);
const valueCell = document.createElement('td');
valueCell.textContent = safe(config.config_value);
row.appendChild(valueCell);
const actionsCell = document.createElement('td');
actionsCell.className = 'd-flex gap-1';
const editButton = document.createElement('button');
editButton.type = 'button';
editButton.className = 'btn btn-sm btn-warning';
editButton.textContent = 'Edit';
editButton.addEventListener('click', function() {
openEditModal(config);
});
actionsCell.appendChild(editButton);
actionsCell.appendChild(buildDeleteForm(config.id));
row.appendChild(actionsCell);
tableBody.appendChild(row);
});
initDataTable();
};
const fetchConfigs = function() {
tableBody.innerHTML = '<tr><td colspan="4" class="text-center text-muted">Loading configurations...</td></tr>';
fetch(endpoint, {
headers: { 'Accept': 'application/json' },
credentials: 'same-origin',
})
.then(function(response) {
if (!response.ok) {
throw new Error('Request failed with status ' + response.status);
}
return response.json();
})
.then(function(payload) {
const list = payload && Array.isArray(payload.configs) ? payload.configs : [];
configs = list;
renderConfigs();
})
.catch(function(error) {
console.error(error);
destroyDataTable();
tableBody.innerHTML = '<tr><td colspan="4" class="text-center text-danger">Failed to load configurations.</td></tr>';
});
};
fetchConfigs();
});
</script>
<?= $this->endSection() ?>
+283
View File
@@ -0,0 +1,283 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Al Rahma Sunday School</title>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="" name="keywords">
<meta content="" name="description">
<!-- Favicon -->
<link href="<?= base_url('assets/images/favicon.ico') ?>" rel="icon">
<!-- Google Web Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;600&family=Inter:wght@600&family=Lobster+Two:wght@700&display=swap" rel="stylesheet">
<!-- Icon Font Stylesheet -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css" rel="stylesheet">
<!-- Libraries Stylesheet -->
<link href="<?= base_url('lib/animate/animate.min.css') ?>" rel="stylesheet">
<link href="<?= base_url('lib/owlcarousel/assets/owl.carousel.min.css') ?>" rel="stylesheet">
<!-- Customized Bootstrap Stylesheet -->
<link href="<?= base_url('css/bootstrap.min.css') ?>" rel="stylesheet">
<!-- Template Stylesheet -->
<link href="<?= base_url('css/style.css') ?>" rel="stylesheet">
</head>
<body>
<div class="container-xxl bg-white p-0">
<!-- Spinner Start -->
<div id="spinner" class="show bg-white position-fixed translate-middle w-100 vh-100 top-50 start-50 d-flex align-items-center justify-content-center">
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<!-- Spinner End -->
<!-- Navbar Start -->
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px;">
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
</a>
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<div class="navbar-nav mx-auto">
<a href="<?= base_url('/') ?>" class="nav-item nav-link">Home</a>
<a href="<?= base_url('/about') ?>" class="nav-item nav-link">About Us</a>
<a href="<?= base_url('/classes') ?>" class="nav-item nav-link">Classes</a>
<a href="<?= base_url('/contact') ?>" class="nav-item nav-link">Contact Us</a>
</div>
<div class="d-flex">
<a href="/user/login" class="btn btn-primary rounded-pill px-3">Login<i class="fa fa-arrow-right ms-3"></i></a>
<a href="/register" class="btn btn-primary rounded-pill px-3 me-2">Register<i class="fa fa-arrow-right ms-3"></i></a>
</div>
</div>
</nav>
<!-- Navbar End -->
<!-- Page Header Start -->
<div class="container-xxl py-5 page-header position-relative mb-5">
<div class="container py-5">
<h1 class="display-2 text-white animated slideInDown mb-4">Contact Us</h1>
<nav aria-label="breadcrumb animated slideInDown">
<ol class="breadcrumb">
<li class="breadcrumb-item text-green"><a href="/">Home</a></li>
<li class="breadcrumb-item text-green"><a href="#">Pages</a></li>
<li class="breadcrumb-item text-green active" aria-current="page">Contact Us</li>
</ol>
</nav>
</div>
</div>
<!-- Page Header End -->
<!-- Contact Start -->
<div class="container-xxl py-5">
<div class="container">
<div class="text-center mx-auto mb-5 wow fadeInUp" data-wow-delay="0.1s" style="max-width: 600px;">
<h1 class="mb-3">Get In Touch</h1>
<p>Get in touch with us today to learn more about our programs and how you can get involved.</p>
</div>
<div class="row g-4 mb-5">
<div class="col-md-6 col-lg-4 text-center wow fadeInUp" data-wow-delay="0.1s">
<div class="bg-light rounded-circle d-inline-flex align-items-center justify-content-center mb-4"
style="width: 75px; height: 75px;">
<i class="fa fa-map-marker-alt fa-2x text-primary"></i>
</div>
<h6>5 Courthouse Lane, Chelmsford, MA 01824</h6>
</div>
<div class="col-md-6 col-lg-4 text-center wow fadeInUp" data-wow-delay="0.3s">
<div class="bg-light rounded-circle d-inline-flex align-items-center justify-content-center mb-4"
style="width: 75px; height: 75px;">
<i class="fa fa-envelope-open fa-2x text-primary"></i>
</div>
<h6>alrahma.isgl@gmail.com</h6>
</div>
<div class="col-md-6 col-lg-4 text-center wow fadeInUp" data-wow-delay="0.5s">
<div class="bg-light rounded-circle d-inline-flex align-items-center justify-content-center mb-4"
style="width: 75px; height: 75px;">
<i class="fa fa-phone-alt fa-2x text-primary"></i>
</div>
<h6>+1 978-364-0219</h6>
</div>
</div>
<div class="bg-light rounded">
<div class="row g-0">
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.1s">
<div class="h-100 d-flex flex-column justify-content-center p-5">
<form id="contactForm">
<div class="row g-3">
<div class="col-sm-6">
<div class="form-floating">
<input type="text" class="form-control border-0" id="name" name="name" placeholder="Your Name">
<label for="name">Your Name</label>
</div>
</div>
<div class="col-sm-6">
<div class="form-floating">
<input type="email" class="form-control border-0" id="email" name="email" placeholder="Your Email">
<label for="email">Your Email</label>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<input type="text" class="form-control border-0" id="subject" name="subject" placeholder="Subject">
<label for="subject">Subject</label>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<textarea class="form-control border-0" placeholder="Leave a message here" id="message" name="message" style="height: 100px"></textarea>
<label for="message">Message</label>
</div>
</div>
<div class="col-12">
<button class="btn btn-primary w-100 py-3" type="submit">Send Message</button>
</div>
<div id="formResponse" class="mt-3"></div>
</div>
</form>
</div>
</div>
<div class="col-lg-6 wow fadeIn" data-wow-delay="0.5s" style="min-height: 400px;">
<div class="position-relative h-100">
<iframe class="position-relative rounded w-100 h-100"
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2949.902891223497!2d-71.3606721845427!3d42.59682637917086!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x89e3a489bb344a85%3A0xeaba1b29330726cb!2s5%20Courthouse%20Ln%2C%20Chelmsford%2C%20MA%2001824%2C%20USA!5e0!3m2!1sen!2sbd!4v1627993075161!5m2!1sen!2sbd"
frameborder="0" style="min-height: 400px; border:0;" allowfullscreen=""
aria-hidden="false" tabindex="0"></iframe>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Contact End -->
<!-- Footer Start -->
<div class="container-fluid bg-dark text-white-50 footer pt-5 mt-5 wow fadeIn" data-wow-delay="0.1s">
<div class="container py-5">
<div class="row g-5">
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Get In Touch</h3>
<p class="mb-2"><i class="fa fa-map-marker-alt me-3"></i>5 Courthouse Lane, Chelmsford, MA 01824
</p>
<p class="mb-2"><i class="fa fa-phone-alt me-3"></i>+1 978-364-0219
</p>
<p class="mb-2"><i class="fa fa-envelope me-3"></i>alrahma.isgl@gmail.com
</p>
<div class="d-flex pt-2">
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-twitter"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-facebook-f"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-youtube"></i></a>
<a class="btn btn-outline-light btn-social" href=""><i class="fab fa-linkedin-in"></i></a>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Quick Links</h3>
<a class="btn btn-link text-white-50" href="<?= base_url('/about') ?>">About Us</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/contact') ?>">Contact Us</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/services') ?>">Our Services</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/privacy') ?>">Privacy Policy</a>
<a class="btn btn-link text-white-50" href="<?= base_url('/terms') ?>">Terms & Condition</a>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Photo Gallery</h3>
<div class="row g-2 pt-2">
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-1.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-2.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-3.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-4.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-5.jpg') ?>" alt="">
</div>
<div class="col-4">
<img class="img-fluid rounded bg-light p-1" src="<?= base_url('"assets/images/classes-6.jpg') ?>" alt="">
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<h3 class="text-white mb-4">Newsletter</h3>
<p>Our newsletter is your gateway to staying informed and connected with our Sunday school community. </p>
<div class="position-relative mx-auto" style="max-width: 400px;">
<input class="form-control bg-transparent w-100 py-3 ps-4 pe-5" type="text" placeholder="Your email">
<button type="button" class="btn btn-primary py-2 position-absolute top-0 end-0 mt-2 me-2">SignUp</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="copyright">
<div class="row">
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
&copy; <a class="border-bottom" href="#">Al Rahma Sunday School by ISGL</a>, All Right Reserved.
Designed By <a class="border-bottom" href="https://htmlcodex.com">HTML Codex</a>
</div>
<div class="col-md-6 text-center text-md-end">
<div class="footer-menu">
<a href="#">Home</a>
<a href="#">Cookies</a>
<a href="#">Help</a>
<a href="#">FQAs</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer End -->
<!-- Back to Top -->
<a href="#" class="btn btn-lg btn-primary btn-lg-square back-to-top"><i class="bi bi-arrow-up"></i></a>
</div>
<!-- JavaScript Libraries -->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="<?= base_url('lib/wow/wow.min.js') ?>"></script>
<script src="<?= base_url('lib/easing/easing.min.js') ?>"></script>
<script src="<?= base_url('lib/waypoints/waypoints.min.js') ?>"></script>
<script src="<?= base_url('lib/owlcarousel/owl.carousel.min.js') ?>"></script>
<!-- Template Javascript -->
<script src="<?= base_url('js/main.js') ?>"></script>
<script>
document.getElementById('contactForm').addEventListener('submit', function(e) {
e.preventDefault();
let formData = new FormData(this);
fetch('contact_process.php', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
document.getElementById('formResponse').innerHTML = data;
document.getElementById('contactForm').reset();
})
.catch(error => console.error('Error:', error));
});
</script>
</body>
</html>
+37
View File
@@ -0,0 +1,37 @@
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$name = strip_tags(trim($_POST["name"]));
$email = filter_var(trim($_POST["email"]), FILTER_SANITIZE_EMAIL);
$subject = strip_tags(trim($_POST["subject"]));
$message = trim($_POST["message"]);
// Check that data was sent to the mailer.
if (empty($name) || empty($subject) || empty($message) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo "Oops! There was a problem with your submission. Please complete the form and try again.";
exit;
}
// Set the recipient email address.
$recipient = "alrahma.isgl@gmail.com";
// Set the email subject.
$email_subject = "New contact from $name: $subject";
// Build the email content.
$email_content = "Name: $name\n";
$email_content .= "Email: $email\n\n";
$email_content .= "Message:\n$message\n";
// Build the email headers.
$email_headers = "From: $name <$email>";
// Send the email.
if (mail($recipient, $email_subject, $email_content, $email_headers)) {
echo "Thank you! Your message has been sent.";
} else {
echo "Oops! Something went wrong and we couldn't send your message.";
}
} else {
echo "There was a problem with your submission, please try again.";
}
?>
+117
View File
@@ -0,0 +1,117 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container mt-4">
<h1>Apply Discount Voucher</h1>
<?= $this->include('partials/academic_filter') ?>
<form method="post" action="">
<?= csrf_field() ?>
<div class="d-flex gap-2 mb-3 justify-content-end">
<div>
<button type="submit" class="btn btn-success">Apply Voucher</button>
<button type="button" class="btn btn-info" onclick="window.location.href='/discounts/list'">Cancel</button>
</div>
</div>
<div class="mb-3">
<label for="voucher_id" class="form-label">Select Voucher Code</label>
<select name="voucher_id" id="voucher_id" class="form-select" required>
<option value="">-- Select Voucher --</option>
<?php foreach ($vouchers as $voucher): ?>
<option value="<?= esc($voucher['id']) ?>">
<?= esc($voucher['code']) ?> (<?= esc(ucfirst($voucher['discount_type'])) ?> - <?= esc($voucher['discount_value']) ?><?= $voucher['discount_type'] === 'percent' ? '%' : '$' ?>)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="allowAdditional" name="allow_additional" value="1" checked>
<label class="form-check-label" for="allowAdditional">
Allow additional discounts for parents who already have discounts
</label>
</div>
<div class="mb-3">
<label class="form-label">Select Parents</label>
<div class="table-responsive">
<table class="table table-bordered" id="parentsTable">
<thead>
<tr>
<th><input type="checkbox" id="checkAll"></th>
<th>Parent ID</th>
<th>Parent Name</th>
<th>Email</th>
<th>Has Discount?</th> <!-- NEW -->
<th>Total Discount Amount</th> <!-- NEW -->
</tr>
</thead>
<tbody>
<?php foreach ($parents as $parent): ?>
<tr>
<td>
<input
type="checkbox"
name="parent_ids[]"
value="<?= esc($parent['id']) ?>"
class="parent-check"
data-has-discount="<?= $parent['has_discount'] ? '1' : '0' ?>"
<?= $parent['has_discount'] ? 'disabled' : '' ?>>
</td>
<td><?= esc($parent['school_id'] ?? 'N/A') ?></td>
<td><?= esc($parent['firstname'] . ' ' . $parent['lastname']) ?></td>
<td><?= esc($parent['email']) ?></td>
<td>
<?= isset($parent['has_discount']) && $parent['has_discount'] ? 'Yes' : 'No' ?>
</td>
<td>
$<?= number_format($parent['total_discount'] ?? 0, 2) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</form>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
$(document).ready(function() {
const table = $('#parentsTable').DataTable();
function syncDiscountLocks() {
const allowAdditional = $('#allowAdditional').is(':checked');
$('.parent-check').each(function() {
const hasDiscount = $(this).data('has-discount') === 1 || $(this).data('has-discount') === '1';
if (hasDiscount) {
if (allowAdditional) {
$(this).prop('disabled', false);
} else {
$(this).prop('checked', false).prop('disabled', true);
}
}
});
}
$('#allowAdditional').on('change', syncDiscountLocks);
$('#allowAdditional').prop('checked', true);
syncDiscountLocks();
table.on('draw', function() {
syncDiscountLocks();
});
$('#checkAll').click(function() {
$('.parent-check:enabled').prop('checked', this.checked);
});
});
</script>
<?= $this->endSection() ?>

Some files were not shown because too many files have changed in this diff Show More