Files
2026-04-07 18:42:39 -04:00

528 lines
22 KiB
PHP

<?= $this->extend('layout/management_layout') ?>
<?= $this->section('content') ?>
<div
class="container-fluid"
data-api-list="<?= esc($apiListUrl ?? '') ?>"
data-api-assign="<?= esc($apiAssignUrl ?? '') ?>"
data-api-delete="<?= esc($apiDeleteUrl ?? '') ?>"
data-csrf-name="<?= esc($csrfTokenName ?? csrf_token()) ?>"
data-csrf-value="<?= esc($csrfTokenValue ?? csrf_hash()) ?>"
>
<div class="wrapper">
<div class="content">
<h2 class="text-center mt-4 mb-2">Teacher Class Assignments</h2>
<?php if (!empty($missingYear)): ?>
<div class="alert alert-warning" role="alert">
Current school year is not configured. This page is read-only until configured in
<a href="<?= site_url('configuration/configuration_view') ?>" class="alert-link">Add/Edit Configuration</a> (key: <code>school_year</code>).
</div>
<?php endif; ?>
<div class="d-flex justify-content-center align-items-center gap-2 mb-3">
<label for="tcaYearSelect" class="col-form-label">School year</label>
<form method="get" action="<?= site_url('administrator/teacher_class_assignment') ?>" class="d-flex align-items-center gap-2">
<select id="tcaYearSelect" name="schoolYear" class="form-select form-select-sm" style="min-width: 180px;">
<?php
$years = isset($schoolYears) && is_array($schoolYears) ? $schoolYears : [];
if (empty($years) && !empty($schoolYear)) $years = [$schoolYear];
foreach ($years as $y): $val = is_array($y) && isset($y['school_year']) ? $y['school_year'] : (string)$y; ?>
<option value="<?= esc($val) ?>" <?= ((string)($schoolYear ?? '') === (string)$val) ? 'selected' : '' ?>>
<?= esc($val) ?>
</option>
<?php endforeach; ?>
</select>
<?php $semVal = (string)($semester ?? ($_GET['semester'] ?? '')); ?>
<label for="tcaSemesterSelect" class="col-form-label">Semester</label>
<select id="tcaSemesterSelect" 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>
<?php if (isset($isCurrentYear) && !$isCurrentYear): ?>
<span class="badge bg-secondary">Read-only (Past Year)</span>
<?php endif; ?>
</div>
<?= view('partials/flash_messages') ?>
<div id="assignmentMessages"></div>
<table id="teachersTable" class="display table table-striped table-hover align-middle">
<thead class="table-dark">
<tr>
<th>#</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Phone Number</th>
<th>Role</th>
<th>Assigned Classes</th>
<th>School Year</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center text-muted">Loading...</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<?= $this->include('partials/assign_class_teacher_modal') ?>
<?= $this->endSection() ?>
<?= $this->section('scripts') ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
var container = document.querySelector('.container-fluid[data-api-list]');
if (!container) {
return;
}
var apiList = container.getAttribute('data-api-list') || '';
var apiAssign = container.getAttribute('data-api-assign') || '';
var apiDelete = container.getAttribute('data-api-delete') || '';
var csrfName = container.getAttribute('data-csrf-name') || '';
var csrfValue = container.getAttribute('data-csrf-value') || '';
var tableEl = document.getElementById('teachersTable');
var tbody = tableEl ? tableEl.querySelector('tbody') : null;
var messages = document.getElementById('assignmentMessages');
var selectedYear = <?= json_encode((string)($schoolYear ?? '')) ?>;
var isCurrentYear = <?= json_encode((bool)($isCurrentYear ?? true)) ?>;
var classSelect = document.getElementById('class_section_id');
var assignForm = document.getElementById('assignClassForm');
var assignButton = document.getElementById('assignClassButton');
var teacherRoleHidden = document.getElementById('teacherRoleHidden');
var teacherRoleDisplay = document.getElementById('teacherRoleDisplay');
var assignModalEl = document.getElementById('assignClassModal');
function updateCsrfFromResponse(data) {
if (!data) return;
if (data.csrf_token && data.csrf_hash) {
csrfName = data.csrf_token;
csrfValue = data.csrf_hash;
container.setAttribute('data-csrf-name', csrfName);
container.setAttribute('data-csrf-value', csrfValue);
var hiddenField = document.getElementById('assignCsrfField');
if (hiddenField) {
hiddenField.setAttribute('name', csrfName);
hiddenField.value = csrfValue;
}
}
}
function applyCsrf(payload) {
var data = Object.assign({}, payload || {});
if (csrfName) {
data[csrfName] = csrfValue;
}
return data;
}
function bootstrapAlertClass(type) {
if (type === 'success') return 'alert-success';
if (type === 'info') return 'alert-info';
if (type === 'warning') return 'alert-warning';
return 'alert-danger';
}
function showMessage(type, text) {
if (!messages || !text) return;
var alert = document.createElement('div');
alert.className = 'alert ' + bootstrapAlertClass(type) + ' alert-dismissible fade show';
alert.setAttribute('role', 'alert');
alert.innerHTML = '<span>' + text + '</span>'
+ '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>';
messages.appendChild(alert);
setTimeout(function () {
if (alert && alert.parentNode) {
alert.classList.remove('show');
alert.classList.add('fade');
setTimeout(function () {
if (alert.parentNode) {
alert.parentNode.removeChild(alert);
}
}, 200);
}
}, 6000);
}
function renderAssignedList(assignments, label, teacher) {
var fragment = document.createDocumentFragment();
assignments.forEach(function (assignment) {
var li = document.createElement('li');
li.className = 'd-flex align-items-center justify-content-between gap-2 mb-1';
var text = document.createElement('span');
text.innerHTML = '<strong>' + (assignment.class_section_name || 'Class #' + assignment.section_id) + '</strong> (' + label + ')';
li.appendChild(text);
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-outline-danger btn-sm remove-assignment-btn';
btn.textContent = 'Remove';
btn.setAttribute('data-teacher-id', teacher.teacher_id);
btn.setAttribute('data-teacher-name', teacher.name || '');
btn.setAttribute('data-class-section-id', assignment.section_id);
btn.setAttribute('data-class-name', assignment.class_section_name || 'Class #' + assignment.section_id);
var position = (assignment.role || '').toLowerCase();
if (position !== 'main' && position !== 'ta') {
position = label.toLowerCase() === 'ta' ? 'ta' : 'main';
}
btn.setAttribute('data-position', position);
var assignmentSemester = assignment.semester || '';
var assignmentSchoolYear = assignment.school_year || selectedYear || '';
btn.setAttribute('data-semester', assignmentSemester);
btn.setAttribute('data-school-year', assignmentSchoolYear);
li.appendChild(btn);
fragment.appendChild(li);
});
return fragment;
}
function renderTeachersTable(teachers, schoolYear) {
if (!tbody) return;
if (window.jQuery && $.fn.DataTable && $.fn.DataTable.isDataTable('#teachersTable')) {
$('#teachersTable').DataTable().destroy();
}
tbody.innerHTML = '';
if (!teachers || teachers.length === 0) {
var emptyRow = document.createElement('tr');
var emptyCell = document.createElement('td');
emptyCell.className = 'text-center text-muted';
emptyCell.textContent = 'No teachers found.';
emptyRow.appendChild(emptyCell);
for (var i = 0; i < 8; i++) {
emptyRow.appendChild(document.createElement('td'));
}
tbody.appendChild(emptyRow);
} else {
teachers.forEach(function (teacher, index) {
var row = document.createElement('tr');
var orderCell = document.createElement('td');
orderCell.className = 'text-center';
orderCell.textContent = index + 1;
row.appendChild(orderCell);
var firstCell = document.createElement('td');
firstCell.textContent = teacher.firstname || '';
row.appendChild(firstCell);
var lastCell = document.createElement('td');
lastCell.textContent = teacher.lastname || '';
row.appendChild(lastCell);
var emailCell = document.createElement('td');
emailCell.textContent = teacher.email || '';
row.appendChild(emailCell);
var phoneCell = document.createElement('td');
phoneCell.textContent = teacher.cellphone || '';
row.appendChild(phoneCell);
var roleCell = document.createElement('td');
var role = (teacher.role || '').replace(/_/g, ' ');
roleCell.textContent = role ? role.charAt(0).toUpperCase() + role.slice(1) : 'N/A';
row.appendChild(roleCell);
var assignedCell = document.createElement('td');
if ((teacher.main_assignments && teacher.main_assignments.length) ||
(teacher.ta_assignments && teacher.ta_assignments.length)) {
var list = document.createElement('ul');
list.className = 'list-unstyled mb-0';
if (teacher.main_assignments && teacher.main_assignments.length) {
list.appendChild(renderAssignedList(teacher.main_assignments, 'Main', teacher));
}
if (teacher.ta_assignments && teacher.ta_assignments.length) {
list.appendChild(renderAssignedList(teacher.ta_assignments, 'TA', teacher));
}
assignedCell.appendChild(list);
} else {
var empty = document.createElement('em');
empty.textContent = 'No classes assigned';
assignedCell.appendChild(empty);
}
row.appendChild(assignedCell);
var schoolYearCell = document.createElement('td');
schoolYearCell.textContent = teacher.school_year || schoolYear || '';
row.appendChild(schoolYearCell);
var actionsCell = document.createElement('td');
var assignBtn = document.createElement('button');
assignBtn.type = 'button';
assignBtn.className = 'btn btn-success btn-sm assign-class-btn';
if (!isCurrentYear) { assignBtn.disabled = true; assignBtn.title = 'Editing disabled for non-current year'; }
assignBtn.setAttribute('data-bs-toggle', 'modal');
assignBtn.setAttribute('data-bs-target', '#assignClassModal');
assignBtn.setAttribute('data-teacher-id', teacher.teacher_id);
assignBtn.setAttribute('data-teacher-name', teacher.name || '');
assignBtn.setAttribute('data-teacher-role', teacher.role || '');
assignBtn.textContent = 'Assign Class';
actionsCell.appendChild(assignBtn);
row.appendChild(actionsCell);
tbody.appendChild(row);
});
}
if (window.jQuery && $.fn.DataTable) {
$('#teachersTable').DataTable({
pageLength: 100,
lengthMenu: [5, 10, 25, 50, 100],
order: [[0, 'asc']],
columnDefs: [
{ orderable: false, targets: [6, 8] }
]
});
}
}
function populateClassOptions(classes) {
if (!classSelect) {
return;
}
classSelect.innerHTML = '';
var placeholder = document.createElement('option');
placeholder.value = '';
placeholder.disabled = true;
placeholder.selected = true;
placeholder.textContent = 'Select a class';
classSelect.appendChild(placeholder);
(classes || []).forEach(function (cls) {
var option = document.createElement('option');
option.value = cls.class_section_id;
option.textContent = cls.class_section_name || ('Class #' + cls.class_section_id);
classSelect.appendChild(option);
});
}
function loadAssignments() {
if (!apiList || !tbody) {
return;
}
tbody.innerHTML = '<tr>'
+ '<td class="text-center text-muted">Loading...</td>'
+ '<td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td>'
+ '</tr>';
var url = apiList + (selectedYear ? ('?schoolYear=' + encodeURIComponent(selectedYear)) : '');
fetch(url, {
headers: { 'Accept': 'application/json' },
credentials: 'same-origin'
})
.then(function (response) {
return response.json()
.catch(function () { return {}; })
.then(function (data) {
return { ok: response.ok, data: data };
});
})
.then(function (result) {
if (!result) {
throw new Error('No response received.');
}
var data = result.data || {};
updateCsrfFromResponse(data);
if (!result.ok || data.ok === false) {
showMessage('danger', data.message || 'Unable to load teacher assignments.');
return;
}
renderTeachersTable(data.teachers || [], data.school_year || '');
populateClassOptions(data.classes || []);
})
.catch(function (error) {
showMessage('danger', error.message || 'Failed to load assignments.');
});
}
function submitAssignment(payload) {
if (!apiAssign || !isCurrentYear) {
showMessage('warning', 'Editing disabled for non-current year.');
return;
}
fetch(apiAssign, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify(applyCsrf(payload))
})
.then(function (response) {
return response.json()
.catch(function () { return {}; })
.then(function (data) {
return { ok: response.ok, data: data };
});
})
.then(function (result) {
if (!result) {
throw new Error('No response received.');
}
var data = result.data || {};
updateCsrfFromResponse(data);
if (!result.ok || data.ok === false) {
showMessage('danger', data.message || 'Unable to assign class.');
return;
}
showMessage('success', data.message || 'Class assigned successfully.');
if (assignForm) {
assignForm.reset();
}
if (assignModalEl && window.bootstrap) {
var modalInstance = bootstrap.Modal.getInstance(assignModalEl);
if (modalInstance) {
modalInstance.hide();
}
}
loadAssignments();
})
.catch(function (error) {
showMessage('danger', error.message || 'Unable to assign class.');
});
}
function removeAssignment(payload) {
if (!apiDelete || !isCurrentYear) {
showMessage('warning', 'Editing disabled for non-current year.');
return;
}
fetch(apiDelete, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify(applyCsrf(payload))
})
.then(function (response) {
return response.json()
.catch(function () { return {}; })
.then(function (data) {
return { ok: response.ok, data: data };
});
})
.then(function (result) {
if (!result) {
throw new Error('No response received.');
}
var data = result.data || {};
updateCsrfFromResponse(data);
if (!result.ok || data.ok === false) {
showMessage('danger', data.message || 'Unable to remove assignment.');
return;
}
showMessage('success', data.message || 'Assignment removed successfully.');
loadAssignments();
})
.catch(function (error) {
showMessage('danger', error.message || 'Unable to remove assignment.');
});
}
document.addEventListener('click', function (event) {
var assignBtn = event.target.closest('.assign-class-btn');
if (assignBtn) {
var teacherId = assignBtn.getAttribute('data-teacher-id') || '';
var teacherName = assignBtn.getAttribute('data-teacher-name') || '';
var teacherRole = assignBtn.getAttribute('data-teacher-role') || '';
var nameInput = document.getElementById('teacherName');
var idInput = document.getElementById('teacherId');
if (idInput) {
idInput.value = teacherId;
}
if (nameInput) {
nameInput.value = teacherName;
}
if (teacherRoleHidden) {
teacherRoleHidden.value = teacherRole;
}
if (teacherRoleDisplay) {
teacherRoleDisplay.value = teacherRole === 'teacher_assistant' ? 'Teaching Assistant' : 'Teacher';
}
}
var removeBtn = event.target.closest('.remove-assignment-btn');
if (removeBtn) {
event.preventDefault();
var teacherId = removeBtn.getAttribute('data-teacher-id');
var classSectionId = removeBtn.getAttribute('data-class-section-id');
var position = removeBtn.getAttribute('data-position');
var semester = removeBtn.getAttribute('data-semester') || '';
var schoolYear = removeBtn.getAttribute('data-school-year') || '';
var className = removeBtn.getAttribute('data-class-name') || 'this class';
var teacherName = removeBtn.getAttribute('data-teacher-name') || 'this teacher';
if (confirm('Remove ' + className + ' (' + position.toUpperCase() + ') from ' + teacherName + '?')) {
removeAssignment({
teacher_id: teacherId,
class_section_id: classSectionId,
position: position,
semester: semester,
school_year: schoolYear
});
}
}
});
if (assignButton && assignForm) {
assignButton.addEventListener('click', function (event) {
event.preventDefault();
if (!assignForm.checkValidity()) {
assignForm.reportValidity ? assignForm.reportValidity() : alert('Please fill out all required fields.');
return;
}
submitAssignment({
teacher_id: assignForm.teacher_id.value,
teacher_role: assignForm.teacher_role ? assignForm.teacher_role.value : (teacherRoleHidden ? teacherRoleHidden.value : ''),
class_section_id: assignForm.class_section_id.value
});
});
}
loadAssignments();
});
</script>
<?= $this->endSection() ?>