fix enrollment logic, add financial aid, fix class distribution
Tests / PHPUnit (push) Failing after 1m6s

This commit is contained in:
root
2026-08-15 15:07:16 -04:00
parent c12bb59372
commit 4603d9ced2
95 changed files with 6892 additions and 1295 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,54 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid py-3">
<h2 class="mb-3">Financial Aid Requests</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 method="get" class="row g-2 mb-3">
<div class="col-md-3">
<input class="form-control" name="school_year" placeholder="School year" value="<?= esc($schoolYear ?? '') ?>">
</div>
<div class="col-md-2">
<button class="btn btn-primary" type="submit">Filter</button>
</div>
</form>
<div class="table-responsive">
<table class="table table-striped no-mgmt-sticky" data-no-mgmt-sticky>
<thead>
<tr>
<th>ID</th>
<th>Parent</th>
<th>Year</th>
<th>Status</th>
<th>Requested</th>
<th>Submitted</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach (($requests ?? []) as $row): ?>
<?php $parent = $parents[(int) ($row['parent_id'] ?? 0)] ?? []; ?>
<tr>
<td><?= (int) ($row['id'] ?? 0) ?></td>
<td><?= esc(trim(($parent['firstname'] ?? '') . ' ' . ($parent['lastname'] ?? '')) ?: ('#' . (int) ($row['parent_id'] ?? 0))) ?></td>
<td><?= esc($row['school_year'] ?? '') ?></td>
<td><?= esc($row['status'] ?? '') ?></td>
<td><?= $row['requested_amount'] !== null && $row['requested_amount'] !== '' ? '$' . number_format((float) $row['requested_amount'], 2) : '—' ?></td>
<td><?= esc($row['created_at'] ?? '') ?></td>
<td><a class="btn btn-sm btn-outline-primary" href="<?= site_url('administrator/financial-aid/' . (int) $row['id']) ?>">Review</a></td>
</tr>
<?php endforeach; ?>
<?php if (empty($requests)): ?>
<tr><td colspan="7" class="text-muted text-center">No financial aid requests found.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?= $this->endSection() ?>
@@ -0,0 +1,52 @@
<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container py-3">
<a href="<?= site_url('administrator/financial-aid') ?>" class="small">&larr; Back to queue</a>
<h2 class="mt-2">Review Financial Aid Request #<?= (int) ($requestRow['id'] ?? 0) ?></h2>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<div class="border rounded p-3 mb-3">
<div><strong>Parent:</strong> <?= esc(trim(($parent['firstname'] ?? '') . ' ' . ($parent['lastname'] ?? ''))) ?> <?= esc($parent['email'] ?? '') ?></div>
<div><strong>School year:</strong> <?= esc($requestRow['school_year'] ?? '') ?></div>
<div><strong>Status:</strong> <?= esc($requestRow['status'] ?? '') ?></div>
<div><strong>Household size:</strong> <?= esc((string) ($requestRow['household_size'] ?? 'Not provided')) ?></div>
<div><strong>Requested amount:</strong> <?= $requestRow['requested_amount'] !== null && $requestRow['requested_amount'] !== '' ? '$' . number_format((float) $requestRow['requested_amount'], 2) : 'Not specified' ?></div>
<div class="mt-2"><strong>Need statement</strong></div>
<p><?= nl2br(esc($requestRow['need_statement'] ?? '')) ?></p>
<div><strong>Students</strong></div>
<ul>
<?php foreach (($students ?? []) as $student): ?>
<li><?= esc(trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''))) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php if (in_array((string) ($requestRow['status'] ?? ''), ['submitted', 'under_review'], true)): ?>
<div class="row g-3">
<div class="col-md-6">
<form method="post" action="<?= site_url('administrator/financial-aid/' . (int) $requestRow['id'] . '/approve') ?>" class="border rounded p-3">
<?= csrf_field() ?>
<h5>Approve</h5>
<label class="form-label" for="admin_amount">Amount to apply</label>
<input class="form-control mb-2" type="number" min="0.01" step="0.01" name="admin_amount" id="admin_amount" required value="<?= esc(old('admin_amount', $requestRow['requested_amount'] ?? '')) ?>">
<label class="form-label" for="admin_note">Note</label>
<textarea class="form-control mb-2" name="admin_note" id="admin_note" rows="3"><?= esc(old('admin_note')) ?></textarea>
<button class="btn btn-success" type="submit">Approve and apply to invoice</button>
</form>
</div>
<div class="col-md-6">
<form method="post" action="<?= site_url('administrator/financial-aid/' . (int) $requestRow['id'] . '/deny') ?>" class="border rounded p-3">
<?= csrf_field() ?>
<h5>Deny</h5>
<label class="form-label" for="deny_note">Reason</label>
<textarea class="form-control mb-2" name="admin_note" id="deny_note" rows="3" required><?= esc(old('admin_note')) ?></textarea>
<button class="btn btn-danger" type="submit">Deny request</button>
</form>
</div>
</div>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
@@ -152,17 +152,17 @@
<div class="card-body">
<div class="row g-3 align-items-end mb-2">
<div class="col-md-2">
<label for="sectionCount" class="form-label">Number of Sections</label>
<label for="sectionCount" class="form-label">Max Number of Sections</label>
<input type="number" min="1" id="sectionCount" class="form-control" placeholder="e.g. 2" />
</div>
<div class="col-md-2">
<label for="minStudents" class="form-label">Minimum Students</label>
<label for="minStudents" class="form-label">Min Students Per Section</label>
<input type="number" min="1" id="minStudents" class="form-control" placeholder="e.g. 20" />
</div>
<div class="col-md-2">
<!--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-->
<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>
@@ -217,6 +217,13 @@
</tr>
</thead>
<tbody id="tblBody"></tbody>
<tfoot class="table-light">
<tr>
<th>Total</th>
<th class="text-end" id="allStudentsTotal">0</th>
<th></th>
</tr>
</tfoot>
</table>
</div>
</div>
@@ -247,6 +254,7 @@
}, $classes ?? [])), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>;
const tblBody = document.getElementById('tblBody');
const allStudentsTotal = document.getElementById('allStudentsTotal');
const sectionCountInput = document.getElementById('sectionCount');
const minInput = document.getElementById('minStudents');
const maxInput = document.getElementById('maxStudents');
@@ -352,6 +360,20 @@
});
});
if (!resolvedClassId && selectedClassId > 0) {
const firstSection = (sectionsByClassId[String(selectedClassId)] || [])[0] || null;
if (firstSection) {
select.value = String(firstSection.id);
resolvedClassId = selectedClassId;
} else {
const baseClass = baseClasses.find(c => c.class_id === selectedClassId) || null;
if (baseClass) {
select.value = String(baseClass.class_section_id);
resolvedClassId = selectedClassId;
}
}
}
const selectedOption = select.options[select.selectedIndex] || null;
if (!resolvedClassId && selectedOption) {
resolvedClassId = parseInt(selectedOption.dataset.classId || '0', 10);
@@ -380,8 +402,10 @@
function buildInitialTable(rows) {
tblBody.innerHTML = '';
rowIndexByClassId = {};
let allTotal = 0;
rows.forEach(function(r){
allTotal += parseInt(r.total || '0', 10) || 0;
const tr = document.createElement('tr');
tr.dataset.classId = r.class_id;
tr.dataset.classSectionId = r.class_section_id;
@@ -417,13 +441,16 @@
renderSectionsForRow(r.class_section_id, r.sections, r.students || []);
}
});
if (allStudentsTotal) {
allStudentsTotal.textContent = String(allTotal);
}
applyStudentSearch();
}
function runDistribution(baseSectionId, baseName) {
const sectionCount = parseInt(sectionCountInput.value || '0', 10);
const minStudents = parseInt(minInput.value || '0', 10);
const maxStudents = parseInt(maxInput.value || '0', 10);
const maxStudents = parseInt((maxInput && maxInput.value) || '0', 10);
if (!sectionCount || sectionCount <= 0 || !minStudents || minStudents <= 0) {
msgEl.textContent = 'Enter number of sections and minimum students first.';
return;
@@ -611,6 +638,7 @@
student_name: assignment.student_name || 'Student',
age_at_reference: assignment.age_at_reference ?? null,
gender: assignment.gender || '',
previous_final_score: assignment.previous_final_score ?? null,
last_year_class_section: assignment.last_year_class_section || '',
class_id: parseInt(assignment.class_id || section.class_id || '0', 10),
class_section_id: parseInt(assignment.class_section_id || section.class_section_id || '0', 10),
@@ -623,6 +651,27 @@
return out;
}
function assignmentStats(assignments) {
const stats = { male: 0, female: 0, scoreTotal: 0, scoreCount: 0 };
(Array.isArray(assignments) ? assignments : []).forEach(function(assignment){
const gender = String(assignment.gender || '').trim().toLowerCase();
if (gender === 'female' || gender === 'f') {
stats.female++;
} else if (gender === 'male' || gender === 'm') {
stats.male++;
}
const score = Number(assignment.previous_final_score);
if (Number.isFinite(score)) {
stats.scoreTotal += score;
stats.scoreCount++;
}
});
stats.averageScore = stats.scoreCount ? stats.scoreTotal / stats.scoreCount : null;
return stats;
}
function renderClassRoster(tr, assignments) {
const wrap = document.createElement('div');
wrap.className = 'distribution-class-roster';
@@ -641,6 +690,23 @@
title.appendChild(count);
wrap.appendChild(title);
const stats = assignmentStats(assignments);
const meta = document.createElement('div');
meta.className = 'distribution-meta';
if (stats.averageScore !== null) {
const avg = document.createElement('span');
avg.className = 'badge text-bg-light';
avg.textContent = 'Avg ' + stats.averageScore.toFixed(2);
meta.appendChild(avg);
}
if (stats.male || stats.female) {
const gender = document.createElement('span');
gender.className = 'badge text-bg-light';
gender.textContent = 'M ' + stats.male + ' / F ' + stats.female;
meta.appendChild(gender);
}
if (meta.children.length) wrap.appendChild(meta);
if (!assignments.length) {
const empty = document.createElement('div');
empty.className = 'distribution-empty';
+3 -24
View File
@@ -30,7 +30,6 @@ $warningText = static function (array $warnings): string {
'include_event_only' => !empty($filters['include_event_only']) ? '1' : '0',
'include_paid_invoices' => !empty($filters['include_paid_invoices']) ? '1' : '0',
'unit_price' => $filters['unit_price'] ?? '',
'youth_unit_price' => $filters['youth_unit_price'] ?? '',
]);
?>
<a href="<?= site_url('administrator/tuition-forecast/export?' . $exportQuery) ?>" class="btn btn-success">
@@ -71,7 +70,7 @@ $warningText = static function (array $warnings): string {
</select>
</div>
<div class="col-md-3">
<label for="unit_price" class="form-label">Grades Unit Price</label>
<label for="unit_price" class="form-label">Base Tuition</label>
<input
type="number"
min="0"
@@ -80,19 +79,7 @@ $warningText = static function (array $warnings): string {
name="unit_price"
class="form-control"
value="<?= esc((string) ($filters['unit_price'] ?? ($summary['unit_price'] ?? ''))) ?>"
placeholder="New tuition unit price for grades">
</div>
<div class="col-md-3">
<label for="youth_unit_price" class="form-label">Youth Unit Price</label>
<input
type="number"
min="0"
step="0.01"
id="youth_unit_price"
name="youth_unit_price"
class="form-control"
value="<?= esc((string) ($filters['youth_unit_price'] ?? ($summary['youth_unit_price'] ?? ''))) ?>"
placeholder="New tuition unit price for youth">
placeholder="First student tuition">
</div>
<div class="col-md-2">
<div class="form-check">
@@ -149,19 +136,11 @@ $warningText = static function (array $warnings): string {
<div class="col-xl col-lg-3 col-md-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="text-muted small">Grades Unit Price</div>
<div class="text-muted small">Base Tuition</div>
<div class="fs-5 fw-semibold"><?= esc($fmtMoney($summary['unit_price'] ?? 0)) ?></div>
</div>
</div>
</div>
<div class="col-xl col-lg-3 col-md-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="text-muted small">Youth Unit Price</div>
<div class="fs-5 fw-semibold"><?= esc($fmtMoney($summary['youth_unit_price'] ?? 0)) ?></div>
</div>
</div>
</div>
<div class="col-xl col-lg-3 col-md-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
+508 -318
View File
@@ -1,256 +1,418 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container my-5">
<h3 class="text-center text-success" style="font-family: Arial, sans-serif;">Enroll in Classes</h3>
<?php if (!empty($selectedYear)): ?>
<div class="text-center text-muted small">School Year: <?= esc($selectedYear) ?></div>
<?php endif; ?>
</div>
<?= $this->section('styles') ?>
<style>
.enrollment-start-panel {
border: 1px solid #d8e7dc;
border-radius: 8px;
background: #f8fbf8;
padding: 1rem;
}
.enrollment-stepper {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: .5rem;
}
.enrollment-step {
border-bottom: 3px solid #dee2e6;
color: #6c757d;
font-size: .85rem;
padding: .35rem 0;
text-align: center;
}
.enrollment-step.is-active {
border-color: #198754;
color: #198754;
font-weight: 700;
}
.student-select-card {
border: 1px solid #dee2e6;
border-radius: 8px;
cursor: pointer;
padding: .85rem;
transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.student-select-card.is-selected {
background: #eef8f0;
border-color: #198754;
box-shadow: 0 0 0 .15rem rgba(25, 135, 84, .14);
}
.student-select-card.is-disabled {
background: #f8f9fa;
color: #6c757d;
cursor: not-allowed;
}
.student-select-icon {
align-items: center;
border: 1px solid #adb5bd;
border-radius: 50%;
display: inline-flex;
height: 1.65rem;
justify-content: center;
width: 1.65rem;
}
.student-select-card.is-selected .student-select-icon {
background: #198754;
border-color: #198754;
color: #fff;
}
.enrollment-policy-frame {
border: 1px solid #dee2e6;
border-radius: 8px;
height: min(54vh, 520px);
min-height: 320px;
width: 100%;
}
.enrollment-modal-footer {
gap: .5rem;
}
@media (max-width: 575.98px) {
.enrollment-stepper {
gap: .25rem;
}
.enrollment-step {
font-size: .72rem;
}
.enrollment-modal-footer {
align-items: stretch;
flex-direction: column;
}
.enrollment-modal-footer .btn {
width: 100%;
}
.student-select-card {
padding: .75rem;
}
.enrollment-status-table,
.enrollment-status-table thead,
.enrollment-status-table tbody,
.enrollment-status-table tr,
.enrollment-status-table th,
.enrollment-status-table td {
display: block;
width: 100%;
}
.enrollment-status-table thead {
display: none;
}
.enrollment-status-table {
border: 0;
}
.enrollment-status-table tr {
border: 1px solid #dee2e6;
border-radius: 8px;
margin-bottom: .85rem;
overflow: hidden;
}
.enrollment-status-table td {
align-items: flex-start;
border-bottom: 1px solid #eef1f3;
display: flex;
gap: .75rem;
justify-content: space-between;
padding: .75rem;
text-align: right;
}
.enrollment-status-table td:last-child {
border-bottom: 0;
}
.enrollment-status-table td::before {
color: #6c757d;
content: attr(data-label);
flex: 0 0 42%;
font-size: .8rem;
font-weight: 700;
text-align: left;
}
.enrollment-status-table td > * {
max-width: 58%;
}
}
</style>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?php
// Put this near the very top of the file (before output), or right after the extend/section lines.
$tz = (string) (config('School')->attendance['timezone'] ?? user_timezone());
$deadlineObj = (new DateTime($lastDayOfRegistration, new DateTimeZone($tz)))->setTime(23, 59, 59);
$nowObj = new DateTime('now', new DateTimeZone($tz));
$nowObj = new DateTime('now', new DateTimeZone($tz));
$deadlinePassed = $nowObj > $deadlineObj;
$deadlineISO = $deadlineObj->format('Y-m-d\TH:i:sP'); // for JS
$hasAcceptedSchoolPolicy = (bool) ($hasAcceptedSchoolPolicy ?? false);
$familyFinancialSummary = is_array($familyFinancialSummary ?? null) ? $familyFinancialSummary : [];
$money = static function ($amount) use ($familyFinancialSummary): string {
$currency = (string) ($familyFinancialSummary['currency'] ?? '$');
return $currency . number_format((float) $amount, 2);
};
$statusBadge = static function (?string $status): string {
$status = strtolower(trim((string) $status));
return match ($status) {
'admission under review' => '<span class="badge bg-primary">admission under review</span>',
'review & decision' => '<span class="badge bg-secondary">review &amp; decision</span>',
'payment pending' => '<span class="badge bg-warning text-dark">payment pending</span>',
'enrolled' => '<span class="badge bg-success">enrolled</span>',
'withdraw under review' => '<span class="badge bg-warning text-dark">withdraw under review</span>',
'refund pending' => '<span class="badge bg-info text-dark">refund pending</span>',
'withdrawn' => '<span class="badge bg-danger">withdrawn</span>',
'waitlist' => '<span class="badge bg-secondary">waitlist</span>',
'denied' => '<span class="badge bg-danger">denied</span>',
'not enrolled' => '<span class="badge bg-light text-dark">not enrolled</span>',
default => '<span class="badge bg-light text-dark">unknown</span>',
};
};
$enrollableCount = 0;
$withdrawableCount = 0;
foreach (($students ?? []) as $student) {
$eligibilityMessage = is_array($student['enrollment_eligibility_message'] ?? null) ? $student['enrollment_eligibility_message'] : ['blocking' => false];
if (($student['enrollment_status'] ?? '') === 'not enrolled' && !$deadlinePassed && $isEditable && empty($eligibilityMessage['blocking'])) {
$enrollableCount++;
}
if (($student['enrollment_status'] ?? '') === 'enrolled' && $isEditable) {
$withdrawableCount++;
}
}
?>
<!-- Registration Info -->
<div class="alert alert-info mb-3">
<p>Enrollment is the process of officially signing up your child for the upcoming school year.</p>
<ul>
<li>Last Day for Enrollment is <strong><?= esc(local_date($lastDayOfRegistration, 'm-d-Y')) ?></strong>.</li>
<li>Once you click "Save", the enrollment status will change to <strong>admission under review</strong>.</li>
<li>Payments are processed on the first day of school: <strong><?= esc(local_date($schoolStartDate, 'm-d-Y')) ?></strong>.</li>
</ul>
</div>
<?php if ($familyFinancialSummary !== []): ?>
<div class="border rounded p-3 mb-3 bg-light">
<div class="fw-semibold mb-2">Family Account Information</div>
<div class="row g-2">
<div class="col-md-4">
<span class="text-muted">Previous-year carry-over balance:</span>
<strong><?= esc($money($familyFinancialSummary['carry_over_balance'] ?? 0)) ?></strong>
</div>
<div class="col-md-4">
<span class="text-muted">Registration fee:</span>
<strong><?= esc($money($familyFinancialSummary['registration_fee'] ?? 0)) ?></strong>
</div>
<div class="col-md-4">
<span class="text-muted">Tuition due now:</span>
<strong><?= esc($money($familyFinancialSummary['tuition_due_at_registration'] ?? 0)) ?></strong>
</div>
<div class="col-md-4">
<span class="text-muted">Mandatory fees:</span>
<strong><?= esc($money($familyFinancialSummary['mandatory_fees'] ?? 0)) ?></strong>
</div>
<div class="col-md-4">
<span class="text-muted">Current-year account balance:</span>
<strong><?= esc($money($familyFinancialSummary['current_balance'] ?? 0)) ?></strong>
</div>
<div class="col-md-4">
<span class="text-muted">Total currently due:</span>
<strong><?= esc($money($familyFinancialSummary['amount_due'] ?? 0)) ?></strong>
</div>
</div>
<?php if (!empty($familyFinancialSummary['policy_message'])): ?>
<div class="small text-muted mt-2"><?= esc($familyFinancialSummary['policy_message']) ?></div>
<div class="container my-4">
<div class="text-center mb-4">
<h3 class="text-success" style="font-family: Arial, sans-serif;">Enroll in Classes</h3>
<?php if (!empty($selectedYear)): ?>
<div class="text-muted small">School Year: <?= esc($selectedYear) ?></div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<?php if (session()->getFlashdata('error')): ?>
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
<?php endif; ?>
<?php $hasAcceptedSchoolPolicy = (bool) ($hasAcceptedSchoolPolicy ?? false); ?>
<?php if (!empty($students)): ?>
<form action="<?= base_url('/parent/enroll_classes_handler') ?>" method="post">
<?= csrf_field() ?>
<input type="hidden" id="accept_school_policy_input" name="accept_school_policy" value="<?= $hasAcceptedSchoolPolicy ? '1' : '0' ?>">
<div class="table-responsive">
<table class="table table-striped table-bordered align-middle">
<thead>
<tr>
<th>#</th>
<th>School ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Age</th>
<th>Gender</th>
<th>Grade</th>
<th>Decision</th>
<th>Required Action</th>
<th>Enroll</th>
<th>Withdraw</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($students as $index => $student): ?>
<?php $eligibilityMessage = is_array($student['enrollment_eligibility_message'] ?? null) ? $student['enrollment_eligibility_message'] : ['message' => '', 'blocking' => false, 'level' => 'info']; ?>
<div class="alert alert-info mb-3">
<p>Enrollment is the process of officially signing up your child for the upcoming school year.</p>
<ul class="mb-0">
<li>Last Day for Enrollment is <strong><?= esc(local_date($lastDayOfRegistration, 'm-d-Y')) ?></strong>.</li>
<li>After submit, enrollment status changes to <strong>admission under review</strong>.</li>
<li>Payments are processed on the first day of school: <strong><?= esc(local_date($schoolStartDate, 'm-d-Y')) ?></strong>.</li>
</ul>
</div>
<?php if ($familyFinancialSummary !== []): ?>
<div class="border rounded p-3 mb-3 bg-light">
<div class="fw-semibold mb-2">Family Account Information</div>
<div class="row g-2">
<div class="col-md-4"><span class="text-muted">Previous-year carry-over balance:</span> <strong><?= esc($money($familyFinancialSummary['carry_over_balance'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Registration fee:</span> <strong><?= esc($money($familyFinancialSummary['registration_fee'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Tuition due now:</span> <strong><?= esc($money($familyFinancialSummary['tuition_due_at_registration'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Mandatory fees:</span> <strong><?= esc($money($familyFinancialSummary['mandatory_fees'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Current-year account balance:</span> <strong><?= esc($money($familyFinancialSummary['current_balance'] ?? 0)) ?></strong></div>
<div class="col-md-4"><span class="text-muted">Total currently due:</span> <strong><?= esc($money($familyFinancialSummary['amount_due'] ?? 0)) ?></strong></div>
</div>
<?php if (!empty($familyFinancialSummary['policy_message'])): ?>
<div class="small text-muted mt-2"><?= esc($familyFinancialSummary['policy_message']) ?></div>
<?php endif; ?>
<div class="small mt-2"><a href="<?= site_url('parent/financial-aid') ?>">Request financial aid</a></div>
</div>
<?php endif; ?>
<?php if (!empty($students)): ?>
<form action="<?= base_url('/parent/enroll_classes_handler') ?>" method="post" id="enrollmentFlowForm">
<?= csrf_field() ?>
<input type="hidden" id="accept_school_policy_input" name="accept_school_policy" value="<?= $hasAcceptedSchoolPolicy ? '1' : '0' ?>">
<div class="enrollment-start-panel mb-4">
<div class="row g-3 align-items-center">
<div class="col-lg">
<div class="fw-semibold">Enrollment process</div>
<div class="text-muted small">
Select students, review policy information, then submit enrollment.
</div>
</div>
<div class="col-lg-auto">
<button type="button"
class="btn btn-success btn-lg w-100"
id="startEnrollmentButton"
<?= (!$isEditable || $enrollableCount === 0 || $deadlinePassed) ? 'disabled' : '' ?>>
Start Enrollment
</button>
</div>
</div>
<?php if ($deadlinePassed): ?>
<div class="small text-danger mt-2">Enrollment closed on <?= esc($deadlineObj->format('m-d-Y')) ?>.</div>
<?php elseif ($enrollableCount === 0): ?>
<div class="small text-muted mt-2">No students are currently available for new enrollment.</div>
<?php endif; ?>
</div>
<div class="table-responsive mb-4">
<table class="table table-striped table-bordered align-middle enrollment-status-table">
<thead>
<tr>
<td><?= $index + 1 ?></td>
<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['age'] ?? 'N/A') ?></td>
<td><?= esc($student['gender'] ?? 'N/A') ?></td>
<td>
<?php
$section = $student['class_section'] ?? null;
echo $section
? esc(preg_replace_callback('/\byouth\b/i', fn($m) => ucfirst(strtolower($m[0])), $section))
: 'Not Assigned';
?>
</td>
<td><?= esc($student['transition_evaluation']['decision_label'] ?? 'Pending') ?></td>
<td><?= esc($student['required_action_label'] ?? 'Contact the school administration.') ?></td>
<!-- Enroll Checkbox -->
<td>
<?php if ($student['enrollment_status'] === 'not enrolled'): ?>
<?php $disableEnrollUI = $deadlinePassed || !$isEditable || (bool) ($eligibilityMessage['blocking'] ?? false); ?>
<input type="checkbox"
name="enroll[]"
value="<?= esc($student['id']) ?>"
data-decision-message="<?= esc($eligibilityMessage['message'] ?? '') ?>"
data-decision-blocking="<?= !empty($eligibilityMessage['blocking']) ? '1' : '0' ?>"
data-decision-message-target="enrollment-decision-message-<?= esc($student['id']) ?>"
<?= $disableEnrollUI ? 'disabled' : '' ?>>
<?php elseif (in_array($student['enrollment_status'], ['enrolled', 'admission under review', 'review & decision', 'payment pending', 'withdraw under review'])): ?>
<input type="checkbox" checked disabled>
<?php else: ?>
<input type="checkbox" disabled>
<?php endif; ?>
</td>
<!-- Withdraw Checkbox -->
<td>
<?php if ($student['enrollment_status'] === 'enrolled'): ?>
<input type="checkbox" name="withdraw[]" value="<?= esc($student['id']) ?>" <?= !$isEditable ? 'disabled' : '' ?>>
<?php elseif ($student['enrollment_status'] === 'withdrawn'): ?>
<input type="checkbox" checked disabled>
<?php else: ?>
<input type="checkbox" disabled>
<?php endif; ?>
</td>
<!-- Status -->
<td>
<?php
$status = strtolower(trim($student['enrollment_status'] ?? ''));
switch ($status) {
case 'admission under review':
echo '<span class="badge bg-primary">admission under review</span>';
break;
case 'review & decision':
echo '<span class="badge bg-secondary">review &amp; decision</span>';
break;
case 'payment pending':
echo '<span class="badge bg-warning text-dark">payment pending</span>';
break;
case 'enrolled':
echo '<span class="badge bg-success">enrolled</span>';
break;
case 'withdraw under review':
echo '<span class="badge bg-warning text-dark">withdraw under review</span>';
break;
case 'refund pending':
echo '<span class="badge bg-info text-dark">refund pending</span>';
break;
case 'withdrawn':
echo '<span class="badge bg-danger">withdrawn</span>';
break;
case 'waitlist':
echo '<span class="badge bg-secondary">waitlist</span>'; // lighter and neutral
break;
case 'denied':
echo '<span class="badge bg-danger">denied</span>'; // red stands out clearly
break;
case 'not enrolled':
echo '<span class="badge bg-light text-dark">not enrolled</span>'; // very neutral
break;
default:
echo '<span class="badge bg-light text-dark">unknown</span>';
}
?>
</td>
<th>Student</th>
<th>Grade</th>
<th>Decision</th>
<th>Required Action</th>
<th>Status</th>
<th>Withdraw</th>
</tr>
<?php if (($eligibilityMessage['message'] ?? '') !== ''): ?>
<tr id="enrollment-decision-message-<?= esc($student['id']) ?>" class="enrollment-decision-message-row">
<td colspan="12">
<div class="alert alert-<?= esc($eligibilityMessage['level'] ?? 'info') ?> mb-0">
<?= esc($eligibilityMessage['message']) ?>
</div>
</thead>
<tbody>
<?php foreach ($students as $student): ?>
<?php
$studentName = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? ''));
$studentName = $studentName !== '' ? $studentName : 'Student';
$section = $student['class_section'] ?? null;
$gradeLabel = $section
? preg_replace_callback('/\byouth\b/i', fn($m) => ucfirst(strtolower($m[0])), (string) $section)
: 'Not Assigned';
?>
<tr>
<td data-label="Student">
<div class="fw-semibold"><?= esc($studentName) ?></div>
<div class="text-muted small">School ID: <?= esc($student['school_id'] ?? 'N/A') ?></div>
</td>
<td data-label="Grade"><?= esc($gradeLabel) ?></td>
<td data-label="Decision"><?= esc($student['transition_evaluation']['decision_label'] ?? 'Pending') ?></td>
<td data-label="Required Action"><?= esc($student['required_action_label'] ?? 'Contact administration') ?></td>
<td data-label="Status"><?= $statusBadge($student['enrollment_status'] ?? '') ?></td>
<td data-label="Withdraw">
<?php if (($student['enrollment_status'] ?? '') === 'enrolled'): ?>
<div class="form-check form-switch m-0">
<input class="form-check-input" type="checkbox" name="withdraw[]" value="<?= esc($student['id']) ?>" id="withdraw-<?= esc($student['id']) ?>" <?= !$isEditable ? 'disabled' : '' ?>>
<label class="form-check-label small" for="withdraw-<?= esc($student['id']) ?>">Request</label>
</div>
<?php elseif (($student['enrollment_status'] ?? '') === 'withdrawn'): ?>
<span class="text-muted small">Withdrawn</span>
<?php else: ?>
<span class="text-muted small">Unavailable</span>
<?php endif; ?>
</td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Save Button -->
<?php
$today = local_date(utc_now(), 'Y-m-d');
$disableDueToDate = ($today >= $lastDayOfRegistration);
// Allow submit for withdrawals even after deadline; keep editability guard only
$disableSave = !$isEditable;
?>
<div class="d-flex justify-content-center">
<button type="submit"
class="btn btn-lg btn-success <?= $disableSave ? 'disabled' : '' ?>"
<?= $disableSave ? 'disabled' : '' ?>
title="<?php
if (!$isEditable) {
echo 'Editing is not allowed for this record.';
}
?>">
Submit
</button>
</div>
<br>
</form>
<?php else: ?>
<p>No students found for the selected school year. Please register your kids first.</p>
<?php endif; ?>
<!-- School Policy Modal -->
<div class="modal fade" id="schoolPolicyModal" tabindex="-1" aria-labelledby="schoolPolicyModalLabel" aria-hidden="true" data-bs-backdrop="static">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="schoolPolicyModalLabel">School Policies</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="modal-body">
<iframe src="<?= base_url('policy/school_policy') ?>" width="100%" height="520" frameborder="0" title="School Policies"></iframe>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" value="1" id="schoolPolicyAcceptedCheckbox" <?= $hasAcceptedSchoolPolicy ? 'checked disabled' : '' ?>>
<label class="form-check-label" for="schoolPolicyAcceptedCheckbox">
I have read and accept all school policies.
</label>
<?php if ($withdrawableCount > 0): ?>
<div class="d-flex justify-content-center mb-4">
<button type="submit" class="btn btn-outline-danger" id="withdrawSubmitButton">Submit Withdrawal Request</button>
</div>
<?php endif; ?>
<div class="modal fade" id="enrollmentFlowModal" tabindex="-1" aria-labelledby="enrollmentFlowModalLabel" aria-hidden="true" data-bs-backdrop="static">
<div class="modal-dialog modal-xl modal-dialog-scrollable modal-fullscreen-sm-down">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="enrollmentFlowModalLabel">Enroll your childs</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="enrollment-stepper mb-3" aria-label="Enrollment steps">
<div class="enrollment-step is-active" data-step-indicator="0">Students</div>
<div class="enrollment-step" data-step-indicator="1">Policy</div>
<div class="enrollment-step" data-step-indicator="2">Submit</div>
</div>
<div data-step-panel="0">
<h6 class="fw-semibold">Select student names</h6>
<div class="text-muted small mb-3">Tap each student you want to enroll for <?= esc($selectedYear ?? 'the selected school year') ?>.</div>
<div class="d-grid gap-2">
<?php foreach ($students as $student): ?>
<?php
$eligibilityMessage = is_array($student['enrollment_eligibility_message'] ?? null) ? $student['enrollment_eligibility_message'] : ['message' => '', 'blocking' => false, 'level' => 'info'];
$studentName = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? ''));
$studentName = $studentName !== '' ? $studentName : 'Student';
$canEnroll = ($student['enrollment_status'] ?? '') === 'not enrolled'
&& !$deadlinePassed
&& $isEditable
&& empty($eligibilityMessage['blocking']);
$section = $student['class_section'] ?? null;
$gradeLabel = $section
? preg_replace_callback('/\byouth\b/i', fn($m) => ucfirst(strtolower($m[0])), (string) $section)
: 'Not Assigned';
?>
<div class="student-select-card <?= $canEnroll ? '' : 'is-disabled' ?>"
data-student-card
data-student-id="<?= esc($student['id']) ?>"
data-selectable="<?= $canEnroll ? '1' : '0' ?>">
<div class="d-flex gap-3 align-items-start">
<span class="student-select-icon" aria-hidden="true"><i class="bi bi-check2"></i></span>
<div class="flex-grow-1">
<div class="d-flex flex-wrap gap-2 align-items-center">
<div class="fw-semibold"><?= esc($studentName) ?></div>
<?= $statusBadge($student['enrollment_status'] ?? '') ?>
</div>
<div class="small text-muted">Grade: <?= esc($gradeLabel) ?> &middot; School ID: <?= esc($student['school_id'] ?? 'N/A') ?></div>
<div class="small">Required action: <?= esc($student['required_action_label'] ?? 'Contact administration') ?></div>
<?php if (($eligibilityMessage['message'] ?? '') !== ''): ?>
<div class="alert alert-<?= esc($eligibilityMessage['level'] ?? 'info') ?> py-2 px-3 mt-2 mb-0 small">
<?= esc($eligibilityMessage['message']) ?>
</div>
<?php endif; ?>
</div>
</div>
<?php if ($canEnroll): ?>
<input class="d-none" type="checkbox" name="enroll[]" value="<?= esc($student['id']) ?>" data-enroll-input>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="d-none" data-step-panel="1">
<h6 class="fw-semibold">Policy info update</h6>
<div class="text-muted small mb-3">Review the current school policies before submitting enrollment.</div>
<iframe src="<?= base_url('policy/school_policy') ?>" class="enrollment-policy-frame" frameborder="0" title="School Policies"></iframe>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" value="1" id="schoolPolicyAcceptedCheckbox" <?= $hasAcceptedSchoolPolicy ? 'checked disabled' : '' ?>>
<label class="form-check-label" for="schoolPolicyAcceptedCheckbox">
I have read and accept all school policies.
</label>
</div>
</div>
<div class="d-none" data-step-panel="2">
<h6 class="fw-semibold">Enroll submit</h6>
<div class="text-muted small mb-3">Review the selected students, then submit enrollment.</div>
<div id="enrollmentReviewList" class="d-grid gap-2"></div>
<div class="alert alert-warning mt-3 mb-0 small">
Submitting sends the selected enrollment(s) to admission review.
</div>
</div>
</div>
<div class="modal-footer enrollment-modal-footer">
<button type="button" class="btn btn-outline-secondary" id="enrollmentBackButton">Back</button>
<button type="button" class="btn btn-success" id="enrollmentNextButton">Next</button>
<button type="submit" class="btn btn-success d-none" id="enrollmentSubmitButton">Submit Enrollment</button>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" id="acceptSchoolPolicyButton" <?= $hasAcceptedSchoolPolicy ? '' : 'disabled' ?>>
Accept Policy
</button>
</div>
</div>
</div>
</form>
<?php else: ?>
<p>No students found for the selected school year. Please register your kids first.</p>
<?php endif; ?>
</div>
<!-- Enrollment Deadline Modal -->
<div class="modal fade" id="deadlineModal" tabindex="-1" aria-labelledby="deadlineModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
@@ -259,11 +421,8 @@ $money = static function ($amount) use ($familyFinancialSummary): string {
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Enrollment for the selected school year closed on
<strong><?= esc($deadlineObj->format('m-d-Y')) ?></strong>.
You can still request a withdrawal (if applicable), but new enrollments are no longer accepted.
<br><br>
If you believe this is an error, please contact the school office.
Enrollment for the selected school year closed on <strong><?= esc($deadlineObj->format('m-d-Y')) ?></strong>.
You can still request a withdrawal if applicable.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">OK</button>
@@ -271,133 +430,164 @@ $money = static function ($amount) use ($familyFinancialSummary): string {
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Values from PHP
const deadlinePassed = <?= $deadlinePassed ? 'true' : 'false' ?>;
const enrollmentDeadline = new Date("<?= esc($deadlineISO) ?>");
let hasAcceptedSchoolPolicy = <?= $hasAcceptedSchoolPolicy ? 'true' : 'false' ?>;
// Bootstrap Modal
const modalEl = document.getElementById('deadlineModal');
const deadlineModal = modalEl ? new bootstrap.Modal(modalEl) : null;
const policyModalEl = document.getElementById('schoolPolicyModal');
const policyModal = policyModalEl ? new bootstrap.Modal(policyModalEl) : null;
const form = document.getElementById('enrollmentFlowForm');
const startButton = document.getElementById('startEnrollmentButton');
const flowModalEl = document.getElementById('enrollmentFlowModal');
const deadlineModalEl = document.getElementById('deadlineModal');
const flowModal = flowModalEl ? new bootstrap.Modal(flowModalEl) : null;
const deadlineModal = deadlineModalEl ? new bootstrap.Modal(deadlineModalEl) : null;
const policyAcceptedInput = document.getElementById('accept_school_policy_input');
const policyAcceptedCheckbox = document.getElementById('schoolPolicyAcceptedCheckbox');
const acceptPolicyButton = document.getElementById('acceptSchoolPolicyButton');
const showDeadlineModal = () => {
if (deadlineModal) deadlineModal.show();
};
const showPolicyModal = () => {
if (policyModal) policyModal.show();
};
const setDecisionMessageVisible = (checkbox, visible) => {
const targetId = checkbox.dataset.decisionMessageTarget || '';
const target = targetId ? document.getElementById(targetId) : null;
if (target) {
target.classList.toggle('d-none', !visible);
const backButton = document.getElementById('enrollmentBackButton');
const nextButton = document.getElementById('enrollmentNextButton');
const submitButton = document.getElementById('enrollmentSubmitButton');
const reviewList = document.getElementById('enrollmentReviewList');
let currentStep = 0;
let hasAcceptedSchoolPolicy = <?= $hasAcceptedSchoolPolicy ? 'true' : 'false' ?>;
const selectedEnrollInputs = () => form ? Array.from(form.querySelectorAll("input[name='enroll[]']:checked")) : [];
const selectedWithdrawInputs = () => form ? Array.from(form.querySelectorAll("input[name='withdraw[]']:checked")) : [];
function setStep(step) {
currentStep = Math.max(0, Math.min(2, step));
document.querySelectorAll('[data-step-panel]').forEach(panel => {
panel.classList.toggle('d-none', Number(panel.dataset.stepPanel) !== currentStep);
});
document.querySelectorAll('[data-step-indicator]').forEach(indicator => {
indicator.classList.toggle('is-active', Number(indicator.dataset.stepIndicator) === currentStep);
});
backButton.classList.toggle('d-none', currentStep === 0);
nextButton.classList.toggle('d-none', currentStep === 2);
submitButton.classList.toggle('d-none', currentStep !== 2);
if (currentStep === 2) {
renderReview();
}
};
const hideAllDecisionMessages = () => {
document.querySelectorAll('.enrollment-decision-message-row').forEach(row => {
row.classList.add('d-none');
});
};
}
if (policyAcceptedCheckbox && acceptPolicyButton) {
policyAcceptedCheckbox.addEventListener('change', function() {
acceptPolicyButton.disabled = !this.checked;
function renderReview() {
const cards = selectedEnrollInputs().map(input => {
const card = input.closest('[data-student-card]');
const name = card ? card.querySelector('.fw-semibold')?.textContent?.trim() : 'Student';
const meta = card ? card.querySelector('.small.text-muted')?.textContent?.trim() : '';
return '<div class="border rounded p-3"><div class="fw-semibold">' + escapeHtml(name || 'Student') + '</div><div class="small text-muted">' + escapeHtml(meta || '') + '</div></div>';
});
reviewList.innerHTML = cards.length ? cards.join('') : '<div class="alert alert-warning mb-0">No students selected.</div>';
}
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, function(char) {
return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' })[char];
});
}
if (acceptPolicyButton) {
acceptPolicyButton.addEventListener('click', function() {
if (policyAcceptedCheckbox && !policyAcceptedCheckbox.checked) {
return;
}
hasAcceptedSchoolPolicy = true;
if (policyAcceptedInput) {
policyAcceptedInput.value = '1';
}
if (policyModal) {
policyModal.hide();
}
});
function syncPolicyAccepted() {
hasAcceptedSchoolPolicy = !!(policyAcceptedCheckbox && policyAcceptedCheckbox.checked);
if (policyAcceptedInput) {
policyAcceptedInput.value = hasAcceptedSchoolPolicy ? '1' : '0';
}
}
// The enrollment form
const form = document.querySelector("form[action*='enroll_classes_handler']");
// 1) Block clicking "enroll" checkboxes after deadline
document.querySelectorAll("input[name='enroll[]']").forEach(cb => {
cb.addEventListener("click", function(e) {
if (startButton && flowModal) {
startButton.addEventListener('click', function() {
if (deadlinePassed) {
e.preventDefault();
e.stopImmediatePropagation();
this.checked = false;
showDeadlineModal();
if (deadlineModal) deadlineModal.show();
return;
}
setStep(0);
flowModal.show();
});
}
if (!hasAcceptedSchoolPolicy) {
e.preventDefault();
e.stopImmediatePropagation();
this.checked = false;
showPolicyModal();
document.querySelectorAll('[data-student-card]').forEach(card => {
card.addEventListener('click', function() {
if (this.dataset.selectable !== '1') {
return;
}
if (this.checked && this.dataset.decisionMessage) {
setDecisionMessageVisible(this, true);
if (this.dataset.decisionBlocking === '1') {
e.preventDefault();
e.stopImmediatePropagation();
this.checked = false;
return;
}
} else {
setDecisionMessageVisible(this, false);
const input = this.querySelector('[data-enroll-input]');
if (!input) {
return;
}
input.checked = !input.checked;
this.classList.toggle('is-selected', input.checked);
});
});
// 2) Prevent submission if any enroll[] is checked after deadline
if (policyAcceptedCheckbox) {
policyAcceptedCheckbox.addEventListener('change', syncPolicyAccepted);
}
if (backButton) {
backButton.addEventListener('click', function() {
setStep(currentStep - 1);
});
}
if (nextButton) {
nextButton.addEventListener('click', function() {
if (currentStep === 0 && selectedEnrollInputs().length === 0) {
alert('Please select at least one student to enroll.');
return;
}
if (currentStep === 1) {
syncPolicyAccepted();
if (!hasAcceptedSchoolPolicy) {
alert('Please read and accept the school policies before continuing.');
return;
}
}
setStep(currentStep + 1);
});
}
if (form) {
form.addEventListener("submit", function(e) {
const anyBoxesChecked = form.querySelectorAll("input[name='enroll[]']:checked").length > 0;
const anyWithdrawChecked = form.querySelectorAll("input[name='withdraw[]']:checked").length > 0;
form.addEventListener('submit', function(e) {
if (e.submitter && e.submitter.id === 'withdrawSubmitButton') {
selectedEnrollInputs().forEach(input => {
input.checked = false;
input.closest('[data-student-card]')?.classList.remove('is-selected');
});
}
if (!anyBoxesChecked && !anyWithdrawChecked) {
if (e.submitter && e.submitter.id === 'enrollmentSubmitButton') {
selectedWithdrawInputs().forEach(input => {
input.checked = false;
});
}
const anyEnroll = selectedEnrollInputs().length > 0;
const anyWithdraw = selectedWithdrawInputs().length > 0;
syncPolicyAccepted();
if (!anyEnroll && !anyWithdraw) {
e.preventDefault();
alert("Please select at least one student to enroll or withdraw before saving.");
alert('Please select at least one student to enroll or withdraw before submitting.');
return;
}
if (!hasAcceptedSchoolPolicy && anyBoxesChecked) {
if (deadlinePassed && anyEnroll) {
e.preventDefault();
showPolicyModal();
if (deadlineModal) deadlineModal.show();
return;
}
if (deadlinePassed && anyBoxesChecked) {
if (anyEnroll && !hasAcceptedSchoolPolicy) {
e.preventDefault();
showDeadlineModal();
setStep(1);
if (flowModal) flowModal.show();
return;
}
// 3) If only withdrawals are selected, ask for a quick confirmation
if (!anyBoxesChecked && anyWithdrawChecked) {
if (!anyEnroll && anyWithdraw) {
const ok = confirm('Confirm withdrawal request for the selected student(s)?');
if (!ok) {
e.preventDefault();
return;
}
}
});
+93
View File
@@ -0,0 +1,93 @@
<?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?>
<div class="container my-5">
<h3 class="text-center text-success">Financial Aid</h3>
<?php if (!empty($schoolYear)): ?>
<div class="text-center text-muted small mb-3">School Year: <?= esc($schoolYear) ?></div>
<?php endif; ?>
<?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="alert alert-warning" role="alert">
<h4 class="alert-heading">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
Bring these documents to the school office
</h4>
<p class="mb-2">
You must bring <strong>2 recent pay stubs</strong> <strong>and</strong> <strong>last years tax return</strong>
to the school office so administration can review your financial aid request.
</p>
<ul class="mb-2">
<li><strong>Recent pay stubs</strong></li>
<li><strong>Last years tax return</strong></li>
</ul>
<p class="mb-0">This is required whether you are submitting a new request or already have one open.</p>
</div>
<p>Use this form to request a tuition reduction. Administration will review the request and apply any approved amount to your invoice.</p>
<?php if (!empty($requests)): ?>
<div class="table-responsive mb-4">
<table class="table table-bordered">
<thead>
<tr>
<th>Submitted</th>
<th>Status</th>
<th>Requested</th>
<th>Approved amount</th>
<th>Note</th>
</tr>
</thead>
<tbody>
<?php foreach ($requests as $row): ?>
<tr>
<td><?= esc($row['created_at'] ?? '') ?></td>
<td><?= esc($row['status'] ?? '') ?></td>
<td><?= $row['requested_amount'] !== null && $row['requested_amount'] !== '' ? '$' . number_format((float) $row['requested_amount'], 2) : 'Not specified' ?></td>
<td><?= $row['admin_amount'] !== null && $row['admin_amount'] !== '' ? '$' . number_format((float) $row['admin_amount'], 2) : '—' ?></td>
<td><?= esc($row['admin_note'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php if (empty($openRequest)): ?>
<form method="post" action="<?= site_url('parent/financial-aid') ?>" class="border rounded p-3 bg-light">
<?= csrf_field() ?>
<div class="mb-3">
<label class="form-label">Students</label>
<?php foreach (($students ?? []) as $student): ?>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="student_ids[]" value="<?= (int) $student['id'] ?>" id="fa_student_<?= (int) $student['id'] ?>">
<label class="form-check-label" for="fa_student_<?= (int) $student['id'] ?>">
<?= esc(trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''))) ?>
</label>
</div>
<?php endforeach; ?>
</div>
<div class="mb-3">
<label class="form-label" for="household_size">Household size</label>
<input class="form-control" type="number" min="1" name="household_size" id="household_size" value="<?= esc(old('household_size')) ?>">
</div>
<div class="mb-3">
<label class="form-label" for="requested_amount">Requested amount (optional)</label>
<input class="form-control" type="number" min="0" step="0.01" name="requested_amount" id="requested_amount" value="<?= esc(old('requested_amount')) ?>">
</div>
<div class="mb-3">
<label class="form-label" for="need_statement">Why are you requesting financial aid?</label>
<textarea class="form-control" name="need_statement" id="need_statement" rows="5" required><?= esc(old('need_statement')) ?></textarea>
</div>
<button class="btn btn-success" type="submit">Submit request</button>
</form>
<?php else: ?>
<div class="alert alert-info">You already have an open request. Administration will update it after review. Remember to bring recent pay stubs and last years tax return to the school office.</div>
<?php endif; ?>
</div>
<?= $this->endSection() ?>
+3
View File
@@ -109,6 +109,9 @@ $deadlineDisplay = formatCalendarDateOnly($dueDate);
Cash, checks and debit/credit cards are all accepted forms of payment. However, if you elect to pay in
installments, only cash and checks will be accepted.
</li>
<li>
Need help with tuition? <a href="<?= site_url('parent/financial-aid') ?>">Request financial aid</a>.
</li>
</ul>
</div>
+6
View File
@@ -325,6 +325,12 @@ switch ($role) {
'label' => 'Invoice',
'title' => 'View and pay your child(ren)\'s invoice securely (tuition & events).',
],
[
'href' => base_url('/parent/financial-aid'),
'icon' => 'bi-heart',
'label' => 'Financial Aid',
'title' => 'Request a tuition reduction for review by school administration.',
],
[
'href' => base_url('/parent/attendance'),
'icon' => 'bi-calendar-check',
+5 -12
View File
@@ -48,8 +48,7 @@ After the registration period ends, parents will be invited to join WhatsApp gra
'title' => 'Tuition',
'body' => 'All parents will be asked to <u>pay tuition for the year</u> or come to an agreement with the administration about an interest-free installment plan where <u>payments are made at the beginning of each month starting October 1st</u>. Accepted methods of payments are cash, check and debit/credit card. Tuition covers school expenses related to operations, purchase of books/supplies/materials, organizing events for students, etc... The school is committed to offer high quality services while maintaining low cost of operations so that tuition is affordable by the majority of parents. <u>Annual</u> tuition for this school year is as follows:
<u>Kindergarten & Grades 1 9</u>: <strong>$370</strong> per child + <strong>$220</strong> per additional child
<u>Youth</u>: $200
<u>All students</u>: <strong>$380</strong> for the first child + <strong>$280</strong> for each additional child
Examples:
<table border="1" cellspacing="0" cellpadding="8" style="border-collapse: collapse; text-align: center; width: 100%;">
@@ -67,22 +66,16 @@ Examples:
</thead>
<tbody>
<tr>
<td># kids in K & Grades 19</td>
<td># of children</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td># kids in Youth</td>
<td>0</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>Total Tuition</td>
<td><strong>$370</strong></td>
<td><strong>$370 + $220 = $590</strong></td>
<td><strong>$370 + 2×$220 + $200 = $1010</strong></td>
<td><strong>$380</strong></td>
<td><strong>$380 + $280 = $660</strong></td>
<td><strong>$380 + 2×$280 = $940</strong></td>
</tr>
</tbody>
</table>'
+18 -51
View File
@@ -13,11 +13,8 @@
$money = static fn ($value): string => '$' . number_format((float) $value, 2);
$activeId = (int) ($activeYear['id'] ?? 0);
$nextDraftId = (int) ($nextDraftYear['id'] ?? 0);
$newDraftName = (string) ($nextDraftDefaults['name'] ?? '');
$defaultNewPreviousYear = $nextDraftDefaults['previous_year'] ?? null;
$defaultNewPreviousYearName = (string) ($defaultNewPreviousYear['name'] ?? 'None');
$closingPreviewUrl = $activeId > 0
? site_url('administrator/school-years/' . $activeId . '/closing/preview' . ($nextDraftId > 0 ? '?' . http_build_query(['target_school_year_id' => $nextDraftId]) : ''))
? site_url('administrator/school-years/' . $activeId . '/closing/preview')
: '';
?>
@@ -51,14 +48,16 @@
<?php elseif ($activeYear && $nextDraftYear): ?>
<div class="text-muted">Review promotions and balances for <?= esc($activeYear['name']) ?>. When all blockers are resolved, end it and start <?= esc($nextDraftYear['name']) ?>.</div>
<?php elseif ($activeYear): ?>
<div class="text-muted">Create the next school year first. It will stay as a draft until the current year is closed.</div>
<div class="text-muted">Review promotions and balances for <?= esc($activeYear['name']) ?>. The next school year will be created automatically when you close this year.</div>
<?php elseif ($nextDraftYear): ?>
<div class="text-muted">No year is active. Start <?= esc($nextDraftYear['name']) ?> when you are ready.</div>
<?php elseif (empty($schoolYears)): ?>
<div class="text-muted">Create the first school year to begin.</div>
<?php else: ?>
<div class="text-muted">Create a school year draft to begin.</div>
<div class="text-muted">The next school year is created automatically when you close the current year.</div>
<?php endif; ?>
<ol class="small text-muted ps-3 mt-2 mb-0">
<li>Create the next year as a draft.</li>
<li>Open the end-year checklist. The next draft year is created automatically.</li>
<li>Review the end-year checklist for the current year.</li>
<li>End the current year, then start the next year.</li>
</ol>
@@ -66,74 +65,42 @@
<div class="d-flex align-items-start">
<?php if ($closingYear): ?>
<a class="btn btn-primary" href="<?= site_url('administrator/school-years/' . (int) $closingYear['id'] . '/closing/preview') ?>">Finish End-Year Checklist</a>
<?php elseif ($activeYear && $nextDraftYear): ?>
<a class="btn btn-primary" href="<?= esc($closingPreviewUrl, 'attr') ?>">Close Year</a>
<?php elseif ($activeYear): ?>
<button class="btn btn-success" type="button" data-bs-toggle="collapse" data-bs-target="#schoolYearCreateForm">Create Next Year</button>
<a class="btn btn-primary" href="<?= esc($closingPreviewUrl, 'attr') ?>">Close Year</a>
<?php elseif ($nextDraftYear): ?>
<button class="btn btn-success" type="button" data-bs-toggle="modal" data-bs-target="#activateSchoolYearModal<?= $nextDraftId ?>">Start <?= esc($nextDraftYear['name']) ?></button>
<?php else: ?>
<?php elseif (empty($schoolYears)): ?>
<button class="btn btn-success" type="button" data-bs-toggle="collapse" data-bs-target="#schoolYearCreateForm">Create School Year</button>
<?php endif; ?>
</div>
</div>
</div>
<?php if (empty($schoolYears)): ?>
<div class="collapse mb-4" id="schoolYearCreateForm">
<div class="border rounded bg-white p-3">
<form action="<?= site_url('administrator/school-years/store') ?>" method="post" class="row g-3 align-items-end">
<?= csrf_field() ?>
<div class="col-md-2">
<div class="col-md-3">
<label class="form-label" for="new_school_year_name">School Year</label>
<input
class="form-control"
id="new_school_year_name"
name="name"
type="text"
value="<?= esc($newDraftName !== '' ? $newDraftName : 'Not available', 'attr') ?>"
readonly
placeholder="2025-2026"
pattern="\d{4}-\d{4}"
required
>
</div>
<div class="col-md-2">
<label class="form-label" for="new_previous_school_year_display">Previous Year</label>
<input
class="form-control"
id="new_previous_school_year_display"
type="text"
value="<?= esc($defaultNewPreviousYearName, 'attr') ?>"
readonly
>
</div>
<div class="col-md-2">
<label class="form-label" for="new_school_year_starts_on">Starts On</label>
<input class="form-control" id="new_school_year_starts_on" name="starts_on" type="date">
</div>
<div class="col-md-2">
<label class="form-label" for="new_school_year_ends_on">Ends On</label>
<input class="form-control" id="new_school_year_ends_on" name="ends_on" type="date">
</div>
<div class="col-md-2">
<label class="form-label" for="new_registration_starts_on">Registration Starts</label>
<input class="form-control" id="new_registration_starts_on" name="registration_starts_on" type="date">
</div>
<div class="col-md-2">
<label class="form-label" for="new_registration_ends_on">Registration Ends</label>
<input class="form-control" id="new_registration_ends_on" name="registration_ends_on" type="date">
</div>
<div class="col-md-2">
<label class="form-label" for="new_fall_makeup_exam_on">Fall Makeup Exam</label>
<input class="form-control" id="new_fall_makeup_exam_on" name="fall_makeup_exam_on" type="date">
</div>
<div class="col-md-2 d-flex gap-2">
<button class="btn btn-primary" type="submit" <?= $newDraftName === '' ? 'disabled' : '' ?>>Save Draft</button>
<div class="col-md-3 d-flex gap-2">
<button class="btn btn-primary" type="submit">Save Draft</button>
<button class="btn btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#schoolYearCreateForm">Cancel</button>
</div>
<div class="col-12">
<label class="form-label" for="new_school_year_description">Description</label>
<textarea class="form-control" id="new_school_year_description" name="description" rows="2"></textarea>
</div>
</form>
</div>
</div>
<?php endif; ?>
<div class="table-responsive">
<table id="schoolYearsTable" class="table table-striped table-hover align-middle">
@@ -216,7 +183,7 @@
</li>
<?php elseif ($status === 'active'): ?>
<li>
<a class="dropdown-item" href="<?= site_url('administrator/school-years/' . $id . '/closing/preview' . ($nextDraftId > 0 ? '?' . http_build_query(['target_school_year_id' => $nextDraftId]) : '')) ?>">Close year</a>
<a class="dropdown-item" href="<?= site_url('administrator/school-years/' . $id . '/closing/preview') ?>">Close year</a>
</li>
<?php elseif ($status === 'closing'): ?>
<li>