Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f51be91e4 | |||
| 92016f90d0 | |||
| 45d7895182 | |||
| 596d368b1d |
@@ -537,6 +537,7 @@ $routes->post('payment/event_charges', 'View\EventController::eventUpdate');
|
|||||||
$routes->get('administrator/event-charges', 'View\EventController::eventShow');
|
$routes->get('administrator/event-charges', 'View\EventController::eventShow');
|
||||||
$routes->post('administrator/event-charges/remove/(:num)', 'View\EventController::removeCharge/$1');
|
$routes->post('administrator/event-charges/remove/(:num)', 'View\EventController::removeCharge/$1');
|
||||||
$routes->post('administrator/event-charges/payment/(:num)', 'View\EventController::toggleEventPayment/$1');
|
$routes->post('administrator/event-charges/payment/(:num)', 'View\EventController::toggleEventPayment/$1');
|
||||||
|
$routes->post('administrator/event-charges/waiver/(:num)', 'View\EventController::toggleWaiverStatus/$1');
|
||||||
$routes->get('administrator/get-students-with-charges', 'View\EventController::getStudentsWithCharges');
|
$routes->get('administrator/get-students-with-charges', 'View\EventController::getStudentsWithCharges');
|
||||||
|
|
||||||
$routes->get('parent/events', 'View\ParentController::parentEventPage', ['filter' => 'auth:parent']);
|
$routes->get('parent/events', 'View\ParentController::parentEventPage', ['filter' => 'auth:parent']);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class EventController extends ResourceController
|
|||||||
protected $categories;
|
protected $categories;
|
||||||
protected $enrollmentModel;
|
protected $enrollmentModel;
|
||||||
private ?bool $eventChargesHasCreatedBy = null;
|
private ?bool $eventChargesHasCreatedBy = null;
|
||||||
|
private ?bool $eventChargesHasWaiverSigned = null;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -85,6 +86,41 @@ class EventController extends ResourceController
|
|||||||
return $this->eventChargesHasCreatedBy;
|
return $this->eventChargesHasCreatedBy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function eventChargesSupportsWaiverSigned(): bool
|
||||||
|
{
|
||||||
|
if ($this->eventChargesHasWaiverSigned !== null) {
|
||||||
|
return $this->eventChargesHasWaiverSigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = Database::connect();
|
||||||
|
$this->eventChargesHasWaiverSigned = $db->fieldExists('waiver_signed', 'event_charges');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->eventChargesHasWaiverSigned = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->eventChargesHasWaiverSigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getEventChargesReturnTo(): string
|
||||||
|
{
|
||||||
|
$fallback = site_url('administrator/event-charges');
|
||||||
|
$returnTo = trim((string) ($this->request->getPost('return_to') ?? ''));
|
||||||
|
if ($returnTo === '') {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
$base = rtrim((string) base_url(), '/');
|
||||||
|
if (str_starts_with($returnTo, '/')) {
|
||||||
|
return $returnTo;
|
||||||
|
}
|
||||||
|
if ($base !== '' && str_starts_with($returnTo, $base)) {
|
||||||
|
return $returnTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$eventModel = new EventModel();
|
$eventModel = new EventModel();
|
||||||
@@ -220,6 +256,59 @@ class EventController extends ResourceController
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if ($updated) {
|
if ($updated) {
|
||||||
|
if ($this->request->getPost('add_to_calendar')) {
|
||||||
|
$calendarModel = new CalendarModel();
|
||||||
|
|
||||||
|
$title = trim((string) $this->request->getPost('event_name'));
|
||||||
|
$date = (string) $this->request->getPost('expiration_date');
|
||||||
|
$schoolYear = (string) $this->request->getPost('school_year');
|
||||||
|
$semester = (string) $this->request->getPost('semester');
|
||||||
|
|
||||||
|
if ($title !== '' && $date !== '' && $schoolYear !== '') {
|
||||||
|
$data = [
|
||||||
|
'title' => $title,
|
||||||
|
'description' => (string) $this->request->getPost('description'),
|
||||||
|
'event_type' => 'Event',
|
||||||
|
'date' => $date,
|
||||||
|
'notify_parent' => $this->request->getPost('notify_parent') ? 1 : 0,
|
||||||
|
'notify_teacher' => $this->request->getPost('notify_teacher') ? 1 : 0,
|
||||||
|
'notify_admin' => $this->request->getPost('notify_admin') ? 1 : 0,
|
||||||
|
'no_school' => 0,
|
||||||
|
'school_year' => $schoolYear,
|
||||||
|
'semester' => $semester ?: $this->semester,
|
||||||
|
];
|
||||||
|
if (!$calendarModel->supportsEventType()) {
|
||||||
|
unset($data['event_type']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$existingByPreviousEvent = $calendarModel
|
||||||
|
->where('school_year', (string) ($event['school_year'] ?? ''))
|
||||||
|
->where('date', (string) ($event['expiration_date'] ?? ''))
|
||||||
|
->where('title', (string) ($event['event_name'] ?? ''));
|
||||||
|
if ($calendarModel->supportsEventType() && isset($data['event_type'])) {
|
||||||
|
$existingByPreviousEvent = $existingByPreviousEvent->where('event_type', $data['event_type']);
|
||||||
|
}
|
||||||
|
$existingCalendar = $existingByPreviousEvent->first();
|
||||||
|
|
||||||
|
if ($existingCalendar) {
|
||||||
|
$calendarModel->update($existingCalendar['id'], $data);
|
||||||
|
} else {
|
||||||
|
$dupQuery = $calendarModel
|
||||||
|
->where('school_year', $data['school_year'])
|
||||||
|
->where('date', $data['date'])
|
||||||
|
->where('title', $data['title']);
|
||||||
|
if ($calendarModel->supportsEventType() && isset($data['event_type'])) {
|
||||||
|
$dupQuery = $dupQuery->where('event_type', $data['event_type']);
|
||||||
|
}
|
||||||
|
$duplicate = $dupQuery->first();
|
||||||
|
|
||||||
|
if (!$duplicate) {
|
||||||
|
$calendarModel->save($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$redirect = redirect()->to('/administrator/events')->with('success', 'Event updated successfully');
|
$redirect = redirect()->to('/administrator/events')->with('success', 'Event updated successfully');
|
||||||
if ($this->request->getPost('send_email_parent')) {
|
if ($this->request->getPost('send_email_parent')) {
|
||||||
$emailStatus = $this->broadcastEventToParents((int) $id);
|
$emailStatus = $this->broadcastEventToParents((int) $id);
|
||||||
@@ -517,6 +606,7 @@ class EventController extends ResourceController
|
|||||||
|
|
||||||
$parentsForInvoice = [];
|
$parentsForInvoice = [];
|
||||||
$supportsCreatedBy = $this->eventChargesSupportsCreatedBy();
|
$supportsCreatedBy = $this->eventChargesSupportsCreatedBy();
|
||||||
|
$supportsWaiverSigned = $this->eventChargesSupportsWaiverSigned();
|
||||||
|
|
||||||
foreach ($participations as $studentId => $value) {
|
foreach ($participations as $studentId => $value) {
|
||||||
$existing = $this->eventChargesModel->where([
|
$existing = $this->eventChargesModel->where([
|
||||||
@@ -543,6 +633,9 @@ class EventController extends ResourceController
|
|||||||
'updated_by' => $userId,
|
'updated_by' => $userId,
|
||||||
'class_section_id'=> $classSectionId,
|
'class_section_id'=> $classSectionId,
|
||||||
];
|
];
|
||||||
|
if ($supportsWaiverSigned) {
|
||||||
|
$updateData['waiver_signed'] = (int) ($existing['waiver_signed'] ?? 0);
|
||||||
|
}
|
||||||
if (isset($event['amount']) && ((float)$event['amount'] !== (float)($existing['charged'] ?? 0))) {
|
if (isset($event['amount']) && ((float)$event['amount'] !== (float)($existing['charged'] ?? 0))) {
|
||||||
$updateData['charged'] = $event['amount'];
|
$updateData['charged'] = $event['amount'];
|
||||||
}
|
}
|
||||||
@@ -561,6 +654,9 @@ class EventController extends ResourceController
|
|||||||
'semester' => $semester,
|
'semester' => $semester,
|
||||||
'updated_by' => $userId
|
'updated_by' => $userId
|
||||||
];
|
];
|
||||||
|
if ($supportsWaiverSigned) {
|
||||||
|
$insertData['waiver_signed'] = 0;
|
||||||
|
}
|
||||||
if ($supportsCreatedBy) {
|
if ($supportsCreatedBy) {
|
||||||
$insertData['created_by'] = $userId;
|
$insertData['created_by'] = $userId;
|
||||||
}
|
}
|
||||||
@@ -577,6 +673,7 @@ class EventController extends ResourceController
|
|||||||
$parentPhone = trim($pieces['parent_phone'] ?? '');
|
$parentPhone = trim($pieces['parent_phone'] ?? '');
|
||||||
$parentEmail = trim((string)($pieces['parent_email'] ?? ''));
|
$parentEmail = trim((string)($pieces['parent_email'] ?? ''));
|
||||||
$markPaid = (string)($pieces['paid'] ?? '0') === '1';
|
$markPaid = (string)($pieces['paid'] ?? '0') === '1';
|
||||||
|
$waiverSigned = (string)($pieces['waiver_signed'] ?? '0') === '1';
|
||||||
|
|
||||||
if (!$firstname && !$lastname) {
|
if (!$firstname && !$lastname) {
|
||||||
continue;
|
continue;
|
||||||
@@ -611,7 +708,7 @@ class EventController extends ResourceController
|
|||||||
$existingExternal = $this->eventChargesModel->where($matchConditions)->first();
|
$existingExternal = $this->eventChargesModel->where($matchConditions)->first();
|
||||||
|
|
||||||
if ($existingExternal) {
|
if ($existingExternal) {
|
||||||
$this->eventChargesModel->update($existingExternal['id'], [
|
$updateData = [
|
||||||
'participation' => 'yes',
|
'participation' => 'yes',
|
||||||
'charged' => $event['amount'],
|
'charged' => $event['amount'],
|
||||||
'updated_by' => $userId,
|
'updated_by' => $userId,
|
||||||
@@ -621,7 +718,11 @@ class EventController extends ResourceController
|
|||||||
'external_parent_lastname' => $parentLast !== '' ? $parentLast : ($existingExternal['external_parent_lastname'] ?? null),
|
'external_parent_lastname' => $parentLast !== '' ? $parentLast : ($existingExternal['external_parent_lastname'] ?? null),
|
||||||
'external_parent_phone' => $parentPhone !== '' ? $parentPhone : ($existingExternal['external_parent_phone'] ?? null),
|
'external_parent_phone' => $parentPhone !== '' ? $parentPhone : ($existingExternal['external_parent_phone'] ?? null),
|
||||||
'external_parent_email' => $parentEmail !== '' ? $parentEmail : ($existingExternal['external_parent_email'] ?? null),
|
'external_parent_email' => $parentEmail !== '' ? $parentEmail : ($existingExternal['external_parent_email'] ?? null),
|
||||||
]);
|
];
|
||||||
|
if ($supportsWaiverSigned) {
|
||||||
|
$updateData['waiver_signed'] = $waiverSigned ? 1 : 0;
|
||||||
|
}
|
||||||
|
$this->eventChargesModel->update($existingExternal['id'], $updateData);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,6 +744,9 @@ class EventController extends ResourceController
|
|||||||
'semester' => $semester,
|
'semester' => $semester,
|
||||||
'updated_by' => $userId,
|
'updated_by' => $userId,
|
||||||
];
|
];
|
||||||
|
if ($supportsWaiverSigned) {
|
||||||
|
$insertData['waiver_signed'] = $waiverSigned ? 1 : 0;
|
||||||
|
}
|
||||||
if ($supportsCreatedBy) {
|
if ($supportsCreatedBy) {
|
||||||
$insertData['created_by'] = $userId;
|
$insertData['created_by'] = $userId;
|
||||||
}
|
}
|
||||||
@@ -672,13 +776,14 @@ class EventController extends ResourceController
|
|||||||
|
|
||||||
public function removeCharge($chargeId = null)
|
public function removeCharge($chargeId = null)
|
||||||
{
|
{
|
||||||
|
$returnTo = $this->getEventChargesReturnTo();
|
||||||
if (!$chargeId) {
|
if (!$chargeId) {
|
||||||
return redirect()->back()->with('error', 'Invalid charge.');
|
return redirect()->to($returnTo)->with('error', 'Invalid charge.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$charge = $this->eventChargesModel->find($chargeId);
|
$charge = $this->eventChargesModel->find($chargeId);
|
||||||
if (!$charge) {
|
if (!$charge) {
|
||||||
return redirect()->back()->with('error', 'Charge not found.');
|
return redirect()->to($returnTo)->with('error', 'Charge not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$paymentId = (int)($charge['event_payment_id'] ?? 0);
|
$paymentId = (int)($charge['event_payment_id'] ?? 0);
|
||||||
@@ -691,19 +796,20 @@ class EventController extends ResourceController
|
|||||||
$this->invoiceController->generateInvoice((string)$charge['parent_id'], (string)($charge['school_year'] ?? $this->schoolYear), (string)($charge['semester'] ?? $this->semester), false);
|
$this->invoiceController->generateInvoice((string)$charge['parent_id'], (string)($charge['school_year'] ?? $this->schoolYear), (string)($charge['semester'] ?? $this->semester), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Participation removed, invoices updated.');
|
return redirect()->to($returnTo)->with('success', 'Participation removed, invoices updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toggleEventPayment($chargeId = null)
|
public function toggleEventPayment($chargeId = null)
|
||||||
{
|
{
|
||||||
|
$returnTo = $this->getEventChargesReturnTo();
|
||||||
if (!$chargeId) {
|
if (!$chargeId) {
|
||||||
return redirect()->back()->with('error', 'Invalid charge.');
|
return redirect()->to($returnTo)->with('error', 'Invalid charge.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$isPaid = $this->request->getPost('paid') === '1';
|
$isPaid = $this->request->getPost('paid') === '1';
|
||||||
$meta = $this->applyEventPaymentStatus((int)$chargeId, $isPaid);
|
$meta = $this->applyEventPaymentStatus((int)$chargeId, $isPaid);
|
||||||
if (!$meta) {
|
if (!$meta) {
|
||||||
return redirect()->back()->with('error', 'Charge not found.');
|
return redirect()->to($returnTo)->with('error', 'Charge not found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($meta['invoice_id'])) {
|
if (!empty($meta['invoice_id'])) {
|
||||||
@@ -711,7 +817,33 @@ class EventController extends ResourceController
|
|||||||
$this->fixInvoiceStatusAfterCharge((int)$meta['parent_id'], (string)$meta['school_year']);
|
$this->fixInvoiceStatusAfterCharge((int)$meta['parent_id'], (string)$meta['school_year']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Event payment status updated.');
|
return redirect()->to($returnTo)->with('success', 'Event payment status updated.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toggleWaiverStatus($chargeId = null)
|
||||||
|
{
|
||||||
|
$returnTo = $this->getEventChargesReturnTo();
|
||||||
|
if (!$chargeId) {
|
||||||
|
return redirect()->to($returnTo)->with('error', 'Invalid charge.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->eventChargesSupportsWaiverSigned()) {
|
||||||
|
return redirect()->to($returnTo)->with('error', 'Waiver tracking is not available until the database migration is applied.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$charge = $this->eventChargesModel->find($chargeId);
|
||||||
|
if (!$charge) {
|
||||||
|
return redirect()->to($returnTo)->with('error', 'Charge not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$signed = $this->request->getPost('waiver_signed') === '1';
|
||||||
|
|
||||||
|
$this->eventChargesModel->update((int) $chargeId, [
|
||||||
|
'waiver_signed' => $signed ? 1 : 0,
|
||||||
|
'updated_by' => session()->get('user_id'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->to($returnTo)->with('success', $signed ? 'Waiver marked as signed.' : 'Waiver marked as unsigned.');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parentHasEnrollment(int $parentId, string $schoolYear): bool
|
private function parentHasEnrollment(int $parentId, string $schoolYear): bool
|
||||||
|
|||||||
@@ -102,11 +102,9 @@ class ExamDraftController extends BaseController
|
|||||||
session()->set('class_section_id', $selectedClass);
|
session()->set('class_section_id', $selectedClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
$allDrafts = $this->examDraftModel
|
$classSectionIds = array_map(static fn($a) => (int) $a['class_section_id'], $assignments);
|
||||||
->select($this->draftSelectColumns())
|
|
||||||
->select('cs.class_section_name')
|
$allDrafts = $this->visibleTeacherDraftsQuery($teacherId, $classSectionIds)
|
||||||
->join('classSection cs', 'cs.class_section_id = exam_drafts.class_section_id', 'left')
|
|
||||||
->where($this->authorIdColumn, $teacherId)
|
|
||||||
->orderBy('created_at', 'DESC')
|
->orderBy('created_at', 'DESC')
|
||||||
->findAll();
|
->findAll();
|
||||||
|
|
||||||
@@ -117,10 +115,10 @@ class ExamDraftController extends BaseController
|
|||||||
$row['final_pdf_file'] = $pdf;
|
$row['final_pdf_file'] = $pdf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$row = $this->attachTeacherDraftContext($row, $teacherId);
|
||||||
}
|
}
|
||||||
unset($row);
|
unset($row);
|
||||||
|
|
||||||
$classSectionIds = array_map(static fn($a) => (int) $a['class_section_id'], $assignments);
|
|
||||||
$legacyExams = [];
|
$legacyExams = [];
|
||||||
// Guard against missing schema column in older databases
|
// Guard against missing schema column in older databases
|
||||||
if ($this->hasIsLegacyColumn) {
|
if ($this->hasIsLegacyColumn) {
|
||||||
@@ -129,8 +127,9 @@ class ExamDraftController extends BaseController
|
|||||||
|
|
||||||
$legacyQuery = $this->examDraftModel
|
$legacyQuery = $this->examDraftModel
|
||||||
->select($this->draftSelectColumns())
|
->select($this->draftSelectColumns())
|
||||||
->select('cs.class_section_name')
|
->select('cs.class_section_name, u.firstname AS teacher_first, u.lastname AS teacher_last')
|
||||||
->join('classSection cs', 'cs.class_section_id = exam_drafts.class_section_id', 'left')
|
->join('classSection cs', 'cs.class_section_id = exam_drafts.class_section_id', 'left')
|
||||||
|
->join('users u', 'u.id = exam_drafts.' . $this->authorIdColumn, 'left')
|
||||||
->where('exam_drafts.is_legacy', 1)
|
->where('exam_drafts.is_legacy', 1)
|
||||||
->where('exam_drafts.status', 'legacy')
|
->where('exam_drafts.status', 'legacy')
|
||||||
->where('exam_drafts.final_file IS NOT NULL', null, false);
|
->where('exam_drafts.final_file IS NOT NULL', null, false);
|
||||||
@@ -143,6 +142,11 @@ class ExamDraftController extends BaseController
|
|||||||
->orderBy('cs.class_section_name', 'ASC')
|
->orderBy('cs.class_section_name', 'ASC')
|
||||||
->orderBy('exam_drafts.created_at', 'DESC')
|
->orderBy('exam_drafts.created_at', 'DESC')
|
||||||
->findAll();
|
->findAll();
|
||||||
|
|
||||||
|
foreach ($legacyExams as &$row) {
|
||||||
|
$row = $this->attachTeacherDraftContext($row, $teacherId);
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
} else {
|
} else {
|
||||||
// Legacy column absent, show all drafts and skip legacy tab query
|
// Legacy column absent, show all drafts and skip legacy tab query
|
||||||
$drafts = $allDrafts;
|
$drafts = $allDrafts;
|
||||||
@@ -768,10 +772,11 @@ class ExamDraftController extends BaseController
|
|||||||
return $this->response->setStatusCode(401);
|
return $this->response->setStatusCode(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
$drafts = $this->examDraftModel
|
$assignments = $this->teacherClassModel->getClassAssignmentsByUserId($teacherId, $this->schoolYear);
|
||||||
->select($this->draftSelectColumns())
|
$classSectionIds = array_map(static fn($a) => (int) $a['class_section_id'], $assignments);
|
||||||
->select('id, status, acceptance_type, updated_at, reviewed_at')
|
|
||||||
->where($this->authorIdColumn, $teacherId)
|
$drafts = $this->visibleTeacherDraftsQuery($teacherId, $classSectionIds)
|
||||||
|
->select('exam_drafts.id, exam_drafts.status, exam_drafts.acceptance_type, exam_drafts.updated_at, exam_drafts.reviewed_at')
|
||||||
->orderBy('updated_at', 'DESC')
|
->orderBy('updated_at', 'DESC')
|
||||||
->findAll();
|
->findAll();
|
||||||
|
|
||||||
@@ -817,6 +822,58 @@ class ExamDraftController extends BaseController
|
|||||||
return $validIds[0] ?? 0;
|
return $validIds[0] ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function visibleTeacherDraftsQuery(int $teacherId, array $classSectionIds)
|
||||||
|
{
|
||||||
|
$query = $this->examDraftModel
|
||||||
|
->select($this->draftSelectColumns())
|
||||||
|
->select('cs.class_section_name, u.firstname AS teacher_first, u.lastname AS teacher_last')
|
||||||
|
->join('classSection cs', 'cs.class_section_id = exam_drafts.class_section_id', 'left')
|
||||||
|
->join('users u', 'u.id = exam_drafts.' . $this->authorIdColumn, 'left');
|
||||||
|
|
||||||
|
if (empty($classSectionIds)) {
|
||||||
|
return $query->where('exam_drafts.' . $this->authorIdColumn, $teacherId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->groupStart()
|
||||||
|
->where('exam_drafts.' . $this->authorIdColumn, $teacherId)
|
||||||
|
->orGroupStart()
|
||||||
|
->whereIn('exam_drafts.class_section_id', $classSectionIds)
|
||||||
|
->where('exam_drafts.' . $this->authorIdColumn . ' !=', $teacherId)
|
||||||
|
->where('exam_drafts.status !=', 'draft');
|
||||||
|
|
||||||
|
if ($this->schoolYear !== '') {
|
||||||
|
$query->where('exam_drafts.school_year', $this->schoolYear);
|
||||||
|
}
|
||||||
|
if ($this->semester !== '') {
|
||||||
|
$query->where('exam_drafts.semester', $this->semester);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->groupEnd()
|
||||||
|
->groupEnd();
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function attachTeacherDraftContext(array $row, int $viewerId): array
|
||||||
|
{
|
||||||
|
$isOwn = $this->draftTeacherId($row) === $viewerId;
|
||||||
|
$row['is_own_submission'] = $isOwn;
|
||||||
|
$row['teacher_display_name'] = $isOwn ? 'You' : $this->draftTeacherName($row);
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function draftTeacherName(array $draft): string
|
||||||
|
{
|
||||||
|
$name = trim(((string) ($draft['teacher_first'] ?? '')) . ' ' . ((string) ($draft['teacher_last'] ?? '')));
|
||||||
|
if ($name !== '') {
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$teacherId = $this->draftTeacherId($draft);
|
||||||
|
return $teacherId > 0 ? 'Teacher #' . $teacherId : 'Teacher';
|
||||||
|
}
|
||||||
|
|
||||||
private function statusBadgeMap(): array
|
private function statusBadgeMap(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -224,6 +224,10 @@ class FilesController extends Controller
|
|||||||
throw PageNotFoundException::forPageNotFound();
|
throw PageNotFoundException::forPageNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$this->canAccessExamDraftFile($name, $subdir)) {
|
||||||
|
return $this->response->setStatusCode(403, 'You are not allowed to access this file.');
|
||||||
|
}
|
||||||
|
|
||||||
$mime = 'application/octet-stream';
|
$mime = 'application/octet-stream';
|
||||||
if (function_exists('finfo_open')) {
|
if (function_exists('finfo_open')) {
|
||||||
$fi = finfo_open(FILEINFO_MIME_TYPE);
|
$fi = finfo_open(FILEINFO_MIME_TYPE);
|
||||||
@@ -317,4 +321,53 @@ class FilesController extends Controller
|
|||||||
}
|
}
|
||||||
return 'teacher_file';
|
return 'teacher_file';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function canAccessExamDraftFile(string $filename, string $subdir): bool
|
||||||
|
{
|
||||||
|
$userId = (int) (session()->get('user_id') ?? 0);
|
||||||
|
if ($userId <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$role = strtolower((string) (session()->get('role') ?? ''));
|
||||||
|
if ($role === 'admin') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = Database::connect();
|
||||||
|
$fileColumn = $subdir === 'finals' ? 'final_file' : $this->resolveExamDraftFileColumn($db);
|
||||||
|
|
||||||
|
$draft = $db->table('exam_drafts ed')
|
||||||
|
->select('ed.class_section_id, ed.school_year, ed.teacher_id, ed.author_id')
|
||||||
|
->where('ed.' . $fileColumn, $filename)
|
||||||
|
->limit(1)
|
||||||
|
->get()
|
||||||
|
->getRowArray();
|
||||||
|
|
||||||
|
if (empty($draft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$authorId = (int) ($draft['teacher_id'] ?? $draft['author_id'] ?? 0);
|
||||||
|
if ($authorId > 0 && $authorId === $userId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$classSectionId = (int) ($draft['class_section_id'] ?? 0);
|
||||||
|
if ($classSectionId <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$assignmentQuery = $db->table('teacher_class')
|
||||||
|
->select('id')
|
||||||
|
->where('teacher_id', $userId)
|
||||||
|
->where('class_section_id', $classSectionId);
|
||||||
|
|
||||||
|
$schoolYear = trim((string) ($draft['school_year'] ?? ''));
|
||||||
|
if ($schoolYear !== '') {
|
||||||
|
$assignmentQuery->where('school_year', $schoolYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $assignmentQuery->limit(1)->countAllResults() > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1797,16 +1797,34 @@ $existing = $this->studentModel
|
|||||||
$activeEventCount = is_array($activeEvents) ? count($activeEvents) : 0;
|
$activeEventCount = is_array($activeEvents) ? count($activeEvents) : 0;
|
||||||
|
|
||||||
// Get charges (participation info)
|
// Get charges (participation info)
|
||||||
$chargesList = $this->chargesModel->getChargesWithEventInfo($parentId, $schoolYear);
|
$chargesList = $this->chargesModel->getChargesWithEventInfo($parentId, $schoolYear, $semester);
|
||||||
|
|
||||||
// Build a map: "studentId:eventId" => [ 'participation' => ..., 'date' => ... ]
|
// Build a map: "studentId:eventId" => [ 'participation' => ..., 'date' => ... ]
|
||||||
$charges = [];
|
$charges = [];
|
||||||
|
$externalParticipantsByEvent = [];
|
||||||
foreach ($chargesList as $charge) {
|
foreach ($chargesList as $charge) {
|
||||||
$key = $charge['student_id'] . ':' . $charge['event_id'];
|
$studentId = $charge['student_id'] ?? null;
|
||||||
$charges[$key] = [
|
$eventId = (int) ($charge['event_id'] ?? 0);
|
||||||
'participation' => $charge['participation'],
|
|
||||||
'date' => $charge['updated_at'] ?? $charge['created_at'] // Use updated_at if available
|
if (!empty($studentId)) {
|
||||||
];
|
$key = $studentId . ':' . $eventId;
|
||||||
|
$charges[$key] = [
|
||||||
|
'participation' => $charge['participation'],
|
||||||
|
'date' => $charge['updated_at'] ?? $charge['created_at'], // Use updated_at if available
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$externalName = trim((string) ($charge['external_firstname'] ?? '') . ' ' . (string) ($charge['external_lastname'] ?? ''));
|
||||||
|
if ($eventId > 0 && $externalName !== '') {
|
||||||
|
$externalParticipantsByEvent[$eventId][] = [
|
||||||
|
'name' => $externalName,
|
||||||
|
'note' => (string) ($charge['external_note'] ?? ''),
|
||||||
|
'participation' => (string) ($charge['participation'] ?? ''),
|
||||||
|
'event_paid' => !empty($charge['event_paid']),
|
||||||
|
'charged' => (float) ($charge['charged'] ?? ($charge['event_amount'] ?? 0)),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get enrolled students
|
// Get enrolled students
|
||||||
@@ -1815,6 +1833,7 @@ $existing = $this->studentModel
|
|||||||
return view('parent/event_participation', [
|
return view('parent/event_participation', [
|
||||||
'activeEvents' => $activeEvents,
|
'activeEvents' => $activeEvents,
|
||||||
'charges' => $charges,
|
'charges' => $charges,
|
||||||
|
'externalParticipantsByEvent' => $externalParticipantsByEvent,
|
||||||
'yourStudents' => $students,
|
'yourStudents' => $students,
|
||||||
'activeEventCount' => $activeEventCount,
|
'activeEventCount' => $activeEventCount,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Database\Migrations;
|
||||||
|
|
||||||
|
use CodeIgniter\Database\Migration;
|
||||||
|
|
||||||
|
class AddWaiverSignedToEventCharges extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
$fields = [
|
||||||
|
'waiver_signed' => [
|
||||||
|
'type' => 'TINYINT',
|
||||||
|
'constraint' => 1,
|
||||||
|
'default' => 0,
|
||||||
|
'after' => 'charged',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $this->db->fieldExists('waiver_signed', 'event_charges')) {
|
||||||
|
$this->forge->addColumn('event_charges', $fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
if ($this->db->fieldExists('waiver_signed', 'event_charges')) {
|
||||||
|
$this->forge->dropColumn('event_charges', 'waiver_signed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ class EventChargesModel extends Model
|
|||||||
'student_id',
|
'student_id',
|
||||||
'participation',
|
'participation',
|
||||||
'charged',
|
'charged',
|
||||||
|
'waiver_signed',
|
||||||
'event_paid',
|
'event_paid',
|
||||||
'event_payment_id',
|
'event_payment_id',
|
||||||
'class_section_id',
|
'class_section_id',
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Description</label>
|
<label class="form-label">Description</label>
|
||||||
<textarea name="description" class="form-control"></textarea>
|
<textarea id="event_description" name="description" class="form-control"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -108,17 +108,37 @@
|
|||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
<?= $this->section('scripts') ?>
|
<?= $this->section('scripts') ?>
|
||||||
|
<script src="<?= base_url('assets/tinymce/tinymce.min.js') ?>"></script>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
const toggle = document.getElementById('add_to_calendar');
|
const toggle = document.getElementById('add_to_calendar');
|
||||||
const recipients = document.getElementById('calendar_recipients');
|
const recipients = document.getElementById('calendar_recipients');
|
||||||
if (!toggle || !recipients) return;
|
const description = document.getElementById('event_description');
|
||||||
|
|
||||||
function sync() {
|
if (toggle && recipients) {
|
||||||
recipients.style.display = toggle.checked ? '' : 'none';
|
function sync() {
|
||||||
|
recipients.style.display = toggle.checked ? '' : 'none';
|
||||||
|
}
|
||||||
|
toggle.addEventListener('change', sync);
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description && window.tinymce) {
|
||||||
|
tinymce.init({
|
||||||
|
selector: '#event_description',
|
||||||
|
base_url: '<?= base_url('assets/tinymce') ?>',
|
||||||
|
suffix: '.min',
|
||||||
|
license_key: 'gpl',
|
||||||
|
height: 320,
|
||||||
|
menubar: true,
|
||||||
|
branding: false,
|
||||||
|
promotion: false,
|
||||||
|
plugins: 'advlist autolink lists link charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime table help wordcount',
|
||||||
|
toolbar: 'undo redo | blocks | bold italic underline strikethrough forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link table | removeformat | preview code',
|
||||||
|
convert_urls: false,
|
||||||
|
content_style: 'body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; }'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
toggle.addEventListener('change', sync);
|
|
||||||
sync();
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<h2>Edit Event</h2>
|
<h2>Edit Event</h2>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$defaultCategories = ['Fun Event-1', 'Fun Event-2', 'Fun Event-3'];
|
||||||
|
$existingCategories = array_map('strval', $categories ?? []);
|
||||||
|
$allCategories = array_unique(array_merge($defaultCategories, $existingCategories));
|
||||||
|
?>
|
||||||
|
|
||||||
<?php if (session()->getFlashdata('error')): ?>
|
<?php if (session()->getFlashdata('error')): ?>
|
||||||
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
|
<div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -23,7 +29,7 @@
|
|||||||
<label class="form-label">Category</label>
|
<label class="form-label">Category</label>
|
||||||
<select name="event_category" class="form-control" required>
|
<select name="event_category" class="form-control" required>
|
||||||
<option value="" disabled <?= empty($event['event_category']) ? 'selected' : '' ?>>Select category</option>
|
<option value="" disabled <?= empty($event['event_category']) ? 'selected' : '' ?>>Select category</option>
|
||||||
<?php foreach (($categories ?? []) as $category): ?>
|
<?php foreach ($allCategories as $category): ?>
|
||||||
<option value="<?= esc($category) ?>" <?= (string)($event['event_category'] ?? '') === (string)$category ? 'selected' : '' ?>>
|
<option value="<?= esc($category) ?>" <?= (string)($event['event_category'] ?? '') === (string)$category ? 'selected' : '' ?>>
|
||||||
<?= esc(ucwords($category)) ?>
|
<?= esc(ucwords($category)) ?>
|
||||||
</option>
|
</option>
|
||||||
@@ -33,7 +39,7 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Description</label>
|
<label class="form-label">Description</label>
|
||||||
<textarea name="description" class="form-control"><?= esc($event['description']) ?></textarea>
|
<textarea id="event_description" name="description" class="form-control"><?= esc($event['description']) ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -44,7 +50,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Current Flyer</label><br>
|
<label class="form-label">Current Flyer</label><br>
|
||||||
<?php if ($event['flyer']): ?>
|
<?php if ($event['flyer']): ?>
|
||||||
<img src="<?= base_url('writable/uploads/' . $event['flyer']) ?>" width="150" class="mb-2">
|
<img src="<?= base_url('uploads/' . ltrim((string) $event['flyer'], '/')) ?>" width="150" class="mb-2">
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="text-muted">No flyer uploaded.</div>
|
<div class="text-muted">No flyer uploaded.</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -70,6 +76,37 @@
|
|||||||
<input type="text" name="school_year" class="form-control" value="<?= esc($event['school_year']) ?>" required>
|
<input type="text" name="school_year" class="form-control" value="<?= esc($event['school_year']) ?>" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">School Calendar</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-check mb-2">
|
||||||
|
<input class="form-check-input" type="checkbox" id="add_to_calendar" name="add_to_calendar" value="1">
|
||||||
|
<label class="form-check-label" for="add_to_calendar">
|
||||||
|
Add this event to the school calendar
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="calendar_recipients" class="ms-3" style="display:none;">
|
||||||
|
<div class="mb-2 fw-semibold">Visible On Calendars</div>
|
||||||
|
<div class="d-flex flex-wrap gap-3">
|
||||||
|
<label class="form-check-label">
|
||||||
|
<input class="form-check-input me-1" type="checkbox" name="notify_parent" value="1" checked>
|
||||||
|
Parents
|
||||||
|
</label>
|
||||||
|
<label class="form-check-label">
|
||||||
|
<input class="form-check-input me-1" type="checkbox" name="notify_teacher" value="1" checked>
|
||||||
|
Teachers
|
||||||
|
</label>
|
||||||
|
<label class="form-check-label">
|
||||||
|
<input class="form-check-input me-1" type="checkbox" name="notify_admin" value="1" checked>
|
||||||
|
Admins
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">If none are selected, the event is visible to everyone.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header">Parent Email Broadcast</div>
|
<div class="card-header">Parent Email Broadcast</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -89,3 +126,39 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
|
<?= $this->section('scripts') ?>
|
||||||
|
<script src="<?= base_url('assets/tinymce/tinymce.min.js') ?>"></script>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
const toggle = document.getElementById('add_to_calendar');
|
||||||
|
const recipients = document.getElementById('calendar_recipients');
|
||||||
|
const description = document.getElementById('event_description');
|
||||||
|
|
||||||
|
if (toggle && recipients) {
|
||||||
|
function sync() {
|
||||||
|
recipients.style.display = toggle.checked ? '' : 'none';
|
||||||
|
}
|
||||||
|
toggle.addEventListener('change', sync);
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description && window.tinymce) {
|
||||||
|
tinymce.init({
|
||||||
|
selector: '#event_description',
|
||||||
|
base_url: '<?= base_url('assets/tinymce') ?>',
|
||||||
|
suffix: '.min',
|
||||||
|
license_key: 'gpl',
|
||||||
|
height: 320,
|
||||||
|
menubar: true,
|
||||||
|
branding: false,
|
||||||
|
promotion: false,
|
||||||
|
plugins: 'advlist autolink lists link charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime table help wordcount',
|
||||||
|
toolbar: 'undo redo | blocks | bold italic underline strikethrough forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link table | removeformat | preview code',
|
||||||
|
convert_urls: false,
|
||||||
|
content_style: 'body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; }'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?= $this->endSection() ?>
|
||||||
|
|||||||
@@ -3,6 +3,54 @@
|
|||||||
|
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<h3>Event Charges</h3>
|
<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')): ?>
|
<?php if (session()->getFlashdata('success')): ?>
|
||||||
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
|
<div class="alert alert-success"><?= session()->getFlashdata('success') ?></div>
|
||||||
@@ -128,11 +176,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<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">
|
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#externalParticipantModal">
|
||||||
Add non-school participant
|
Add non-school participant
|
||||||
</button>
|
</button>
|
||||||
<div class="form-text">After adding participants, click Submit to save them to the database.</div>
|
<?php endif; ?>
|
||||||
<div id="externalParticipantsHidden"></div>
|
<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>
|
||||||
|
|
||||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||||
@@ -160,12 +209,24 @@
|
|||||||
<?php if (empty($grouped)): ?>
|
<?php if (empty($grouped)): ?>
|
||||||
<div class="alert alert-info">No charges found.</div>
|
<div class="alert alert-info">No charges found.</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
|
<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>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" id="printAllEventCharges">
|
||||||
|
Print All Participant Lists
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="eventChargeCards">
|
||||||
<?php foreach ($grouped as $eventId => $data): ?>
|
<?php foreach ($grouped as $eventId => $data): ?>
|
||||||
<?php $eventLabel = $data['label']; ?>
|
<?php $eventLabel = $data['label']; ?>
|
||||||
<?php $rows = $data['rows']; ?>
|
<?php $rows = $data['rows']; ?>
|
||||||
<div class="card mb-4 event-card" data-event-id="<?= esc($eventId) ?>">
|
<div class="card mb-4 event-card" data-event-id="<?= esc($eventId) ?>">
|
||||||
<div class="card-header bg-light fw-semibold">
|
<div class="card-header bg-light fw-semibold event-card-header">
|
||||||
<?= esc($eventLabel) ?>
|
<span><?= esc($eventLabel) ?></span>
|
||||||
|
<div class="event-card-header-actions no-print">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm print-event-card" data-event-id="<?= esc($eventId) ?>">
|
||||||
|
Print This List
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<?php
|
<?php
|
||||||
@@ -173,18 +234,19 @@
|
|||||||
$totalCharged = 0.0;
|
$totalCharged = 0.0;
|
||||||
?>
|
?>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table id="eventTable_<?= esc($eventId) ?>" class="table table-bordered table-striped mb-0 no-mgmt-sticky">
|
<table id="eventTable_<?= esc($eventId) ?>" class="table table-bordered table-striped mb-0 no-mgmt-sticky event-charges-table">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th><button type="button" class="sortable-header" data-sort-index="0" data-sort-type="number">ID</button></th>
|
||||||
<th>Parent Name</th>
|
<th><button type="button" class="sortable-header" data-sort-index="1" data-sort-type="text">Parent Name</button></th>
|
||||||
<th>Student Name</th>
|
<th><button type="button" class="sortable-header" data-sort-index="2" data-sort-type="text">Student Name</button></th>
|
||||||
<th>External Info</th>
|
<th><button type="button" class="sortable-header" data-sort-index="3" data-sort-type="text">External Info</button></th>
|
||||||
<th>Class Section</th>
|
<th><button type="button" class="sortable-header" data-sort-index="4" data-sort-type="text">Class Section</button></th>
|
||||||
<th>Charged Amount</th>
|
<th><button type="button" class="sortable-header" data-sort-index="5" data-sort-type="number">Charged Amount</button></th>
|
||||||
<th>Created</th>
|
<th><button type="button" class="sortable-header" data-sort-index="6" data-sort-type="date">Created</button></th>
|
||||||
<th>Fees Paid</th>
|
<th><button type="button" class="sortable-header" data-sort-index="7" data-sort-type="text">Waiver</button></th>
|
||||||
<th>Payment</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>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -193,9 +255,25 @@
|
|||||||
<?php
|
<?php
|
||||||
$isParticipating = ($charge['participation'] === 'yes');
|
$isParticipating = ($charge['participation'] === 'yes');
|
||||||
$isEventPaid = !empty($charge['event_paid']);
|
$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>
|
<tr>
|
||||||
<td><?= esc($charge['id']) ?></td>
|
<td data-sort-value="<?= esc((string) ($charge['id'] ?? '')) ?>"><?= esc($charge['id']) ?></td>
|
||||||
<?php
|
<?php
|
||||||
$studentName = $charge['student_firstname']
|
$studentName = $charge['student_firstname']
|
||||||
? trim($charge['student_firstname'] . ' ' . $charge['student_lastname'])
|
? trim($charge['student_firstname'] . ' ' . $charge['student_lastname'])
|
||||||
@@ -211,44 +289,69 @@
|
|||||||
($charge['external_parent_lastname'] ?? '')
|
($charge['external_parent_lastname'] ?? '')
|
||||||
);
|
);
|
||||||
$externalParentPhone = $charge['external_parent_phone'] ?? '';
|
$externalParentPhone = $charge['external_parent_phone'] ?? '';
|
||||||
|
$externalParentEmail = $charge['external_parent_email'] ?? '';
|
||||||
$standardParentName = trim($charge['parent_firstname'] . ' ' . $charge['parent_lastname']);
|
$standardParentName = trim($charge['parent_firstname'] . ' ' . $charge['parent_lastname']);
|
||||||
$parentColumnName = $externalParentLabel ?: ($standardParentName ?: '—');
|
$parentColumnName = $externalParentLabel ?: ($standardParentName ?: '—');
|
||||||
|
$externalInfoValue = trim(implode(' ', array_filter([
|
||||||
|
$externalParentPhone,
|
||||||
|
$externalParentEmail,
|
||||||
|
$externalNote,
|
||||||
|
])));
|
||||||
?>
|
?>
|
||||||
<td><?= esc($parentColumnName) ?></td>
|
<td data-sort-value="<?= esc(strtolower($parentColumnName)) ?>"><?= esc($parentColumnName) ?></td>
|
||||||
<td>
|
<td data-sort-value="<?= esc(strtolower($displayName)) ?>">
|
||||||
<?= esc($displayName) ?>
|
<?= esc($displayName) ?>
|
||||||
<?php if ($externalNote): ?>
|
<?php if ($externalNote): ?>
|
||||||
<small class="text-muted d-block"><?= esc($externalNote) ?></small>
|
<small class="text-muted d-block"><?= esc($externalNote) ?></small>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td data-sort-value="<?= esc(strtolower($externalInfoValue)) ?>">
|
||||||
<?php if ($externalParentPhone): ?>
|
<?php if ($externalParentPhone): ?>
|
||||||
<div class="text-muted small"><?= esc($externalParentPhone) ?></div>
|
<div class="text-muted small"><?= esc($externalParentPhone) ?></div>
|
||||||
<?php else: ?>
|
<?php endif; ?>
|
||||||
|
<?php if ($externalParentEmail): ?>
|
||||||
|
<div class="text-muted small"><?= esc($externalParentEmail) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!$externalParentPhone && !$externalParentEmail): ?>
|
||||||
<span class="text-muted small">—</span>
|
<span class="text-muted small">—</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td data-sort-value="<?= esc(strtolower((string) ($classSectionNames[$charge['class_section_id'] ?? ''] ?? ''))) ?>">
|
||||||
<?= esc($classSectionNames[$charge['class_section_id'] ?? ''] ?? '—') ?>
|
<?= esc($classSectionNames[$charge['class_section_id'] ?? ''] ?? '—') ?>
|
||||||
</td>
|
</td>
|
||||||
<td>$<?= esc(number_format($charge['event_amount'] ?? 0, 2)) ?></td>
|
<td data-sort-value="<?= esc(number_format((float) ($charge['event_amount'] ?? 0), 2, '.', '')) ?>">$<?= esc(number_format($charge['event_amount'] ?? 0, 2)) ?></td>
|
||||||
<td><?= esc(!empty($charge['created_at']) ? local_datetime($charge['created_at'], 'm-d-Y H:i') : '') ?></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
|
<?php
|
||||||
$feeAmount = (float) ($charge['event_amount'] ?? 0);
|
$feeAmount = (float) ($charge['event_amount'] ?? 0);
|
||||||
$hasBalanceRecord = array_key_exists((int)$charge['parent_id'], $parentBalances);
|
$hasBalanceRecord = array_key_exists((int)$charge['parent_id'], $parentBalances);
|
||||||
$parentBalance = $hasBalanceRecord ? (float)$parentBalances[$charge['parent_id']] : null;
|
$parentBalance = $hasBalanceRecord ? (float)$parentBalances[$charge['parent_id']] : null;
|
||||||
|
$waiverSigned = !empty($charge['waiver_signed']);
|
||||||
$feeIsPaid = $isParticipating && ($isEventPaid || ($hasBalanceRecord && $parentBalance <= 0));
|
$feeIsPaid = $isParticipating && ($isEventPaid || ($hasBalanceRecord && $parentBalance <= 0));
|
||||||
?>
|
?>
|
||||||
<td class="text-center">
|
<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): ?>
|
<?php if ($feeIsPaid): ?>
|
||||||
<span class="badge bg-success">Paid</span>
|
<span class="badge bg-success">Paid</span>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<span class="badge bg-danger">Unpaid</span>
|
<span class="badge bg-danger">Unpaid</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<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">
|
<form method="post" action="<?= site_url('administrator/event-charges/payment/' . esc($charge['id'])) ?>" class="d-inline-flex align-items-center">
|
||||||
<?= csrf_field() ?>
|
<?= csrf_field() ?>
|
||||||
|
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
|
||||||
<input type="hidden" name="paid" value="<?= $feeIsPaid ? '1' : '0' ?>">
|
<input type="hidden" name="paid" value="<?= $feeIsPaid ? '1' : '0' ?>">
|
||||||
<input type="checkbox" class="form-check-input" id="eventPayment_<?= esc($charge['id']) ?>"
|
<input type="checkbox" class="form-check-input" id="eventPayment_<?= esc($charge['id']) ?>"
|
||||||
<?= $isEventPaid ? 'checked' : '' ?>
|
<?= $isEventPaid ? 'checked' : '' ?>
|
||||||
@@ -263,6 +366,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<form action="<?= site_url('administrator/event-charges/remove/' . esc($charge['id'])) ?>" method="post" onsubmit="return confirm('Remove this participation and update the charge?');">
|
<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() ?>
|
<?= csrf_field() ?>
|
||||||
|
<input type="hidden" name="return_to" value="<?= esc($returnTo) ?>">
|
||||||
<button type="submit" class="btn btn-outline-danger btn-sm">Remove</button>
|
<button type="submit" class="btn btn-outline-danger btn-sm">Remove</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,7 +384,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th colspan="4">Totals</th>
|
<th colspan="4">Totals</th>
|
||||||
<th>$<?= esc(number_format($totalCharged, 2)) ?></th>
|
<th>$<?= esc(number_format($totalCharged, 2)) ?></th>
|
||||||
<th colspan="5">
|
<th colspan="6">
|
||||||
<span class="badge bg-secondary">
|
<span class="badge bg-secondary">
|
||||||
<?= esc($totalParticipants) ?> participating
|
<?= esc($totalParticipants) ?> participating
|
||||||
</span>
|
</span>
|
||||||
@@ -292,9 +396,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</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 fade" id="externalParticipantModal" tabindex="-1" aria-labelledby="externalParticipantModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -349,6 +455,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
@@ -426,174 +533,11 @@ function loadStudentsWithCharges() {
|
|||||||
loadStudentsWithCharges();
|
loadStudentsWithCharges();
|
||||||
}
|
}
|
||||||
|
|
||||||
const $externalHidden = $('#externalParticipantsHidden');
|
const $externalSaveButton = $('#externalParticipantSave');
|
||||||
const storageKey = 'eventChargesExternalParticipants';
|
|
||||||
let externalDrafts = [];
|
|
||||||
let editingKey = null;
|
|
||||||
|
|
||||||
const escapeHtml = (value) => {
|
|
||||||
const s = String(value ?? '');
|
|
||||||
return s.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildExternalRow(entry) {
|
|
||||||
const key = entry.key;
|
|
||||||
const isPaid = !!entry.paid;
|
|
||||||
const firstEsc = escapeHtml(entry.firstname);
|
|
||||||
const lastEsc = escapeHtml(entry.lastname);
|
|
||||||
const noteEsc = escapeHtml(entry.note);
|
|
||||||
const parentFirstEsc = escapeHtml(entry.parentFirstname);
|
|
||||||
const parentLastEsc = escapeHtml(entry.parentLastname);
|
|
||||||
const parentPhoneEsc = escapeHtml(entry.parentPhone);
|
|
||||||
const parentEmailEsc = escapeHtml(entry.parentEmail);
|
|
||||||
const label = (firstEsc || lastEsc)
|
|
||||||
? `${firstEsc || ''} ${lastEsc || ''}`.trim()
|
|
||||||
: 'Unnamed participant';
|
|
||||||
const parentLabel = (parentFirstEsc || parentLastEsc)
|
|
||||||
? `<div class="text-muted small">Parent: ${parentFirstEsc} ${parentLastEsc}</div>`
|
|
||||||
: '';
|
|
||||||
const phoneLabel = parentPhoneEsc
|
|
||||||
? `<div class="text-muted small">${parentPhoneEsc}</div>`
|
|
||||||
: '';
|
|
||||||
const parentNameForRow = entry.parentDisplayName || '—';
|
|
||||||
const amountDisplay = ((entry.eventAmount ?? 0) || 0.0).toFixed(2);
|
|
||||||
|
|
||||||
const $wrapper = $(`
|
|
||||||
<div data-key="${key}">
|
|
||||||
<input type="hidden" name="external_participants[${key}][firstname]" value="${firstEsc}">
|
|
||||||
<input type="hidden" name="external_participants[${key}][lastname]" value="${lastEsc}">
|
|
||||||
<input type="hidden" name="external_participants[${key}][note]" value="${noteEsc}">
|
|
||||||
<input type="hidden" name="external_participants[${key}][parent_firstname]" value="${parentFirstEsc}">
|
|
||||||
<input type="hidden" name="external_participants[${key}][parent_lastname]" value="${parentLastEsc}">
|
|
||||||
<input type="hidden" name="external_participants[${key}][parent_phone]" value="${parentPhoneEsc}">
|
|
||||||
<input type="hidden" name="external_participants[${key}][parent_email]" value="${parentEmailEsc}">
|
|
||||||
<input type="hidden" name="external_participants[${key}][paid]" value="${isPaid ? '1' : '0'}">
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
$externalHidden.append($wrapper);
|
|
||||||
|
|
||||||
const $tableBody = $(`#eventTable_${entry.eventId} tbody`);
|
|
||||||
if (!$tableBody.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const createdDisplay = new Date().toLocaleString();
|
|
||||||
const $previewRow = $(`
|
|
||||||
<tr class="external-preview table-warning" data-key="${key}" data-event-id="${entry.eventId}">
|
|
||||||
<td>—</td>
|
|
||||||
<td>${escapeHtml(parentNameForRow)}</td>
|
|
||||||
<td>
|
|
||||||
<strong>${label}</strong>
|
|
||||||
${noteEsc ? `<div class="text-muted small">${noteEsc}</div>` : ''}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
${phoneLabel}
|
|
||||||
${phoneLabel ? '' : '<span class="text-muted small">—</span>'}
|
|
||||||
</td>
|
|
||||||
<td>External</td>
|
|
||||||
<td>$${amountDisplay}</td>
|
|
||||||
<td>${createdDisplay}</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<span class="badge bg-warning text-dark">Pending</span>
|
|
||||||
${isPaid ? '<span class="badge bg-success ms-1 external-paid-badge">Paid on save</span>' : '<span class="badge bg-secondary ms-1 external-paid-badge d-none">Paid on save</span>'}
|
|
||||||
</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<div class="form-check d-inline-flex align-items-center gap-2 justify-content-center m-0">
|
|
||||||
<input class="form-check-input external-paid-toggle" type="checkbox" data-key="${key}" ${isPaid ? 'checked' : ''}>
|
|
||||||
<span class="small text-muted">On save</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="text-end d-flex gap-2">
|
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm edit-external-participant" data-key="${key}">
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm remove-external-participant" data-key="${key}">
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`);
|
|
||||||
$tableBody.append($previewRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
function persistDrafts() {
|
|
||||||
localStorage.setItem(storageKey, JSON.stringify(externalDrafts));
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveExternalDraft(entry) {
|
|
||||||
entry.key = entry.key ?? `${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
||||||
entry.paid = !!entry.paid;
|
|
||||||
removeExternalEntryFromDom(entry.key);
|
|
||||||
externalDrafts = externalDrafts.filter((existing) => existing.key !== entry.key);
|
|
||||||
externalDrafts.push(entry);
|
|
||||||
persistDrafts();
|
|
||||||
buildExternalRow(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeExternalEntryFromDom(key) {
|
|
||||||
$(`tr.external-preview[data-key="${key}"]`).remove();
|
|
||||||
$externalHidden.find(`div[data-key="${key}"]`).remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeDraft(key) {
|
|
||||||
externalDrafts = externalDrafts.filter((entry) => entry.key !== key);
|
|
||||||
persistDrafts();
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadDrafts() {
|
|
||||||
const raw = localStorage.getItem(storageKey);
|
|
||||||
if (!raw) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const saved = JSON.parse(raw);
|
|
||||||
if (!Array.isArray(saved)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
externalDrafts = saved;
|
|
||||||
const currentEvent = $('#event_id').val();
|
|
||||||
externalDrafts.forEach((entry) => {
|
|
||||||
if (String(entry.eventId) === String(currentEvent)) {
|
|
||||||
buildExternalRow(entry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to load external drafts', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDraftByKey(key) {
|
|
||||||
return externalDrafts.find((entry) => entry.key === key);
|
|
||||||
}
|
|
||||||
|
|
||||||
function openExternalModalForEntry(key) {
|
|
||||||
const entry = getDraftByKey(key);
|
|
||||||
if (!entry) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
editingKey = key;
|
|
||||||
$('#modalExternalFirstName').val(entry.firstname);
|
|
||||||
$('#modalExternalLastName').val(entry.lastname);
|
|
||||||
$('#modalExternalNote').val(entry.note);
|
|
||||||
$('#modalParentFirstName').val(entry.parentFirstname);
|
|
||||||
$('#modalParentLastName').val(entry.parentLastname);
|
|
||||||
$('#modalParentPhone').val(entry.parentPhone);
|
|
||||||
$('#modalParentEmail').val(entry.parentEmail);
|
|
||||||
if (modalInstance) {
|
|
||||||
modalInstance.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const modalElement = document.getElementById('externalParticipantModal');
|
const modalElement = document.getElementById('externalParticipantModal');
|
||||||
const modalInstance = modalElement ? new bootstrap.Modal(modalElement) : null;
|
const modalInstance = modalElement ? new bootstrap.Modal(modalElement) : null;
|
||||||
if (modalElement) {
|
|
||||||
modalElement.addEventListener('hidden.bs.modal', () => {
|
|
||||||
editingKey = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function clearModalInputs() {
|
function clearModalInputs() {
|
||||||
$('#modalExternalFirstName').val('');
|
$('#modalExternalFirstName').val('');
|
||||||
$('#modalExternalLastName').val('');
|
$('#modalExternalLastName').val('');
|
||||||
@@ -604,6 +548,225 @@ function loadStudentsWithCharges() {
|
|||||||
$('#modalParentEmail').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 escapeHtml(value) {
|
||||||
|
const s = String(value ?? '');
|
||||||
|
return s.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
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 buildPrintableCard(card) {
|
||||||
|
const clone = card.cloneNode(true);
|
||||||
|
clone.querySelectorAll('.no-print').forEach((node) => node.remove());
|
||||||
|
clone.querySelectorAll('.sortable-header').forEach((button) => {
|
||||||
|
const headerText = button.textContent || '';
|
||||||
|
const textNode = document.createTextNode(headerText);
|
||||||
|
button.parentNode.replaceChild(textNode, button);
|
||||||
|
});
|
||||||
|
clone.querySelectorAll('form').forEach((form) => {
|
||||||
|
const cell = form.closest('td');
|
||||||
|
if (!cell) {
|
||||||
|
form.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const label = form.querySelector('.form-check-label');
|
||||||
|
const checkbox = form.querySelector('input[type="checkbox"]');
|
||||||
|
const text = label
|
||||||
|
? label.textContent.trim()
|
||||||
|
: (checkbox && checkbox.checked ? 'Paid' : 'Unpaid');
|
||||||
|
cell.textContent = text || '—';
|
||||||
|
});
|
||||||
|
clone.querySelectorAll('tr').forEach((row) => {
|
||||||
|
if (row.children.length > 10) {
|
||||||
|
row.removeChild(row.lastElementChild);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return clone.outerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPrintWindow(title, contentHtml) {
|
||||||
|
const printWindow = window.open('', '_blank', 'noopener,noreferrer,width=1200,height=900');
|
||||||
|
if (!printWindow) {
|
||||||
|
alert('Unable to open the print preview window. Please allow pop-ups for this site.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const style = `
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; margin: 24px; color: #212529; }
|
||||||
|
h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
|
||||||
|
p { margin-top: 0; color: #6c757d; }
|
||||||
|
.card { border: 1px solid #dee2e6; border-radius: 0.5rem; margin-bottom: 1.5rem; overflow: hidden; }
|
||||||
|
.card-header { background: #f8f9fa; padding: 0.9rem 1rem; font-weight: 700; }
|
||||||
|
.table { width: 100%; border-collapse: collapse; }
|
||||||
|
.table th, .table td { border: 1px solid #dee2e6; padding: 0.55rem 0.7rem; text-align: left; vertical-align: top; }
|
||||||
|
.table thead th, .table tfoot th { background: #f8f9fa; }
|
||||||
|
.badge { display: inline-block; padding: 0.2rem 0.45rem; border-radius: 999px; font-size: 0.8rem; font-weight: 700; }
|
||||||
|
.bg-success { background: #198754; color: #fff; }
|
||||||
|
.bg-danger { background: #dc3545; color: #fff; }
|
||||||
|
.bg-secondary { background: #6c757d; color: #fff; }
|
||||||
|
.text-muted { color: #6c757d; }
|
||||||
|
.small { font-size: 0.875rem; }
|
||||||
|
@media print {
|
||||||
|
body { margin: 0; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
printWindow.document.open();
|
||||||
|
printWindow.document.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>${escapeHtml(title)}</title>
|
||||||
|
${style}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>${escapeHtml(title)}</h1>
|
||||||
|
<p>Generated on ${escapeHtml(new Date().toLocaleString())}</p>
|
||||||
|
${contentHtml}
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
window.print();
|
||||||
|
};
|
||||||
|
<\/script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
printWindow.document.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function printEventCards(cards, title) {
|
||||||
|
const printable = cards.map((card) => buildPrintableCard(card)).join('');
|
||||||
|
openPrintWindow(title, printable);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.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);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.print-event-card').on('click', function() {
|
||||||
|
const eventId = String($(this).data('eventId'));
|
||||||
|
const card = document.querySelector(`.event-card[data-event-id="${eventId}"]`);
|
||||||
|
if (!card) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const title = $(card).find('.card-header span').first().text().trim() || 'Event Participants';
|
||||||
|
printEventCards([card], `${title} Participant List`);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#printAllEventCharges').on('click', function() {
|
||||||
|
const cards = Array.from(document.querySelectorAll('.event-card'));
|
||||||
|
if (!cards.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
printEventCards(cards, 'All Event Participant Lists');
|
||||||
|
});
|
||||||
|
|
||||||
$('#externalParticipantSave').on('click', function() {
|
$('#externalParticipantSave').on('click', function() {
|
||||||
const firstName = capitalizeName($('#modalExternalFirstName').val().trim());
|
const firstName = capitalizeName($('#modalExternalFirstName').val().trim());
|
||||||
const lastName = capitalizeName($('#modalExternalLastName').val().trim());
|
const lastName = capitalizeName($('#modalExternalLastName').val().trim());
|
||||||
@@ -621,7 +784,6 @@ function loadStudentsWithCharges() {
|
|||||||
alert('Select an event before adding a non-school participant.');
|
alert('Select an event before adding a non-school participant.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const eventAmount = parseFloat($('#event_id option:selected').data('amount')) || 0;
|
|
||||||
const explicitParentName = `${parentFirst} ${parentLast}`.trim();
|
const explicitParentName = `${parentFirst} ${parentLast}`.trim();
|
||||||
if (!explicitParentName) {
|
if (!explicitParentName) {
|
||||||
alert('Please enter the external kid parent name.');
|
alert('Please enter the external kid parent name.');
|
||||||
@@ -631,9 +793,7 @@ function loadStudentsWithCharges() {
|
|||||||
alert('Please enter the external kid parent email.');
|
alert('Please enter the external kid parent email.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parentName = explicitParentName;
|
submitExternalParticipant({
|
||||||
const draftEntry = editingKey ? getDraftByKey(editingKey) : null;
|
|
||||||
const entry = {
|
|
||||||
firstname: firstName,
|
firstname: firstName,
|
||||||
lastname: lastName,
|
lastname: lastName,
|
||||||
note: note,
|
note: note,
|
||||||
@@ -641,61 +801,10 @@ function loadStudentsWithCharges() {
|
|||||||
parentLastname: parentLast,
|
parentLastname: parentLast,
|
||||||
parentPhone: parentPhone,
|
parentPhone: parentPhone,
|
||||||
parentEmail: parentEmail,
|
parentEmail: parentEmail,
|
||||||
eventId: (draftEntry && draftEntry.eventId) ? draftEntry.eventId : eventId,
|
eventId: eventId
|
||||||
eventAmount: (draftEntry && draftEntry.eventAmount) ? draftEntry.eventAmount : eventAmount,
|
});
|
||||||
parentDisplayName: (draftEntry && draftEntry.parentDisplayName) ? draftEntry.parentDisplayName : parentName,
|
|
||||||
paid: (draftEntry && typeof draftEntry.paid !== 'undefined') ? !!draftEntry.paid : false,
|
|
||||||
};
|
|
||||||
if (editingKey) {
|
|
||||||
entry.key = editingKey;
|
|
||||||
}
|
|
||||||
saveExternalDraft(entry);
|
|
||||||
editingKey = null;
|
|
||||||
clearModalInputs();
|
clearModalInputs();
|
||||||
if (modalInstance) {
|
|
||||||
modalInstance.hide();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.remove-external-participant', function() {
|
|
||||||
const $entry = $(this).closest('[data-key]');
|
|
||||||
const key = $entry.data('key');
|
|
||||||
removeExternalEntryFromDom(key);
|
|
||||||
if (key) {
|
|
||||||
removeDraft(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$(document).on('click', '.edit-external-participant', function() {
|
|
||||||
const key = $(this).data('key');
|
|
||||||
if (key) {
|
|
||||||
openExternalModalForEntry(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$(document).on('change', '.external-paid-toggle', function() {
|
|
||||||
const key = $(this).data('key');
|
|
||||||
const paid = !!this.checked;
|
|
||||||
if (!key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entry = getDraftByKey(key);
|
|
||||||
if (entry) {
|
|
||||||
entry.paid = paid;
|
|
||||||
persistDrafts();
|
|
||||||
}
|
|
||||||
$externalHidden
|
|
||||||
.find(`div[data-key="${key}"] input[name="external_participants[${key}][paid]"]`)
|
|
||||||
.val(paid ? '1' : '0');
|
|
||||||
const $badge = $(`tr.external-preview[data-key="${key}"] .external-paid-badge`);
|
|
||||||
if (paid) {
|
|
||||||
$badge.removeClass('d-none').addClass('bg-success').text('Paid on save');
|
|
||||||
} else {
|
|
||||||
$badge.addClass('d-none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#event-participant-form').on('submit', function() {
|
|
||||||
localStorage.removeItem(storageKey);
|
|
||||||
});
|
|
||||||
loadDrafts();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ $amount = ($amountRaw !== null && $amountRaw !== '') ? number_format((float) $am
|
|||||||
<p style="margin:0.25rem 0;"><strong>Amount:</strong> $<?= esc($amount) ?></p>
|
<p style="margin:0.25rem 0;"><strong>Amount:</strong> $<?= esc($amount) ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (!empty($event['description'])): ?>
|
<?php if (!empty($event['description'])): ?>
|
||||||
<p style="margin:0.75rem 0;"><?= nl2br(esc((string) $event['description'])) ?></p>
|
<div style="margin:0.75rem 0;"><?= $event['description'] ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (!empty($flyerUrl)): ?>
|
<?php if (!empty($flyerUrl)): ?>
|
||||||
<div style="margin:1rem 0;">
|
<div style="margin:1rem 0;">
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($activeEvents as $event): ?>
|
<?php foreach ($activeEvents as $event): ?>
|
||||||
|
<?php $externalParticipants = $externalParticipantsByEvent[$event['id']] ?? []; ?>
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
@@ -42,6 +43,53 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<h6 class="mb-1">Description</h6>
|
||||||
|
<?php if (!empty($event['description'])): ?>
|
||||||
|
<div class="mb-0"><?= $event['description'] ?></div>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="mb-0">No description</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($externalParticipants)): ?>
|
||||||
|
<div class="mb-3">
|
||||||
|
<h6 class="mb-2">External Participants</h6>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Student Name</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Fee</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($externalParticipants as $participant): ?>
|
||||||
|
<?php
|
||||||
|
$isPaid = !empty($participant['event_paid']) || ((float) ($participant['charged'] ?? 0) <= 0);
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<?= esc($participant['name']) ?> <span class="text-muted">(external)</span>
|
||||||
|
<?php if (!empty($participant['note'])): ?>
|
||||||
|
<small class="text-muted d-block"><?= esc($participant['note']) ?></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-<?= $isPaid ? 'success' : 'danger' ?>">
|
||||||
|
<?= $isPaid ? 'Paid' : 'Unpaid' ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-nowrap">$<?= esc(number_format((float) ($participant['charged'] ?? 0), 2)) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Participation Table -->
|
<!-- Participation Table -->
|
||||||
<form method="post" action="<?= site_url('parent/updateParticipation') ?>">
|
<form method="post" action="<?= site_url('parent/updateParticipation') ?>">
|
||||||
<?= csrf_field() ?>
|
<?= csrf_field() ?>
|
||||||
@@ -51,11 +99,8 @@
|
|||||||
<table class="table table-sm table-bordered">
|
<table class="table table-sm table-bordered">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Student First Name</th>
|
<th>Student Name</th>
|
||||||
<th>Student Last Name</th>
|
|
||||||
<th class="text-center">Participate</th>
|
<th class="text-center">Participate</th>
|
||||||
<th>Description</th>
|
|
||||||
<th>Event Fees</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -65,8 +110,7 @@
|
|||||||
$current = $charges[$key]['participation'] ?? '';
|
$current = $charges[$key]['participation'] ?? '';
|
||||||
?>
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?= esc($student['firstname']) ?></td>
|
<td><?= esc(trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''))) ?></td>
|
||||||
<td><?= esc($student['lastname']) ?></td>
|
|
||||||
<td class="text-center align-middle">
|
<td class="text-center align-middle">
|
||||||
<div class="d-inline-flex align-items-center gap-3">
|
<div class="d-inline-flex align-items-center gap-3">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
@@ -83,8 +127,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td><?= esc($event['description'] ?: 'No description') ?></td>
|
|
||||||
<td class="text-nowrap">$<?= esc(number_format((float) ($event['amount'] ?? 0), 2)) ?></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -89,12 +89,14 @@ $maxUploadBytes = $maxUploadBytes ?? (12 * 1024 * 1024);
|
|||||||
};
|
};
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<h2 class="h5 mb-3">Your submissions</h2>
|
<h2 class="h5 mb-1">Visible submissions</h2>
|
||||||
|
<p class="text-muted small">Includes your uploads and submitted exam drafts from teachers assigned to the same class-section.</p>
|
||||||
<?php if (!empty($drafts)): ?>
|
<?php if (!empty($drafts)): ?>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped align-middle teacher-drafts-table">
|
<table class="table table-striped align-middle teacher-drafts-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Teacher</th>
|
||||||
<th>Class</th>
|
<th>Class</th>
|
||||||
<th>Title / type</th>
|
<th>Title / type</th>
|
||||||
<th>Ver.</th>
|
<th>Ver.</th>
|
||||||
@@ -111,6 +113,7 @@ $maxUploadBytes = $maxUploadBytes ?? (12 * 1024 * 1024);
|
|||||||
$badgeHtml = $renderBadge($st, $statusBadges);
|
$badgeHtml = $renderBadge($st, $statusBadges);
|
||||||
?>
|
?>
|
||||||
<tr data-draft-id="<?= (int) ($d['id'] ?? 0) ?>">
|
<tr data-draft-id="<?= (int) ($d['id'] ?? 0) ?>">
|
||||||
|
<td><?= esc($d['teacher_display_name'] ?? 'Teacher') ?></td>
|
||||||
<td><?= esc($d['class_section_name'] ?? '') ?></td>
|
<td><?= esc($d['class_section_name'] ?? '') ?></td>
|
||||||
<td class="title-cell"><?= esc($d['draft_title'] ?? $d['exam_type'] ?? '') ?></td>
|
<td class="title-cell"><?= esc($d['draft_title'] ?? $d['exam_type'] ?? '') ?></td>
|
||||||
<td><?= esc((string) ($d['version'] ?? '')) ?></td>
|
<td><?= esc((string) ($d['version'] ?? '')) ?></td>
|
||||||
@@ -168,6 +171,7 @@ $maxUploadBytes = $maxUploadBytes ?? (12 * 1024 * 1024);
|
|||||||
<table class="table table-sm table-striped align-middle teacher-drafts-table">
|
<table class="table table-sm table-striped align-middle teacher-drafts-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Teacher</th>
|
||||||
<th>Class</th>
|
<th>Class</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th>Ver.</th>
|
<th>Ver.</th>
|
||||||
@@ -183,6 +187,7 @@ $maxUploadBytes = $maxUploadBytes ?? (12 * 1024 * 1024);
|
|||||||
<?php foreach ($legacyExams as $d): ?>
|
<?php foreach ($legacyExams as $d): ?>
|
||||||
<?php $st = strtolower((string) ($d['status'] ?? '')); ?>
|
<?php $st = strtolower((string) ($d['status'] ?? '')); ?>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td><?= esc($d['teacher_display_name'] ?? 'Teacher') ?></td>
|
||||||
<td><?= esc($d['class_section_name'] ?? '') ?></td>
|
<td><?= esc($d['class_section_name'] ?? '') ?></td>
|
||||||
<td class="title-cell"><?= esc($d['draft_title'] ?? $d['exam_type'] ?? '') ?></td>
|
<td class="title-cell"><?= esc($d['draft_title'] ?? $d['exam_type'] ?? '') ?></td>
|
||||||
<td><?= esc((string) ($d['version'] ?? '')) ?></td>
|
<td><?= esc((string) ($d['version'] ?? '')) ?></td>
|
||||||
|
|||||||
Reference in New Issue
Block a user