recreate project
This commit is contained in:
@@ -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">
|
||||
We’re 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">
|
||||
©
|
||||
<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 author’s credit link/attribution link/backlink. If you'd like to use the template without the footer author’s 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,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">
|
||||
© <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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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(/"/g, '"').replace(/'/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(/"/g, '"').replace(/'/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 / Crohn’s 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 (2–30 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 (2–30 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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
© <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 author’s credit link/attribution link/backlink. If you'd like to use the template without the footer author’s 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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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>
|
||||
@@ -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 & 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 & 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() ?>
|
||||
@@ -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> <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('&','&').replaceAll('<','<')
|
||||
.replaceAll('>','>').replaceAll('"','"').replaceAll("'", "'");
|
||||
}
|
||||
|
||||
// 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 & 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() ?>
|
||||
@@ -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;"> </span> 1 Absence — Auto Email</span>
|
||||
<span><span class="badge rounded-pill" style="background-color:#fadb14;color:#000;"> </span> 2 Absences (row/≤3 weeks) — Team Notify</span>
|
||||
<span><span class="badge rounded-pill" style="background-color:#fa8c16;"> </span> 3 Absences (row/≤4 weeks) — Team Notify</span>
|
||||
<span><span class="badge rounded-pill" style="background-color:#ff4d4f;"> </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;"> </span> 2 Lates (row/≤3 weeks) — Auto Email</span>
|
||||
<span><span class="badge rounded-pill" style="background-color:#002766;"> </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('&', '&').replaceAll('<', '<')
|
||||
.replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", "'");
|
||||
}
|
||||
|
||||
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 & 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']) ?> <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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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() ?>
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
© <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 author’s credit link/attribution link/backlink. If you'd like to use the template without the footer author’s 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,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() ?>
|
||||
@@ -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 we’ll just print in the array’s 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>
|
||||
@@ -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">
|
||||
© <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 author’s credit link/attribution link/backlink. If you'd like to use the template without the footer author’s 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>
|
||||
@@ -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() ?>
|
||||
@@ -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">
|
||||
© <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>
|
||||
@@ -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.";
|
||||
}
|
||||
?>
|
||||
@@ -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
Reference in New Issue
Block a user