From 0d8a13d3ac08e75b3222dda2c1613e02d616ac47 Mon Sep 17 00:00:00 2001 From: GalangSefin <158027366+GalangSefin@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:48:13 +0700 Subject: [PATCH] upload all changes in project sirekom --- .htaccess | 15 + .../Controllers/Admin/SiswaController.php | 2 +- .../Api/Admin/AlumniController.php | 229 ++++--- .../Api/Admin/DashboardAdminController.php | 103 ++-- .../Controllers/Api/Admin/GuruController.php | 260 ++++++-- .../Api/Admin/JurusanController.php | 215 +++++-- .../Api/Admin/ProspekKerjaController.php | 237 +++++--- .../Api/Admin/RiwayatController.php | 85 ++- .../Controllers/Api/Admin/SiswaController.php | 211 ++++--- app/Http/Controllers/Api/AuthController.php | 18 +- .../Student/DashboardStudentController.php | 40 +- .../Student/RecommendationApiController.php | 223 ++++--- app/Models/Alumni.php | 17 +- app/Models/Guru.php | 29 +- app/Models/Jurusan.php | 2 +- app/Models/Siswa.php | 12 +- app/Models/User.php | 5 +- app/Services/PythonRecommendationService.php | 89 +++ config/services.php | 19 +- flask_api/requirements.txt | 2 +- index.php | 18 + resources/views/admin/alumni/alumni.blade.php | 99 ++- .../views/admin/alumni/alumnicreate.blade.php | 120 +++- .../views/admin/alumni/alumniedit.blade.php | 171 +++++- resources/views/admin/dashboard.blade.php | 61 +- resources/views/admin/guru/guru.blade.php | 93 ++- .../views/admin/guru/gurucreate.blade.php | 148 ++++- resources/views/admin/guru/guruedit.blade.php | 161 ++++- .../views/admin/jurusan/jurusan.blade.php | 119 ++-- .../admin/jurusan/jurusancreate.blade.php | 124 +++- .../views/admin/jurusan/jurusanedit.blade.php | 172 +++++- .../admin/prospek/prospekkerja.blade.php | 102 +++- .../prospek/prospekkerjacreate.blade.php | 191 +++++- .../admin/prospek/prospekkerjaedit.blade.php | 238 ++++++-- resources/views/admin/riwayat.blade.php | 213 ++++--- resources/views/admin/siswa/siswa.blade.php | 324 ++++------ .../views/admin/siswa/siswacreate.blade.php | 185 ++++-- .../views/admin/siswa/siswaedit.blade.php | 232 ++++--- resources/views/auth/login.blade.php | 34 +- resources/views/student/dashboard.blade.php | 144 +++-- resources/views/student/hasil.blade.php | 113 ++-- resources/views/student/prediksi.blade.php | 567 ++++++++---------- routes/api.php | 80 ++- routes/web.php | 8 +- 44 files changed, 3858 insertions(+), 1672 deletions(-) create mode 100644 .htaccess create mode 100644 app/Services/PythonRecommendationService.php create mode 100644 index.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..a3564e1 --- /dev/null +++ b/.htaccess @@ -0,0 +1,15 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + + RewriteRule ^ index.php [L] + \ No newline at end of file diff --git a/app/Http/Controllers/Admin/SiswaController.php b/app/Http/Controllers/Admin/SiswaController.php index 36c8194..fa1f989 100644 --- a/app/Http/Controllers/Admin/SiswaController.php +++ b/app/Http/Controllers/Admin/SiswaController.php @@ -16,7 +16,7 @@ public function create() return view('admin.siswa.siswacreate'); } - public function edit($id) + public function edit($id) { return view('admin.siswa.siswaedit', compact('id')); } diff --git a/app/Http/Controllers/Api/Admin/AlumniController.php b/app/Http/Controllers/Api/Admin/AlumniController.php index 24c01ba..b2396ca 100644 --- a/app/Http/Controllers/Api/Admin/AlumniController.php +++ b/app/Http/Controllers/Api/Admin/AlumniController.php @@ -6,102 +6,195 @@ use App\Models\Alumni; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; +use Throwable; class AlumniController extends Controller { public function index(Request $request) { - $perPage = (int) $request->input('per_page', 7); - $perPage = in_array($perPage, [5, 7]) ? $perPage : 7; + try { + $perPage = (int) $request->input('per_page', 7); + $perPage = in_array($perPage, [5, 7, 10, 25, 50, 100]) ? $perPage : 7; - $search = trim((string) $request->input('search', '')); + $search = trim((string) $request->input('search', '')); - $query = Alumni::query()->orderBy('id', 'desc'); + $query = Alumni::query()->orderBy('id', 'desc'); - if ($search !== '') { - $query->where(function ($q) use ($search) { - $q->where('id_alumni', 'like', "%{$search}%") - ->orWhere('nama_alumni', 'like', "%{$search}%") - ->orWhere('perguruan_tinggi', 'like', "%{$search}%") - ->orWhere('jurusan_diambil', 'like', "%{$search}%"); - }); + if ($search !== '') { + $query->where(function ($q) use ($search) { + $q->where('id_alumni', 'like', "%{$search}%") + ->orWhere('nama_alumni', 'like', "%{$search}%") + ->orWhere('perguruan_tinggi', 'like', "%{$search}%") + ->orWhere('jurusan_diambil', 'like', "%{$search}%"); + }); + } + + $alumni = $query->paginate($perPage); + + return response()->json([ + 'success' => true, + 'message' => 'Data alumni berhasil dimuat.', + 'data' => $alumni->items(), + 'current_page' => $alumni->currentPage(), + 'last_page' => $alumni->lastPage(), + 'per_page' => $alumni->perPage(), + 'total' => $alumni->total(), + 'from' => $alumni->firstItem(), + 'to' => $alumni->lastItem(), + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Gagal memuat data alumni.', + 'error' => $e->getMessage(), + ], 500); } - - $alumni = $query->paginate($perPage); - - return response()->json([ - 'success' => true, - 'data' => $alumni->items(), - 'current_page' => $alumni->currentPage(), - 'last_page' => $alumni->lastPage(), - 'per_page' => $alumni->perPage(), - 'total' => $alumni->total(), - 'from' => $alumni->firstItem(), - 'to' => $alumni->lastItem(), - ], 200); } public function store(Request $request) { - $validator = Validator::make($request->all(), [ - 'nama_alumni' => 'required|string|max:255', - 'perguruan_tinggi' => 'required|string', - 'jurusan_diambil' => 'required|string', - ], [ - 'nama_alumni.required' => 'Nama alumni wajib diisi.', - 'perguruan_tinggi.required' => 'Nama Perguruan Tinggi harus diisi.', - 'jurusan_diambil.required' => 'Jurusan yang diambil tidak boleh kosong.', - ]); + try { + $validator = Validator::make($request->all(), [ + 'nama_alumni' => 'required|string|max:255', + 'perguruan_tinggi' => 'required|string|max:255', + 'jurusan_diambil' => 'required|string|max:255', + ], [ + 'nama_alumni.required' => 'Nama alumni wajib diisi.', + 'perguruan_tinggi.required' => 'Nama perguruan tinggi wajib diisi.', + 'jurusan_diambil.required' => 'Jurusan yang diambil wajib diisi.', + ]); - if ($validator->fails()) { - return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal.', + 'errors' => $validator->errors(), + ], 422); + } + + $alumni = new Alumni(); + $alumni->nama_alumni = $request->input('nama_alumni'); + $alumni->perguruan_tinggi = $request->input('perguruan_tinggi'); + $alumni->jurusan_diambil = $request->input('jurusan_diambil'); + $alumni->profil_nilai = $request->input('profil_nilai'); + $alumni->profil_minat = $request->input('profil_minat'); + $alumni->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Data alumni berhasil ditambahkan.', + 'data' => $alumni, + ], 201); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat menyimpan data alumni.', + 'error' => $e->getMessage(), + ], 500); } + } - $alumni = Alumni::create($request->all()); + public function show($id) + { + try { + $alumni = Alumni::find($id); - return response()->json([ - 'success' => true, - 'message' => 'Alumni ' . $alumni->id_alumni . ' berhasil ditambahkan.', - 'data' => $alumni - ], 201); + if (!$alumni) { + return response()->json([ + 'success' => false, + 'message' => 'Data alumni tidak ditemukan.', + ], 404); + } + + return response()->json([ + 'success' => true, + 'message' => 'Data alumni berhasil ditemukan.', + 'data' => $alumni, + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat mengambil data alumni.', + 'error' => $e->getMessage(), + ], 500); + } } public function update(Request $request, $id) { - $alumni = Alumni::find($id); + try { + $alumni = Alumni::find($id); - if (!$alumni) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$alumni) { + return response()->json([ + 'success' => false, + 'message' => 'Data alumni tidak ditemukan.', + ], 404); + } + + $validator = Validator::make($request->all(), [ + 'nama_alumni' => 'required|string|max:255', + 'perguruan_tinggi' => 'required|string|max:255', + 'jurusan_diambil' => 'required|string|max:255', + ], [ + 'nama_alumni.required' => 'Nama alumni wajib diisi.', + 'perguruan_tinggi.required' => 'Nama perguruan tinggi wajib diisi.', + 'jurusan_diambil.required' => 'Jurusan yang diambil wajib diisi.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal.', + 'errors' => $validator->errors(), + ], 422); + } + + $alumni->nama_alumni = $request->input('nama_alumni'); + $alumni->perguruan_tinggi = $request->input('perguruan_tinggi'); + $alumni->jurusan_diambil = $request->input('jurusan_diambil'); + $alumni->profil_nilai = $request->input('profil_nilai'); + $alumni->profil_minat = $request->input('profil_minat'); + $alumni->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Data alumni berhasil diperbarui.', + 'data' => $alumni, + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat memperbarui data alumni.', + 'error' => $e->getMessage(), + ], 500); } - - $validator = Validator::make($request->all(), [ - 'nama_alumni' => 'required|string|max:255', - 'perguruan_tinggi' => 'required|string', - 'jurusan_diambil' => 'required|string', - ]); - - if ($validator->fails()) { - return response()->json(['success' => false, 'errors' => $validator->errors()], 422); - } - - $alumni->update($request->all()); - - return response()->json([ - 'success' => true, - 'message' => 'Data ' . $alumni->id_alumni . ' berhasil diperbarui' - ], 200); } public function destroy($id) { - $alumni = Alumni::find($id); + try { + $alumni = Alumni::find($id); - if (!$alumni) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$alumni) { + return response()->json([ + 'success' => false, + 'message' => 'Data alumni tidak ditemukan.', + ], 404); + } + + $alumni->delete(); + + return response()->json([ + 'success' => true, + 'message' => 'Data alumni berhasil dihapus.', + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Data alumni tidak dapat dihapus karena masih digunakan atau terjadi kesalahan server.', + 'error' => $e->getMessage(), + ], 500); } - - $alumni->delete(); - - return response()->json(['success' => true, 'message' => 'Data Alumni berhasil dihapus'], 200); } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/Admin/DashboardAdminController.php b/app/Http/Controllers/Api/Admin/DashboardAdminController.php index 8b96aba..c0bcd70 100644 --- a/app/Http/Controllers/Api/Admin/DashboardAdminController.php +++ b/app/Http/Controllers/Api/Admin/DashboardAdminController.php @@ -3,40 +3,41 @@ namespace App\Http\Controllers\Api\Admin; use App\Http\Controllers\Controller; -use App\Models\User; use App\Models\RiwayatRekomendasi; +use App\Models\Siswa; +use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Schema; -use Exception; +use Throwable; class DashboardAdminController extends Controller { public function index(Request $request) { try { - $tableName = (new RiwayatRekomendasi())->getTable(); + $riwayatModel = new RiwayatRekomendasi(); + $tableName = $riwayatModel->getTable(); - $totalSiswa = User::whereIn('role', ['siswa', 'student'])->count(); + $totalSiswa = 0; + + try { + $siswaModel = new Siswa(); + if (Schema::hasTable($siswaModel->getTable())) { + $totalSiswa = Siswa::count(); + } + } catch (Throwable $e) { + $totalSiswa = 0; + } if ($totalSiswa === 0) { - $totalSiswa = RiwayatRekomendasi::distinct('user_id')->count('user_id'); - } - - $totalRekomendasi = RiwayatRekomendasi::count(); - - $kolomAkurasi = null; - - if (Schema::hasColumn($tableName, 'akurasi_prediksi')) { - $kolomAkurasi = 'akurasi_prediksi'; - } elseif (Schema::hasColumn($tableName, 'probabilitas')) { - $kolomAkurasi = 'probabilitas'; - } elseif (Schema::hasColumn($tableName, 'akurasi')) { - $kolomAkurasi = 'akurasi'; + $totalSiswa = User::whereIn('role', ['siswa', 'student'])->count(); } + $totalRekomendasi = Schema::hasTable($tableName) ? RiwayatRekomendasi::count() : 0; + $kolomAkurasi = $this->getKolomAkurasi($tableName); $rataAkurasi = null; - if ($kolomAkurasi) { + if ($kolomAkurasi && Schema::hasTable($tableName)) { $avg = RiwayatRekomendasi::whereNotNull($kolomAkurasi)->avg($kolomAkurasi); $rataAkurasi = $avg !== null ? round((float) $avg, 2) : null; } @@ -45,44 +46,64 @@ public function index(Request $request) ? 'Berdasarkan rata-rata tingkat keyakinan dari seluruh hasil rekomendasi.' : 'Belum ada nilai keyakinan prediksi yang tersimpan. Lakukan prediksi baru terlebih dahulu.'; - $selectDistribusi = 'hasil_rekomendasi_jurusan as jurusan, COUNT(*) as total'; + $distribusiJurusan = collect(); - if ($kolomAkurasi) { - $selectDistribusi .= ', ROUND(AVG(' . $kolomAkurasi . '), 2) as rata_akurasi'; + if (Schema::hasTable($tableName) && Schema::hasColumn($tableName, 'hasil_rekomendasi_jurusan')) { + $selectDistribusi = 'hasil_rekomendasi_jurusan as jurusan, COUNT(*) as total'; + + if ($kolomAkurasi) { + $selectDistribusi .= ', ROUND(AVG(' . $kolomAkurasi . '), 2) as rata_akurasi'; + } + + $distribusiJurusan = RiwayatRekomendasi::selectRaw($selectDistribusi) + ->whereNotNull('hasil_rekomendasi_jurusan') + ->groupBy('hasil_rekomendasi_jurusan') + ->orderBy('total', 'desc') + ->limit(10) + ->get() + ->map(function ($item) use ($kolomAkurasi) { + return [ + 'jurusan' => $item->jurusan, + 'total' => (int) $item->total, + 'rata_akurasi' => $kolomAkurasi && $item->rata_akurasi !== null + ? round((float) $item->rata_akurasi, 2) + : null, + ]; + }); } - $distribusiJurusan = RiwayatRekomendasi::selectRaw($selectDistribusi) - ->whereNotNull('hasil_rekomendasi_jurusan') - ->groupBy('hasil_rekomendasi_jurusan') - ->orderBy('total', 'desc') - ->limit(10) - ->get() - ->map(function ($item) use ($kolomAkurasi) { - return [ - 'jurusan' => $item->jurusan, - 'total' => (int) $item->total, - 'rata_akurasi' => $kolomAkurasi && $item->rata_akurasi !== null - ? round((float) $item->rata_akurasi, 2) - : null, - ]; - }); - return response()->json([ 'success' => true, + 'message' => 'Statistik dashboard berhasil dimuat.', 'data' => [ 'total_siswa' => $totalSiswa, 'rekomendasi_diproses' => $totalRekomendasi, 'rata_rata_akurasi' => $rataAkurasi, 'akurasi_status' => $akurasiStatus, 'distribusi_jurusan' => $distribusiJurusan, - ] + ], ], 200); - - } catch (Exception $e) { + } catch (Throwable $e) { return response()->json([ 'success' => false, - 'message' => 'Error: ' . $e->getMessage() + 'message' => 'Gagal memuat statistik dashboard.', + 'error' => $e->getMessage(), ], 500); } } + + private function getKolomAkurasi(string $tableName): ?string + { + if (!Schema::hasTable($tableName)) { + return null; + } + + foreach (['akurasi_prediksi', 'probabilitas', 'akurasi'] as $column) { + if (Schema::hasColumn($tableName, $column)) { + return $column; + } + } + + return null; + } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/Admin/GuruController.php b/app/Http/Controllers/Api/Admin/GuruController.php index f184040..bff4f41 100644 --- a/app/Http/Controllers/Api/Admin/GuruController.php +++ b/app/Http/Controllers/Api/Admin/GuruController.php @@ -4,98 +4,242 @@ use App\Http\Controllers\Controller; use App\Models\Guru; +use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\DB; +use Throwable; class GuruController extends Controller { + /** + * Display a listing of the resource. + */ public function index(Request $request) { - $perPage = (int) $request->input('per_page', 7); - $perPage = in_array($perPage, [5, 7]) ? $perPage : 7; + try { + $perPage = (int) $request->input('per_page', 7); + $perPage = in_array($perPage, [5, 7, 10, 25]) ? $perPage : 7; - $search = trim((string) $request->input('search', '')); + $search = trim((string) $request->input('search', '')); - $query = Guru::query()->orderBy('id', 'desc'); + $query = Guru::query()->with('user')->orderBy('id', 'desc'); - if ($search !== '') { - $query->where(function ($q) use ($search) { - $q->where('id_guru', 'like', "%{$search}%") - ->orWhere('nama_guru', 'like', "%{$search}%"); - }); + if ($search !== '') { + $query->where(function ($q) use ($search) { + $q->where('id_guru', 'like', "%{$search}%") + ->orWhere('nama_guru', 'like', "%{$search}%"); + }); + } + + $guru = $query->paginate($perPage); + + return response()->json([ + 'success' => true, + 'message' => 'Data guru berhasil dimuat.', + 'data' => $guru->items(), + 'current_page' => $guru->currentPage(), + 'last_page' => $guru->lastPage(), + 'per_page' => $guru->perPage(), + 'total' => $guru->total(), + ], 200); + + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat memuat data guru.', + 'error' => $e->getMessage() + ], 500); } - - $guru = $query->paginate($perPage); - - return response()->json([ - 'success' => true, - 'data' => $guru->items(), - 'current_page' => $guru->currentPage(), - 'last_page' => $guru->lastPage(), - 'per_page' => $guru->perPage(), - 'total' => $guru->total(), - 'from' => $guru->firstItem(), - 'to' => $guru->lastItem(), - ], 200); } + /** + * Store a newly created resource in storage. + */ public function store(Request $request) { $validator = Validator::make($request->all(), [ 'nama_guru' => 'required|string|max:255', - ], [ - 'nama_guru.required' => 'Nama guru tidak boleh kosong.', + 'username' => 'required|string|max:255|unique:users,username', + 'email' => 'required|string|email|max:255|unique:users,email', + 'password' => 'required|string|min:8', ]); if ($validator->fails()) { - return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal.', + 'errors' => $validator->errors() + ], 422); } - $guru = Guru::create($request->all()); + DB::beginTransaction(); - return response()->json([ - 'success' => true, - 'message' => 'Guru ' . $guru->id_guru . ' berhasil ditambahkan.', - 'data' => $guru - ], 201); + try { + // Create User Akun + $user = User::create([ + 'name' => $request->nama_guru, + 'username' => $request->username, + 'email' => $request->email, + 'password' => Hash::make($request->password), + 'role' => 'admin', + ]); + + // Create Data Guru terikat dengan user_id + $guru = Guru::create([ + 'user_id' => $user->id, + 'nama_guru' => $request->nama_guru, + ]); + + DB::commit(); + + $guru->load('user'); + + return response()->json([ + 'success' => true, + 'message' => 'Data guru dan akun login berhasil disimpan.', + 'data' => $guru + ], 201); + + } catch (Throwable $e) { + DB::rollBack(); + + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat menyimpan data guru.', + 'error' => $e->getMessage() + ], 500); + } } + /** + * Display the specified resource. + */ + public function show($id) + { + try { + $guru = Guru::with('user')->find($id); + + if (!$guru) { + return response()->json([ + 'success' => false, + 'message' => 'Data guru tidak ditemukan.' + ], 404); + } + + return response()->json([ + 'success' => true, + 'message' => 'Detail data guru berhasil ditemukan.', + 'data' => $guru + ], 200); + + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat mengambil detail data guru.', + 'error' => $e->getMessage() + ], 500); + } + } + + /** + * Update the specified resource in storage. + */ public function update(Request $request, $id) { - $guru = Guru::find($id); + try { + $guru = Guru::find($id); - if (!$guru) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$guru) { + return response()->json([ + 'success' => false, + 'message' => 'Data guru tidak ditemukan.' + ], 404); + } + + $validator = Validator::make($request->all(), [ + 'nama_guru' => 'required|string|max:255', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal.', + 'errors' => $validator->errors() + ], 422); + } + + DB::beginTransaction(); + + $guru->update([ + 'nama_guru' => $request->nama_guru + ]); + + if ($guru->user) { + $guru->user->update([ + 'name' => $request->nama_guru + ]); + } + + DB::commit(); + $guru->load('user'); + + return response()->json([ + 'success' => true, + 'message' => 'Data guru berhasil diperbarui.', + 'data' => $guru + ], 200); + + } catch (Throwable $e) { + DB::rollBack(); + + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat memperbarui data guru.', + 'error' => $e->getMessage() + ], 500); } - - $validator = Validator::make($request->all(), [ - 'nama_guru' => 'required|string|max:255', - ], [ - 'nama_guru.required' => 'Update gagal, nama guru harus diisi.', - ]); - - if ($validator->fails()) { - return response()->json(['success' => false, 'errors' => $validator->errors()], 422); - } - - $guru->update($request->all()); - - return response()->json([ - 'success' => true, - 'message' => 'Data ' . $guru->id_guru . ' berhasil diperbarui' - ], 200); } + /** + * Remove the specified resource from storage. + */ public function destroy($id) { - $guru = Guru::find($id); + try { + $guru = Guru::find($id); - if (!$guru) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$guru) { + return response()->json([ + 'success' => false, + 'message' => 'Data guru tidak ditemukan.' + ], 404); + } + + DB::beginTransaction(); + + if ($guru->user_id) { + User::destroy($guru->user_id); + } + + $guru->delete(); + + DB::commit(); + + return response()->json([ + 'success' => true, + 'message' => 'Data guru dan akun login berhasil dihapus.' + ], 200); + + } catch (Throwable $e) { + DB::rollBack(); + + return response()->json([ + 'success' => false, + 'message' => 'Data guru tidak dapat dihapus atau terjadi kesalahan server.', + 'error' => $e->getMessage() + ], 500); } - - $guru->delete(); - - return response()->json(['success' => true, 'message' => 'Data Guru berhasil dihapus'], 200); } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/Admin/JurusanController.php b/app/Http/Controllers/Api/Admin/JurusanController.php index 93a5edf..db141ad 100644 --- a/app/Http/Controllers/Api/Admin/JurusanController.php +++ b/app/Http/Controllers/Api/Admin/JurusanController.php @@ -6,98 +6,191 @@ use App\Models\Jurusan; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; +use Throwable; class JurusanController extends Controller { public function index(Request $request) { - $perPage = (int) $request->input('per_page', 7); - $perPage = in_array($perPage, [5, 7]) ? $perPage : 7; + try { + $perPage = (int) $request->input('per_page', 7); + $perPage = in_array($perPage, [5, 7, 10, 25, 50, 100]) ? $perPage : 7; - $search = trim((string) $request->input('search', '')); + $search = trim((string) $request->input('search', '')); - $query = Jurusan::query()->orderBy('id', 'desc'); + // Mengambil data jurusan beserta semua prospek kerja yang terkait dengannya + $query = Jurusan::with('prospeks')->orderBy('id', 'desc'); - if ($search !== '') { - $query->where(function ($q) use ($search) { - $q->where('nama_jurusan', 'like', "%{$search}%") - ->orWhere('rumpun', 'like', "%{$search}%") - ->orWhere('prospek_karir', 'like', "%{$search}%"); - }); + if ($search !== '') { + $query->where(function ($q) use ($search) { + $q->where('nama_jurusan', 'like', "%{$search}%") + ->orWhere('rumpun', 'like', "%{$search}%") + // Memperbaiki pencarian agar mencari ke dalam relasi tabel prospeks + ->orWhereHas('prospeks', function ($relationQuery) use ($search) { + $relationQuery->where('nama_profesi', 'like', "%{$search}%"); + }); + }); + } + + $jurusan = $query->paginate($perPage); + + return response()->json([ + 'success' => true, + 'message' => 'Data jurusan berhasil dimuat.', + 'data' => $jurusan->items(), + 'current_page' => $jurusan->currentPage(), + 'last_page' => $jurusan->lastPage(), + 'per_page' => $jurusan->perPage(), + 'total' => $jurusan->total(), + 'from' => $jurusan->firstItem(), + 'to' => $jurusan->lastItem(), + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat memuat data jurusan.', + 'error' => $e->getMessage(), + ], 500); } - - $jurusan = $query->paginate($perPage); - - return response()->json([ - 'success' => true, - 'data' => $jurusan->items(), - 'current_page' => $jurusan->currentPage(), - 'last_page' => $jurusan->lastPage(), - 'per_page' => $jurusan->perPage(), - 'total' => $jurusan->total(), - 'from' => $jurusan->firstItem(), - 'to' => $jurusan->lastItem(), - ], 200); } - public function store(Request $request) { - $validator = Validator::make($request->all(), [ - 'nama_jurusan' => 'required|string|max:255', - 'rumpun' => 'required|string', - ]); + try { + $validator = Validator::make($request->all(), [ + 'nama_jurusan' => 'required|string|max:255', + 'rumpun' => ['required', 'string', Rule::in(['Saintek', 'Soshum'])], + ], [ + 'nama_jurusan.required' => 'Nama jurusan wajib diisi.', + 'rumpun.required' => 'Rumpun jurusan wajib dipilih.', + 'rumpun.in' => 'Rumpun hanya boleh Saintek atau Soshum.', + ]); - if ($validator->fails()) { - return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal.', + 'errors' => $validator->errors(), + ], 422); + } + + $jurusan = new Jurusan(); + $jurusan->nama_jurusan = $request->input('nama_jurusan'); + $jurusan->rumpun = $request->input('rumpun'); + $jurusan->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Data jurusan berhasil ditambahkan.', + 'data' => $jurusan, + ], 201); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat menyimpan data jurusan.', + 'error' => $e->getMessage(), + ], 500); } - - $jurusan = Jurusan::create($request->all()); - - return response()->json([ - 'success' => true, - 'message' => 'Jurusan berhasil ditambahkan', - 'data' => $jurusan - ], 201); } public function show($id) { - $jurusan = Jurusan::find($id); + try { + $jurusan = Jurusan::find($id); - if (!$jurusan) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$jurusan) { + return response()->json([ + 'success' => false, + 'message' => 'Data jurusan tidak ditemukan.', + ], 404); + } + + return response()->json([ + 'success' => true, + 'message' => 'Data jurusan berhasil ditemukan.', + 'data' => $jurusan, + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat mengambil data jurusan.', + 'error' => $e->getMessage(), + ], 500); } - - return response()->json(['success' => true, 'data' => $jurusan], 200); } public function update(Request $request, $id) { - $jurusan = Jurusan::find($id); + try { + $jurusan = Jurusan::find($id); - if (!$jurusan) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$jurusan) { + return response()->json([ + 'success' => false, + 'message' => 'Data jurusan tidak ditemukan.', + ], 404); + } + + $validator = Validator::make($request->all(), [ + 'nama_jurusan' => [ + 'required', + 'string', + 'max:255', + Rule::unique('jurusans', 'nama_jurusan')->ignore($id), + ], + 'rumpun' => 'required|in:Saintek,Soshum', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal.', + 'errors' => $validator->errors(), + ], 422); + } + + $jurusan->nama_jurusan = $request->input('nama_jurusan'); + $jurusan->rumpun = $request->input('rumpun'); + $jurusan->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Data jurusan berhasil diperbarui.', + 'data' => $jurusan, + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat memperbarui data jurusan.', + 'error' => $e->getMessage(), + ], 500); } - - $jurusan->update($request->all()); - - return response()->json([ - 'success' => true, - 'message' => 'Jurusan berhasil diperbarui', - 'data' => $jurusan - ], 200); } public function destroy($id) { - $jurusan = Jurusan::find($id); + try { + $jurusan = Jurusan::find($id); - if (!$jurusan) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$jurusan) { + return response()->json([ + 'success' => false, + 'message' => 'Data jurusan tidak ditemukan.', + ], 404); + } + + $jurusan->delete(); + + return response()->json([ + 'success' => true, + 'message' => 'Data jurusan berhasil dihapus.', + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Data jurusan tidak dapat dihapus karena masih digunakan pada data lain atau terjadi kesalahan server.', + 'error' => $e->getMessage(), + ], 500); } - - $jurusan->delete(); - - return response()->json(['success' => true, 'message' => 'Jurusan berhasil dihapus'], 200); } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/Admin/ProspekKerjaController.php b/app/Http/Controllers/Api/Admin/ProspekKerjaController.php index b8cf3f1..c3ddc21 100644 --- a/app/Http/Controllers/Api/Admin/ProspekKerjaController.php +++ b/app/Http/Controllers/Api/Admin/ProspekKerjaController.php @@ -6,114 +6,199 @@ use App\Models\ProspekKerja; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; +use Throwable; class ProspekKerjaController extends Controller { public function index(Request $request) { - $perPage = (int) $request->input('per_page', 7); - $perPage = in_array($perPage, [5, 7]) ? $perPage : 7; + try { + $perPage = (int) $request->input('per_page', 7); + $perPage = in_array($perPage, [5, 7, 10, 25, 50, 100]) ? $perPage : 7; - $search = trim((string) $request->input('search', '')); + $search = trim((string) $request->input('search', '')); - $query = ProspekKerja::with('jurusan')->orderBy('id', 'desc'); + $query = ProspekKerja::with('jurusan')->orderBy('id', 'desc'); - if ($search !== '') { - $query->where(function ($q) use ($search) { - $q->where('nama_profesi', 'like', "%{$search}%") - ->orWhere('range_gaji', 'like', "%{$search}%") - ->orWhere('deskripsi_pekerjaan', 'like', "%{$search}%") - ->orWhereHas('jurusan', function ($jurusan) use ($search) { - $jurusan->where('nama_jurusan', 'like', "%{$search}%"); - }); - }); + if ($search !== '') { + $query->where(function ($q) use ($search) { + $q->where('nama_profesi', 'like', "%{$search}%") + ->orWhere('range_gaji', 'like', "%{$search}%") + ->orWhere('deskripsi_pekerjaan', 'like', "%{$search}%") + ->orWhereHas('jurusan', function ($jurusan) use ($search) { + $jurusan->where('nama_jurusan', 'like', "%{$search}%"); + }); + }); + } + + $prospek = $query->paginate($perPage); + + return response()->json([ + 'success' => true, + 'message' => 'Data prospek kerja berhasil dimuat.', + 'data' => $prospek->items(), + 'current_page' => $prospek->currentPage(), + 'last_page' => $prospek->lastPage(), + 'per_page' => $prospek->perPage(), + 'total' => $prospek->total(), + 'from' => $prospek->firstItem(), + 'to' => $prospek->lastItem(), + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Gagal memuat data prospek kerja.', + 'error' => $e->getMessage(), + ], 500); } - - $prospek = $query->paginate($perPage); - - return response()->json([ - 'success' => true, - 'data' => $prospek->items(), - 'current_page' => $prospek->currentPage(), - 'last_page' => $prospek->lastPage(), - 'per_page' => $prospek->perPage(), - 'total' => $prospek->total(), - 'from' => $prospek->firstItem(), - 'to' => $prospek->lastItem(), - ], 200); } public function store(Request $request) { - $validator = Validator::make($request->all(), [ - 'jurusan_id' => 'required|exists:jurusans,id', - 'nama_profesi' => 'required|string|max:255', - 'range_gaji' => 'nullable|string|max:255', - 'deskripsi_pekerjaan' => 'nullable|string', - ]); + try { + $validator = Validator::make($request->all(), [ + 'jurusan_id' => 'required|integer|exists:jurusans,id', + 'nama_profesi' => 'required|string|max:255', + 'range_gaji' => 'nullable|string|max:255', + 'deskripsi_pekerjaan' => 'nullable|string', + ], [ + 'jurusan_id.required' => 'Jurusan wajib dipilih.', + 'jurusan_id.exists' => 'Jurusan yang dipilih tidak ditemukan.', + 'nama_profesi.required' => 'Nama profesi wajib diisi.', + ]); - if ($validator->fails()) { - return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal.', + 'errors' => $validator->errors(), + ], 422); + } + + $prospek = new ProspekKerja(); + $prospek->jurusan_id = $request->input('jurusan_id'); + $prospek->nama_profesi = $request->input('nama_profesi'); + $prospek->range_gaji = $request->input('range_gaji'); + $prospek->deskripsi_pekerjaan = $request->input('deskripsi_pekerjaan'); + $prospek->save(); + $prospek->load('jurusan'); + + return response()->json([ + 'success' => true, + 'message' => 'Data prospek kerja berhasil ditambahkan.', + 'data' => $prospek, + ], 201); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat menyimpan data prospek kerja.', + 'error' => $e->getMessage(), + ], 500); } - - $prospek = ProspekKerja::create($request->all()); - - return response()->json([ - 'success' => true, - 'message' => 'Prospek Kerja berhasil ditambahkan', - 'data' => $prospek - ], 201); } public function show($id) { - $prospek = ProspekKerja::find($id); + try { + $prospek = ProspekKerja::with('jurusan')->find($id); - if (!$prospek) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$prospek) { + return response()->json([ + 'success' => false, + 'message' => 'Data prospek kerja tidak ditemukan.', + ], 404); + } + + return response()->json([ + 'success' => true, + 'message' => 'Data prospek kerja berhasil ditemukan.', + 'data' => $prospek, + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat mengambil data prospek kerja.', + 'error' => $e->getMessage(), + ], 500); } - - return response()->json(['success' => true, 'data' => $prospek], 200); } public function update(Request $request, $id) { - $prospek = ProspekKerja::find($id); + try { + $prospek = ProspekKerja::find($id); - if (!$prospek) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$prospek) { + return response()->json([ + 'success' => false, + 'message' => 'Data prospek kerja tidak ditemukan.', + ], 404); + } + + $validator = Validator::make($request->all(), [ + 'jurusan_id' => 'required|integer|exists:jurusans,id', + 'nama_profesi' => 'required|string|max:255', + 'range_gaji' => 'nullable|string|max:255', + 'deskripsi_pekerjaan' => 'nullable|string', + ], [ + 'jurusan_id.required' => 'Jurusan wajib dipilih.', + 'jurusan_id.exists' => 'Jurusan yang dipilih tidak ditemukan.', + 'nama_profesi.required' => 'Nama profesi wajib diisi.', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal.', + 'errors' => $validator->errors(), + ], 422); + } + + $prospek->jurusan_id = $request->input('jurusan_id'); + $prospek->nama_profesi = $request->input('nama_profesi'); + $prospek->range_gaji = $request->input('range_gaji'); + $prospek->deskripsi_pekerjaan = $request->input('deskripsi_pekerjaan'); + $prospek->save(); + $prospek->load('jurusan'); + + return response()->json([ + 'success' => true, + 'message' => 'Data prospek kerja berhasil diperbarui.', + 'data' => $prospek, + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan saat memperbarui data prospek kerja.', + 'error' => $e->getMessage(), + ], 500); } - - $validator = Validator::make($request->all(), [ - 'jurusan_id' => 'required|exists:jurusans,id', - 'nama_profesi' => 'required|string|max:255', - 'range_gaji' => 'nullable|string|max:255', - 'deskripsi_pekerjaan' => 'nullable|string', - ]); - - if ($validator->fails()) { - return response()->json(['success' => false, 'errors' => $validator->errors()], 422); - } - - $prospek->update($request->all()); - - return response()->json([ - 'success' => true, - 'message' => 'Prospek Kerja berhasil diperbarui', - 'data' => $prospek - ], 200); } public function destroy($id) { - $prospek = ProspekKerja::find($id); + try { + $prospek = ProspekKerja::find($id); - if (!$prospek) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$prospek) { + return response()->json([ + 'success' => false, + 'message' => 'Data prospek kerja tidak ditemukan.', + ], 404); + } + + $prospek->delete(); + + return response()->json([ + 'success' => true, + 'message' => 'Data prospek kerja berhasil dihapus.', + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Data prospek kerja tidak dapat dihapus karena masih digunakan atau terjadi kesalahan server.', + 'error' => $e->getMessage(), + ], 500); } - - $prospek->delete(); - - return response()->json(['success' => true, 'message' => 'Prospek Kerja berhasil dihapus'], 200); } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/Admin/RiwayatController.php b/app/Http/Controllers/Api/Admin/RiwayatController.php index 2f5fc69..74e84fc 100644 --- a/app/Http/Controllers/Api/Admin/RiwayatController.php +++ b/app/Http/Controllers/Api/Admin/RiwayatController.php @@ -6,7 +6,7 @@ use App\Models\RiwayatRekomendasi; use Illuminate\Http\Request; use Illuminate\Support\Facades\Schema; -use Exception; +use Throwable; class RiwayatController extends Controller { @@ -14,7 +14,7 @@ public function index(Request $request) { try { $perPage = (int) $request->input('per_page', 7); - $perPage = in_array($perPage, [5, 7]) ? $perPage : 7; + $perPage = in_array($perPage, [5, 7, 10, 25, 50, 100]) ? $perPage : 7; $search = trim((string) $request->input('search', '')); @@ -49,6 +49,7 @@ public function index(Request $request) return response()->json([ 'success' => true, + 'message' => 'Data riwayat berhasil dimuat.', 'data' => $riwayat->items(), 'current_page' => $riwayat->currentPage(), 'last_page' => $riwayat->lastPage(), @@ -57,11 +58,11 @@ public function index(Request $request) 'from' => $riwayat->firstItem(), 'to' => $riwayat->lastItem(), ], 200); - - } catch (Exception $e) { + } catch (Throwable $e) { return response()->json([ 'success' => false, - 'message' => 'Error: ' . $e->getMessage() + 'message' => 'Gagal memuat data riwayat.', + 'error' => $e->getMessage(), ], 500); } } @@ -69,10 +70,18 @@ public function index(Request $request) public function show($id) { try { - $detail = RiwayatRekomendasi::with('user:id,name,email')->findOrFail($id); + $detail = RiwayatRekomendasi::with('user:id,name,email')->find($id); + + if (!$detail) { + return response()->json([ + 'success' => false, + 'message' => 'Data detail riwayat tidak ditemukan.', + ], 404); + } return response()->json([ 'success' => true, + 'message' => 'Detail riwayat berhasil ditemukan.', 'data' => [ 'id' => $detail->id, 'tanggal' => $detail->created_at ? $detail->created_at->format('d M Y H:i:s') : '-', @@ -85,13 +94,20 @@ public function show($id) 'kondisi_ekonomi' => $this->getLabelEkonomi($detail->gaji_ortu), 'gaji_ortu' => $detail->gaji_ortu, 'nilai_ringkas' => [ - 'Matematika' => $detail->matematika, + 'Agama' => $detail->agama, + 'PKN' => $detail->pkn, 'B. Indonesia' => $detail->bahasa_indonesia, 'B. Inggris' => $detail->bahasa_inggris, + 'Matematika' => $detail->matematika, 'Fisika' => $detail->fisika, 'Kimia' => $detail->kimia, 'Biologi' => $detail->biologi, + 'Sejarah' => $detail->sejarah, + 'Geografi' => $detail->geografi, 'Ekonomi' => $detail->ekonomi_mapel, + 'Sosiologi' => $detail->sosiologi, + 'Seni Budaya' => $detail->seni_budaya, + 'PJOK' => $detail->pjok, 'Informatika' => $detail->informatika, ], 'riasec' => [ @@ -102,14 +118,41 @@ public function show($id) 'Enterprising' => $detail->riasec_e, 'Conventional' => $detail->riasec_c, ], - ] + ], ], 200); - - } catch (Exception $e) { + } catch (Throwable $e) { return response()->json([ 'success' => false, - 'message' => 'Data detail tidak ditemukan: ' . $e->getMessage() - ], 404); + 'message' => 'Terjadi kesalahan saat mengambil detail riwayat.', + 'error' => $e->getMessage(), + ], 500); + } + } + + public function destroy($id) + { + try { + $riwayat = RiwayatRekomendasi::find($id); + + if (!$riwayat) { + return response()->json([ + 'success' => false, + 'message' => 'Data riwayat tidak ditemukan.', + ], 404); + } + + $riwayat->delete(); + + return response()->json([ + 'success' => true, + 'message' => 'Data riwayat berhasil dihapus.', + ], 200); + } catch (Throwable $e) { + return response()->json([ + 'success' => false, + 'message' => 'Data riwayat gagal dihapus.', + 'error' => $e->getMessage(), + ], 500); } } @@ -117,15 +160,13 @@ private function getAkurasiPrediksi($riwayat): ?float { $tableName = (new RiwayatRekomendasi())->getTable(); - if (!Schema::hasColumn($tableName, 'akurasi_prediksi')) { - return null; + foreach (['akurasi_prediksi', 'probabilitas', 'akurasi'] as $column) { + if (Schema::hasColumn($tableName, $column) && $riwayat->{$column} !== null) { + return round((float) $riwayat->{$column}, 2); + } } - if ($riwayat->akurasi_prediksi === null) { - return null; - } - - return round((float) $riwayat->akurasi_prediksi, 2); + return null; } private function getRataNilai($riwayat): float @@ -148,7 +189,7 @@ private function getRataNilai($riwayat): float $riwayat->informatika, ]; - $nilai = array_filter($nilai, fn ($item) => $item !== null); + $nilai = array_filter($nilai, fn ($item) => $item !== null && $item !== ''); if (count($nilai) === 0) { return 0; @@ -175,6 +216,10 @@ private function getMinatUtama($riwayat): string private function getLabelEkonomi($gaji): string { + if ($gaji === null || $gaji === '') { + return '-'; + } + $gaji = (float) $gaji; if ($gaji < 2000000) { diff --git a/app/Http/Controllers/Api/Admin/SiswaController.php b/app/Http/Controllers/Api/Admin/SiswaController.php index bf05164..724474c 100644 --- a/app/Http/Controllers/Api/Admin/SiswaController.php +++ b/app/Http/Controllers/Api/Admin/SiswaController.php @@ -4,134 +4,185 @@ use App\Http\Controllers\Controller; use App\Models\Siswa; +use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\DB; +use Illuminate\Validation\Rule; +use Throwable; class SiswaController extends Controller { public function index(Request $request) { - $perPage = (int) $request->input('per_page', 7); - $perPage = in_array($perPage, [5, 7]) ? $perPage : 7; + try { + $perPage = (int) $request->input('per_page', 7); + $perPage = in_array($perPage, [5, 7, 10, 25, 50, 100]) ? $perPage : 7; + $search = trim((string) $request->input('search', '')); - $search = trim((string) $request->input('search', '')); + $query = Siswa::with('user')->orderBy('id', 'desc'); - $query = Siswa::with(['user', 'jurusan'])->orderBy('id', 'desc'); + if ($search !== '') { + $query->where(function ($q) use ($search) { + $q->where('nama_lengkap', 'like', "%{$search}%") + ->orWhere('nisn', 'like', "%{$search}%") + ->orWhere('kelas', 'like', "%{$search}%") + ->orWhereHas('user', function ($user) use ($search) { + $user->where('username', 'like', "%{$search}%"); + }); + }); + } - if ($search !== '') { - $query->where(function ($q) use ($search) { - $q->where('id_siswa', 'like', "%{$search}%") - ->orWhere('nama_lengkap', 'like', "%{$search}%") - ->orWhere('nisn', 'like', "%{$search}%") - ->orWhere('kelas', 'like', "%{$search}%") - ->orWhere('peminatan', 'like', "%{$search}%") - ->orWhereHas('user', function ($user) use ($search) { - $user->where('name', 'like', "%{$search}%") - ->orWhere('email', 'like', "%{$search}%"); - }) - ->orWhereHas('jurusan', function ($jurusan) use ($search) { - $jurusan->where('nama_jurusan', 'like', "%{$search}%"); - }); - }); + $siswa = $query->paginate($perPage); + + return response()->json(['success' => true, 'data' => $siswa], 200); + } catch (Throwable $e) { + return response()->json(['success' => false, 'message' => $e->getMessage()], 500); } - - $siswa = $query->paginate($perPage); - - return response()->json([ - 'success' => true, - 'data' => $siswa->items(), - 'current_page' => $siswa->currentPage(), - 'last_page' => $siswa->lastPage(), - 'per_page' => $siswa->perPage(), - 'total' => $siswa->total(), - 'from' => $siswa->firstItem(), - 'to' => $siswa->lastItem(), - ], 200); } public function store(Request $request) { $validator = Validator::make($request->all(), [ - 'user_id' => 'required|exists:users,id|unique:siswas,user_id', - 'jurusan_id' => 'required|exists:jurusans,id', - 'nama_lengkap' => 'required|string|max:255', - 'nisn' => 'required|string|unique:siswas,nisn', - 'kelas' => 'required|string', - 'peminatan' => 'required|string', - ], [ - 'user_id.unique' => 'Gagal! Akun User ID ini sudah terikat dengan data siswa lain.', - 'nisn.unique' => 'Gagal! NISN ' . $request->nisn . ' sudah terdaftar di sistem.', - 'nama_lengkap.required' => 'Kolom nama lengkap tidak boleh dikosongkan.', - 'nisn.required' => 'Kolom NISN wajib diisi.', - 'user_id.exists' => 'User ID yang Anda masukkan tidak terdaftar di database.', + 'username' => ['required', Rule::unique(User::class, 'username')], + 'password' => 'required|min:6', + 'nama_lengkap' => 'required', + 'nisn' => ['required', Rule::unique(Siswa::class, 'nisn')], + 'kelas' => 'required', ]); if ($validator->fails()) { - return response()->json([ - 'success' => false, - 'message' => 'Validasi gagal', - 'errors' => $validator->errors() - ], 422); + return response()->json(['success' => false, 'message' => 'Validasi gagal', 'errors' => $validator->errors()], 422); } - $siswa = Siswa::create($request->all()); + DB::beginTransaction(); + try { + $usernameClean = str_replace(' ', '', $request->username); + $otomatisEmail = strtolower($usernameClean) . '@gmail.com'; - return response()->json([ - 'success' => true, - 'message' => 'Siswa ' . $siswa->id_siswa . ' (' . $siswa->nama_lengkap . ') berhasil ditambahkan.', - 'data' => $siswa - ], 201); + $user = User::create([ + 'name' => $request->nama_lengkap, + 'username' => $request->username, + 'email' => $otomatisEmail, + 'password' => Hash::make($request->password), + 'role' => 'siswa', + ]); + + Siswa::create([ + 'user_id' => $user->id, + 'nama_lengkap' => $request->nama_lengkap, + 'nisn' => $request->nisn, + 'kelas' => $request->kelas, + ]); + + DB::commit(); + return response()->json(['success' => true, 'message' => 'Data siswa berhasil ditambahkan.'], 201); + } catch (\Throwable $e) { + DB::rollBack(); + return response()->json(['success' => false, 'message' => 'Gagal menyimpan data.', 'error' => $e->getMessage()], 500); + } } public function show($id) { - $siswa = Siswa::find($id); + try { + $siswa = Siswa::with('user')->find($id); - if (!$siswa) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$siswa) { + return response()->json(['success' => false, 'message' => 'Data siswa tidak ditemukan.'], 404); + } + + return response()->json(['success' => true, 'data' => $siswa], 200); + } catch (Throwable $e) { + return response()->json(['success' => false, 'message' => 'Gagal memuat rincian.', 'error' => $e->getMessage()], 500); } - - return response()->json(['success' => true, 'data' => $siswa], 200); } public function update(Request $request, $id) { $siswa = Siswa::find($id); - + if (!$siswa) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + return response()->json(['success' => false, 'message' => 'Data siswa tidak ditemukan.'], 404); } $validator = Validator::make($request->all(), [ - 'nama_lengkap' => 'required|string|max:255', - 'nisn' => 'required|string|unique:siswas,nisn,' . $id, - 'kelas' => 'required|string', - ], [ - 'nisn.unique' => 'Gagal Update! NISN ' . $request->nisn . ' sudah digunakan siswa lain.' + 'username' => [ + 'nullable', + Rule::unique(User::class, 'username')->ignore($siswa->user_id) + ], + 'nisn' => [ + 'required', + Rule::unique(Siswa::class, 'nisn')->ignore($siswa->id) + ], + 'nama_lengkap' => 'required', + 'kelas' => 'required', ]); if ($validator->fails()) { - return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + return response()->json(['success' => false, 'message' => 'Validasi gagal', 'errors' => $validator->errors()], 422); } - $siswa->update($request->all()); + DB::beginTransaction(); + try { + if ($siswa->user_id) { + $user = User::find($siswa->user_id); + if ($user) { + $user->name = $request->nama_lengkap; + + if ($request->filled('username')) { + $user->username = $request->username; + $usernameClean = str_replace(' ', '', $request->username); + $user->email = strtolower($usernameClean) . '@gmail.com'; + } - return response()->json([ - 'success' => true, - 'message' => 'Data ' . $siswa->id_siswa . ' berhasil diperbarui' - ], 200); + if ($request->filled('password')) { + $user->password = Hash::make($request->password); + } + $user->save(); + } + } + + $siswa->update([ + 'nama_lengkap' => $request->nama_lengkap, + 'nisn' => $request->nisn, + 'kelas' => $request->kelas + ]); + + DB::commit(); + + return response()->json([ + 'success' => true, + 'message' => 'Data siswa berhasil diperbarui.', + 'data' => $siswa->load('user') + ], 200); + } catch (Throwable $e) { + DB::rollBack(); + return response()->json(['success' => false, 'message' => 'Gagal memperbarui data.', 'error' => $e->getMessage()], 500); + } } public function destroy($id) { - $siswa = Siswa::find($id); + try { + $siswa = Siswa::find($id); - if (!$siswa) { - return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404); + if (!$siswa) { + return response()->json(['success' => false, 'message' => 'Data tidak ditemukan.'], 404); + } + + if ($siswa->user_id) { + $userId = $siswa->user_id; + $siswa->delete(); + User::destroy($userId); + } else { + $siswa->delete(); + } + + return response()->json(['success' => true, 'message' => 'Data berhasil dihapus.'], 200); + } catch (Throwable $e) { + return response()->json(['success' => false, 'message' => 'Gagal menghapus.', 'error' => $e->getMessage()], 500); } - - $siswa->delete(); - - return response()->json(['success' => true, 'message' => 'Data berhasil dihapus'], 200); } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/AuthController.php b/app/Http/Controllers/Api/AuthController.php index 4cacdab..dfe6391 100644 --- a/app/Http/Controllers/Api/AuthController.php +++ b/app/Http/Controllers/Api/AuthController.php @@ -6,7 +6,6 @@ use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; -use Illuminate\Validation\ValidationException; class AuthController extends Controller { @@ -26,10 +25,8 @@ public function login(Request $request) ], 401); } - // Hapus token lama $user->tokens()->delete(); - // Buat token baru $token = $user->createToken('auth_token')->plainTextToken; return response()->json([ @@ -37,8 +34,9 @@ public function login(Request $request) 'message' => 'Login berhasil', 'access_token' => $token, 'token_type' => 'Bearer', - 'role' => $user->role, // Penting untuk redirect di frontend + 'role' => $user->role, 'user' => [ + 'id' => $user->id, 'name' => $user->name, 'username' => $user->username, ] @@ -47,7 +45,15 @@ public function login(Request $request) public function logout(Request $request) { - $request->user()->currentAccessToken()->delete(); - return response()->json(['message' => 'Berhasil logout']); + $token = $request->user()?->currentAccessToken(); + + if ($token) { + $token->delete(); + } + + return response()->json([ + 'success' => true, + 'message' => 'Berhasil logout' + ]); } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/Student/DashboardStudentController.php b/app/Http/Controllers/Api/Student/DashboardStudentController.php index 9abfd59..711b6a6 100644 --- a/app/Http/Controllers/Api/Student/DashboardStudentController.php +++ b/app/Http/Controllers/Api/Student/DashboardStudentController.php @@ -13,9 +13,12 @@ public function index() { try { $user = Auth::user(); - - if (!$user) { - return response()->json(['success' => false, 'message' => 'Unauthorized'], 401); + + if (! $user) { + return response()->json([ + 'success' => false, + 'message' => 'Unauthorized' + ], 401); } $totalAktivitas = RiwayatRekomendasi::where('user_id', $user->id)->count(); @@ -32,7 +35,7 @@ public function index() return [ 'id' => $item->id, 'jurusan' => $item->hasil_rekomendasi_jurusan, - 'tanggal' => $item->created_at->diffForHumans(), + 'tanggal' => $item->created_at ? $item->created_at->diffForHumans() : '-', ]; }); @@ -41,17 +44,30 @@ public function index() ->get() ->map(function ($item, $index) { $mapel = [ - 'agama', 'pkn', 'bahasa_indonesia', 'bahasa_inggris', 'matematika', - 'fisika', 'kimia', 'biologi', 'sejarah', 'geografi', - 'ekonomi_mapel', 'sosiologi', 'seni_budaya', 'pjok', 'informatika' + 'agama', + 'pkn', + 'bahasa_indonesia', + 'bahasa_inggris', + 'matematika', + 'fisika', + 'kimia', + 'biologi', + 'sejarah', + 'geografi', + 'ekonomi_mapel', + 'sosiologi', + 'seni_budaya', + 'pjok', + 'informatika' ]; $totalNilai = 0; + foreach ($mapel as $m) { - $totalNilai += (float) $item->$m; + $totalNilai += (float) ($item->$m ?? 0); } - - $rataRata = $totalNilai / count($mapel); + + $rataRata = count($mapel) > 0 ? $totalNilai / count($mapel) : 0; return [ 'label' => 'Tes ' . ($index + 1), @@ -63,7 +79,9 @@ public function index() 'success' => true, 'data' => [ 'total_aktivitas' => $totalAktivitas, - 'rekomendasi_terakhir' => $rekomendasiTerakhir ? $rekomendasiTerakhir->hasil_rekomendasi_jurusan : 'Belum Ada Data', + 'rekomendasi_terakhir' => $rekomendasiTerakhir + ? $rekomendasiTerakhir->hasil_rekomendasi_jurusan + : 'Belum Ada Data', 'aktivitas_terbaru' => $aktivitasTerbaru, 'tren_nilai' => $trenNilai ] diff --git a/app/Http/Controllers/Api/Student/RecommendationApiController.php b/app/Http/Controllers/Api/Student/RecommendationApiController.php index 4c53ce6..63fe7f6 100644 --- a/app/Http/Controllers/Api/Student/RecommendationApiController.php +++ b/app/Http/Controllers/Api/Student/RecommendationApiController.php @@ -4,18 +4,26 @@ use App\Http\Controllers\Controller; use App\Models\RiwayatRekomendasi; +use App\Models\Jurusan; +use App\Services\PythonRecommendationService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Schema; class RecommendationApiController extends Controller { - public function hitungPrediksi(Request $request) + public function health(PythonRecommendationService $pythonService) + { + $result = $pythonService->health(); + + return response()->json($result, $result['success'] ? 200 : 500); + } + + public function hitungPrediksi(Request $request, PythonRecommendationService $pythonService) { $user = Auth::user(); - if (!$user) { + if (! $user) { return response()->json([ 'success' => false, 'message' => 'User belum login.' @@ -49,49 +57,52 @@ public function hitungPrediksi(Request $request) 'gaji_ortu' => 'required|numeric|min:0', ]); - $payload = $validated; - $payload['ekonomi_orang_tua'] = self::getLabelEkonomiSingkat($validated['gaji_ortu']); + $payloadPython = $validated; + + $payloadPython['ekonomi_orang_tua'] = self::getLabelEkonomiSingkat($validated['gaji_ortu']); + + unset($payloadPython['gaji_ortu']); try { - $response = Http::timeout(15) - ->acceptJson() - ->post('http://127.0.0.1:5000/predict', $payload); + $pythonResult = $pythonService->predict($payloadPython); - if (!$response->successful()) { + if (! $pythonResult['success']) { return response()->json([ 'success' => false, - 'message' => 'ML Engine Error: ' . ($response->json()['message'] ?? $response->body()) + 'message' => 'ML Engine Error: ' . ($pythonResult['message'] ?? 'Tidak dapat menghubungi Flask API.'), + 'detail' => $pythonResult, ], 500); } - $responseData = $response->json(); + $responseData = $pythonResult['data']; - if (!isset($responseData['success']) || !$responseData['success']) { + if (! is_array($responseData)) { + return response()->json([ + 'success' => false, + 'message' => 'Respons Flask API tidak valid.' + ], 500); + } + + if (($responseData['success'] ?? true) === false) { return response()->json([ 'success' => false, 'message' => 'ML Engine Error: ' . ($responseData['message'] ?? 'Unknown Error') ], 500); } - $teksJurusan = $responseData['prediksi_jurusan'] ?? null; + $teksJurusan = $responseData['prediksi_jurusan'] + ?? $responseData['rekomendasi'] + ?? $responseData['jurusan'] + ?? null; - if (!$teksJurusan) { + if (! $teksJurusan) { return response()->json([ 'success' => false, 'message' => 'Prediksi jurusan tidak ditemukan pada respons Flask.' ], 500); } - /* - * Ambil nilai probabilitas dari Flask. - * Contoh respons Flask yang disarankan: - * { - * "success": true, - * "prediksi_jurusan": "Teknik Informatika", - * "probabilitas": 86.45 - * } - */ - $akurasiPrediksi = self::normalisasiAkurasi($responseData['probabilitas'] ?? null); + $akurasiPrediksi = self::ambilAkurasiDariResponse($responseData, $teksJurusan); $dataRiwayat = [ 'user_id' => $user->id, @@ -123,16 +134,16 @@ public function hitungPrediksi(Request $request) 'hasil_rekomendasi_jurusan' => $teksJurusan, ]; - /* - * Kolom akurasi_prediksi hanya diisi kalau migration baru sudah dijalankan. - * Ini mencegah error kalau kolom belum ada. - */ $tableName = (new RiwayatRekomendasi())->getTable(); if (Schema::hasColumn($tableName, 'akurasi_prediksi')) { $dataRiwayat['akurasi_prediksi'] = $akurasiPrediksi; } + if (Schema::hasColumn($tableName, 'probabilitas')) { + $dataRiwayat['probabilitas'] = $akurasiPrediksi; + } + $riwayat = RiwayatRekomendasi::create($dataRiwayat); return response()->json([ @@ -140,7 +151,9 @@ public function hitungPrediksi(Request $request) 'message' => 'Prediksi berhasil dibuat.', 'redirect_url' => route('student.result', ['id' => $riwayat->id]), 'prediksi_jurusan' => $teksJurusan, - 'akurasi_prediksi' => $akurasiPrediksi + 'akurasi_prediksi' => $akurasiPrediksi, + 'top_predictions' => $responseData['top_predictions'] ?? null, + 'riwayat_id' => $riwayat->id, ]); } catch (\Throwable $e) { @@ -153,7 +166,16 @@ public function hitungPrediksi(Request $request) public function getHasilRekomendasi($id) { - $riwayat = RiwayatRekomendasi::findOrFail($id); + $user = Auth::user(); + + if (! $user) { + return response()->json([ + 'success' => false, + 'message' => 'User belum login.' + ], 401); + } + + $riwayat = RiwayatRekomendasi::where('user_id', $user->id)->findOrFail($id); return response()->json([ 'success' => true, @@ -166,7 +188,7 @@ public static function formatHasilData(RiwayatRekomendasi $riwayat): array return [ 'id' => $riwayat->id, 'hasil_rekomendasi_jurusan' => $riwayat->hasil_rekomendasi_jurusan, - 'akurasi_prediksi' => $riwayat->akurasi_prediksi, + 'akurasi_prediksi' => $riwayat->akurasi_prediksi ?? $riwayat->probabilitas ?? null, 'label_ekonomi' => self::getLabelEkonomiDetail($riwayat->gaji_ortu), 'minat_utama' => self::getMinatUtamaLabel($riwayat), 'prospek_kerja' => self::getProspekKerjaText($riwayat->hasil_rekomendasi_jurusan), @@ -182,6 +204,34 @@ public static function formatHasilData(RiwayatRekomendasi $riwayat): array ]; } + private static function ambilAkurasiDariResponse(array $responseData, ?string $jurusan = null): ?float + { + $nilaiLangsung = $responseData['probabilitas'] + ?? $responseData['confidence'] + ?? $responseData['akurasi_prediksi'] + ?? null; + + if ($nilaiLangsung !== null) { + return self::normalisasiAkurasi($nilaiLangsung); + } + + $topPredictions = $responseData['top_predictions'] ?? null; + + if (! is_array($topPredictions) || count($topPredictions) === 0) { + return null; + } + + if ($jurusan) { + foreach ($topPredictions as $item) { + if (($item['jurusan'] ?? null) === $jurusan) { + return self::normalisasiAkurasi($item['probabilitas'] ?? null); + } + } + } + + return self::normalisasiAkurasi($topPredictions[0]['probabilitas'] ?? null); + } + private static function normalisasiAkurasi($value): ?float { if ($value === null || $value === '') { @@ -194,28 +244,16 @@ private static function normalisasiAkurasi($value): ?float $value = trim($value); } - if (!is_numeric($value)) { + if (! is_numeric($value)) { return null; } $number = (float) $value; - /* - * Jika Flask mengirim 0.86, ubah menjadi 86. - * Jika Flask mengirim 86.45, tetap 86.45. - */ - if ($number <= 1) { + if ($number >= 0 && $number <= 1) { $number = $number * 100; } - if ($number < 0) { - $number = 0; - } - - if ($number > 100) { - $number = 100; - } - return round($number, 2); } @@ -225,7 +263,9 @@ public static function getLabelEkonomiSingkat($gaji): string if ($gaji < 2000000) { return 'Kurang Mampu'; - } elseif ($gaji <= 5000000) { + } + + if ($gaji <= 5000000) { return 'Menengah'; } @@ -237,60 +277,65 @@ public static function getLabelEkonomiDetail($gaji): string $gaji = (float) $gaji; if ($gaji < 2000000) { - return 'Kurang Mampu (< Rp 2.000.000)'; - } elseif ($gaji <= 5000000) { - return 'Menengah (Rp 2.000.000 - Rp 5.000.000)'; + return 'Ekonomi Kurang Mampu'; } - return 'Mampu (> Rp 5.000.000)'; + if ($gaji <= 5000000) { + return 'Ekonomi Menengah'; + } + + return 'Ekonomi Mampu'; } public static function getMinatUtamaLabel(RiwayatRekomendasi $riwayat): string { - $riasecScores = [ - 'r' => (float) $riwayat->riasec_r, - 'i' => (float) $riwayat->riasec_i, - 'a' => (float) $riwayat->riasec_a, - 's' => (float) $riwayat->riasec_s, - 'e' => (float) $riwayat->riasec_e, - 'c' => (float) $riwayat->riasec_c, + $scores = [ + 'Realistic' => (float) $riwayat->riasec_r, + 'Investigative' => (float) $riwayat->riasec_i, + 'Artistic' => (float) $riwayat->riasec_a, + 'Social' => (float) $riwayat->riasec_s, + 'Enterprising' => (float) $riwayat->riasec_e, + 'Conventional' => (float) $riwayat->riasec_c, ]; - arsort($riasecScores); - $topCode = array_key_first($riasecScores); + arsort($scores); - $labels = [ - 'r' => 'Teknis & Lapangan', - 'i' => 'Sains, Riset & Analitis', - 'a' => 'Kreatif & Desain', - 's' => 'Sosial & Pelayanan', - 'e' => 'Bisnis & Kepemimpinan', - 'c' => 'Administratif & Terstruktur', - ]; - - return $labels[$topCode] ?? 'Belum teridentifikasi'; + return array_key_first($scores) ?? 'Belum diketahui'; } - public static function getProspekKerjaText($jurusan): string - { - $jurusan = strtolower((string) $jurusan); - if (str_contains($jurusan, 'ekonomi') || str_contains($jurusan, 'manajemen') || str_contains($jurusan, 'bisnis')) { - return 'Lulusan bidang ini dapat berkarier sebagai manajer bisnis, staf keuangan, analis pasar, konsultan bisnis, wirausahawan, maupun pegawai instansi pemerintah dan BUMN pada sektor ekonomi dan administrasi.'; - } - - if (str_contains($jurusan, 'teknik') || str_contains($jurusan, 'informatika') || str_contains($jurusan, 'komputer')) { - return 'Lulusan bidang ini memiliki peluang karier sebagai software developer, analis sistem, teknisi, network engineer, data analyst, maupun profesional teknologi di perusahaan swasta, startup, dan instansi pemerintah.'; - } - - if (str_contains($jurusan, 'kesehatan') || str_contains($jurusan, 'keperawatan') || str_contains($jurusan, 'farmasi')) { - return 'Lulusan bidang ini dapat berkarier di rumah sakit, klinik, laboratorium, industri kesehatan, layanan masyarakat, maupun lembaga penelitian kesehatan.'; - } - - if (str_contains($jurusan, 'pendidikan') || str_contains($jurusan, 'guru')) { - return 'Lulusan bidang ini memiliki prospek sebagai guru, tenaga pendidik, instruktur pelatihan, pengembang kurikulum, maupun konsultan pendidikan.'; - } - - return 'Prospek karier untuk jurusan ini sangat luas, mulai dari spesialis industri, konsultan ahli, hingga pegawai instansi pemerintah/BUMN di sektor terkait.'; +public static function getProspekKerjaText(?string $jurusan): string +{ + if (! $jurusan) { + return 'Prospek kerja belum tersedia.'; } + + // 1. Cari data Jurusan di database berdasarkan nama jurusan hasil prediksi + $dataJurusan = \App\Models\Jurusan::where('nama_jurusan', $jurusan)->first(); + + // 2. Jika jurusan tidak ditemukan atau tidak memiliki data prospek kerja + if (! $dataJurusan || $dataJurusan->prospeks->isEmpty()) { + return 'Prospek kerja untuk program studi ' . $jurusan . ' belum diinputkan oleh admin.'; + } + + // 3. Ambil semua data prospek kerja terkait dan susun menjadi format HTML list yang rapi + $htmlOutput = ''; + + return $htmlOutput; +} } \ No newline at end of file diff --git a/app/Models/Alumni.php b/app/Models/Alumni.php index bacf9a9..c848a1d 100644 --- a/app/Models/Alumni.php +++ b/app/Models/Alumni.php @@ -19,13 +19,18 @@ protected static function boot() { parent::boot(); + // Otomatis membuat id_alumni yang unik dengan aman saat data dibuat static::creating(function ($model) { - $latest = static::orderBy('id', 'desc')->first(); - if (!$latest) { - $model->id_alumni = 'AL01'; - } else { - $number = intval(substr($latest->id_alumni, 2)) + 1; - $model->id_alumni = 'AL' . str_pad($number, 2, '0', STR_PAD_LEFT); + if (empty($model->id_alumni)) { + $nextNumber = ((int) static::max('id')) + 1; + $candidate = 'ALM' . str_pad($nextNumber, 4, '0', STR_PAD_LEFT); + + while (static::where('id_alumni', $candidate)->exists()) { + $nextNumber++; + $candidate = 'ALM' . str_pad($nextNumber, 4, '0', STR_PAD_LEFT); + } + + $model->id_alumni = $candidate; } }); } diff --git a/app/Models/Guru.php b/app/Models/Guru.php index 5c86caa..7977f91 100644 --- a/app/Models/Guru.php +++ b/app/Models/Guru.php @@ -3,22 +3,37 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; class Guru extends Model { - protected $fillable = ['id_guru', 'nama_guru']; + // Memastikan nama tabel sesuai dengan database + protected $table = 'gurus'; + + + protected $fillable = ['user_id', 'id_guru', 'nama_guru']; + + + public function user(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } protected static function boot() { parent::boot(); static::creating(function ($model) { - $latest = static::orderBy('id', 'desc')->first(); - if (!$latest) { - $model->id_guru = 'GR01'; - } else { - $number = intval(substr($latest->id_guru, 2)) + 1; - $model->id_guru = 'GR' . str_pad($number, 2, '0', STR_PAD_LEFT); + if (empty($model->id_guru)) { + $nextNumber = ((int) static::max('id')) + 1; + $candidate = 'GRU' . str_pad($nextNumber, 4, '0', STR_PAD_LEFT); + + while (static::where('id_guru', $candidate)->exists()) { + $nextNumber++; + $candidate = 'GRU' . str_pad($nextNumber, 4, '0', STR_PAD_LEFT); + } + + $model->id_guru = $candidate; } }); } diff --git a/app/Models/Jurusan.php b/app/Models/Jurusan.php index 5acc2e7..aa83ed1 100644 --- a/app/Models/Jurusan.php +++ b/app/Models/Jurusan.php @@ -6,7 +6,7 @@ class Jurusan extends Model { - protected $fillable = ['nama_jurusan', 'rumpun', 'deskripsi']; + protected $fillable = ['nama_jurusan', 'rumpun',]; // Relasi: Satu Jurusan memiliki banyak Prospek Kerja public function prospeks() diff --git a/app/Models/Siswa.php b/app/Models/Siswa.php index 67ac74b..d5df437 100644 --- a/app/Models/Siswa.php +++ b/app/Models/Siswa.php @@ -5,17 +5,15 @@ use Illuminate\Database\Eloquent\Model; class Siswa extends Model + { + protected $table = 'siswas'; protected $fillable = [ 'id_siswa', 'user_id', - 'jurusan_id', 'nama_lengkap', 'nisn', - 'kelas', - 'peminatan', - 'minat_bakat', - 'ekonomi' + 'kelas' ]; protected static function boot() @@ -39,8 +37,4 @@ protected static function boot() public function user() { return $this->belongsTo(User::class); } - - public function jurusan() { - return $this->belongsTo(Jurusan::class); - } } \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index b379c22..873e05a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -11,9 +11,8 @@ class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable; use HasApiTokens, HasFactory, Notifiable; - + /** * The attributes that are mass assignable. * @@ -49,4 +48,4 @@ protected function casts(): array 'password' => 'hashed', ]; } -} +} \ No newline at end of file diff --git a/app/Services/PythonRecommendationService.php b/app/Services/PythonRecommendationService.php new file mode 100644 index 0000000..9f7c673 --- /dev/null +++ b/app/Services/PythonRecommendationService.php @@ -0,0 +1,89 @@ +baseUrl = rtrim((string) config('services.python_api.url'), '/'); + $this->timeout = (int) config('services.python_api.timeout', 30); + } + + public function health(): array + { + try { + if ($this->baseUrl === '') { + return [ + 'success' => false, + 'status_code' => 500, + 'data' => null, + 'message' => 'PYTHON_API_URL belum diatur pada file .env.', + ]; + } + + $response = Http::timeout($this->timeout) + ->acceptJson() + ->get($this->baseUrl . '/health'); + + return [ + 'success' => $response->successful(), + 'status_code' => $response->status(), + 'data' => $response->json(), + 'message' => $response->successful() + ? 'Laravel berhasil terhubung ke Flask API.' + : 'Laravel gagal membaca Flask API.', + ]; + } catch (Throwable $e) { + return [ + 'success' => false, + 'status_code' => 500, + 'data' => null, + 'message' => 'Tidak dapat terhubung ke Flask API: ' . $e->getMessage(), + ]; + } + } + + public function predict(array $payload): array + { + try { + if ($this->baseUrl === '') { + return [ + 'success' => false, + 'status_code' => 500, + 'data' => null, + 'message' => 'PYTHON_API_URL belum diatur pada file .env.', + ]; + } + + $response = Http::timeout($this->timeout) + ->acceptJson() + ->asJson() + ->post($this->baseUrl . '/predict', $payload); + + $json = $response->json(); + + return [ + 'success' => $response->successful(), + 'status_code' => $response->status(), + 'data' => $json, + 'message' => $response->successful() + ? 'Prediksi berhasil diproses oleh Flask API.' + : ($json['message'] ?? $response->body() ?? 'Prediksi gagal diproses oleh Flask API.'), + ]; + } catch (Throwable $e) { + return [ + 'success' => false, + 'status_code' => 500, + 'data' => null, + 'message' => 'Koneksi ke Flask API gagal: ' . $e->getMessage(), + ]; + } + } +} \ No newline at end of file diff --git a/config/services.php b/config/services.php index 6a90eb8..182f531 100644 --- a/config/services.php +++ b/config/services.php @@ -6,12 +6,6 @@ |-------------------------------------------------------------------------- | Third Party Services |-------------------------------------------------------------------------- - | - | This file is for storing the credentials for third party services such - | as Mailgun, Postmark, AWS and more. This file provides the de facto - | location for this type of information, allowing packages to have - | a conventional file to locate the various service credentials. - | */ 'postmark' => [ @@ -35,4 +29,15 @@ ], ], -]; + /* + |-------------------------------------------------------------------------- + | Python Flask API + |-------------------------------------------------------------------------- + */ + + 'python_api' => [ + 'url' => env('PYTHON_API_URL', 'https://galangadji.pythonanywhere.com'), + 'timeout' => env('PYTHON_API_TIMEOUT', 30), + ], + +]; \ No newline at end of file diff --git a/flask_api/requirements.txt b/flask_api/requirements.txt index 4aa5253..6b12213 100644 --- a/flask_api/requirements.txt +++ b/flask_api/requirements.txt @@ -2,6 +2,6 @@ flask flask-cors pandas numpy -scikit-learn==1.6.1 +scikit-learn==1.8.0 joblib openpyxl \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..b7aacdb --- /dev/null +++ b/index.php @@ -0,0 +1,18 @@ +usePublicPath(__DIR__); + +$app->handleRequest(Request::capture()); \ No newline at end of file diff --git a/resources/views/admin/alumni/alumni.blade.php b/resources/views/admin/alumni/alumni.blade.php index 88d42e6..0d9ab86 100644 --- a/resources/views/admin/alumni/alumni.blade.php +++ b/resources/views/admin/alumni/alumni.blade.php @@ -45,6 +45,7 @@ Opsi + @@ -65,13 +66,39 @@ @endsection \ No newline at end of file diff --git a/resources/views/admin/alumni/alumniedit.blade.php b/resources/views/admin/alumni/alumniedit.blade.php index 6db4408..e779902 100644 --- a/resources/views/admin/alumni/alumniedit.blade.php +++ b/resources/views/admin/alumni/alumniedit.blade.php @@ -15,23 +15,32 @@
+
+
+
+
- - Batal + + + + Batal +
@@ -40,51 +49,159 @@ -@endpush @endsection \ No newline at end of file diff --git a/resources/views/admin/guru/guru.blade.php b/resources/views/admin/guru/guru.blade.php index 6674bf8..ce673d6 100644 --- a/resources/views/admin/guru/guru.blade.php +++ b/resources/views/admin/guru/guru.blade.php @@ -43,6 +43,7 @@ Opsi + @@ -63,13 +64,39 @@ @endsection \ No newline at end of file diff --git a/resources/views/admin/guru/guruedit.blade.php b/resources/views/admin/guru/guruedit.blade.php index 8febcbf..fd8aab0 100644 --- a/resources/views/admin/guru/guruedit.blade.php +++ b/resources/views/admin/guru/guruedit.blade.php @@ -15,15 +15,22 @@
+
+
- - Batal + + + + Batal +
@@ -32,47 +39,155 @@ @endsection \ No newline at end of file diff --git a/resources/views/admin/jurusan/jurusanedit.blade.php b/resources/views/admin/jurusan/jurusanedit.blade.php index 60361d8..77562ef 100644 --- a/resources/views/admin/jurusan/jurusanedit.blade.php +++ b/resources/views/admin/jurusan/jurusanedit.blade.php @@ -15,11 +15,13 @@
+
+
-
- - -
-
+ +
- - Batal + + + + Batal +
@@ -43,50 +47,156 @@ -@endpush @endsection \ No newline at end of file diff --git a/resources/views/admin/siswa/siswa.blade.php b/resources/views/admin/siswa/siswa.blade.php index 2b4646d..38a44ec 100644 --- a/resources/views/admin/siswa/siswa.blade.php +++ b/resources/views/admin/siswa/siswa.blade.php @@ -21,271 +21,187 @@
- +
Tampilkan
-
- - - - - - - - - - - - - - - - -
NoID SiswaNama LengkapNISNKelasOpsi
- Memuat data siswa... -
-
- -
-
- Menampilkan 0 sampai 0 dari 0 data +
-
-
+
diff --git a/resources/views/admin/siswa/siswaedit.blade.php b/resources/views/admin/siswa/siswaedit.blade.php index e3df4ab..848aec3 100644 --- a/resources/views/admin/siswa/siswaedit.blade.php +++ b/resources/views/admin/siswa/siswaedit.blade.php @@ -7,43 +7,76 @@
-

Edit Data Siswa

-

Memuat data...

+

Edit Data Profil Siswa

+

Ubah rincian informasi dan akun profil siswa.

-
- - + + +
+ +
+
+ Informasi Akun Login +
+
+
+ +
+ + +
+ +
+ + +
+ +
+
+ Data Profil Siswa +
+
+
+
- +
- -
- - + +
+ +
-
- - -
- -
- - + + + + + + + + + +
- - Batal + + + + Batal +
@@ -52,71 +85,134 @@
@endsection \ No newline at end of file diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 3330576..a732cb6 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -75,20 +75,26 @@ class="input-modern w-full text-slate-900"
- + - @@ -153,7 +75,7 @@

- Isi data berikut jika ingin membuat rekomendasi baru. Jika ingin melihat hasil sebelumnya, klik menu Hasil Rekomendasi pada navbar. + Isi data berikut untuk mendapatkan rekomendasi jurusan.

@@ -161,7 +83,6 @@
-

💰 Kondisi Ekonomi @@ -176,15 +97,9 @@ id="gaji_ortu" min="0" placeholder="Contoh: 3000000" - class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base font-semibold text-slate-700 outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100" - > - -

- Isi dengan angka saja tanpa titik atau koma. -

+ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base font-semibold text-slate-700 outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100">

-

🎯 Kuesioner Minat dan Bakat @@ -273,7 +188,6 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f

-

📚 Nilai Rapor @@ -284,146 +198,57 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f

-
- - -
+ @php + $mapel = [ + 'agama' => 'Agama', + 'pkn' => 'PKN', + 'bahasa_indonesia' => 'B. Indonesia', + 'bahasa_inggris' => 'B. Inggris', + 'matematika' => 'Matematika', + 'fisika' => 'Fisika', + 'kimia' => 'Kimia', + 'biologi' => 'Biologi', + 'sejarah' => 'Sejarah', + 'geografi' => 'Geografi', + 'ekonomi_mapel' => 'Ekonomi', + 'sosiologi' => 'Sosiologi', + 'seni_budaya' => 'Seni Budaya', + 'pjok' => 'PJOK', + 'informatika' => 'Informatika', + ]; + @endphp -
- - -
+ @foreach ($mapel as $id => $label) +
+ -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
+ +
+ @endforeach

- -
- - Kembali - - - -
+
- -