This commit is contained in:
@@ -25,9 +25,88 @@ $deadlineISO = $deadlineObj->format('Y-m-d\TH:i:sP'); // for JS
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="alert alert-danger"><?= esc(session()->getFlashdata('error')) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php $hasAcceptedSchoolPolicy = (bool) ($hasAcceptedSchoolPolicy ?? false); ?>
|
||||
<?php
|
||||
$fallMakeupExamLabel = !empty($fallMakeupExamOn) ? local_date($fallMakeupExamOn, 'm-d-Y') : null;
|
||||
$decisionMessageForStudent = static function (array $student) use ($fallMakeupExamLabel): array {
|
||||
$decisionRow = is_array($student['previous_year_decision'] ?? null) ? $student['previous_year_decision'] : [];
|
||||
$decision = trim((string) ($decisionRow['decision'] ?? ''));
|
||||
$source = strtolower(trim((string) ($decisionRow['source'] ?? '')));
|
||||
$decisionKey = strtolower($decision);
|
||||
$name = trim((string) ($student['firstname'] ?? 'Your child') . ' ' . (string) ($student['lastname'] ?? ''));
|
||||
$name = $name !== '' ? $name : 'Your child';
|
||||
$previousClass = trim((string) ($decisionRow['class_section_name'] ?? ''));
|
||||
$previousClass = $previousClass !== '' ? $previousClass : 'the same class';
|
||||
|
||||
if ($decisionKey === 'pass' || ($decisionKey === '' && $source !== 'pending')) {
|
||||
return ['message' => '', 'blocking' => false, 'level' => 'info'];
|
||||
}
|
||||
|
||||
if ($decisionKey === 'make-up exam in fall') {
|
||||
$dateText = $fallMakeupExamLabel !== null ? ' on ' . $fallMakeupExamLabel : '';
|
||||
return [
|
||||
'message' => $name . ' has a make-up exam decision. Enrollment can be completed only after the fall make-up exam' . $dateText . ' and after the result is finalized.',
|
||||
'blocking' => true,
|
||||
'level' => 'warning',
|
||||
];
|
||||
}
|
||||
|
||||
if ($decisionKey === 'deferred decision') {
|
||||
return [
|
||||
'message' => $name . ' has a deferred decision. Enrollment can be completed only after you visit the administration to discuss your child\'s situation.',
|
||||
'blocking' => true,
|
||||
'level' => 'warning',
|
||||
];
|
||||
}
|
||||
|
||||
if ($decisionKey === 'repeat class') {
|
||||
return [
|
||||
'message' => $name . ' has a repeat class decision and is accepted only in ' . $previousClass . '.',
|
||||
'blocking' => false,
|
||||
'level' => 'info',
|
||||
];
|
||||
}
|
||||
|
||||
if ($decisionKey === 'expel') {
|
||||
return [
|
||||
'message' => $name . ' has an expel decision. Enrollment cannot be completed online; please contact the administration.',
|
||||
'blocking' => true,
|
||||
'level' => 'danger',
|
||||
];
|
||||
}
|
||||
|
||||
if ($decisionKey === 'withdrawn') {
|
||||
return [
|
||||
'message' => $name . ' was marked withdrawn in the final decision. Please contact the administration before requesting enrollment.',
|
||||
'blocking' => true,
|
||||
'level' => 'warning',
|
||||
];
|
||||
}
|
||||
|
||||
if ($decisionKey === '' || $source === 'pending') {
|
||||
return [
|
||||
'message' => $name . ' does not have a final promotion decision yet. Enrollment can be completed only after the administration finalizes the decision.',
|
||||
'blocking' => true,
|
||||
'level' => 'warning',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'message' => $name . ' has a "' . $decision . '" decision. Please contact the administration before requesting enrollment.',
|
||||
'blocking' => true,
|
||||
'level' => 'warning',
|
||||
];
|
||||
};
|
||||
?>
|
||||
|
||||
<?php if (!empty($students)): ?>
|
||||
<form action="<?= base_url('/parent/enroll_classes_handler') ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" id="accept_school_policy_input" name="accept_school_policy" value="<?= $hasAcceptedSchoolPolicy ? '1' : '0' ?>">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered align-middle">
|
||||
<thead>
|
||||
@@ -46,6 +125,7 @@ $deadlineISO = $deadlineObj->format('Y-m-d\TH:i:sP'); // for JS
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($students as $index => $student): ?>
|
||||
<?php $decisionMessage = $decisionMessageForStudent($student); ?>
|
||||
<tr>
|
||||
<td><?= $index + 1 ?></td>
|
||||
<td><?= esc($student['school_id'] ?? 'N/A') ?></td>
|
||||
@@ -69,6 +149,9 @@ $deadlineISO = $deadlineObj->format('Y-m-d\TH:i:sP'); // for JS
|
||||
<input type="checkbox"
|
||||
name="enroll[]"
|
||||
value="<?= esc($student['id']) ?>"
|
||||
data-decision-message="<?= esc($decisionMessage['message']) ?>"
|
||||
data-decision-blocking="<?= $decisionMessage['blocking'] ? '1' : '0' ?>"
|
||||
data-decision-message-target="enrollment-decision-message-<?= esc($student['id']) ?>"
|
||||
<?= $disableEnrollUI ? 'disabled' : '' ?>>
|
||||
<?php elseif (in_array($student['enrollment_status'], ['enrolled', 'admission under review', 'payment pending', 'withdraw under review'])): ?>
|
||||
<input type="checkbox" checked disabled>
|
||||
@@ -127,6 +210,15 @@ $deadlineISO = $deadlineObj->format('Y-m-d\TH:i:sP'); // for JS
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php if ($decisionMessage['message'] !== ''): ?>
|
||||
<tr id="enrollment-decision-message-<?= esc($student['id']) ?>" class="enrollment-decision-message-row d-none">
|
||||
<td colspan="10">
|
||||
<div class="alert alert-<?= esc($decisionMessage['level']) ?> mb-0">
|
||||
<?= esc($decisionMessage['message']) ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -158,6 +250,32 @@ $deadlineISO = $deadlineObj->format('Y-m-d\TH:i:sP'); // for JS
|
||||
<?php else: ?>
|
||||
<p>No students found for the selected school year. Please register your kids first.</p>
|
||||
<?php endif; ?>
|
||||
<!-- School Policy Modal -->
|
||||
<div class="modal fade" id="schoolPolicyModal" tabindex="-1" aria-labelledby="schoolPolicyModalLabel" aria-hidden="true" data-bs-backdrop="static">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-success text-white">
|
||||
<h5 class="modal-title" id="schoolPolicyModalLabel">School Policies</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<iframe src="<?= base_url('policy/school_policy') ?>" width="100%" height="520" frameborder="0" title="School Policies"></iframe>
|
||||
<div class="form-check mt-3">
|
||||
<input class="form-check-input" type="checkbox" value="1" id="schoolPolicyAcceptedCheckbox" <?= $hasAcceptedSchoolPolicy ? 'checked disabled' : '' ?>>
|
||||
<label class="form-check-label" for="schoolPolicyAcceptedCheckbox">
|
||||
I have read and accept all school policies.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-success" id="acceptSchoolPolicyButton" <?= $hasAcceptedSchoolPolicy ? '' : 'disabled' ?>>
|
||||
Accept Policy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Enrollment Deadline Modal -->
|
||||
<div class="modal fade" id="deadlineModal" tabindex="-1" aria-labelledby="deadlineModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
@@ -188,13 +306,56 @@ $deadlineISO = $deadlineObj->format('Y-m-d\TH:i:sP'); // for JS
|
||||
// Values from PHP
|
||||
const deadlinePassed = <?= $deadlinePassed ? 'true' : 'false' ?>;
|
||||
const enrollmentDeadline = new Date("<?= esc($deadlineISO) ?>");
|
||||
let hasAcceptedSchoolPolicy = <?= $hasAcceptedSchoolPolicy ? 'true' : 'false' ?>;
|
||||
|
||||
// Bootstrap Modal
|
||||
const modalEl = document.getElementById('deadlineModal');
|
||||
const deadlineModal = modalEl ? new bootstrap.Modal(modalEl) : null;
|
||||
const policyModalEl = document.getElementById('schoolPolicyModal');
|
||||
const policyModal = policyModalEl ? new bootstrap.Modal(policyModalEl) : null;
|
||||
const policyAcceptedInput = document.getElementById('accept_school_policy_input');
|
||||
const policyAcceptedCheckbox = document.getElementById('schoolPolicyAcceptedCheckbox');
|
||||
const acceptPolicyButton = document.getElementById('acceptSchoolPolicyButton');
|
||||
const showDeadlineModal = () => {
|
||||
if (deadlineModal) deadlineModal.show();
|
||||
};
|
||||
const showPolicyModal = () => {
|
||||
if (policyModal) policyModal.show();
|
||||
};
|
||||
const setDecisionMessageVisible = (checkbox, visible) => {
|
||||
const targetId = checkbox.dataset.decisionMessageTarget || '';
|
||||
const target = targetId ? document.getElementById(targetId) : null;
|
||||
if (target) {
|
||||
target.classList.toggle('d-none', !visible);
|
||||
}
|
||||
};
|
||||
const hideAllDecisionMessages = () => {
|
||||
document.querySelectorAll('.enrollment-decision-message-row').forEach(row => {
|
||||
row.classList.add('d-none');
|
||||
});
|
||||
};
|
||||
|
||||
if (policyAcceptedCheckbox && acceptPolicyButton) {
|
||||
policyAcceptedCheckbox.addEventListener('change', function() {
|
||||
acceptPolicyButton.disabled = !this.checked;
|
||||
});
|
||||
}
|
||||
|
||||
if (acceptPolicyButton) {
|
||||
acceptPolicyButton.addEventListener('click', function() {
|
||||
if (policyAcceptedCheckbox && !policyAcceptedCheckbox.checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasAcceptedSchoolPolicy = true;
|
||||
if (policyAcceptedInput) {
|
||||
policyAcceptedInput.value = '1';
|
||||
}
|
||||
if (policyModal) {
|
||||
policyModal.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The enrollment form
|
||||
const form = document.querySelector("form[action*='enroll_classes_handler']");
|
||||
@@ -202,11 +363,35 @@ $deadlineISO = $deadlineObj->format('Y-m-d\TH:i:sP'); // for JS
|
||||
// 1) Block clicking "enroll" checkboxes after deadline
|
||||
document.querySelectorAll("input[name='enroll[]']").forEach(cb => {
|
||||
cb.addEventListener("click", function(e) {
|
||||
hideAllDecisionMessages();
|
||||
|
||||
if (deadlinePassed) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
this.checked = false;
|
||||
showDeadlineModal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasAcceptedSchoolPolicy) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
this.checked = false;
|
||||
showPolicyModal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.checked && this.dataset.decisionMessage) {
|
||||
setDecisionMessageVisible(this, true);
|
||||
|
||||
if (this.dataset.decisionBlocking === '1') {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
this.checked = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
setDecisionMessageVisible(this, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -223,6 +408,12 @@ $deadlineISO = $deadlineObj->format('Y-m-d\TH:i:sP'); // for JS
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasAcceptedSchoolPolicy && anyBoxesChecked) {
|
||||
e.preventDefault();
|
||||
showPolicyModal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (deadlinePassed && anyBoxesChecked) {
|
||||
e.preventDefault();
|
||||
showDeadlineModal();
|
||||
|
||||
@@ -110,7 +110,7 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
||||
?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?php if ($isEditable && $kid['enrollment'] == 0): ?>
|
||||
<?php if ($isEditable && !empty($kid['can_delete'])): ?>
|
||||
<form action="<?= base_url('/parent/delete_student/' . $kid['id']) ?>"
|
||||
method="post"
|
||||
style="display:inline">
|
||||
@@ -126,7 +126,7 @@ $disableEmergencyBtn = count($emergencies) >= $maxEmergency;
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" disabled title="<?= $isEditable ? 'Student is already enrolled' : 'This school year is read-only' ?>">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" disabled title="<?= $isEditable ? 'Student has enrollment history and cannot be deleted' : 'This school year is read-only' ?>">
|
||||
<i class="fas fa-ban"></i> Delete
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -218,30 +218,6 @@
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['pass'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Other</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['other_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Queued</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['queued'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Assigned</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['assigned'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Applied</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['applied'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Pending</div>
|
||||
@@ -250,14 +226,8 @@
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Missing</div>
|
||||
<div class="fw-semibold text-danger"><?= esc((string) ($promotionSummary['missing_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-6">
|
||||
<div class="border rounded p-2 h-100">
|
||||
<div class="text-muted small">Missing Queue</div>
|
||||
<div class="fw-semibold text-danger"><?= esc((string) ($promotionSummary['missing_queue'] ?? 0)) ?></div>
|
||||
<div class="text-muted small">Other</div>
|
||||
<div class="fw-semibold"><?= esc((string) ($promotionSummary['other_decision'] ?? 0)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
$money = static fn ($value): string => '$' . number_format((float) $value, 2);
|
||||
$activeId = (int) ($activeYear['id'] ?? 0);
|
||||
$nextDraftId = (int) ($nextDraftYear['id'] ?? 0);
|
||||
$defaultNewPreviousYear = $activeYear ?: ($schoolYears[0] ?? null);
|
||||
$defaultNewPreviousYearName = (string) ($defaultNewPreviousYear['name'] ?? 'None');
|
||||
$previousYearNameByNewYearName = [];
|
||||
foreach (($schoolYears ?? []) as $existingYear) {
|
||||
$existingName = (string) ($existingYear['name'] ?? '');
|
||||
if (preg_match('/^(\d{4})-(\d{4})$/', $existingName, $matches)) {
|
||||
$previousYearNameByNewYearName[$matches[2] . '-' . ((int) $matches[2] + 1)] = $existingName;
|
||||
}
|
||||
}
|
||||
$closingPreviewUrl = $activeId > 0
|
||||
? site_url('administrator/school-years/' . $activeId . '/closing/preview' . ($nextDraftId > 0 ? '?' . http_build_query(['target_school_year_id' => $nextDraftId]) : ''))
|
||||
: '';
|
||||
@@ -85,13 +94,15 @@
|
||||
<input class="form-control" id="new_school_year_name" name="name" placeholder="2026-2027" required pattern="\d{4}-\d{4}">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="new_previous_school_year_id">Previous Year</label>
|
||||
<select class="form-select" id="new_previous_school_year_id" name="previous_school_year_id">
|
||||
<option value="">None</option>
|
||||
<?php foreach (($schoolYears ?? []) as $existingYear): ?>
|
||||
<option value="<?= (int) ($existingYear['id'] ?? 0) ?>"><?= esc($existingYear['name'] ?? '') ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<label class="form-label" for="new_previous_school_year_display">Previous Year</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="new_previous_school_year_display"
|
||||
type="text"
|
||||
value="<?= esc($defaultNewPreviousYearName, 'attr') ?>"
|
||||
data-default-previous-year="<?= esc($defaultNewPreviousYearName, 'attr') ?>"
|
||||
readonly
|
||||
>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="new_school_year_starts_on">Starts On</label>
|
||||
@@ -109,6 +120,10 @@
|
||||
<label class="form-label" for="new_registration_ends_on">Registration Ends</label>
|
||||
<input class="form-control" id="new_registration_ends_on" name="registration_ends_on" type="date">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="new_fall_makeup_exam_on">Fall Makeup Exam</label>
|
||||
<input class="form-control" id="new_fall_makeup_exam_on" name="fall_makeup_exam_on" type="date">
|
||||
</div>
|
||||
<div class="col-md-2 d-flex gap-2">
|
||||
<button class="btn btn-primary" type="submit">Save Draft</button>
|
||||
<button class="btn btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#schoolYearCreateForm">Cancel</button>
|
||||
@@ -149,6 +164,7 @@
|
||||
<td>
|
||||
<div><?= $formatDate($year['starts_on'] ?? null) ?></div>
|
||||
<div class="text-muted small"><?= $formatDate($year['ends_on'] ?? null) ?></div>
|
||||
<div class="text-muted small">Fall makeup: <?= $formatDate($year['fall_makeup_exam_on'] ?? null) ?></div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge <?= esc($statusClasses[$status] ?? 'bg-secondary') ?>">
|
||||
@@ -338,6 +354,10 @@
|
||||
<label class="form-label" for="registration_ends_on_<?= $id ?>">Registration Ends</label>
|
||||
<input class="form-control" id="registration_ends_on_<?= $id ?>" name="registration_ends_on" type="date" value="<?= esc($year['registration_ends_on'] ?? '') ?>">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label" for="fall_makeup_exam_on_<?= $id ?>">Fall Makeup Exam</label>
|
||||
<input class="form-control" id="fall_makeup_exam_on_<?= $id ?>" name="fall_makeup_exam_on" type="date" value="<?= esc($year['fall_makeup_exam_on'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<label class="form-label" for="description_<?= $id ?>">Description</label>
|
||||
<textarea class="form-control" id="description_<?= $id ?>" name="description" rows="3"><?= esc($year['description'] ?? '') ?></textarea>
|
||||
@@ -415,6 +435,21 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
order: [[0, 'desc']]
|
||||
});
|
||||
}
|
||||
|
||||
const newYearInput = document.getElementById('new_school_year_name');
|
||||
const previousYearDisplay = document.getElementById('new_previous_school_year_display');
|
||||
const previousYearByNewYear = <?= json_encode($previousYearNameByNewYearName, JSON_UNESCAPED_SLASHES) ?>;
|
||||
|
||||
if (newYearInput && previousYearDisplay) {
|
||||
const defaultPreviousYear = previousYearDisplay.dataset.defaultPreviousYear || 'None';
|
||||
const updatePreviousYear = function () {
|
||||
const newYearName = newYearInput.value.trim();
|
||||
previousYearDisplay.value = previousYearByNewYear[newYearName] || defaultPreviousYear;
|
||||
};
|
||||
|
||||
newYearInput.addEventListener('input', updatePreviousYear);
|
||||
updatePreviousYear();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -257,6 +257,9 @@
|
||||
(Read school policies here)
|
||||
</button>
|
||||
</label>
|
||||
<?php if (isset($errors['accept_school_policy'])): ?>
|
||||
<div class="text-danger small"><?= esc($errors['accept_school_policy']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-center border p-2 bg-light item">
|
||||
|
||||
Reference in New Issue
Block a user