fix enrollment, invoice, payment and financila aid
This commit is contained in:
@@ -254,17 +254,35 @@ $statusBadge = static function (?string $status): string {
|
||||
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;
|
||||
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'])) {
|
||||
$evaluation = is_array($student['transition_evaluation'] ?? null) ? $student['transition_evaluation'] : [];
|
||||
if (($evaluation['can_enroll'] ?? false) === true && !$deadlinePassed && $isEditable) {
|
||||
$enrollableCount++;
|
||||
}
|
||||
if (($student['enrollment_status'] ?? '') === 'enrolled' && $isEditable) {
|
||||
$withdrawableCount++;
|
||||
}
|
||||
}
|
||||
$studentCount = count($students ?? []);
|
||||
?>
|
||||
|
||||
<div class="container my-4">
|
||||
@@ -311,15 +329,17 @@ foreach (($students ?? []) as $student) {
|
||||
<button type="button"
|
||||
class="btn btn-success btn-lg w-100"
|
||||
id="startEnrollmentButton"
|
||||
<?= (!$isEditable || $enrollableCount === 0 || $deadlinePassed) ? 'disabled' : '' ?>>
|
||||
<?= (!$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 available for new enrollment.</div>
|
||||
<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>
|
||||
|
||||
@@ -332,6 +352,7 @@ foreach (($students ?? []) as $student) {
|
||||
<th>Decision</th>
|
||||
<th>Required Action</th>
|
||||
<th>Status</th>
|
||||
<th>Eligibility</th>
|
||||
<th>Withdraw</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -352,8 +373,24 @@ foreach (($students ?? []) as $student) {
|
||||
</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="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) {
|
||||
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">
|
||||
@@ -464,12 +501,19 @@ foreach (($students ?? []) as $student) {
|
||||
<?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';
|
||||
$canEnroll = ($student['enrollment_status'] ?? '') === 'not enrolled'
|
||||
$canEnroll = ($evaluation['can_enroll'] ?? false) === true
|
||||
&& !$deadlinePassed
|
||||
&& $isEditable
|
||||
&& empty($eligibilityMessage['blocking']);
|
||||
&& $isEditable;
|
||||
$hasSettledEnrollment = $hasSettledEnrollmentStatus($student['enrollment_status'] ?? '', $student['admission_status'] ?? '');
|
||||
$blockMessage = $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)
|
||||
@@ -502,7 +546,10 @@ foreach (($students ?? []) as $student) {
|
||||
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-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">
|
||||
@@ -622,7 +669,11 @@ foreach (($students ?? []) as $student) {
|
||||
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>
|
||||
@@ -631,8 +682,12 @@ foreach (($students ?? []) as $student) {
|
||||
id="parent-contact-street"
|
||||
name="parent_contact[address_street]"
|
||||
value="<?= esc($parentContact['address_street'] ?? '') ?>"
|
||||
maxlength="255"
|
||||
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>
|
||||
@@ -641,7 +696,11 @@ foreach (($students ?? []) as $student) {
|
||||
id="parent-contact-apt"
|
||||
name="parent_contact[apt]"
|
||||
value="<?= esc($parentContact['apt'] ?? '') ?>"
|
||||
maxlength="15">
|
||||
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>
|
||||
@@ -650,8 +709,12 @@ foreach (($students ?? []) as $student) {
|
||||
id="parent-contact-city"
|
||||
name="parent_contact[city]"
|
||||
value="<?= esc($parentContact['city'] ?? '') ?>"
|
||||
maxlength="100"
|
||||
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>
|
||||
@@ -662,7 +725,12 @@ foreach (($students ?? []) as $student) {
|
||||
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>
|
||||
@@ -672,6 +740,7 @@ foreach (($students ?? []) as $student) {
|
||||
<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>
|
||||
@@ -777,6 +846,22 @@ foreach (($students ?? []) as $student) {
|
||||
<?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">
|
||||
@@ -804,8 +889,15 @@ foreach (($students ?? []) as $student) {
|
||||
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');
|
||||
@@ -825,17 +917,12 @@ foreach (($students ?? []) as $student) {
|
||||
'currency' => (string) ($enrollmentFeeSchedule['currency'] ?? '$'),
|
||||
'firstStudentFee' => (float) ($enrollmentFeeSchedule['first_student_fee'] ?? 0),
|
||||
'secondStudentFee' => (float) ($enrollmentFeeSchedule['second_student_fee'] ?? 0),
|
||||
'registrationFee' => (float) ($enrollmentFeeSchedule['registration_fee'] ?? 0),
|
||||
'tuitionDueAtRegistration' => (float) ($enrollmentFeeSchedule['tuition_due_at_registration'] ?? 0),
|
||||
'mandatoryFees' => (float) ($enrollmentFeeSchedule['mandatory_fees'] ?? 0),
|
||||
], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
||||
const familyFinancial = <?= json_encode([
|
||||
'carryOverBalance' => (float) ($familyFinancialSummary['carry_over_balance'] ?? 0),
|
||||
'registrationFee' => (float) ($familyFinancialSummary['registration_fee'] ?? 0),
|
||||
'tuitionDueAtRegistration' => (float) ($familyFinancialSummary['tuition_due_at_registration'] ?? 0),
|
||||
'mandatoryFees' => (float) ($familyFinancialSummary['mandatory_fees'] ?? 0),
|
||||
'currentBalance' => (float) ($familyFinancialSummary['current_balance'] ?? 0),
|
||||
'amountDue' => (float) ($familyFinancialSummary['amount_due'] ?? 0),
|
||||
'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),
|
||||
], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>;
|
||||
let currentStep = 0;
|
||||
let hasAcceptedSchoolPolicy = <?= $hasAcceptedSchoolPolicy ? 'true' : 'false' ?>;
|
||||
@@ -872,15 +959,11 @@ foreach (($students ?? []) as $student) {
|
||||
const selectedCount = selectedEnrollInputs().length;
|
||||
const tuitionDue = selectedCount > 0 ? calculateSelectedTuition() : 0;
|
||||
const carryOver = Number(familyFinancial.carryOverBalance || 0);
|
||||
const registrationFee = Number(familyFinancial.registrationFee || feeSchedule.registrationFee || 0);
|
||||
const mandatoryFees = Number(familyFinancial.mandatoryFees || feeSchedule.mandatoryFees || 0);
|
||||
const currentBalance = Number(familyFinancial.currentBalance || 0);
|
||||
const amountDue = Math.max(0, carryOver) + Math.max(0, currentBalance) + registrationFee + tuitionDue + mandatoryFees;
|
||||
const amountDue = Math.max(0, carryOver) + Math.max(0, currentBalance) + tuitionDue;
|
||||
const values = {
|
||||
carry_over_balance: carryOver,
|
||||
registration_fee: registrationFee,
|
||||
tuition_due_at_registration: tuitionDue,
|
||||
mandatory_fees: mandatoryFees,
|
||||
current_balance: currentBalance,
|
||||
amount_due: amountDue,
|
||||
};
|
||||
@@ -982,6 +1065,35 @@ foreach (($students ?? []) as $student) {
|
||||
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() || '';
|
||||
@@ -1004,36 +1116,96 @@ foreach (($students ?? []) as $student) {
|
||||
}
|
||||
}
|
||||
|
||||
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 phone = digitsOnly(parentPhoneInput?.value || '');
|
||||
if (phone.length !== 10) {
|
||||
alert('Please enter a valid 10-digit phone number.');
|
||||
parentPhoneInput?.focus();
|
||||
return false;
|
||||
}
|
||||
if (!parentStreetInput || parentStreetInput.value.trim().length < 5) {
|
||||
alert('Please enter your home street address.');
|
||||
parentStreetInput?.focus();
|
||||
return false;
|
||||
}
|
||||
if (!parentCityInput || parentCityInput.value.trim().length < 2) {
|
||||
alert('Please enter your city.');
|
||||
parentCityInput?.focus();
|
||||
return false;
|
||||
}
|
||||
if (!parentStateInput || parentStateInput.value.trim() === '') {
|
||||
alert('Please select your state.');
|
||||
parentStateInput?.focus();
|
||||
return false;
|
||||
}
|
||||
if (!parentZipInput || digitsOnly(parentZipInput.value).length !== 5) {
|
||||
alert('Please enter a valid 5-digit ZIP code.');
|
||||
parentZipInput?.focus();
|
||||
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) : [];
|
||||
document.querySelectorAll('[data-student-card]').forEach(card => {
|
||||
@@ -1192,24 +1364,143 @@ foreach (($students ?? []) as $student) {
|
||||
});
|
||||
});
|
||||
|
||||
function showBlockModal(message, options = {}) {
|
||||
const alreadyEnrolled = options.alreadyEnrolled === true;
|
||||
if (blockModalTitle) {
|
||||
blockModalTitle.textContent = options.title || (alreadyEnrolled ? 'Already Enrolled' : 'Enrollment Not Available');
|
||||
}
|
||||
if (blockModalHeader) {
|
||||
blockModalHeader.classList.toggle('bg-danger', !alreadyEnrolled);
|
||||
blockModalHeader.classList.toggle('bg-success', alreadyEnrolled);
|
||||
}
|
||||
if (blockModalBody) {
|
||||
blockModalBody.textContent = 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 || '',
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
document.querySelectorAll('[data-student-card]').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 && !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', function() {
|
||||
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 eligibleCards = Array.from(document.querySelectorAll('[data-student-card]')).filter(card => {
|
||||
const studentId = String(card.dataset.studentId || '');
|
||||
return latestEligibility[studentId]?.can_enroll === true;
|
||||
});
|
||||
|
||||
if (eligibleCards.length === 0) {
|
||||
const firstBlocked = 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,
|
||||
title: firstBlocked?.block_title || (alreadyEnrolled ? 'Already Enrolled' : 'Enrollment Not Available'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setStep(0);
|
||||
flowModal.show();
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-student-card]').forEach(card => {
|
||||
card.addEventListener('click', function(event) {
|
||||
card.addEventListener('click', async function(event) {
|
||||
if (event.target.closest('input, select, textarea, label')) {
|
||||
return;
|
||||
}
|
||||
if (this.dataset.selectable !== '1') {
|
||||
return;
|
||||
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) {
|
||||
@@ -1320,21 +1611,41 @@ foreach (($students ?? []) as $student) {
|
||||
});
|
||||
}
|
||||
|
||||
if (parentPhoneInput) {
|
||||
parentPhoneInput.addEventListener('input', function() {
|
||||
this.value = formatPhoneDisplay(this.value);
|
||||
[
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
if (parentZipInput) {
|
||||
parentZipInput.addEventListener('input', function() {
|
||||
this.value = digitsOnly(this.value).substring(0, 5);
|
||||
input.addEventListener('blur', function() {
|
||||
shapeParentContactField(input);
|
||||
validateParentContactField(input);
|
||||
renderParentContactReview();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (enrollmentStartStep >= 2 && startButton && !startButton.disabled && !<?= $flashError ? 'true' : 'false' ?>) {
|
||||
openEnrollmentAtStep(enrollmentStartStep - 1);
|
||||
}
|
||||
|
||||
refreshEligibility().catch(() => {
|
||||
// Keep server-rendered eligibility if refresh fails.
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
Reference in New Issue
Block a user