This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 & 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')): ?>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 : '');
|
||||
|
||||
@@ -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 & 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 & 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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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') ?>">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user