fix teacher pages and adopt the school year
Tests / PHPUnit (push) Successful in 1m14s

This commit is contained in:
root
2026-07-17 00:38:59 -04:00
parent 539d0eb220
commit 0d0a771dd1
7 changed files with 97 additions and 26 deletions
+1
View File
@@ -418,6 +418,7 @@ $routes->get('exam-drafts/files/final/(:segment)', 'View\FilesController::examDr
$routes->get('teacher/progress', 'ClassProgressController::history', ['filter' => 'auth:teacher,teacher_assistant']);
$routes->get('teacher/progress/submit', 'ClassProgressController::create', ['filter' => 'auth:teacher,teacher_assistant']); $routes->get('teacher/progress/submit', 'ClassProgressController::create', ['filter' => 'auth:teacher,teacher_assistant']);
$routes->post('teacher/progress/store', 'ClassProgressController::store', ['filter' => 'auth:teacher,teacher_assistant']); $routes->post('teacher/progress/store', 'ClassProgressController::store', ['filter' => 'auth:teacher,teacher_assistant']);
$routes->get('teacher/progress/history', 'ClassProgressController::history', ['filter' => 'auth:teacher,teacher_assistant']); $routes->get('teacher/progress/history', 'ClassProgressController::history', ['filter' => 'auth:teacher,teacher_assistant']);
+3 -3
View File
@@ -289,7 +289,7 @@ class ClassProgressController extends BaseController
} }
$row['status_label'] = self::STATUS_OPTIONS[$row['status']] ?? 'Unknown'; $row['status_label'] = self::STATUS_OPTIONS[$row['status']] ?? 'Unknown';
$weeklyReports = $this->reportModel $weeklyReportsQuery = $this->reportModel
->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name') ->select('class_progress_reports.*, cs.class_section_name, CONCAT(IFNULL(u.firstname, ""), " ", IFNULL(u.lastname, "")) AS teacher_name')
->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left') ->join('classSection cs', 'cs.class_section_id = class_progress_reports.class_section_id', 'left')
->join('users u', 'u.id = class_progress_reports.teacher_id', 'left') ->join('users u', 'u.id = class_progress_reports.teacher_id', 'left')
@@ -346,7 +346,7 @@ class ClassProgressController extends BaseController
throw new PageNotFoundException('Progress report not found.'); throw new PageNotFoundException('Progress report not found.');
} }
$weeklyReports = $this->reportModel $weeklyReportsQuery = $this->reportModel
->select('class_progress_reports.*') ->select('class_progress_reports.*')
->whereIn('teacher_id', $allowedTeacherIds) ->whereIn('teacher_id', $allowedTeacherIds)
->where('class_section_id', $row['class_section_id']) ->where('class_section_id', $row['class_section_id'])
@@ -513,7 +513,7 @@ class ClassProgressController extends BaseController
} }
} }
$weeklyReports = $this->reportModel $weeklyReportsQuery = $this->reportModel
->select('class_progress_reports.*') ->select('class_progress_reports.*')
->whereIn('teacher_id', $allowedTeacherIds) ->whereIn('teacher_id', $allowedTeacherIds)
->where('class_section_id', $classSectionId) ->where('class_section_id', $classSectionId)
@@ -3,6 +3,7 @@
namespace App\Controllers\View; namespace App\Controllers\View;
use App\Controllers\BaseController; use App\Controllers\BaseController;
use App\Exceptions\SchoolYear\SchoolYearWriteConflictException;
use App\Models\ClassSectionModel; use App\Models\ClassSectionModel;
use App\Models\InventoryItemModel; use App\Models\InventoryItemModel;
use App\Models\InventoryCategoryModel; use App\Models\InventoryCategoryModel;
@@ -713,6 +714,8 @@ class InventoryController extends BaseController
*/ */
public function teacherDistributeForm() public function teacherDistributeForm()
{ {
$schoolYearContext = $this->resolveSchoolYearContext();
// 1) Build teacher/class context (auth/role/no-class handled inside) // 1) Build teacher/class context (auth/role/no-class handled inside)
$ctx = $this->buildTeacherClassContext(); $ctx = $this->buildTeacherClassContext();
if ($ctx instanceof \CodeIgniter\HTTP\RedirectResponse) { if ($ctx instanceof \CodeIgniter\HTTP\RedirectResponse) {
@@ -858,11 +861,19 @@ class InventoryController extends BaseController
'schoolYear' => $ctx['schoolYear'], 'schoolYear' => $ctx['schoolYear'],
'semester' => $ctx['semester'], 'semester' => $ctx['semester'],
'isEditable' => ! $schoolYearContext->isReadonly(),
]); ]);
} }
public function teacherDistributeStore() public function teacherDistributeStore()
{ {
$schoolYearContext = $this->resolveSchoolYearContext();
try {
$this->assertSchoolYearWritable($schoolYearContext);
} catch (SchoolYearWriteConflictException $e) {
return redirect()->back()->withInput()->with('error', $e->getMessage());
}
$ctx = $this->buildTeacherClassContext(); $ctx = $this->buildTeacherClassContext();
if ($ctx instanceof \CodeIgniter\HTTP\RedirectResponse) { if ($ctx instanceof \CodeIgniter\HTTP\RedirectResponse) {
return $ctx; return $ctx;
+25 -8
View File
@@ -1,9 +1,18 @@
<?= $this->extend('layout/main_layout') ?> <?= $this->extend('layout/main_layout') ?>
<?= $this->section('content') ?> <?= $this->section('content') ?>
<?php
$isEditable = (bool) ($isEditable ?? true);
$disabledAttr = $isEditable ? '' : ' disabled';
?>
<div class="container my-4"> <div class="container my-4">
<h4 class="text-dark mb-3" style="font-family: Arial, sans-serif;">Distribute Books to Students</h4> <h4 class="text-dark mb-3" style="font-family: Arial, sans-serif;">Distribute Books to Students</h4>
<?= view('partials/flash_messages') ?> <?= view('partials/flash_messages') ?>
<?php if (!$isEditable): ?>
<div class="alert alert-info">
You are viewing <?= esc($schoolYear ?? 'a closed school year') ?>. Book distribution actions are read-only for closed years.
</div>
<?php endif; ?>
<!-- Filter bar (GET) — no class section shown --> <!-- Filter bar (GET) — no class section shown -->
<form id="filterForm" class="row g-3 mb-3" method="get" action="<?= site_url('inventory/books/distribute') ?>"> <form id="filterForm" class="row g-3 mb-3" method="get" action="<?= site_url('inventory/books/distribute') ?>">
@@ -43,7 +52,7 @@
</div> </div>
<!-- Submission form (POST) --> <!-- Submission form (POST) -->
<form method="post" action="<?= site_url('inventory/books/distribute') ?>"> <form method="post" action="<?= site_url('inventory/books/distribute') ?>" data-readonly="<?= $isEditable ? '0' : '1' ?>">
<?= csrf_field() ?> <?= csrf_field() ?>
<!-- Keep class section hidden --> <!-- Keep class section hidden -->
<input type="hidden" name="class_section_id" value="<?= esc($class_section_id) ?>"> <input type="hidden" name="class_section_id" value="<?= esc($class_section_id) ?>">
@@ -53,8 +62,8 @@
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<span>Students</span> <span>Students</span>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-info" id="checkAllBtn">Check All</button> <button type="button" class="btn btn-sm btn-info" id="checkAllBtn"<?= $disabledAttr ?>>Check All</button>
<button type="button" class="btn btn-sm btn-secondary" id="uncheckAllBtn">Uncheck All</button> <button type="button" class="btn btn-sm btn-secondary" id="uncheckAllBtn"<?= $disabledAttr ?>>Uncheck All</button>
</div> </div>
</div> </div>
@@ -87,7 +96,7 @@
class="form-check-input student-box" class="form-check-input student-box"
name="student_ids[]" name="student_ids[]"
value="<?= esc($sid) ?>" value="<?= esc($sid) ?>"
<?= $given ? 'checked' : '' ?>> <?= $given ? 'checked' : '' ?><?= $disabledAttr ?>>
</td> </td>
<td><?= esc($student['school_id'] ?? '-') ?></td> <td><?= esc($student['school_id'] ?? '-') ?></td>
<td><?= esc($student['firstname'] ?? '') ?></td> <td><?= esc($student['firstname'] ?? '') ?></td>
@@ -111,11 +120,11 @@
<div class="card-footer"> <div class="card-footer">
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Note (optional)</label> <label class="form-label">Note (optional)</label>
<textarea class="form-control" rows="2" name="note" placeholder="e.g., Distributed in class today"></textarea> <textarea class="form-control" rows="2" name="note" placeholder="e.g., Distributed in class today"<?= $disabledAttr ?>></textarea>
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="button" class="btn btn-success">Deduct & Save</button> <button type="button" class="btn btn-success"<?= $disabledAttr ?>>Deduct & Save</button>
<button type="button" class="btn btn-secondary" id="clearSelectionBtn">Cancel</button> <button type="button" class="btn btn-secondary" id="clearSelectionBtn"<?= $disabledAttr ?>>Cancel</button>
</div> </div>
</div> </div>
</div> </div>
@@ -129,15 +138,20 @@
<?= $this->section('scripts') ?> <?= $this->section('scripts') ?>
<script> <script>
const distributeForm = document.querySelector('form[data-readonly]');
const distributeReadonly = distributeForm?.getAttribute('data-readonly') === '1';
// Auto-submit filter when book changes // Auto-submit filter when book changes
document.getElementById('bookSelect')?.addEventListener('change', () => { document.getElementById('bookSelect')?.addEventListener('change', () => {
document.getElementById('filterForm').submit(); document.getElementById('filterForm').submit();
}); });
document.getElementById('checkAllBtn')?.addEventListener('click', () => { document.getElementById('checkAllBtn')?.addEventListener('click', () => {
if (distributeReadonly) return;
document.querySelectorAll('.student-box').forEach(cb => cb.checked = true); document.querySelectorAll('.student-box').forEach(cb => cb.checked = true);
}); });
document.getElementById('uncheckAllBtn')?.addEventListener('click', () => { document.getElementById('uncheckAllBtn')?.addEventListener('click', () => {
if (distributeReadonly) return;
document.querySelectorAll('.student-box').forEach(cb => cb.checked = false); document.querySelectorAll('.student-box').forEach(cb => cb.checked = false);
}); });
</script> </script>
@@ -149,17 +163,20 @@
}); });
document.getElementById('checkAllBtn')?.addEventListener('click', () => { document.getElementById('checkAllBtn')?.addEventListener('click', () => {
if (distributeReadonly) return;
document.querySelectorAll('.student-box').forEach(cb => cb.checked = true); document.querySelectorAll('.student-box').forEach(cb => cb.checked = true);
}); });
document.getElementById('uncheckAllBtn')?.addEventListener('click', () => { document.getElementById('uncheckAllBtn')?.addEventListener('click', () => {
if (distributeReadonly) return;
document.querySelectorAll('.student-box').forEach(cb => cb.checked = false); document.querySelectorAll('.student-box').forEach(cb => cb.checked = false);
}); });
// Cancel button clears all checkboxes // Cancel button clears all checkboxes
document.getElementById('clearSelectionBtn')?.addEventListener('click', () => { document.getElementById('clearSelectionBtn')?.addEventListener('click', () => {
if (distributeReadonly) return;
document.querySelectorAll('.student-box').forEach(cb => cb.checked = false); document.querySelectorAll('.student-box').forEach(cb => cb.checked = false);
}); });
</script> </script>
<?= $this->endSection() ?> <?= $this->endSection() ?>
+1
View File
@@ -28,6 +28,7 @@
<link rel="stylesheet" href="<?= base_url('assets/css/style.css') ?>"> <link rel="stylesheet" href="<?= base_url('assets/css/style.css') ?>">
<link rel="stylesheet" href="<?= base_url('assets/css/landing_page.css?v=1.1') ?>"> <link rel="stylesheet" href="<?= base_url('assets/css/landing_page.css?v=1.1') ?>">
<link rel="stylesheet" href="<?= base_url('assets/css/custom.css') ?>"> <link rel="stylesheet" href="<?= base_url('assets/css/custom.css') ?>">
<?= $this->renderSection('styles') ?>
<?php <?php
// Accent + menu palettes (shared with management layout) // Accent + menu palettes (shared with management layout)
+21 -5
View File
@@ -1,4 +1,8 @@
<?= $this->extend('layout/main_layout') ?> <?= $this->extend('layout/main_layout') ?>
<?= $this->section('styles') ?>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.10/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.10/css/dataTables.bootstrap5.min.css">
<?= $this->endSection() ?>
<?= $this->section('content') ?> <?= $this->section('content') ?>
<style> <style>
@@ -131,10 +135,6 @@
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="9" class="text-center">No students found in this class.</td>
</tr>
<?php endif; ?> <?php endif; ?>
</tbody> </tbody>
</table> </table>
@@ -154,17 +154,33 @@
<?= $this->endSection() ?> <?= $this->endSection() ?>
<?= $this->section('scripts') ?> <?= $this->section('scripts') ?>
<script src="https://cdn.datatables.net/1.13.10/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.10/js/dataTables.bootstrap5.min.js"></script>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
if (!(window.jQuery && jQuery.fn && jQuery.fn.DataTable)) {
return;
}
<?php if (!empty($classes)): ?> <?php if (!empty($classes)): ?>
<?php foreach ($classes as $index => $class): ?> <?php foreach ($classes as $index => $class): ?>
$('#classTable<?= $index ?>').DataTable({ const table<?= $index ?> = $('#classTable<?= $index ?>');
const expectedColumns<?= $index ?> = table<?= $index ?>.find('thead th').length;
const hasInvalidRows<?= $index ?> = table<?= $index ?>.find('tbody tr').toArray().some(function(row) {
return $(row).children('td, th').length !== expectedColumns<?= $index ?>;
});
if (hasInvalidRows<?= $index ?>) {
return;
}
table<?= $index ?>.DataTable({
// Keep columns visible and avoid squeezing // Keep columns visible and avoid squeezing
responsive: false, // prevent column hiding that can make table look small responsive: false, // prevent column hiding that can make table look small
autoWidth: true, // let DT calculate widths, we'll still force 100% via CSS autoWidth: true, // let DT calculate widths, we'll still force 100% via CSS
scrollX: true, // allow horizontal scroll when needed scrollX: true, // allow horizontal scroll when needed
pageLength: 25, pageLength: 25,
lengthChange: false, lengthChange: false,
language: {
emptyTable: 'No students found in this class.'
},
columnDefs: [ columnDefs: [
{ targets: '_all', className: 'text-nowrap' } // avoid wrapping labels { targets: '_all', className: 'text-nowrap' } // avoid wrapping labels
] ]
+35 -10
View File
@@ -1,5 +1,10 @@
<?= $this->extend('layout/main_layout') ?> <?= $this->extend('layout/main_layout') ?>
<?= $this->section('styles') ?>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.10/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.10/css/dataTables.bootstrap5.min.css">
<?= $this->endSection() ?>
<?= $this->section('content') ?> <?= $this->section('content') ?>
<div class="container-xxl py-5"> <div class="container-xxl py-5">
<div class="container"> <div class="container">
@@ -282,6 +287,8 @@
<?= $this->endSection() ?> <?= $this->endSection() ?>
<?= $this->section('scripts') ?> <?= $this->section('scripts') ?>
<script src="https://cdn.datatables.net/1.13.10/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.10/js/dataTables.bootstrap5.min.js"></script>
<script> <script>
document.addEventListener("DOMContentLoaded",()=>{ document.addEventListener("DOMContentLoaded",()=>{
const form=document.getElementById("attendanceForm"); const form=document.getElementById("attendanceForm");
@@ -316,16 +323,34 @@ document.addEventListener("DOMContentLoaded",()=>{
<script> <script>
$(function(){ $(function(){
$('#teachersTable').DataTable({ if (!(window.jQuery && jQuery.fn && jQuery.fn.DataTable)) {
pageLength:100, return;
order:[], }
columnDefs:[{targets:[0,3,4,5,6],className:'text-center'}]
}); function hasValidRows($table) {
$('#studentsTable').DataTable({ const expectedColumns = $table.find('thead th').length;
pageLength:100, return !$table.find('tbody tr').toArray().some(function(row) {
order:[], return $(row).children('td, th').length !== expectedColumns;
columnDefs:[{targets:[0,4,5,6,7],className:'text-center'}] });
}); }
const $teachersTable = $('#teachersTable');
if ($teachersTable.length && hasValidRows($teachersTable)) {
$teachersTable.DataTable({
pageLength:100,
order:[],
columnDefs:[{targets:[0,3,4,5,6],className:'text-center'}]
});
}
const $studentsTable = $('#studentsTable');
if ($studentsTable.length && hasValidRows($studentsTable)) {
$studentsTable.DataTable({
pageLength:100,
order:[],
columnDefs:[{targets:[0,4,5,6,7],className:'text-center'}]
});
}
}); });
</script> </script>
<?= $this->endSection() ?> <?= $this->endSection() ?>