configModel = new ConfigurationModel(); $this->calendarModel = new CalendarModel(); $this->homeworkModel = new HomeworkModel(); $this->db = \Config\Database::connect(); $this->semester = (string) ($this->configModel->getConfig('semester') ?? ''); $this->schoolYear = (string) ($this->configModel->getConfig('school_year') ?? ''); } public function index() { // Semester-based date range (Fall or Spring) derived from school year start. [$start, $end] = $this->getDateRangeForSemester($this->schoolYear, $this->semester); // Move start to the first Sunday on/after start if ((int)$start->format('w') !== 0) { $start = (clone $start)->modify('next sunday'); } // Collect all Sundays within range $sundays = []; $d = clone $start; while ($d <= $end) { $sundays[] = $d->format('Y-m-d'); $d->modify('+7 days'); } // Map "no school" events by date (Y-m-d) using explicit DB flag $events = $this->calendarModel->getEventsBySchoolYear($this->schoolYear) ?? []; $eventDays = []; foreach ($events as $ev) { $ymd = substr((string)($ev['date'] ?? ''), 0, 10); $no = (int)($ev['no_school'] ?? 0); if ($ymd && $no === 1) { $eventDays[$ymd] = true; // yellow highlight for no-school day } } // Map Sundays to ordinal homework_index, skipping event Sundays $dateToIndex = []; $idx = 0; foreach ($sundays as $ymd) { if (!isset($eventDays[$ymd])) { $idx++; $dateToIndex[$ymd] = $idx; } else { $dateToIndex[$ymd] = null; // event day (no homework expected) } } $limitToSemester = $this->hasTeacherAssignments($this->schoolYear, $this->semester); // Aggregate homework presence + first entered date per class_section_id + homework_index $hwQ = $this->db->table('homework') ->select('class_section_id, homework_index, MIN(created_at) AS first_created, COUNT(*) AS cnt') ->where('school_year', $this->schoolYear); if ($limitToSemester && $this->semester !== '') { $hwQ->where('semester', $this->semester); } $rows = $hwQ->groupBy('class_section_id, homework_index') ->get() ->getResultArray(); $hasHomework = []; $hwEnteredAt = []; $homeworkSubmissionCounts = []; foreach ($rows as $r) { $csid = (int)($r['class_section_id'] ?? 0); $hi = (int)($r['homework_index'] ?? 0); $cnt = (int)($r['cnt'] ?? 0); if ($csid > 0 && $hi > 0 && $cnt > 0) { $hasHomework[$csid][$hi] = true; $dateStr = substr((string)($r['first_created'] ?? ''), 0, 10); $hwEnteredAt[$csid][$hi] = $dateStr ?: null; $homeworkSubmissionCounts[$csid] = ($homeworkSubmissionCounts[$csid] ?? 0) + 1; } } // Build date-based presence mapped to the nearest prior non-NoSchool Sunday. $hb = $this->db->table('homework') ->select("class_section_id, DATE(created_at) AS hw_date, MIN(created_at) AS first_created, COUNT(*) AS cnt", false) ->where('school_year', $this->schoolYear); if ($limitToSemester && $this->semester !== '') { $hb->where('semester', $this->semester); } $rowsByDate = $hb->groupBy('class_section_id, DATE(created_at)', '', false) ->orderBy('hw_date', 'ASC', false) ->get() ->getResultArray(); $hasHomeworkByDate = []; $hwEnteredAtByDate = []; foreach ($rowsByDate as $r) { $csid = (int)($r['class_section_id'] ?? 0); $d = substr((string)($r['hw_date'] ?? ''), 0, 10); $cnt = (int)($r['cnt'] ?? 0); $firstCreated = substr((string)($r['first_created'] ?? ''), 0, 10); if ($csid <= 0 || !$d || $cnt <= 0) { continue; } // Find the index of the last Sunday on or before $d $baseIndex = -1; for ($i = count($sundays) - 1; $i >= 0; $i--) { if ($sundays[$i] <= $d) { $baseIndex = $i; break; } } if ($baseIndex < 0) { continue; } // Map this homework day to only the nearest prior non–No School Sunday $j = $baseIndex; while ($j >= 0 && !empty($eventDays[$sundays[$j]])) { $j--; } if ($j < 0) { continue; } $sd = $sundays[$j]; // Mark homework on that Sunday for this class_section (single mapping) $hasHomeworkByDate[$csid][$sd] = true; if (empty($hwEnteredAtByDate[$csid][$sd])) { $hwEnteredAtByDate[$csid][$sd] = $firstCreated ?: $d; } else { // Keep the earliest date for display $existing = (string)$hwEnteredAtByDate[$csid][$sd]; $candidate = $firstCreated ?: $d; if ($candidate && (!$existing || $candidate < $existing)) { $hwEnteredAtByDate[$csid][$sd] = $candidate; } } } // Fetch teachers and TAs per section for this term $tb = $this->db->table('teacher_class tc') ->select('tc.class_section_id, tc.position, cs.class_section_name, cs.class_id, u.firstname, u.lastname') ->join('classSection cs', 'cs.class_section_id = tc.class_section_id', 'left') ->join('users u', 'u.id = tc.teacher_id', 'left') ->where('tc.school_year', $this->schoolYear); $teacherRows = $tb->where('tc.class_section_id IS NOT NULL', null, false) ->whereIn('tc.position', ['main','ta']) ->orderBy('cs.class_id', 'ASC') ->orderBy('cs.class_section_name', 'ASC') ->orderBy("FIELD(tc.position, 'main','ta')", '', false) ->orderBy('u.firstname', 'ASC') ->get() ->getResultArray(); $bySection = []; foreach ($teacherRows as $r) { $sid = (int)($r['class_section_id'] ?? 0); if ($sid <= 0) continue; if (!isset($bySection[$sid])) { $bySection[$sid] = [ 'class_section_id' => $sid, 'class_id' => (int)($r['class_id'] ?? 0), 'class_section_name' => (string)($r['class_section_name'] ?? ''), 'teachers' => [], 'tas' => [], ]; } $name = trim(($r['firstname'] ?? '') . ' ' . ($r['lastname'] ?? '')); $pos = strtolower((string)($r['position'] ?? '')); if ($name !== '') { if ($pos === 'main') { $bySection[$sid]['teachers'][] = $name; } elseif ($pos === 'ta') { $bySection[$sid]['tas'][] = $name; } } } $teachers = array_values($bySection); // Sort by grade order: KG(13) → Grade 1..11 → Youth(12) → others usort($teachers, function ($a, $b) { $ai = (int)($a['class_id'] ?? 0); $bi = (int)($b['class_id'] ?? 0); $ord = function ($cid) { if ($cid === 13) return 0; // KG first if ($cid >= 1 && $cid <= 11) return $cid; // Grades 1..11 if ($cid === 12) return 99; // Youth last of known return 200 + $cid; // any others after }; $cmp = $ord($ai) <=> $ord($bi); if ($cmp !== 0) return $cmp; return strnatcasecmp($a['class_section_name'] ?? '', $b['class_section_name'] ?? ''); }); // Simple server-side pagination for rows (8 lines per page) $perPage = 8; $totalRows = count($teachers); $totalPages = max(1, (int)ceil($totalRows / $perPage)); $page = (int) ($this->request->getGet('page') ?? 1); if ($page < 1) $page = 1; if ($page > $totalPages) $page = $totalPages; $offset = ($page - 1) * $perPage; $teachersPage = array_slice($teachers, $offset, $perPage); return view('grading/homework_tracking', [ 'semester' => $this->semester, 'schoolYear' => $this->schoolYear, 'sundays' => $sundays, 'eventDays' => $eventDays, 'dateToIndex' => $dateToIndex, 'teachers' => $teachersPage, 'hasHomework' => $hasHomework, 'hwEnteredAt' => $hwEnteredAt, 'homeworkSubmissionCounts' => $homeworkSubmissionCounts, 'hasHomeworkByDate' => $hasHomeworkByDate, 'hwEnteredAtByDate' => $hwEnteredAtByDate, 'page' => $page, 'totalPages' => $totalPages, 'perPage' => $perPage, 'totalRows' => $totalRows, ]); } /** * Returns true if teacher_class has any rows for the given school year. * The semester argument is ignored because assignments are tracked per year. */ private function hasTeacherAssignments(string $schoolYear, string $semester): bool { $year = trim((string)$schoolYear); if ($year === '') { return false; } if (array_key_exists($year, $this->teacherAssignmentCache)) { return $this->teacherAssignmentCache[$year]; } $cnt = $this->db->table('teacher_class') ->where('school_year', $year) ->countAllResults(); $this->teacherAssignmentCache[$year] = $cnt > 0; return $this->teacherAssignmentCache[$year]; } /** * Compute start/end dates for the given semester within the school year. * Fall: 09/21/{startYear} to 01/18/{startYear+1} * Spring: 01/25/{startYear+1} to 05/31/{startYear+1} * Defaults to today if parsing fails. */ private function getDateRangeForSemester(string $schoolYear, string $semester): array { $startYear = null; if (preg_match('/\b(20\d{2})\b/', $schoolYear, $m)) { $startYear = (int)$m[1]; } if ($startYear === null) { $today = new \DateTime('today'); return [$today, $today]; } $nextYear = $startYear + 1; $sem = strtolower(trim($semester)); $start = $end = null; try { if ($sem === 'spring') { $start = new \DateTime("{$nextYear}-01-25"); $end = new \DateTime("{$nextYear}-05-31"); } else { // default/fall $start = new \DateTime("{$startYear}-09-21"); $end = new \DateTime("{$nextYear}-01-18"); } } catch (\Throwable $e) { $today = new \DateTime('today'); return [$today, $today]; } return [$start, $end]; } }