This commit is contained in:
@@ -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']);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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() ?>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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() ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user