1747 lines
94 KiB
PHP
1747 lines
94 KiB
PHP
<?= $this->extend('layout/main_layout') ?>
|
||
|
||
<?= $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(5, minmax(0, 1fr));
|
||
gap: .5rem;
|
||
}
|
||
|
||
.enrollment-step {
|
||
border-bottom: 3px solid #dee2e6;
|
||
color: #6c757d;
|
||
font-size: .85rem;
|
||
line-height: 1.2;
|
||
overflow-wrap: anywhere;
|
||
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: clamp(520px, 72vh, 900px);
|
||
width: 100%;
|
||
}
|
||
|
||
@media (max-width: 767.98px) {
|
||
.enrollment-policy-frame {
|
||
height: 72vh;
|
||
min-height: 480px;
|
||
}
|
||
}
|
||
|
||
.enrollment-withdraw-control {
|
||
align-items: center;
|
||
display: inline-flex;
|
||
gap: .5rem;
|
||
justify-content: flex-end;
|
||
min-width: max-content;
|
||
padding-left: 0;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.enrollment-withdraw-control .form-check-input {
|
||
flex: 0 0 auto;
|
||
margin-left: 0;
|
||
}
|
||
|
||
.enrollment-modal-footer {
|
||
gap: .5rem;
|
||
}
|
||
|
||
.enrollment-summary-card {
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 8px;
|
||
padding: 1rem;
|
||
}
|
||
|
||
.enrollment-summary-lines {
|
||
display: grid;
|
||
gap: .35rem;
|
||
}
|
||
|
||
.enrollment-summary-line {
|
||
align-items: baseline;
|
||
display: flex;
|
||
gap: .5rem;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.enrollment-summary-label {
|
||
color: #6c757d;
|
||
flex: 0 0 auto;
|
||
font-size: .875rem;
|
||
}
|
||
|
||
.enrollment-summary-value {
|
||
font-size: .875rem;
|
||
font-weight: 600;
|
||
min-width: 0;
|
||
overflow-wrap: anywhere;
|
||
text-align: right;
|
||
}
|
||
|
||
@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%;
|
||
}
|
||
|
||
.enrollment-status-table td[data-label="Withdraw"] {
|
||
align-items: center;
|
||
}
|
||
|
||
.enrollment-status-table td[data-label="Withdraw"] > * {
|
||
max-width: none;
|
||
}
|
||
|
||
.enrollment-status-table td[data-label="Withdraw"] .enrollment-withdraw-control {
|
||
margin-left: auto;
|
||
}
|
||
|
||
.enrollment-summary-line {
|
||
align-items: flex-start;
|
||
display: block;
|
||
}
|
||
|
||
.enrollment-summary-value {
|
||
display: block;
|
||
text-align: left;
|
||
}
|
||
}
|
||
</style>
|
||
<?= $this->endSection() ?>
|
||
|
||
<?= $this->section('content') ?>
|
||
<?php
|
||
$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));
|
||
$deadlinePassed = $nowObj > $deadlineObj;
|
||
$hasAcceptedSchoolPolicy = (bool) ($hasAcceptedSchoolPolicy ?? false);
|
||
$familyFinancialSummary = is_array($familyFinancialSummary ?? null) ? $familyFinancialSummary : [];
|
||
$enrollmentFeeSchedule = is_array($enrollmentFeeSchedule ?? null) ? $enrollmentFeeSchedule : [];
|
||
$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 & 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>',
|
||
};
|
||
};
|
||
$alreadyEnrolledMessage = static fn (?string $status): string => \App\Support\Enrollment\EnrollmentEligibility::alreadyEnrolledMessage($status);
|
||
$alreadyEnrolledTitle = static fn (?string $status): string => \App\Support\Enrollment\EnrollmentEligibility::alreadyEnrolledTitle($status);
|
||
$hasSettledEnrollmentStatus = static function (?string $status, ?string $admissionStatus = ''): bool {
|
||
if (strtolower(trim((string) $admissionStatus)) === 'accepted') {
|
||
return true;
|
||
}
|
||
|
||
return in_array(strtolower(trim((string) $status)), [
|
||
'admission under review',
|
||
'review & decision',
|
||
'payment pending',
|
||
'enrolled',
|
||
'waitlist',
|
||
'withdraw under review',
|
||
'refund pending',
|
||
], true);
|
||
};
|
||
$enrollableCount = 0;
|
||
$withdrawableCount = 0;
|
||
$currentYearTuitionStudentCount = 0;
|
||
foreach (($students ?? []) as $student) {
|
||
$studentStatus = strtolower(trim((string) ($student['enrollment_status'] ?? '')));
|
||
$studentAdmissionStatus = strtolower(trim((string) ($student['admission_status'] ?? '')));
|
||
if (
|
||
! in_array($studentStatus, ['withdrawn', 'withdraw under review', 'refund pending', 'denied', 'not enrolled'], true)
|
||
&& (
|
||
in_array($studentStatus, ['admission under review', 'review & decision', 'payment pending', 'enrolled', 'waitlist'], true)
|
||
|| $studentAdmissionStatus === 'accepted'
|
||
)
|
||
) {
|
||
$currentYearTuitionStudentCount++;
|
||
}
|
||
|
||
$evaluation = is_array($student['transition_evaluation'] ?? null) ? $student['transition_evaluation'] : [];
|
||
if (
|
||
(($evaluation['can_enroll'] ?? false) === true || ($evaluation['parent_enrollment_allowed'] ?? false) === true)
|
||
&& !$deadlinePassed
|
||
&& $isEditable
|
||
) {
|
||
$enrollableCount++;
|
||
}
|
||
if (($student['enrollment_status'] ?? '') === 'enrolled' && $isEditable) {
|
||
$withdrawableCount++;
|
||
}
|
||
}
|
||
$studentCount = count($students ?? []);
|
||
?>
|
||
|
||
<div class="container my-4">
|
||
<div class="text-center mb-4">
|
||
<h3 class="text-success">Enroll in Classes</h3>
|
||
<?php if (!empty($selectedYear)): ?>
|
||
<div class="text-muted small">School Year: <?= esc($selectedYear) ?></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php
|
||
$flashError = session()->getFlashdata('error');
|
||
$flashSuccess = session()->getFlashdata('success');
|
||
?>
|
||
<?php if ($flashError): ?>
|
||
<div class="alert alert-danger"><?= esc($flashError) ?></div>
|
||
<?php endif; ?>
|
||
<?php if ($flashSuccess): ?>
|
||
<div class="alert alert-success"><?= esc($flashSuccess) ?></div>
|
||
<?php endif; ?>
|
||
|
||
<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>Payments are processed on the first day of school: <strong><?= esc(local_date($schoolStartDate, 'm-d-Y')) ?></strong>.</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<?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">
|
||
Review student information, confirm your home address and phone, acknowledge policies, review tuition and balances, then submit enrollment.
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-auto">
|
||
<button type="button"
|
||
class="btn btn-success btn-lg w-100"
|
||
id="startEnrollmentButton"
|
||
<?= (!$isEditable || $studentCount === 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 ($studentCount === 0): ?>
|
||
<div class="small text-muted mt-2">No students are currently linked to this account.</div>
|
||
<?php elseif ($enrollableCount === 0): ?>
|
||
<div class="small text-muted mt-2">No students are currently eligible for enrollment. You may still click Start Enrollment to review the reason.</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="table-responsive mb-4">
|
||
<table class="table table-striped table-bordered align-middle enrollment-status-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Student</th>
|
||
<th>Grade</th>
|
||
<th>Decision</th>
|
||
<th>Required Action</th>
|
||
<th>Status</th>
|
||
<th>Eligibility</th>
|
||
<th>Withdraw</th>
|
||
</tr>
|
||
</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['parent_enrollment_state'] ?? $student['required_action_label'] ?? 'Contact administration') ?></td>
|
||
<td data-label="Status"><?= $statusBadge($student['enrollment_status'] ?? '') ?></td>
|
||
<td data-label="Eligibility">
|
||
<?php
|
||
$evaluation = is_array($student['transition_evaluation'] ?? null) ? $student['transition_evaluation'] : [];
|
||
if ($hasSettledEnrollmentStatus($student['enrollment_status'] ?? '', $student['admission_status'] ?? '')) {
|
||
echo '<span class="text-muted small">' . esc($alreadyEnrolledMessage((string) ($student['enrollment_status'] ?? ''))) . '</span>';
|
||
} elseif (($evaluation['can_enroll'] ?? false) === true || ($evaluation['parent_enrollment_allowed'] ?? false) === true) {
|
||
echo '<span class="text-success small">Eligible</span>';
|
||
} elseif (! empty($evaluation['primary_parent_message'])) {
|
||
echo '<span class="text-danger small">' . esc($evaluation['primary_parent_message']) . '</span>';
|
||
} elseif (! empty($student['enrollment_eligibility_message']['message'])) {
|
||
echo '<span class="text-danger small">' . esc($student['enrollment_eligibility_message']['message']) . '</span>';
|
||
} else {
|
||
echo '<span class="text-muted small">Not eligible</span>';
|
||
}
|
||
?>
|
||
</td>
|
||
<td data-label="Withdraw">
|
||
<?php if (($student['enrollment_status'] ?? '') === 'enrolled'): ?>
|
||
<div class="form-check form-switch m-0 enrollment-withdraw-control">
|
||
<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 endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<?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">Submit Enrollment</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">Student Info</div>
|
||
<div class="enrollment-step" data-step-indicator="1">Contact</div>
|
||
<div class="enrollment-step" data-step-indicator="2">Policies</div>
|
||
<div class="enrollment-step" data-step-indicator="3">Tuition</div>
|
||
<div class="enrollment-step" data-step-indicator="4">Submit</div>
|
||
</div>
|
||
|
||
<div data-step-panel="0">
|
||
<h6 class="fw-semibold">Review student information</h6>
|
||
<div class="text-muted small mb-3">Select each student you want to enroll for <?= esc($selectedYear ?? 'the selected school year') ?>. Photo consent, medical conditions, and allergies can be updated below.</div>
|
||
<?php
|
||
$enrollmentMedicalOptions = [
|
||
'None',
|
||
'ADHD (Attention-Deficit/Hyperactivity Disorder)',
|
||
'Anxiety or Emotional Disorders',
|
||
'Asthma',
|
||
'Autism Spectrum Disorder (ASD)',
|
||
'Behavioral or Conduct Disorders',
|
||
'Blindness / Vision Impairment',
|
||
'Celiac Disease (Gluten Intolerance)',
|
||
'Cerebral Palsy',
|
||
'Cystic Fibrosis',
|
||
'Depression',
|
||
'Diabetes (Type 1 or Type 2)',
|
||
'Down Syndrome',
|
||
'Dyslexia or Learning Disabilities',
|
||
'Eating Disorders',
|
||
'Eczema / Severe Skin Conditions',
|
||
'Epilepsy / Seizure Disorders',
|
||
'Hearing Impairments / Deafness',
|
||
'Heart Conditions (congenital or acquired)',
|
||
'Hemophilia / Bleeding Disorders',
|
||
'Kidney Disease',
|
||
'Migraines / Chronic Headaches',
|
||
'Obsessive-Compulsive Disorder (OCD)',
|
||
'Physical Disabilities / Mobility Impairments',
|
||
'PTSD (Post-Traumatic Stress Disorder)',
|
||
'Sickle Cell Anemia',
|
||
'Speech and Language Disorders',
|
||
'Thyroid Disorders',
|
||
'Tourette Syndrome',
|
||
'Traumatic Brain Injury (TBI)',
|
||
'Rheumatic diseases',
|
||
'Ulcerative Colitis / Crohn’s Disease',
|
||
'Other',
|
||
];
|
||
$enrollmentAllergyOptions = [
|
||
'None',
|
||
'Animal Dander (cats, dogs, etc.)',
|
||
'Antibiotics',
|
||
'Bee stings',
|
||
'Cockroach',
|
||
'Corn',
|
||
'Dust Mites',
|
||
'Egg',
|
||
'Fire ant stings',
|
||
'Fish',
|
||
'Fragrances / Perfumes',
|
||
'Latex',
|
||
'Milk / Dairy',
|
||
'Mold',
|
||
'Mosquito bites',
|
||
'Peanut',
|
||
'Pollen (grass, tree, weed)',
|
||
'Sesame',
|
||
'Shellfish (shrimp, crab, lobster, etc.)',
|
||
'Soy',
|
||
'Tree Nuts (almond, cashew, walnut, etc.)',
|
||
'Wasp stings',
|
||
'Wheat / Gluten',
|
||
'Other',
|
||
];
|
||
?>
|
||
<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'];
|
||
$evaluation = is_array($student['transition_evaluation'] ?? null) ? $student['transition_evaluation'] : [];
|
||
$studentName = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? ''));
|
||
$studentName = $studentName !== '' ? $studentName : 'Student';
|
||
$studentId = (int) ($student['id'] ?? 0);
|
||
$isPreselectedStudent = in_array($studentId, array_map('intval', $preselectedStudentIds ?? []), true);
|
||
$canEnroll = (
|
||
($evaluation['can_enroll'] ?? false) === true
|
||
|| ($evaluation['parent_enrollment_allowed'] ?? false) === true
|
||
)
|
||
&& !$deadlinePassed
|
||
&& $isEditable;
|
||
$hasSettledEnrollment = $hasSettledEnrollmentStatus($student['enrollment_status'] ?? '', $student['admission_status'] ?? '');
|
||
$blockMessage = $canEnroll
|
||
? ''
|
||
: ($hasSettledEnrollment
|
||
? $alreadyEnrolledMessage((string) ($student['enrollment_status'] ?? ''))
|
||
: (string) ($evaluation['primary_parent_message'] ?? $eligibilityMessage['message'] ?? ''));
|
||
$blockTitle = $hasSettledEnrollment
|
||
? $alreadyEnrolledTitle((string) ($student['enrollment_status'] ?? ''))
|
||
: 'Enrollment Not Available';
|
||
$section = $student['class_section'] ?? null;
|
||
$gradeLabel = $section
|
||
? preg_replace_callback('/\byouth\b/i', fn($m) => ucfirst(strtolower($m[0])), (string) $section)
|
||
: 'Not Assigned';
|
||
$medicalSelected = is_array($student['medical_conditions'] ?? null)
|
||
? array_values(array_filter(array_map('trim', $student['medical_conditions'])))
|
||
: array_values(array_filter(array_map('trim', preg_split('/[,\n;]+/', (string) ($student['medical_conditions'] ?? ''), -1, PREG_SPLIT_NO_EMPTY) ?: [])));
|
||
$allergySelected = is_array($student['allergies'] ?? null)
|
||
? array_values(array_filter(array_map('trim', $student['allergies'])))
|
||
: array_values(array_filter(array_map('trim', preg_split('/[,\n;]+/', (string) ($student['allergies'] ?? ''), -1, PREG_SPLIT_NO_EMPTY) ?: [])));
|
||
$medicalOther = implode(', ', array_diff($medicalSelected, $enrollmentMedicalOptions));
|
||
$allergyOther = implode(', ', array_diff($allergySelected, $enrollmentAllergyOptions));
|
||
if ($medicalOther !== '') {
|
||
$medicalSelected[] = 'Other';
|
||
}
|
||
if ($allergyOther !== '') {
|
||
$allergySelected[] = 'Other';
|
||
}
|
||
$dobDisplay = !empty($student['dob']) ? local_date($student['dob'], 'm-d-Y') : 'N/A';
|
||
$photoConsentValue = (string) ($student['photo_consent'] ?? '');
|
||
if ($photoConsentValue !== '0' && $photoConsentValue !== '1') {
|
||
$photoConsentValue = !empty($student['photo_consent']) ? '1' : '';
|
||
}
|
||
?>
|
||
<div class="student-select-card <?= $canEnroll ? '' : 'is-disabled' ?>"
|
||
data-student-card
|
||
data-student-id="<?= esc($student['id']) ?>"
|
||
data-student-name="<?= esc($studentName) ?>"
|
||
data-requested-student="<?= $isPreselectedStudent ? '1' : '0' ?>"
|
||
data-school-id="<?= esc($student['school_id'] ?? 'N/A') ?>"
|
||
data-current-grade="<?= esc($gradeLabel) ?>"
|
||
data-required-action="<?= esc($student['required_action_label'] ?? 'Contact administration') ?>"
|
||
data-expected-placement="<?= esc($student['expected_placement_label'] ?? $gradeLabel) ?>"
|
||
data-is-new="<?= (string) ($student['is_new'] ?? '1') === '1' ? '1' : '0' ?>"
|
||
data-selectable="<?= $canEnroll ? '1' : '0' ?>"
|
||
data-already-enrolled="<?= $hasSettledEnrollment ? '1' : '0' ?>"
|
||
data-block-title="<?= esc($blockTitle) ?>"
|
||
data-block-message="<?= esc($blockMessage) ?>">
|
||
<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 mt-1">
|
||
<div>School ID: <?= esc($student['school_id'] ?? 'N/A') ?></div>
|
||
<div>Date of birth: <?= esc($dobDisplay) ?><?php if (isset($student['age'])): ?> · Age: <?= esc($student['age']) ?><?php endif; ?></div>
|
||
<div>Gender: <?= esc($student['gender'] ?? 'N/A') ?></div>
|
||
<?php if ((string) ($student['is_new'] ?? '1') === '1'): ?>
|
||
<div>Registration grade: <?= esc($student['registration_grade'] ?? 'N/A') ?></div>
|
||
<?php endif; ?>
|
||
<?php if ((string) ($student['is_new'] ?? '0') === '0'): ?>
|
||
<div>Expected placement: <?= esc($student['expected_placement_label'] ?? $gradeLabel) ?></div>
|
||
<?php endif; ?>
|
||
|
||
</div>
|
||
|
||
<?php if ($canEnroll): ?>
|
||
<input type="hidden" name="student_info[<?= esc($student['id']) ?>][firstname]" value="<?= esc($student['firstname'] ?? '') ?>">
|
||
<input type="hidden" name="student_info[<?= esc($student['id']) ?>][lastname]" value="<?= esc($student['lastname'] ?? '') ?>">
|
||
<input type="hidden" name="student_info[<?= esc($student['id']) ?>][dob]" value="<?= esc($student['dob'] ?? '') ?>">
|
||
<input type="hidden" name="student_info[<?= esc($student['id']) ?>][gender]" value="<?= esc($student['gender'] ?? '') ?>">
|
||
<input type="hidden" name="student_info[<?= esc($student['id']) ?>][registration_grade]" value="<?= esc($student['registration_grade'] ?? '') ?>">
|
||
|
||
<div class="row g-2 mt-3" data-student-edit-fields>
|
||
<div class="col-12">
|
||
<div class="fw-semibold small mb-1">Editable information</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold" for="student-photo-consent-<?= esc($student['id']) ?>">Photo consent</label>
|
||
<select class="form-select form-select-sm" id="student-photo-consent-<?= esc($student['id']) ?>" name="student_info[<?= esc($student['id']) ?>][photo_consent]" required disabled data-student-edit-input>
|
||
<option value="">Select</option>
|
||
<option value="1" <?= $photoConsentValue === '1' ? 'selected' : '' ?>>Yes</option>
|
||
<option value="0" <?= $photoConsentValue === '0' ? 'selected' : '' ?>>No</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold">Medical conditions</label>
|
||
<div class="border rounded p-2 bg-white small" style="max-height: 180px; overflow-y: auto;" data-health-group="medical_conditions">
|
||
<?php foreach ($enrollmentMedicalOptions as $opt): ?>
|
||
<?php $optId = 'student-medical-' . $student['id'] . '-' . md5($opt); ?>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="<?= esc($optId) ?>" name="student_info[<?= esc($student['id']) ?>][medical_conditions][]" value="<?= esc($opt) ?>" <?= in_array($opt, $medicalSelected, true) ? 'checked' : '' ?> disabled data-student-edit-input data-health-option="<?= $opt === 'Other' ? 'other' : ($opt === 'None' ? 'none' : 'item') ?>">
|
||
<label class="form-check-label" for="<?= esc($optId) ?>"><?= esc($opt) ?></label>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<input type="text" class="form-control form-control-sm mt-2 <?= $medicalOther === '' ? 'd-none' : '' ?>" name="student_info[<?= esc($student['id']) ?>][medical_condition_other]" value="<?= esc($medicalOther) ?>" placeholder="Please specify if Other is selected" disabled data-student-edit-input data-other-input="medical_conditions" maxlength="100">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold">Allergies</label>
|
||
<div class="border rounded p-2 bg-white small" style="max-height: 180px; overflow-y: auto;" data-health-group="allergies">
|
||
<?php foreach ($enrollmentAllergyOptions as $opt): ?>
|
||
<?php $optId = 'student-allergy-' . $student['id'] . '-' . md5($opt); ?>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="<?= esc($optId) ?>" name="student_info[<?= esc($student['id']) ?>][allergies][]" value="<?= esc($opt) ?>" <?= in_array($opt, $allergySelected, true) ? 'checked' : '' ?> disabled data-student-edit-input data-health-option="<?= $opt === 'Other' ? 'other' : ($opt === 'None' ? 'none' : 'item') ?>">
|
||
<label class="form-check-label" for="<?= esc($optId) ?>"><?= esc($opt) ?></label>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<input type="text" class="form-control form-control-sm mt-2 <?= $allergyOther === '' ? 'd-none' : '' ?>" name="student_info[<?= esc($student['id']) ?>][allergy_other]" value="<?= esc($allergyOther) ?>" placeholder="Please specify if Other is selected" disabled data-student-edit-input data-other-input="allergies" maxlength="100">
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?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">
|
||
<?php
|
||
$parentContact = is_array($parentContact ?? null) ? $parentContact : [];
|
||
$parentContactStates = [
|
||
'CT' => 'Connecticut',
|
||
'ME' => 'Maine',
|
||
'MA' => 'Massachusetts',
|
||
'NH' => 'New Hampshire',
|
||
'NY' => 'New York',
|
||
'RI' => 'Rhode Island',
|
||
'VT' => 'Vermont',
|
||
];
|
||
$currentParentState = strtoupper(trim((string) ($parentContact['state'] ?? '')));
|
||
if ($currentParentState !== '' && ! isset($parentContactStates[$currentParentState])) {
|
||
$parentContactStates[$currentParentState] = $currentParentState;
|
||
}
|
||
$parentDisplayName = trim((string) ($parentContact['firstname'] ?? '') . ' ' . (string) ($parentContact['lastname'] ?? ''));
|
||
?>
|
||
<h6 class="fw-semibold">Confirm home address and phone</h6>
|
||
<div class="text-muted small mb-3">
|
||
Please review and update your contact information before continuing enrollment.
|
||
<?php if ($parentDisplayName !== ''): ?>
|
||
This account is for <?= esc($parentDisplayName) ?>.
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<label class="form-label small fw-semibold" for="parent-contact-cellphone">Phone</label>
|
||
<input type="tel"
|
||
class="form-control"
|
||
id="parent-contact-cellphone"
|
||
name="parent_contact[cellphone]"
|
||
value="<?= esc($parentContact['cellphone'] ?? '') ?>"
|
||
maxlength="12"
|
||
inputmode="numeric"
|
||
autocomplete="tel"
|
||
title="Enter a valid 10-digit phone number (e.g., 123-456-7890)"
|
||
required>
|
||
<div class="form-text text-muted">10-digit US phone number (e.g., 123-456-7890).</div>
|
||
<div id="parent-contact-cellphone-error" class="invalid-feedback"></div>
|
||
</div>
|
||
<div class="col-md-8">
|
||
<label class="form-label small fw-semibold" for="parent-contact-street">Home street address</label>
|
||
<input type="text"
|
||
class="form-control"
|
||
id="parent-contact-street"
|
||
name="parent_contact[address_street]"
|
||
value="<?= esc($parentContact['address_street'] ?? '') ?>"
|
||
maxlength="50"
|
||
autocomplete="address-line1"
|
||
title="2–50 characters. Letters, numbers, spaces, periods, and hyphens only."
|
||
required>
|
||
<div class="form-text text-muted">2–50 characters. Letters, numbers, spaces, periods, and hyphens only.</div>
|
||
<div id="parent-contact-street-error" class="invalid-feedback"></div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold" for="parent-contact-apt">Apt / Unit</label>
|
||
<input type="text"
|
||
class="form-control"
|
||
id="parent-contact-apt"
|
||
name="parent_contact[apt]"
|
||
value="<?= esc($parentContact['apt'] ?? '') ?>"
|
||
maxlength="15"
|
||
autocomplete="address-line2"
|
||
title="Optional. Letters, numbers, spaces, periods, and hyphens only.">
|
||
<div class="form-text text-muted">Optional. Max 15 characters.</div>
|
||
<div id="parent-contact-apt-error" class="invalid-feedback"></div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold" for="parent-contact-city">City</label>
|
||
<input type="text"
|
||
class="form-control"
|
||
id="parent-contact-city"
|
||
name="parent_contact[city]"
|
||
value="<?= esc($parentContact['city'] ?? '') ?>"
|
||
maxlength="30"
|
||
autocomplete="address-level2"
|
||
title="2–30 characters. Letters, spaces, periods, apostrophes, and hyphens only."
|
||
required>
|
||
<div class="form-text text-muted">2–30 characters. Letters and spaces allowed.</div>
|
||
<div id="parent-contact-city-error" class="invalid-feedback"></div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold" for="parent-contact-zip">ZIP code</label>
|
||
<input type="text"
|
||
class="form-control"
|
||
id="parent-contact-zip"
|
||
name="parent_contact[zip]"
|
||
value="<?= esc($parentContact['zip'] ?? '') ?>"
|
||
maxlength="5"
|
||
inputmode="numeric"
|
||
pattern="\d{5}"
|
||
autocomplete="postal-code"
|
||
title="Please enter a 5-digit ZIP code"
|
||
required>
|
||
<div class="form-text text-muted">5-digit US ZIP code only.</div>
|
||
<div id="parent-contact-zip-error" class="invalid-feedback"></div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small fw-semibold" for="parent-contact-state">State</label>
|
||
<select class="form-select" id="parent-contact-state" name="parent_contact[state]" required>
|
||
<option value="">Select</option>
|
||
<?php foreach ($parentContactStates as $abbr => $stateName): ?>
|
||
<option value="<?= esc($abbr) ?>" <?= $currentParentState === $abbr ? 'selected' : '' ?>><?= esc($stateName) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<div id="parent-contact-state-error" class="invalid-feedback"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-none" data-step-panel="2">
|
||
<h6 class="fw-semibold">Acknowledge school policies</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="3">
|
||
<h6 class="fw-semibold">Review tuition, fees and balance</h6>
|
||
<div class="text-muted small mb-3">Review the family account information before submitting enrollment.</div>
|
||
<?php if ($familyFinancialSummary !== []): ?>
|
||
<div class="border rounded p-3 bg-light">
|
||
<div class="row g-2">
|
||
<div class="col-md-4"><span class="text-muted">Previous-year carry-over balance:</span> <strong data-financial-value="carry_over_balance"><?= esc($money($familyFinancialSummary['carry_over_balance'] ?? 0)) ?></strong></div>
|
||
<div class="col-md-4"><span class="text-muted">Total currently due:</span> <strong data-financial-value="amount_due"><?= esc($money($familyFinancialSummary['amount_due'] ?? 0)) ?></strong></div>
|
||
</div>
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="alert alert-warning mb-0">Family account information is not available. Please contact the administration if you have questions about tuition or balance.</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="d-none" data-step-panel="4">
|
||
<h6 class="fw-semibold">Submit enrollment</h6>
|
||
<div class="text-muted small mb-3">Review the enrollment summary before submitting.</div>
|
||
|
||
<div class="mb-3">
|
||
<div class="fw-semibold mb-2">Summary</div>
|
||
<div id="enrollmentFeeReviewList" class="d-grid gap-2"></div>
|
||
</div>
|
||
|
||
<div class="enrollment-summary-card mb-3" id="parentContactReviewCard">
|
||
<div class="fw-semibold mb-2">Parent contact</div>
|
||
<div class="enrollment-summary-lines">
|
||
<div class="enrollment-summary-line">
|
||
<span class="enrollment-summary-label">Phone:</span>
|
||
<span class="enrollment-summary-value" data-parent-review="cellphone"></span>
|
||
</div>
|
||
<div class="enrollment-summary-line">
|
||
<span class="enrollment-summary-label">Home address:</span>
|
||
<span class="enrollment-summary-value" data-parent-review="address"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-md-6">
|
||
<div class="border rounded p-3 h-100">
|
||
<div class="fw-semibold mb-2">School dates</div>
|
||
<div class="small"><span class="text-muted">First school day:</span> <strong><?= esc(!empty($schoolStartDate) ? local_date($schoolStartDate, 'm-d-Y') : 'TBD') ?></strong></div>
|
||
<div class="small"><span class="text-muted">Make-up exam date:</span> <strong><?= esc(!empty($fallMakeupExamOn) ? local_date($fallMakeupExamOn, 'm-d-Y') : 'No make-up exam date currently listed') ?></strong></div>
|
||
<div class="small"><span class="text-muted">Enrollment deadline:</span> <strong><?= esc(local_date($lastDayOfRegistration, 'm-d-Y')) ?></strong></div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="border rounded p-3 h-100">
|
||
<div class="fw-semibold mb-2">Financial information</div>
|
||
<?php if ($familyFinancialSummary !== []): ?>
|
||
<div class="small"><span class="text-muted">Carry-over balance:</span> <strong data-financial-value="carry_over_balance"><?= esc($money($familyFinancialSummary['carry_over_balance'] ?? 0)) ?></strong></div>
|
||
<div class="small"><span class="text-muted">Tuition due now:</span> <strong data-financial-value="tuition_due_at_registration"><?= esc($money($familyFinancialSummary['tuition_due_at_registration'] ?? 0)) ?></strong></div>
|
||
<div class="small"><span class="text-muted">Total currently due:</span> <strong data-financial-value="amount_due"><?= esc($money($familyFinancialSummary['amount_due'] ?? 0)) ?></strong></div>
|
||
<?php else: ?>
|
||
<div class="small text-muted">Financial information is not available.</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="alert alert-warning mb-0 small">
|
||
<div class="fw-semibold mb-1">Important information</div>
|
||
<ul class="mb-0 ps-3">
|
||
<li>Submitting sends the selected enrollment(s) for newly registered students to Admissions for review.</li>
|
||
<li>Returning students who passed last year grade are automatically granted admission.</li>
|
||
<li>Payments are processed on the first day of school.</li>
|
||
<?php if (!empty($fallMakeupExamOn)): ?>
|
||
<li>Students with a make-up exam decision may initially remain in the same grade until the make-up exam result is confirmed.</li>
|
||
<?php endif; ?>
|
||
<li>Contact administration if any student information, fee, or placement detail looks incorrect.</li>
|
||
</ul>
|
||
</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>
|
||
</form>
|
||
<?php else: ?>
|
||
<p>No students found for the selected school year. Please register your kids first.</p>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="modal fade" id="enrollmentBlockModal" tabindex="-1" aria-labelledby="enrollmentBlockModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-dialog-centered">
|
||
<div class="modal-content">
|
||
<div class="modal-header bg-danger text-white">
|
||
<h5 class="modal-title" id="enrollmentBlockModalLabel">Enrollment Not Available</h5>
|
||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body" id="enrollmentBlockModalBody">
|
||
Enrollment cannot continue at this time. Please contact school administration.
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<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">
|
||
<div class="modal-header bg-danger text-white">
|
||
<h5 class="modal-title" id="deadlineModalLabel">Enrollment Closed</h5>
|
||
<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.
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">OK</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?= $this->endSection() ?>
|
||
|
||
<?= $this->section('scripts') ?>
|
||
<script>
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
const deadlinePassed = <?= $deadlinePassed ? 'true' : 'false' ?>;
|
||
const form = document.getElementById('enrollmentFlowForm');
|
||
const startButton = document.getElementById('startEnrollmentButton');
|
||
const flowModalEl = document.getElementById('enrollmentFlowModal');
|
||
const deadlineModalEl = document.getElementById('deadlineModal');
|
||
const blockModalEl = document.getElementById('enrollmentBlockModal');
|
||
const blockModalTitle = document.getElementById('enrollmentBlockModalLabel');
|
||
const blockModalHeader = blockModalEl ? blockModalEl.querySelector('.modal-header') : null;
|
||
const blockModalBody = document.getElementById('enrollmentBlockModalBody');
|
||
const flowModal = flowModalEl ? new bootstrap.Modal(flowModalEl) : null;
|
||
const deadlineModal = deadlineModalEl ? new bootstrap.Modal(deadlineModalEl) : null;
|
||
const blockModal = blockModalEl ? new bootstrap.Modal(blockModalEl) : null;
|
||
const eligibilityRefreshUrl = <?= json_encode(base_url('/parent/enrollment_eligibility_refresh'), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
||
let latestEligibility = {};
|
||
const policyAcceptedInput = document.getElementById('accept_school_policy_input');
|
||
const policyAcceptedCheckbox = document.getElementById('schoolPolicyAcceptedCheckbox');
|
||
const backButton = document.getElementById('enrollmentBackButton');
|
||
const nextButton = document.getElementById('enrollmentNextButton');
|
||
const submitButton = document.getElementById('enrollmentSubmitButton');
|
||
const feeReviewList = document.getElementById('enrollmentFeeReviewList');
|
||
const parentPhoneInput = document.getElementById('parent-contact-cellphone');
|
||
const parentStreetInput = document.getElementById('parent-contact-street');
|
||
const parentAptInput = document.getElementById('parent-contact-apt');
|
||
const parentCityInput = document.getElementById('parent-contact-city');
|
||
const parentStateInput = document.getElementById('parent-contact-state');
|
||
const parentZipInput = document.getElementById('parent-contact-zip');
|
||
const finalStep = 4;
|
||
const enrollmentStartStep = <?= (int) ($enrollmentStartStep ?? 0) ?>;
|
||
const preselectedStudentIds = <?= json_encode(array_values(array_map('intval', $preselectedStudentIds ?? [])), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
||
const feeSchedule = <?= json_encode([
|
||
'currency' => (string) ($enrollmentFeeSchedule['currency'] ?? '$'),
|
||
'firstStudentFee' => (float) ($enrollmentFeeSchedule['first_student_fee'] ?? 0),
|
||
'secondStudentFee' => (float) ($enrollmentFeeSchedule['second_student_fee'] ?? 0),
|
||
'tuitionDueAtRegistration' => (float) ($enrollmentFeeSchedule['tuition_due_at_registration'] ?? 0),
|
||
], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
||
const familyFinancial = <?= json_encode([
|
||
'carryOverBalance' => (float) ($familyFinancialSummary['carry_forward_balance'] ?? $familyFinancialSummary['carry_over_balance'] ?? 0),
|
||
'currentBalance' => (float) ($familyFinancialSummary['current_year_balance'] ?? $familyFinancialSummary['current_balance'] ?? 0),
|
||
'amountDue' => (float) ($familyFinancialSummary['total_enrollment_due'] ?? $familyFinancialSummary['amount_due'] ?? 0),
|
||
'currentYearTuitionStudentCount' => (int) ($currentYearTuitionStudentCount ?? 0),
|
||
], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
||
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")) : [];
|
||
const studentCards = () => Array.from(document.querySelectorAll('[data-student-card]'));
|
||
const requestedStudentCards = () => {
|
||
const requested = studentCards().filter(card => card.dataset.requestedStudent === '1');
|
||
return requested.length ? requested : studentCards();
|
||
};
|
||
|
||
function setStep(step) {
|
||
currentStep = Math.max(0, Math.min(finalStep, 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 === finalStep);
|
||
submitButton.classList.toggle('d-none', currentStep !== finalStep);
|
||
if (currentStep >= 3) {
|
||
updateFinancialReview();
|
||
}
|
||
if (currentStep === finalStep) {
|
||
renderReview();
|
||
}
|
||
}
|
||
|
||
function calculateSelectedTuition() {
|
||
return selectedEnrollInputs().reduce((total, input, index) => {
|
||
const familyPosition = Number(familyFinancial.currentYearTuitionStudentCount || 0) + index;
|
||
return total + (familyPosition === 0 ? Number(feeSchedule.firstStudentFee || 0) : Number(feeSchedule.secondStudentFee || 0));
|
||
}, 0);
|
||
}
|
||
|
||
function updateFinancialReview() {
|
||
const selectedCount = selectedEnrollInputs().length;
|
||
const tuitionDue = selectedCount > 0 ? calculateSelectedTuition() : 0;
|
||
const carryOver = Number(familyFinancial.carryOverBalance || 0);
|
||
const currentBalance = Number(familyFinancial.currentBalance || 0);
|
||
const amountDue = Math.max(0, carryOver) + Math.max(0, currentBalance) + tuitionDue;
|
||
const values = {
|
||
carry_over_balance: carryOver,
|
||
tuition_due_at_registration: tuitionDue,
|
||
current_balance: currentBalance,
|
||
amount_due: amountDue,
|
||
};
|
||
|
||
Object.keys(values).forEach(key => {
|
||
document.querySelectorAll('[data-financial-value="' + key + '"]').forEach(node => {
|
||
node.textContent = formatMoney(values[key]);
|
||
});
|
||
});
|
||
}
|
||
|
||
function renderReview() {
|
||
updateFinancialReview();
|
||
const cards = selectedEnrollInputs().map(input => {
|
||
const card = input.closest('[data-student-card]');
|
||
const firstName = card ? card.querySelector("input[name$='[firstname]']")?.value?.trim() : '';
|
||
const lastName = card ? card.querySelector("input[name$='[lastname]']")?.value?.trim() : '';
|
||
const dob = card ? card.querySelector("input[name$='[dob]']")?.value?.trim() : '';
|
||
const grade = card ? card.querySelector("input[name$='[registration_grade]']")?.value?.trim() : '';
|
||
const gender = card ? card.querySelector("input[name$='[gender]']")?.value?.trim() : '';
|
||
const schoolId = card?.dataset.schoolId || 'N/A';
|
||
const currentGrade = card?.dataset.currentGrade || '';
|
||
const expectedPlacement = card?.dataset.expectedPlacement || '';
|
||
const requiredAction = card?.dataset.requiredAction || '';
|
||
const isNewStudent = card?.dataset.isNew === '1';
|
||
const name = (firstName + ' ' + lastName).trim() || card?.querySelector('.fw-semibold')?.textContent?.trim() || 'Student';
|
||
return {
|
||
name,
|
||
schoolId,
|
||
dob,
|
||
gender,
|
||
registrationGrade: isNewStudent ? grade : '',
|
||
currentGrade,
|
||
expectedPlacement,
|
||
requiredAction,
|
||
};
|
||
});
|
||
|
||
if (!feeReviewList) {
|
||
return;
|
||
}
|
||
|
||
if (!cards.length) {
|
||
feeReviewList.innerHTML = '<div class="alert alert-warning mb-0">No students selected.</div>';
|
||
renderParentContactReview();
|
||
return;
|
||
}
|
||
|
||
feeReviewList.innerHTML = cards.map((student, index) => {
|
||
const familyPosition = Number(familyFinancial.currentYearTuitionStudentCount || 0) + index;
|
||
const tuitionFee = familyPosition === 0 ? feeSchedule.firstStudentFee : feeSchedule.secondStudentFee;
|
||
const lines = [
|
||
['Student', student.name],
|
||
['School ID', student.schoolId || 'N/A'],
|
||
['Date of birth', student.dob || 'N/A'],
|
||
['Gender', student.gender || 'N/A'],
|
||
['Tuition', formatMoney(tuitionFee)],
|
||
['Current grade', student.currentGrade || 'N/A'],
|
||
['Expected placement', student.expectedPlacement || 'Pending'],
|
||
];
|
||
|
||
return '<div class="enrollment-summary-card">' +
|
||
'<div class="enrollment-summary-lines">' +
|
||
lines.map(([label, value]) => {
|
||
return '<div class="enrollment-summary-line">' +
|
||
'<span class="enrollment-summary-label">' + escapeHtml(label) + ':</span>' +
|
||
'<span class="enrollment-summary-value">' + escapeHtml(value) + '</span>' +
|
||
'</div>';
|
||
}).join('') +
|
||
'</div>' +
|
||
'</div>';
|
||
}).join('');
|
||
renderParentContactReview();
|
||
}
|
||
|
||
function escapeHtml(value) {
|
||
return String(value).replace(/[&<>"']/g, function(char) {
|
||
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[char];
|
||
});
|
||
}
|
||
|
||
function formatMoney(amount) {
|
||
const numericAmount = Number(amount || 0);
|
||
return String(feeSchedule.currency || '$') + numericAmount.toFixed(2);
|
||
}
|
||
|
||
function digitsOnly(value) {
|
||
return String(value || '').replace(/\D/g, '');
|
||
}
|
||
|
||
function formatPhoneDisplay(value) {
|
||
const digits = digitsOnly(value).substring(0, 10);
|
||
if (digits.length > 6) {
|
||
return digits.substring(0, 3) + '-' + digits.substring(3, 6) + '-' + digits.substring(6);
|
||
}
|
||
if (digits.length > 3) {
|
||
return digits.substring(0, 3) + '-' + digits.substring(3);
|
||
}
|
||
return digits;
|
||
}
|
||
|
||
function titleCaseContact(value) {
|
||
return String(value || '')
|
||
.trim()
|
||
.replace(/\s+/g, ' ')
|
||
.toLowerCase()
|
||
.replace(/(^|[\s\-'])([a-z])/g, function(_, prefix, letter) {
|
||
return prefix + letter.toUpperCase();
|
||
});
|
||
}
|
||
|
||
const parentContactRules = {
|
||
phone: /^\(?\d{3}\)?[-\s]?\d{3}[-\s]?\d{4}$/,
|
||
street: /^[A-Za-z0-9.\s-]{2,50}$/,
|
||
apt: /^[A-Za-z0-9.\s-]{0,15}$/,
|
||
city: /^[A-Za-z\s.'-]{2,30}$/,
|
||
zip: /^\d{5}$/,
|
||
};
|
||
|
||
function setParentContactError(input, message) {
|
||
if (!input) {
|
||
return;
|
||
}
|
||
const error = document.getElementById(input.id + '-error');
|
||
input.classList.toggle('is-invalid', !!message);
|
||
if (error) {
|
||
error.textContent = message || '';
|
||
}
|
||
}
|
||
|
||
function parentContactAddress() {
|
||
const street = parentStreetInput?.value?.trim() || '';
|
||
const apt = parentAptInput?.value?.trim() || '';
|
||
const city = parentCityInput?.value?.trim() || '';
|
||
const state = parentStateInput?.value?.trim() || '';
|
||
const zip = parentZipInput?.value?.trim() || '';
|
||
const line = [street, apt].filter(Boolean).join(', ');
|
||
const cityLine = [city, state, zip].filter(Boolean).join(' ');
|
||
return [line, cityLine].filter(Boolean).join(', ') || 'N/A';
|
||
}
|
||
|
||
function renderParentContactReview() {
|
||
const phoneNode = document.querySelector('[data-parent-review="cellphone"]');
|
||
const addressNode = document.querySelector('[data-parent-review="address"]');
|
||
if (phoneNode) {
|
||
phoneNode.textContent = formatPhoneDisplay(parentPhoneInput?.value || '') || 'N/A';
|
||
}
|
||
if (addressNode) {
|
||
addressNode.textContent = parentContactAddress();
|
||
}
|
||
}
|
||
|
||
function validateParentContactField(input) {
|
||
if (!input) {
|
||
return true;
|
||
}
|
||
|
||
const value = input.value.trim();
|
||
let message = '';
|
||
|
||
switch (input.id) {
|
||
case 'parent-contact-cellphone':
|
||
if (!parentContactRules.phone.test(value) || digitsOnly(value).length !== 10) {
|
||
message = 'Please enter a valid 10-digit phone number.';
|
||
}
|
||
break;
|
||
case 'parent-contact-street':
|
||
if (!parentContactRules.street.test(value)) {
|
||
message = 'Street address must be 2–50 characters and may contain only letters, numbers, spaces, periods, and hyphens.';
|
||
}
|
||
break;
|
||
case 'parent-contact-apt':
|
||
if (value && !parentContactRules.apt.test(value)) {
|
||
message = 'Apartment or unit may contain only letters, numbers, spaces, periods, and hyphens.';
|
||
}
|
||
break;
|
||
case 'parent-contact-city':
|
||
if (!parentContactRules.city.test(value)) {
|
||
message = 'City must be 2–30 characters and may contain only letters, spaces, periods, apostrophes, and hyphens.';
|
||
}
|
||
break;
|
||
case 'parent-contact-zip':
|
||
if (!parentContactRules.zip.test(digitsOnly(value))) {
|
||
message = 'Please enter a valid 5-digit ZIP code.';
|
||
}
|
||
break;
|
||
case 'parent-contact-state':
|
||
if (!value) {
|
||
message = 'Please select your state.';
|
||
}
|
||
break;
|
||
}
|
||
|
||
setParentContactError(input, message);
|
||
return message === '';
|
||
}
|
||
|
||
function validateParentContact() {
|
||
const fields = [
|
||
parentPhoneInput,
|
||
parentStreetInput,
|
||
parentAptInput,
|
||
parentCityInput,
|
||
parentZipInput,
|
||
parentStateInput,
|
||
];
|
||
fields.forEach(shapeParentContactField);
|
||
renderParentContactReview();
|
||
let firstInvalid = null;
|
||
fields.forEach(field => {
|
||
if (!validateParentContactField(field) && !firstInvalid) {
|
||
firstInvalid = field;
|
||
}
|
||
});
|
||
if (firstInvalid) {
|
||
firstInvalid.focus();
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function shapeParentContactField(input) {
|
||
if (!input) {
|
||
return;
|
||
}
|
||
if (input === parentPhoneInput) {
|
||
input.value = formatPhoneDisplay(input.value);
|
||
return;
|
||
}
|
||
if (input === parentZipInput) {
|
||
input.value = digitsOnly(input.value).substring(0, 5);
|
||
return;
|
||
}
|
||
if (input === parentStreetInput || input === parentCityInput) {
|
||
input.value = titleCaseContact(input.value);
|
||
return;
|
||
}
|
||
if (input === parentAptInput) {
|
||
input.value = input.value.trim().replace(/\s+/g, ' ').toUpperCase();
|
||
}
|
||
}
|
||
|
||
function selectStudentsForEnrollment(ids) {
|
||
const wanted = Array.isArray(ids) ? ids.map(Number).filter(id => id > 0) : [];
|
||
const cards = wanted.length ? requestedStudentCards() : studentCards();
|
||
cards.forEach(card => {
|
||
if (card.dataset.selectable !== '1') {
|
||
return;
|
||
}
|
||
const studentId = Number(card.dataset.studentId || 0);
|
||
if (wanted.length && !wanted.includes(studentId)) {
|
||
return;
|
||
}
|
||
const input = card.querySelector('[data-enroll-input]');
|
||
if (!input) {
|
||
return;
|
||
}
|
||
input.checked = true;
|
||
card.classList.add('is-selected');
|
||
setStudentFieldsEnabled(card, true);
|
||
});
|
||
}
|
||
|
||
function openEnrollmentAtStep(step) {
|
||
if (!flowModal || deadlinePassed) {
|
||
return false;
|
||
}
|
||
if (selectedEnrollInputs().length === 0) {
|
||
selectStudentsForEnrollment(preselectedStudentIds);
|
||
}
|
||
if (selectedEnrollInputs().length === 0) {
|
||
return false;
|
||
}
|
||
setStep(step);
|
||
flowModal.show();
|
||
return true;
|
||
}
|
||
|
||
function syncPolicyAccepted() {
|
||
hasAcceptedSchoolPolicy = !!(policyAcceptedCheckbox && policyAcceptedCheckbox.checked);
|
||
if (policyAcceptedInput) {
|
||
policyAcceptedInput.value = hasAcceptedSchoolPolicy ? '1' : '0';
|
||
}
|
||
}
|
||
|
||
function setStudentFieldsEnabled(card, enabled) {
|
||
card.querySelectorAll('[data-student-edit-input]').forEach(field => {
|
||
field.disabled = !enabled;
|
||
});
|
||
syncHealthGroup(card, 'medical_conditions');
|
||
syncHealthGroup(card, 'allergies');
|
||
}
|
||
|
||
function syncHealthGroup(card, groupName) {
|
||
const selected = !!card.querySelector('[data-enroll-input]')?.checked;
|
||
const group = card.querySelector('[data-health-group="' + groupName + '"]');
|
||
const otherInput = card.querySelector('[data-other-input="' + groupName + '"]');
|
||
if (!group) {
|
||
return;
|
||
}
|
||
|
||
const noneChecked = !!group.querySelector('[data-health-option="none"]:checked');
|
||
const otherChecked = !!group.querySelector('[data-health-option="other"]:checked');
|
||
|
||
group.querySelectorAll('input[type="checkbox"]').forEach(input => {
|
||
if (!selected) {
|
||
input.disabled = true;
|
||
return;
|
||
}
|
||
|
||
if (input.dataset.healthOption === 'none') {
|
||
input.disabled = false;
|
||
return;
|
||
}
|
||
|
||
if (noneChecked) {
|
||
input.checked = false;
|
||
input.disabled = true;
|
||
return;
|
||
}
|
||
|
||
input.disabled = false;
|
||
});
|
||
|
||
if (otherInput) {
|
||
otherInput.classList.toggle('d-none', !otherChecked);
|
||
otherInput.disabled = !selected || !otherChecked;
|
||
if (!otherChecked) {
|
||
otherInput.value = '';
|
||
}
|
||
}
|
||
}
|
||
|
||
function validateSelectedStudentHealth() {
|
||
for (const input of selectedEnrollInputs()) {
|
||
const card = input.closest('[data-student-card]');
|
||
if (!card) {
|
||
continue;
|
||
}
|
||
const name = card.querySelector('.fw-semibold')?.textContent?.trim() || 'Student';
|
||
const photo = card.querySelector("select[name$='[photo_consent]']");
|
||
if (!photo || photo.value === '') {
|
||
alert('Please select photo consent for ' + name + '.');
|
||
return false;
|
||
}
|
||
|
||
const medicalChecked = card.querySelectorAll('[data-health-group="medical_conditions"] input[type="checkbox"]:checked');
|
||
if (!medicalChecked.length) {
|
||
alert('Please select medical conditions for ' + name + '.');
|
||
return false;
|
||
}
|
||
const medicalOther = card.querySelector('[data-other-input="medical_conditions"]');
|
||
if (card.querySelector('[data-health-group="medical_conditions"] [data-health-option="other"]:checked') && (!medicalOther || !medicalOther.value.trim())) {
|
||
alert('Please specify the other medical condition for ' + name + '.');
|
||
return false;
|
||
}
|
||
|
||
const allergyChecked = card.querySelectorAll('[data-health-group="allergies"] input[type="checkbox"]:checked');
|
||
if (!allergyChecked.length) {
|
||
alert('Please select allergies for ' + name + '.');
|
||
return false;
|
||
}
|
||
const allergyOther = card.querySelector('[data-other-input="allergies"]');
|
||
if (card.querySelector('[data-health-group="allergies"] [data-health-option="other"]:checked') && (!allergyOther || !allergyOther.value.trim())) {
|
||
alert('Please specify the other allergy for ' + name + '.');
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
studentCards().forEach(card => {
|
||
card.querySelectorAll('[data-health-group]').forEach(group => {
|
||
group.addEventListener('change', function(event) {
|
||
const target = event.target;
|
||
if (!(target instanceof HTMLInputElement) || target.type !== 'checkbox') {
|
||
return;
|
||
}
|
||
const groupName = group.getAttribute('data-health-group');
|
||
if (!groupName) {
|
||
return;
|
||
}
|
||
|
||
if (target.dataset.healthOption === 'none' && target.checked) {
|
||
group.querySelectorAll('[data-health-option="item"], [data-health-option="other"]').forEach(input => {
|
||
input.checked = false;
|
||
});
|
||
}
|
||
|
||
if ((target.dataset.healthOption === 'item' || target.dataset.healthOption === 'other') && target.checked) {
|
||
const noneInput = group.querySelector('[data-health-option="none"]');
|
||
if (noneInput) {
|
||
noneInput.checked = false;
|
||
}
|
||
}
|
||
|
||
syncHealthGroup(card, groupName);
|
||
});
|
||
});
|
||
});
|
||
|
||
function showBlockModal(message, options = {}) {
|
||
const alreadyEnrolled = options.alreadyEnrolled === true;
|
||
const warning = options.warning === true;
|
||
if (blockModalTitle) {
|
||
blockModalTitle.textContent = options.title || (alreadyEnrolled ? 'Already Enrolled' : 'Enrollment Not Available');
|
||
}
|
||
if (blockModalHeader) {
|
||
blockModalHeader.classList.toggle('bg-danger', !alreadyEnrolled && !warning);
|
||
blockModalHeader.classList.toggle('bg-success', alreadyEnrolled && !warning);
|
||
blockModalHeader.classList.toggle('bg-warning', warning);
|
||
blockModalHeader.classList.toggle('text-dark', warning);
|
||
blockModalHeader.classList.toggle('text-white', !warning);
|
||
}
|
||
if (blockModalBody) {
|
||
const messages = Array.isArray(options.messages) ? options.messages.filter(Boolean) : [];
|
||
if (messages.length > 1) {
|
||
const list = document.createElement('ul');
|
||
list.className = 'mb-0 ps-4';
|
||
messages.forEach(item => {
|
||
const li = document.createElement('li');
|
||
const separatorIndex = item.indexOf(': ');
|
||
if (separatorIndex > 0) {
|
||
const name = document.createElement('strong');
|
||
name.textContent = item.substring(0, separatorIndex);
|
||
li.appendChild(name);
|
||
li.appendChild(document.createTextNode(item.substring(separatorIndex)));
|
||
} else {
|
||
li.textContent = item;
|
||
}
|
||
list.appendChild(li);
|
||
});
|
||
blockModalBody.replaceChildren(list);
|
||
} else {
|
||
blockModalBody.textContent = messages[0] || message || (alreadyEnrolled
|
||
? 'This student is already enrolled for the selected school year.'
|
||
: 'Enrollment cannot continue at this time. Please contact school administration.');
|
||
}
|
||
}
|
||
if (blockModal) {
|
||
blockModal.show();
|
||
}
|
||
}
|
||
|
||
function blockDetailsForCard(card) {
|
||
const studentId = String(card?.dataset?.studentId || '');
|
||
const row = latestEligibility[studentId] || {};
|
||
const alreadyEnrolled = row.decision === 'ALREADY_ENROLLED' || card.dataset.alreadyEnrolled === '1';
|
||
return {
|
||
alreadyEnrolled,
|
||
title: row.block_title || card.dataset.blockTitle || (alreadyEnrolled ? 'Already Enrolled' : 'Enrollment Not Available'),
|
||
message: row.primary_parent_message || card.dataset.blockMessage || '',
|
||
};
|
||
}
|
||
|
||
function blockMessagesForCards(cards) {
|
||
return cards
|
||
.map(card => {
|
||
const studentId = String(card.dataset.studentId || '');
|
||
const row = latestEligibility[studentId] || {};
|
||
const message = row.primary_parent_message
|
||
|| (row.blocking_rule_codes || []).join(', ')
|
||
|| card.dataset.blockMessage
|
||
|| '';
|
||
if (!message) {
|
||
return '';
|
||
}
|
||
const name = row.student_name || card.dataset.studentName || 'Student';
|
||
return name + ': ' + message;
|
||
})
|
||
.filter(Boolean);
|
||
}
|
||
|
||
async function refreshEligibility() {
|
||
const response = await fetch(eligibilityRefreshUrl, {
|
||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||
credentials: 'same-origin',
|
||
});
|
||
if (!response.ok) {
|
||
throw new Error('Unable to refresh eligibility.');
|
||
}
|
||
const payload = await response.json();
|
||
latestEligibility = {};
|
||
(payload.students || []).forEach(row => {
|
||
latestEligibility[String(row.student_id)] = row;
|
||
});
|
||
if (payload.financial_summary) {
|
||
familyFinancial.carryOverBalance = Number(payload.financial_summary.carry_forward_balance || 0);
|
||
familyFinancial.currentBalance = Number(payload.financial_summary.current_year_balance || 0);
|
||
familyFinancial.amountDue = Number(payload.financial_summary.total_enrollment_due || 0);
|
||
updateFinancialReview();
|
||
}
|
||
applyEligibilityToCards();
|
||
return latestEligibility;
|
||
}
|
||
|
||
function applyEligibilityToCards() {
|
||
let shouldReload = false;
|
||
|
||
studentCards().forEach(card => {
|
||
const studentId = String(card.dataset.studentId || '');
|
||
const row = latestEligibility[studentId];
|
||
if (!row) {
|
||
return;
|
||
}
|
||
|
||
const wasBlocked = card.dataset.selectable !== '1';
|
||
const canEnroll = (row.can_enroll === true || row.parent_enrollment_allowed === true) && !deadlinePassed;
|
||
card.dataset.selectable = canEnroll ? '1' : '0';
|
||
card.classList.toggle('is-disabled', !canEnroll);
|
||
if (row.primary_parent_message) {
|
||
card.dataset.blockMessage = row.primary_parent_message;
|
||
}
|
||
if (row.decision === 'ALREADY_ENROLLED') {
|
||
card.dataset.alreadyEnrolled = '1';
|
||
card.dataset.blockTitle = row.block_title || 'Already Enrolled';
|
||
}
|
||
|
||
if (wasBlocked && canEnroll && !card.querySelector('[data-enroll-input]')) {
|
||
shouldReload = true;
|
||
}
|
||
});
|
||
|
||
if (shouldReload) {
|
||
window.location.reload();
|
||
}
|
||
}
|
||
|
||
if (startButton && flowModal) {
|
||
startButton.addEventListener('click', async function() {
|
||
if (deadlinePassed) {
|
||
if (deadlineModal) deadlineModal.show();
|
||
return;
|
||
}
|
||
try {
|
||
await refreshEligibility();
|
||
} catch (error) {
|
||
alert(error.message || 'Unable to refresh enrollment eligibility.');
|
||
return;
|
||
}
|
||
|
||
const cardsToConsider = requestedStudentCards();
|
||
const eligibleCards = cardsToConsider.filter(card => {
|
||
const studentId = String(card.dataset.studentId || '');
|
||
const row = latestEligibility[studentId] || {};
|
||
return row.can_enroll === true || row.parent_enrollment_allowed === true;
|
||
});
|
||
|
||
if (eligibleCards.length === 0) {
|
||
const otherBlockedCards = studentCards().filter(card => !cardsToConsider.includes(card));
|
||
const blockedMessages = blockMessagesForCards(cardsToConsider)
|
||
.concat(blockMessagesForCards(otherBlockedCards));
|
||
const firstBlocked = cardsToConsider
|
||
.map(card => latestEligibility[String(card.dataset.studentId || '')])
|
||
.find(row => row && (row.primary_parent_message || (row.blocking_rule_codes || []).length > 0))
|
||
|| Object.values(latestEligibility).find(row => row.primary_parent_message || (row.blocking_rule_codes || []).length > 0);
|
||
const alreadyEnrolled = firstBlocked?.decision === 'ALREADY_ENROLLED';
|
||
const blockReason = firstBlocked?.primary_parent_message
|
||
|| (firstBlocked?.blocking_rule_codes || []).join(', ')
|
||
|| 'No students are currently eligible for enrollment.';
|
||
showBlockModal(blockReason, {
|
||
alreadyEnrolled,
|
||
messages: blockedMessages,
|
||
warning: alreadyEnrolled && blockedMessages.length > 1,
|
||
title: firstBlocked?.block_title || (alreadyEnrolled ? 'Already Enrolled' : 'Enrollment Not Available'),
|
||
});
|
||
return;
|
||
}
|
||
|
||
setStep(0);
|
||
flowModal.show();
|
||
});
|
||
}
|
||
|
||
studentCards().forEach(card => {
|
||
card.addEventListener('click', async function(event) {
|
||
if (event.target.closest('input, select, textarea, label')) {
|
||
return;
|
||
}
|
||
if (this.dataset.selectable !== '1') {
|
||
try {
|
||
await refreshEligibility();
|
||
} catch (error) {
|
||
// Fall back to the last known server-rendered message.
|
||
}
|
||
if (this.dataset.selectable !== '1') {
|
||
const details = blockDetailsForCard(this);
|
||
showBlockModal(details.message, details);
|
||
return;
|
||
}
|
||
}
|
||
const input = this.querySelector('[data-enroll-input]');
|
||
if (!input) {
|
||
return;
|
||
}
|
||
input.checked = !input.checked;
|
||
this.classList.toggle('is-selected', input.checked);
|
||
setStudentFieldsEnabled(this, input.checked);
|
||
});
|
||
});
|
||
|
||
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 === 0 && !validateSelectedStudentHealth()) {
|
||
return;
|
||
}
|
||
if (currentStep === 1 && !validateParentContact()) {
|
||
return;
|
||
}
|
||
if (currentStep === 2) {
|
||
syncPolicyAccepted();
|
||
if (!hasAcceptedSchoolPolicy) {
|
||
alert('Please read and accept the school policies before continuing.');
|
||
return;
|
||
}
|
||
}
|
||
setStep(currentStep + 1);
|
||
});
|
||
}
|
||
|
||
if (form) {
|
||
form.addEventListener('submit', function(e) {
|
||
if (e.submitter && e.submitter.id === 'withdrawSubmitButton') {
|
||
selectedEnrollInputs().forEach(input => {
|
||
input.checked = false;
|
||
const card = input.closest('[data-student-card]');
|
||
card?.classList.remove('is-selected');
|
||
if (card) {
|
||
setStudentFieldsEnabled(card, false);
|
||
}
|
||
});
|
||
}
|
||
|
||
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 submitting.');
|
||
return;
|
||
}
|
||
|
||
if (deadlinePassed && anyEnroll) {
|
||
e.preventDefault();
|
||
if (deadlineModal) deadlineModal.show();
|
||
return;
|
||
}
|
||
|
||
if (anyEnroll && !validateSelectedStudentHealth()) {
|
||
e.preventDefault();
|
||
setStep(0);
|
||
if (flowModal) flowModal.show();
|
||
return;
|
||
}
|
||
|
||
if (anyEnroll && !validateParentContact()) {
|
||
e.preventDefault();
|
||
setStep(1);
|
||
if (flowModal) flowModal.show();
|
||
return;
|
||
}
|
||
|
||
if (anyEnroll && !hasAcceptedSchoolPolicy) {
|
||
e.preventDefault();
|
||
setStep(2);
|
||
if (flowModal) flowModal.show();
|
||
return;
|
||
}
|
||
|
||
if (!anyEnroll && anyWithdraw) {
|
||
const ok = confirm('Confirm withdrawal request for the selected student(s)?');
|
||
if (!ok) {
|
||
e.preventDefault();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
[
|
||
parentPhoneInput,
|
||
parentStreetInput,
|
||
parentAptInput,
|
||
parentCityInput,
|
||
parentZipInput,
|
||
parentStateInput,
|
||
].forEach(input => {
|
||
if (!input) {
|
||
return;
|
||
}
|
||
input.addEventListener('input', function() {
|
||
if (input === parentPhoneInput) {
|
||
input.value = formatPhoneDisplay(input.value);
|
||
}
|
||
if (input === parentZipInput) {
|
||
input.value = digitsOnly(input.value).substring(0, 5);
|
||
}
|
||
validateParentContactField(input);
|
||
renderParentContactReview();
|
||
});
|
||
input.addEventListener('blur', function() {
|
||
shapeParentContactField(input);
|
||
validateParentContactField(input);
|
||
renderParentContactReview();
|
||
});
|
||
});
|
||
|
||
refreshEligibility().then(() => {
|
||
if (enrollmentStartStep >= 1 && startButton && !startButton.disabled && !<?= $flashError ? 'true' : 'false' ?>) {
|
||
openEnrollmentAtStep(enrollmentStartStep - 1);
|
||
}
|
||
}).catch(() => {
|
||
if (enrollmentStartStep >= 1 && startButton && !startButton.disabled && !<?= $flashError ? 'true' : 'false' ?>) {
|
||
openEnrollmentAtStep(enrollmentStartStep - 1);
|
||
}
|
||
// Keep server-rendered eligibility if refresh fails.
|
||
});
|
||
});
|
||
</script>
|
||
<?= $this->endSection() ?>
|