From ba87598d3a2b7f347981f9c3b5bd375627f2937a Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Jul 2026 23:03:51 -0400 Subject: [PATCH] fix pages and add distribution system --- app/Controllers/PrintRequests.php | 46 +- .../View/AdministratorController.php | 181 +++--- app/Controllers/View/AssignmentController.php | 90 +++ app/Controllers/View/EventController.php | 14 +- .../View/ExtraChargesController.php | 76 +-- .../View/NotificationsController.php | 31 +- app/Controllers/View/ParentController.php | 50 +- .../View/RolePermissionController.php | 26 + app/Controllers/View/StaffController.php | 31 +- app/Controllers/View/StudentController.php | 554 +++++++++++++----- app/Controllers/View/TeacherController.php | 44 +- app/Controllers/View/UserController.php | 29 + ...CreateStudentSectionDistributionDrafts.php | 4 +- ...StudentSectionDistributionDraftIndexes.php | 54 ++ app/Models/StudentModel.php | 34 +- app/Views/administrator/calendar_view.php | 28 - .../administrator/emergency_contact/index.php | 1 - app/Views/administrator/parent_profile.php | 1 - .../sections_auto_distribute.php | 129 ++-- .../student_class_assignment.php | 28 +- .../teacher_class_assignment.php | 19 - app/Views/discounts/list.php | 1 - .../enroll_withdraw/enrollment_withdrawal.php | 37 +- app/Views/enroll_withdraw/new-students.php | 1 - app/Views/expenses/index.php | 1 - app/Views/flags/flags_management.php | 1 - app/Views/notifications/list_active.php | 155 ++--- app/Views/notifications/list_deleted.php | 1 - app/Views/partials/school_year_selector.php | 17 +- app/Views/payment/extra_charges.php | 58 +- app/Views/payment/unpaid_parents.php | 2 - app/Views/print_requests/admin_index.php | 8 +- app/Views/reimbursements/index.php | 23 - app/Views/rolepermission/assign_role.php | 1 - app/Views/staff/index.php | 1 - app/Views/user/login_activity.php | 1 - app/Views/user/user_list.php | 1 - distribution_classes.md | 241 ++++++++ 38 files changed, 1362 insertions(+), 658 deletions(-) create mode 100644 app/Database/Migrations/2026-07-16-000200_FixStudentSectionDistributionDraftIndexes.php create mode 100644 distribution_classes.md diff --git a/app/Controllers/PrintRequests.php b/app/Controllers/PrintRequests.php index 4979191..cf2bc15 100644 --- a/app/Controllers/PrintRequests.php +++ b/app/Controllers/PrintRequests.php @@ -73,30 +73,37 @@ class PrintRequests extends BaseController public function admin_index() { - $db = \Config\Database::connect(); - $query = $db->query(" - SELECT - pr.*, - u.firstname, - u.lastname, + $context = $this->resolveSchoolYearContext(); + $schoolYear = $context->yearName(); + + $printRequestsQuery = $this->printRequestModel + ->select(' + print_requests.*, + u.firstname, + u.lastname, cs.class_section_name, admins.firstname AS admin_firstname, admins.lastname AS admin_lastname - FROM print_requests pr - LEFT JOIN users u ON u.id = pr.teacher_id - LEFT JOIN classSection cs ON cs.class_section_id = pr.class_id - LEFT JOIN users admins ON admins.id = pr.admin_id - ORDER BY + ') + ->join('users u', 'u.id = print_requests.teacher_id', 'left') + ->join('classSection cs', 'cs.class_section_id = print_requests.class_id', 'left') + ->join('users admins', 'admins.id = print_requests.admin_id', 'left'); + + $this->applyPrintRequestSchoolYearScope($printRequestsQuery, $schoolYear); + + $data['print_requests'] = $printRequestsQuery + ->orderBy(" CASE - WHEN pr.status = 'not_assigned' THEN 1 - WHEN pr.status = 'assigned' THEN 2 - WHEN pr.status = 'done' THEN 3 - WHEN pr.status = 'delivered' THEN 4 + WHEN print_requests.status = 'not_assigned' THEN 1 + WHEN print_requests.status = 'assigned' THEN 2 + WHEN print_requests.status = 'done' THEN 3 + WHEN print_requests.status = 'delivered' THEN 4 ELSE 5 - END ASC, - pr.required_by ASC - "); - $data['print_requests'] = $query->getResultArray(); + END + ", 'ASC', false) + ->orderBy('print_requests.required_by', 'ASC') + ->findAll(); + $data['isSchoolYearReadonly'] = $context->isReadonly(); return view('print_requests/admin_index', $data); } @@ -156,6 +163,7 @@ class PrintRequests extends BaseController // Case 1: Admin status update if ($this->request->getPost('status')) { + $this->assertSchoolYearWritable($this->resolveSchoolYearContext()); $user_id = session()->get('user_id'); $current_status = $request['status']; $new_status = $this->request->getPost('status'); diff --git a/app/Controllers/View/AdministratorController.php b/app/Controllers/View/AdministratorController.php index 1f37e00..56664ba 100644 --- a/app/Controllers/View/AdministratorController.php +++ b/app/Controllers/View/AdministratorController.php @@ -19,6 +19,7 @@ use App\Models\AdminNotificationSubjectModel; use Doctrine\DBAL\Configuration; use App\Services\FeeCalculationService; use App\Models\StudentClassModel; +use App\Models\StudentSectionDistributionDraftModel; use App\Controllers\View\EmailController; use App\Controllers\View\InvoiceController; use App\Models\StaffAttendanceModel; @@ -2159,6 +2160,10 @@ class AdministratorController extends BaseController public function parentProfiles() { + if ($redirect = $this->redirectWithoutLegacyTermFilters('administrator/parent_profiles')) { + return $redirect; + } + // Fetch all users with their roles in one go $allUsers = $this->userModel->findAll(); $parents = []; @@ -2215,6 +2220,27 @@ class AdministratorController extends BaseController return view('administrator/parent_profile', ['parents' => $parents]); } + private function redirectWithoutLegacyTermFilters(string $route): ?\CodeIgniter\HTTP\RedirectResponse + { + $legacyTermKeys = ['school_year', 'schoolYear', 'year', 'semester']; + $query = $this->request->getGet(); + $hasLegacyTermFilter = false; + + foreach ($legacyTermKeys as $key) { + if (array_key_exists($key, $query)) { + unset($query[$key]); + $hasLegacyTermFilter = true; + } + } + + if (!$hasLegacyTermFilter) { + return null; + } + + $target = site_url($route) . (!empty($query) ? '?' . http_build_query($query) : ''); + return redirect()->to($target); + } + public function manageUsers() {// Fetch all users $users = $this->userModel->findAll(); @@ -2298,8 +2324,8 @@ class AdministratorController extends BaseController public function showEnrollmentWithdrawalPage() { try { - $schoolYears = $this->availableSchoolYears(); - $selectedYear = $this->selectedEnrollmentSchoolYear($schoolYears); + $schoolYearContext = $this->resolveSchoolYearContext(); + $selectedYear = $schoolYearContext->yearName(); $students = $this->studentModel->getStudentsWithClassAndEnrollment($selectedYear); @@ -2399,11 +2425,10 @@ class AdministratorController extends BaseController return view('enroll_withdraw/enrollment_withdrawal', [ 'students' => $students, 'classes' => $classes, // <-- used by the modal - - - - - -
- - - -
-
- - Reset -
- - getFlashdata('success')): ?>
getFlashdata('success') ?> diff --git a/app/Views/administrator/emergency_contact/index.php b/app/Views/administrator/emergency_contact/index.php index 9b9d94f..9377d29 100644 --- a/app/Views/administrator/emergency_contact/index.php +++ b/app/Views/administrator/emergency_contact/index.php @@ -4,7 +4,6 @@

