fix logo circle, start email body
This commit is contained in:
@@ -311,7 +311,8 @@ class Services extends BaseService
|
||||
return new \App\Services\EnrollmentRegistrationEmailService(
|
||||
\Config\Database::connect(),
|
||||
static::enrollmentTransition(),
|
||||
static::emailService()
|
||||
static::emailService(),
|
||||
model(\App\Models\ConfigurationModel::class)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ class EnrollmentAdminController extends BaseController
|
||||
'schoolYears' => $this->schoolYears(),
|
||||
'classSections' => $this->classSections($schoolYear),
|
||||
'admins' => $this->adminUsers(),
|
||||
'enrollmentFollowups' => $this->enrollmentFollowups($schoolYear, $openFlags),
|
||||
'auditRows' => $this->auditRows($schoolYear),
|
||||
'activeExceptions' => $canManageExceptions ? $this->enrollmentExceptions($schoolYear) : [],
|
||||
'exceptionNeeded' => $this->exceptionNeededFromFlags($openFlags),
|
||||
|
||||
@@ -1115,7 +1115,7 @@ class StudentController extends BaseController
|
||||
$builder->where('is_active', 1);
|
||||
}
|
||||
|
||||
$this->applyDistributionAgeFilter($builder, $year);
|
||||
$this->applyDistributionAgeFilter($builder, $year, 'dob');
|
||||
$rows = array_merge($rows, $builder->get()->getResultArray());
|
||||
}
|
||||
|
||||
@@ -1326,7 +1326,7 @@ class StudentController extends BaseController
|
||||
$builder->where('is_active', 1);
|
||||
}
|
||||
|
||||
$this->applyDistributionAgeFilter($builder, $year);
|
||||
$this->applyDistributionAgeFilter($builder, $year, 'dob');
|
||||
$rows = $builder
|
||||
->orderBy('lastname', 'ASC')
|
||||
->orderBy('firstname', 'ASC')
|
||||
@@ -2138,14 +2138,14 @@ class StudentController extends BaseController
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function applyDistributionAgeFilter($builder, string $schoolYear): void
|
||||
private function applyDistributionAgeFilter($builder, string $schoolYear, string $dobColumn = 'students.dob'): void
|
||||
{
|
||||
[$earliestDob, $latestDob] = $this->distributionAgeBirthDateWindow($schoolYear);
|
||||
|
||||
$builder
|
||||
->where('students.dob IS NOT NULL', null, false)
|
||||
->where('students.dob >=', $earliestDob)
|
||||
->where('students.dob <=', $latestDob);
|
||||
->where($dobColumn . ' IS NOT NULL', null, false)
|
||||
->where($dobColumn . ' >=', $earliestDob)
|
||||
->where($dobColumn . ' <=', $latestDob);
|
||||
}
|
||||
|
||||
private function distributionAgeBirthDateWindow(string $schoolYear): array
|
||||
|
||||
@@ -2,18 +2,20 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\ConfigurationModel;
|
||||
use App\Support\Enrollment\DeliberationDecision;
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class EnrollmentRegistrationEmailService
|
||||
{
|
||||
public const TEMPLATE_VERSION = 'phase5_consolidated_v2';
|
||||
public const TEMPLATE_VERSION = 'phase5_consolidated_v3';
|
||||
|
||||
public function __construct(
|
||||
private readonly BaseConnection $db,
|
||||
private readonly EnrollmentTransitionService $transitionService,
|
||||
private readonly EmailService $emailService,
|
||||
private readonly ?ConfigurationModel $configurationModel = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -151,6 +153,7 @@ final class EnrollmentRegistrationEmailService
|
||||
$opens = $this->dateText($schoolYear['registration_opens_at'] ?? $schoolYear['registration_starts_on'] ?? null);
|
||||
$studentRows = [];
|
||||
$studentIds = [];
|
||||
$hasReEnrollmentEligibleStudent = false;
|
||||
|
||||
foreach ($family['students'] as $student) {
|
||||
$studentId = (int) ($student['id'] ?? 0);
|
||||
@@ -161,18 +164,16 @@ final class EnrollmentRegistrationEmailService
|
||||
$evaluation = $this->transitionService->evaluate($studentId, $previousYear, $schoolYearName, 'parent');
|
||||
$studentRows[] = $this->studentRow($student, $evaluation, $opens, $deadline);
|
||||
$studentIds[] = $studentId;
|
||||
$hasReEnrollmentEligibleStudent = $hasReEnrollmentEligibleStudent || $this->canCompleteParentReEnrollment($evaluation);
|
||||
}
|
||||
|
||||
$financial = $this->financialSection((int) $family['parent_user_id'], $schoolYear, $previousYear);
|
||||
$subject = 'Registration for ' . $schoolYearName . ' Is Now Open';
|
||||
$bodyHtml = '<p>Dear ' . esc($family['name']) . ',</p>'
|
||||
. '<p>Registration for the ' . esc($schoolYearName) . ' school year is now open. Please review the student summary below and complete the portal steps before ' . esc($deadline) . '.</p>'
|
||||
. '<p>Registration for the ' . esc($schoolYearName) . ' school year is now open. Please review the student(s) summary below and complete the portal steps before ' . esc($deadline) . '.</p>'
|
||||
. $this->studentsTable($studentRows)
|
||||
. $financial
|
||||
. '<h3>Registration Steps</h3>'
|
||||
. '<p>Re-enrollment opens on ' . esc($opens) . ' and closes on ' . esc($deadline) . '. Complete re-enrollment for each eligible student before the deadline to secure their enrollment for the upcoming school year.</p>'
|
||||
. '<ol><li>Sign in to the parent portal.</li><li>Review student information and upload any required documents.</li><li>Acknowledge school policies.</li><li>Review tuition, fees, and any carry-over balance.</li><li>Submit Enrollment.</li></ol>'
|
||||
. '<p>Registration portal: <a href="' . esc(site_url('parent/enroll_classes')) . '">' . esc(site_url('parent/enroll_classes')) . '</a></p>'
|
||||
. $this->registrationStepsSection($opens, $deadline, $hasReEnrollmentEligibleStudent)
|
||||
. '<p>For assistance, please contact the school administration.</p>'
|
||||
. '<p>Sincerely,<br>Al Rahma Sunday School<br>School Administration</p>';
|
||||
|
||||
@@ -189,16 +190,32 @@ final class EnrollmentRegistrationEmailService
|
||||
];
|
||||
}
|
||||
|
||||
private function canCompleteParentReEnrollment(array $evaluation): bool
|
||||
{
|
||||
return (bool) ($evaluation['parent_enrollment_allowed'] ?? false);
|
||||
}
|
||||
|
||||
private function registrationStepsSection(string $opens, string $deadline, bool $include): string
|
||||
{
|
||||
if (! $include) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<h3>Re-enrollment Steps</h3>'
|
||||
. '<p>Re-enrollment opens on ' . esc($opens) . ' and closes on ' . esc($deadline) . '. Complete re-enrollment for each eligible student before the deadline to secure their enrollment for the upcoming school year.</p>'
|
||||
. '<ol><li>Sign in to the parent portal.</li><li>Review/update student and contact information.</li><li>Acknowledge school policies.</li><li>Review tuition, fees and any carry-over balance.</li><li>Submit Enrollment.</li></ol>'
|
||||
. '<p>Registration portal: <a href="' . esc(site_url('parent/enroll_classes')) . '">' . esc(site_url('parent/enroll_classes')) . '</a></p>';
|
||||
}
|
||||
|
||||
private function studentsTable(array $studentRows): string
|
||||
{
|
||||
if ($studentRows === []) {
|
||||
return '<p>No eligible linked students were found for this email.</p>';
|
||||
}
|
||||
|
||||
return '<table style="width:100%; border-collapse:collapse; margin:12px 0 18px; font-size:14px;">'
|
||||
. '<tbody>'
|
||||
return '<div style="margin:12px 0 18px;">'
|
||||
. implode('', $studentRows)
|
||||
. '</tbody></table>';
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
private function studentRow(array $student, array $evaluation, string $opens, string $deadline): string
|
||||
@@ -207,17 +224,52 @@ final class EnrollmentRegistrationEmailService
|
||||
$decision = DeliberationDecision::display((string) ($evaluation['deliberation_decision'] ?? ''));
|
||||
$status = $this->registrationStatus($evaluation);
|
||||
$placement = $this->placementText($evaluation);
|
||||
$requiredAction = $this->requiredAction($evaluation, $deadline);
|
||||
$requiredAction = $this->requiredAction($evaluation, $deadline, $name);
|
||||
$message = $this->decisionMessage($name, $evaluation, $opens, $deadline);
|
||||
$nextStepHtml = $this->decisionMessageHtml($message, (string) ($evaluation['deliberation_decision'] ?? ''));
|
||||
if ($requiredAction !== '') {
|
||||
$nextStepHtml .= '<br><strong>' . esc($requiredAction) . '</strong>';
|
||||
}
|
||||
|
||||
return '<table role="presentation" cellpadding="0" cellspacing="0" style="width:100%; border:1px solid #d9e2ec; border-collapse:collapse; margin:0 0 12px; font-size:14px; line-height:1.45;">'
|
||||
. '<tbody>'
|
||||
. '<tr>'
|
||||
. '<td style="background:#f4f8fb; border-bottom:1px solid #d9e2ec; padding:10px 12px; vertical-align:top; word-break:break-word;">'
|
||||
. '<strong style="font-size:16px; color:#1f2937;">' . esc($name) . '</strong>'
|
||||
. '</td>'
|
||||
. '</tr>'
|
||||
. $this->studentDetailRow('Decision', esc($decision ?: 'Pending'))
|
||||
. $this->studentDetailRow('Registration Status', '<strong>' . esc($status) . '</strong><br>' . esc($placement))
|
||||
. $this->studentDetailRow('Next Step', $nextStepHtml)
|
||||
. '</tbody></table>';
|
||||
}
|
||||
|
||||
private function studentDetailRow(string $label, string $valueHtml): string
|
||||
{
|
||||
return '<tr>'
|
||||
. '<td style="border:1px solid #ddd; padding:8px; vertical-align:top;">' . esc($name) . '</td>'
|
||||
. '<td style="border:1px solid #ddd; padding:8px; vertical-align:top;">' . esc($decision ?: 'Pending') . '</td>'
|
||||
. '<td style="border:1px solid #ddd; padding:8px; vertical-align:top;"><strong>' . esc($status) . '</strong><br>' . esc($placement) . '</td>'
|
||||
. '<td style="border:1px solid #ddd; padding:8px; vertical-align:top;">' . esc($message) . '<br><strong>' . esc($requiredAction) . '</strong></td>'
|
||||
. '<td style="border-bottom:1px solid #edf2f7; padding:10px 12px; vertical-align:top; word-break:break-word;">'
|
||||
. '<div style="font-size:12px; line-height:1.3; color:#64748b; text-transform:uppercase; font-weight:bold; margin:0 0 4px;">' . esc($label) . '</div>'
|
||||
. '<div style="color:#1f2937;">' . $valueHtml . '</div>'
|
||||
. '</td>'
|
||||
. '</tr>';
|
||||
}
|
||||
|
||||
private function decisionMessageHtml(string $message, string $decision): string
|
||||
{
|
||||
if ($decision !== DeliberationDecision::MAKE_UP_EXAM) {
|
||||
return nl2br(esc($message), false);
|
||||
}
|
||||
|
||||
$lines = explode("\n", $message);
|
||||
$lastIndex = count($lines) - 1;
|
||||
foreach ($lines as $index => $line) {
|
||||
$escaped = esc($line);
|
||||
$lines[$index] = $index === $lastIndex ? '<strong>' . $escaped . '</strong>' : $escaped;
|
||||
}
|
||||
|
||||
return implode('<br>', $lines);
|
||||
}
|
||||
|
||||
private function decisionMessage(string $name, array $evaluation, string $opens, string $deadline): string
|
||||
{
|
||||
if ((string) ($evaluation['deliberation_decision'] ?? '') === DeliberationDecision::PASSED) {
|
||||
@@ -231,12 +283,29 @@ final class EnrollmentRegistrationEmailService
|
||||
}
|
||||
|
||||
return match ((string) ($evaluation['deliberation_decision'] ?? '')) {
|
||||
DeliberationDecision::REPEAT_CLASS => 'The deliberation decision for ' . $name . ' is to repeat the current grade. After registration is completed, the student will remain in the same grade and class when available.',
|
||||
DeliberationDecision::MAKE_UP_EXAM => 'The deliberation decision for ' . $name . ' is pending the result of a make-up exam. Registration may be completed now. The student will initially remain in the same grade.',
|
||||
DeliberationDecision::REPEAT_CLASS => 'The deliberation decision for ' . $name . ' is to repeat the current grade. After re-enrollment is completed, the student will remain in the same grade.',
|
||||
DeliberationDecision::MAKE_UP_EXAM => 'The final academic decision for ' . $name . ' is currently pending the result of a make-up exam. This exam is scheduled for ' . $this->makeupExamDateText() . ' from 9:30 AM to 11:00 AM at ISGL.'
|
||||
. "\n" . 'The exam result will determine whether the student advances to the next grade or repeats the current class. Failure to attend the make-up exam will automatically result in the student repeating the class, as no further retake opportunities will be available.'
|
||||
. "\n" . 'You must re-enroll ' . $name . ' before the make-up exam can be taken.',
|
||||
default => 'Please review the registration portal for the current enrollment status.',
|
||||
};
|
||||
}
|
||||
|
||||
private function makeupExamDateText(): string
|
||||
{
|
||||
$raw = trim((string) (($this->configurationModel ?? new ConfigurationModel())->getConfig('make-up-exam') ?? ''));
|
||||
if ($raw === '') {
|
||||
return 'a date to be announced';
|
||||
}
|
||||
|
||||
$timestamp = strtotime($raw);
|
||||
if ($timestamp !== false) {
|
||||
return date('m-d-Y', $timestamp);
|
||||
}
|
||||
|
||||
return $raw;
|
||||
}
|
||||
|
||||
private function financialSection(int $parentId, array $schoolYear, ?string $previousYear): string
|
||||
{
|
||||
$carry = $previousYear !== null ? $this->invoiceBalance($parentId, $previousYear) : 0.0;
|
||||
@@ -250,14 +319,11 @@ final class EnrollmentRegistrationEmailService
|
||||
|
||||
$message = trim((string) ($schoolYear['financial_policy_message'] ?? ''));
|
||||
if ($message === '') {
|
||||
$message = 'The balance is shown for information and does not currently block registration.';
|
||||
$message = 'The balance needs to be settled with the school before the re-enrollment process can be started.';
|
||||
}
|
||||
|
||||
return '<h3>Family Account Information</h3>'
|
||||
. '<p><strong>Carry-over balance:</strong> $' . number_format($carry, 2) . '<br>'
|
||||
. '<strong>Registration fee:</strong> $' . number_format($registrationFee, 2) . '<br>'
|
||||
. '<strong>New-year tuition due now:</strong> $' . number_format($tuition, 2) . '<br>'
|
||||
. '<strong>Mandatory fees:</strong> $' . number_format($mandatory, 2) . '<br>'
|
||||
. '<strong>Total currently due:</strong> $' . number_format($total, 2) . '</p>'
|
||||
. '<p>' . esc($message) . '</p>';
|
||||
}
|
||||
@@ -428,13 +494,13 @@ final class EnrollmentRegistrationEmailService
|
||||
'automatic_distribution_pending' => $this->assignedGradeText($evaluation),
|
||||
'same_class_assigned' => 'Same grade and class',
|
||||
'temporary_same_grade' => 'Same grade initially',
|
||||
'manual_class_required' => 'Same grade - administrative class assignment required',
|
||||
'manual_class_required' => 'Same grade',
|
||||
'exit_required' => 'Completion or exit process required',
|
||||
default => 'Pending',
|
||||
};
|
||||
}
|
||||
|
||||
private function requiredAction(array $evaluation, string $deadline): string
|
||||
private function requiredAction(array $evaluation, string $deadline, string $name = 'The student'): string
|
||||
{
|
||||
if ((string) ($evaluation['deliberation_decision'] ?? '') === DeliberationDecision::PASSED) {
|
||||
return 'Complete re-enrollment before ' . $deadline . '.';
|
||||
@@ -444,7 +510,7 @@ final class EnrollmentRegistrationEmailService
|
||||
return ($evaluation['adult_student'] ?? false) ? 'Student must complete the authorized adult-student process or contact administration.' : 'Contact the school administration.';
|
||||
}
|
||||
return ($evaluation['deliberation_decision'] ?? '') === DeliberationDecision::MAKE_UP_EXAM
|
||||
? 'Complete re-enrollment and follow the school instructions regarding the make-up exam.'
|
||||
? ''
|
||||
: 'Complete re-enrollment before ' . $deadline . '.';
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
@@ -222,7 +222,6 @@ $nonOverridableCodes = ['STUDENT_NOT_LINKED', 'SOURCE_YEAR_NOT_FOUND', 'TARGET_Y
|
||||
$previewStudents = is_array($preview['students'] ?? null) ? $preview['students'] : [];
|
||||
$hasGrantableStudent = false;
|
||||
$openFlagCount = (int) ($openFlagCount ?? 0);
|
||||
$followupCount = count($enrollmentFollowups ?? []);
|
||||
$activeExceptionCount = 0;
|
||||
foreach (($activeExceptions ?? []) as $exceptionRow) {
|
||||
if (($exceptionRow['status'] ?? '') === 'active') {
|
||||
@@ -273,15 +272,9 @@ $reasonCodes = is_array($exceptionReasonCodes ?? null) ? $exceptionReasonCodes :
|
||||
<span class="badge rounded-pill <?= $openFlagCount > 0 ? 'bg-primary' : 'bg-secondary' ?> tab-count"><?= $openFlagCount ?></span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="followup-tab" data-bs-toggle="tab" data-bs-target="#followup-pane" type="button" role="tab">
|
||||
2. Follow-up
|
||||
<span class="badge rounded-pill bg-secondary tab-count"><?= $followupCount ?></span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="emails-tab" data-bs-toggle="tab" data-bs-target="#emails-pane" type="button" role="tab">
|
||||
3. Parent emails
|
||||
2. Parent emails
|
||||
<span class="badge rounded-pill <?= $launchApproved ? 'bg-success' : ($launchReady ? 'bg-info text-dark' : 'bg-danger') ?> tab-count"><?= esc($emailStepLabel) ?></span>
|
||||
</button>
|
||||
</li>
|
||||
@@ -348,8 +341,7 @@ $reasonCodes = is_array($exceptionReasonCodes ?? null) ? $exceptionReasonCodes :
|
||||
<th>School ID</th>
|
||||
<th>Issue</th>
|
||||
<th>Details</th>
|
||||
<th>Assigned</th>
|
||||
<th>Created</th>
|
||||
<th>Update by</th>
|
||||
<th class="no-sort" style="min-width: 280px;">What to do</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -380,7 +372,6 @@ $reasonCodes = is_array($exceptionReasonCodes ?? null) ? $exceptionReasonCodes :
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?= esc($flag['assignee_name'] ?: '') ?></td>
|
||||
<td><?= esc(!empty($flag['created_at']) ? local_datetime($flag['created_at'], 'm-d-Y H:i') : '') ?></td>
|
||||
<td class="action-stack">
|
||||
<?php if (($flag['status'] ?? '') === 'open'): ?>
|
||||
<?php if ($flagTypeValue === 'CLASS_REASSIGNMENT_REQUIRED'): ?>
|
||||
@@ -435,67 +426,6 @@ $reasonCodes = is_array($exceptionReasonCodes ?? null) ? $exceptionReasonCodes :
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="followup-pane" role="tabpanel">
|
||||
<h3 class="h5 mb-1">Enrollments still waiting on staff</h3>
|
||||
<p class="text-muted">These students already have an enrollment record. If a class or exception is needed, handle it in Work queue.</p>
|
||||
<div class="table-responsive mb-4">
|
||||
<table id="enrollmentFollowupTable" class="table table-bordered table-striped align-middle enrollment-admin-datatable no-mgmt-sticky no-dt-fixedheader" data-no-mgmt-sticky data-no-dt-fixedheader>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Student</th>
|
||||
<th>School ID</th>
|
||||
<th>Enrollment status</th>
|
||||
<th>Decision</th>
|
||||
<th>Placement</th>
|
||||
<th>Class</th>
|
||||
<th>Exception / reason</th>
|
||||
<th>Updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!empty($enrollmentFollowups)): ?>
|
||||
<?php foreach ($enrollmentFollowups as $row): ?>
|
||||
<?php
|
||||
$statusValue = (string) ($row['enrollment_status'] ?? '');
|
||||
$placementValue = (string) ($row['placement_status'] ?? '');
|
||||
$decisionValue = (string) ($row['deliberation_decision'] ?? '');
|
||||
$statusClass = match (strtolower($statusValue)) {
|
||||
'review & decision' => 'bg-warning text-dark',
|
||||
'waitlist' => 'bg-info text-dark',
|
||||
'denied' => 'bg-danger',
|
||||
default => 'bg-secondary',
|
||||
};
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= esc($row['student_name'] ?? '') ?>
|
||||
<?= student_enrollment_status_button($row, $schoolYear ?? null) ?>
|
||||
</td>
|
||||
<td><?= esc($row['school_id'] ?? '') ?></td>
|
||||
<td><span class="badge <?= esc($statusClass) ?>"><?= esc($statusValue) ?></span></td>
|
||||
<td><?= esc($decisionValue !== '' ? ucwords(str_replace('_', ' ', $decisionValue)) : 'Pending') ?></td>
|
||||
<td><?= esc(enrollment_admin_placement_label($placementValue)) ?></td>
|
||||
<td><?= esc($row['class_section_name'] ?? '') ?></td>
|
||||
<td>
|
||||
<?php if (!empty($row['exception_required'])): ?>
|
||||
<span class="badge bg-warning text-dark">Exception</span>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$exceptionReason = (string) ($row['exception_reason'] ?? '');
|
||||
$reasonParts = array_values(array_filter(array_map('trim', explode(',', $exceptionReason))));
|
||||
$looksLikeCodes = $reasonParts !== [] && count(array_filter($reasonParts, static fn (string $part): bool => (bool) preg_match('/^[A-Z0-9_]+$/', $part))) === count($reasonParts);
|
||||
?>
|
||||
<div class="small"><?= esc($looksLikeCodes ? implode(', ', array_map('enrollment_admin_flag_label', $reasonParts)) : $exceptionReason) ?></div>
|
||||
</td>
|
||||
<td><?= esc(!empty($row['updated_at']) ? local_datetime($row['updated_at'], 'm-d-Y H:i') : '') ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="exceptions-pane" role="tabpanel">
|
||||
<h3 class="h5 mb-1">Family exception tools</h3>
|
||||
<p class="text-muted">Use the lookup to grant a family exception after reviewing the matching work queue item. Saved exceptions stay here for audit and revocation.</p>
|
||||
@@ -904,7 +834,6 @@ $reasonCodes = is_array($exceptionReasonCodes ?? null) ? $exceptionReasonCodes :
|
||||
|
||||
var tableMessages = {
|
||||
enrollmentEmailExamplesTable: 'No parent decision email examples found for this school year.',
|
||||
enrollmentFollowupTable: 'No enrollment records need follow-up for this school year.',
|
||||
enrollmentFlagsTable: 'No students with enrollment blockages were found for these filters.',
|
||||
enrollmentFamilyEligibilityTable: 'No linked students found for this parent.',
|
||||
enrollmentExceptionsTable: 'No enrollment exceptions found for this school year.',
|
||||
@@ -945,11 +874,27 @@ $reasonCodes = is_array($exceptionReasonCodes ?? null) ? $exceptionReasonCodes :
|
||||
});
|
||||
}
|
||||
|
||||
function activateTabFromHash() {
|
||||
if (!window.bootstrap || !window.bootstrap.Tab || !window.location.hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
var target = window.location.hash;
|
||||
var trigger = document.querySelector('[data-bs-toggle="tab"][data-bs-target="' + target + '"]');
|
||||
if (trigger) {
|
||||
window.bootstrap.Tab.getOrCreateInstance(trigger).show();
|
||||
}
|
||||
}
|
||||
|
||||
mergeExceptionPaneIntoWorkQueue();
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initEnrollmentAdminTables);
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
activateTabFromHash();
|
||||
initEnrollmentAdminTables();
|
||||
});
|
||||
} else {
|
||||
activateTabFromHash();
|
||||
initEnrollmentAdminTables();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
<style>
|
||||
.enrollment-email-preview-frame .email-container {
|
||||
max-width: 1280px;
|
||||
width: min(1280px, 100%);
|
||||
max-width: 640px;
|
||||
width: min(640px, 100%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<h2 class="mb-1">Registration Email Preview</h2>
|
||||
<div class="text-muted"><?= esc($schoolYear ?? '') ?> · Parent #<?= esc((string) ($parentId ?? '')) ?></div>
|
||||
</div>
|
||||
<a class="btn btn-secondary" href="<?= site_url('administrator/enrollment-admin?school_year=' . rawurlencode((string) ($schoolYear ?? ''))) ?>">Back</a>
|
||||
<a class="btn btn-secondary" href="<?= site_url('administrator/enrollment-admin?school_year=' . rawurlencode((string) ($schoolYear ?? ''))) ?>#emails-pane">Back</a>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<?= csrf_field() ?>
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
.verify-card { max-width: 520px; margin: 60px auto; }
|
||||
.badge-valid { background: #198754; }
|
||||
.badge-invalid { background: #dc3545; }
|
||||
.cert-logo { width: 70px; height: 70px; border-radius: 50%; object-fit: cover; }
|
||||
.cert-logo { width: 70px; height: 70px; border-radius: 50%; object-fit: contain; background-color: #fff; }
|
||||
.field-label { font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; color: #6c757d; }
|
||||
.field-value { font-size: 1.05rem; font-weight: 500; }
|
||||
</style>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="modal-content rounded-4 shadow border-0" style="max-width: 600px; margin: auto;">
|
||||
<div class="modal-body text-center p-5">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="Logo"
|
||||
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;" class="mb-4">
|
||||
style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;" class="mb-4">
|
||||
<h5 class="modal-title text-danger mb-3" id="blockedLabel">Access Blocked</h5>
|
||||
<p class="lead mb-3">Too many password reset attempts have been made.</p>
|
||||
<p>Please try again after 24 hours or contact support if you need urgent assistance.</p>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="bg-white p-5 rounded-5 shadow registration-form container" style="max-width: 600px; width: 100%;">
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
+1
-1
@@ -413,7 +413,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 60px; width: 60px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 60px; width: 60px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title"></h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="email-nopicture">
|
||||
<img src="https://alrahmaisgl.org/assets/images/alrahma_logo.png" alt="" style="width: 110px; height: 110px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="https://alrahmaisgl.org/assets/images/alrahma_logo.png" alt="" style="width: 110px; height: 110px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</div>
|
||||
|
||||
<div class="email-body">
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
#navbarManagement[data-mgmt-menu-mode="dark"] .navbar-toggler-icon { filter: invert(1) brightness(2); }
|
||||
.logout-btn { border-color: var(--mgmt-primary) !important; color: var(--mgmt-primary) !important; }
|
||||
.logout-btn:hover { background-color: var(--mgmt-primary) !important; color: #fff !important; }
|
||||
.school-logo-circle { border-radius: 50%; object-fit: cover; }
|
||||
.school-logo-circle { border-radius: 50%; object-fit: contain; background-color: #fff; }
|
||||
|
||||
/* Sidebar (hover to reveal) */
|
||||
#navbarManagement.mgmt-sidebar {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- navbar.php -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" class="school-logo-circle" style="height: 40px; width: 40px; object-fit: cover; border-radius: 50%;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" class="school-logo-circle" style="height: 40px; width: 40px; object-fit: contain; border-radius: 50%; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<div class="d-flex align-items-center w-100">
|
||||
<!-- Brand (left) -->
|
||||
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-1" href="/administrator/administratordashboard">
|
||||
<img src="<?= base_url('assets/images/logo.png') ?>" alt="School Icon" style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover; margin-right: 8px;">
|
||||
<img src="<?= base_url('assets/images/logo.png') ?>" alt="School Icon" style="width: 40px; height: 40px; border-radius: 50%; object-fit: contain; background-color: #fff; margin-right: 8px;">
|
||||
<strong>School Management Dashboard</strong>
|
||||
</a>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="bg-white p-5 rounded-5 shadow registration-form container" style="max-width: 600px; width: 100%;">
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<!-- Navbar Start -->
|
||||
<nav class="navbar navbar-expand-lg bg-white navbar-light sticky-top px-4 px-lg-5 py-lg-0">
|
||||
<a href="<?= base_url('/') ?>" class="navbar-brand d-flex align-items-center">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('images/logo.png') ?>" alt="Al Rahma Sunday School" style="height: 40px; width: 40px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h1 class="m-0 ms-2 green-title">Al Rahma Sunday School</h1>
|
||||
</a>
|
||||
<button type="button" class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarCollapse">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt=""
|
||||
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt=""
|
||||
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="modal-content rounded-4 shadow border-0" style="max-width: 600px; margin: auto;">
|
||||
|
||||
<div class="modal-body text-center">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="Logo" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="Logo" style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
<h5 class="modal-title text-success" id="emailConfirmLabel">Check Your Email</h5>
|
||||
<p class="lead mt-3">
|
||||
A link to reset the password has been sent to this email:<br>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="modal fade" id="successModal" tabindex="-1" aria-labelledby="successModalLabel" aria-hidden="true">
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</a>
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-success shadow">
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt=""
|
||||
style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<?= csrf_field() ?>
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;"></a>
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;"></a>
|
||||
</div>
|
||||
<h3 class="text-center text-success" style="font-family: Arial, sans-serif;">Reset Your Password</h3>
|
||||
<br>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<?= csrf_field(); ?>
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<?= csrf_field(); ?>
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover;">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 120px; height: 120px; border-radius: 50%; object-fit: contain; background-color: #fff;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -323,14 +323,16 @@ color: #080808;
|
||||
.navbar-logo {
|
||||
height: 50px !important;
|
||||
width: 50px !important;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.school-logo-circle {
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ html {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
background-color: #fff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ body {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
background-color: #fff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,8 @@ body.centered {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
background-color: #fff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@ body {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for smaller screens */
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Services\EmailService;
|
||||
use App\Services\EnrollmentRegistrationEmailService;
|
||||
use App\Services\EnrollmentTransitionService;
|
||||
use App\Support\Enrollment\DeliberationDecision;
|
||||
use App\Models\ConfigurationModel;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -55,18 +56,75 @@ final class EnrollmentRegistrationEmailServiceTest extends TestCase
|
||||
$this->assertSame('', $html);
|
||||
}
|
||||
|
||||
public function testMakeupExamDecisionMessageUsesConfiguredDate(): void
|
||||
{
|
||||
$configModel = $this->createMock(ConfigurationModel::class);
|
||||
$configModel->expects($this->once())
|
||||
->method('getConfig')
|
||||
->with('make-up-exam')
|
||||
->willReturn('2026-09-13');
|
||||
|
||||
$service = $this->service(configModel: $configModel);
|
||||
|
||||
$message = $this->invoke($service, 'decisionMessage', ['Student Name', [
|
||||
'deliberation_decision' => DeliberationDecision::MAKE_UP_EXAM,
|
||||
'blockers' => [],
|
||||
], 'August 1, 2026', 'August 31, 2026']);
|
||||
|
||||
$this->assertStringContainsString('The final academic decision for Student Name is currently pending the result of a make-up exam.', $message);
|
||||
$this->assertStringContainsString('This exam is scheduled for 09-13-2026 from 9:30 AM to 11:00 AM at ISGL.', $message);
|
||||
$this->assertStringContainsString("\n" . 'The exam result will determine whether the student advances to the next grade or repeats the current class. Failure to attend the make-up exam will automatically result in the student repeating the class, as no further retake opportunities will be available.', $message);
|
||||
$this->assertStringContainsString("\n" . 'You must re-enroll Student Name before the make-up exam can be taken.', $message);
|
||||
$this->assertStringNotContainsString('on date at ISGL', $message);
|
||||
|
||||
$action = $this->invoke($service, 'requiredAction', [[
|
||||
'deliberation_decision' => DeliberationDecision::MAKE_UP_EXAM,
|
||||
'blockers' => [],
|
||||
], 'August 31, 2026', 'Student Name']);
|
||||
|
||||
$this->assertSame('', $action);
|
||||
}
|
||||
|
||||
public function testMakeupExamStudentRowRendersMessageLineBreaks(): void
|
||||
{
|
||||
$configModel = $this->createMock(ConfigurationModel::class);
|
||||
$configModel->method('getConfig')
|
||||
->with('make-up-exam')
|
||||
->willReturn('2026-09-13');
|
||||
|
||||
$service = $this->service(configModel: $configModel);
|
||||
|
||||
$html = $this->invoke($service, 'studentRow', [[
|
||||
'firstname' => 'Student',
|
||||
'lastname' => 'Name',
|
||||
], [
|
||||
'deliberation_decision' => DeliberationDecision::MAKE_UP_EXAM,
|
||||
'placement_status' => 'temporary_same_grade',
|
||||
'blockers' => [],
|
||||
], 'August 1, 2026', 'August 31, 2026']);
|
||||
|
||||
$this->assertStringContainsString('from 9:30 AM to 11:00 AM at ISGL.<br>', $html);
|
||||
$this->assertStringContainsString('available.<br>', $html);
|
||||
$this->assertStringContainsString('<strong>You must re-enroll Student Name before the make-up exam can be taken.</strong>', $html);
|
||||
$this->assertStringNotContainsString('before they are permitted', $html);
|
||||
}
|
||||
|
||||
public function testFinancialSectionShowsWhenAnyAmountIsDue(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$html = $this->invoke($service, 'financialSection', [10, [
|
||||
'registration_fee' => 25,
|
||||
'tuition_due_at_registration' => 0,
|
||||
'mandatory_fees' => 0,
|
||||
'tuition_due_at_registration' => 10,
|
||||
'mandatory_fees' => 5,
|
||||
], null]);
|
||||
|
||||
$this->assertStringContainsString('Family Account Information', $html);
|
||||
$this->assertStringContainsString('Total currently due:</strong> $25.00', $html);
|
||||
$this->assertStringContainsString('Carry-over balance:</strong> $0.00', $html);
|
||||
$this->assertStringContainsString('Total currently due:</strong> $40.00', $html);
|
||||
$this->assertStringNotContainsString('Registration fee:', $html);
|
||||
$this->assertStringNotContainsString('New-year tuition due now:', $html);
|
||||
$this->assertStringNotContainsString('Mandatory fees:', $html);
|
||||
}
|
||||
|
||||
public function testAutomaticDistributionPlacementShowsOnlyGrade(): void
|
||||
@@ -81,6 +139,75 @@ final class EnrollmentRegistrationEmailServiceTest extends TestCase
|
||||
$this->assertSame('Grade 9', $placement);
|
||||
}
|
||||
|
||||
public function testStudentSummaryUsesStackedEmailRows(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$html = $this->invoke($service, 'studentRow', [[
|
||||
'firstname' => 'Test',
|
||||
'lastname' => 'Student',
|
||||
], [
|
||||
'deliberation_decision' => DeliberationDecision::PASSED,
|
||||
'placement_status' => 'automatic_distribution_pending',
|
||||
'source_grade_name' => '4',
|
||||
'assigned_grade_name' => '5',
|
||||
'blockers' => [],
|
||||
], 'August 1, 2026', 'August 31, 2026']);
|
||||
|
||||
$this->assertStringContainsString('role="presentation"', $html);
|
||||
$this->assertStringContainsString('Decision', $html);
|
||||
$this->assertStringContainsString('Registration Status', $html);
|
||||
$this->assertStringContainsString('Next Step', $html);
|
||||
$this->assertStringContainsString('word-break:break-word', $html);
|
||||
$this->assertStringNotContainsString('</td><td', $html);
|
||||
}
|
||||
|
||||
public function testStudentsTableWrapsStackedStudentBlocks(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$html = $this->invoke($service, 'studentsTable', [['<table role="presentation"></table>']]);
|
||||
|
||||
$this->assertStringStartsWith('<div style="margin:12px 0 18px;">', $html);
|
||||
$this->assertStringContainsString('<table role="presentation"></table>', $html);
|
||||
$this->assertStringEndsWith('</div>', $html);
|
||||
}
|
||||
|
||||
public function testRegistrationStepsSectionIsHiddenWhenNoStudentCanReEnroll(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$html = $this->invoke($service, 'registrationStepsSection', ['August 1, 2026', 'August 31, 2026', false]);
|
||||
|
||||
$this->assertSame('', $html);
|
||||
}
|
||||
|
||||
public function testRegistrationStepsSectionShowsWhenAnyStudentCanReEnroll(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$html = $this->invoke($service, 'registrationStepsSection', ['August 1, 2026', 'August 31, 2026', true]);
|
||||
|
||||
$this->assertStringContainsString('Re-enrollment Steps', $html);
|
||||
$this->assertStringContainsString('Sign in to the parent portal.', $html);
|
||||
$this->assertStringContainsString('parent/enroll_classes', $html);
|
||||
}
|
||||
|
||||
public function testParentReEnrollmentEligibilityUsesTransitionAllowedFlag(): void
|
||||
{
|
||||
$service = $this->service();
|
||||
|
||||
$this->assertTrue($this->invoke($service, 'canCompleteParentReEnrollment', [[
|
||||
'parent_enrollment_allowed' => true,
|
||||
'blockers' => [],
|
||||
]]));
|
||||
|
||||
$this->assertFalse($this->invoke($service, 'canCompleteParentReEnrollment', [[
|
||||
'parent_enrollment_allowed' => false,
|
||||
'blockers' => ['Contact administration.'],
|
||||
]]));
|
||||
}
|
||||
|
||||
public function testForceCannotSendWithoutAdminLaunchApproval(): void
|
||||
{
|
||||
$emailService = $this->createMock(EmailService::class);
|
||||
@@ -108,7 +235,7 @@ final class EnrollmentRegistrationEmailServiceTest extends TestCase
|
||||
return $reflection->invokeArgs($service, $args);
|
||||
}
|
||||
|
||||
private function service(?BaseConnection $db = null, ?EmailService $emailService = null): EnrollmentRegistrationEmailService
|
||||
private function service(?BaseConnection $db = null, ?EmailService $emailService = null, ?ConfigurationModel $configModel = null): EnrollmentRegistrationEmailService
|
||||
{
|
||||
$db ??= $this->createMock(BaseConnection::class);
|
||||
|
||||
@@ -116,6 +243,7 @@ final class EnrollmentRegistrationEmailServiceTest extends TestCase
|
||||
$db,
|
||||
new EnrollmentTransitionService($db),
|
||||
$emailService ?? $this->createMock(EmailService::class),
|
||||
$configModel,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user