805 lines
42 KiB
PHP
805 lines
42 KiB
PHP
<?= $this->extend('layout/management_layout') ?>
|
||
<?= $this->section('content') ?>
|
||
|
||
<div class="container mt-5">
|
||
<h3>Event Charges</h3>
|
||
<style>
|
||
.event-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
}
|
||
.event-card-header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
.sortable-header {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
padding: 0;
|
||
border: 0;
|
||
background: transparent;
|
||
color: inherit;
|
||
font-weight: 600;
|
||
text-decoration: none;
|
||
}
|
||
.sortable-header:hover,
|
||
.sortable-header:focus {
|
||
color: var(--bs-primary);
|
||
text-decoration: none;
|
||
}
|
||
.sortable-header::after {
|
||
content: '↕';
|
||
font-size: 0.85rem;
|
||
opacity: 0.45;
|
||
}
|
||
.sortable-header.sorted-asc::after {
|
||
content: '↑';
|
||
opacity: 1;
|
||
}
|
||
.sortable-header.sorted-desc::after {
|
||
content: '↓';
|
||
opacity: 1;
|
||
}
|
||
@media print {
|
||
.no-print {
|
||
display: none !important;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<?php if (session()->getFlashdata('success')): ?>
|
||
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
|
||
<?php endif; ?>
|
||
<?php if (session()->getFlashdata('error')): ?>
|
||
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
|
||
<?php endif; ?>
|
||
|
||
<?php
|
||
$classSectionNames = $classSectionNames ?? [];
|
||
$semesterOptions = $semesterOptions ?? [];
|
||
$schoolYearOptions = $schoolYearOptions ?? [];
|
||
?>
|
||
|
||
<?php
|
||
$filterParentId = $filterParentId ?? 0;
|
||
$parentBalances = $parentBalances ?? [];
|
||
$selectedEvent = null;
|
||
if (!empty($filterEventId)) {
|
||
foreach ($events as $event) {
|
||
if ((int)$event['id'] === (int)$filterEventId) {
|
||
$selectedEvent = $event;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
?>
|
||
<div class="card mb-4">
|
||
<div class="card-body">
|
||
<form method="get" action="<?= site_url('administrator/event-charges') ?>" class="row g-3 align-items-end">
|
||
<?php if ($filterEventId > 0): ?>
|
||
<input type="hidden" name="event_id" value="<?= esc($filterEventId) ?>">
|
||
<?php endif; ?>
|
||
<?php if ($filterParentId > 0): ?>
|
||
<input type="hidden" name="parent_id" value="<?= esc($filterParentId) ?>">
|
||
<?php endif; ?>
|
||
<div class="col-md-4">
|
||
<label for="semester_filter" class="form-label">Semester</label>
|
||
<select id="semester_filter" name="semester" class="form-select">
|
||
<option value="">-- All semesters --</option>
|
||
<?php foreach ($semesterOptions as $option): ?>
|
||
<option value="<?= esc($option) ?>" <?= $semester === $option ? 'selected' : '' ?>>
|
||
<?= esc($option) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label for="school_year_filter" class="form-label">School Year</label>
|
||
<select id="school_year_filter" name="school_year" class="form-select">
|
||
<option value="">-- All school years --</option>
|
||
<?php foreach ($schoolYearOptions as $option): ?>
|
||
<option value="<?= esc($option) ?>" <?= $school_year === $option ? 'selected' : '' ?>>
|
||
<?= esc($option) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="col-auto">
|
||
<button type="submit" class="btn btn-outline-primary">Filter</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add New Charge Form -->
|
||
<div class="card mb-4">
|
||
<div class="card-body">
|
||
<form id="event-participant-form" action="<?= site_url('payment/event_charges') ?>" method="post">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="school_year" value="<?= esc($school_year) ?>">
|
||
<input type="hidden" name="semester" value="<?= esc($semester) ?>">
|
||
<div class="row g-3">
|
||
<div class="col-md-4">
|
||
<label for="event_id" class="form-label">Select Event</label>
|
||
<select name="event_id" id="event_id" class="form-select" required>
|
||
<option value="">-- All events --</option>
|
||
<?php foreach ($events as $event): ?>
|
||
<option value="<?= esc($event['id']) ?>" data-amount="<?= esc(number_format($event['amount'] ?? 0, 2, '.', '')) ?>" <?= isset($filterEventId) && $filterEventId == $event['id'] ? 'selected' : '' ?>>
|
||
<?= esc($event['event_name']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<?php if ($selectedEvent): ?>
|
||
<div class="col-md-8">
|
||
<div class="border rounded bg-light p-3 h-100">
|
||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
<h6 class="m-0"><?= esc($selectedEvent['event_name']) ?></h6>
|
||
<span class="badge bg-info text-dark">
|
||
$<?= esc(number_format($selectedEvent['amount'] ?? 0, 2)) ?> fee
|
||
</span>
|
||
</div>
|
||
<?php $eventDescription = trim((string) ($selectedEvent['description'] ?? '')); ?>
|
||
<div class="mb-1">
|
||
<button type="button"
|
||
class="btn btn-link btn-sm px-0 py-0 text-decoration-none event-description-toggle"
|
||
data-bs-toggle="collapse"
|
||
data-bs-target="#selectedEventDescription"
|
||
aria-expanded="false"
|
||
aria-controls="selectedEventDescription"
|
||
data-label-collapsed="Show description"
|
||
data-label-expanded="Hide description">
|
||
Show description
|
||
</button>
|
||
<div class="collapse mt-2" id="selectedEventDescription">
|
||
<div class="text-muted small" style="white-space: pre-line;">
|
||
<?= esc($eventDescription !== '' ? $eventDescription : 'No description provided for this event.') ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php if (!empty($selectedEvent['expiration_date'])): ?>
|
||
<small class="text-secondary">
|
||
Expires: <?= esc(local_date($selectedEvent['expiration_date'], 'm-d-Y')) ?>
|
||
</small>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="col-md-4">
|
||
<label for="parent_id" class="form-label">Parents List</label>
|
||
<select id="parent_id" name="parent_id" class="form-select">
|
||
<option value="">-- Select Parent --</option>
|
||
<?php foreach ($parents as $parent): ?>
|
||
<option value="<?= esc($parent['id']) ?>" <?= $filterParentId && $filterParentId == $parent['id'] ? 'selected' : '' ?>>
|
||
<?= esc($parent['firstname'] . ' ' . $parent['lastname']) ?> (<?= esc($parent['school_id']) ?>)
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-4" id="studentCheckboxContainer" style="display: none;">
|
||
<label class="form-label">Select Students:</label>
|
||
<div id="studentList" class="row g-3"></div>
|
||
</div>
|
||
|
||
<div class="mt-4">
|
||
<?php if (!in_array(strtolower((string)(session()->get('role') ?? '')), ['parent', 'teacher', 'teacher_assistant'], true)): ?>
|
||
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#externalParticipantModal">
|
||
Add non-school participant
|
||
</button>
|
||
<?php endif; ?>
|
||
<div class="form-text">Enrolled students are saved when you click Submit. Non-school participants are saved immediately and will then appear in all browsers.</div>
|
||
</div>
|
||
|
||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||
<button type="submit" class="btn btn-primary">Submit</button>
|
||
<a href="<?= site_url('administrator/events') ?>" class="btn btn-success">Event List</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<!-- Charge Tables grouped by event -->
|
||
<?php
|
||
$grouped = [];
|
||
foreach ($charges as $charge) {
|
||
$eventId = (int)($charge['event_id'] ?? 0);
|
||
if (!isset($grouped[$eventId])) {
|
||
$grouped[$eventId] = [
|
||
'label' => $charge['event_name'] ?? 'N/A',
|
||
'rows' => [],
|
||
];
|
||
}
|
||
$grouped[$eventId]['rows'][] = $charge;
|
||
}
|
||
?>
|
||
|
||
<?php if (empty($grouped)): ?>
|
||
<div class="alert alert-info">No charges found.</div>
|
||
<?php else: ?>
|
||
<?php
|
||
$printBaseParams = array_filter([
|
||
'school_year' => $school_year,
|
||
'semester' => $semester,
|
||
'parent_id' => $filterParentId > 0 ? $filterParentId : null,
|
||
'event_id' => $filterEventId > 0 ? $filterEventId : null,
|
||
], static fn ($value) => $value !== null && $value !== '');
|
||
$printAllUrl = site_url('administrator/event-charges/pdf');
|
||
if (!empty($printBaseParams)) {
|
||
$printAllUrl .= '?' . http_build_query($printBaseParams);
|
||
}
|
||
?>
|
||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3 no-print">
|
||
<p class="text-muted mb-0">Click a column header to sort the participants list.</p>
|
||
<a href="<?= esc($printAllUrl) ?>"
|
||
class="btn btn-outline-secondary pdf-print-link"
|
||
data-base-href="<?= esc($printAllUrl) ?>"
|
||
data-print-scope="all">
|
||
Print All Participant Lists
|
||
</a>
|
||
</div>
|
||
<div id="eventChargeCards">
|
||
<?php foreach ($grouped as $eventId => $data): ?>
|
||
<?php $eventLabel = $data['label']; ?>
|
||
<?php $rows = $data['rows']; ?>
|
||
<?php
|
||
$singlePrintParams = array_filter([
|
||
'school_year' => $school_year,
|
||
'semester' => $semester,
|
||
'parent_id' => $filterParentId > 0 ? $filterParentId : null,
|
||
'event_id' => $eventId,
|
||
], static fn ($value) => $value !== null && $value !== '');
|
||
$singlePrintUrl = site_url('administrator/event-charges/pdf') . '?' . http_build_query($singlePrintParams);
|
||
?>
|
||
<div class="card mb-4 event-card" data-event-id="<?= esc($eventId) ?>">
|
||
<div class="card-header bg-light fw-semibold event-card-header">
|
||
<span><?= esc($eventLabel) ?></span>
|
||
<div class="event-card-header-actions no-print">
|
||
<a href="<?= esc($singlePrintUrl) ?>"
|
||
class="btn btn-outline-secondary btn-sm pdf-print-link"
|
||
data-base-href="<?= esc($singlePrintUrl) ?>"
|
||
data-print-scope="single"
|
||
data-event-id="<?= esc($eventId) ?>">
|
||
Print This List
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<?php
|
||
$totalParticipants = 0;
|
||
$totalCharged = 0.0;
|
||
?>
|
||
<div class="table-responsive">
|
||
<table id="eventTable_<?= esc($eventId) ?>" class="table table-bordered table-striped mb-0 no-mgmt-sticky event-charges-table">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th><button type="button" class="sortable-header" data-sort-index="0" data-sort-type="number">ID</button></th>
|
||
<th><button type="button" class="sortable-header" data-sort-index="1" data-sort-type="text">Parent Name</button></th>
|
||
<th><button type="button" class="sortable-header" data-sort-index="2" data-sort-type="text">Student Name</button></th>
|
||
<th><button type="button" class="sortable-header" data-sort-index="3" data-sort-type="text">External Info</button></th>
|
||
<th><button type="button" class="sortable-header" data-sort-index="4" data-sort-type="text">Class Section</button></th>
|
||
<th><button type="button" class="sortable-header" data-sort-index="5" data-sort-type="number">Charged Amount</button></th>
|
||
<th><button type="button" class="sortable-header" data-sort-index="6" data-sort-type="date">Created</button></th>
|
||
<th><button type="button" class="sortable-header" data-sort-index="7" data-sort-type="text">Waiver</button></th>
|
||
<th><button type="button" class="sortable-header" data-sort-index="8" data-sort-type="text">Fees Paid</button></th>
|
||
<th><button type="button" class="sortable-header" data-sort-index="9" data-sort-type="text">Payment</button></th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($rows as $charge): ?>
|
||
<?php
|
||
$isParticipating = ($charge['participation'] === 'yes');
|
||
$isEventPaid = !empty($charge['event_paid']);
|
||
$returnParams = [];
|
||
if (!empty($semester)) {
|
||
$returnParams['semester'] = $semester;
|
||
}
|
||
if (!empty($school_year)) {
|
||
$returnParams['school_year'] = $school_year;
|
||
}
|
||
if (!empty($filterEventId)) {
|
||
$returnParams['event_id'] = $filterEventId;
|
||
}
|
||
if (!empty($filterParentId)) {
|
||
$returnParams['parent_id'] = $filterParentId;
|
||
}
|
||
$returnTo = site_url('administrator/event-charges')
|
||
. (!empty($returnParams) ? '?' . http_build_query($returnParams) : '')
|
||
. '#eventTable_' . (int) ($charge['event_id'] ?? 0);
|
||
?>
|
||
<tr data-charge-id="<?= esc((string) ($charge['id'] ?? '')) ?>">
|
||
<td data-sort-value="<?= esc((string) ($charge['id'] ?? '')) ?>"><?= esc($charge['id']) ?></td>
|
||
<?php
|
||
$studentName = $charge['student_firstname']
|
||
? trim($charge['student_firstname'] . ' ' . $charge['student_lastname'])
|
||
: '';
|
||
$externalName = trim(
|
||
($charge['external_firstname'] ?? '') . ' ' .
|
||
($charge['external_lastname'] ?? '')
|
||
);
|
||
$displayName = $studentName ?: ($externalName ?: '—');
|
||
$externalNote = $charge['external_note'] ?? '';
|
||
$externalParentLabel = trim(
|
||
($charge['external_parent_firstname'] ?? '') . ' ' .
|
||
($charge['external_parent_lastname'] ?? '')
|
||
);
|
||
$externalParentPhone = $charge['external_parent_phone'] ?? '';
|
||
$externalParentEmail = $charge['external_parent_email'] ?? '';
|
||
$parentPhone = $charge['parent_cellphone'] ?? '';
|
||
$standardParentName = trim($charge['parent_firstname'] . ' ' . $charge['parent_lastname']);
|
||
$parentColumnName = $externalParentLabel ?: ($standardParentName ?: '—');
|
||
$isExternalParticipant = $externalName !== '';
|
||
$infoPhone = $isExternalParticipant ? $externalParentPhone : $parentPhone;
|
||
$infoEmail = $isExternalParticipant ? $externalParentEmail : '';
|
||
$externalInfoValue = trim(implode(' ', array_filter([
|
||
$infoPhone,
|
||
$infoEmail,
|
||
$externalNote,
|
||
])));
|
||
?>
|
||
<td data-sort-value="<?= esc(strtolower($parentColumnName)) ?>"><?= esc($parentColumnName) ?></td>
|
||
<td data-sort-value="<?= esc(strtolower($displayName)) ?>">
|
||
<?= esc($displayName) ?>
|
||
<?php if ($externalNote): ?>
|
||
<small class="text-muted d-block"><?= esc($externalNote) ?></small>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td data-sort-value="<?= esc(strtolower($externalInfoValue)) ?>">
|
||
<?php if ($infoPhone): ?>
|
||
<div class="text-muted small"><?= esc($infoPhone) ?></div>
|
||
<?php endif; ?>
|
||
<?php if ($infoEmail): ?>
|
||
<div class="text-muted small"><?= esc($infoEmail) ?></div>
|
||
<?php endif; ?>
|
||
<?php if (!$infoPhone && !$infoEmail): ?>
|
||
<span class="text-muted small">—</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td data-sort-value="<?= esc(strtolower((string) ($classSectionNames[$charge['class_section_id'] ?? ''] ?? ''))) ?>">
|
||
<?= esc($classSectionNames[$charge['class_section_id'] ?? ''] ?? '—') ?>
|
||
</td>
|
||
<td data-sort-value="<?= esc(number_format((float) ($charge['event_amount'] ?? 0), 2, '.', '')) ?>">$<?= esc(number_format($charge['event_amount'] ?? 0, 2)) ?></td>
|
||
<td data-sort-value="<?= esc((string) ($charge['created_at'] ?? '')) ?>"><?= esc(!empty($charge['created_at']) ? local_datetime($charge['created_at'], 'm-d-Y H:i') : '') ?></td>
|
||
<?php
|
||
$feeAmount = (float) ($charge['event_amount'] ?? 0);
|
||
$hasBalanceRecord = array_key_exists((int)$charge['parent_id'], $parentBalances);
|
||
$parentBalance = $hasBalanceRecord ? (float)$parentBalances[$charge['parent_id']] : null;
|
||
$waiverSigned = !empty($charge['waiver_signed']);
|
||
$feeIsPaid = $isParticipating && ($isEventPaid || ($hasBalanceRecord && $parentBalance <= 0));
|
||
?>
|
||
<td class="text-center" data-sort-value="<?= $waiverSigned ? 'signed' : 'unsigned' ?>">
|
||
<form method="post" action="<?= site_url('administrator/event-charges/waiver/' . esc($charge['id'])) ?>" class="d-inline-flex align-items-center">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
|
||
<input type="hidden" name="waiver_signed" value="<?= $waiverSigned ? '1' : '0' ?>">
|
||
<input type="checkbox" class="form-check-input" id="eventWaiver_<?= esc($charge['id']) ?>"
|
||
<?= $waiverSigned ? 'checked' : '' ?>
|
||
onchange="this.form.waiver_signed.value = this.checked ? 1 : 0; this.form.submit();">
|
||
<label class="form-check-label ms-2" for="eventWaiver_<?= esc($charge['id']) ?>" aria-hidden="true">
|
||
<?= $waiverSigned ? 'Signed' : 'Unsigned' ?>
|
||
</label>
|
||
</form>
|
||
</td>
|
||
<td class="text-center" data-sort-value="<?= $feeIsPaid ? 'paid' : 'unpaid' ?>">
|
||
<?php if ($feeIsPaid): ?>
|
||
<span class="badge bg-success">Paid</span>
|
||
<?php else: ?>
|
||
<span class="badge bg-danger">Unpaid</span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td class="text-center" data-sort-value="<?= $isEventPaid ? 'paid' : 'unpaid' ?>">
|
||
<form method="post" action="<?= site_url('administrator/event-charges/payment/' . esc($charge['id'])) ?>" class="d-inline-flex align-items-center">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
|
||
<input type="hidden" name="paid" value="<?= $feeIsPaid ? '1' : '0' ?>">
|
||
<input type="checkbox" class="form-check-input" id="eventPayment_<?= esc($charge['id']) ?>"
|
||
<?= $isEventPaid ? 'checked' : '' ?>
|
||
onchange="this.form.paid.value = this.checked ? 1 : 0; this.form.submit();">
|
||
<label class="form-check-label ms-2" for="eventPayment_<?= esc($charge['id']) ?>" aria-hidden="true">Paid</label>
|
||
</form>
|
||
</td>
|
||
<td class="text-nowrap">
|
||
<div class="d-flex gap-1 align-items-center">
|
||
<a href="<?= site_url('administrator/event-charges') ?>?event_id=<?= esc($charge['event_id']) ?>&parent_id=<?= esc($charge['parent_id']) ?>#event-participant-form" class="btn btn-outline-primary btn-sm">
|
||
Edit
|
||
</a>
|
||
<form action="<?= site_url('administrator/event-charges/remove/' . esc($charge['id'])) ?>" method="post" onsubmit="return confirm('Remove this participation and update the charge?');">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
|
||
<button type="submit" class="btn btn-outline-danger btn-sm">Remove</button>
|
||
</form>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<?php
|
||
if ($isParticipating) {
|
||
$totalParticipants++;
|
||
$totalCharged += (float) ($charge['event_amount'] ?? 0);
|
||
}
|
||
?>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
<tfoot class="table-light">
|
||
<tr>
|
||
<th colspan="4">Totals</th>
|
||
<th>$<?= esc(number_format($totalCharged, 2)) ?></th>
|
||
<th colspan="6">
|
||
<span class="badge bg-secondary">
|
||
<?= esc($totalParticipants) ?> participating
|
||
</span>
|
||
</th>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php if (!in_array(strtolower((string)(session()->get('role') ?? '')), ['parent', 'teacher', 'teacher_assistant'], true)): ?>
|
||
<div class="modal fade" id="externalParticipantModal" tabindex="-1" aria-labelledby="externalParticipantModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<form id="externalParticipantForm" class="needs-validation" novalidate>
|
||
<?= csrf_field() ?>
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="externalParticipantModalLabel">Add Non-School Participant</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="small text-muted">Provide the student’s name and the accompanying guardian/contact details.</p>
|
||
<div class="mb-3">
|
||
<label class="form-label">Student / Kid Info</label>
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<input type="text" class="form-control" id="modalExternalFirstName" placeholder="First name" required>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<input type="text" class="form-control" id="modalExternalLastName" placeholder="Last name" required>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Parent / Contact Info</label>
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<input type="text" class="form-control" id="modalParentFirstName" placeholder="Parent first name" required>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<input type="text" class="form-control" id="modalParentLastName" placeholder="Parent last name" required>
|
||
</div>
|
||
<div class="col-12">
|
||
<input type="text" class="form-control" id="modalParentPhone" placeholder="Parent phone or contact">
|
||
</div>
|
||
<div class="col-12">
|
||
<input type="email" class="form-control" id="modalParentEmail" placeholder="Parent email" required>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label" for="modalExternalNote">Note <span class="text-muted small">(optional)</span></label>
|
||
<textarea rows="2" class="form-control" id="modalExternalNote" placeholder="Additional info (e.g., guest, sibling, etc.)"></textarea>
|
||
</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-primary" id="externalParticipantSave">
|
||
Add to list
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?= $this->endSection() ?>
|
||
|
||
<?= $this->section('scripts') ?>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
document.querySelectorAll('.event-description-toggle').forEach(function (button) {
|
||
const targetSelector = button.getAttribute('data-bs-target');
|
||
const target = targetSelector ? document.querySelector(targetSelector) : null;
|
||
if (!target) {
|
||
return;
|
||
}
|
||
|
||
target.addEventListener('show.bs.collapse', function () {
|
||
button.textContent = button.dataset.labelExpanded || 'Hide description';
|
||
});
|
||
|
||
target.addEventListener('hide.bs.collapse', function () {
|
||
button.textContent = button.dataset.labelCollapsed || 'Show description';
|
||
});
|
||
});
|
||
});
|
||
|
||
function loadStudentsWithCharges() {
|
||
let parentId = $('#parent_id').val();
|
||
let eventId = $('#event_id').val();
|
||
let semester = '<?= esc($semester) ?>';
|
||
let schoolYear = '<?= esc($school_year) ?>';
|
||
|
||
if (parentId && eventId) {
|
||
$.get('<?= base_url('administrator/get-students-with-charges') ?>', {
|
||
parent_id: parentId,
|
||
event_id: eventId,
|
||
semester: semester,
|
||
school_year: schoolYear
|
||
}, function(data) {
|
||
let container = $('#studentList');
|
||
container.empty();
|
||
|
||
if (data.length > 0) {
|
||
$('#studentCheckboxContainer').show();
|
||
data.forEach(student => {
|
||
container.append(`
|
||
<div class="col-md-4">
|
||
<div class="form-check">
|
||
<input type="hidden" name="participation[${student.id}]" value="no">
|
||
<input class="form-check-input" type="checkbox" name="participation[${student.id}]" value="yes" id="student_${student.id}" ${student.charged ? 'checked' : ''}>
|
||
<label class="form-check-label" for="student_${student.id}">
|
||
${student.name}
|
||
</label>
|
||
</div>
|
||
</div>
|
||
`);
|
||
});
|
||
} else {
|
||
$('#studentCheckboxContainer').hide();
|
||
}
|
||
}, 'json');
|
||
} else {
|
||
$('#studentCheckboxContainer').hide();
|
||
$('#studentList').empty();
|
||
}
|
||
}
|
||
|
||
const capitalizeName = (value) => {
|
||
if (!value) return '';
|
||
return value
|
||
.split(' ')
|
||
.map(part => {
|
||
const trimmed = part.trim();
|
||
if (!trimmed) return '';
|
||
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1).toLowerCase();
|
||
})
|
||
.filter(Boolean)
|
||
.join(' ');
|
||
};
|
||
|
||
$(function() {
|
||
$('#parent_id').on('change', loadStudentsWithCharges);
|
||
|
||
$('#event_id').on('change', function() {
|
||
let eventId = $(this).val();
|
||
let url = new URL(window.location.href);
|
||
if (eventId) {
|
||
url.searchParams.set('event_id', eventId);
|
||
} else {
|
||
url.searchParams.delete('event_id');
|
||
}
|
||
window.location.href = url.toString();
|
||
});
|
||
|
||
if ($('#event_id').val() && $('#parent_id').val()) {
|
||
loadStudentsWithCharges();
|
||
}
|
||
|
||
const $externalSaveButton = $('#externalParticipantSave');
|
||
|
||
const modalElement = document.getElementById('externalParticipantModal');
|
||
const modalInstance = modalElement ? new bootstrap.Modal(modalElement) : null;
|
||
|
||
function clearModalInputs() {
|
||
$('#modalExternalFirstName').val('');
|
||
$('#modalExternalLastName').val('');
|
||
$('#modalExternalNote').val('');
|
||
$('#modalParentFirstName').val('');
|
||
$('#modalParentLastName').val('');
|
||
$('#modalParentPhone').val('');
|
||
$('#modalParentEmail').val('');
|
||
}
|
||
|
||
function appendHiddenField(form, name, value) {
|
||
const input = document.createElement('input');
|
||
input.type = 'hidden';
|
||
input.name = name;
|
||
input.value = value;
|
||
form.appendChild(input);
|
||
}
|
||
|
||
function submitExternalParticipant(payload) {
|
||
const form = document.createElement('form');
|
||
form.method = 'post';
|
||
form.action = '<?= site_url('payment/event_charges') ?>';
|
||
|
||
appendHiddenField(form, '<?= csrf_token() ?>', '<?= csrf_hash() ?>');
|
||
appendHiddenField(form, 'school_year', '<?= esc($school_year) ?>');
|
||
appendHiddenField(form, 'semester', '<?= esc($semester) ?>');
|
||
appendHiddenField(form, 'event_id', payload.eventId);
|
||
appendHiddenField(form, 'external_participants[new][firstname]', payload.firstname);
|
||
appendHiddenField(form, 'external_participants[new][lastname]', payload.lastname);
|
||
appendHiddenField(form, 'external_participants[new][note]', payload.note);
|
||
appendHiddenField(form, 'external_participants[new][parent_firstname]', payload.parentFirstname);
|
||
appendHiddenField(form, 'external_participants[new][parent_lastname]', payload.parentLastname);
|
||
appendHiddenField(form, 'external_participants[new][parent_phone]', payload.parentPhone);
|
||
appendHiddenField(form, 'external_participants[new][parent_email]', payload.parentEmail);
|
||
appendHiddenField(form, 'external_participants[new][waiver_signed]', '0');
|
||
appendHiddenField(form, 'external_participants[new][paid]', '0');
|
||
|
||
document.body.appendChild(form);
|
||
form.submit();
|
||
}
|
||
|
||
function getSortValue(row, columnIndex, sortType) {
|
||
const cell = row.children[columnIndex];
|
||
if (!cell) {
|
||
return sortType === 'number' ? 0 : '';
|
||
}
|
||
const rawValue = cell.getAttribute('data-sort-value');
|
||
const fallbackValue = (cell.textContent || '').trim();
|
||
const value = rawValue !== null ? rawValue : fallbackValue;
|
||
if (sortType === 'number') {
|
||
const parsed = parseFloat(String(value).replace(/[^0-9.-]/g, ''));
|
||
return Number.isNaN(parsed) ? 0 : parsed;
|
||
}
|
||
if (sortType === 'date') {
|
||
const time = Date.parse(value);
|
||
return Number.isNaN(time) ? 0 : time;
|
||
}
|
||
return String(value).toLowerCase();
|
||
}
|
||
|
||
function updateSortIndicators(table, columnIndex, direction) {
|
||
$(table).find('.sortable-header').each(function() {
|
||
const isCurrent = Number($(this).data('sortIndex')) === Number(columnIndex);
|
||
$(this)
|
||
.toggleClass('sorted-asc', isCurrent && direction === 'asc')
|
||
.toggleClass('sorted-desc', isCurrent && direction === 'desc')
|
||
.attr('aria-sort', isCurrent ? direction : 'none');
|
||
});
|
||
}
|
||
|
||
function sortEventTable(table, columnIndex, sortType, direction) {
|
||
if (!table || !table.tBodies.length) {
|
||
return;
|
||
}
|
||
const tbody = table.tBodies[0];
|
||
const rows = Array.from(tbody.rows);
|
||
rows.sort((a, b) => {
|
||
const first = getSortValue(a, columnIndex, sortType);
|
||
const second = getSortValue(b, columnIndex, sortType);
|
||
if (first < second) {
|
||
return direction === 'asc' ? -1 : 1;
|
||
}
|
||
if (first > second) {
|
||
return direction === 'asc' ? 1 : -1;
|
||
}
|
||
return 0;
|
||
});
|
||
rows.forEach((row) => tbody.appendChild(row));
|
||
table.dataset.sortColumn = String(columnIndex);
|
||
table.dataset.sortType = sortType;
|
||
table.dataset.sortDirection = direction;
|
||
updateSortIndicators(table, columnIndex, direction);
|
||
}
|
||
|
||
function applyActiveSort(table) {
|
||
if (!table || !table.dataset || !table.dataset.sortColumn) {
|
||
return;
|
||
}
|
||
sortEventTable(
|
||
table,
|
||
Number(table.dataset.sortColumn),
|
||
table.dataset.sortType || 'text',
|
||
table.dataset.sortDirection || 'asc'
|
||
);
|
||
}
|
||
|
||
function getTableRowOrder(table) {
|
||
if (!table || !table.tBodies.length) {
|
||
return [];
|
||
}
|
||
|
||
return Array.from(table.tBodies[0].rows)
|
||
.map((row) => String(row.dataset.chargeId || '').trim())
|
||
.filter(Boolean);
|
||
}
|
||
|
||
function buildPdfUrl(link) {
|
||
const baseHref = String(link.dataset.baseHref || link.getAttribute('href') || '');
|
||
const url = new URL(baseHref, window.location.origin);
|
||
const scope = String(link.dataset.printScope || 'single');
|
||
|
||
if (scope === 'all') {
|
||
document.querySelectorAll('.event-card').forEach((card) => {
|
||
const eventId = String(card.dataset.eventId || '').trim();
|
||
const table = card.querySelector('.event-charges-table');
|
||
const rowOrder = getTableRowOrder(table);
|
||
if (eventId && rowOrder.length) {
|
||
url.searchParams.set(`row_order[${eventId}]`, rowOrder.join(','));
|
||
}
|
||
});
|
||
return url.toString();
|
||
}
|
||
|
||
const eventId = String(link.dataset.eventId || '').trim();
|
||
const table = eventId ? document.getElementById(`eventTable_${eventId}`) : null;
|
||
const rowOrder = getTableRowOrder(table);
|
||
if (eventId && rowOrder.length) {
|
||
url.searchParams.set(`row_order[${eventId}]`, rowOrder.join(','));
|
||
}
|
||
|
||
return url.toString();
|
||
}
|
||
|
||
$('.sortable-header').on('click', function() {
|
||
const $button = $(this);
|
||
const table = $button.closest('table').get(0);
|
||
const columnIndex = Number($button.data('sortIndex'));
|
||
const sortType = String($button.data('sortType') || 'text');
|
||
const currentColumn = Number(table.dataset.sortColumn);
|
||
const currentDirection = table.dataset.sortDirection || 'asc';
|
||
const direction = currentColumn === columnIndex && currentDirection === 'asc' ? 'desc' : 'asc';
|
||
sortEventTable(table, columnIndex, sortType, direction);
|
||
});
|
||
|
||
$('.pdf-print-link').on('click', function() {
|
||
this.href = buildPdfUrl(this);
|
||
});
|
||
|
||
$('#externalParticipantSave').on('click', function() {
|
||
const firstName = capitalizeName($('#modalExternalFirstName').val().trim());
|
||
const lastName = capitalizeName($('#modalExternalLastName').val().trim());
|
||
const note = $('#modalExternalNote').val().trim();
|
||
if (!firstName && !lastName) {
|
||
alert('Please provide at least a first or last name for the non-school participant.');
|
||
return;
|
||
}
|
||
const parentFirst = capitalizeName($('#modalParentFirstName').val().trim());
|
||
const parentLast = capitalizeName($('#modalParentLastName').val().trim());
|
||
const parentPhone = $('#modalParentPhone').val().trim();
|
||
const parentEmail = $('#modalParentEmail').val().trim();
|
||
const eventId = $('#event_id').val();
|
||
if (!eventId) {
|
||
alert('Select an event before adding a non-school participant.');
|
||
return;
|
||
}
|
||
const explicitParentName = `${parentFirst} ${parentLast}`.trim();
|
||
if (!explicitParentName) {
|
||
alert('Please enter the external kid parent name.');
|
||
return;
|
||
}
|
||
if (!parentEmail) {
|
||
alert('Please enter the external kid parent email.');
|
||
return;
|
||
}
|
||
submitExternalParticipant({
|
||
firstname: firstName,
|
||
lastname: lastName,
|
||
note: note,
|
||
parentFirstname: parentFirst,
|
||
parentLastname: parentLast,
|
||
parentPhone: parentPhone,
|
||
parentEmail: parentEmail,
|
||
eventId: eventId
|
||
});
|
||
clearModalInputs();
|
||
});
|
||
});
|
||
</script>
|
||
<?= $this->endSection() ?>
|