Emergency Contact Information

- include('partials/academic_filter') ?>
diff --git a/app/Views/administrator/parent_profile.php b/app/Views/administrator/parent_profile.php index b1cff20..8b473d6 100644 --- a/app/Views/administrator/parent_profile.php +++ b/app/Views/administrator/parent_profile.php @@ -4,7 +4,6 @@

Parent Profiles

- include('partials/academic_filter') ?>
diff --git a/app/Views/administrator/sections_auto_distribute.php b/app/Views/administrator/sections_auto_distribute.php index 9feb9f0..39f0f25 100644 --- a/app/Views/administrator/sections_auto_distribute.php +++ b/app/Views/administrator/sections_auto_distribute.php @@ -5,37 +5,26 @@

Auto-Distribute Students into Sections

-
-
-
-
- - - -
-
-
-
- - +
+ +
-
+
+ + +
+
+ + +
+
-
+
@@ -46,7 +35,7 @@
- + @@ -73,17 +62,18 @@ const tblBody = document.getElementById('tblBody'); const tblHeader = document.getElementById('tblHeader'); - const perInput = document.getElementById('globalPer'); + const sectionCountInput = document.getElementById('sectionCount'); + const minInput = document.getElementById('minStudents'); + const maxInput = document.getElementById('maxStudents'); const msgEl = document.getElementById('pageMsg'); const refreshBtn= document.getElementById('refreshTotalsBtn'); let headerSections = []; // list of generated section names as columns let rowIndexByClassId = {}; // mapping to locate rows - function calcNeeded(total) { - const per = parseInt(perInput.value || '0', 10); - if (!per || per <= 0) return ''; - return Math.ceil(total / per); + function selectedSectionCount() { + const count = parseInt(sectionCountInput.value || '0', 10); + return count > 0 ? count : ''; } function ensureSectionColumns(sectionNames) { @@ -129,7 +119,7 @@ const tdNeed = document.createElement('td'); tdNeed.className = 'text-end need-cell'; - tdNeed.textContent = calcNeeded(r.total); + tdNeed.textContent = selectedSectionCount(); tr.appendChild(tdNeed); const tdAct = document.createElement('td'); @@ -143,24 +133,36 @@ tblBody.appendChild(tr); rowIndexByClassId[r.class_id] = tr; }); + + rows.forEach(function(r){ + if (Array.isArray(r.sections) && r.sections.length) { + renderSectionsForRow(r.class_section_name || '', r.sections); + } + }); } function updateNeeds() { document.querySelectorAll('#tblBody tr').forEach(function(tr){ - const total = parseInt(tr.children[1].textContent || '0', 10); const needCell = tr.querySelector('.need-cell'); - if (needCell) needCell.textContent = calcNeeded(total); + if (needCell) needCell.textContent = selectedSectionCount(); }); } function runDistribution(baseSectionId, baseName) { - const per = parseInt(perInput.value || '0', 10); - if (!per || per <= 0) { msgEl.textContent = 'Enter students per section first.'; return; } + const sectionCount = parseInt(sectionCountInput.value || '0', 10); + const minStudents = parseInt(minInput.value || '0', 10); + const maxStudents = parseInt(maxInput.value || '0', 10); + if (!sectionCount || sectionCount <= 0 || !minStudents || minStudents <= 0) { + msgEl.textContent = 'Enter number of sections and minimum students first.'; + return; + } msgEl.textContent = 'Distributing ' + (baseName || '') + '...'; const fd = new FormData(); fd.append('class_section_id', String(baseSectionId)); - fd.append('students_per_section', String(per)); + fd.append('section_count', String(sectionCount)); + fd.append('min_students_per_section', String(minStudents)); + if (maxStudents > 0) fd.append('max_students_per_section', String(maxStudents)); fd.append('school_year', selectedYear); // CSRF const csrfNameEl = document.getElementById('csrfName'); @@ -184,24 +186,39 @@ msgEl.textContent = res && res.message ? res.message : 'Completed.'; - // Collect section names, then ensure header columns - const names = res.sections.map(s => s.class_section_name).filter(Boolean); - ensureSectionColumns(names); - - // Fill row cells for this class - const tr = Array.from(document.querySelectorAll('#tblBody tr')).find(function(_tr){ - return (_tr.children[0].textContent || '') === (baseName || ''); - }); - if (!tr) return; - - res.sections.forEach(function(s){ - const td = tr.querySelector('td[data-section="'+ s.class_section_name +'"]'); - if (td) td.textContent = (s.total ?? 0) + ' (M'+ (s.male ?? 0) + '/F'+ (s.female ?? 0) +')'; - }); + renderSectionsForRow(baseName || '', res.sections); }) .catch(() => { msgEl.textContent = 'Failed to distribute. Please try again.'; }); } + function renderSectionsForRow(baseName, sections) { + const names = sections.map(s => s.class_section_name).filter(Boolean); + ensureSectionColumns(names); + + const tr = Array.from(document.querySelectorAll('#tblBody tr')).find(function(_tr){ + return (_tr.children[0].textContent || '') === (baseName || ''); + }); + if (!tr) return; + + sections.forEach(function(s){ + const td = Array.from(tr.querySelectorAll('td[data-section]')).find(cell => cell.dataset.section === s.class_section_name); + if (!td) return; + + const studentNames = Array.isArray(s.student_names) ? s.student_names : []; + td.innerHTML = ''; + const count = document.createElement('div'); + count.className = 'fw-semibold small mb-1'; + count.textContent = (s.total ?? studentNames.length) + ' students'; + td.appendChild(count); + + const list = document.createElement('div'); + list.className = 'small'; + list.style.whiteSpace = 'normal'; + list.textContent = studentNames.length ? studentNames.join(', ') : 'No students assigned'; + td.appendChild(list); + }); + } + function loadTotals() { msgEl.textContent = 'Loading totals...'; fetch(totalsUrl, { headers: { 'X-Requested-With': 'XMLHttpRequest' }}) @@ -215,10 +232,14 @@ .catch(() => { msgEl.textContent = 'Failed to load totals.'; }); } - refreshBtn.addEventListener('click', function(){ updateNeeds(); }); + refreshBtn.addEventListener('click', function(){ loadTotals(); }); document.getElementById('generateAllBtn').addEventListener('click', async function(){ - const per = parseInt(perInput.value || '0', 10); - if (!per || per <= 0) { msgEl.textContent = 'Enter students per section first.'; return; } + const sectionCount = parseInt(sectionCountInput.value || '0', 10); + const minStudents = parseInt(minInput.value || '0', 10); + if (!sectionCount || sectionCount <= 0 || !minStudents || minStudents <= 0) { + msgEl.textContent = 'Enter number of sections and minimum students first.'; + return; + } // Collect base sections from rows const rows = Array.from(document.querySelectorAll('#tblBody tr')) .map(tr => ({ id: tr.dataset.classSectionId, name: tr.dataset.className })) @@ -231,7 +252,7 @@ } msgEl.textContent = 'All distributions completed.'; }); - perInput.addEventListener('input', function(){ updateNeeds(); }); + sectionCountInput.addEventListener('input', function(){ updateNeeds(); }); loadTotals(); })(); diff --git a/app/Views/administrator/student_class_assignment.php b/app/Views/administrator/student_class_assignment.php index 9e99a2a..356282e 100644 --- a/app/Views/administrator/student_class_assignment.php +++ b/app/Views/administrator/student_class_assignment.php @@ -5,29 +5,6 @@

