From bfa343b69f0d8cadf5e4ac3472dbcd291e348723 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 20 Aug 2026 12:59:59 -0400 Subject: [PATCH] fix is_active student flag and apply it in all pages --- app/Config/Autoload.php | 2 +- .../View/AdministratorController.php | 27 ++++ app/Controllers/View/AttendanceController.php | 30 +++- .../View/CompetitionScoresController.php | 11 +- app/Controllers/View/FinalController.php | 1 + app/Controllers/View/GradingController.php | 95 +++++++++++-- app/Controllers/View/HomeworkController.php | 4 +- app/Controllers/View/MidtermController.php | 5 +- .../View/ParticipationController.php | 4 +- app/Controllers/View/ProjectController.php | 4 +- app/Controllers/View/QuizController.php | 4 +- app/Controllers/View/ScoreController.php | 4 +- app/Controllers/View/StudentController.php | 24 +++- app/Controllers/View/TeacherController.php | 3 + app/Helpers/student_status_helper.php | 84 +++++++++++ app/Models/StudentClassModel.php | 97 +++++++++++-- app/Models/StudentModel.php | 130 +++++++++++++++--- app/Views/admin/certificates/audit_log.php | 5 +- app/Views/admin/certificates/index.php | 7 +- .../admin/competition_winners/scores.php | 5 +- .../admin/competition_winners/winners.php | 5 +- app/Views/admin/student_score_card.php | 5 +- .../administrator/administratordashboard.php | 10 +- app/Views/administrator/class_assignment.php | 2 + app/Views/administrator/daily_attendance.php | 105 ++++++++++---- .../daily_attendance_analysis.php | 5 +- .../administrator/emergency_contact/index.php | 2 +- .../enrollment_admin_dashboard.php | 17 ++- .../administrator/events/event_charges.php | 1 + .../administrator/financial_aid_review.php | 5 +- .../administrator/grading_management.php | 5 +- app/Views/administrator/late_slip_logs.php | 5 +- app/Views/administrator/search_results.php | 5 +- .../student_class_assignment.php | 1 + app/Views/administrator/student_profiles.php | 1 + app/Views/administrator/tuition_forecast.php | 5 +- app/Views/attendance/attendance_tracking.php | 5 +- app/Views/attendance/early_dismissals.php | 5 +- app/Views/attendance/parent_reports.php | 5 +- app/Views/attendance/violations_notified.php | 1 + app/Views/attendance/violations_pending.php | 1 + app/Views/grading/all_decisions.php | 15 +- app/Views/grading/below_sixty.php | 33 +++-- app/Views/grading/below_sixty_decisions.php | 46 ++++--- app/Views/grading/comments.php | 48 ++++--- app/Views/grading/final.php | 13 +- app/Views/grading/grading_main.php | 9 +- app/Views/grading/homework.php | 7 +- app/Views/grading/midterm.php | 13 +- app/Views/grading/participation.php | 13 +- app/Views/grading/placement.php | 23 ++-- app/Views/grading/placement_batch.php | 25 ++-- app/Views/grading/placement_index.php | 39 ++++-- app/Views/grading/project.php | 7 +- app/Views/grading/quiz.php | 7 +- app/Views/grading/test.php | 14 +- public/assets/js/proofread.js | 6 +- 57 files changed, 820 insertions(+), 240 deletions(-) create mode 100644 app/Helpers/student_status_helper.php diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index e4376d5..54bc601 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -98,6 +98,6 @@ class Autoload extends AutoloadConfig * * @var list */ - public $helpers = ['url', 'form', 'pbkdf2', 'document', 'time', 'api', 'global_config']; + public $helpers = ['url', 'form', 'pbkdf2', 'document', 'time', 'api', 'global_config', 'student_status']; } diff --git a/app/Controllers/View/AdministratorController.php b/app/Controllers/View/AdministratorController.php index 8742bfd..07bae3d 100644 --- a/app/Controllers/View/AdministratorController.php +++ b/app/Controllers/View/AdministratorController.php @@ -2135,6 +2135,31 @@ class AdministratorController extends BaseController ->getResultArray(); } + $enrollmentStatusByStudentId = []; + $studentIds = array_values(array_unique(array_filter(array_map( + static fn (array $row): int => (int) ($row['id'] ?? 0), + $students + )))); + if ($selectedYear !== '' && !empty($studentIds)) { + $enrollmentRows = $db->table('enrollments') + ->select('student_id, enrollment_status') + ->whereIn('student_id', $studentIds) + ->where('school_year', $selectedYear) + ->orderBy('student_id', 'ASC') + ->orderBy('updated_at', 'DESC') + ->orderBy('enrollment_date', 'DESC') + ->orderBy('id', 'DESC') + ->get() + ->getResultArray(); + + foreach ($enrollmentRows as $enrollmentRow) { + $studentId = (int) ($enrollmentRow['student_id'] ?? 0); + if ($studentId > 0 && !isset($enrollmentStatusByStudentId[$studentId])) { + $enrollmentStatusByStudentId[$studentId] = (string) ($enrollmentRow['enrollment_status'] ?? ''); + } + } + } + // === Inject current-year class_section_name from student_class and replace grade === foreach ($students as $i => $row) { $sid = (int) ($row['id'] ?? 0); @@ -2142,9 +2167,11 @@ class AdministratorController extends BaseController $classSectionName = (string) ($this->studentClassModel->getClassSectionNameByStudentId($sid, $selectedYear) ?? ''); $students[$i]['class_section_name'] = $classSectionName; + $students[$i]['enrollment_status'] = $enrollmentStatusByStudentId[$sid] ?? ''; } else { // Keep keys consistent even if id missing $students[$i]['class_section_name'] = ''; + $students[$i]['enrollment_status'] = ''; } $studentYear = trim((string) ($row['school_year'] ?? '')); diff --git a/app/Controllers/View/AttendanceController.php b/app/Controllers/View/AttendanceController.php index fb77a39..ff86524 100644 --- a/app/Controllers/View/AttendanceController.php +++ b/app/Controllers/View/AttendanceController.php @@ -267,9 +267,9 @@ public function showUpdateAttendanceForm() $currentSunday = $sundayDates[2]; // Students roster for this section/year/semester (try section code, then PK fallback) - $students = $this->studentModel->getByClassAndYear($class_section_id, $schoolYear, $semester); + $students = $this->studentModel->getActiveByClassAndYear($class_section_id, $schoolYear, $semester); if (empty($students) && $classSectionPk > 0 && $classSectionPk !== $class_section_id) { - $students = $this->studentModel->getByClassAndYear($classSectionPk, $schoolYear, $semester); + $students = $this->studentModel->getActiveByClassAndYear($classSectionPk, $schoolYear, $semester); } // Build student attendance rows @@ -598,11 +598,12 @@ public function showUpdateAttendanceForm() $seenStudentIds[$studentId] = true; $student = $this->studentModel - ->select('id, firstname, lastname, school_id') + ->select('id, firstname, lastname, school_id, is_active') ->where('id', $studentId) - ->where('is_active', 1) ->first(); if (!$student) continue; + $student['enrollment_status'] = $sc['enrollment_status'] ?? ''; + $student['is_withdrawn'] = (int)($sc['is_withdrawn'] ?? 0); $studentsBySection[$secCode][] = $student; $hasRoster = true; @@ -1027,9 +1028,11 @@ public function showUpdateAttendanceForm() continue; } $student = $this->studentModel - ->select('id, firstname, lastname, school_id') + ->select('id, firstname, lastname, school_id, is_active') ->find($studentId); if (!$student) continue; + $student['enrollment_status'] = $sc['enrollment_status'] ?? ''; + $student['is_withdrawn'] = (int)($sc['is_withdrawn'] ?? 0); $studentsBySection[$secCode][] = $student; $hasRoster = true; @@ -1603,7 +1606,7 @@ public function showUpdateAttendanceForm() // optional score updates (never bubble) try { - $studentUserInfo = $this->studentModel->getStudentInfoByClassSectionId( + $studentUserInfo = $this->studentModel->getActiveStudentInfoByClassSectionId( $classSectionId, $semester, $schoolYear @@ -1722,6 +1725,19 @@ public function showUpdateAttendanceForm() $semester = (string)($this->semester ?? ''); $schoolYear = (string)($this->schoolYear ?? ''); + $activeStudents = $this->studentModel->getActiveByClassAndYear($classSectionId, $schoolYear, $semester); + $activeStudentIds = array_fill_keys(array_map(static fn(array $row): int => (int)($row['id'] ?? 0), $activeStudents), true); + $attendanceData = array_values(array_filter($attendanceData, static function ($row) use ($activeStudentIds): bool { + $sid = (int)($row['student_id'] ?? 0); + return $sid > 0 && isset($activeStudentIds[$sid]); + })); + + if (empty($attendanceData)) { + return redirect()->to('/teacher/showupdate_attendance?class_section_id=' . $classSectionId) + ->with('status', 'error') + ->with('message', 'No active student attendance data provided.'); + } + // Existing attendance rows for today (used to lock parent/admin submissions) $existingRows = $this->attendanceDataModel ->where('class_section_id', $classSectionId) @@ -2027,7 +2043,7 @@ public function showUpdateAttendanceForm() } try { - $studentUserInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId, $this->semester, $this->schoolYear); + $studentUserInfo = $this->studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $this->semester, $this->schoolYear); $this->semesterScoreService->updateScoresForStudents($studentUserInfo); } catch (\Throwable $e) { // ignore diff --git a/app/Controllers/View/CompetitionScoresController.php b/app/Controllers/View/CompetitionScoresController.php index 4a99a40..d3b6f83 100644 --- a/app/Controllers/View/CompetitionScoresController.php +++ b/app/Controllers/View/CompetitionScoresController.php @@ -386,12 +386,14 @@ class CompetitionScoresController extends BaseController return 0; } - $builder = $this->db->table($this->classStudentTable) + $builder = $this->db->table($this->classStudentTable . ' cs') ->select('COUNT(*) AS total') - ->where('class_section_id', $classSectionId); + ->join('students s', 's.id = cs.student_id', 'inner') + ->where('cs.class_section_id', $classSectionId) + ->where('s.is_active', 1); if ($schoolYear && $this->db->fieldExists('school_year', $this->classStudentTable)) { - $builder->where('school_year', $schoolYear); + $builder->where('cs.school_year', $schoolYear); } $row = $builder->get()->getRowArray(); @@ -407,7 +409,8 @@ class CompetitionScoresController extends BaseController $builder = $this->db->table('students s') ->select('s.id, s.school_id, s.firstname, s.lastname') ->join($this->classStudentTable . ' cs', 'cs.student_id = s.id', 'inner') - ->where('cs.class_section_id', $classSectionId); + ->where('cs.class_section_id', $classSectionId) + ->where('s.is_active', 1); $hasSchoolYear = $this->db->fieldExists('school_year', $this->classStudentTable); if ($hasSchoolYear && !empty($competition['school_year'])) { diff --git a/app/Controllers/View/FinalController.php b/app/Controllers/View/FinalController.php index c96d094..0454d4c 100644 --- a/app/Controllers/View/FinalController.php +++ b/app/Controllers/View/FinalController.php @@ -131,6 +131,7 @@ class FinalController extends BaseController ) ->join('(' . $latestFinalSub . ') fl', 'fl.student_id = s.id', 'left', false) ->join('final_exam fe', 'fe.id = fl.max_id', 'left') + ->where('s.is_active', 1) ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC'); diff --git a/app/Controllers/View/GradingController.php b/app/Controllers/View/GradingController.php index bc66a27..b9866cc 100644 --- a/app/Controllers/View/GradingController.php +++ b/app/Controllers/View/GradingController.php @@ -521,6 +521,9 @@ class GradingController extends BaseController 'school_id' => $r['school_id'] ?? null, 'firstname' => $r['firstname'] ?? null, 'lastname' => $r['lastname'] ?? null, + 'is_active' => (int)($r['is_active'] ?? 1), + 'enrollment_status' => $r['enrollment_status'] ?? '', + 'is_withdrawn' => (int)($r['is_withdrawn'] ?? 0), 'class_id' => $classId, 'ptap' => is_null($ptapScore) ? null : round((float) $ptapScore, 2), 'semester_score' => is_null($semesterScore) ? null : round((float) $semesterScore, 2), @@ -997,11 +1000,16 @@ class GradingController extends BaseController private function fetchActiveStudentsWithSection(string $schoolYear): array { return $this->db->table('students s') - ->select('s.id AS student_id, s.school_id, s.firstname, s.lastname, sc.class_section_id, cs.class_section_name, c.class_name') + ->select('s.id AS student_id, s.school_id, s.firstname, s.lastname, s.is_active, sc.class_section_id, cs.class_section_name, c.class_name, e.enrollment_status, e.is_withdrawn') ->join('student_class sc', 'sc.student_id = s.id AND sc.school_year = ' . $this->db->escape($schoolYear), 'left') + ->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left') ->join('`classSection` cs', 'cs.class_section_id = sc.class_section_id', 'left') ->join('classes c', 'c.id = cs.class_id', 'left') - ->where('s.is_active', 1) + ->groupStart() + ->where('s.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd() ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC') ->get() @@ -1032,13 +1040,18 @@ class GradingController extends BaseController } $rows = $this->db->table('placement_scores ps') - ->select('ps.batch_id, ps.score, s.school_id, s.firstname, s.lastname, cs.class_section_name, c.class_name') + ->select('ps.batch_id, ps.student_id, ps.score, s.school_id, s.firstname, s.lastname, s.is_active, e.enrollment_status, e.is_withdrawn, cs.class_section_name, c.class_name') ->join('students s', 's.id = ps.student_id', 'inner') ->join('student_class sc', 'sc.student_id = s.id AND sc.school_year = ' . $this->db->escape($schoolYear), 'left') + ->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left') ->join('`classSection` cs', 'cs.class_section_id = sc.class_section_id', 'left') ->join('classes c', 'c.id = cs.class_id', 'left') ->whereIn('ps.batch_id', $batchIds) - ->where('s.is_active', 1) + ->groupStart() + ->where('s.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd() ->orderBy('ps.batch_id', 'ASC') ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC') @@ -1621,6 +1634,9 @@ public function belowSixty() 's.school_id', 's.firstname', 's.lastname', + 's.is_active', + 'e.enrollment_status', + 'e.is_withdrawn', 'pl.level AS placement_level', // Prefer business-id match; fall back to pk match @@ -1640,6 +1656,11 @@ public function belowSixty() ->distinct() ->join('`classSection` cs', 'cs.class_section_id = sc.class_section_id', 'left') ->join('students s', 's.id = sc.student_id', 'inner') + ->join( + 'enrollments e', + "e.student_id = s.id AND e.school_year = {$yrEsc}", + 'left' + ) ->join( 'placement_levels pl', 'pl.student_id = s.id', @@ -1665,7 +1686,11 @@ public function belowSixty() 'left' ) ->where('sc.school_year', $schoolYear) - ->where('s.is_active', 1); // Exclude removed/inactive students + ->groupStart() + ->where('s.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd(); return $builder ->orderBy('cs.class_id', 'ASC') @@ -1800,6 +1825,7 @@ public function belowSixty() ->select('s.school_id') ->select('s.firstname') ->select('s.lastname') + ->select('MAX(s.is_active) AS is_active', false) ->select('cs.class_section_name') ->select("'year' AS semester", false) ->select("MAX(CASE WHEN LOWER(TRIM(ss.semester)) = 'fall' THEN ss.homework_avg END) AS fall_homework_avg", false) @@ -1856,9 +1882,15 @@ public function belowSixty() MAX(CASE WHEN LOWER(TRIM(ss.semester)) = 'fall' THEN ss.final_exam_score END) + MAX(CASE WHEN LOWER(TRIM(ss.semester)) = 'spring' THEN ss.final_exam_score END) ) / 2 AS final_exam_score", false) + ->select('MAX(e.enrollment_status) AS enrollment_status, MAX(e.is_withdrawn) AS is_withdrawn', false) ->join('students s', 's.id = ss.student_id', 'inner') + ->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left') ->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left') - ->where('s.is_active', 1) + ->groupStart() + ->where('s.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd() ->where('ss.school_year', $schoolYear) ->where('ss.semester_score IS NOT NULL', null, false) ->where("LOWER(TRIM(ss.semester)) IN ('fall', 'spring')", null, false) @@ -1888,6 +1920,7 @@ public function belowSixty() 's.school_id', 's.firstname', 's.lastname', + 's.is_active', 'cs.class_section_name', 'ss.semester', 'ss.homework_avg', @@ -1899,10 +1932,17 @@ public function belowSixty() 'ss.midterm_exam_score', 'ss.final_exam_score', 'ss.semester_score', + 'e.enrollment_status', + 'e.is_withdrawn', ]) ->join('students s', 's.id = ss.student_id', 'inner') + ->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left') ->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left') - ->where('s.is_active', 1) + ->groupStart() + ->where('s.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd() ->where('ss.school_year', $schoolYear) ->where('ss.semester_score IS NOT NULL', null, false) ->where('ss.semester_score <', 60) @@ -2014,10 +2054,15 @@ public function belowSixty() 'ss.semester_score', ]) ->join('students s', 's.id = ss.student_id', 'inner') + ->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left') ->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left') ->where('ss.school_year', $schoolYear) ->where('ss.student_id', $studentId) - ->where('s.is_active', 1) + ->groupStart() + ->where('s.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd() ->where("LOWER(TRIM(ss.semester))", $semesterKey) ->get() ->getRowArray(); @@ -2354,13 +2399,21 @@ public function belowSixty() 's.lastname', 's.age', 's.school_id', + 's.is_active', 'cs.class_section_name', + 'e.enrollment_status', + 'e.is_withdrawn', 'LOWER(TRIM(ss.semester)) AS sem_key', 'ss.semester_score', ]) ->join('students s', 's.id = ss.student_id', 'inner') + ->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left') ->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left') - ->where('s.is_active', 1) + ->groupStart() + ->where('s.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd() ->where('ss.school_year', $schoolYear) ->whereIn('LOWER(TRIM(ss.semester))', ['fall', 'spring']) ->where('ss.semester_score IS NOT NULL', null, false) @@ -3404,16 +3457,24 @@ public function allDecisions() 's.lastname', 's.gender', 's.dob', + 's.is_active', 'ss.class_section_id', 'cs.class_section_name', 'c.class_name', + 'e.enrollment_status', + 'e.is_withdrawn', 'LOWER(TRIM(ss.semester)) AS sem_key', 'ss.semester_score', ]) ->join('students s', 's.id = ss.student_id', 'inner') + ->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left') ->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left') ->join('classes c', 'c.id = cs.class_id', 'left') - ->where('s.is_active', 1) + ->groupStart() + ->where('s.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd() ->where('ss.school_year', $schoolYear) ->whereIn('LOWER(TRIM(ss.semester))', ['fall', 'spring']) ->where('ss.semester_score IS NOT NULL', null, false) @@ -3440,6 +3501,9 @@ public function allDecisions() 'lastname' => $sr['lastname'] ?? '', 'gender' => $sr['gender'] ?? '', 'dob' => $sr['dob'] ?? '', + 'is_active' => (int)($sr['is_active'] ?? 1), + 'enrollment_status' => $sr['enrollment_status'] ?? '', + 'is_withdrawn' => (int)($sr['is_withdrawn'] ?? 0), 'class_section_id' => (int)($sr['class_section_id'] ?? 0), 'class_name' => $sr['class_name'] ?? '', 'class_section_name' => $sr['class_section_name'] ?? '', @@ -3538,6 +3602,9 @@ public function allDecisions() 'lastname' => $info['lastname'], 'gender' => $info['gender'] ?? '', 'dob' => $info['dob'] ?? '', + 'is_active' => (int)($info['is_active'] ?? 1), + 'enrollment_status' => $info['enrollment_status'] ?? '', + 'is_withdrawn' => (int)($info['is_withdrawn'] ?? 0), 'class_section_id' => (int)($info['class_section_id'] ?? 0), 'class_name' => $currentClassName, 'class_section_name' => $currentClassSectionName, @@ -3615,13 +3682,19 @@ public function generateAllDecisions() 's.id AS student_id', 's.firstname', 's.lastname', + 's.is_active', 'cs.class_section_name', 'LOWER(TRIM(ss.semester)) AS sem_key', 'ss.semester_score', ]) ->join('students s', 's.id = ss.student_id', 'inner') + ->join('enrollments e', 'e.student_id = s.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left') ->join('classSection cs', 'cs.class_section_id = ss.class_section_id', 'left') - ->where('s.is_active', 1) + ->groupStart() + ->where('s.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd() ->where('ss.school_year', $schoolYear) ->whereIn('LOWER(TRIM(ss.semester))', ['fall', 'spring']) ->where('ss.semester_score IS NOT NULL', null, false) diff --git a/app/Controllers/View/HomeworkController.php b/app/Controllers/View/HomeworkController.php index 3a4da13..22bd8b3 100644 --- a/app/Controllers/View/HomeworkController.php +++ b/app/Controllers/View/HomeworkController.php @@ -176,7 +176,7 @@ class HomeworkController extends BaseController } } } - $studentUserInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); + $studentUserInfo = $this->studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { @@ -404,7 +404,7 @@ class HomeworkController extends BaseController return $this->showHomeworkMngt($updatedBy); } - $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId); + $studentTeacherInfo = $this->studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { diff --git a/app/Controllers/View/MidtermController.php b/app/Controllers/View/MidtermController.php index 448f187..e5f978f 100644 --- a/app/Controllers/View/MidtermController.php +++ b/app/Controllers/View/MidtermController.php @@ -122,7 +122,7 @@ class MidtermController extends BaseController true ); - $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); + $studentTeacherInfo = $this->studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { @@ -155,7 +155,7 @@ class MidtermController extends BaseController session()->setFlashdata('status', 'Midterm exam scores updated successfully.'); - $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); + $studentTeacherInfo = $this->studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { @@ -288,6 +288,7 @@ class MidtermController extends BaseController // Join the "latest midterm per student" subquery, then the actual midterm row ->join('(' . $latestMidtermSub . ') ml', 'ml.student_id = s.id', 'left', false) ->join('midterm_exam me', 'me.id = ml.max_id', 'left') + ->where('s.is_active', 1) ->orderBy('s.lastname', 'ASC') ->orderBy('s.firstname', 'ASC'); diff --git a/app/Controllers/View/ParticipationController.php b/app/Controllers/View/ParticipationController.php index 784b82e..3dbe492 100644 --- a/app/Controllers/View/ParticipationController.php +++ b/app/Controllers/View/ParticipationController.php @@ -123,7 +123,7 @@ class ParticipationController extends BaseController true ); - $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); + $studentTeacherInfo = $this->studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { @@ -156,7 +156,7 @@ class ParticipationController extends BaseController session()->setFlashdata('status', 'Participation scores updated successfully.'); - $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); + $studentTeacherInfo = $this->studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { diff --git a/app/Controllers/View/ProjectController.php b/app/Controllers/View/ProjectController.php index 9152cf5..f2c8ca1 100644 --- a/app/Controllers/View/ProjectController.php +++ b/app/Controllers/View/ProjectController.php @@ -227,7 +227,7 @@ class ProjectController extends BaseController } } - $studentTeacherInfo = $studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); + $studentTeacherInfo = $studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { @@ -361,7 +361,7 @@ class ProjectController extends BaseController return $this->showProjectMngt($updatedBy); } - $studentTeacherInfo = $studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); + $studentTeacherInfo = $studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { diff --git a/app/Controllers/View/QuizController.php b/app/Controllers/View/QuizController.php index e8b75ce..21f29f2 100644 --- a/app/Controllers/View/QuizController.php +++ b/app/Controllers/View/QuizController.php @@ -181,7 +181,7 @@ class QuizController extends BaseController } } - $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); + $studentTeacherInfo = $this->studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { @@ -444,7 +444,7 @@ class QuizController extends BaseController return $this->showQuizMngt(); } - $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); + $studentTeacherInfo = $this->studentModel->getActiveStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear); // Call the updateScoresForStudents method try { diff --git a/app/Controllers/View/ScoreController.php b/app/Controllers/View/ScoreController.php index dd926ca..86fb8b4 100644 --- a/app/Controllers/View/ScoreController.php +++ b/app/Controllers/View/ScoreController.php @@ -217,7 +217,7 @@ class ScoreController extends BaseController $classSectionName = $csRow['class_section_name'] ?? ''; log_message('debug', "ScoreController::index teacher {$teacherId} classSection {$classSectionId} semester {$effectiveSemester}"); - $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId( + $studentTeacherInfo = $this->studentModel->getActiveStudentInfoByClassSectionId( $classSectionId, $effectiveSemester, $effectiveSchoolYear @@ -1179,7 +1179,7 @@ class ScoreController extends BaseController // Ensure semester_scores are re-calculated when midterm/final exams change. if (in_array(strtolower($table), ['final_exam', 'midterm_exam'], true) && $this->semesterScoreService !== null) { try { - $studentTeacherInfo = $this->studentModel->getStudentInfoByClassSectionId( + $studentTeacherInfo = $this->studentModel->getActiveStudentInfoByClassSectionId( $classSectionId, $semester, $this->schoolYear diff --git a/app/Controllers/View/StudentController.php b/app/Controllers/View/StudentController.php index 114e747..a149c7d 100644 --- a/app/Controllers/View/StudentController.php +++ b/app/Controllers/View/StudentController.php @@ -2783,13 +2783,23 @@ class StudentController extends BaseController return redirect()->back()->with('error', 'Invalid student id.'); } + $roleRaw = session()->get('role'); + if (is_array($roleRaw)) { + $roleRaw = $roleRaw[0] ?? 'guest'; + } + $role = strtolower((string)($roleRaw ?? 'guest')); + $isAdminScoreCardAccess = in_array($role, ['administrator', 'admin', 'principal', 'administrative staff'], true); + $student = $this->studentModel - ->select('id, firstname, lastname, school_id') + ->select('id, firstname, lastname, school_id, is_active') ->where('id', $studentId) ->first(); if (!$student) { return $this->response->setStatusCode(404)->setBody('
Student not found.
'); } + if (!$isAdminScoreCardAccess && (int)($student['is_active'] ?? 0) !== 1) { + return redirect()->to('/student/score-card/list')->with('error', 'Student score card is not available.'); + } $rows = $this->db->table('semester_scores ss') ->select([ @@ -3011,14 +3021,14 @@ class StudentController extends BaseController $schoolYear = $this->currentSchoolYearName((string) ($this->schoolYear ?? '')); $parentId = (int)(session()->get('user_id') ?? 0); if ($parentId > 0 && $schoolYear !== '') { - $students = $this->studentModel->getByParentAndYear($parentId, $schoolYear); + $students = $this->studentModel->getActiveByParentAndYear($parentId, $schoolYear); } } elseif (in_array($role, ['teacher', 'teacher_assistant', 'teacher_dashboard'], true)) { $schoolYear = $this->currentSchoolYearName((string) ($this->schoolYear ?? '')); $classSectionId = (int)(session()->get('class_section_id') ?? 0); if ($classSectionId > 0 && !empty($schoolYear)) { - $students = $this->studentModel->getByClassAndYear($classSectionId, $schoolYear); + $students = $this->studentModel->getActiveByClassAndYear($classSectionId, $schoolYear); } if (empty($students)) { @@ -3039,10 +3049,10 @@ class StudentController extends BaseController $studentClassModel = new \App\Models\StudentClassModel(); $rows = $studentClassModel->getStudentsByClassSectionIds($sectionIds, $schoolYear); $unique = []; - foreach ($rows as $r) { - $sid = (int)($r['student_id'] ?? 0); - if ($sid > 0) { - $unique[$sid] = [ + foreach ($rows as $r) { + $sid = (int)($r['student_id'] ?? 0); + if ($sid > 0 && (int)($r['is_active'] ?? 0) === 1) { + $unique[$sid] = [ 'id' => $sid, 'school_id' => $r['school_id'] ?? '', 'firstname' => $r['firstname'] ?? '', diff --git a/app/Controllers/View/TeacherController.php b/app/Controllers/View/TeacherController.php index 2bce733..c8147f3 100644 --- a/app/Controllers/View/TeacherController.php +++ b/app/Controllers/View/TeacherController.php @@ -125,6 +125,9 @@ class TeacherController extends BaseController if (!empty($classSectionIds)) { // 1) Fetch all students for the selected class sections $students = $this->studentClassModel->getStudentsByClassSectionIds($classSectionIds, (string) $this->schoolYear); + $students = array_values(array_filter($students, static function (array $student): bool { + return (int)($student['is_active'] ?? 0) === 1; + })); if (!empty($students)) { // 2) Collect unique student IDs diff --git a/app/Helpers/student_status_helper.php b/app/Helpers/student_status_helper.php new file mode 100644 index 0000000..95fa5e4 --- /dev/null +++ b/app/Helpers/student_status_helper.php @@ -0,0 +1,84 @@ + 0) { + $schoolYear = trim((string)($schoolYear ?? '')); + if ($schoolYear === '') { + try { + $schoolYear = (string)((new \App\Models\ConfigurationModel())->getConfig('school_year') ?? ''); + } catch (\Throwable $e) { + $schoolYear = ''; + } + } + + static $statusCache = []; + $cacheKey = $studentId . ':' . $schoolYear; + if (!array_key_exists($cacheKey, $statusCache)) { + $statusCache[$cacheKey] = ''; + if ($schoolYear !== '') { + try { + $row = db_connect()->table('enrollments') + ->select('enrollment_status, is_withdrawn') + ->where('student_id', $studentId) + ->where('school_year', $schoolYear) + ->orderBy('updated_at', 'DESC') + ->orderBy('enrollment_date', 'DESC') + ->orderBy('id', 'DESC') + ->get(1) + ->getRowArray(); + $rowStatus = strtolower(trim((string)($row['enrollment_status'] ?? ''))); + if ($rowStatus === 'widthran' || $rowStatus === 'widthrawan' || $rowStatus === 'withdrawan' || $rowStatus === 'withdrawn student' || (int)($row['is_withdrawn'] ?? 0) === 1) { + $rowStatus = 'withdrawn'; + } + $statusCache[$cacheKey] = $rowStatus; + } catch (\Throwable $e) { + $statusCache[$cacheKey] = ''; + } + } + } + $status = $statusCache[$cacheKey]; + } + + return $status; + } +} + +if (!function_exists('student_is_non_active_for_editing')) { + function student_is_non_active_for_editing(array $student, ?string $schoolYear = null): bool + { + $status = student_normalized_enrollment_status($student, $schoolYear); + + if (in_array($status, ['denied', 'withdrawn', 'waitlist'], true)) { + return true; + } + + return array_key_exists('is_active', $student) && (int)$student['is_active'] !== 1; + } +} + +if (!function_exists('student_enrollment_status_button')) { + function student_enrollment_status_button(array $student, ?string $schoolYear = null): string + { + $status = student_normalized_enrollment_status($student, $schoolYear); + + $classes = [ + 'denied' => 'btn-outline-danger', + 'withdrawn' => 'btn-outline-secondary', + 'waitlist' => 'btn-outline-warning', + ]; + + if (!isset($classes[$status])) { + return ''; + } + + return '
' . esc(ucwords($status)) . '
'; + } +} diff --git a/app/Models/StudentClassModel.php b/app/Models/StudentClassModel.php index 17e7bbf..5efd798 100644 --- a/app/Models/StudentClassModel.php +++ b/app/Models/StudentClassModel.php @@ -55,6 +55,32 @@ class StudentClassModel extends Model return $this->db->table($this->table); } + private function includeActiveOrTerminalEnrollment(BaseBuilder $builder, string $schoolYear): BaseBuilder + { + $terminalStatuses = array_map([$this->db, 'escape'], [ + 'denied', + 'withdrawn', + 'widthran', + 'widthrawan', + 'withdrawan', + 'waitlist', + ]); + + $builder->groupStart() + ->where('students.is_active', 1); + + if ($schoolYear !== '') { + $builder->orWhere( + 'LOWER(TRIM(enrollments.enrollment_status)) IN (' . implode(',', $terminalStatuses) . ')', + null, + false + ) + ->orWhere('enrollments.is_withdrawn', 1); + } + + return $builder->groupEnd(); + } + /** * Create a fresh builder scoped to active students. */ @@ -131,7 +157,8 @@ class StudentClassModel extends Model } /** - * Get active students assigned to a class section. + * Get students assigned to a class section, including inactive students with + * terminal current-year enrollment statuses. * * student_class is scoped by school year only. It has no semester column. */ @@ -139,7 +166,22 @@ class StudentClassModel extends Model int $classSectionId, ?string $schoolYear = null ): array { - $builder = $this->activeStudentsBuilder() + $schoolYear = $schoolYear !== null ? trim($schoolYear) : ''; + + $builder = $this->freshBuilder() + ->select('student_class.*, students.is_active, enrollments.enrollment_status, enrollments.is_withdrawn') + ->join( + 'students', + 'students.id = student_class.student_id', + 'inner' + ) + ->join( + 'enrollments', + $schoolYear !== '' + ? 'enrollments.student_id = student_class.student_id AND enrollments.school_year = ' . $this->db->escape($schoolYear) + : 'enrollments.student_id = student_class.student_id', + 'left' + ) ->where( 'student_class.class_section_id', $classSectionId @@ -150,13 +192,15 @@ class StudentClassModel extends Model false ); - if ($schoolYear !== null && trim($schoolYear) !== '') { + if ($schoolYear !== '') { $builder->where( 'student_class.school_year', - trim($schoolYear) + $schoolYear ); } + $this->includeActiveOrTerminalEnrollment($builder, $schoolYear); + $rows = $builder ->orderBy('students.lastname', 'ASC') ->orderBy('students.firstname', 'ASC') @@ -195,7 +239,6 @@ class StudentClassModel extends Model 'student_class.class_section_id', $classSectionId ) - ->where('students.is_active', 1) ->where( 'student_class.class_section_id IS NOT NULL', null, @@ -206,9 +249,16 @@ class StudentClassModel extends Model $builder->where( 'student_class.school_year', $schoolYear + ) + ->join( + 'enrollments', + 'enrollments.student_id = student_class.student_id AND enrollments.school_year = ' . $this->db->escape($schoolYear), + 'left' ); } + $this->includeActiveOrTerminalEnrollment($builder, $schoolYear); + $rows = $builder ->groupBy('student_class.student_id') ->get() @@ -364,7 +414,8 @@ class StudentClassModel extends Model } /** - * Return active students for a set of class-section IDs. + * Return students for a set of class-section IDs, including inactive + * students with terminal current-year enrollment statuses. */ public function getStudentsByClassSectionIds( array $classSectionIds, @@ -379,37 +430,50 @@ class StudentClassModel extends Model return []; } + $schoolYear = $schoolYear !== null ? trim($schoolYear) : ''; + $builder = $this->freshBuilder() ->select([ 'students.id AS student_id', 'students.firstname', 'students.lastname', 'students.school_id', + 'students.is_active', 'students.is_new', 'students.photo_consent', 'students.age', 'student_class.school_year', 'student_class.class_section_id', 'student_class.is_event_only', + 'enrollments.enrollment_status', + 'enrollments.is_withdrawn', ]) ->join( 'students', 'students.id = student_class.student_id', 'inner' ) + ->join( + 'enrollments', + $schoolYear !== '' + ? 'enrollments.student_id = student_class.student_id AND enrollments.school_year = ' . $this->db->escape($schoolYear) + : 'enrollments.student_id = student_class.student_id', + 'left' + ) ->whereIn( 'student_class.class_section_id', $classSectionIds - ) - ->where('students.is_active', 1); + ); - if ($schoolYear !== null && trim($schoolYear) !== '') { + if ($schoolYear !== '') { $builder->where( 'student_class.school_year', - trim($schoolYear) + $schoolYear ); } + $this->includeActiveOrTerminalEnrollment($builder, $schoolYear); + return $builder ->orderBy('students.lastname', 'ASC') ->orderBy('students.firstname', 'ASC') @@ -504,7 +568,6 @@ class StudentClassModel extends Model 'students.id = student_class.student_id', 'inner' ) - ->where('students.is_active', 1) ->where( 'student_class.class_section_id IS NOT NULL', null, @@ -513,12 +576,22 @@ class StudentClassModel extends Model ->groupBy('student_class.class_section_id'); if ($schoolYear !== null && trim($schoolYear) !== '') { + $schoolYear = trim($schoolYear); $builder->where( 'student_class.school_year', - trim($schoolYear) + $schoolYear + ) + ->join( + 'enrollments', + 'enrollments.student_id = student_class.student_id AND enrollments.school_year = ' . $this->db->escape($schoolYear), + 'left' ); + } else { + $schoolYear = ''; } + $this->includeActiveOrTerminalEnrollment($builder, $schoolYear); + $rows = $builder ->get() ->getResultArray(); diff --git a/app/Models/StudentModel.php b/app/Models/StudentModel.php index 873bf81..2ba857d 100644 --- a/app/Models/StudentModel.php +++ b/app/Models/StudentModel.php @@ -292,25 +292,42 @@ class StudentModel extends Model public function getByClassAndYear($class_section_id, $school_year, ?string $semester = null): array { - $studentClassModel = new \App\Models\StudentClassModel(); + $school_year = trim((string) $school_year); - $builder = $studentClassModel - ->select('student_id') - ->where('class_section_id', $class_section_id) - ->where('school_year', $school_year); + $builder = $this->select('students.*, e.enrollment_status, e.is_withdrawn') + ->join('student_class sc', 'sc.student_id = students.id', 'inner') + ->join( + 'enrollments e', + 'e.student_id = students.id AND e.school_year = ' . $this->db->escape($school_year), + 'left' + ) + ->where('sc.class_section_id', $class_section_id) + ->where('sc.school_year', $school_year) + ->groupStart() + ->where('students.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd(); - $studentIds = $builder->findAll(); + return $builder + ->distinct() + ->orderBy('students.firstname', 'ASC') + ->orderBy('students.lastname', 'ASC') + ->findAll(); + } - $studentIds = array_column($studentIds, 'student_id'); + public function getActiveByClassAndYear($class_section_id, $school_year, ?string $semester = null): array + { + $school_year = trim((string) $school_year); - if (empty($studentIds)) { - return []; - } - - return $this->whereIn('id', $studentIds) - ->where('is_active', 1) - ->orderBy('firstname', 'ASC') - ->orderBy('lastname', 'ASC') + return $this->select('students.*') + ->join('student_class sc', 'sc.student_id = students.id', 'inner') + ->where('sc.class_section_id', $class_section_id) + ->where('sc.school_year', $school_year) + ->where('students.is_active', 1) + ->distinct() + ->orderBy('students.firstname', 'ASC') + ->orderBy('students.lastname', 'ASC') ->findAll(); } @@ -322,12 +339,36 @@ class StudentModel extends Model return []; } - return $this->select('students.id, students.school_id, students.firstname, students.lastname') + return $this->select('students.id, students.school_id, students.firstname, students.lastname, students.is_active, e.enrollment_status, e.is_withdrawn') + ->join('student_class', 'student_class.student_id = students.id', 'inner') + ->join('enrollments e', 'e.student_id = students.id AND e.school_year = ' . $this->db->escape($schoolYear), 'left') + ->where('students.parent_id', $parentId) + ->where('student_class.school_year', $schoolYear) + ->groupStart() + ->where('students.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd() + ->groupBy('students.id, students.school_id, students.firstname, students.lastname, students.is_active, e.enrollment_status, e.is_withdrawn') + ->orderBy('students.lastname', 'ASC') + ->orderBy('students.firstname', 'ASC') + ->findAll(); + } + + public function getActiveByParentAndYear(int $parentId, string $schoolYear): array + { + $schoolYear = trim($schoolYear); + + if ($parentId <= 0 || $schoolYear === '') { + return []; + } + + return $this->select('students.id, students.school_id, students.firstname, students.lastname, students.is_active') ->join('student_class', 'student_class.student_id = students.id', 'inner') ->where('students.parent_id', $parentId) - ->where('students.is_active', 1) ->where('student_class.school_year', $schoolYear) - ->groupBy('students.id, students.school_id, students.firstname, students.lastname') + ->where('students.is_active', 1) + ->groupBy('students.id, students.school_id, students.firstname, students.lastname, students.is_active') ->orderBy('students.lastname', 'ASC') ->orderBy('students.firstname', 'ASC') ->findAll(); @@ -443,16 +484,28 @@ class StudentModel extends Model $builder = $this->builder(); $builder - ->select('students.id AS student_id, students.school_id, students.firstname, students.lastname, sc.class_section_id') + ->select('students.id AS student_id, students.school_id, students.firstname, students.lastname, students.is_active, sc.class_section_id, e.enrollment_status, e.is_withdrawn') ->join('student_class sc', 'sc.student_id = students.id', 'inner') - ->where('sc.class_section_id', $classSectionId) - ->where('students.is_active', 1); + ->where('sc.class_section_id', $classSectionId); // Optional term scoping (safe no-ops if null) if ($schoolYear !== null) { $builder->where('sc.school_year', $schoolYear); + $builder->join( + 'enrollments e', + 'e.student_id = students.id AND e.school_year = ' . $this->db->escape($schoolYear), + 'left' + ); + } else { + $builder->join('enrollments e', 'e.student_id = students.id', 'left'); } + $builder->groupStart() + ->where('students.is_active', 1) + ->orWhere("LOWER(TRIM(e.enrollment_status)) IN ('denied','withdrawn','widthran','widthrawan','withdrawan','waitlist')", null, false) + ->orWhere('e.is_withdrawn', 1) + ->groupEnd(); + // Prevent accidental dupes if student_class has multiple rows $builder->distinct(); $builder->orderBy('students.lastname', 'ASC') @@ -472,6 +525,41 @@ class StudentModel extends Model return $results ?: []; } + public function getActiveStudentInfoByClassSectionId($classSectionId, ?string $semester = null, ?string $schoolYear = null): array + { + $classSectionId = (int) $classSectionId; + if ($classSectionId <= 0) { + return []; + } + + $builder = $this->builder(); + $builder + ->select('students.id AS student_id, students.school_id, students.firstname, students.lastname, students.is_active, sc.class_section_id') + ->join('student_class sc', 'sc.student_id = students.id', 'inner') + ->where('sc.class_section_id', $classSectionId) + ->where('students.is_active', 1); + + if ($schoolYear !== null) { + $builder->where('sc.school_year', $schoolYear); + } + + $builder->distinct(); + $builder->orderBy('students.lastname', 'ASC') + ->orderBy('students.firstname', 'ASC'); + + $results = $builder->get()->getResultArray(); + + $userId = (int) (session()->get('user_id') ?? 0); + if (!empty($results)) { + foreach ($results as &$row) { + $row['updated_by'] = $userId; + } + unset($row); + } + + return $results ?: []; + } + // In your existing StudentModel public function getStudentBasic(int $studentId): ?array diff --git a/app/Views/admin/certificates/audit_log.php b/app/Views/admin/certificates/audit_log.php index 59a6c0b..6f5c711 100644 --- a/app/Views/admin/certificates/audit_log.php +++ b/app/Views/admin/certificates/audit_log.php @@ -77,7 +77,10 @@ - + + + + diff --git a/app/Views/admin/certificates/index.php b/app/Views/admin/certificates/index.php index 6d12e9c..8cc2f0c 100644 --- a/app/Views/admin/certificates/index.php +++ b/app/Views/admin/certificates/index.php @@ -245,7 +245,10 @@ $decisionBadge = [ > - + + + + @@ -624,4 +627,4 @@ window.showCertificatePdf=function(url,fn){ }); })(); -endSection() ?> \ No newline at end of file +endSection() ?> diff --git a/app/Views/admin/competition_winners/scores.php b/app/Views/admin/competition_winners/scores.php index 36b4f83..00531b8 100644 --- a/app/Views/admin/competition_winners/scores.php +++ b/app/Views/admin/competition_winners/scores.php @@ -82,7 +82,10 @@ ?> - + + + + - + + + + diff --git a/app/Views/admin/student_score_card.php b/app/Views/admin/student_score_card.php index 2b5d168..f3c9775 100644 --- a/app/Views/admin/student_score_card.php +++ b/app/Views/admin/student_score_card.php @@ -78,7 +78,10 @@ - + + + + Active diff --git a/app/Views/administrator/administratordashboard.php b/app/Views/administrator/administratordashboard.php index 7d1f6e2..d49196a 100644 --- a/app/Views/administrator/administratordashboard.php +++ b/app/Views/administrator/administratordashboard.php @@ -237,7 +237,10 @@ - + + + + @@ -375,7 +378,10 @@ - + + + + diff --git a/app/Views/administrator/class_assignment.php b/app/Views/administrator/class_assignment.php index 82ed525..b0a1bd1 100644 --- a/app/Views/administrator/class_assignment.php +++ b/app/Views/administrator/class_assignment.php @@ -72,8 +72,10 @@ + + diff --git a/app/Views/administrator/daily_attendance.php b/app/Views/administrator/daily_attendance.php index f1a3d8f..a4ab4fa 100644 --- a/app/Views/administrator/daily_attendance.php +++ b/app/Views/administrator/daily_attendance.php @@ -148,6 +148,37 @@ endSection() ?> + + +
+ +
+ + +
+
+ + +
+
+ +
+
+
@@ -358,8 +389,11 @@ $studentsCountByClassId[$cid] = count($uniq); } - // Default active tab = first in sorted order (can still be overridden by ?class_id) - $defaultId = $sortedClassIds[0] ?? null; + // Default active tab = first in sorted order unless ?class_id selects a visible grade. + $requestedClassId = (int)($_GET['class_id'] ?? 0); + $defaultId = ($requestedClassId > 0 && in_array($requestedClassId, $sortedClassIds, true)) + ? $requestedClassId + : ($sortedClassIds[0] ?? null); // ===================================================================== // NEW UTILITIES (dates pivot logic) — includes "today if Sunday, else next Sunday" column @@ -716,18 +750,20 @@ $tAbsent = $summary ? (int)($summary['total_absence'] ?? 0) : 0; - $schoolId = esc($student['school_id']); - $fullNameRaw = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')); - $fullName = esc($fullNameRaw); - $fullNameAttr = esc(strtolower($fullNameRaw)); - ?> - + $schoolId = esc($student['school_id']); + $fullNameRaw = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')); + $fullName = esc($fullNameRaw); + $fullNameAttr = esc(strtolower($fullNameRaw)); + $rowLocked = student_is_non_active_for_editing($student, $schoolYear ?? null); + ?> + + @@ -757,12 +794,13 @@ + + class="form-control form-control-sm" + style="width:140px;" + placeholder="Note (optional)" + + value=""> - diff --git a/app/Views/grading/below_sixty_decisions.php b/app/Views/grading/below_sixty_decisions.php index 5cbde20..de28f9d 100644 --- a/app/Views/grading/below_sixty_decisions.php +++ b/app/Views/grading/below_sixty_decisions.php @@ -176,14 +176,20 @@ if (empty($schoolYears) && $schoolYear !== '') { } $studentName = trim((string)($row['firstname'] ?? '') . ' ' . (string)($row['lastname'] ?? '')); - $studentLabel = $studentName !== '' ? $studentName : 'N/A'; - $currentDecision = (string)($row['decision'] ?? ''); - $currentNotes = (string)($row['decision_notes'] ?? ''); - $badge = $decisionBadge[$currentDecision] ?? null; - ?> + $studentLabel = $studentName !== '' ? $studentName : 'N/A'; + $currentDecision = (string)($row['decision'] ?? ''); + $currentNotes = (string)($row['decision_notes'] ?? ''); + $badge = $decisionBadge[$currentDecision] ?? null; + $rowLocked = student_is_non_active_for_editing($row, $schoolYear ?? null); + $rowClass = trim($rowClass . ' ' . ($rowLocked ? 'table-secondary text-muted' : '')); + $controlsEditable = $isEditable && !$rowLocked; + ?> - + + + + @@ -219,15 +225,15 @@ if (empty($schoolYears) && $schoolYear !== '') { value=""> + class="form-control form-control-sm decision-notes" + rows="3" + placeholder="Add comments or rationale…" + >
- > $label): ?>
@@ -252,10 +258,10 @@ if (empty($schoolYears) && $schoolYear !== '') { diff --git a/app/Views/grading/comments.php b/app/Views/grading/comments.php index f20514d..c472491 100644 --- a/app/Views/grading/comments.php +++ b/app/Views/grading/comments.php @@ -78,16 +78,22 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; - - - - + + + + + @@ -102,7 +108,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; data-first-name="" minlength="100" maxlength="350" - placeholder="Enter PTAP comment" > + placeholder="Enter PTAP comment" >
@@ -111,13 +117,13 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; name="reviews[][ptap]" rows="2" class="form-control review-field" - placeholder="Enter PTAP review" > + placeholder="Enter PTAP review" >
@@ -144,7 +150,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; data-first-name="" minlength="100" maxlength="350" - placeholder="Enter Midterm comment" > + placeholder="Enter Midterm comment" >
@@ -156,13 +162,13 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; data-first-name="" minlength="100" maxlength="350" - placeholder="Enter Midterm review" > + placeholder="Enter Midterm review" >
@@ -187,7 +193,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; data-first-name="" minlength="100" maxlength="350" - placeholder="Enter Final comment" > + placeholder="Enter Final comment" >
@@ -196,13 +202,13 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; name="reviews[][final]" rows="2" class="form-control review-field" - placeholder="Enter Final review" > + placeholder="Enter Final review" >
@@ -225,7 +231,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; data-first-name="" minlength="100" maxlength="350" - placeholder="Enter Attendance comment" > + placeholder="Enter Attendance comment" > @@ -377,7 +383,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; }; const shouldAutoSave = function(field, value) { - if (field.disabled) return false; + if (field.disabled || field.readOnly) return false; const lastSaved = field.getAttribute('data-last-saved') || ''; if (value === lastSaved) return false; const parsed = parseFieldName(field.name || ''); @@ -434,8 +440,9 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; form.addEventListener('submit', function(event) { syncCsrfToken(); const errors = []; - form.querySelectorAll('textarea[data-first-name]').forEach(field => { - const value = field.value.trim(); + form.querySelectorAll('textarea[data-first-name]').forEach(field => { + if (field.disabled || field.readOnly) return; + const value = field.value.trim(); if (value === '') return; const min = 100; @@ -472,9 +479,10 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; const wireCopyButtons = (selector) => { document.querySelectorAll(selector).forEach(button => { button.addEventListener('click', function() { - const source = document.querySelector(this.dataset.copySource || ''); - const target = document.querySelector(this.dataset.copyTarget || ''); - if (!source || !target) return; + const source = document.querySelector(this.dataset.copySource || ''); + const target = document.querySelector(this.dataset.copyTarget || ''); + if (!source || !target) return; + if (target.disabled || target.readOnly) return; target.value = source.value; target.style.height = 'auto'; target.style.height = (target.scrollHeight) + 'px'; diff --git a/app/Views/grading/final.php b/app/Views/grading/final.php index 12db36c..fe34e82 100644 --- a/app/Views/grading/final.php +++ b/app/Views/grading/final.php @@ -88,15 +88,20 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; - - $student): ?> - + + $student): ?> + + + @@ -106,7 +111,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; > + placeholder="Enter score (optional)" > diff --git a/app/Views/grading/grading_main.php b/app/Views/grading/grading_main.php index 90f8625..18b504b 100644 --- a/app/Views/grading/grading_main.php +++ b/app/Views/grading/grading_main.php @@ -357,9 +357,10 @@ Semester Score - - - + + + + @@ -367,8 +368,10 @@ + + diff --git a/app/Views/grading/homework.php b/app/Views/grading/homework.php index 7a61b56..8ea2a2e 100644 --- a/app/Views/grading/homework.php +++ b/app/Views/grading/homework.php @@ -55,14 +55,17 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; - + + @@ -78,7 +81,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; name="scores[][]" value="" class="form-control text-center" - min="0" max="100" step="0.01" > + min="0" max="100" step="0.01" > diff --git a/app/Views/grading/midterm.php b/app/Views/grading/midterm.php index b01e1f6..3fd212a 100644 --- a/app/Views/grading/midterm.php +++ b/app/Views/grading/midterm.php @@ -45,15 +45,20 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; - - $student): ?> - + + $student): ?> + + + @@ -63,7 +68,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; > + placeholder="Enter score (optional)" > diff --git a/app/Views/grading/participation.php b/app/Views/grading/participation.php index e4cffa8..595694f 100644 --- a/app/Views/grading/participation.php +++ b/app/Views/grading/participation.php @@ -45,15 +45,20 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; - - $student): ?> - + + $student): ?> + + + @@ -63,7 +68,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; > + placeholder="Enter score (optional)" > diff --git a/app/Views/grading/placement.php b/app/Views/grading/placement.php index 66bd85f..fca9e72 100644 --- a/app/Views/grading/placement.php +++ b/app/Views/grading/placement.php @@ -53,13 +53,17 @@ - - + + - + + + + + step="1" + value="" + + placeholder="0-100"> diff --git a/app/Views/grading/placement_batch.php b/app/Views/grading/placement_batch.php index 937915d..48a6291 100644 --- a/app/Views/grading/placement_batch.php +++ b/app/Views/grading/placement_batch.php @@ -53,14 +53,18 @@ - + $current = $scoreRow ? (string) ($scoreRow['score'] ?? '') : ''; + $sectionName = (string) ($student['class_section_name'] ?? ''); + $className = (string) ($student['class_name'] ?? ''); + $sectionLabel = $className !== '' ? ($className . ' — ' . $sectionName) : $sectionName; + $rowLocked = student_is_non_active_for_editing($student, $schoolYear ?? null); + ?> + - + + + + @@ -69,9 +73,10 @@ class="form-control form-control-sm" min="0" max="100" - step="1" - value="" - placeholder="0-100"> + step="1" + value="" + + placeholder="0-100"> diff --git a/app/Views/grading/placement_index.php b/app/Views/grading/placement_index.php index f5f6824..3831d98 100644 --- a/app/Views/grading/placement_index.php +++ b/app/Views/grading/placement_index.php @@ -61,13 +61,17 @@ - + $sectionName = (string) ($student['class_section_name'] ?? ''); + $className = (string) ($student['class_name'] ?? ''); + $sectionLabel = $className !== '' ? ($className . ' — ' . $sectionName) : $sectionName; + $rowLocked = student_is_non_active_for_editing($student, $schoolYear ?? null); + ?> + - + + + + @@ -76,9 +80,10 @@ class="form-control form-control-sm" min="0" max="100" - step="1" - value="" - placeholder="0-100"> + step="1" + value="" + + placeholder="0-100"> @@ -154,13 +159,17 @@ - + $sectionName = (string) ($row['class_section_name'] ?? ''); + $className = (string) ($row['class_name'] ?? ''); + $sectionLabel = $className !== '' ? ($className . ' — ' . $sectionName) : $sectionName; + $rowLocked = student_is_non_active_for_editing($row, $schoolYear ?? null); + ?> + - + + + + diff --git a/app/Views/grading/project.php b/app/Views/grading/project.php index 656b610..9fbc2d4 100644 --- a/app/Views/grading/project.php +++ b/app/Views/grading/project.php @@ -56,14 +56,17 @@ - + + @@ -79,7 +82,7 @@ name="scores[][]" value="" class="form-control text-center" - min="0" max="100" step="0.01" > + min="0" max="100" step="0.01" > diff --git a/app/Views/grading/quiz.php b/app/Views/grading/quiz.php index 89e0a67..e07b072 100644 --- a/app/Views/grading/quiz.php +++ b/app/Views/grading/quiz.php @@ -56,14 +56,17 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; - + + @@ -79,7 +82,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; name="scores[][]" value="" class="form-control text-center" - min="0" max="100" step="0.01" > + min="0" max="100" step="0.01" > diff --git a/app/Views/grading/test.php b/app/Views/grading/test.php index f905fe2..6d2b31e 100644 --- a/app/Views/grading/test.php +++ b/app/Views/grading/test.php @@ -41,6 +41,9 @@ switch ($viewFile) { $scoresLocked = !empty($scoresLocked); $lockAttr = $scoresLocked ? 'disabled' : ''; +$rowLocked = student_is_non_active_for_editing($student, $schoolYear ?? null); +$rowLockAttr = $scoresLocked ? $lockAttr : ($rowLocked ? 'readonly aria-disabled="true"' : ''); +$rowButtonLockAttr = ($scoresLocked || $rowLocked) ? 'disabled' : ''; ?> extend('layout/management_layout') ?> @@ -51,6 +54,7 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; +
@@ -79,10 +83,10 @@ $lockAttr = $scoresLocked ? 'disabled' : ''; - > + > - + @@ -91,17 +95,17 @@ $lockAttr = $scoresLocked ? 'disabled' : '';
- > + >
- +
- +
diff --git a/public/assets/js/proofread.js b/public/assets/js/proofread.js index 85f3b64..d1e5848 100644 --- a/public/assets/js/proofread.js +++ b/public/assets/js/proofread.js @@ -81,7 +81,9 @@ btn.type = 'button'; btn.className = 'btn btn-link btn-sm ps-0'; btn.textContent = 'Apply'; + btn.disabled = !!(textarea.disabled || textarea.readOnly); btn.addEventListener('click', () => { + if (textarea.disabled || textarea.readOnly) return; textarea.value = applyMatches(textarea.value, [match]); resize(textarea); textarea.dispatchEvent(new Event('input', { bubbles: true })); @@ -97,6 +99,7 @@ const proofreadField = async (textarea) => { if (!textarea) return { ok: false }; + if (textarea.disabled || textarea.readOnly) return { ok: true, skipped: true }; const targetId = textarea.id || textarea.name || ''; const text = textarea.value || ''; @@ -159,7 +162,8 @@ const proofreadAll = async () => { const btn = document.getElementById('proofreadAllBtn'); const summary = document.getElementById('proofreadAllStatus'); - const fields = Array.from(document.querySelectorAll('textarea.review-field')); + const fields = Array.from(document.querySelectorAll('textarea.review-field:not(:disabled)')) + .filter((field) => !field.readOnly); if (!fields.length) return; if (btn) btn.disabled = true;