recreate project
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user