Student Class Assignment

-
-
-
- - - - - - -
Read-only (Past Year)
@@ -59,10 +36,7 @@
-
- - -
+
diff --git a/app/Views/administrator/teacher_class_assignment.php b/app/Views/administrator/teacher_class_assignment.php index 63beaec..3a2ae56 100644 --- a/app/Views/administrator/teacher_class_assignment.php +++ b/app/Views/administrator/teacher_class_assignment.php @@ -17,25 +17,6 @@ Add/Edit Configuration (key: school_year).
-
- -
- - - - - Read-only (Past Year) - -
diff --git a/app/Views/discounts/list.php b/app/Views/discounts/list.php index cb00043..ca96af9 100644 --- a/app/Views/discounts/list.php +++ b/app/Views/discounts/list.php @@ -3,7 +3,6 @@

Discounts

- include('partials/academic_filter') ?>
Create New Voucher diff --git a/app/Views/enroll_withdraw/enrollment_withdrawal.php b/app/Views/enroll_withdraw/enrollment_withdrawal.php index 00a0d6b..562a53e 100644 --- a/app/Views/enroll_withdraw/enrollment_withdrawal.php +++ b/app/Views/enroll_withdraw/enrollment_withdrawal.php @@ -12,34 +12,11 @@
-
-
-
-
- - - - - - + +
+ Read-only (Past Year)
- -
Read-only (Past Year)
- -
+ getFlashdata('success')): ?>
@@ -161,7 +138,7 @@
Class TotalSections NeededSections Actions
- name="enrollment_status[]" class="form-control enrollment-status" data-student-id="" @@ -182,7 +159,7 @@ - data-student-id="" data-parent-id=""> @@ -212,7 +189,7 @@
- Processing… diff --git a/app/Views/enroll_withdraw/new-students.php b/app/Views/enroll_withdraw/new-students.php index f3c6d60..7121ca2 100644 --- a/app/Views/enroll_withdraw/new-students.php +++ b/app/Views/enroll_withdraw/new-students.php @@ -9,7 +9,6 @@ - include('partials/academic_filter') ?> getFlashdata('success')): ?> diff --git a/app/Views/expenses/index.php b/app/Views/expenses/index.php index ad98fc7..f880952 100644 --- a/app/Views/expenses/index.php +++ b/app/Views/expenses/index.php @@ -3,7 +3,6 @@

Expense / Purchase

- include('partials/academic_filter') ?> diff --git a/app/Views/flags/flags_management.php b/app/Views/flags/flags_management.php index 3c5877a..8edf314 100644 --- a/app/Views/flags/flags_management.php +++ b/app/Views/flags/flags_management.php @@ -2,7 +2,6 @@ section('content') ?>

Incidents Management

- include('partials/academic_filter') ?> getFlashdata('success')): ?>
diff --git a/app/Views/notifications/list_active.php b/app/Views/notifications/list_active.php index 75d3cff..0938c57 100644 --- a/app/Views/notifications/list_active.php +++ b/app/Views/notifications/list_active.php @@ -11,7 +11,6 @@ data-notifications-endpoint="" data-default-target-group="">

Active Notifications

- include('partials/academic_filter') ?> getFlashdata('success')): ?>
getFlashdata('success')) ?>
@@ -61,53 +60,6 @@ section('scripts') ?>