Files
alrahma_sunday_school/app/Views/administrator/student_class_assignment.php
T
2026-02-10 22:11:06 -05:00

644 lines
32 KiB
PHP

<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div class="container-fluid">
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-3">Student Class Assignment</h2>
<div class="row g-2 align-items-center justify-content-center mb-3">
<div class="col-auto"><label for="scaYearSelect" class="col-form-label">School year</label></div>
<div class="col-auto">
<form method="get" action="<?= site_url('administrator/student_class_assignment') ?>" class="d-flex align-items-center gap-2">
<select id="scaYearSelect" name="schoolYear" class="form-select form-select-sm" style="min-width: 180px;">
<?php
$years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : [];
if (empty($years) && !empty($selectedYear)) $years = [$selectedYear];
foreach ($years as $y): $val = is_array($y) && isset($y['school_year']) ? $y['school_year'] : (string)$y; ?>
<option value="<?= esc($val) ?>" <?= ((string)($selectedYear ?? '') === (string)$val) ? 'selected' : '' ?>>
<?= esc($val) ?>
</option>
<?php endforeach; ?>
</select>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<label for="scaSemesterSelect" class="col-form-label">Semester</label>
<select id="scaSemesterSelect" name="semester" class="form-select form-select-sm" style="min-width: 140px;">
<option value="">—</option>
<option value="Fall" <?= (strcasecmp($semVal,'Fall')===0?'selected':'') ?>>Fall</option>
<option value="Spring" <?= (strcasecmp($semVal,'Spring')===0?'selected':'') ?>>Spring</option>
</select>
<button type="submit" class="btn btn-secondary btn-sm">Apply</button>
</form>
</div>
<?php if (isset($isCurrentYear) && !$isCurrentYear): ?>
<div class="col-auto"><span class="badge bg-secondary">Read-only (Past Year)</span></div>
<?php endif; ?>
</div>
<div class="table-responsive">
<!-- Auto-distribute panel -->
<div class="card mb-3">
<div class="card-body">
<form id="autoDistForm" action="<?= site_url('administrator/sections/auto-distribute') ?>" method="post" class="row g-2 align-items-end">
<?= csrf_field() ?>
<div class="col-md-4">
<label for="autoDistSection" class="form-label">Class (base section)</label>
<select id="autoDistSection" name="class_section_id" class="form-select" required>
<option value="">Select class…</option>
<?php if (!empty($classes)): ?>
<?php foreach ($classes as $c): ?>
<?php $nm = (string)($c['class_section_name'] ?? '');
$isBase = (strpos($nm, '-') === false);
if (!$isBase) continue; ?>
<option value="<?= (int)($c['class_section_id'] ?? 0) ?>">
<?= esc($nm) ?>
</option>
<?php endforeach; ?>
<?php endif; ?>
</select>
</div>
<div class="col-md-3">
<label for="autoDistPer" class="form-label">Students per section</label>
<input type="number" min="1" id="autoDistPer" name="students_per_section" class="form-control" required />
</div>
<div class="col-md-3">
<label for="autoDistYear" class="form-label">School year</label>
<input type="text" id="autoDistYear" name="school_year" value="<?= esc($selectedYear ?? '') ?>" class="form-control" />
</div>
<div class="col-md-2 d-grid">
<button type="submit" class="btn btn-outline-primary">Auto-Distribute</button>
</div>
<div class="col-12">
<div id="autoDistMsg" class="small text-muted"></div>
</div>
</form>
</div>
</div>
<table id="studentsTable" class="display table table-striped table-hover align-middle" style="width:100%">
<thead class="table-dark">
<tr>
<th>Student Name</th>
<th>Age</th>
<th>Registration Grade</th>
<th>New Student</th>
<th>Current Assigned Grade</th>
<th>Registration Date</th>
<th>School Year</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($students)): ?>
<?php foreach ($students as $student): ?>
<tr data-student-id="<?= (int)($student['student_id'] ?? 0) ?>"
data-student-name="<?= esc($student['name'] ?? '') ?>"
data-class-ids='<?= esc(json_encode($student['class_section_ids'] ?? [])) ?>'
data-class-names='<?= esc(json_encode($student['class_section_names'] ?? [])) ?>'>
<td>
<a href="#" class="text-decoration-none" data-family-student-id="<?= (int)($student['student_id'] ?? 0) ?>">
<?= esc($student['name']) ?>
</a>
</td>
<td><?= esc($student['age']) ?></td>
<td><?= esc($student['registration_grade']) ?></td>
<td><?= esc($student['new_student']) ?></td>
<?php
$sIds = $student['class_section_ids'] ?? [];
$sNames = $student['class_section_names'] ?? [];
$sidVal = (int)($student['student_id'] ?? 0);
$badgeHtml = '';
if (!empty($sIds) && is_array($sIds)) {
foreach ($sIds as $idx => $cid) {
$nm = $sNames[$idx] ?? ('Class #'.$cid);
$badgeHtml .= '<span class="badge bg-success me-1 mb-1 d-inline-flex align-items-center gap-1">';
$badgeHtml .= '<span>'.esc($nm).'</span>';
$badgeHtml .= '<button type="button" class="btn-close btn-close-white btn-close-sm remove-class-chip" data-student-id="'.$sidVal.'" data-class-id="'.(int)$cid.'" aria-label="Remove"></button>';
$badgeHtml .= '</span>';
}
} else {
$badgeHtml = '<span class="text-muted">No class assigned</span>';
}
?>
<td class="assigned-classes" data-class-ids='<?= esc(json_encode($sIds)) ?>' data-class-names='<?= esc(json_encode($sNames)) ?>'>
<?= $badgeHtml ?>
</td>
<td data-order="<?= esc($student['registration_date']) ?>">
<?php if (!empty($student['registration_date'])): ?>
<?= local_date($student['registration_date'], 'm-d-Y') ?>
<?php else: ?>
-
<?php endif; ?>
</td>
<td><?= esc($student['school_year']) ?></td>
<td>
<button
type="button"
class="btn btn-primary btn-sm assign-student-btn" <?= (isset($isCurrentYear) && !$isCurrentYear) ? 'disabled title="Editing disabled for non-current year"' : '' ?>
data-bs-toggle="modal"
data-bs-target="#assignStudentClassModal"
data-student-id="<?= esc($student['student_id']) ?>"
data-student-name="<?= esc($student['name']) ?>"
data-current-classes="<?= esc($student['class_section_name'] ?? '') ?>"
data-class-ids="<?= esc(json_encode($student['class_section_ids'] ?? [])) ?>"
data-class-names="<?= esc(json_encode($student['class_section_names'] ?? [])) ?>">
Assign Class
</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Single Modal (do NOT include another modal partial) -->
<div class="modal fade" id="assignStudentClassModal" tabindex="-1" aria-labelledby="assignStudentClassModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="assignStudentClassModalLabel">Assign Class to Student</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="assignError" class="alert alert-danger d-none"></div>
<div class="mb-2 small text-muted" id="currentAssignments"></div>
<form id="assignStudentClassForm" action="<?= base_url('assign_class_student') ?>" method="post">
<?= csrf_field() ?>
<input type="hidden" name="student_id" id="studentId">
<div class="mb-3">
<label for="studentName" class="form-label">Student Name</label>
<input type="text" class="form-control" id="studentName" readonly>
</div>
<div class="mb-3">
<label for="student_class_section_id" class="form-label">Select Class(es)</label>
<select name="class_section_id[]" id="student_class_section_id" class="form-control" multiple required size="8">
<option value="" disabled>Select one or more classes</option>
<?php if (!empty($classes) && is_array($classes)): ?>
<?php foreach ($classes as $class_): ?>
<option value="<?= htmlspecialchars($class_['class_section_id']) ?>">
<?= htmlspecialchars($class_['class_section_name']) ?>
</option>
<?php endforeach; ?>
<?php else: ?>
<option value="" disabled>No classes available</option>
<?php endif; ?>
</select>
<div class="form-text">Use Ctrl/Cmd + click to select multiple classes. Existing assignments are kept.</div>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" value="1" id="is_event_only" name="is_event_only">
<label class="form-check-label" for="is_event_only">
Event class only (no tuition)
</label>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="assignStudentClassButton">Assign Class</button>
</div>
</div>
</div>
</div>
</div>
<!-- Remove Modal -->
<div class="modal fade" id="removeStudentClassModal" tabindex="-1" aria-labelledby="removeStudentClassModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="removeStudentClassModalLabel">Remove Class from Student</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="removeError" class="alert alert-danger d-none"></div>
<form id="removeStudentClassForm" action="<?= site_url('remove_class_student') ?>" method="post">
<?= csrf_field() ?>
<input type="hidden" name="student_id" id="removeStudentId">
<div class="mb-3">
<label for="removeStudentName" class="form-label">Student Name</label>
<input type="text" class="form-control" id="removeStudentName" readonly>
</div>
<div class="mb-3">
<label for="remove_class_section_id" class="form-label">Select Class to Remove</label>
<select name="class_section_id" id="remove_class_section_id" class="form-control" required>
<option value="" disabled selected>Select a class</option>
</select>
<div class="form-text">Only currently assigned classes are shown.</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="removeStudentClassButton">Remove Class</button>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
// Helpers to render assigned class chips
let CSRF_NAME = '<?= csrf_token() ?>';
let CSRF_HASH = '<?= csrf_hash() ?>';
function applyCsrfToForms(name, hash) {
if (!name || !hash) return;
$('form').each(function(){
const $f = $(this);
let $fld = $f.find('input[name="'+name+'"]');
if (!$fld.length) {
$f.find('input[type="hidden"]').filter(function(){ return this.name && this.name.startsWith('csrf_'); }).remove();
$fld = $('<input>', {type:'hidden', name});
$f.prepend($fld);
}
$fld.val(hash);
});
}
function updateCsrf($form, res){
if (!$form || !res) return;
let name = res.csrfTokenName || null;
let hash = res.csrfHash || null;
if (!hash) {
$form.find('input[type="hidden"]').each(function(){
const nm = this.name;
if (nm && res[nm]) { name = nm; hash = res[nm]; return false; }
});
}
if (name && hash) {
CSRF_NAME = name;
CSRF_HASH = hash;
applyCsrfToForms(CSRF_NAME, CSRF_HASH);
}
}
function findColIndexByHeader(text){
let idx = -1;
$('#studentsTable thead th').each(function(i){
if ($(this).text().trim() === text) idx = i;
});
return idx >= 0 ? idx : 4; // fallback
}
const CLASS_COL_INDEX = findColIndexByHeader('Current Assigned Grade');
function renderBadgesHtml(ids, names, studentId){
if (!Array.isArray(ids) || ids.length === 0) return '<span class="text-muted">No class assigned</span>';
if (!Array.isArray(names)) names = [];
return ids.map((id, idx) => {
const nm = names[idx] || ('Class #' + id);
return `<span class="badge bg-success me-1 mb-1 d-inline-flex align-items-center gap-1">
<span>${nm}</span>
<button type="button" class="btn-close btn-close-white btn-close-sm remove-class-chip" data-student-id="${studentId}" data-class-id="${id}" aria-label="Remove"></button>
</span>`;
}).join('');
}
function renderAssignedBadges() {
const decodeAttr = (raw) => {
if (!raw) return '';
return raw.replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
};
$('#studentsTable tbody tr').each(function(){
const $row = $(this);
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
if (!$cell.length) return;
if (!$cell.attr('data-class-ids') && $row.attr('data-class-ids')) {
$cell.attr('data-class-ids', $row.attr('data-class-ids'));
}
if (!$cell.attr('data-class-names') && $row.attr('data-class-names')) {
$cell.attr('data-class-names', $row.attr('data-class-names'));
}
const idsRaw = decodeAttr($cell.attr('data-class-ids') || $row.attr('data-class-ids') || '[]');
const namesRaw = decodeAttr($cell.attr('data-class-names') || $row.attr('data-class-names') || '[]');
let ids = [], names = [];
try { ids = JSON.parse(idsRaw) || []; } catch(_) {}
try { names = JSON.parse(namesRaw) || []; } catch(_) {}
const studentId = parseInt($row.attr('data-student-id') || '0', 10);
const badgeHtml = renderBadgesHtml(ids, names, studentId);
$cell.html(badgeHtml || $cell.html());
});
}
window.renderAssignedBadges = renderAssignedBadges;
window.renderBadgesHtml = renderBadgesHtml;
// Keep table row + button data in sync after add/remove so modals stay up-to-date without refresh.
function syncAssignButtonData($row, ids, names, displayVal){
const idsJson = JSON.stringify(ids || []);
const namesJson = JSON.stringify(names || []);
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
$row.attr('data-class-ids', idsJson).attr('data-class-names', namesJson);
$cell.attr('data-class-ids', idsJson).attr('data-class-names', namesJson);
$row.find('.assign-student-btn')
.attr('data-class-ids', idsJson)
.attr('data-class-names', namesJson)
.attr('data-current-classes', displayVal || '')
.data('class-ids', ids || [])
.data('class-names', names || [])
.data('current-classes', displayVal || '');
}
window.syncAssignButtonData = syncAssignButtonData;
function getAssignmentsFromRow($row){
const res = { ids: [], names: [] };
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
const $chips = $cell.find('.remove-class-chip');
if ($chips.length) {
$chips.each(function(){
const cid = parseInt($(this).data('class-id') || 0, 10);
if (cid) res.ids.push(cid);
const label = $(this).closest('.badge').clone().children('button').remove().end().text().trim();
res.names.push(label || ('Class #' + cid));
});
return res;
}
const decodeAttr = (raw) => {
if (!raw) return '';
return raw.replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
};
try { res.ids = JSON.parse(decodeAttr($cell.attr('data-class-ids') || $row.attr('data-class-ids') || '[]')) || []; } catch(_) {}
try { res.names = JSON.parse(decodeAttr($cell.attr('data-class-names') || $row.attr('data-class-names') || '[]')) || []; } catch(_) {}
return res;
}
// Track the rows used by modals across handlers
let rowNode = null;
let removeRowNode = null;
(function () {
if (typeof $ === 'undefined' || !$.fn.DataTable) return;
window.studentsTable = $.fn.DataTable.isDataTable('#studentsTable')
? $('#studentsTable').DataTable()
: $('#studentsTable').DataTable({
responsive: true,
pageLength: 100,
lengthMenu: [5, 10, 25, 50, 100],
order: [[0, 'asc']],
stateSave: true,
columnDefs: [{ orderable: false, targets: [7] }],
language: { emptyTable: 'No students found.' }
});
window.studentsTable.on('draw', function(){ renderAssignedBadges(); });
window.getStudentsTable = function(){
return window.studentsTable || ($.fn.DataTable.isDataTable('#studentsTable') ? $('#studentsTable').DataTable() : null);
};
window.table = window.studentsTable; // legacy reference for existing handlers
// Open modal and set values
$(document).on('click', '.assign-student-btn', function(){
const $row = $(this).closest('tr');
rowNode = $row.get(0);
$('#studentId').val($(this).data('student-id'));
$('#studentName').val($(this).data('student-name'));
// Pre-select currently assigned classes using live badges, fallback to data attrs.
const fromRow = getAssignmentsFromRow($row);
const ids = Array.isArray(fromRow.ids) ? fromRow.ids : [];
const names = Array.isArray(fromRow.names) ? fromRow.names : [];
$('#student_class_section_id').val(ids.map(String)).trigger('change');
let current = $(this).attr('data-current-classes') || $(this).data('current-classes') || '';
if (!current && names.length) current = names.join(', ');
$('#currentAssignments').text(current ? `Currently assigned: ${current}` : 'Currently assigned: none');
$('#assignError').addClass('d-none').text('');
});
// Open remove modal and populate options
$(document).on('click', '.remove-class-btn', function(){
removeRowNode = $(this).closest('tr').get(0);
$('#removeStudentId').val($(this).data('student-id'));
$('#removeStudentName').val($(this).data('student-name'));
$('#removeError').addClass('d-none').text('');
const select = $('#remove_class_section_id');
select.empty();
select.append('<option value="" disabled selected>Select a class</option>');
try {
const ids = JSON.parse($(this).attr('data-class-ids') || '[]') || [];
const names = JSON.parse($(this).attr('data-class-names') || '[]') || [];
ids.forEach((id, idx) => {
const label = names[idx] || `Class #${id}`;
select.append(`<option value="${id}">${label}</option>`);
});
} catch (_) {
select.append('<option value="" disabled>Error loading classes</option>');
}
});
// Button click → trigger AJAX submit
$('#assignStudentClassButton').on('click', function(){
const form = document.getElementById('assignStudentClassForm');
if (form.checkValidity()) $('#assignStudentClassForm').trigger('submit');
else alert('Please select a class before submitting.');
});
// AJAX submit for assign
$(document).on('submit', '#assignStudentClassForm', function(e){
e.preventDefault();
const $form = $(this);
const url = $form.attr('action');
applyCsrfToForms(CSRF_NAME, CSRF_HASH);
const $btn = $('#assignStudentClassButton');
const orig = $btn.text();
$btn.prop('disabled', true).text('Assigning...');
const csrfField = $form.find('input[name="'+CSRF_NAME+'"]');
const csrfHeaderVal = csrfField.length ? csrfField.val() : CSRF_HASH;
$.ajax({
type: 'POST',
url: url,
data: $form.serialize(),
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest',
...(csrfHeaderVal ? {'X-CSRF-TOKEN': csrfHeaderVal} : {})
}
})
.done(function(res){
updateCsrf($form, res);
if (!res || !res.ok) {
$('#assignError').removeClass('d-none').text(res?.message || 'Unable to assign class.');
return;
}
// Update the table cell
if (rowNode) {
const tbl = window.getStudentsTable ? window.getStudentsTable() : null;
if (!tbl) return;
const displayVal = Array.isArray(res.class_section_names)
? res.class_section_names.join(', ')
: (res.class_section_name || '-');
const badgeHtml = renderBadgesHtml(res.class_section_ids || [], res.class_section_names || [], parseInt($(rowNode).attr('data-student-id') || '0', 10));
const rowIdx = tbl.row(rowNode).index();
tbl.cell(rowIdx, CLASS_COL_INDEX).data(badgeHtml);
const $row = $(tbl.row(rowIdx).node());
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
$cell.html(badgeHtml);
syncAssignButtonData($row, res.class_section_ids || [], res.class_section_names || [], displayVal);
tbl.draw(false);
renderAssignedBadges();
$(rowNode).addClass('table-success');
setTimeout(()=>$(rowNode).removeClass('table-success'), 1200);
}
// Close modal
const modalEl = document.getElementById('assignStudentClassModal');
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
})
.fail(function(xhr){
try {
const res = JSON.parse(xhr.responseText);
updateCsrf($form, res);
$('#assignError').removeClass('d-none').text(res?.message || 'Server error.');
} catch (_) {
$('#assignError').removeClass('d-none').text('Network or server error.');
}
})
.always(function(){
$btn.prop('disabled', false).text(orig);
});
});
})();
</script>
<script>
// Handle auto-distribute submit via AJAX
(function(){
const form = document.getElementById('autoDistForm');
if (!form) return;
form.addEventListener('submit', function(e){
e.preventDefault();
const fd = new FormData(form);
const msg = document.getElementById('autoDistMsg');
msg.textContent = 'Running distribution...';
fetch(form.action, { method: 'POST', body: fd, headers: { 'X-Requested-With':'XMLHttpRequest' } })
.then(r => r.json())
.then(res => {
msg.textContent = (res && res.message) ? res.message : 'Done.';
if (res && res.sections) {
console.log('Section summary', res.sections);
}
})
.catch(() => { msg.textContent = 'Failed to distribute.'; });
});
})();
// Handle remove class submit
(function(){
$(document).on('click', '#removeStudentClassButton', function(){
const form = document.getElementById('removeStudentClassForm');
if (form.checkValidity()) {
$('#removeStudentClassForm').trigger('submit');
} else {
alert('Please select a class to remove.');
}
});
$(document).on('submit', '#removeStudentClassForm', function(e){
e.preventDefault();
const $form = $(this);
const url = $form.attr('action');
const $btn = $('#removeStudentClassButton');
const orig = $btn.text();
$btn.prop('disabled', true).text('Removing...');
applyCsrfToForms(CSRF_NAME, CSRF_HASH);
const csrfField = $form.find('input[name="'+CSRF_NAME+'"]');
const csrfHeaderVal = csrfField.length ? csrfField.val() : CSRF_HASH;
$.ajax({
type: 'POST',
url: url,
data: $form.serialize(),
dataType: 'json',
headers: {
'X-Requested-With': 'XMLHttpRequest',
...(csrfHeaderVal ? {'X-CSRF-TOKEN': csrfHeaderVal} : {})
}
})
.done(function(res){
updateCsrf($form, res);
if (!res || !res.ok) {
$('#removeError').removeClass('d-none').text(res?.message || 'Unable to remove class.');
return;
}
// Update the table cell + data attributes
const tbl = window.getStudentsTable ? window.getStudentsTable() : null;
if (tbl) {
const targetRowNode = removeRowNode || $('#studentsTable tbody tr[data-student-id="'+(res.student_id || '')+'"]').get(0);
if (targetRowNode) {
const badgeHtml = renderBadgesHtml(res.remaining_ids || [], res.remaining_names || [], parseInt($(targetRowNode).attr('data-student-id') || '0', 10));
const rowIdx = tbl.row(targetRowNode).index();
tbl.cell(rowIdx, CLASS_COL_INDEX).data(badgeHtml);
const $row = $(tbl.row(rowIdx).node());
const $cell = $row.find('td').eq(CLASS_COL_INDEX);
$cell.html(badgeHtml); // ensure DOM shows latest without refresh
if (typeof syncAssignButtonData === 'function') {
syncAssignButtonData($row, res.remaining_ids || [], res.remaining_names || [], res.remaining_display || '');
}
tbl.draw(false); // redraw to keep ordering but stay on page
}
}
renderAssignedBadges();
const rowEl = removeRowNode || $('#studentsTable tbody tr[data-student-id="'+(res.student_id || '')+'"]').get(0);
if (rowEl) {
$(rowEl).addClass('table-warning');
setTimeout(()=>$(rowEl).removeClass('table-warning'), 1200);
}
const modalEl = document.getElementById('removeStudentClassModal');
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) modal.hide();
})
.fail(function(xhr){
try {
const res = JSON.parse(xhr.responseText);
updateCsrf($form, res);
$('#removeError').removeClass('d-none').text(res?.message || 'Server error.');
} catch (_) {
$('#removeError').removeClass('d-none').text('Network or server error.');
}
})
.always(function(){
$btn.prop('disabled', false).text(orig);
});
});
})();
// Inline chip removal (no modal)
$(document).on('click', '.remove-class-chip', function(e){
e.preventDefault();
const cid = parseInt($(this).data('class-id') || 0, 10);
const sid = parseInt($(this).data('student-id') || 0, 10);
if (!cid || !sid) return;
removeRowNode = $(this).closest('tr').get(0);
$('#removeStudentId').val(sid);
const label = $(this).closest('.badge').clone().children('button').remove().end().text().trim();
$('#remove_class_section_id').empty().append(`<option value="${cid}" selected>${label || ('Class #' + cid)}</option>`).val(cid);
$('#removeStudentClassForm').trigger('submit');
});
// Initial render of badges on page load
renderAssignedBadges();
</script>
<?= $this->endSection() ?>