fix pages and add distribution system
Tests / PHPUnit (push) Failing after 1m13s

This commit is contained in:
root
2026-07-15 23:03:51 -04:00
parent 7f3b24e47f
commit ba87598d3a
38 changed files with 1362 additions and 658 deletions
-28
View File
@@ -17,34 +17,6 @@
</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') ?>
@@ -4,7 +4,6 @@
<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">
@@ -4,7 +4,6 @@
<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>
@@ -5,37 +5,26 @@
<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 class="col-md-2">
<label for="sectionCount" class="form-label">Number of Sections</label>
<input type="number" min="1" id="sectionCount" class="form-control" placeholder="e.g. 2" />
</div>
<div class="col-md-4 d-flex gap-2">
<div class="col-md-2">
<label for="minStudents" class="form-label">Minimum Students</label>
<input type="number" min="1" id="minStudents" class="form-control" placeholder="e.g. 20" />
</div>
<div class="col-md-2">
<label for="maxStudents" class="form-label">Maximum Students</label>
<input type="number" min="1" id="maxStudents" class="form-control" placeholder="Optional" />
</div>
<div class="col-md-3 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">
<div class="col-md-3 text-end">
<span id="pageMsg" class="small text-muted"></span>
</div>
</div>
@@ -46,7 +35,7 @@
<tr id="tblHeader">
<th>Class</th>
<th>Total</th>
<th>Sections Needed</th>
<th>Sections</th>
<th>Actions</th>
</tr>
</thead>
@@ -73,17 +62,18 @@
const tblBody = document.getElementById('tblBody');
const tblHeader = document.getElementById('tblHeader');
const perInput = document.getElementById('globalPer');
const sectionCountInput = document.getElementById('sectionCount');
const minInput = document.getElementById('minStudents');
const maxInput = document.getElementById('maxStudents');
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 selectedSectionCount() {
const count = parseInt(sectionCountInput.value || '0', 10);
return count > 0 ? count : '';
}
function ensureSectionColumns(sectionNames) {
@@ -129,7 +119,7 @@
const tdNeed = document.createElement('td');
tdNeed.className = 'text-end need-cell';
tdNeed.textContent = calcNeeded(r.total);
tdNeed.textContent = selectedSectionCount();
tr.appendChild(tdNeed);
const tdAct = document.createElement('td');
@@ -143,24 +133,36 @@
tblBody.appendChild(tr);
rowIndexByClassId[r.class_id] = tr;
});
rows.forEach(function(r){
if (Array.isArray(r.sections) && r.sections.length) {
renderSectionsForRow(r.class_section_name || '', r.sections);
}
});
}
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);
if (needCell) needCell.textContent = selectedSectionCount();
});
}
function runDistribution(baseSectionId, baseName) {
const per = parseInt(perInput.value || '0', 10);
if (!per || per <= 0) { msgEl.textContent = 'Enter students per section first.'; return; }
const sectionCount = parseInt(sectionCountInput.value || '0', 10);
const minStudents = parseInt(minInput.value || '0', 10);
const maxStudents = parseInt(maxInput.value || '0', 10);
if (!sectionCount || sectionCount <= 0 || !minStudents || minStudents <= 0) {
msgEl.textContent = 'Enter number of sections and minimum students 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('section_count', String(sectionCount));
fd.append('min_students_per_section', String(minStudents));
if (maxStudents > 0) fd.append('max_students_per_section', String(maxStudents));
fd.append('school_year', selectedYear);
// CSRF
const csrfNameEl = document.getElementById('csrfName');
@@ -184,24 +186,39 @@
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) +')';
});
renderSectionsForRow(baseName || '', res.sections);
})
.catch(() => { msgEl.textContent = 'Failed to distribute. Please try again.'; });
}
function renderSectionsForRow(baseName, sections) {
const names = sections.map(s => s.class_section_name).filter(Boolean);
ensureSectionColumns(names);
const tr = Array.from(document.querySelectorAll('#tblBody tr')).find(function(_tr){
return (_tr.children[0].textContent || '') === (baseName || '');
});
if (!tr) return;
sections.forEach(function(s){
const td = Array.from(tr.querySelectorAll('td[data-section]')).find(cell => cell.dataset.section === s.class_section_name);
if (!td) return;
const studentNames = Array.isArray(s.student_names) ? s.student_names : [];
td.innerHTML = '';
const count = document.createElement('div');
count.className = 'fw-semibold small mb-1';
count.textContent = (s.total ?? studentNames.length) + ' students';
td.appendChild(count);
const list = document.createElement('div');
list.className = 'small';
list.style.whiteSpace = 'normal';
list.textContent = studentNames.length ? studentNames.join(', ') : 'No students assigned';
td.appendChild(list);
});
}
function loadTotals() {
msgEl.textContent = 'Loading totals...';
fetch(totalsUrl, { headers: { 'X-Requested-With': 'XMLHttpRequest' }})
@@ -215,10 +232,14 @@
.catch(() => { msgEl.textContent = 'Failed to load totals.'; });
}
refreshBtn.addEventListener('click', function(){ updateNeeds(); });
refreshBtn.addEventListener('click', function(){ loadTotals(); });
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; }
const sectionCount = parseInt(sectionCountInput.value || '0', 10);
const minStudents = parseInt(minInput.value || '0', 10);
if (!sectionCount || sectionCount <= 0 || !minStudents || minStudents <= 0) {
msgEl.textContent = 'Enter number of sections and minimum students 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 }))
@@ -231,7 +252,7 @@
}
msgEl.textContent = 'All distributions completed.';
});
perInput.addEventListener('input', function(){ updateNeeds(); });
sectionCountInput.addEventListener('input', function(){ updateNeeds(); });
loadTotals();
})();
@@ -5,29 +5,6 @@
<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; ?>
@@ -59,10 +36,7 @@
<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>
<input type="hidden" id="autoDistYear" name="school_year" value="<?= esc($selectedYear ?? '') ?>" />
<div class="col-md-2 d-grid">
<button type="submit" class="btn btn-outline-primary">Auto-Distribute</button>
</div>
@@ -17,25 +17,6 @@
<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>
<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>
-1
View File
@@ -3,7 +3,6 @@
<div class="container-fluid mt-4">
<h2 class="text-center mt-4 mb-3">Discounts</h2>
<?= $this->include('partials/academic_filter') ?>
<div class="d-flex gap-2 mb-3 justify-content-end">
<div>
<a href="/discount/create" class="btn btn-primary">Create New Voucher</a>
@@ -12,34 +12,11 @@
</div>
</div>
<?php endif; ?>
<div class="row g-2 align-items-center justify-content-center mb-2">
<div class="col-auto"><label for="schoolYearSelect" class="col-form-label">School year</label></div>
<div class="col-auto">
<form method="get" action="<?= site_url('enroll_withdraw/enrollment_withdrawal') ?>" class="d-flex align-items-center gap-2">
<select id="schoolYearSelect" name="schoolYear" class="form-select form-select-sm" style="min-width:180px;">
<?php if (!empty($schoolYears) && is_array($schoolYears)): ?>
<?php foreach ($schoolYears as $y): ?>
<?php $yval = is_array($y) && isset($y['school_year']) ? (string)$y['school_year'] : (string)$y; ?>
<option value="<?= esc($yval) ?>" <?= (isset($selectedYear) && (string)$selectedYear === $yval) ? 'selected' : '' ?>><?= esc($yval) ?></option>
<?php endforeach; ?>
<?php else: ?>
<option value="<?= esc($selectedYear ?? '') ?>" selected><?= esc($selectedYear ?? '') ?></option>
<?php endif; ?>
</select>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<label for="ewSemester" class="col-form-label">Semester</label>
<select id="ewSemester" 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 (empty($isCurrentYear)): ?>
<div class="text-center mb-2">
<span class="badge bg-secondary">Read-only (Past Year)</span>
</div>
<?php if (isset($selectedYear, $currentYear) && (string)$selectedYear !== (string)$currentYear): ?>
<div class="col-auto"><span class="badge bg-secondary">Read-only (Past Year)</span></div>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Display success and error messages -->
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success">
@@ -161,7 +138,7 @@
<!-- Update Enrollment -->
<td>
<select <?= (isset($selectedYear, $currentYear) && (string)$selectedYear !== (string)$currentYear) ? 'disabled' : '' ?>
<select <?= empty($isCurrentYear) ? 'disabled' : '' ?>
name="enrollment_status[<?= $sid ?>]"
class="form-control enrollment-status"
data-student-id="<?= $sid ?>"
@@ -182,7 +159,7 @@
<td>
<?php $isUnderReview = (strtolower($student['enrollment_status'] ?? '') === 'admission under review'); ?>
<?php if ($isUnderReview): ?>
<select class="form-select form-select-sm assign-class-select" <?= (isset($selectedYear, $currentYear) && (string)$selectedYear !== (string)$currentYear) ? 'disabled' : '' ?>
<select class="form-select form-select-sm assign-class-select" <?= empty($isCurrentYear) ? 'disabled' : '' ?>
data-student-id="<?= $sid ?>"
data-parent-id="<?= $pid ?>">
<option value="">— Select class section —</option>
@@ -212,7 +189,7 @@
</div>
<!-- Centered action buttons under the table -->
<div class="d-flex justify-content-center gap-2 my-3">
<button type="button" id="bulkSaveGenerateButton" class="btn btn-success btn-lg" <?= (isset($selectedYear, $currentYear) && (string)$selectedYear !== (string)$currentYear) ? 'disabled title="Editing disabled for non-current year"' : '' ?> >
<button type="button" id="bulkSaveGenerateButton" class="btn btn-success btn-lg" <?= empty($isCurrentYear) ? 'disabled title="Editing disabled for non-current year"' : '' ?> >
Save &amp; Generate Invoice
</button>
<span id="bulkProgress" class="small text-muted d-none">Processing…</span>
@@ -9,7 +9,6 @@
<span class="badge bg-secondary ms-2"><?= (int)$total_new ?></span>
<?php endif; ?>
</h2>
<?= $this->include('partials/academic_filter') ?>
<!-- Flash messages -->
<?php if (session()->getFlashdata('success')): ?>
-1
View File
@@ -3,7 +3,6 @@
<div class="container-fluid mt-4">
<h2 class="text-center mt-4 mb-3">Expense / Purchase</h2>
<?= $this->include('partials/academic_filter') ?>
<div class="d-flex gap-2 mb-3 justify-content-end">
<a href="<?= base_url('expenses/create') ?>" class="btn btn-success mb-3">Add New</a>
</div>
-1
View File
@@ -2,7 +2,6 @@
<?= $this->section('content') ?>
<div class="container-fluid">
<h2 class="text-center mt-4 mb-3">Incidents Management</h2>
<?= $this->include('partials/academic_filter') ?>
<!-- Flash Success Message -->
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success">
+56 -99
View File
@@ -11,7 +11,6 @@
data-notifications-endpoint="<?= esc($endpoint) ?>"
data-default-target-group="<?= esc($defaultTarget) ?>">
<h2 class="text-center mt-4 mb-3">Active Notifications</h2>
<?= $this->include('partials/academic_filter') ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
@@ -61,53 +60,6 @@
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- FixedHeader helpers / assets ---
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 ensureFixedHeaderAssets() {
const hasFH = !!(window.jQuery && window.jQuery.fn && window.jQuery.fn.dataTable && window.jQuery.fn.dataTable.FixedHeader);
if (hasFH) return Promise.resolve();
return 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')
]).catch(() => {});
}
// Start fetching FixedHeader in parallel
ensureFixedHeaderAssets();
const container = document.querySelector('[data-notifications-endpoint]');
if (!container) {
return;
@@ -123,6 +75,7 @@
const resetBtn = document.getElementById('resetFilterBtn');
let lastPayload = [];
let dataTable = null;
const showError = (message) => {
if (!alertBox) {
@@ -140,23 +93,16 @@
alertBox.textContent = '';
};
const destroyDataTable = () => {
if (!window.jQuery || !window.jQuery.fn || !window.jQuery.fn.DataTable) {
return;
}
if (window.jQuery.fn.DataTable.isDataTable(tableSelector)) {
window.jQuery(tableSelector).DataTable().clear().destroy();
}
};
const initDataTable = () => {
if (!window.jQuery || !window.jQuery.fn || !window.jQuery.fn.DataTable) {
return;
return null;
}
const opts = {
if (dataTable) {
return dataTable;
}
dataTable = window.jQuery(tableSelector).DataTable({
pageLength: 25,
responsive: true,
// Sort by Scheduled At desc, then Created At desc by default
order: [[5, 'desc'], [7, 'desc']],
columnDefs: [
{
@@ -164,22 +110,9 @@
orderable: false,
},
],
};
});
if (window.jQuery.fn.dataTable && window.jQuery.fn.dataTable.FixedHeader) {
opts.fixedHeader = { header: true, headerOffset: getFixedHeaderOffset() };
}
const dt = window.jQuery(tableSelector).DataTable(opts);
// Attach FixedHeader if it loads slightly later
if (!opts.fixedHeader) {
ensureFixedHeaderAssets().then(() => {
if (window.jQuery.fn.dataTable && window.jQuery.fn.dataTable.FixedHeader) {
try { new window.jQuery.fn.dataTable.FixedHeader(dt, { header: true, headerOffset: getFixedHeaderOffset() }); } catch (_) {}
}
});
}
return dataTable;
};
const formatDateTime = (value) => {
@@ -208,21 +141,32 @@
};
const renderTable = () => {
destroyDataTable();
tableBody.innerHTML = '';
if (!dataTable && window.jQuery && window.jQuery.fn && window.jQuery.fn.DataTable) {
tableBody.innerHTML = '';
}
const dt = initDataTable();
if (dt) {
dt.clear();
} else {
tableBody.innerHTML = '';
}
if (!Array.isArray(lastPayload) || lastPayload.length === 0) {
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 8;
cell.classList.add('text-center', 'text-muted');
cell.textContent = 'No active notifications found.';
row.appendChild(cell);
tableBody.appendChild(row);
initDataTable();
if (dt) {
dt.draw();
} else {
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.colSpan = 8;
cell.classList.add('text-center', 'text-muted');
cell.textContent = 'No active notifications found.';
row.appendChild(cell);
tableBody.appendChild(row);
}
return;
}
const rows = [];
lastPayload.forEach((entry, index) => {
const row = document.createElement('tr');
if (entry.isExpired) {
@@ -265,10 +209,15 @@
row.appendChild(cell);
});
tableBody.appendChild(row);
if (!dt) {
tableBody.appendChild(row);
}
rows.push(row);
});
initDataTable();
if (dt) {
dt.rows.add(rows).draw();
}
};
const buildUrl = (target) => {
@@ -290,11 +239,15 @@
clearError();
const url = buildUrl(targetGroup);
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-muted">Loading notifications…</td>
</tr>
`;
if (dataTable) {
dataTable.clear().draw();
} else {
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-muted">Loading notifications…</td>
</tr>
`;
}
return fetch(url, {
headers: {
@@ -314,12 +267,16 @@
})
.catch((error) => {
showError(error.message || 'Unable to load notifications.');
destroyDataTable();
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-danger">Failed to load notifications.</td>
</tr>
`;
const dt = initDataTable();
if (dt) {
dt.clear().draw();
} else {
tableBody.innerHTML = `
<tr>
<td colspan="8" class="text-center text-danger">Failed to load notifications.</td>
</tr>
`;
}
});
};
-1
View File
@@ -15,7 +15,6 @@
data-csrf-name="<?= esc($csrfTokenName) ?>"
data-csrf-value="<?= esc($csrfTokenValue) ?>">
<h2 class="text-center mt-4 mb-3">Deleted Notifications</h2>
<?= $this->include('partials/academic_filter') ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')) ?></div>
+16 -1
View File
@@ -1,4 +1,19 @@
<?php if (!empty($schoolYearSelectorEnabled) && isset($schoolYearContext, $schoolYearOptions)): ?>
<?php
$currentPath = trim(service('request')->getUri()->getPath(), '/');
$hiddenOnPaths = [
'administrator/emergency_contact',
'flags/flags_management',
'administrator/calendar_view',
'administrator/student_class_assignment',
'administrator/sections/auto-distribute',
'admin/enrollment/new-students',
'payment/unpaid-parents',
'discounts/list',
'expenses/index',
'reimbursements/index',
];
?>
<?php if (!in_array($currentPath, $hiddenOnPaths, true) && !empty($schoolYearSelectorEnabled) && isset($schoolYearContext, $schoolYearOptions)): ?>
<?php
$query = service('request')->getUri()->getQuery();
$returnTo = current_url() . ($query !== '' ? '?' . $query : '');
+15 -43
View File
@@ -1,6 +1,7 @@
<!-- app/Views/payment/extra_charges.php -->
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php $isSchoolYearReadonly = (bool)($isSchoolYearReadonly ?? false); ?>
<div class="container-fluid py-4">
@@ -10,46 +11,23 @@
<?php if (session()->get('error')): ?>
<div class="alert alert-danger"><?= session()->get('error') ?></div>
<?php endif; ?>
<?php if ($isSchoolYearReadonly): ?>
<div class="alert alert-warning" role="alert">
This school year is read-only. Charges cannot be changed.
</div>
<?php endif; ?>
<!-- Centered School Year filter (match compact design) -->
<div class="d-flex justify-content-center mb-3">
<form id="chargesYearFilter" class="d-flex align-items-center gap-3" method="get" action="<?= site_url('admin/charges') ?>">
<div class="d-flex justify-content-between align-items-center mb-3 gap-3 flex-wrap">
<h3 class="mb-0">
Add &amp; Deduct Charges
<span class="text-muted ms-2">(<?= esc($schoolYear) ?> - <?= esc($semester) ?>)</span>
</h3>
<div class="d-flex justify-content-between align-items-center mb-3 gap-3 flex-wrap">
<h3 class="mb-0">
Add &amp; Deduct Charges
<span class="text-muted ms-2">(<?= esc($schoolYear) ?> - <?= esc($semester) ?>)</span>
</h3>
<div class="d-flex align-items-center gap-3 flex-wrap">
<label for="schoolYear" class="form-label mb-0">School year</label>
<select id="schoolYear"
name="school_year"
class="form-select form-select-sm rounded-pill px-3"
style="min-width: 180px; width: auto;">
<?php foreach (($schoolYears ?? []) as $sy): ?>
<option value="<?= esc($sy) ?>" <?= (string)$schoolYear === (string)$sy ? 'selected' : '' ?>>
<?= esc($sy) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="d-flex gap-2 mb-3 justify-content-end"></div>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#chargeModal">
<i class="bi bi-plus-circle"></i> Add Charge
</button>
</div>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#chargeModal" <?= $isSchoolYearReadonly ? 'disabled' : '' ?>>
<i class="bi bi-plus-circle"></i> Add Charge
</button>
</div>
<?php if (!empty($status)): ?>
<input type="hidden" name="status" value="<?= esc($status) ?>">
<?php endif; ?>
<?php if (!empty($parentId)): ?>
<input type="hidden" name="parent_id" value="<?= (int)$parentId ?>">
<?php endif; ?>
</form>
</div>
<div class="card shadow-sm">
<div class="card-body table-responsive">
<?php $pageTotal = 0.0; if (!empty($rows)) { foreach ($rows as $__r) { $pageTotal += (float)($__r['amount'] ?? 0); } } ?>
@@ -192,11 +170,6 @@
<?= $this->section('scripts') ?>
<script>
$(function() {
// Auto-apply School Year when selection changes (no Filter button)
$('#chargesYearFilter select[name="school_year"]').on('change', function() {
this.form.submit();
});
const $parent = $('#parentSelect');
const $invId = $('#invoiceIdHidden');
const $invNum = $('#invoiceNumberDisplay');
@@ -221,8 +194,7 @@
if (!pid) return;
$.getJSON('<?= site_url('admin/charges/invoices') ?>', {
parent_id: pid,
school_year: '<?= esc($schoolYear) ?>'
parent_id: pid
}).done(function(data) {
const items = (data && Array.isArray(data.results)) ? data.results : [];
const chosen = pickDefaultInvoice(items);
-2
View File
@@ -27,8 +27,6 @@
<div class="hint">Balance is summed across invoices for the selected school year.</div>
</div>
<?= $this->include('partials/academic_filter') ?>
<?php $flashStatus = session()->getFlashdata('status'); $flashError = session()->getFlashdata('error'); ?>
<?php if (!empty($flashStatus)): ?>
<div class="alert alert-success"><?= esc($flashStatus) ?></div>
+7 -1
View File
@@ -1,5 +1,6 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<?php $isSchoolYearReadonly = (bool) ($isSchoolYearReadonly ?? false); ?>
<div class="container-fluid py-5">
<div class="row">
@@ -10,6 +11,11 @@
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if ($isSchoolYearReadonly): ?>
<div class="alert alert-warning" role="alert">
This school year is read-only. Print request statuses cannot be changed.
</div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?= session()->getFlashdata('error') ?>
@@ -90,7 +96,7 @@
</span>
</td>
<td>
<?php if ($request['status'] != 'delivered'): ?>
<?php if (!$isSchoolYearReadonly && $request['status'] != 'delivered'): ?>
<form action="<?= site_url('print-requests/update/' . $request['id']) ?>" method="post" class="d-flex">
<?= csrf_field() ?>
<input type="hidden" name="admin_id" value="<?= session()->get('user_id') ?>">
-23
View File
@@ -5,16 +5,6 @@
<h2 class="text-center mt-4 mb-3">Reimbursed Expenses</h2>
<form method="get" action="<?= base_url('reimbursements') ?>" class="row g-3 mb-4">
<!-- Semester -->
<div class="col-md-2">
<label class="form-label">Semester</label>
<select name="semester" class="form-select">
<option value="">All</option>
<option value="Fall" <?= request()->getGet('semester') === 'Fall' ? 'selected' : '' ?>>Fall</option>
<option value="Spring" <?= request()->getGet('semester') === 'Spring' ? 'selected' : '' ?>>Spring</option>
</select>
</div>
<!-- Status (from reimbursements.status) -->
<div class="col-md-2">
<label class="form-label">Status</label>
@@ -28,19 +18,6 @@
</select>
</div>
<!-- School Year -->
<div class="col-md-2">
<label class="form-label">School Year</label>
<select name="school_year" class="form-select">
<option value="">All</option>
<?php foreach ($schoolYears as $sy): ?>
<option value="<?= esc($sy) ?>" <?= request()->getGet('school_year') == $sy ? 'selected' : '' ?>>
<?= esc($sy) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- User -->
<div class="col-md-3">
<label class="form-label">Reimbursed To</label>
-1
View File
@@ -13,7 +13,6 @@
<div class="wrapper">
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">Assign Role to Users</h2>
<?= $this->include('partials/academic_filter') ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success"><?= esc(session()->getFlashdata('success')); ?></div>
-1
View File
@@ -3,7 +3,6 @@
<div class="container-fluid mt-4">
<h2 class="text-center mt-4 mb-3">Staff List</h2>
<?= $this->include('partials/academic_filter') ?>
<?php if (!empty($issues_count) && (int)$issues_count > 0): ?>
<div class="alert alert-warning" role="alert">
-1
View File
@@ -13,7 +13,6 @@
<div class="wrapper">
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">Login Activity</h2>
<?= $this->include('partials/academic_filter') ?>
<div id="loginActivityAlert" class="alert alert-danger d-none" role="alert"></div>
-1
View File
@@ -11,7 +11,6 @@
<div class="content"></div>
<h2 class="text-center mt-4 mb-3">User List</h2>
<?= $this->include('partials/academic_filter') ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">