request->getGet('class_id'); $search = $this->request->getGet('search'); $search = is_string($search) ? trim($search) : ''; $unmappedOnly = $this->request->getGet('unmapped_only') === '1' || $this->request->getGet('unmapped_only') === 'true'; $page = max(1, (int) ($this->request->getGet('page') ?? 1)); $perPage = max(5, min(100, (int) ($this->request->getGet('per_page') ?? 20))); $db = \Config\Database::connect(); $builder = $db->table('students') ->select('students.id, students.nisn, students.name, students.gender, students.class_id, students.is_active, classes.grade, classes.major, classes.name AS class_name') ->join('classes', 'classes.id = students.class_id', 'left') ->orderBy('students.name', 'ASC'); if ($unmappedOnly) { $builder->where('students.class_id', null); } elseif ($classId !== null && $classId !== '') { $builder->where('students.class_id', (int) $classId); } if ($search !== '') { $builder->groupStart() ->like('students.name', $search) ->orLike('students.nisn', $search) ->groupEnd(); } $total = $builder->countAllResults(false); $rows = $builder->limit($perPage, ($page - 1) * $perPage)->get()->getResultArray(); $data = array_map(static function ($r) { $grade = $r['grade'] !== null ? trim((string) $r['grade']) : ''; $major = $r['major'] !== null ? trim((string) $r['major']) : ''; $cName = $r['class_name'] !== null ? trim((string) $r['class_name']) : ''; $parts = array_filter([$grade, $major, $cName], static fn ($v) => $v !== ''); $fullLabel = $parts !== [] ? implode(' ', $parts) : null; return [ 'id' => (int) $r['id'], 'nisn' => (string) $r['nisn'], 'name' => (string) $r['name'], 'gender' => $r['gender'] !== null ? (string) $r['gender'] : null, 'class_id' => $r['class_id'] !== null ? (int) $r['class_id'] : null, 'class_label' => $fullLabel, 'is_active' => (int) ($r['is_active'] ?? 1), ]; }, $rows); $totalPages = $total > 0 ? (int) ceil($total / $perPage) : 0; $meta = [ 'total' => $total, 'page' => $page, 'per_page' => $perPage, 'total_pages' => $totalPages, ]; return $this->successResponse($data, 'Students', $meta); } /** * POST /api/academic/students */ public function create(): ResponseInterface { $payload = $this->request->getJSON(true) ?? []; $data = $this->payloadToStudentData($payload); $model = new StudentModel(); if (! $model->validate($data)) { return $this->errorResponse( implode(' ', $model->errors()), $model->errors(), null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY ); } $id = $model->insert($data); if ($id === false) { return $this->errorResponse('Gagal menyimpan siswa', null, null, ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } $row = $model->find($id); return $this->successResponse($this->rowToResponse($row), 'Siswa berhasil ditambahkan', null, ResponseInterface::HTTP_CREATED); } /** * PUT /api/academic/students/{id} */ public function update(int $id): ResponseInterface { $model = new StudentModel(); $row = $model->find($id); if (! $row) { return $this->errorResponse('Siswa tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND); } $payload = $this->request->getJSON(true) ?? []; $data = $this->payloadToStudentData($payload, $row); if (! $model->validate(array_merge(['id' => $id], $data))) { return $this->errorResponse( implode(' ', $model->errors()), $model->errors(), null, ResponseInterface::HTTP_UNPROCESSABLE_ENTITY ); } if ($model->update($id, $data) === false) { return $this->errorResponse('Gagal mengubah siswa', null, null, ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } $updated = $model->find($id); return $this->successResponse($this->rowToResponse($updated), 'Siswa berhasil diubah'); } /** * DELETE /api/academic/students/{id} */ public function delete(int $id): ResponseInterface { $model = new StudentModel(); if (! $model->find($id)) { return $this->errorResponse('Siswa tidak ditemukan', null, null, ResponseInterface::HTTP_NOT_FOUND); } if ($model->delete($id) === false) { return $this->errorResponse('Gagal menghapus siswa', null, null, ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } return $this->successResponse(null, 'Siswa berhasil dihapus'); } private function payloadToStudentData(array $payload, $existing = null): array { $classId = isset($payload['class_id']) && $payload['class_id'] !== '' && $payload['class_id'] !== null ? (int) $payload['class_id'] : null; $gender = isset($payload['gender']) && in_array($payload['gender'], ['L', 'P'], true) ? $payload['gender'] : ($existing && isset($existing->gender) ? $existing->gender : null); $isActive = array_key_exists('is_active', $payload) ? (int) (bool) $payload['is_active'] : ($existing && isset($existing->is_active) ? (int) $existing->is_active : 1); return [ 'nisn' => trim($payload['nisn'] ?? $existing->nisn ?? ''), 'name' => trim($payload['name'] ?? $existing->name ?? ''), 'gender' => $gender, 'class_id' => $classId, 'is_active'=> $isActive, ]; } private function rowToResponse($row): array { $classId = $row->class_id ?? null; $classLabel = null; if ($classId !== null) { $classModel = new ClassModel(); $c = $classModel->find($classId); if ($c) { $classLabel = trim($c->grade . ' ' . $c->major . ' ' . $c->name) ?: (string) $c->name; } } return [ 'id' => (int) $row->id, 'nisn' => (string) $row->nisn, 'name' => (string) $row->name, 'gender' => $row->gender !== null ? (string) $row->gender : null, 'class_id' => $classId !== null ? (int) $classId : null, 'class_label' => $classLabel, 'is_active' => (int) ($row->is_active ?? 1), ]; } }