calendar = model(Calendar::class); $this->homework = model(Homework::class); $this->config = model(Configuration::class); $this->schoolYear = (string) ($this->config->getConfig('school_year') ?? ''); $this->semester = (string) ($this->config->getConfig('semester') ?? ''); } /** * Get homework tracking data with Sunday-based schedule */ public function index() { try { // Get date range from request or use defaults $startDate = $this->request->getGet('start_date') ?? '2025-09-21'; $endDate = $this->request->getGet('end_date') ?? '2026-01-18'; // Parse dates try { $start = new \DateTime($startDate); } catch (\Throwable $e) { $start = new \DateTime('today'); } try { $end = new \DateTime($endDate); } catch (\Throwable $e) { $end = new \DateTime('today'); } // 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->calendar->getEventsBySchoolYear($this->schoolYear) ?? []; $eventDays = []; foreach ($events as $ev) { $ymd = substr((string)($ev['date'] ?? $ev['start_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) } } // Aggregate homework presence + first entered date per class_section_id + homework_index $rows = DB::table('homework') ->select('class_section_id', 'homework_index', DB::raw('MIN(created_at) AS first_created'), DB::raw('COUNT(*) AS cnt')) ->where('school_year', $this->schoolYear) ->where('semester', $this->semester) ->groupBy('class_section_id', 'homework_index') ->get() ->toArray(); $hasHomework = []; $hwEnteredAt = []; 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; } } // Build date-based presence mapped to the nearest prior non-NoSchool Sunday $rowsByDate = DB::table('homework') ->select(DB::raw('class_section_id, DATE(created_at) AS hw_date, MIN(created_at) AS first_created, COUNT(*) AS cnt')) ->where('school_year', $this->schoolYear) ->where('semester', $this->semester) ->groupBy(DB::raw('class_section_id, DATE(created_at)')) ->orderBy('hw_date', 'ASC') ->get() ->toArray(); $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 $teacherRows = DB::table('teacher_class as tc') ->select('tc.class_section_id', 'tc.position', 'cs.class_section_name', 'cs.class_id', 'u.firstname', 'u.lastname') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'tc.class_section_id') ->leftJoin('users as u', 'u.id', '=', 'tc.teacher_id') ->where('tc.school_year', $this->schoolYear) ->where('tc.semester', $this->semester) ->whereNotNull('tc.class_section_id') ->whereIn('tc.position', ['main', 'ta']) ->orderBy('cs.class_id', 'ASC') ->orderBy('cs.class_section_name', 'ASC') ->orderByRaw("FIELD(tc.position, 'main','ta')") ->orderBy('u.firstname', 'ASC') ->get() ->toArray(); $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 $this->success([ 'semester' => $this->semester, 'school_year' => $this->schoolYear, 'sundays' => $sundays, 'event_days' => $eventDays, 'date_to_index' => $dateToIndex, 'teachers' => $teachersPage, 'has_homework' => $hasHomework, 'hw_entered_at' => $hwEnteredAt, 'has_homework_by_date' => $hasHomeworkByDate, 'hw_entered_at_by_date' => $hwEnteredAtByDate, 'pagination' => [ 'page' => $page, 'total_pages' => $totalPages, 'per_page' => $perPage, 'total_rows' => $totalRows, ], ], 'Homework tracking retrieved successfully'); } catch (\Throwable $e) { log_message('error', 'Homework tracking error: ' . $e->getMessage()); return $this->respondError('Failed to retrieve homework tracking: ' . $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR); } } }