user = model(User::class); $this->teacherClass = model(TeacherClass::class); $this->config = model(Configuration::class); $this->teacher = model(Teacher::class); $this->student = model(Student::class); $this->classSection = model(ClassSection::class); $this->studentClass = model(StudentClass::class); $this->medical = model(StudentMedicalCondition::class); $this->allergy = model(StudentAllergy::class); $this->staffAttendance = model(StaffAttendance::class); $this->emailService = app(EmailService::class); $this->semester = (string) ($this->config->getConfig('semester') ?? ''); $this->schoolYear = (string) ($this->config->getConfig('school_year') ?? ''); } public function index() { $page = max(1, (int) ($this->request->getGet('page') ?? 1)); $perPage = min(100, max(1, (int) ($this->request->getGet('per_page') ?? 20))); $query = $this->user->newQuery() ->select('users.*') ->join('user_roles', 'user_roles.user_id', '=', 'users.id') ->join('roles', 'roles.id', '=', 'user_roles.role_id') ->whereIn('LOWER(roles.name)', ['teacher', 'teacher_assistant']) ->groupBy('users.id') ->orderBy('users.lastname', 'ASC'); $result = $this->paginate($query, $page, $perPage); foreach ($result['data'] as &$teacher) { unset($teacher['password'], $teacher['token']); $assignments = $this->teacherClass->newQuery() ->where('teacher_id', $teacher['id']) ->where('school_year', $this->schoolYear) ->where('semester', $this->semester) ->get() ->toArray(); $teacher['class_assignments'] = $assignments; } return $this->success($result, 'Teachers retrieved successfully'); } public function show($id = null) { $teacher = $this->user->newQuery() ->select('users.*') ->join('user_roles', 'user_roles.user_id', '=', 'users.id') ->join('roles', 'roles.id', '=', 'user_roles.role_id') ->whereIn('LOWER(roles.name)', ['teacher', 'teacher_assistant']) ->where('users.id', $id) ->first(); if (!$teacher) { return $this->respondError('Teacher not found', Response::HTTP_NOT_FOUND); } $teacherArray = $teacher->toArray(); unset($teacherArray['password'], $teacherArray['token']); $assignments = $this->teacherClass->newQuery() ->where('teacher_id', $id) ->where('school_year', $this->schoolYear) ->where('semester', $this->semester) ->get() ->toArray(); $teacherArray['class_assignments'] = $assignments; return $this->success($teacherArray, 'Teacher retrieved successfully'); } public function getClasses($id = null) { $assignments = $this->teacherClass->newQuery() ->select([ 'tc.*', 'cs.class_section_name', 'c.name as class_name', ]) ->from('teacher_class as tc') ->leftJoin('class_sections as cs', 'cs.class_section_id', '=', 'tc.class_section_id') ->leftJoin('classes as c', 'c.id', '=', 'cs.class_id') ->where('tc.teacher_id', $id) ->where('tc.school_year', $this->schoolYear) ->where('tc.semester', $this->semester) ->orderBy('cs.class_section_name', 'ASC') ->get() ->toArray(); return $this->success($assignments, 'Teacher classes retrieved successfully'); } /** * GET /api/v1/teachers/class-view * Get class view data for the logged-in teacher */ public function getClassView() { try { $userId = $this->getCurrentUserId(); if (!$userId) { return $this->respondError('User not logged in', Response::HTTP_UNAUTHORIZED); } // Fetch all assignments (as main or TA) $classes = $this->teacherClass->getClassAssignmentsByUserId($userId, $this->schoolYear); if (empty($classes)) { return $this->success([ 'has_classes' => false, 'message' => 'You do not have an assigned class yet. Please contact the administration.', ], 'No classes found'); } // Identify user and their role $user = $this->user->find($userId); if (!$user) { return $this->respondError('User not found', Response::HTTP_NOT_FOUND); } $roles = DB::table('user_roles ur') ->select('r.name') ->join('roles r', 'r.id', '=', 'ur.role_id') ->where('ur.user_id', $userId) ->get() ->map(fn($r) => strtolower($r->name)) ->toArray(); $roleName = 'teacher'; if (in_array('teacher', $roles)) { $roleName = 'teacher'; } elseif (in_array('teacher_assistant', $roles)) { $roleName = 'teacher_assistant'; } else { return $this->respondError('Access denied', Response::HTTP_FORBIDDEN); } // Collect class section IDs $classSectionIds = array_column($classes, 'class_section_id'); $studentsData = []; if (!empty($classSectionIds)) { // Fetch all students for the selected class sections $students = $this->studentClass->getStudentsByClassSectionIds($classSectionIds); if (!empty($students)) { // Collect unique student IDs $studentIds = array_values(array_unique(array_map( static fn(array $row) => (int) ($row['student_id'] ?? 0), $students ))); $studentIds = array_values(array_filter($studentIds)); // Bulk fetch medical conditions & allergies grouped by student_id $conditionsByStudent = !empty($studentIds) ? $this->medical->getMedicalByStudentIds($studentIds) : []; $allergiesByStudent = !empty($studentIds) ? $this->allergy->getAllergiesByStudentIds($studentIds) : []; // Attach medical/allergy data to each student foreach ($students as $student) { $sid = (int) ($student['student_id'] ?? 0); $cid = (int) ($student['class_section_id'] ?? 0); $medList = array_column($conditionsByStudent[$sid] ?? [], 'condition_name'); $algList = array_column($allergiesByStudent[$sid] ?? [], 'allergy'); $student['medical_conditions'] = $medList; $student['allergies'] = $algList; $student['medical_conditions_text'] = implode(', ', $medList); $student['allergies_text'] = implode(', ', $algList); $studentsData[$cid][] = $student; } } } // Build assigned staff lists (all Main teachers and all TAs) per class section $assignedNames = []; if (!empty($classSectionIds)) { $rows = DB::table('teacher_class tc') ->select('tc.class_section_id', 'tc.position', 'u.firstname', 'u.lastname') ->join('users u', 'u.id', '=', 'tc.teacher_id') ->whereIn('tc.class_section_id', $classSectionIds) ->where('tc.school_year', $this->schoolYear) ->where('tc.semester', $this->semester) ->orderBy('tc.class_section_id', 'ASC') ->orderByRaw("FIELD(tc.position,'main','ta')") ->orderBy('u.firstname', 'ASC') ->get(); foreach ($rows as $r) { $sid = (int) ($r->class_section_id ?? 0); $pos = strtolower((string) ($r->position ?? '')); $name = trim(($r->firstname ?? '') . ' ' . ($r->lastname ?? '')); if ($sid === 0 || $name === '') continue; if (in_array($pos, ['main', 'teacher'], true)) { $assignedNames[$sid]['mains'][] = $name; } elseif (in_array($pos, ['ta', 'assistant', 'teacher_assistant'], true)) { $assignedNames[$sid]['tas'][] = $name; } else { $assignedNames[$sid]['others'][] = $name; } } } $classSectionId = $classSectionIds[0] ?? null; return $this->success([ 'teacher_name' => trim(($user['firstname'] ?? '') . ' ' . ($user['lastname'] ?? '')), 'role_name' => $roleName, 'classes' => $classes, 'students' => $studentsData[$classSectionId] ?? [], 'studentsData' => $studentsData, 'class_section_id' => $classSectionId, 'assignedNames' => $assignedNames, ], 'Class view data retrieved successfully'); } catch (\Exception $e) { Log::error('Error in TeacherController::getClassView(): ' . $e->getMessage()); return $this->respondError('An error occurred. Please try again later.', Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * GET /api/v1/teachers/class-assignments * Get teacher class assignment data for administration */ public function getTeacherClassAssignments() { $forYear = trim((string) ($this->request->getGet('schoolYear') ?? $this->request->getGet('school_year') ?? '')); $payload = $this->buildTeacherClassAssignmentPayload($forYear !== '' ? $forYear : null); $payload['ok'] = true; return $this->success($payload, 'Teacher class assignments retrieved successfully'); } /** * POST /api/v1/teachers/class-assignments/assign * Assign a teacher to a class */ public function assignClassTeacher() { $data = $this->payloadData(); $result = $this->handleAssignClassTeacher($data); $statusCode = $result['ok'] ? Response::HTTP_OK : Response::HTTP_UNPROCESSABLE_ENTITY; return response()->json($result, $statusCode); } /** * DELETE /api/v1/teachers/class-assignments * Delete a teacher class assignment */ public function deleteAssignment() { $data = $this->payloadData(); $result = $this->handleDeleteAssignment($data); $statusCode = $result['ok'] ? Response::HTTP_OK : Response::HTTP_UNPROCESSABLE_ENTITY; return response()->json($result, $statusCode); } /** * GET /api/v1/teachers/absence-form * Get absence form data for the logged-in teacher */ public function getAbsenceForm() { $userId = $this->getCurrentUserId(); if (!$userId) { return $this->respondError('Please log in first.', Response::HTTP_UNAUTHORIZED); } $teacher = $this->user->find($userId); $displayName = $teacher ? trim(($teacher['firstname'] ?? '') . ' ' . ($teacher['lastname'] ?? '')) : 'Unknown'; // Fetch existing self-reported staff attendance rows for this user (current term) $existing = $this->staffAttendance ->where('user_id', $userId) ->where('semester', $this->semester) ->where('school_year', $this->schoolYear) ->orderBy('date', 'DESC') ->findAll(); return $this->success([ 'teacher_name' => $displayName, 'semester' => $this->semester, 'schoolYear' => $this->schoolYear, 'existing' => $existing, 'availableDates' => $this->allowedAbsenceDates(), ], 'Absence form data retrieved successfully'); } /** * POST /api/v1/teachers/absence * Submit absence/vacation request */ public function submitAbsence() { $userId = $this->getCurrentUserId(); if (!$userId) { return $this->respondError('Please log in first.', Response::HTTP_UNAUTHORIZED); } if ($this->semester === '' || $this->schoolYear === '') { return $this->respondError('Semester or school year not configured.', Response::HTTP_BAD_REQUEST); } $data = $this->payloadData(); $dates = (array) ($data['dates'] ?? []); $reasonType = trim((string) ($data['reason_type'] ?? '')); $reasonText = trim((string) ($data['reason'] ?? '')); if ($reasonText === '') { return $this->respondValidationError(['reason' => ['Reason is required.']]); } // Build reason string $reasonBase = ($reasonType !== '') ? strtolower($reasonType) : ''; $reason = $reasonBase !== '' ? ($reasonBase . ': ' . $reasonText) : $reasonText; // Enforce allowed Sundays list $allowedDates = $this->allowedAbsenceDates(); $allowedSet = array_fill_keys($allowedDates, true); $roleName = $this->user->getUserRole($userId) ?: null; // Validate dates and save $saved = 0; $invalid = []; $savedDates = []; $dates = array_values(array_unique(array_map('strval', $dates))); foreach ($dates as $d) { $d = trim((string) $d); if ($d === '') continue; $dt = \DateTimeImmutable::createFromFormat('Y-m-d', $d); if (!$dt || $dt->format('Y-m-d') !== $d || empty($allowedSet[$d])) { $invalid[] = $d; continue; } $ok = $this->staffAttendance->upsertOne( userId: $userId, roleName: $roleName, date: $d, semester: $this->semester, schoolYear: $this->schoolYear, status: StaffAttendance::STATUS_ABSENT, reason: $reason, editorId: $userId ); if ($ok) { $saved++; $savedDates[] = $d; } } if (!empty($invalid)) { return $this->respondError('Invalid dates: ' . implode(', ', $invalid), Response::HTTP_BAD_REQUEST); } // Send notification email to principal try { $user = $this->user->find($userId) ?: []; $fullName = trim(($user['firstname'] ?? '') . ' ' . ($user['lastname'] ?? '')) ?: 'Unknown'; $userEmail = $user['email'] ?? ''; $role = $roleName ?: 'staff'; $dateList = !empty($savedDates) ? implode(', ', $savedDates) : implode(', ', $dates); // If teacher/TA, include assigned class(es) for current term $assignedText = '-'; if (in_array(strtolower((string) $roleName), ['teacher', 'teacher_assistant'], true)) { try { $assignments = $this->teacherClass->getClassAssignmentsByUserId($userId, $this->schoolYear, $this->semester); if (!empty($assignments)) { $parts = []; foreach ($assignments as $as) { $name = (string) ($as['class_section_name'] ?? ''); $r = (string) ($as['teacher_role'] ?? ''); if ($name !== '') { $parts[] = $r ? ($name . ' (' . $r . ')') : $name; } } if (!empty($parts)) { $assignedText = implode(', ', $parts); } else { $assignedText = 'No assigned class'; } } else { $assignedText = 'No assigned class'; } } catch (\Throwable $e) { $assignedText = 'N/A'; } } $subject = sprintf('TimeOff Request: %s (%s) — %s', $fullName, ucfirst((string) $role), $dateList ?: 'No dates'); $submittedAt = utc_now(); $body = '
A staff time-off request was submitted from the teacher portal.
' . '| Name | ' . esc($fullName) . ' |
| ' . esc($userEmail) . ' | |
| Role | ' . esc((string) $role) . ' |
| Semester | ' . esc($this->semester) . ' |
| School Year | ' . esc($this->schoolYear) . ' |
| Reason Type | ' . esc($reasonType ?: '-') . ' |
| Reason | ' . esc($reasonText) . ' |
| Dates | ' . esc($dateList ?: '-') . ' |
| Assigned Class(es) | ' . esc($assignedText) . ' |
| Submitted At | ' . esc($submittedAt) . ' |