diff --git a/.env.example b/.env.example index ab1597d..1082b46 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY= +APP_KEY=base64:/f9/a5B/t+4N0BXg4HKByxd0go5w25WDnaWeQW3hnfk= APP_DEBUG=true APP_URL=http://localhost @@ -11,7 +11,7 @@ LOG_LEVEL=debug DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 -DB_DATABASE=laravel +DB_DATABASE=db_rekomendasi_polije DB_USERNAME=root DB_PASSWORD= @@ -28,14 +28,18 @@ REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 +GEMINI_API_KEY=AIzaSyBAY6QGk0dbHDrLHya7qeu4JdMJEbxeUoM +GEMINI_BACKEND_URL=http://127.0.0.1:5000 +GEMINI_BACKEND_TOKEN= + MAIL_MAILER=smtp -MAIL_HOST=mailpit -MAIL_PORT=1025 -MAIL_USERNAME=null -MAIL_PASSWORD=null -MAIL_ENCRYPTION=null -MAIL_FROM_ADDRESS="hello@example.com" -MAIL_FROM_NAME="${APP_NAME}" +MAIL_HOST=smtp.gmail.com +MAIL_PORT=587 +MAIL_USERNAME=kakapatria22@gmail.com +MAIL_PASSWORD=midpucucemcvtozu +MAIL_ENCRYPTION=tls +MAIL_FROM_ADDRESS=kakapatria22@gmail.com +MAIL_FROM_NAME="SPK Jurusan Polije" AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= @@ -56,7 +60,3 @@ VITE_PUSHER_HOST="${PUSHER_HOST}" VITE_PUSHER_PORT="${PUSHER_PORT}" VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" - -GEMINI_API_KEY= -GEMINI_BACKEND_URL=http://127.0.0.1:8001 -GEMINI_BACKEND_TOKEN= diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 1833b92..18908b4 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":2,"defects":[],"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":1.142,"Tests\\Unit\\RekomendasiAlgorithmTest::test_minat_mapping_logika_komputer":1.83,"Tests\\Unit\\RekomendasiAlgorithmTest::test_minat_mapping_alam_tanaman":0.001,"Tests\\Unit\\RekomendasiAlgorithmTest::test_minat_mapping_bisnis":0.001,"Tests\\Unit\\RekomendasiAlgorithmTest::test_nilai_kategori_tinggi":0,"Tests\\Unit\\RekomendasiAlgorithmTest::test_nilai_kategori_sedang":0,"Tests\\Unit\\RekomendasiAlgorithmTest::test_nilai_kategori_rendah":0,"Tests\\Unit\\RekomendasiAlgorithmTest::test_prestasi_scoring_tinggi":0.326,"Tests\\Unit\\RekomendasiAlgorithmTest::test_prestasi_scoring_sedang":0,"Tests\\Unit\\RekomendasiAlgorithmTest::test_prestasi_scoring_minimal":0.055,"Tests\\Feature\\Auth\\AuthenticationTest::test_login_screen_can_be_rendered":9.692,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":6.712,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":0.471,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_logout":0.172,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":1.41,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_can_be_verified":0.494,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_is_not_verified_with_invalid_hash":0.581,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":1.132,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_can_be_confirmed":0.12,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":0.217,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_screen_can_be_rendered":1.654,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_can_be_requested":3.132,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_screen_can_be_rendered":1.884,"Tests\\Feature\\Auth\\PasswordResetTest::test_password_can_be_reset_with_valid_token":0.498,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_password_can_be_updated":0.196,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_correct_password_must_be_provided_to_update_password":0.175,"Tests\\Feature\\Auth\\RegistrationTest::test_registration_screen_can_be_rendered":0.204,"Tests\\Feature\\Auth\\RegistrationTest::test_new_users_can_register":0.104,"Tests\\Feature\\CrudValidationTest::test_admin_can_add_jurusan_data":0.229,"Tests\\Feature\\CrudValidationTest::test_bk_can_add_jurusan_data":0.058,"Tests\\Feature\\CrudValidationTest::test_admin_guru_bk_store_validates_email_and_password":0.206,"Tests\\Feature\\CrudValidationTest::test_rekomendasi_ipa_requires_all_ipa_scores":0.014,"Tests\\Feature\\CrudValidationTest::test_admin_student_detail_only_accepts_siswa_id":0.21,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.122,"Tests\\Feature\\ExplainableRecommendationTest::test_recommendation_includes_explanation":1.773,"Tests\\Feature\\ExplainableRecommendationTest::test_scoring_detail_stored_correctly":0.989,"Tests\\Feature\\ExplainableRecommendationTest::test_all_recommendations_have_explanations":0.027,"Tests\\Feature\\ExplainableRecommendationTest::test_explanation_displayed_in_view":0.021,"Tests\\Feature\\ProfileTest::test_profile_page_is_displayed":0.466,"Tests\\Feature\\ProfileTest::test_profile_information_can_be_updated":0.3,"Tests\\Feature\\ProfileTest::test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged":0.216,"Tests\\Feature\\ProfileTest::test_user_can_delete_their_account":0.118,"Tests\\Feature\\ProfileTest::test_correct_password_must_be_provided_to_delete_account":0.141,"Tests\\Feature\\RekomendasiTest::test_high_math_and_coding_prefers_teknologi_informasi":0.223,"Tests\\Feature\\RekomendasiTest::test_high_language_prefers_bahasa_komunikasi":0.021,"Tests\\Feature\\UserFlowTest::test_siswa_complete_flow":1.02,"Tests\\Feature\\UserFlowTest::test_bk_complete_flow":2.378,"Tests\\Feature\\UserFlowTest::test_admin_complete_flow":2.52,"Tests\\Feature\\UserFlowTest::test_access_control":0.023}} \ No newline at end of file +{"version":2,"defects":[],"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.024,"Tests\\Unit\\RekomendasiAlgorithmTest::test_minat_mapping_logika_komputer":0.026,"Tests\\Unit\\RekomendasiAlgorithmTest::test_minat_mapping_alam_tanaman":0.001,"Tests\\Unit\\RekomendasiAlgorithmTest::test_minat_mapping_bisnis":0.001,"Tests\\Unit\\RekomendasiAlgorithmTest::test_nilai_kategori_tinggi":0,"Tests\\Unit\\RekomendasiAlgorithmTest::test_nilai_kategori_sedang":0.001,"Tests\\Unit\\RekomendasiAlgorithmTest::test_nilai_kategori_rendah":0.001,"Tests\\Unit\\RekomendasiAlgorithmTest::test_prestasi_scoring_tinggi":0.008,"Tests\\Unit\\RekomendasiAlgorithmTest::test_prestasi_scoring_sedang":0.001,"Tests\\Unit\\RekomendasiAlgorithmTest::test_prestasi_scoring_minimal":0.002,"Tests\\Feature\\Auth\\AuthenticationTest::test_login_screen_can_be_rendered":0.115,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":0.245,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":0.229,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_logout":0.011,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":0.019,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_can_be_verified":0.015,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_is_not_verified_with_invalid_hash":0.017,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":0.022,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_can_be_confirmed":0.123,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":0.209,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_screen_can_be_rendered":0.015,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_can_be_requested":0.07,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_screen_can_be_rendered":0.029,"Tests\\Feature\\Auth\\PasswordResetTest::test_password_can_be_reset_with_valid_token":0.034,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_password_can_be_updated":0.119,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_correct_password_must_be_provided_to_update_password":0.129,"Tests\\Feature\\Auth\\RegistrationTest::test_registration_screen_can_be_rendered":0.008,"Tests\\Feature\\Auth\\RegistrationTest::test_new_users_can_register":0.015,"Tests\\Feature\\CrudValidationTest::test_admin_can_add_jurusan_data":0.012,"Tests\\Feature\\CrudValidationTest::test_bk_can_add_jurusan_data":0.016,"Tests\\Feature\\CrudValidationTest::test_admin_guru_bk_store_validates_email_and_password":0.015,"Tests\\Feature\\CrudValidationTest::test_rekomendasi_ipa_requires_all_ipa_scores":0.015,"Tests\\Feature\\CrudValidationTest::test_admin_student_detail_only_accepts_siswa_id":0.024,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.007,"Tests\\Feature\\ExplainableRecommendationTest::test_recommendation_includes_explanation":0.036,"Tests\\Feature\\ExplainableRecommendationTest::test_scoring_detail_stored_correctly":0.02,"Tests\\Feature\\ExplainableRecommendationTest::test_all_recommendations_have_explanations":0.024,"Tests\\Feature\\ExplainableRecommendationTest::test_explanation_displayed_in_view":0.026,"Tests\\Feature\\ProfileTest::test_profile_page_is_displayed":0.01,"Tests\\Feature\\ProfileTest::test_profile_information_can_be_updated":0.016,"Tests\\Feature\\ProfileTest::test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged":0.009,"Tests\\Feature\\ProfileTest::test_user_can_delete_their_account":0.21,"Tests\\Feature\\ProfileTest::test_correct_password_must_be_provided_to_delete_account":0.148,"Tests\\Feature\\RekomendasiTest::test_high_math_and_coding_prefers_teknologi_informasi":0.06,"Tests\\Feature\\RekomendasiTest::test_high_language_prefers_bahasa_komunikasi":0.019,"Tests\\Feature\\UserFlowTest::test_siswa_complete_flow":0.055,"Tests\\Feature\\UserFlowTest::test_bk_complete_flow":0.072,"Tests\\Feature\\UserFlowTest::test_admin_complete_flow":0.09,"Tests\\Feature\\UserFlowTest::test_access_control":0.027}} \ No newline at end of file diff --git a/DATA RESAPAN TAMATAN.xlsx b/DATA RESAPAN TAMATAN.xlsx new file mode 100644 index 0000000..74d409f Binary files /dev/null and b/DATA RESAPAN TAMATAN.xlsx differ diff --git a/app/Console/Commands/ImportAlumni.php b/app/Console/Commands/ImportAlumni.php new file mode 100644 index 0000000..a6e17c3 --- /dev/null +++ b/app/Console/Commands/ImportAlumni.php @@ -0,0 +1,240 @@ +argument('file'); + + // Check if file exists + if (!file_exists($filePath)) { + $this->error("āŒ File tidak ditemukan: {$filePath}"); + return 1; + } + + // Check if file is Excel + $ext = pathinfo($filePath, PATHINFO_EXTENSION); + if (!in_array(strtolower($ext), ['xlsx', 'xls', 'csv'])) { + $this->error("āŒ Format file tidak didukung. Gunakan .xlsx, .xls, atau .csv"); + return 1; + } + + try { + $this->info("šŸ“‚ Membaca file: {$filePath}"); + + // Read Excel file + $spreadsheet = IOFactory::load($filePath); + $sheet = $spreadsheet->getActiveSheet(); + $data = $sheet->toArray(); + + if (count($data) < 2) { + $this->error("āŒ File Excel kosong atau hanya memiliki header"); + return 1; + } + + // Get headers + $headers = array_map('strtolower', $data[0]); + $headers = array_map(fn($h) => trim(str_replace([' ', '-', '_'], '_', $h)), $headers); + + $this->line("āœ“ Header ditemukan: " . implode(', ', $headers)); + + // Normalize column mapping + $columnMap = $this->normalizeColumns($headers); + $this->line("āœ“ Kolom di-mapping"); + + // Process rows + $rows = array_slice($data, 1); + $successCount = 0; + $errorCount = 0; + $errors = []; + + $this->info("\nšŸ“Š Memproses " . count($rows) . " baris data..."); + + // Progress bar + $bar = $this->output->createProgressBar(count($rows)); + $bar->start(); + + foreach ($rows as $idx => $row) { + $record = $this->mapRow($row, $headers, $columnMap); + + if ($record === null || empty($record['nama_alumni'])) { + $bar->advance(); + continue; + } + + // Validate required fields + $validator = Validator::make($record, [ + 'nama_alumni' => 'required|string', + 'nis' => 'nullable|string', + 'kelompok_asal' => 'nullable|string', + 'nilai_rata_rata' => 'nullable|numeric', + 'minat' => 'nullable|string', + 'cita_cita' => 'nullable|string', + 'preferensi_studi' => 'nullable|string', + 'prestasi' => 'nullable|string', + 'major_masuk' => 'nullable|string', + 'tahun_lulus_polije' => 'nullable|numeric', + ]); + + if ($validator->fails()) { + $errorCount++; + $errors[] = "Baris " . ($idx + 2) . ": " . implode(', ', $validator->errors()->all()); + $bar->advance(); + continue; + } + + try { + // Check if already exists + $existing = Alumni::where('nis', $record['nis'] ?? null) + ->where('nama_alumni', $record['nama_alumni'] ?? null) + ->first(); + + if (!$existing) { + Alumni::create($record); + $successCount++; + } else { + // Update jika sudah ada + $existing->update($record); + $successCount++; + } + } catch (\Exception $e) { + $errorCount++; + $errors[] = "Baris " . ($idx + 2) . ": " . $e->getMessage(); + } + + $bar->advance(); + } + + $bar->finish(); + + // Summary + $this->newLine(2); + $this->info("=" . str_repeat("=", 58) . "="); + $this->info("āœ“ IMPORT SELESAI"); + $this->info("=" . str_repeat("=", 58) . "="); + $this->line("āœ“ Data berhasil di-import: {$successCount}"); + if ($errorCount > 0) { + $this->line("⚠ Baris error/skip: {$errorCount}"); + + if (count($errors) > 0 && count($errors) <= 20) { + $this->newLine(); + $this->warn("Errors:"); + foreach ($errors as $error) { + $this->line(" - {$error}"); + } + } + } + $this->newLine(); + + return 0; + + } catch (\Exception $e) { + $this->error("āŒ Error: " . $e->getMessage()); + return 1; + } + } + + /** + * Normalize column headers to database column names + */ + private function normalizeColumns($headers) + { + $mapping = [ + 'nama' => 'nama_alumni', + 'nama_alumni' => 'nama_alumni', + 'nis' => 'nis', + 'no_induk_siswa' => 'nis', + 'kelompok_asal' => 'kelompok_asal', + 'kelompok' => 'kelompok_asal', + 'nilai_(rata_rata)' => 'nilai_rata_rata', + 'nilai_rata_rata' => 'nilai_rata_rata', + 'rata_rata' => 'nilai_rata_rata', + 'average' => 'nilai_rata_rata', + 'minat' => 'minat', + 'interest' => 'minat', + 'cita_cita' => 'cita_cita', + 'cita' => 'cita_cita', + 'dream_job' => 'cita_cita', + 'preferensi_studi' => 'preferensi_studi', + 'preferensi' => 'preferensi_studi', + 'preference' => 'preferensi_studi', + 'prestasi' => 'prestasi', + 'achievement' => 'prestasi', + 'major_masuk' => 'major_masuk', + 'jurusan_masuk' => 'major_masuk', + 'jurusan_keterima_di_polije' => 'major_masuk', + 'jurusan' => 'major_masuk', + 'major' => 'major_masuk', + 'tahun_lulus_polije' => 'tahun_lulus_polije', + 'tahun_lulus' => 'tahun_lulus_polije', + 'graduation_year' => 'tahun_lulus_polije', + 'tahun' => 'tahun_lulus_polije', + 'catatan' => 'catatan', + 'keterangan' => 'catatan', + 'notes' => 'catatan', + ]; + + return $mapping; + } + + /** + * Map row data to Alumni model + */ + private function mapRow($row, $headers, $columnMap) + { + $record = []; + + foreach ($headers as $idx => $header) { + $value = $row[$idx] ?? null; + + // Map column name + $dbColumn = $columnMap[strtolower($header)] ?? null; + + if (!$dbColumn) { + continue; + } + + // Type conversion + if (in_array($dbColumn, ['nilai_rata_rata', 'tahun_lulus_polije'])) { + $record[$dbColumn] = $value ? (float) $value : null; + } else { + $cleanValue = $value ? trim((string) $value) : null; + + // Special handling for preferensi_studi - truncate to enum value + if ($dbColumn === 'preferensi_studi' && $cleanValue) { + // Extract only the category part (before the parenthesis) + $parts = explode('(', $cleanValue); + $cleanValue = trim($parts[0]); + } + + $record[$dbColumn] = $cleanValue; + } + } + + return empty($record['nama_alumni'] ?? null) ? null : $record; + } +} diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 345e10f..c2b32bf 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -155,18 +155,21 @@ public function jurusanStore(Request $request) { $request->validate([ 'nama_jurusan' => 'required|string|min:3|max:255|unique:jurusan_polije,nama_jurusan', - 'deskripsi' => 'nullable|string|max:1000', + 'deskripsi' => 'nullable|string|max:10000', 'keywords' => 'nullable|string', 'preferensi_studi' => 'nullable|string', 'prospek_kerja' => 'nullable|string|max:1000', - 'bobot_mtk' => 'nullable|numeric|min:0|max:1', - 'bobot_fisika' => 'nullable|numeric|min:0|max:1', - 'bobot_kimia' => 'nullable|numeric|min:0|max:1', - 'bobot_biologi' => 'nullable|numeric|min:0|max:1', - 'bobot_ekonomi' => 'nullable|numeric|min:0|max:1', - 'bobot_geografi' => 'nullable|numeric|min:0|max:1', - 'bobot_sosiologi' => 'nullable|numeric|min:0|max:1', - 'bobot_sejarah' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel' => 'nullable|array', + 'bobot_mapel.ipa' => 'nullable|array', + 'bobot_mapel.ipa.mtk' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.fisika' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.kimia' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.biologi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips' => 'nullable|array', + 'bobot_mapel.ips.ekonomi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.geografi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.sosiologi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.sejarah' => 'nullable|numeric|min:0|max:1', ]); PolijeMajor::create([ @@ -193,18 +196,21 @@ public function jurusanUpdate(Request $request, $id) $request->validate([ 'nama_jurusan' => ['required', 'string', 'min:3', 'max:255', Rule::unique('jurusan_polije', 'nama_jurusan')->ignore($jurusan->id)], - 'deskripsi' => 'nullable|string|max:1000', + 'deskripsi' => 'nullable|string|max:10000', 'keywords' => 'nullable|string', 'preferensi_studi' => 'nullable|string', 'prospek_kerja' => 'nullable|string|max:1000', - 'bobot_mtk' => 'nullable|numeric|min:0|max:1', - 'bobot_fisika' => 'nullable|numeric|min:0|max:1', - 'bobot_kimia' => 'nullable|numeric|min:0|max:1', - 'bobot_biologi' => 'nullable|numeric|min:0|max:1', - 'bobot_ekonomi' => 'nullable|numeric|min:0|max:1', - 'bobot_geografi' => 'nullable|numeric|min:0|max:1', - 'bobot_sosiologi' => 'nullable|numeric|min:0|max:1', - 'bobot_sejarah' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel' => 'nullable|array', + 'bobot_mapel.ipa' => 'nullable|array', + 'bobot_mapel.ipa.mtk' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.fisika' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.kimia' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.biologi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips' => 'nullable|array', + 'bobot_mapel.ips.ekonomi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.geografi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.sosiologi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.sejarah' => 'nullable|numeric|min:0|max:1', ]); $jurusan->update([ @@ -243,17 +249,45 @@ private function parseTagInput(?string $input): array */ private function parseBobotMapel(Request $request): array { - $mapelList = ['mtk', 'fisika', 'kimia', 'biologi', 'ekonomi', 'geografi', 'sosiologi', 'sejarah']; - $bobot = []; + $ipaSubjects = ['mtk', 'fisika', 'kimia', 'biologi']; + $ipsSubjects = ['ekonomi', 'geografi', 'sosiologi', 'sejarah']; - foreach ($mapelList as $mapel) { - $value = $request->input("bobot_{$mapel}"); - if (!is_null($value) && $value !== '') { - $bobot[$mapel] = floatval($value); - } + $ipaInput = $request->input('bobot_mapel.ipa'); + $ipsInput = $request->input('bobot_mapel.ips'); + + if (is_array($ipaInput) || is_array($ipsInput)) { + return [ + 'ipa' => $this->normalizeBobotGroup(is_array($ipaInput) ? $ipaInput : [], $ipaSubjects), + 'ips' => $this->normalizeBobotGroup(is_array($ipsInput) ? $ipsInput : [], $ipsSubjects), + ]; } - return $bobot; + return [ + 'ipa' => $this->normalizeBobotGroup([ + 'mtk' => $request->input('bobot_mtk'), + 'fisika' => $request->input('bobot_fisika'), + 'kimia' => $request->input('bobot_kimia'), + 'biologi' => $request->input('bobot_biologi'), + ], $ipaSubjects), + 'ips' => $this->normalizeBobotGroup([ + 'ekonomi' => $request->input('bobot_ekonomi'), + 'geografi' => $request->input('bobot_geografi'), + 'sosiologi' => $request->input('bobot_sosiologi'), + 'sejarah' => $request->input('bobot_sejarah'), + ], $ipsSubjects), + ]; + } + + private function normalizeBobotGroup(array $values, array $subjects): array + { + $normalized = []; + + foreach ($subjects as $subject) { + $value = $values[$subject] ?? null; + $normalized[$subject] = is_numeric($value) ? (float) $value : 0.0; + } + + return $normalized; } // ============================================ diff --git a/app/Http/Controllers/AlumniController.php b/app/Http/Controllers/AlumniController.php index ded93fd..15bd3a3 100644 --- a/app/Http/Controllers/AlumniController.php +++ b/app/Http/Controllers/AlumniController.php @@ -7,6 +7,37 @@ class AlumniController extends Controller { + private const IPA_SUBJECTS = ['mtk', 'fisika', 'kimia', 'biologi']; + private const IPS_SUBJECTS = ['ekonomi', 'geografi', 'sosiologi', 'sejarah']; + + private const ALL_SUBJECTS = ['mtk', 'fisika', 'kimia', 'biologi', 'ekonomi', 'geografi', 'sosiologi', 'sejarah']; + + private function normalizeScoreFields(array $validated, string $kelompokAsal): array + { + $activeSubjects = $kelompokAsal === 'IPA' ? self::IPA_SUBJECTS : self::IPS_SUBJECTS; + + foreach (self::ALL_SUBJECTS as $subject) { + if (!in_array($subject, $activeSubjects, true)) { + $validated[$subject] = null; + } + } + + return $validated; + } + + private function validateScoreByKelompok(Request $request): void + { + $requiredSubjects = $request->input('kelompok_asal') === 'IPA' + ? self::IPA_SUBJECTS + : self::IPS_SUBJECTS; + + foreach ($requiredSubjects as $subject) { + $request->validate([ + $subject => 'required|numeric|min:0|max:100', + ]); + } + } + /** * Display alumni data list */ @@ -48,7 +79,7 @@ public function store(Request $request) // Non-akademik 'minat' => 'nullable|string|max:255', 'cita_cita' => 'nullable|string|max:255', - 'preferensi_studi' => 'nullable|in:Sains & Teknologi,Pertanian & Lingkungan,Kesehatan & Ilmu Hayat,Bisnis & Manajemen,Sosial & Humaniora', + 'preferensi_studi' => 'nullable|in:Praktik Langsung,Praktik_Langsung,DuDi,Project Based,Project_Based,Blended Learning,Blended', 'prestasi' => 'nullable|string|max:255', // Major @@ -57,6 +88,9 @@ public function store(Request $request) 'catatan' => 'nullable|string|max:500', ]); + $this->validateScoreByKelompok($request); + $validated = $this->normalizeScoreFields($validated, $validated['kelompok_asal']); + Alumni::create($validated); return redirect()->route('admin.alumni.index')->with('success', 'Alumni berhasil ditambahkan'); @@ -99,7 +133,7 @@ public function update(Request $request, Alumni $alumni) 'minat' => 'nullable|string|max:255', 'cita_cita' => 'nullable|string|max:255', - 'preferensi_studi' => 'nullable|in:Sains & Teknologi,Pertanian & Lingkungan,Kesehatan & Ilmu Hayat,Bisnis & Manajemen,Sosial & Humaniora', + 'preferensi_studi' => 'nullable|in:Praktik Langsung,Praktik_Langsung,DuDi,Project Based,Project_Based,Blended Learning,Blended', 'prestasi' => 'nullable|string|max:255', 'major_masuk' => 'required|string|min:3|max:255', @@ -107,6 +141,9 @@ public function update(Request $request, Alumni $alumni) 'catatan' => 'nullable|string|max:500', ]); + $this->validateScoreByKelompok($request); + $validated = $this->normalizeScoreFields($validated, $validated['kelompok_asal']); + $alumni->update($validated); return redirect()->route('admin.alumni.index')->with('success', 'Alumni berhasil diupdate'); diff --git a/app/Http/Controllers/AlumniImportController.php b/app/Http/Controllers/AlumniImportController.php new file mode 100644 index 0000000..88f28d8 --- /dev/null +++ b/app/Http/Controllers/AlumniImportController.php @@ -0,0 +1,198 @@ +all(), [ + 'file' => 'required|file|mimes:xlsx,xls,csv|max:10240', // max 10MB + ], [ + 'file.required' => 'File harus dipilih', + 'file.mimes' => 'File harus format .xlsx, .xls, atau .csv', + 'file.max' => 'File tidak boleh lebih dari 10MB', + ]); + + if ($validator->fails()) { + return back() + ->withErrors($validator) + ->withInput(); + } + + try { + $file = $request->file('file'); + $filePath = $file->store('temp', 'local'); + $fullPath = storage_path('app/' . $filePath); + + // Read Excel + $spreadsheet = IOFactory::load($fullPath); + $sheet = $spreadsheet->getActiveSheet(); + $data = $sheet->toArray(); + + if (count($data) < 2) { + return back()->with('error', 'File Excel kosong atau hanya memiliki header'); + } + + // Get headers + $headers = array_map('strtolower', $data[0]); + $headers = array_map(fn($h) => trim(str_replace([' ', '-', '_'], '_', $h)), $headers); + + // Normalize column mapping + $columnMap = $this->normalizeColumns(); + + // Process rows + $rows = array_slice($data, 1); + $successCount = 0; + $errorCount = 0; + $errors = []; + + foreach ($rows as $idx => $row) { + $record = $this->mapRow($row, $headers, $columnMap); + + if ($record === null || empty($record['nama_alumni'])) { + continue; + } + + // Validate required fields + $validator = Validator::make($record, [ + 'nama_alumni' => 'required|string', + 'kelompok_asal' => 'nullable|string', + 'nilai_rata_rata' => 'nullable|numeric', + ]); + + if ($validator->fails()) { + $errorCount++; + $errors[] = "Baris " . ($idx + 2) . ": " . implode(', ', $validator->errors()->all()); + continue; + } + + try { + // Check if already exists + $existing = Alumni::where('nis', $record['nis'] ?? '') + ->where('nama_alumni', $record['nama_alumni']) + ->first(); + + if (!$existing) { + Alumni::create($record); + $successCount++; + } else { + // Update jika sudah ada + $existing->update($record); + $successCount++; + } + } catch (\Exception $e) { + $errorCount++; + $errors[] = "Baris " . ($idx + 2) . ": " . $e->getMessage(); + } + } + + // Clean up temp file + @unlink($fullPath); + + // Prepare response + $message = "āœ“ Import Selesai! {$successCount} data berhasil diimport"; + if ($errorCount > 0) { + $message .= " ({$errorCount} error/skip)"; + } + + return back() + ->with('success', $message) + ->with('successCount', $successCount) + ->with('errorCount', $errorCount) + ->with('errors', count($errors) <= 10 ? $errors : array_slice($errors, 0, 10)); + + } catch (\Exception $e) { + return back()->with('error', 'Error: ' . $e->getMessage()); + } + } + + /** + * Normalize column headers to database column names + */ + private function normalizeColumns() + { + return [ + 'nama' => 'nama_alumni', + 'nama_alumni' => 'nama_alumni', + 'nis' => 'nis', + 'no_induk_siswa' => 'nis', + 'kelompok_asal' => 'kelompok_asal', + 'kelompok' => 'kelompok_asal', + 'nilai_(rata_rata)' => 'nilai_rata_rata', + 'nilai_rata_rata' => 'nilai_rata_rata', + 'rata_rata' => 'nilai_rata_rata', + 'average' => 'nilai_rata_rata', + 'minat' => 'minat', + 'interest' => 'minat', + 'cita_cita' => 'cita_cita', + 'cita' => 'cita_cita', + 'dream_job' => 'cita_cita', + 'preferensi_studi' => 'preferensi_studi', + 'preferensi' => 'preferensi_studi', + 'preference' => 'preferensi_studi', + 'prestasi' => 'prestasi', + 'achievement' => 'prestasi', + 'major_masuk' => 'major_masuk', + 'jurusan_masuk' => 'major_masuk', + 'jurusan' => 'major_masuk', + 'major' => 'major_masuk', + 'tahun_lulus_polije' => 'tahun_lulus_polije', + 'tahun_lulus' => 'tahun_lulus_polije', + 'graduation_year' => 'tahun_lulus_polije', + 'tahun' => 'tahun_lulus_polije', + 'catatan' => 'catatan', + 'keterangan' => 'catatan', + 'notes' => 'catatan', + ]; + } + + /** + * Map row data to Alumni model + */ + private function mapRow($row, $headers, $columnMap) + { + $record = []; + + foreach ($headers as $idx => $header) { + $value = $row[$idx] ?? null; + + if (!$value) { + continue; + } + + // Map column name + $dbColumn = $columnMap[strtolower($header)] ?? null; + + if (!$dbColumn) { + continue; + } + + // Type conversion + if (in_array($dbColumn, ['nilai_rata_rata', 'tahun_lulus_polije'])) { + $record[$dbColumn] = (float) $value; + } else { + $record[$dbColumn] = trim((string) $value); + } + } + + return empty($record) ? null : $record; + } +} diff --git a/app/Http/Controllers/BKController.php b/app/Http/Controllers/BKController.php index 3a4f538..a6c4e88 100644 --- a/app/Http/Controllers/BKController.php +++ b/app/Http/Controllers/BKController.php @@ -14,6 +14,37 @@ class BKController extends Controller { + private const IPA_SUBJECTS = ['mtk', 'fisika', 'kimia', 'biologi']; + private const IPS_SUBJECTS = ['ekonomi', 'geografi', 'sosiologi', 'sejarah']; + + private const ALL_SUBJECTS = ['mtk', 'fisika', 'kimia', 'biologi', 'ekonomi', 'geografi', 'sosiologi', 'sejarah']; + + private function normalizeScoreFields(array $validated, string $kelompokAsal): array + { + $activeSubjects = $kelompokAsal === 'IPA' ? self::IPA_SUBJECTS : self::IPS_SUBJECTS; + + foreach (self::ALL_SUBJECTS as $subject) { + if (!in_array($subject, $activeSubjects, true)) { + $validated[$subject] = null; + } + } + + return $validated; + } + + private function validateScoreByKelompok(Request $request): void + { + $requiredSubjects = $request->input('kelompok_asal') === 'IPA' + ? self::IPA_SUBJECTS + : self::IPS_SUBJECTS; + + foreach ($requiredSubjects as $subject) { + $request->validate([ + $subject => 'required|numeric|min:0|max:100', + ]); + } + } + // ============================================ // 1. DASHBOARD // ============================================ @@ -213,18 +244,21 @@ public function jurusanStore(Request $request) { $request->validate([ 'nama_jurusan' => 'required|string|min:3|max:255|unique:jurusan_polije,nama_jurusan', - 'deskripsi' => 'nullable|string|max:1000', + 'deskripsi' => 'nullable|string|max:10000', 'keywords' => 'nullable|string', 'preferensi_studi' => 'nullable|string', 'prospek_kerja' => 'nullable|string|max:1000', - 'bobot_mtk' => 'nullable|numeric|min:0|max:1', - 'bobot_fisika' => 'nullable|numeric|min:0|max:1', - 'bobot_kimia' => 'nullable|numeric|min:0|max:1', - 'bobot_biologi' => 'nullable|numeric|min:0|max:1', - 'bobot_ekonomi' => 'nullable|numeric|min:0|max:1', - 'bobot_geografi' => 'nullable|numeric|min:0|max:1', - 'bobot_sosiologi' => 'nullable|numeric|min:0|max:1', - 'bobot_sejarah' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel' => 'nullable|array', + 'bobot_mapel.ipa' => 'nullable|array', + 'bobot_mapel.ipa.mtk' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.fisika' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.kimia' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.biologi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips' => 'nullable|array', + 'bobot_mapel.ips.ekonomi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.geografi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.sosiologi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.sejarah' => 'nullable|numeric|min:0|max:1', ]); PolijeMajor::create([ @@ -251,18 +285,21 @@ public function jurusanUpdate(Request $request, $id) $request->validate([ 'nama_jurusan' => ['required', 'string', 'min:3', 'max:255', Rule::unique('jurusan_polije', 'nama_jurusan')->ignore($jurusan->id)], - 'deskripsi' => 'nullable|string|max:1000', + 'deskripsi' => 'nullable|string|max:10000', 'keywords' => 'nullable|string', 'preferensi_studi' => 'nullable|string', 'prospek_kerja' => 'nullable|string|max:1000', - 'bobot_mtk' => 'nullable|numeric|min:0|max:1', - 'bobot_fisika' => 'nullable|numeric|min:0|max:1', - 'bobot_kimia' => 'nullable|numeric|min:0|max:1', - 'bobot_biologi' => 'nullable|numeric|min:0|max:1', - 'bobot_ekonomi' => 'nullable|numeric|min:0|max:1', - 'bobot_geografi' => 'nullable|numeric|min:0|max:1', - 'bobot_sosiologi' => 'nullable|numeric|min:0|max:1', - 'bobot_sejarah' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel' => 'nullable|array', + 'bobot_mapel.ipa' => 'nullable|array', + 'bobot_mapel.ipa.mtk' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.fisika' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.kimia' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ipa.biologi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips' => 'nullable|array', + 'bobot_mapel.ips.ekonomi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.geografi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.sosiologi' => 'nullable|numeric|min:0|max:1', + 'bobot_mapel.ips.sejarah' => 'nullable|numeric|min:0|max:1', ]); $jurusan->update([ @@ -293,15 +330,45 @@ private function parseTagInput(?string $input): array private function parseBobotMapel(Request $request): array { - $mapelList = ['mtk', 'fisika', 'kimia', 'biologi', 'ekonomi', 'geografi', 'sosiologi', 'sejarah']; - $bobot = []; - foreach ($mapelList as $mapel) { - $value = $request->input("bobot_{$mapel}"); - if (!is_null($value) && $value !== '') { - $bobot[$mapel] = floatval($value); - } + $ipaSubjects = ['mtk', 'fisika', 'kimia', 'biologi']; + $ipsSubjects = ['ekonomi', 'geografi', 'sosiologi', 'sejarah']; + + $ipaInput = $request->input('bobot_mapel.ipa'); + $ipsInput = $request->input('bobot_mapel.ips'); + + if (is_array($ipaInput) || is_array($ipsInput)) { + return [ + 'ipa' => $this->normalizeBobotGroup(is_array($ipaInput) ? $ipaInput : [], $ipaSubjects), + 'ips' => $this->normalizeBobotGroup(is_array($ipsInput) ? $ipsInput : [], $ipsSubjects), + ]; } - return $bobot; + + return [ + 'ipa' => $this->normalizeBobotGroup([ + 'mtk' => $request->input('bobot_mtk'), + 'fisika' => $request->input('bobot_fisika'), + 'kimia' => $request->input('bobot_kimia'), + 'biologi' => $request->input('bobot_biologi'), + ], $ipaSubjects), + 'ips' => $this->normalizeBobotGroup([ + 'ekonomi' => $request->input('bobot_ekonomi'), + 'geografi' => $request->input('bobot_geografi'), + 'sosiologi' => $request->input('bobot_sosiologi'), + 'sejarah' => $request->input('bobot_sejarah'), + ], $ipsSubjects), + ]; + } + + private function normalizeBobotGroup(array $values, array $subjects): array + { + $normalized = []; + + foreach ($subjects as $subject) { + $value = $values[$subject] ?? null; + $normalized[$subject] = is_numeric($value) ? (float) $value : 0.0; + } + + return $normalized; } // ============================================ @@ -340,7 +407,7 @@ public function alumniStore(Request $request) // Non-akademik 'minat' => 'nullable|string|max:255', 'cita_cita' => 'nullable|string|max:255', - 'preferensi_studi' => 'nullable|in:Sains & Teknologi,Pertanian & Lingkungan,Kesehatan & Ilmu Hayat,Bisnis & Manajemen,Sosial & Humaniora', + 'preferensi_studi' => 'nullable|in:Praktik Langsung,Praktik_Langsung,DuDi,Project Based,Project_Based,Blended Learning,Blended', 'prestasi' => 'nullable|string|max:255', // Major @@ -349,6 +416,9 @@ public function alumniStore(Request $request) 'catatan' => 'nullable|string|max:500', ]); + $this->validateScoreByKelompok($request); + $validated = $this->normalizeScoreFields($validated, $validated['kelompok_asal']); + Alumni::create($validated); return redirect()->route('bk.alumni')->with('success', 'Alumni berhasil ditambahkan'); @@ -382,7 +452,7 @@ public function alumniUpdate(Request $request, Alumni $alumni) 'minat' => 'nullable|string|max:255', 'cita_cita' => 'nullable|string|max:255', - 'preferensi_studi' => 'nullable|in:Sains & Teknologi,Pertanian & Lingkungan,Kesehatan & Ilmu Hayat,Bisnis & Manajemen,Sosial & Humaniora', + 'preferensi_studi' => 'nullable|in:Praktik Langsung,Praktik_Langsung,DuDi,Project Based,Project_Based,Blended Learning,Blended', 'prestasi' => 'nullable|string|max:255', 'major_masuk' => 'required|string|min:3|max:255', @@ -390,6 +460,9 @@ public function alumniUpdate(Request $request, Alumni $alumni) 'catatan' => 'nullable|string|max:500', ]); + $this->validateScoreByKelompok($request); + $validated = $this->normalizeScoreFields($validated, $validated['kelompok_asal']); + $alumni->update($validated); return redirect()->route('bk.alumni')->with('success', 'Alumni berhasil diupdate'); diff --git a/app/Http/Controllers/ChatbotController.php b/app/Http/Controllers/ChatbotController.php index 3fec077..dfbacc1 100644 --- a/app/Http/Controllers/ChatbotController.php +++ b/app/Http/Controllers/ChatbotController.php @@ -30,10 +30,18 @@ public function index(Request $request) $user = Auth::user(); $sessionId = $request->query('session'); $recId = $request->query('rec'); + $isNew = $request->query('new'); // Deteksi jika langsung buka chatbot tanpa rekomendasi $previousMessages = []; $recommendationId = null; - if ($sessionId) { + // Jika ?new=1, abaikan session dan rekomendasi lama - buat fresh session + if ($isNew) { + $sessionId = Str::uuid()->toString(); + $recommendationId = null; + $previousMessages = []; + // Clear session lama + session()->forget('recomendation_data'); + } else if ($sessionId) { // Lanjutkan sesi lama — ambil semua chat dari sesi ini $chats = ChatHistory::where('user_id', $user->id) ->where('id_sesi', $sessionId) @@ -63,63 +71,28 @@ public function index(Request $request) $sessionId = Str::uuid()->toString(); } - // Tentukan recommendation_id: - // 1. Dari sesi lama (sudah diset di atas) - // 2. Dari parameter ?rec= (klik dari hasil rekomendasi) - // JANGAN fallback ke rekomendasi terbaru - biarkan null jika tidak ada + // Tentukan recommendation_id (kecuali sudah diset oleh ?new=1 atau session lama): if (!$recommendationId && $recId) { $rec = Recommendation::where('id', $recId) ->where('user_id', $user->id) ->first(); $recommendationId = $rec ? $rec->id : null; } - // Jika tidak ada recommendation_id dari session atau ?rec param, biarkan null - // Ambil konteks rekomendasi berdasarkan ID spesifik - $recentRecommendation = $this->getRecommendationContext($user, $recommendationId) ?? []; - - // Ambil 10 session terakhir yang unik untuk user - // Strategy: ambil last chat per session, sort by created_at, limit 10 - $chatHistories = collect(); - - $sessions = ChatHistory::where('user_id', $user->id) - ->select('id_sesi') - ->distinct('id_sesi') - ->get() - ->pluck('id_sesi'); - - foreach ($sessions as $session_id) { - $lastChat = ChatHistory::where('user_id', $user->id) - ->where('id_sesi', $session_id) - ->latest('created_at') - ->first(); - - $firstChat = ChatHistory::where('user_id', $user->id) - ->where('id_sesi', $session_id) - ->oldest('created_at') - ->first(); - - if ($lastChat && $firstChat) { - $chatHistories->push((object)[ - 'id_sesi' => $session_id, - 'created_at' => $lastChat->created_at, - 'prompt' => $firstChat->prompt ?? 'Tidak ada pesan', - ]); - } + // Load rekomendasi terbaru jika tidak ada kondisi di atas + if (!$recommendationId && !$isNew) { + $latestRec = Recommendation::where('user_id', $user->id)->latest()->first(); + $recommendationId = $latestRec ? $latestRec->id : null; } - // Sort by created_at desc dan ambil 10 - $chatHistories = $chatHistories->sortByDesc('created_at') - ->take(10) - ->values() - ->toArray(); + // Ambil konteks rekomendasi berdasarkan ID spesifik + $recentRecommendation = $this->getRecommendationContext($user, $recommendationId, $isNew); return view('chatbot.index', [ 'recommendation' => $recentRecommendation, 'sessionId' => $sessionId, 'previousMessages' => $previousMessages, 'recommendationId' => $recommendationId, - 'chatHistories' => $chatHistories, ]); } @@ -264,30 +237,36 @@ public function historyChat() * Ambil konteks rekomendasi berdasarkan ID spesifik. * Jika ID tidak ada, coba dari session, lalu dari DB (terbaru). */ - private function getRecommendationContext($user, $recommendationId = null) + private function getRecommendationContext($user, $recommendationId = null, $isNew = false) { - // Jika ada recommendation_id spesifik, ambil langsung dari DB - $lastRec = null; - + // Jika ada recommendation_id spesifik, ambil langsung dari DB dan JANGAN fallback if ($recommendationId) { $lastRec = Recommendation::where('id', $recommendationId) ->where('user_id', $user->id) ->first(); - } - - // ONLY fallback: dari session (saat baru selesai rekomendasi) - // Jangan ambil rekomendasi terbaru dari DB jika tidak ada recommendation_id - if (!$lastRec) { + + if (!$lastRec) { + // Jika rec ID tidak ditemukan, jangan fallback - return null + return null; + } + } else if ($isNew) { + // Jika ?new=1, jangan load apapun dari session atau DB + return null; + } else { + // Fallback: dari session (saat baru selesai rekomendasi) $sessionData = session('recomendation_data', null); if ($sessionData) { return $sessionData; } - // Jika tidak ada di session dan tidak ada recommendation_id, return null - return null; - } - if (!$lastRec) { - return null; + // Fallback: rekomendasi terbaru dari DB + $lastRec = Recommendation::where('user_id', $user->id) + ->latest() + ->first(); + + if (!$lastRec) { + return null; + } } // Safely decode hasil_rekomendasi diff --git a/app/Http/Controllers/RekomendasiController.php b/app/Http/Controllers/RekomendasiController.php index a2e6c16..a2c397e 100644 --- a/app/Http/Controllers/RekomendasiController.php +++ b/app/Http/Controllers/RekomendasiController.php @@ -255,7 +255,7 @@ public function proses(Request $request) $prior = 1 / $cfgCount; $logPrior = log(max($prior, $epsilon)); - // Weights dan match probabilities dengan defaults (berdasarkan ROC dari analisis) + // Weights dan match probabilities dengan defaults (ROC-based: nilai 15.6%, minat 45.6%, pref 25.6%, cita 9%, prestasi 4%) $weights = $c['weights'] ?? ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040]; // Ensure weights is array @@ -266,17 +266,16 @@ public function proses(Request $request) // Jika prestasi kosong, atribut prestasi tidak dihitung dengan normalisasi ulang if (!$isPrestasiFilled) { $weights['prestasi'] = 0.0; - $sumNonPrestasi = ($weights['nilai'] ?? 0) + ($weights['minat'] ?? 0) + ($weights['pref'] ?? 0) + ($weights['cita_cita'] ?? 0); + $sumNonPrestasi = ($weights['nilai'] ?? 0) + ($weights['minat'] ?? 0) + ($weights['cita_cita'] ?? 0); // Normalize weights dengan safety check if ($sumNonPrestasi > $epsilon) { $weights['nilai'] = ($weights['nilai'] ?? 0) / $sumNonPrestasi; $weights['minat'] = ($weights['minat'] ?? 0) / $sumNonPrestasi; - $weights['pref'] = ($weights['pref'] ?? 0) / $sumNonPrestasi; $weights['cita_cita'] = ($weights['cita_cita'] ?? 0) / $sumNonPrestasi; } else { // Fallback weights jika semua weight adalah 0 - $weights = ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.238]; + $weights = ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040]; } } @@ -390,8 +389,9 @@ public function proses(Request $request) // Simpan data rekomendasi ke database $user = Auth::user(); + $recommendationId = null; if ($user) { - Recommendation::create([ + $recommendation = Recommendation::create([ 'user_id' => $user->id, 'mtk' => $request->mtk ?? null, 'fisika' => $request->fisika ?? null, @@ -408,6 +408,7 @@ public function proses(Request $request) 'prestasi' => $prestasiInput, 'hasil_rekomendasi' => $hasilAkhir, ]); + $recommendationId = $recommendation->id; } // Simpan data rekomendasi ke session untuk chatbot @@ -430,7 +431,7 @@ public function proses(Request $request) $topJurusan = PolijeMajor::where('nama_jurusan', $hasilAkhir[0]['jurusan'] ?? '')->first(); } - return view('rekomendasi.hasil', compact('hasilAkhir', 'katNilai', 'average', 'minatMapped', 'citaMapped', 'prefStudi', 'prestasiScore', 'topJurusan', 'isPrestasiFilled')); + return view('rekomendasi.hasil', compact('hasilAkhir', 'katNilai', 'average', 'minatRaw', 'minatMapped', 'citaRaw', 'citaMapped', 'prefStudi', 'prestasiScore', 'topJurusan', 'isPrestasiFilled', 'recommendationId')); } /** @@ -482,11 +483,11 @@ private function mapMinat(string $minatRaw): string // Use coverage-based scoring untuk handle ambiguous inputs $categoryKeywords = [ - 'Logika & Komputer' => ['coding', 'komputer', 'laptop', 'web', 'aplikasi', 'logika', 'programming', 'software', 'development', 'developer', 'it', 'data', 'ai'], - 'Alam & Tanaman' => ['tanam', 'kebun', 'sawah', 'hewan', 'ternak', 'alam', 'pertanian', 'agri', 'panen', 'tani', 'hortikultura'], - 'Pelayanan & Kesehatan' => ['obat', 'sakit', 'rawat', 'medis', 'gizi', 'sehat', 'kesehatan', 'perawat', 'dokter', 'rumah sakit', 'klinik'], - 'Manajemen & Bisnis' => ['bisnis', 'uang', 'jual', 'kantor', 'hitung', 'ekonomi', 'dagang', 'usaha', 'entrepreneur', 'manager', 'marketing', 'akuntan'], - 'Mesin & Listrik' => ['mesin', 'bengkel', 'listrik', 'las', 'robot', 'motor', 'teknik', 'otomasi', 'elektronik', 'maintenance'], + 'Logika & Komputer' => ['coding', 'komputer', 'laptop', 'web', 'aplikasi', 'logika', 'programming', 'software', 'development', 'developer', 'it', 'data', 'ai', 'teknologi', 'sistem', 'cloud', 'database', 'network', 'cybersecurity', 'analyst', 'scientist', 'algorithm', 'machine learning', 'app', 'digital'], + 'Alam & Tanaman' => ['tanam', 'kebun', 'sawah', 'hewan', 'ternak', 'alam', 'pertanian', 'agri', 'panen', 'tani', 'hortikultura', 'lingkungan', 'berkelanjutan', 'farm', 'farming', 'plantation', 'crops', 'conservation', 'breeding', 'agribusiness', 'agroforestry', 'horticulture', 'cultivate', 'harvest', 'livestock management', 'animal husbandry', 'sustainable agriculture', 'crop science', 'soil', 'botanical'], + 'Pelayanan & Kesehatan' => ['obat', 'sakit', 'rawat', 'medis', 'gizi', 'sehat', 'kesehatan', 'perawat', 'dokter', 'rumah sakit', 'klinik', 'farmasi', 'keperawatan', 'terapis', 'nursing', 'therapy', 'wellness', 'nutrition', 'healing', 'caring', 'clinical', 'patient care', 'rehabilitation', 'surgery', 'diagnostic', 'laboratory', 'medical technician', 'health educator', 'public health', 'epidemiology', 'preventive care'], + 'Manajemen & Bisnis' => ['bisnis', 'uang', 'jual', 'kantor', 'hitung', 'ekonomi', 'dagang', 'usaha', 'entrepreneur', 'manager', 'marketing', 'akuntan', 'finance', 'keuangan', 'sales', 'trading', 'commerce', 'leadership', 'startup', 'corporate', 'organization', 'administration', 'strategic planning', 'operations', 'budget', 'investment', 'capital', 'supply chain', 'logistics', 'human resources'], + 'Mesin & Listrik' => ['mesin', 'bengkel', 'listrik', 'las', 'robot', 'motor', 'teknik', 'otomasi', 'elektronik', 'maintenance', 'industri', 'manufaktur', 'mechanical', 'electrical', 'automation', 'construction', 'repair', 'welding', 'hydraulic', 'pneumatic', 'power generation', 'circuit', 'transformer', 'machinery operation', 'fabrication', 'installation', 'troubleshooting'], ]; // Score setiap kategori berdasarkan keyword coverage @@ -520,12 +521,12 @@ private function mapCitaCita(string $citaRaw): string // Map cita-cita ke category berdasarkan keywords $careerCategories = [ - 'IT & Software' => ['programmer', 'developer', 'software', 'coding', 'hacker', 'web', 'database', 'it', 'engineer'], - 'Agriculture' => ['petani', 'pertanian', 'agribisnis', 'kebun', 'ternak', 'peternak', 'agronomi'], - 'Healthcare' => ['dokter', 'perawat', 'medis', 'gizi', 'terapis', 'farmasi', 'kesehatan'], - 'Business' => ['entrepreneur', 'manager', 'marketing', 'sales', 'akuntan', 'keuangan', 'bisnis'], - 'Engineering' => ['teknik', 'engineer', 'mesin', 'listrik', 'bengkel', 'maintenance', 'industri'], - 'Communication' => ['jurnalis', 'komunikator', 'presenter', 'content', 'pariwisata', 'hospitality'], + 'IT & Software' => ['programmer', 'developer', 'software', 'coding', 'web', 'database', 'it', 'scientist', 'analyst', 'data', 'cloud', 'architect', 'cybersecurity', 'security', 'devops', 'backend', 'frontend', 'fullstack', 'sysadmin', 'network admin', 'cto', 'tech lead', 'ai', 'machine learning'], + 'Agriculture' => ['petani', 'pertanian', 'agribisnis', 'kebun', 'ternak', 'peternak', 'agronomi', 'farming', 'livestock', 'agronomist', 'farmer', 'farm manager', 'plantation', 'crops specialist', 'agritech', 'horticultural', 'agricultural scientist', 'soil scientist', 'breeding specialist', 'extension officer', 'crop consultant', 'forestry', 'fishery manager'], + 'Healthcare' => ['dokter', 'perawat', 'medis', 'gizi', 'terapis', 'farmasi', 'kesehatan', 'nursing', 'therapist', 'pharmacist', 'nutritionist', 'clinician', 'public health', 'midwife', 'radiologist', 'dentist', 'nurse', 'surgeon', 'diagnostician', 'laboratory technician', 'paramedic', 'health educator', 'epidemiologist', 'wellness coach'], + 'Business' => ['entrepreneur', 'manager', 'marketing', 'sales', 'akuntan', 'keuangan', 'bisnis', 'accountant', 'consultant', 'finance', 'cfo', 'ceo', 'director', 'treasurer', 'auditor', 'trader', 'investor', 'controller', 'operations manager', 'strategic planner', 'business analyst', 'supply chain manager', 'hr manager', 'corporate executive'], + 'Engineering' => ['teknik', 'engineer', 'mesin', 'listrik', 'bengkel', 'maintenance', 'industri', 'technician', 'constructor', 'mechanical engineer', 'electrical engineer', 'automation', 'supervisor', 'foreman', 'technologist', 'specialist', 'civil engineer', 'welding specialist', 'hydraulics engineer', 'power engineer', 'manufacturing engineer', 'maintenance supervisor'], + 'Communication' => ['jurnalis', 'komunikator', 'presenter', 'content', 'pariwisata', 'hospitality', 'tour', 'guide', 'public relations', 'ambassador', 'interpreter', 'diplomat', 'broadcaster', 'event organizer', 'marketing specialist', 'pr specialist', 'copywriter', 'social media manager', 'travel consultant', 'hospitality manager', 'cultural ambassador', 'media producer'], ]; // Score setiap kategori diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php index 2b92f65..8955130 100644 --- a/app/Http/Requests/Auth/LoginRequest.php +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests\Auth; +use App\Models\User; use Illuminate\Auth\Events\Lockout; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Auth; @@ -54,17 +55,39 @@ public function authenticate(): void /** * Ensure the login request is not rate limited. + * Special handling: Students get 3 attempts, others get 5 attempts. * * @throws \Illuminate\Validation\ValidationException */ public function ensureIsNotRateLimited(): void { - if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { + $email = $this->string('email'); + $user = User::where('email', $email)->first(); + + // Tentukan limit berdasarkan role (khusus siswa: 3x, lainnya: 5x) + $maxAttempts = 5; // Default untuk BK, Admin, dan user lainnya + $isStudent = false; + + if ($user && $user->role === 'siswa') { + $maxAttempts = 3; // Siswa hanya boleh 3x + $isStudent = true; + } + + if (! RateLimiter::tooManyAttempts($this->throttleKey(), $maxAttempts)) { return; } event(new Lockout($this)); + // Special message untuk siswa yang sudah 3x gagal + if ($isStudent) { + throw ValidationException::withMessages([ + 'email' => 'āŒ Anda sudah salah password 3 kali. Silakan reset password melalui "Lupa Password" untuk keamanan akun Anda.', + 'forgot_password' => true, // Flag khusus untuk redirect + 'email_value' => $email, // Kirim email untuk auto-fill + ]); + } + $seconds = RateLimiter::availableIn($this->throttleKey()); throw ValidationException::withMessages([ diff --git a/app/Models/Alumni.php b/app/Models/Alumni.php index e591ae7..76365d0 100644 --- a/app/Models/Alumni.php +++ b/app/Models/Alumni.php @@ -15,14 +15,7 @@ class Alumni extends Model 'nama_alumni', 'nis', 'kelompok_asal', - 'mtk', - 'fisika', - 'kimia', - 'biologi', - 'ekonomi', - 'geografi', - 'sosiologi', - 'sejarah', + 'nilai_rata_rata', 'minat', 'cita_cita', 'preferensi_studi', @@ -33,42 +26,6 @@ class Alumni extends Model ]; protected $casts = [ - 'mtk' => 'float', - 'fisika' => 'float', - 'kimia' => 'float', - 'biologi' => 'float', - 'ekonomi' => 'float', - 'geografi' => 'float', - 'sosiologi' => 'float', - 'sejarah' => 'float', 'nilai_rata_rata' => 'float', ]; - - /** - * Hitung nilai rata-rata otomatis - */ - public static function booted() - { - static::saving(function ($alumni) { - // Gather nilai based on kelompok_asal - $nilaiFields = ['mtk']; - - if ($alumni->kelompok_asal == 'IPA') { - $nilaiFields = ['mtk', 'fisika', 'kimia', 'biologi']; - } else { - $nilaiFields = ['mtk', 'ekonomi', 'geografi', 'sosiologi', 'sejarah']; - } - - $nilaiValues = []; - foreach ($nilaiFields as $field) { - if (!is_null($alumni->$field)) { - $nilaiValues[] = $alumni->$field; - } - } - - $alumni->nilai_rata_rata = count($nilaiValues) > 0 - ? round(array_sum($nilaiValues) / count($nilaiValues), 2) - : null; - }); - } } diff --git a/app/Models/User.php b/app/Models/User.php index 11c19af..f772693 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; +use App\Notifications\ResetPasswordNotification; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -61,4 +62,12 @@ public function chatHistories() { return $this->hasMany(ChatHistory::class); } + + /** + * Send the password reset notification. + */ + public function sendPasswordResetNotification($token) + { + $this->notify(new ResetPasswordNotification($token)); + } } diff --git a/app/Notifications/ResetPasswordNotification.php b/app/Notifications/ResetPasswordNotification.php new file mode 100644 index 0000000..7827918 --- /dev/null +++ b/app/Notifications/ResetPasswordNotification.php @@ -0,0 +1,69 @@ +token = $token; + } + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + $resetUrl = url(route('password.reset', [ + 'token' => $this->token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false)); + + return (new MailMessage) + ->subject('šŸ”‘ Reset Password - Sistem Pemilihan Jurusan Polije') + ->view('emails.reset-password', [ + 'user' => $notifiable, + 'resetUrl' => $resetUrl, + 'expiresIn' => config('auth.passwords.users.expire', 60), + ]); + } + + /** + * Get the array representation of the notification. + * + * @return array + */ + public function toArray(object $notifiable): array + { + return [ + // + ]; + } +} diff --git a/composer.json b/composer.json index b08d29f..7896252 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.0", "laravel/sanctum": "^3.2", - "laravel/tinker": "^2.8" + "laravel/tinker": "^2.8", + "phpoffice/phpspreadsheet": "^5.7" }, "require-dev": { "doctrine/dbal": "^3.10", diff --git a/composer.lock b/composer.lock index b48dc29..4f7e6e4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bd2fbf3fa53fb313c47897eb28e7a171", + "content-hash": "c97868c64e1e33e893e3495daa4ea07a", "packages": [ { "name": "barryvdh/laravel-dompdf", @@ -212,6 +212,85 @@ ], "time": "2023-12-11T17:09:12+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -2120,6 +2199,191 @@ ], "time": "2024-09-21T08:32:55+00:00" }, + { + "name": "maennchen/zipstream-php", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "77bebeb4c6c340bb3c11c843b2cffd8bbfde4d5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/77bebeb4c6c340bb3c11c843b2cffd8bbfde4d5e", + "reference": "77bebeb4c6c340bb3c11c843b2cffd8bbfde4d5e", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.3" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.86", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^12.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan MƤnnchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "AndrĆ”s KolesĆ”r", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.2" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2026-04-11T18:38:28+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, { "name": "masterminds/html5", "version": "2.10.0", @@ -2694,6 +2958,115 @@ ], "time": "2024-11-21T10:36:35+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "5.7.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "9f55d3b9b7bcb1084fda8340e4b7ce4ed10cd0c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/9f55d3b9b7bcb1084fda8340e4b7ce4ed10cd0c8", + "reference": "9f55d3b9b7bcb1084fda8340e4b7ce4ed10cd0c8", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-filter": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^8.1", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^2.0 || ^3.0", + "ext-intl": "*", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.5", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1 || ^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard and StringHelper::setLocale()", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + }, + { + "name": "Owen Leibman" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.7.0" + }, + "time": "2026-04-20T02:42:17+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.5", diff --git a/config/polije.php b/config/polije.php index 747dd22..054ab98 100644 --- a/config/polije.php +++ b/config/polije.php @@ -13,81 +13,81 @@ 'nilai' => 'Sedang', 'minat' => 'Alam & Tanaman', 'pref' => ['Pertanian & Lingkungan'], - 'cita_cita_keywords' => ['pertanian', 'petani', 'kebun', 'sawah', 'panen', 'tanaman', 'agronomi', 'perkebunan', 'hortikultura'], + 'cita_cita_keywords' => ['pertanian', 'petani', 'kebun', 'sawah', 'panen', 'tanaman', 'agronomi', 'perkebunan', 'hortikultura', 'farmer', 'agronomist', 'cultivation'], 'skills_required' => ['Observasi', 'Kerja Lapangan', 'Pemeliharaan Tanaman'], - 'weights' => ['nilai' => 0.40, 'minat' => 0.35, 'pref' => 0.15, 'prestasi' => 0.05, 'cita_cita' => 0.05], + 'weights' => ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040], 'match_prob' => ['nilai' => 0.80, 'minat' => 0.90, 'pref' => 0.85, 'prestasi' => 0.65, 'cita_cita' => 0.85], ], 'Teknologi Pertanian' => [ 'nilai' => 'Tinggi', 'minat' => 'Alam & Tanaman', 'pref' => ['Sains & Teknologi', 'Pertanian & Lingkungan'], - 'cita_cita_keywords' => ['teknologi', 'inovasi', 'otomasi', 'mesin pertanian', 'smart farming', 'teknologi pangan'], + 'cita_cita_keywords' => ['teknologi', 'inovasi', 'otomasi', 'mesin pertanian', 'smart farming', 'teknologi pangan', 'agritech', 'agricultural engineer'], 'skills_required' => ['Problem Solving', 'Teknologi', 'Inovasi'], - 'weights' => ['nilai' => 0.50, 'minat' => 0.25, 'pref' => 0.15, 'prestasi' => 0.05, 'cita_cita' => 0.05], + 'weights' => ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040], 'match_prob' => ['nilai' => 0.85, 'minat' => 0.90, 'pref' => 0.85, 'prestasi' => 0.75, 'cita_cita' => 0.80], ], 'Peternakan' => [ 'nilai' => 'Sedang', 'minat' => 'Alam & Tanaman', 'pref' => ['Pertanian & Lingkungan', 'Kesehatan & Ilmu Hayat'], - 'cita_cita_keywords' => ['ternak', 'hewan', 'peternakan', 'peternak', 'sapi', 'ayam', 'unggas', 'veteriner', 'farm'], + 'cita_cita_keywords' => ['ternak', 'hewan', 'peternakan', 'peternak', 'sapi', 'ayam', 'unggas', 'veteriner', 'farm', 'livestock', 'husbandry', 'zootechnist'], 'skills_required' => ['Perawatan Hewan', 'Kesabaran', 'Manajemen'], - 'weights' => ['nilai' => 0.40, 'minat' => 0.40, 'pref' => 0.10, 'prestasi' => 0.05, 'cita_cita' => 0.05], + 'weights' => ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040], 'match_prob' => ['nilai' => 0.80, 'minat' => 0.88, 'pref' => 0.80, 'prestasi' => 0.65, 'cita_cita' => 0.88], ], 'Manajemen Agribisnis' => [ 'nilai' => 'Sedang', 'minat' => 'Manajemen & Bisnis', 'pref' => ['Bisnis & Manajemen', 'Pertanian & Lingkungan'], - 'cita_cita_keywords' => ['bisnis', 'agribisnis', 'usaha', 'entrepreneur', 'pengusaha', 'manajer', 'marketing', 'wirausaha'], + 'cita_cita_keywords' => ['bisnis', 'agribisnis', 'usaha', 'entrepreneur', 'pengusaha', 'manajer', 'marketing', 'wirausaha', 'director', 'executive', 'manager'], 'skills_required' => ['Manajemen', 'Bisnis Acumen', 'Komunikasi'], - 'weights' => ['nilai' => 0.35, 'minat' => 0.40, 'pref' => 0.15, 'prestasi' => 0.05, 'cita_cita' => 0.05], + 'weights' => ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040], 'match_prob' => ['nilai' => 0.75, 'minat' => 0.90, 'pref' => 0.80, 'prestasi' => 0.70, 'cita_cita' => 0.85], ], 'Teknologi Informasi' => [ 'nilai' => 'Tinggi', 'minat' => 'Logika & Komputer', 'pref' => ['Sains & Teknologi'], - 'cita_cita_keywords' => ['programmer', 'developer', 'coding', 'software', 'web developer', 'hacker', 'it', 'data analyst', 'ai engineer', 'mobile developer'], + 'cita_cita_keywords' => ['programmer', 'developer', 'coding', 'software', 'web developer', 'hacker', 'it', 'data analyst', 'ai engineer', 'mobile developer', 'devops', 'cloud engineer', 'scientist', 'architect'], 'skills_required' => ['Coding', 'Problem Solving', 'Logika'], - 'weights' => ['nilai' => 0.45, 'minat' => 0.35, 'pref' => 0.12, 'prestasi' => 0.05, 'cita_cita' => 0.03], + 'weights' => ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040], 'match_prob' => ['nilai' => 0.90, 'minat' => 0.92, 'pref' => 0.85, 'prestasi' => 0.75, 'cita_cita' => 0.85], ], 'Teknik' => [ 'nilai' => 'Sedang', 'minat' => 'Mesin & Listrik', 'pref' => ['Sains & Teknologi'], - 'cita_cita_keywords' => ['mesin', 'bengkel', 'teknisi', 'listrik', 'elektronik', 'otomasi', 'instalasi', 'panel', 'mekatronika', 'maintenance'], + 'cita_cita_keywords' => ['mesin', 'bengkel', 'teknisi', 'listrik', 'elektronik', 'otomasi', 'instalasi', 'panel', 'mekatronika', 'maintenance', 'engineer', 'technician', 'supervisor'], 'skills_required' => ['Mekanik', 'Elektrik', 'Teknik', 'Presisi'], - 'weights' => ['nilai' => 0.42, 'minat' => 0.38, 'pref' => 0.12, 'prestasi' => 0.05, 'cita_cita' => 0.03], + 'weights' => ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040], 'match_prob' => ['nilai' => 0.82, 'minat' => 0.90, 'pref' => 0.85, 'prestasi' => 0.71, 'cita_cita' => 0.85], ], 'Kesehatan' => [ 'nilai' => 'Tinggi', 'minat' => 'Pelayanan & Kesehatan', 'pref' => ['Kesehatan & Ilmu Hayat'], - 'cita_cita_keywords' => ['dokter', 'perawat', 'medis', 'gizi', 'kesehatan', 'pelayanan', 'terapis', 'farmasi', 'rekam medis', 'kesehatan masyarakat'], + 'cita_cita_keywords' => ['dokter', 'perawat', 'medis', 'gizi', 'kesehatan', 'pelayanan', 'terapis', 'farmasi', 'rekam medis', 'kesehatan masyarakat', 'nurse', 'pharmacist', 'nutritionist', 'clinician'], 'skills_required' => ['Komunikasi', 'Empati', 'Presisi Medis'], - 'weights' => ['nilai' => 0.45, 'minat' => 0.35, 'pref' => 0.10, 'prestasi' => 0.05, 'cita_cita' => 0.05], + 'weights' => ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040], 'match_prob' => ['nilai' => 0.90, 'minat' => 0.90, 'pref' => 0.80, 'prestasi' => 0.75, 'cita_cita' => 0.90], ], 'Bahasa, Komunikasi, dan Pariwisata' => [ 'nilai' => 'Sedang', 'minat' => 'Umum', 'pref' => ['Sosial & Humaniora', 'Bisnis & Manajemen'], - 'cita_cita_keywords' => ['tour guide', 'pariwisata', 'bahasa', 'komunikasi', 'jurnalis', 'marketing', 'public relation', 'content creator', 'hospitality'], + 'cita_cita_keywords' => ['tour guide', 'pariwisata', 'bahasa', 'komunikasi', 'jurnalis', 'marketing', 'public relation', 'content creator', 'hospitality', 'ambassador', 'presenter', 'broadcaster'], 'skills_required' => ['Komunikasi', 'Bahasa', 'Kepribadian'], - 'weights' => ['nilai' => 0.30, 'minat' => 0.40, 'pref' => 0.15, 'prestasi' => 0.08, 'cita_cita' => 0.07], + 'weights' => ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040], 'match_prob' => ['nilai' => 0.70, 'minat' => 0.85, 'pref' => 0.80, 'prestasi' => 0.70, 'cita_cita' => 0.85], ], 'Bisnis' => [ 'nilai' => 'Sedang', 'minat' => 'Manajemen & Bisnis', 'pref' => ['Bisnis & Manajemen'], - 'cita_cita_keywords' => ['manager', 'pimpinan', 'bisnis', 'accounting', 'marketing', 'sales', 'akuntan', 'keuangan', 'bank'], + 'cita_cita_keywords' => ['manager', 'pimpinan', 'bisnis', 'accounting', 'marketing', 'sales', 'akuntan', 'keuangan', 'bank', 'finance', 'accountant', 'auditor'], 'skills_required' => ['Manajemen', 'Leadership', 'Keuangan'], - 'weights' => ['nilai' => 0.35, 'minat' => 0.40, 'pref' => 0.15, 'prestasi' => 0.05, 'cita_cita' => 0.05], + 'weights' => ['nilai' => 0.156, 'minat' => 0.456, 'pref' => 0.256, 'cita_cita' => 0.090, 'prestasi' => 0.040], 'match_prob' => ['nilai' => 0.75, 'minat' => 0.90, 'pref' => 0.80, 'prestasi' => 0.70, 'cita_cita' => 0.85], ], ], diff --git a/database/migrations/2026_04_29_simplify_alumni_table.php b/database/migrations/2026_04_29_simplify_alumni_table.php index 3c4cc35..c7b619c 100644 --- a/database/migrations/2026_04_29_simplify_alumni_table.php +++ b/database/migrations/2026_04_29_simplify_alumni_table.php @@ -15,35 +15,34 @@ */ public function up(): void { - $driver = DB::connection()->getDriverName(); - - // Skip for SQLite since it has limited ALTER TABLE support - if ($driver === 'sqlite') { - return; - } - - // For MySQL/PostgreSQL: drop unnecessary columns - $columnsToDrop = []; - foreach (['success_status', 'ranking_saat_rekomendasi', 'predicted_score', 'ipk_lulus', 'karir_outcome'] as $col) { - if (Schema::hasColumn('alumni', $col)) { - $columnsToDrop[] = $col; + Schema::table('alumni', function (Blueprint $table) { + // Drop unnecessary columns + if (Schema::hasColumn('alumni', 'success_status')) { + $table->dropColumn('success_status'); } - } + if (Schema::hasColumn('alumni', 'ranking_saat_rekomendasi')) { + $table->dropColumn('ranking_saat_rekomendasi'); + } + if (Schema::hasColumn('alumni', 'predicted_score')) { + $table->dropColumn('predicted_score'); + } + if (Schema::hasColumn('alumni', 'ipk_lulus')) { + $table->dropColumn('ipk_lulus'); + } + if (Schema::hasColumn('alumni', 'karir_outcome')) { + $table->dropColumn('karir_outcome'); + } + }); - if (!empty($columnsToDrop)) { - Schema::table('alumni', function (Blueprint $table) use ($columnsToDrop) { - $table->dropColumn($columnsToDrop); - }); - } - - // Drop and recreate preferensi_studi column - if (Schema::hasColumn('alumni', 'preferensi_studi')) { - Schema::table('alumni', function (Blueprint $table) { + // Update preferensi_studi dengan raw SQL untuk menghindari Doctrine enum issue + Schema::table('alumni', function (Blueprint $table) { + if (Schema::hasColumn('alumni', 'preferensi_studi')) { $table->dropColumn('preferensi_studi'); - }); - } + } + }); Schema::table('alumni', function (Blueprint $table) { + // Add kembali preferensi_studi dengan enum values yang tepat $table->enum('preferensi_studi', [ 'Sains & Teknologi', 'Pertanian & Lingkungan', @@ -56,7 +55,9 @@ public function up(): void public function down(): void { - // Rollback logic (if needed) + Schema::table('alumni', function (Blueprint $table) { + // + }); } }; diff --git a/database/migrations/2026_05_17_drop_subject_values_from_alumni_table.php b/database/migrations/2026_05_17_drop_subject_values_from_alumni_table.php new file mode 100644 index 0000000..af4e5c4 --- /dev/null +++ b/database/migrations/2026_05_17_drop_subject_values_from_alumni_table.php @@ -0,0 +1,39 @@ +dropColumn([ + 'mtk', + 'fisika', + 'kimia', + 'biologi', + 'ekonomi', + 'geografi', + 'sosiologi', + 'sejarah' + ]); + }); + } + + public function down(): void + { + Schema::table('alumni', function (Blueprint $table) { + $table->float('mtk')->nullable(); + $table->float('fisika')->nullable(); + $table->float('kimia')->nullable(); + $table->float('biologi')->nullable(); + $table->float('ekonomi')->nullable(); + $table->float('geografi')->nullable(); + $table->float('sosiologi')->nullable(); + $table->float('sejarah')->nullable(); + }); + } +}; diff --git a/database/seeders/BkSeeder.php b/database/seeders/BkSeeder.php new file mode 100644 index 0000000..b8bb1cf --- /dev/null +++ b/database/seeders/BkSeeder.php @@ -0,0 +1,39 @@ + 'gurubk1@polije.ac.id', 'name' => 'Konselor BK 1', 'password' => 'bk123456'], + ['email' => 'gurubk2@polije.ac.id', 'name' => 'Konselor BK 2', 'password' => 'bk234567'], + ['email' => 'gurubk3@polije.ac.id', 'name' => 'Konselor BK 3', 'password' => 'bk345678'], + ]; + + foreach ($bkUsers as $u) { + User::firstOrCreate( + ['email' => $u['email']], + [ + 'name' => $u['name'], + 'password' => Hash::make($u['password']), + 'role' => 'bk', + 'email_verified_at' => now(), + ] + ); + + echo "Created or exists: {$u['email']} / {$u['password']}\n"; + } + + echo "āœ… 3 BK accounts seeded.\n"; + } +} diff --git a/database/seeders/RecommendationAndChatSeeder.php b/database/seeders/RecommendationAndChatSeeder.php new file mode 100644 index 0000000..82e021c --- /dev/null +++ b/database/seeders/RecommendationAndChatSeeder.php @@ -0,0 +1,331 @@ + 2, // Siswa 1 + 'mtk' => 85, 'fisika' => 88, 'kimia' => 82, 'biologi' => 75, + 'minat' => 'Suka coding dan bikin aplikasi, interested di cyber security juga', + 'preferensi_studi' => 'Sains & Teknologi', + 'cita_cita' => 'Jadi software engineer di startup lokal, pengen develop aplikasi yang bermanfaat', + 'prestasi' => 'Juara 2 Kompetisi Programming tingkat provinsi tahun lalu', + ], + [ + 'user_id' => 3, // Siswa 2 + 'mtk' => 92, 'fisika' => 90, 'kimia' => 88, 'biologi' => 85, + 'minat' => 'Sangat tertarik dengan robotika dan automation, suka ngutak-atik elektronik', + 'preferensi_studi' => 'Sains & Teknologi', + 'cita_cita' => 'Ingin bekerja di industri manufaktur sebagai engineer untuk bikin sistem otomasi', + 'prestasi' => 'Finalis Olimpiade Fisika Nasional, sudah ada 3 paten design robotik', + ], + [ + 'user_id' => 4, // Siswa 3 + 'mtk' => 78, 'fisika' => 81, 'kimia' => 79, 'biologi' => 92, + 'minat' => 'Passionate tentang bioteknologi dan kesehatan, sering baca tentang genomik', + 'preferensi_studi' => 'Kesehatan & Ilmu Hayat', + 'cita_cita' => 'Mau lanjut ke kedokteran atau jadi peneliti di bidang biomedis', + 'prestasi' => 'Presentasi paper biologi di seminar nasional, pernah volunteer di klinik', + ], + [ + 'user_id' => 5, // Siswa 4 + 'mtk' => 88, 'fisika' => 85, 'kimia' => 90, 'biologi' => 80, + 'minat' => 'Suka kimia organik, proses manufaktur, dan industri pertanian', + 'preferensi_studi' => 'Pertanian & Lingkungan', + 'cita_cita' => 'Bikin startup agritech yang sustainable, pengen improve produktivitas petani lokal', + 'prestasi' => 'Juara Karya Ilmiah tentang pupuk organik, punya UKM pupuk sendiri', + ], + [ + 'user_id' => 6, // Siswa 5 + 'mtk' => 82, 'fisika' => 84, 'kimia' => 86, 'biologi' => 88, + 'minat' => 'Selalu tertarik dengan peternakan modern dan nutrisi hewan', + 'preferensi_studi' => 'Pertanian & Lingkungan', + 'cita_cita' => 'Jadi manager farm modern atau ahli nutrisi ternak, pengen export hasil', + 'prestasi' => 'Punya kandang ternak sapi yang sudah operational, mengikuti workshop peternakan', + ], + + // Siswa IPS - Bisnis & Sosial oriented + [ + 'user_id' => 7, // Siswa 6 + 'ekonomi' => 85, 'geografi' => 88, 'sosiologi' => 80, 'sejarah' => 78, + 'minat' => 'Suka analisa bisnis dan strategi marketing, aktif di club entrepreneur', + 'preferensi_studi' => 'Bisnis & Manajemen', + 'cita_cita' => 'Mau founder perusahaan logistik yang bisa layani seluruh Indonesia', + 'prestasi' => 'Ketua OSIS 2 tahun, udah coba business plan shipping dengan teman', + ], + [ + 'user_id' => 8, // Siswa 7 + 'ekonomi' => 90, 'geografi' => 85, 'sosiologi' => 88, 'sejarah' => 82, + 'minat' => 'Tertarik accounting dan finance, suka ngitung dan detail', + 'preferensi_studi' => 'Bisnis & Manajemen', + 'cita_cita' => 'Jadi akuntan profesional dan buka kantor akuntan sendiri', + 'prestasi' => 'Juara Lomba Akuntansi, sudah belajar MYOB dan software akuntansi', + ], + [ + 'user_id' => 9, // Siswa 8 + 'ekonomi' => 78, 'geografi' => 82, 'sosiologi' => 92, 'sejarah' => 88, + 'minat' => 'Sangat peduli sosial dan keadilan, aktif di organisasi kemanusiaan', + 'preferensi_studi' => 'Sosial & Humaniora', + 'cita_cita' => 'Jadi social worker atau policy maker yang bisa bantu masyarakat kecil', + 'prestasi' => 'Koordinator program CSR, pernah design program beasiswa untuk anak kurang mampu', + ], + [ + 'user_id' => 10, // Siswa 9 + 'ekonomi' => 88, 'geografi' => 90, 'sosiologi' => 85, 'sejarah' => 86, + 'minat' => 'Hobi pariwisata dan guide, suka cerita tentang budaya daerah', + 'preferensi_studi' => 'Sosial & Humaniora', + 'cita_cita' => 'Develop destinasi pariwisata lokal yang sustainable, buat kuliner branded', + 'prestasi' => 'Sudah jadi tour guide 6 bulan, punya grup social media wisata lokal', + ], + [ + 'user_id' => 11, // Siswa 10 + 'ekonomi' => 82, 'geografi' => 88, 'sosiologi' => 80, 'sejarah' => 85, + 'minat' => 'Senang dengan komunikasi dan public speaking, aktif di debat club', + 'preferensi_studi' => 'Sosial & Humaniora', + 'cita_cita' => 'Jadi komunikasi profesional atau journalist yang investigatif', + 'prestasi' => 'Finalis Debat Nasional, pernah bikin dokumenter lokal, punya podcast', + ], + + // Tambahan data diverse + [ + 'user_id' => 2, + 'mtk' => 79, 'fisika' => 75, 'kimia' => 80, 'biologi' => 88, + 'minat' => 'Biologi laut dan konservasi, sering ke pantai untuk research', + 'preferensi_studi' => 'Pertanian & Lingkungan', + 'cita_cita' => 'Jadi peneliti laut atau oceanographer, lindungi ekosistem terumbu karang', + 'prestasi' => 'Peserta program konservasi laut, pernah publikasi artikel tentang coral bleaching', + ], + [ + 'user_id' => 3, + 'mtk' => 86, 'fisika' => 92, 'kimia' => 85, 'biologi' => 78, + 'minat' => 'Tertarik mesin presisi dan manufacturing, suka dismantle dan assemble', + 'preferensi_studi' => 'Sains & Teknologi', + 'cita_cita' => 'Jadi chief engineer di pabrik industri atau buka workshop manufaktur', + 'prestasi' => 'Juara kompetisi mesin, punya skill CAD dan CNC machining', + ], + [ + 'user_id' => 4, + 'mtk' => 81, 'fisika' => 80, 'kimia' => 88, 'biologi' => 90, + 'minat' => 'Farmasi dan obat-obatan, sering ikut diskusi medical science', + 'preferensi_studi' => 'Kesehatan & Ilmu Hayat', + 'cita_cita' => 'Jadi apoteker atau researcher farmasi, develop obat lokal berkualitas', + 'prestasi' => 'Internship di apotek besar, belajar formulation dan quality control', + ], + [ + 'user_id' => 5, + 'mtk' => 84, 'fisika' => 82, 'kimia' => 92, 'biologi' => 85, + 'minat' => 'Proses industri dan chemical engineering, suka tonton cara produksi', + 'preferensi_studi' => 'Sains & Teknologi', + 'cita_cita' => 'Jadi chemical engineer, optimalkan proses produksi di industri tekstil', + 'prestasi' => 'Design proposal chemical processing untuk usaha keluarga', + ], + [ + 'user_id' => 6, + 'mtk' => 77, 'fisika' => 79, 'kimia' => 81, 'biologi' => 90, + 'minat' => 'Veteriner dan kesehatan hewan, punya pengalaman tangani hewan sakit', + 'preferensi_studi' => 'Kesehatan & Ilmu Hayat', + 'cita_cita' => 'Jadi dokter hewan dan buka klinik hewan modern di daerah', + 'prestasi' => 'Volunteer di animal shelter, treatment beberapa hewan terlantar', + ], + [ + 'user_id' => 7, + 'ekonomi' => 82, 'geografi' => 85, 'sosiologi' => 78, 'sejarah' => 80, + 'minat' => 'Digital marketing dan e-commerce, aktif buat konten di social media', + 'preferensi_studi' => 'Bisnis & Manajemen', + 'cita_cita' => 'Jadi digital marketing specialist atau content creator profesional', + 'prestasi' => 'Manage social media bisnis teman, follower tumbuh jadi 50 ribu', + ], + [ + 'user_id' => 8, + 'ekonomi' => 88, 'geografi' => 82, 'sosiologi' => 85, 'sejarah' => 84, + 'minat' => 'Banking dan investment, suka belajar tentang saham dan cryptocurrency', + 'preferensi_studi' => 'Bisnis & Manajemen', + 'cita_cita' => 'Jadi financial advisor atau investment manager di bank besar', + 'prestasi' => 'Peserta kompetisi investment game, udah trading saham sendiri', + ], + [ + 'user_id' => 9, + 'ekonomi' => 75, 'geografi' => 88, 'sosiologi' => 90, 'sejarah' => 85, + 'minat' => 'Pengembangan masyarakat dan pendidikan, pernah ajar anak-anak kurang mampu', + 'preferensi_studi' => 'Sosial & Humaniora', + 'cita_cita' => 'Jadi pendidik atau foundation director yang bisa rubah hidup banyak anak', + 'prestasi' => 'Founder program belajar gratis untuk anak pinggiran, sudah 50 peserta', + ], + [ + 'user_id' => 10, + 'ekonomi' => 85, 'geografi' => 92, 'sosiologi' => 88, 'sejarah' => 86, + 'minat' => 'Travel dan budaya lokal, suka explore setiap daerah dan documenting', + 'preferensi_studi' => 'Sosial & Humaniora', + 'cita_cita' => 'Jadi travel blogger profesional atau cultural ambassador', + 'prestasi' => 'Blog wisata udah 100k visitors, punya instagram tentang kuliner lokal', + ], + [ + 'user_id' => 11, + 'ekonomi' => 80, 'geografi' => 85, 'sosiologi' => 88, 'sejarah' => 90, + 'minat' => 'Sejarah dan warisan budaya, aktif di club heritage preservation', + 'preferensi_studi' => 'Sosial & Humaniora', + 'cita_cita' => 'Jadi historian atau kurator museum, lestarikan warisan budaya Indonesia', + 'prestasi' => 'Dokumentasi situs sejarah lokal, pernah present di forum budaya', + ], + [ + 'user_id' => 2, + 'mtk' => 88, 'fisika' => 86, 'kimia' => 84, 'biologi' => 82, + 'minat' => 'AI dan machine learning, ikut online course dan kaggle competition', + 'preferensi_studi' => 'Sains & Teknologi', + 'cita_cita' => 'Jadi AI specialist atau data scientist, develop solution untuk Indonesia', + 'prestasi' => 'Rank 500 di kaggle, bikin model prediksi untuk project sekolah', + ], + [ + 'user_id' => 3, + 'mtk' => 89, 'fisika' => 88, 'kimia' => 87, 'biologi' => 75, + 'minat' => 'Game development dan creative coding, punya beberapa game project', + 'preferensi_studi' => 'Sains & Teknologi', + 'cita_cita' => 'Jadi game developer di studio internasional atau bikin studio sendiri', + 'prestasi' => 'Game yang dibuat udah dimainkan ribuan orang, dapat award di expo', + ], + [ + 'user_id' => 4, + 'mtk' => 80, 'fisika' => 79, 'kimia' => 90, 'biologi' => 92, + 'minat' => 'Gizi dan kesehatan masyarakat, aktif di program kesehatan komunitas', + 'preferensi_studi' => 'Kesehatan & Ilmu Hayat', + 'cita_cita' => 'Jadi ahli gizi profesional, program nutrisi untuk anak Indonesia', + 'prestasi' => 'Design program gizi untuk sekolah di daerah tertinggal, pernah grant', + ], + [ + 'user_id' => 5, + 'mtk' => 83, 'fisika' => 84, 'kimia' => 89, 'biologi' => 86, + 'minat' => 'Sustainability dan green technology, interested di renewable energy', + 'preferensi_studi' => 'Pertanian & Lingkungan', + 'cita_cita' => 'Jadi engineer yang fokus sustainable development dan clean energy', + 'prestasi' => 'Project solar panel untuk sekolah, design water management system', + ], + [ + 'user_id' => 6, + 'mtk' => 81, 'fisika' => 80, 'kimia' => 82, 'biologi' => 89, + 'minat' => 'Agronomi modern dan precision farming, ikuti webinar tentang smart farm', + 'preferensi_studi' => 'Pertanian & Lingkungan', + 'cita_cita' => 'Jadi smart farmer yang pakai teknologi untuk maksimalkan hasil panen', + 'prestasi' => 'Pilot project smart farming dengan sensor IoT, hasil bagus', + ], + [ + 'user_id' => 7, + 'ekonomi' => 84, 'geografi' => 86, 'sosiologi' => 82, 'sejarah' => 76, + 'minat' => 'Supply chain dan logistik, suka pelajari efficient delivery systems', + 'preferensi_studi' => 'Bisnis & Manajemen', + 'cita_cita' => 'Jadi supply chain director atau logistics innovator untuk e-commerce', + 'prestasi' => 'Analyze supply chain usaha keluarga, improve efficiency 40 persen', + ], + [ + 'user_id' => 8, + 'ekonomi' => 87, 'geografi' => 84, 'sosiologi' => 86, 'sejarah' => 83, + 'minat' => 'Corporate finance dan management accounting, belajar SAP dan MYOB', + 'preferensi_studi' => 'Bisnis & Manajemen', + 'cita_cita' => 'Jadi CFO atau finance director di perusahaan multinational', + 'prestasi' => 'Intern di finance department perusahaan, handle reconciliation', + ], + [ + 'user_id' => 9, + 'ekonomi' => 76, 'geografi' => 87, 'sosiologi' => 92, 'sejarah' => 88, + 'minat' => 'Community development dan empowerment, pernah training leadership', + 'preferensi_studi' => 'Sosial & Humaniora', + 'cita_cita' => 'Jadi community development officer di NGO atau social enterprise', + 'prestasi' => 'Facilitate community meeting dan program design untuk 500 orang', + ], + [ + 'user_id' => 10, + 'ekonomi' => 86, 'geografi' => 89, 'sosiologi' => 87, 'sejarah' => 85, + 'minat' => 'Hospitality dan business management, experience internship di hotel', + 'preferensi_studi' => 'Bisnis & Manajemen', + 'cita_cita' => 'Jadi hotel manager atau develop hospitality brand lokal kelas dunia', + 'prestasi' => 'Manage event 200 orang, guest satisfaction score 95 persen', + ], + ]; + + // Insert recommendations + foreach ($recommendationData as $data) { + Recommendation::create(array_merge($data, [ + 'hasil_rekomendasi' => [ + [ + 'jurusan' => 'Teknik Informatika', + 'skor' => rand(70, 95) / 100, + 'detail' => [ + 'nilai' => rand(60, 95) / 100, + 'minat' => rand(70, 98) / 100, + 'pref' => rand(60, 95) / 100, + 'cita' => rand(75, 98) / 100, + 'prestasi' => rand(50, 90) / 100, + ] + ] + ] + ])); + } + + // Data chat history yang natural dan tidak template + $chatData = [ + // Chat tentang rekomendasi + ['user_id' => 2, 'id_sesi' => 'session_1', 'pertanyaan' => 'Jadi menurut sistem saya cocok ambil jurusan apa ya?', 'jawaban' => 'Berdasarkan hasil analisis, untuk profile Anda yang nilai MTK-Fisika bagus dan minat coding kuat, Teknik Informatika adalah pilihan yang selaras. Anda bisa fokus cybersecurity atau development.'], + ['user_id' => 2, 'id_sesi' => 'session_1', 'pertanyaan' => 'Cybersecurity lebih amankan karir atau development?', 'jawaban' => 'Dua-duanya prospek bagus. Cybersecurity ada demand dari government dan finance yang stabil. Development skills lebih flexible bisa startup atau big tech. Lihat passion Anda lebih ke yang mana.'], + ['user_id' => 3, 'id_sesi' => 'session_2', 'pertanyaan' => 'Saya pengen jurusan yang bisa langsung applicable untuk robotika', 'jawaban' => 'Untuk robotika, Teknik Elektro atau Teknik Mesin sangat applicable. Dengan score fisika 90 Anda, dua jurusan ini cocok. Teknik Elektro kalau fokus control system, Teknik Mesin kalau fokus mechanical design.'], + ['user_id' => 3, 'id_sesi' => 'session_2', 'pertanyaan' => 'Lebih pilih mana sih?', 'jawaban' => 'Lihat passion Anda lebih ke elektrik atau mekanik? Kalau suka coding dan control systems, Teknik Elektro. Kalau suka design fisik dan assemble-dismantle, Teknik Mesin. Keduanya relevant untuk robotika.'], + ['user_id' => 4, 'id_sesi' => 'session_3', 'pertanyaan' => 'Bisa lanjut kedokteran dari TPI nggak?', 'jawaban' => 'Untuk kedokteran, value akademik dan jalur masuk lebih strict dibanding TPI. Tapi calondidik bisa persiapan dari TPI, terutama biologi dan kimia. Fokus banget di olimpiade sains untuk booster nilai masuk.'], + ['user_id' => 4, 'id_sesi' => 'session_3', 'pertanyaan' => 'TPI itu apa ya singkatannya?', 'jawaban' => 'Teknik Pertanian dan Industri di Polije. Tapi kalau target kedokteran, mungkin bisa cek juga program kesehatan dulu untuk experience, atau aplikasi kedokteran pure dari outcome awal.'], + + // Chat tentang karir + ['user_id' => 5, 'id_sesi' => 'session_4', 'pertanyaan' => 'Agribusiness itu nanti kerjanya kayak apa?', 'jawaban' => 'Bisa jadi manager farm, business development untuk agritech, supply chain untuk produk pertanian, atau entrepreneur. Banyak pilihan tergantung spesialisasi yang dipilih di kuliah.'], + ['user_id' => 5, 'id_sesi' => 'session_4', 'pertanyaan' => 'Gaji fresh graduate agritech berapa kira-kira?', 'jawaban' => 'Startup agritech biasanya 4-6 juta tergantung posisi dan lokasi. Corporate agribusiness bisa 5-8 juta. Tapi kalau entrepreneurship sendiri, income tergantung success rate usaha Anda.'], + + // Chat tentang program belajar + ['user_id' => 6, 'id_sesi' => 'session_5', 'pertanyaan' => 'Polije punya program internship nggak?', 'jawaban' => 'Ada, biasanya di semester akhir. Untuk program Peternakan, ada partnership dengan farm modern dan feed company. Cek dengan prodi langsung untuk detail placement.'], + ['user_id' => 6, 'id_sesi' => 'session_5', 'pertanyaan' => 'Boleh apply internship di abroad nggak?', 'jawaban' => 'Tergantung kebijakan prodi, tapi generally bisa diskusi dengan pembimbing. Ada beberapa students yang udah internship di Malaysia atau Thailand. Usahakan komunikasi dengan prodi sejak early semester.'], + + // Chat tentang persiapan + ['user_id' => 7, 'id_sesi' => 'session_6', 'pertanyaan' => 'Perlu les atau belajar sendiri sebelum masuk kuliah?', 'jawaban' => 'Tergantung kesiapan Anda. Kalau values academic udah solid, bisa self-study dengan referensi online. Tapi kalau mau jadi standout di semester pertama, bisa ambil kursus online tentang fondasi subject.'], + ['user_id' => 7, 'id_sesi' => 'session_6', 'pertanyaan' => 'Ada rekomendasi website atau platform belajar nggak?', 'jawaban' => 'Udah banyak. Coursera, Udemy, Khan Academy bagus untuk foundational. Indonesia ada Ruangguru atau Zenius. Tapi yang paling penting engagement sama dosen dan teman-teman di kelas nanti.'], + + // Chat tentang keputusan + ['user_id' => 8, 'id_sesi' => 'session_7', 'pertanyaan' => 'Agak ragu antara Akuntansi sama Management', 'jawaban' => 'Dua jurusan ini beda fokus. Akuntansi lebih technical dan detail-oriented, Management lebih strategic dan leader-focused. Lihat strength Anda: detail dan angka atau big picture dan people? Itu bisa guide decision.'], + ['user_id' => 8, 'id_sesi' => 'session_7', 'pertanyaan' => 'Kalau pilih Management, bisa dapat nilai invest yang bagus nggak dari sekolah?', 'jawaban' => 'Bisa. Dengan nilai ekonomi 90 Anda, management akan mudah. Cuma akuntansi mungkin lebih certified langsung (JATS). Management lebih butuh experience dan soft skill yang dibangun di luar kelas.'], + + // Chat tentang scholarship + ['user_id' => 9, 'id_sesi' => 'session_8', 'pertanyaan' => 'Ada beasiswa untuk jurusan sosial nggak?', 'jawaban' => 'Ada beberapa dari government dan foundation. Polije ada beasiswa akademik based on nilai. NGO juga sering sponsor untuk program kemanusiaan atau education. Tapi bisa juga dari corporate yang peduli CSR.'], + ['user_id' => 9, 'id_sesi' => 'session_8', 'pertanyaan' => 'Requirement beasiswa biasanya apa?', 'jawaban' => 'Tergantung sumber. Academic scholarship butuh nilai bagus. Need-based butuh dokumentasi ekonomi. Full scholarship biasanya maintain GPA minimal 3.0 dan aktif di organisasi sosial.'], + + // Chat tentang prospek kerja + ['user_id' => 10, 'id_sesi' => 'session_9', 'pertanyaan' => 'Pariwisata di Indonesia prospeknya gimana pasca COVID?', 'jawaban' => 'Recovery udah bagus. Domestic tourism naik, international mulai balik. Tapi industry butuh profesional yang innovative, bukan hanya tour guide. Positioning diri Anda sebagai strategic player, bukan operational level.'], + ['user_id' => 10, 'id_sesi' => 'session_9', 'pertanyaan' => 'Jadi hospitality manager itu punya team berapa biasanya?', 'jawaban' => 'Tergantung size hotel. Small hotel (50 kamar) mungkin 5-10 staff. Mid-range (100-200 kamar) 20-50 staff. Besar (300+ kamar) bisa 100+. Semakin besar, semakin kompleks management-nya.'], + + // Chat about challenges + ['user_id' => 11, 'id_sesi' => 'session_10', 'pertanyaan' => 'Komunikasi itu subject berat nggak di kuliah?', 'jawaban' => 'Tidak terlalu berat seperti teknik atau science. Tapi demanding dalam hal project, presentasi, dan banyak reading. Kalau Anda suka talking dan creative, ini cocok. Tantangan lebih ke academic rigor, bukan mathematical complexity.'], + ['user_id' => 11, 'id_sesi' => 'session_10', 'pertanyaan' => 'Ada lab atau praktik langsung nggak?', 'jawaban' => 'Ada. Biasanya ada lab communicative, media production, bahkan ada broadcast studio. Praktik langsung dengan project nyata untuk client atau media partner. Itu yang membuat komunikasi menarik.'], + + // More natural chats + ['user_id' => 2, 'id_sesi' => 'session_11', 'pertanyaan' => 'Kenalin dong senior-senior di Teknik Informatika', 'jawaban' => 'Udah ada program mentoring di Polije. Senior bisa di-connect melalui alumni network atau student organization. Mereka helpful banget share experience dan tips lolos interview di tech company.'], + ['user_id' => 3, 'id_sesi' => 'session_12', 'pertanyaan' => 'Lab di Teknik Elektro lengkap nggak untuk robotika?', 'jawaban' => 'Lumayan lengkap. Ada microcontroller lab, digital logic lab. Untuk advanced robotics, bisa kolaborasi dengan Teknik Mesin juga. Ada makerspace bersama yang membantu student projects.'], + ['user_id' => 4, 'id_sesi' => 'session_13', 'pertanyaan' => 'Kesehatan di Polije fokus apa ya?', 'jawaban' => 'Ada program Ilmu Gizi, Teknologi Laboratorium Medis, dan Kesehatan Masyarakat. Fokus ke applied health, tidak pure medical science. Cocok untuk Anda yang peduli community health.'], + ['user_id' => 5, 'id_sesi' => 'session_14', 'pertanyaan' => 'Mahasiswa agribusiness banyak nggak?', 'jawaban' => 'Cukup banyak, sekitar 100 per tahun. Intake lumayan tinggi karena demand industri. Networking sama peers bisa bagus untuk future business partnership.'], + ['user_id' => 6, 'id_sesi' => 'session_15', 'pertanyaan' => 'Peternakan di Polije punya farm sendiri nggak?', 'jawaban' => 'Ada. Farm sendiri untuk praktik langsung sama students. Facilities bagus, ada kandang modern, equipment hewan medis. Bisa langsung hands-on dari semester pertama.'], + ['user_id' => 7, 'id_sesi' => 'session_16', 'pertanyaan' => 'Manajemen jurusan itu terlalu general nggak?', 'jawaban' => 'Dalam kuliah ada specialization. Semester awal general foundation, nanti semester 5-6 bisa pilih tracks seperti operations, finance, marketing, atau digital business. Jadi nggak general-general amat.'], + ['user_id' => 8, 'id_sesi' => 'session_17', 'pertanyaan' => 'Akuntansi itu paling banyak ngitung nggak si?', 'jawaban' => 'Lumayan. Tapi sekarang accounting lebih ke understanding system, analysis, dan business advisory. Ngitung masih ada tapi banyak software yang bantu. Focus lebih ke thinking skills.'], + ['user_id' => 9, 'id_sesi' => 'session_18', 'pertanyaan' => 'Social welfare program itu study apa aja sih?', 'jawaban' => 'Social policy, community development methods, case management, research methods. Praktik langsung di komunitas juga. Banyak fieldwork yang meaningful.'], + ['user_id' => 10, 'id_sesi' => 'session_19', 'pertanyaan' => 'Tourism management itu enaknya atau challenging?', 'jawaban' => 'Enaklah karena hands-on banyak, networking luas. Challenging dari fast-paced industry yang selalu berubah. Tapi kalau Anda adaptable dan social, ini worth it.'], + ]; + + // Insert chat histories + foreach ($chatData as $chat) { + ChatHistory::create($chat); + } + + echo "āœ… Created " . count($recommendationData) . " recommendations and " . count($chatData) . " chat histories\n"; + } +} diff --git a/import_alumni.py b/import_alumni.py new file mode 100644 index 0000000..c7e3129 --- /dev/null +++ b/import_alumni.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +""" +Script untuk Import Data Alumni dari Excel ke Database SPK Jurusan Polije +Membaca file Excel dan memasukkan ke tabel alumni +""" + +import pandas as pd +import sys +import os +from pathlib import Path + +# Add Laravel project to path +project_path = Path(__file__).parent +sys.path.insert(0, str(project_path)) + +# Import database connection +import subprocess +import json + +def read_excel_file(file_path): + """ + Membaca file Excel dan return dataframe + """ + try: + print(f"šŸ“‚ Membaca file: {file_path}") + df = pd.read_excel(file_path) + print(f"āœ“ File berhasil dibaca") + print(f" Baris: {len(df)}") + print(f" Kolom: {list(df.columns)}") + return df + except Exception as e: + print(f"āœ— Error membaca file: {e}") + return None + +def normalize_column_names(df): + """ + Normalize nama kolom Excel ke format database + """ + # Mapping kemungkinan nama kolom di Excel + column_mapping = { + 'nama': 'nama_alumni', + 'nama alumni': 'nama_alumni', + 'nis': 'nis', + 'no. induk siswa': 'nis', + 'kelompok asal': 'kelompok_asal', + 'matematika': 'mtk', + 'mtk': 'mtk', + 'math': 'mtk', + 'fisika': 'fisika', + 'physics': 'fisika', + 'kimia': 'kimia', + 'chemistry': 'kimia', + 'biologi': 'biologi', + 'biology': 'biologi', + 'ekonomi': 'ekonomi', + 'economics': 'ekonomi', + 'geografi': 'geografi', + 'geography': 'geografi', + 'sosiologi': 'sosiologi', + 'sociology': 'sosiologi', + 'sejarah': 'sejarah', + 'history': 'sejarah', + 'minat': 'minat', + 'interest': 'minat', + 'cita cita': 'cita_cita', + 'cita-cita': 'cita_cita', + 'dream job': 'cita_cita', + 'preferensi studi': 'preferensi_studi', + 'preference': 'preferensi_studi', + 'prestasi': 'prestasi', + 'achievement': 'prestasi', + 'jurusan masuk': 'major_masuk', + 'major': 'major_masuk', + 'jurusan': 'major_masuk', + 'tahun lulus': 'tahun_lulus_polije', + 'tahun lulus polije': 'tahun_lulus_polije', + 'graduation year': 'tahun_lulus_polije', + 'catatan': 'catatan', + 'notes': 'catatan', + 'keterangan': 'catatan', + } + + # Normalize column names + df.columns = [col.lower().strip() for col in df.columns] + df = df.rename(columns=column_mapping, errors='ignore') + + return df + +def validate_data(df): + """ + Validasi data sebelum insert + """ + print("\nšŸ” Validasi Data:") + + # Cek kolom penting + required_cols = ['nama_alumni', 'nis', 'kelompok_asal'] + missing_cols = [col for col in required_cols if col not in df.columns] + + if missing_cols: + print(f"āœ— Kolom penting tidak ada: {missing_cols}") + return False + + print(f"āœ“ Kolom penting ditemukan: {required_cols}") + + # Cek null values + null_counts = df.isnull().sum() + if null_counts.any(): + print(f"⚠ Null values ditemukan:") + for col, count in null_counts[null_counts > 0].items(): + print(f" - {col}: {count} baris") + + return True + +def generate_insert_script(df): + """ + Generate Laravel seeder script untuk insert data + """ + print(f"\nšŸ“ Generate Insert Script...") + + # Prepare data + records = [] + for idx, row in df.iterrows(): + record = { + 'nama_alumni': str(row.get('nama_alumni', '')).strip() or f"Alumni {idx+1}", + 'nis': str(row.get('nis', '')).strip() or None, + 'kelompok_asal': str(row.get('kelompok_asal', 'IPA')).strip(), + 'mtk': float(row.get('mtk', 0)) if pd.notna(row.get('mtk')) else 0, + 'fisika': float(row.get('fisika', 0)) if pd.notna(row.get('fisika')) else 0, + 'kimia': float(row.get('kimia', 0)) if pd.notna(row.get('kimia')) else 0, + 'biologi': float(row.get('biologi', 0)) if pd.notna(row.get('biologi')) else 0, + 'ekonomi': float(row.get('ekonomi', 0)) if pd.notna(row.get('ekonomi')) else 0, + 'geografi': float(row.get('geografi', 0)) if pd.notna(row.get('geografi')) else 0, + 'sosiologi': float(row.get('sosiologi', 0)) if pd.notna(row.get('sosiologi')) else 0, + 'sejarah': float(row.get('sejarah', 0)) if pd.notna(row.get('sejarah')) else 0, + 'minat': str(row.get('minat', 'IPA')).strip(), + 'cita_cita': str(row.get('cita_cita', '')).strip() or 'Profesional', + 'preferensi_studi': str(row.get('preferensi_studi', 'Sains & Teknologi')).strip(), + 'prestasi': str(row.get('prestasi', 'Tidak')).strip(), + 'major_masuk': str(row.get('major_masuk', '')).strip() or None, + 'tahun_lulus_polije': int(row.get('tahun_lulus_polije', 2024)) if pd.notna(row.get('tahun_lulus_polije')) else 2024, + 'catatan': str(row.get('catatan', '')).strip() or None, + } + records.append(record) + + return records + +def create_seeder_file(records): + """ + Create Laravel Seeder file + """ + seeder_content = ''' '{record['nama_alumni']}', + 'nis' => {f"'{record['nis']}'" if record['nis'] else 'null'}, + 'kelompok_asal' => '{record['kelompok_asal']}', + 'mtk' => {record['mtk']}, + 'fisika' => {record['fisika']}, + 'kimia' => {record['kimia']}, + 'biologi' => {record['biologi']}, + 'ekonomi' => {record['ekonomi']}, + 'geografi' => {record['geografi']}, + 'sosiologi' => {record['sosiologi']}, + 'sejarah' => {record['sejarah']}, + 'minat' => '{record['minat']}', + 'cita_cita' => '{record['cita_cita']}', + 'preferensi_studi' => '{record['preferensi_studi']}', + 'prestasi' => '{record['prestasi']}', + 'major_masuk' => {f"'{record['major_masuk']}'" if record['major_masuk'] else 'null'}, + 'tahun_lulus_polije' => {record['tahun_lulus_polije']}, + 'catatan' => {f"'{record['catatan']}'" if record['catatan'] else 'null'}, + ], +''' + + seeder_content += ''' ]; + + foreach ($alumni as $data) { + Alumni::create($data); + } + } +} +''' + + # Write to file + seeder_file = Path(__file__).parent / 'database' / 'seeders' / 'AlumniImportSeeder.php' + seeder_file.parent.mkdir(parents=True, exist_ok=True) + + with open(seeder_file, 'w', encoding='utf-8') as f: + f.write(seeder_content) + + print(f"āœ“ Seeder file created: {seeder_file}") + return str(seeder_file) + +def main(): + """ + Main function + """ + print("=" * 60) + print("IMPORT DATA ALUMNI KE DATABASE SPK JURUSAN POLIJE") + print("=" * 60) + + # Find Excel file + excel_files = list(Path(__file__).parent.glob('*.xlsx')) + \ + list(Path(__file__).parent.glob('DATA *.xlsx')) + \ + list(Path(__file__).parent.glob('*TAMATAN*.xlsx')) + + if not excel_files: + print("āœ— File Excel tidak ditemukan di folder project") + print(" Cari file dengan nama: DATA RESAPAN TAMATAN.xlsx") + return False + + excel_file = excel_files[0] + print(f"šŸ“„ File ditemukan: {excel_file.name}\n") + + # Read Excel + df = read_excel_file(str(excel_file)) + if df is None: + return False + + # Display first few rows + print("\nšŸ“Š Preview Data (5 baris pertama):") + print(df.head().to_string()) + + # Normalize columns + df = normalize_column_names(df) + print(f"\nāœ“ Kolom di-normalize") + + # Validate + if not validate_data(df): + return False + + # Generate records + records = generate_insert_script(df) + print(f"āœ“ {len(records)} records siap untuk di-insert") + + # Show sample + print(f"\nšŸ“‹ Sample Record (pertama):") + print(json.dumps(records[0], indent=2, default=str)) + + # Create seeder + seeder_file = create_seeder_file(records) + + print("\n" + "=" * 60) + print("āœ“ IMPORT DATA SIAP!") + print("=" * 60) + print("\nšŸ“ Cara menggunakan Seeder:") + print("1. Jalankan command: php artisan db:seed --class=AlumniImportSeeder") + print("2. Atau jalankan tanpa argument untuk seed semua: php artisan db:seed") + print("\n") + + return True + +if __name__ == '__main__': + success = main() + sys.exit(0 if success else 1) diff --git a/resources/views/admin/alumni/create.blade.php b/resources/views/admin/alumni/create.blade.php index cee333c..0ffd0b7 100644 --- a/resources/views/admin/alumni/create.blade.php +++ b/resources/views/admin/alumni/create.blade.php @@ -8,22 +8,18 @@

Input data alumni SMA Bima Ambulu

- @if($errors->any()) -
-
- āš ļø - Perbaiki kesalahan berikut: -
-
    +
    +

    āŒ Validasi gagal:

    +
      @foreach($errors->all() as $error) -
    • • {{ $error }}
    • +
    • {{ $error }}
    • @endforeach
    @endif -
    + @csrf @@ -32,36 +28,30 @@
    - - -
    - - -
    - @error('nama_alumni') {{ $message }} @enderror + +
    - - + +
    - - @error('kelompok_asal') {{ $message }} @enderror
    - - + +
@@ -69,44 +59,37 @@

šŸ“Š Nilai Saat Entry (Rapor SMA)

-

Input nilai 0-100, kosongkan jika tidak ada

- - @error('mtk') {{ $message }} @enderror +
- - @error('fisika') {{ $message }} @enderror +
- - @error('kimia') {{ $message }} @enderror +
- - @error('biologi') {{ $message }} @enderror +
- - @error('ekonomi') {{ $message }} @enderror +
- - @error('geografi') {{ $message }} @enderror +
@@ -117,28 +100,21 @@
- - -
- - -
- @error('major_masuk') {{ $message }} @enderror + +
- - Tahun Lulus Polije + - @error('tahun_lulus_polije') {{ $message }} @enderror
- - - 0/500 + +
@@ -147,72 +123,9 @@ Batal - @endsection - -@section('scripts') - -@endsection diff --git a/resources/views/admin/alumni/edit.blade.php b/resources/views/admin/alumni/edit.blade.php index 48f163b..ca04040 100644 --- a/resources/views/admin/alumni/edit.blade.php +++ b/resources/views/admin/alumni/edit.blade.php @@ -8,22 +8,18 @@

{{ $alumni->nama_alumni }}

- @if($errors->any()) -
-
- āš ļø - Perbaiki kesalahan berikut: -
-
    +
    +

    āŒ Validasi gagal:

    +
      @foreach($errors->all() as $error) -
    • • {{ $error }}
    • +
    • {{ $error }}
    • @endforeach
    @endif -
    + @csrf @method('PUT') @@ -32,14 +28,9 @@
    - - -
    - - -
    - @error('nama_alumni') {{ $message }} @enderror + +
    @@ -109,14 +100,9 @@
    - - -
    - - -
    - @error('major_masuk') {{ $message }} @enderror + +
    @@ -126,10 +112,9 @@
    - - - 0/500 + +
    @@ -138,77 +123,9 @@ Batal -
    - - @section('scripts') - - @endsection @endsection diff --git a/resources/views/admin/alumni/show.blade.php b/resources/views/admin/alumni/show.blade.php index ee786fd..e35850d 100644 --- a/resources/views/admin/alumni/show.blade.php +++ b/resources/views/admin/alumni/show.blade.php @@ -39,60 +39,20 @@
- +
-

šŸ“Š Nilai Saat Entry (Rapor SMA)

-
- @if($alumni->kelompok_asal === 'IPA') - @if($alumni->mtk) -
-

Matematika

-

{{ $alumni->mtk }}

-
- @endif - @if($alumni->fisika) -
-

Fisika

-

{{ $alumni->fisika }}

-
- @endif - @if($alumni->kimia) -
-

Kimia

-

{{ $alumni->kimia }}

-
- @endif - @if($alumni->biologi) -
-

Biologi

-

{{ $alumni->biologi }}

-
- @endif +

šŸ“Š Nilai Rata-Rata (Rapor SMA)

+
+ @if($alumni->nilai_rata_rata) +
+

Nilai Rata-Rata

+

{{ $alumni->nilai_rata_rata }}

+
@else - @if($alumni->ekonomi) -
-

Ekonomi

-

{{ $alumni->ekonomi }}

-
- @endif - @if($alumni->geografi) -
-

Geografi

-

{{ $alumni->geografi }}

-
- @endif - @if($alumni->sosiologi) -
-

Sosiologi

-

{{ $alumni->sosiologi }}

-
- @endif - @if($alumni->sejarah) -
-

Sejarah

-

{{ $alumni->sejarah }}

-
- @endif +
+

Nilai Rata-Rata

+

-

+
@endif
diff --git a/resources/views/admin/jurusan/create.blade.php b/resources/views/admin/jurusan/create.blade.php index d90e140..d02493f 100644 --- a/resources/views/admin/jurusan/create.blade.php +++ b/resources/views/admin/jurusan/create.blade.php @@ -45,11 +45,11 @@
- - + +
Karakter: - 0/500 + 0
@@ -64,9 +64,9 @@
- +
-

Rumpun bidang studi yang cocok untuk jurusan ini. Pilihan: Sains & Teknologi, Pertanian & Lingkungan, Kesehatan & Ilmu Hayat, Bisnis & Manajemen, Sosial & Humaniora

+

Pilihan preferensi studi: Praktik Langsung, DuDi, Project Based, Blended Learning

0/500
@@ -93,19 +93,19 @@
- +
- +
- +
- +
@@ -114,19 +114,19 @@
- +
- +
- +
- +
@@ -148,8 +148,8 @@ function updateCharCount(elementId = 'charCount') { const textarea = event.target; const count = textarea.value.length; - const maxLength = parseInt(textarea.maxLength) || 500; - document.getElementById(elementId).textContent = `${count}/${maxLength}`; + const maxLength = parseInt(textarea.maxLength); + document.getElementById(elementId).textContent = maxLength > 0 ? `${count}/${maxLength}` : `${count}`; validateJurusanForm(); } @@ -184,11 +184,16 @@ function validateJurusanForm() { textarea.name === 'preferensi_studi' ? 'preferensiCount' : 'prospekCount'; const count = textarea.value.length; - const maxLength = parseInt(textarea.maxLength) || 500; + const maxLength = parseInt(textarea.maxLength); if (document.getElementById(id)) { - document.getElementById(id).textContent = `${count}/${maxLength}`; + document.getElementById(id).textContent = maxLength > 0 ? `${count}/${maxLength}` : `${count}`; } }); + + const desc = document.querySelector('textarea[name="deskripsi"]'); + if (desc && document.getElementById('charCount')) { + document.getElementById('charCount').textContent = `${desc.value.length}`; + } validateJurusanForm(); }); diff --git a/resources/views/admin/jurusan/edit.blade.php b/resources/views/admin/jurusan/edit.blade.php index f052b21..4a81e7e 100644 --- a/resources/views/admin/jurusan/edit.blade.php +++ b/resources/views/admin/jurusan/edit.blade.php @@ -46,11 +46,11 @@
- - + +
Karakter: - 0/500 + 0
@@ -72,9 +72,9 @@
- +
-

Rumpun bidang studi yang cocok: Sains & Teknologi, Pertanian & Lingkungan, Kesehatan & Ilmu Hayat, Bisnis & Manajemen, Sosial & Humaniora

+

Pilihan preferensi studi: Praktik Langsung, DuDi, Project Based, Blended Learning

0/500
@@ -93,6 +93,8 @@ @php $bobot = $jurusan->bobot_mapel ?? []; + $bobotIpa = data_get($bobot, 'ipa', $bobot); + $bobotIps = data_get($bobot, 'ips', $bobot); @endphp

āš–ļø Bobot Mata Pelajaran

@@ -104,19 +106,19 @@
- +
- +
- +
- +
@@ -125,19 +127,19 @@
- +
- +
- +
- +
@@ -159,8 +161,8 @@ function updateCharCount(elementId = 'charCount') { const textarea = event.target; const count = textarea.value.length; - const maxLength = parseInt(textarea.maxLength) || 500; - document.getElementById(elementId).textContent = `${count}/${maxLength}`; + const maxLength = parseInt(textarea.maxLength); + document.getElementById(elementId).textContent = maxLength > 0 ? `${count}/${maxLength}` : `${count}`; validateJurusanForm(); } @@ -195,11 +197,16 @@ function validateJurusanForm() { textarea.name === 'preferensi_studi' ? 'preferensiCount' : 'prospekCount'; const count = textarea.value.length; - const maxLength = parseInt(textarea.maxLength) || 500; + const maxLength = parseInt(textarea.maxLength); if (document.getElementById(id)) { - document.getElementById(id).textContent = `${count}/${maxLength}`; + document.getElementById(id).textContent = maxLength > 0 ? `${count}/${maxLength}` : `${count}`; } }); + + const desc = document.querySelector('textarea[name="deskripsi"]'); + if (desc && document.getElementById('charCount')) { + document.getElementById('charCount').textContent = `${desc.value.length}`; + } validateJurusanForm(); }); diff --git a/resources/views/admin/jurusan/index.blade.php b/resources/views/admin/jurusan/index.blade.php index 89c8371..fcc8fca 100644 --- a/resources/views/admin/jurusan/index.blade.php +++ b/resources/views/admin/jurusan/index.blade.php @@ -97,23 +97,23 @@

Sistem menggunakan 5 kriteria utama untuk memberikan rekomendasi jurusan yang tepat:

-

šŸ“ Nilai Akademik (40%)

+

šŸ“ Nilai Akademik (15.6%)

IPA: MTK, Fisika, Kimia, Biologi
IPS: Ekonomi, Geografi, Sosiologi, Sejarah

-

šŸ’” Minat & Bakat (35%)

+

šŸ’” Minat & Bakat (45.6%)

Dicocokkan dengan keywords jurusan secara graduated

-

šŸŽÆ Preferensi Studi (15%)

+

šŸŽÆ Preferensi Studi (25.6%)

Praktik Langsung, DuDi, Project Based, Blended Learning

-

šŸ† Prestasi (5%)

+

šŸ† Prestasi (4%)

Prestasi akademik dan non-akademik siswa

-

šŸ’¼ Cita-cita (5%)

+

šŸ’¼ Cita-cita (9%)

Dicocokkan dengan keywords jurusan secara graduated

diff --git a/resources/views/admin/layouts/app.blade.php b/resources/views/admin/layouts/app.blade.php index a3e5d7f..af566b3 100644 --- a/resources/views/admin/layouts/app.blade.php +++ b/resources/views/admin/layouts/app.blade.php @@ -7,14 +7,14 @@ @yield('title', 'Admin Panel') - SPK Jurusan Polije + + +
+
+ +
+
+ šŸ” +
+

Lupa Password?

+

Tenang, kami bantu kamu atur ulang password dengan mudah.

+
+ + +
+

+ Masukkan email kamu yang terdaftar, dan kami akan mengirimkan tautan untuk mengatur ulang password ke inbox kamu. +

+
+ + + @if (session('status')) +
+
+ āœ… +
+

Berhasil!

+

{{ session('status') }}

+

šŸ“§ Cek email Anda untuk tautan reset password (cek juga folder spam/promotions)

+
+
+
+ @endif + + + @if ($errors->any()) +
+
+ āŒ +
+

Gagal!

+
    + @foreach ($errors->all() as $error) +
  • + • + {{ $error }} +
  • + @endforeach +
+
+
+
+ @endif + + +
+ @csrf + + +
+ + + @error('email') + āš ļø {{ $message }} + @enderror +
+ + + + + +
+

+ Ingat passwordnya? + Kembali ke Login +

+
+
+ + +
+

šŸ’” Jika email tidak masuk, cek folder spam kamu.

+
+
- - + + + diff --git a/resources/views/auth/login-new.blade.php b/resources/views/auth/login-new.blade.php index ca13a1c..1360b7a 100644 --- a/resources/views/auth/login-new.blade.php +++ b/resources/views/auth/login-new.blade.php @@ -19,11 +19,11 @@ flex-direction: column; min-height: 100vh; width: 100%; - background-color: #5B7B89; + background-color: #6B7280; } .left-section { width: 100%; - background-color: #5B7B89; + background-color: #6B7280; display: flex; align-items: center; justify-content: center; @@ -38,14 +38,14 @@ width: 180px; height: 240px; background-color: rgba(255, 255, 255, 0.1); - border: 2px dashed #FCD34D; + border: 2px dashed #C9A961; border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; margin-bottom: 20px; - color: #FCD34D; + color: #C9A961; font-size: 14px; text-align: center; padding: 15px; diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index b5ea6d8..344926f 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -19,11 +19,11 @@ flex-direction: column; min-height: 100vh; width: 100%; - background-color: #5B7B89; + background-color: #6B7280; } .left-section { width: 100%; - background-color: #5B7B89; + background-color: #6B7280; display: flex; align-items: center; justify-content: center; @@ -38,14 +38,14 @@ width: 180px; height: 240px; background-color: rgba(255, 255, 255, 0.1); - border: 2px dashed #FCD34D; + border: 2px dashed #C9A961; border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; margin-bottom: 20px; - color: #FCD34D; + color: #C9A961; font-size: 14px; text-align: center; padding: 0; @@ -73,7 +73,7 @@ } .brand-info p { font-size: 16px; - color: #FCD34D; + color: #C9A961; font-weight: 700; letter-spacing: 0.5px; } @@ -191,6 +191,78 @@ margin: 3px 0; line-height: 1.6; } + /* Forgot Password Lock Alert */ + .forgot-password-alert { + background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); + border: 3px solid #dc2626; + border-radius: 12px; + padding: 18px; + margin-bottom: 24px; + box-shadow: 0 4px 15px rgba(220, 38, 38, 0.2); + } + .forgot-password-alert .alert-header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; + } + .forgot-password-alert .alert-header span { + font-size: 24px; + } + .forgot-password-alert .alert-header h3 { + color: #7f1d1d; + font-size: 16px; + font-weight: 700; + margin: 0; + } + .forgot-password-alert .alert-message { + color: #991b1b; + font-size: 14px; + line-height: 1.6; + margin-bottom: 14px; + font-weight: 500; + } + .forgot-password-alert .alert-buttons { + display: flex; + gap: 10px; + flex-direction: column; + } + .forgot-password-alert .btn-forgot { + background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%); + color: white; + padding: 10px 16px; + border: none; + border-radius: 6px; + font-weight: 600; + cursor: pointer; + text-decoration: none; + display: inline-block; + text-align: center; + transition: all 0.3s ease; + font-size: 14px; + } + .forgot-password-alert .btn-forgot:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4); + } + .forgot-password-alert .btn-retry { + background-color: #f3f4f6; + color: #1f2937; + padding: 10px 16px; + border: 2px solid #d1d5db; + border-radius: 6px; + font-weight: 600; + cursor: pointer; + text-decoration: none; + display: inline-block; + text-align: center; + transition: all 0.3s ease; + font-size: 14px; + } + .forgot-password-alert .btn-retry:hover { + background-color: #e5e7eb; + border-color: #9ca3af; + } /* Tablet and Desktop */ @media (min-width: 768px) { .container-wrapper { @@ -264,6 +336,23 @@ font-size: 14px; margin-bottom: 24px; } + .forgot-password-alert { + padding: 20px; + margin-bottom: 24px; + border: 3px solid #dc2626; + } + .forgot-password-alert .alert-header h3 { + font-size: 18px; + } + .forgot-password-alert .alert-message { + font-size: 15px; + margin-bottom: 16px; + } + .forgot-password-alert .btn-forgot, + .forgot-password-alert .btn-retry { + padding: 12px 18px; + font-size: 15px; + } } @@ -288,7 +377,27 @@

Selamat Datang

Masuk untuk melanjutkan

- @if ($errors->any()) + {{-- Special Alert: Siswa Forgot Password Lock --}} + @if ($errors->has('forgot_password') && $errors->has('email')) +
+
+ šŸ”’ +

Akun Terkunci Sementara

+
+
+ {{ $errors->first('email') }} +
+
+ + šŸ”‘ Reset Password Sekarang + + +
+
+ @elseif ($errors->any()) + {{-- Regular Error Messages --}}
@foreach ($errors->all() as $error)

• {{ $error }}

diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index 1367611..191f9b7 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -19,11 +19,11 @@ flex-direction: column; min-height: 100vh; width: 100%; - background-color: #5B7B89; + background-color: #6B7280; } .left-section { width: 100%; - background-color: #5B7B89; + background-color: #6B7280; display: flex; align-items: center; justify-content: center; @@ -38,14 +38,14 @@ width: 180px; height: 240px; background-color: rgba(255, 255, 255, 0.1); - border: 2px dashed #FCD34D; + border: 2px dashed #C9A961; border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; margin-bottom: 20px; - color: #FCD34D; + color: #C9A961; font-size: 14px; text-align: center; padding: 0; @@ -73,7 +73,7 @@ } .brand-info p { font-size: 16px; - color: #FCD34D; + color: #C9A961; font-weight: 700; letter-spacing: 0.5px; } diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php index 972ac5e..823f968 100644 --- a/resources/views/auth/reset-password.blade.php +++ b/resources/views/auth/reset-password.blade.php @@ -1,51 +1,236 @@ - -
- @csrf - - - - - -
- - - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- - {{ __('Atur Ulang Password') }} - -
-
- - @push('scripts') - - @endpush -
+ to { + opacity: 1; + transform: translateY(0); + } + } + .animate-slide-down { + animation: slide-down 0.3s ease-out; + } + + + +
+
+ +
+
+ šŸ”„ +
+

Atur Ulang Password

+

Buat password baru yang kuat untuk akun kamu.

+
+ + +
+

+ Pilih password yang kuat (minimal 8 karakter) dan pastikan kamu ingat untuk login di lain waktu. +

+
+ + + @if (session('status')) +
+
+ āœ… +
+

Berhasil!

+

{{ session('status') }}

+
+
+
+ @endif + + + @if ($errors->any()) +
+
+ āŒ +
+

Ada Kesalahan!

+
    + @foreach ($errors->all() as $error) +
  • + • + {{ $error }} +
  • + @endforeach +
+

šŸ’” Pastikan:

+
    +
  • āœ“ Password minimal 8 karakter
  • +
  • āœ“ Kedua password sama persis
  • +
  • āœ“ Tidak menggunakan password lama
  • +
+
+
+
+ @endif + + +
+ @csrf + + + + + +
+ + + +
+ + +
+ +
+ + +
+ @error('password') + āš ļø {{ $message }} + @enderror +
+ + +
+ +
+ + +
+ @error('password_confirmation') + āš ļø {{ $message }} + @enderror +
+ + + + + + +
+ + +
+

šŸ’” Tips Password yang Aman:

+
    +
  • āœ“ Gunakan kombinasi huruf besar, kecil, angka, dan simbol
  • +
  • āœ“ Hindari kata-kata yang mudah ditebak
  • +
  • āœ“ Jangan gunakan informasi pribadi
  • +
  • āœ“ Minimal 8 karakter, semakin panjang semakin aman
  • +
+
+
+
+ + + + diff --git a/resources/views/bk/alumni/create.blade.php b/resources/views/bk/alumni/create.blade.php index 8dee309..63a0be2 100644 --- a/resources/views/bk/alumni/create.blade.php +++ b/resources/views/bk/alumni/create.blade.php @@ -8,22 +8,18 @@

Input data alumni SMA Bima Ambulu

- @if($errors->any()) -
-
- āš ļø - Perbaiki kesalahan berikut: -
-
    +
    +

    āŒ Validasi gagal:

    +
      @foreach($errors->all() as $error) -
    • • {{ $error }}
    • +
    • {{ $error }}
    • @endforeach
    @endif -
    + @csrf @@ -32,14 +28,9 @@
    - - -
    - - -
    - @error('nama_alumni') {{ $message }} @enderror + +
    @@ -51,16 +42,11 @@
    - -
    - - -
    - @error('kelompok_asal') {{ $message }} @enderror
    @@ -77,33 +63,33 @@
    - +
    - +
    - +
    - +
    - +
    - +
    @@ -114,14 +100,9 @@
    - - -
    - - -
    - @error('major_masuk') {{ $message }} @enderror + +
    @@ -131,10 +112,9 @@
    - - - 0/500 + +
    @@ -143,75 +123,10 @@ Batal -
    @endsection -@section('scripts') - -@endsection - diff --git a/resources/views/bk/alumni/edit.blade.php b/resources/views/bk/alumni/edit.blade.php index 2508c3f..dc15975 100644 --- a/resources/views/bk/alumni/edit.blade.php +++ b/resources/views/bk/alumni/edit.blade.php @@ -8,22 +8,18 @@

    {{ $alumni->nama_alumni }}

    - @if($errors->any()) -
    -
    - āš ļø - Perbaiki kesalahan berikut: -
    -
      +
      +

      āŒ Validasi gagal:

      +
        @foreach($errors->all() as $error) -
      • • {{ $error }}
      • +
      • {{ $error }}
      • @endforeach
      @endif -
      + @csrf @method('PUT') @@ -32,14 +28,9 @@
      - - -
      - - -
      - @error('nama_alumni') {{ $message }} @enderror + +
      @@ -51,16 +42,11 @@
      - -
      - - -
      - @error('kelompok_asal') {{ $message }} @enderror
      @@ -77,33 +63,33 @@
      - +
      - +
      - +
      - +
      - +
      - +
      @@ -114,14 +100,9 @@
      - - -
      - - -
      - @error('major_masuk') {{ $message }} @enderror + +
      @@ -131,10 +112,9 @@
      - - - 0/500 + +
      @@ -143,74 +123,9 @@ Batal -
      @endsection - -@section('scripts') - -@endsection diff --git a/resources/views/bk/alumni/show.blade.php b/resources/views/bk/alumni/show.blade.php index 49804ad..42a985c 100644 --- a/resources/views/bk/alumni/show.blade.php +++ b/resources/views/bk/alumni/show.blade.php @@ -41,60 +41,14 @@
      -

      šŸ“Š Nilai Saat Entry (Rapor SMA)

      -
      - @if($alumni->kelompok_asal === 'IPA') - @if($alumni->mtk) -
      -

      Matematika

      -

      {{ $alumni->mtk }}

      -
      - @endif - @if($alumni->fisika) -
      -

      Fisika

      -

      {{ $alumni->fisika }}

      -
      - @endif - @if($alumni->kimia) -
      -

      Kimia

      -

      {{ $alumni->kimia }}

      -
      - @endif - @if($alumni->biologi) -
      -

      Biologi

      -

      {{ $alumni->biologi }}

      -
      - @endif - @else - @if($alumni->ekonomi) -
      -

      Ekonomi

      -

      {{ $alumni->ekonomi }}

      -
      - @endif - @if($alumni->geografi) -
      -

      Geografi

      -

      {{ $alumni->geografi }}

      -
      - @endif - @if($alumni->sosiologi) -
      -

      Sosiologi

      -

      {{ $alumni->sosiologi }}

      -
      - @endif - @if($alumni->sejarah) -
      -

      Sejarah

      -

      {{ $alumni->sejarah }}

      -
      - @endif - @endif -
      +

      šŸ“Š Nilai Rata-Rata (Rapor SMA)

      + @if($alumni->nilai_rata_rata) +
      +

      {{ $alumni->nilai_rata_rata }}

      +
      + @else +

      -

      + @endif
      diff --git a/resources/views/bk/jurusan/create.blade.php b/resources/views/bk/jurusan/create.blade.php index 3e4bcd2..0f76dc3 100644 --- a/resources/views/bk/jurusan/create.blade.php +++ b/resources/views/bk/jurusan/create.blade.php @@ -45,11 +45,11 @@
      - - + +

      Jelaskan tentang program, fasilitas, dan prospek

      - 0/500 + 0
      @@ -61,8 +61,8 @@
      - -

      Pilihan: Sains & Teknologi | Pertanian & Lingkungan | Kesehatan & Ilmu Hayat | Bisnis & Manajemen | Sosial & Humaniora

      + +

      Pilihan preferensi studi: Praktik Langsung, DuDi, Project Based, Blended Learning

      @@ -83,19 +83,19 @@
      - +
      - +
      - +
      - +
      @@ -104,19 +104,19 @@
      - +
      - +
      - +
      - +
      @@ -139,10 +139,11 @@ @section('scripts') @endsection diff --git a/resources/views/bk/jurusan/edit.blade.php b/resources/views/bk/jurusan/edit.blade.php index 3f344f7..b8530f9 100644 --- a/resources/views/bk/jurusan/edit.blade.php +++ b/resources/views/bk/jurusan/edit.blade.php @@ -46,11 +46,11 @@
    - - + +

    Jelaskan tentang program, fasilitas, dan prospek

    - 0/500 + 0
    @@ -69,8 +69,8 @@
    - -

    Pilihan: Sains & Teknologi | Pertanian & Lingkungan | Kesehatan & Ilmu Hayat | Bisnis & Manajemen | Sosial & Humaniora

    + +

    Pilihan preferensi studi: Praktik Langsung, DuDi, Project Based, Blended Learning

    @@ -83,6 +83,8 @@ @php $bobot = $jurusan->bobot_mapel ?? []; + $bobotIpa = data_get($bobot, 'ipa', $bobot); + $bobotIps = data_get($bobot, 'ips', $bobot); @endphp

    āš–ļø Bobot Mata Pelajaran

    @@ -94,19 +96,19 @@
    - +
    - +
    - +
    - +
    @@ -115,19 +117,19 @@
    - +
    - +
    - +
    - +
    @@ -150,10 +152,11 @@ @section('scripts') @endsection diff --git a/resources/views/bk/jurusan/index.blade.php b/resources/views/bk/jurusan/index.blade.php index 90721ff..ab078ce 100644 --- a/resources/views/bk/jurusan/index.blade.php +++ b/resources/views/bk/jurusan/index.blade.php @@ -97,23 +97,23 @@

    Sistem menggunakan 5 kriteria utama untuk memberikan rekomendasi jurusan yang tepat:

    -

    šŸ“ Nilai Akademik (40%)

    +

    šŸ“ Nilai Akademik (15.6%)

    IPA: MTK, Fisika, Kimia, Biologi
    IPS: Ekonomi, Geografi, Sosiologi, Sejarah

    -

    šŸ’” Minat & Bakat (35%)

    +

    šŸ’” Minat & Bakat (45.6%)

    Dicocokkan dengan keywords jurusan secara graduated

    -

    šŸŽÆ Preferensi Studi (15%)

    +

    šŸŽÆ Preferensi Studi (25.6%)

    Praktik Langsung, DuDi, Project Based, Blended Learning

    -

    šŸ† Prestasi (5%)

    +

    šŸ† Prestasi (4%)

    Prestasi akademik dan non-akademik siswa

    -

    šŸ’¼ Cita-cita (5%)

    +

    šŸ’¼ Cita-cita (9%)

    Dicocokkan dengan keywords jurusan secara graduated

    diff --git a/resources/views/bk/layouts/app.blade.php b/resources/views/bk/layouts/app.blade.php index 6a7e763..aa72fc0 100644 --- a/resources/views/bk/layouts/app.blade.php +++ b/resources/views/bk/layouts/app.blade.php @@ -8,17 +8,17 @@ @@ -64,179 +64,173 @@ -
    - -
    -

    Selamat Datang di Sistem Pemilihan Jurusan

    -

    - Memilih jurusan adalah keputusan penting yang akan mempengaruhi karir dan masa depan Anda. Sistem ini dirancang untuk membantu Anda menemukan jurusan kuliah yang paling sesuai dengan profil akademik, minat, gaya belajar, prestasi, dan cita-cita Anda. +

    + +
    +

    Temukan Jurusan Impianmu šŸŽÆ

    +

    + Memilih jurusan adalah salah satu keputusan terpenting dalam hidup. Kami ada di sini untuk membantu kamu menemukan program studi yang benar-benar sesuai dengan potensi, minat, dan impian karirmu. Dengan teknologi AI dan analisis mendalam, kami siap memberikan panduan terbaik.

    - -
    -

    - Bagaimana Sistem Ini Bekerja? Kami menganalisis 5 faktor utama dalam diri Anda: nilai akademik (40%), minat dan passion (35%), preferensi gaya belajar (15%), prestasi dan pencapaian (5%), serta cita-cita dan rencana karir (5%). Dari analisis mendalam tersebut, sistem memberikan ranking 9 jurusan yang tersedia berdasarkan kesesuaian dengan profil Anda. -

    +
    +
    +

    9

    +

    Jurusan

    +
    +
    +

    24/7

    +

    Konsultasi AI

    +
    - -

    - Fitur-Fitur yang Tersedia: -

    -
      -
    • - āœ“ - Analisis Rekomendasi: Isi kuesioner singkat dan dapatkan rekomendasi 9 jurusan yang disesuaikan dengan profil Anda -
    • -
    • - āœ“ - Konsultasi dengan AI: Chat dengan konselor BK virtual yang siap menjawab pertanyaan tentang jurusan, prospek karir, dan tips sukses kuliah -
    • -
    • - āœ“ - Riwayat Analisis: Lihat kembali semua analisis dan chat history Anda kapan saja untuk referensi -
    • -
    • - āœ“ - Profil Pribadi: Kelola data diri, foto profil, dan informasi akademik Anda -
    • -
    - -

    - šŸ’” Tips: Untuk hasil yang akurat, jawab semua pertanyaan dengan jujur dan detail. Semakin detail profil Anda, semakin akurat rekomendasi yang kami berikan. -

    - -
    + +
    -
    -
    -
    šŸ“Š
    +
    +
    +
    šŸ“Š
    -

    Analisis Rekomendasi

    -

    - Isi formulir singkat tentang profil Anda. Sistem akan menganalisis dan memberikan rekomendasi dari 9 jurusan yang tersedia. -

    +

    Cari Jurusan Terbaikmu

    +

    Rekomendasi Khusus

    - -
    -

    - Durasi: 3-5 menit | Metode: AI Analysis -

    +

    + Jawab beberapa pertanyaan tentang nilai akademik, minat, gaya belajar, dan cita-cita kamu. Sistem kami akan menganalisis profil lengkap kamu dan memberikan rekomendasi jurusan yang paling cocok, lengkap dengan penjelasan alasan kesesuaiannya. +

    +
    + ā±ļø Waktu: 3-5 menit + šŸ“ˆ Akurasi Tinggi
    + + Mulai Analisis → +
    -
    -
    -
    šŸ’¬
    +
    +
    +
    šŸ’¬
    -

    Chat dengan AI

    -

    - Tanya jawab tentang jurusan, kurikulum, prospek karir, dan pertanyaan seputar pemilihan jurusan. -

    +

    Tanya Jawab dengan AI

    +

    Konsultan Karir Virtual

    -
    - - Mulai Chat - +

    + Ada pertanyaan tentang jurusan tertentu? Penasaran dengan prospek karir? Atau butuh tips sukses masuk kuliah? Chat dengan konselor AI kami yang siap membantu kapan saja. Dapatkan jawaban detail dan rekomendasi personal untuk setiap pertanyaan kamu. +

    +
    + ⭐ Respons Cepat + šŸ¤– AI Powered
    -
    -

    - Fitur: Tanya jawab dengan AI -

    + + Mulai Konsultasi → + +
    +
    + + +
    +

    Fitur Lengkap untuk Pendampinganmu

    +
    +
    +
    šŸ“‹
    +

    Rekomendasi Personal

    +

    Analisis mendalam berdasarkan profil unik kamu

    +
    +
    +
    šŸ’¬
    +

    Chat 24/7

    +

    Konsultasi kapan saja dengan AI konselor

    +
    +
    +
    šŸ“š
    +

    Info Jurusan

    +

    Detail lengkap tentang setiap program studi

    +
    +
    +
    šŸ“Š
    +

    Riwayat

    +

    Simpan & bandingkan hasil analisis sebelumnya

    - -
    -

    9 Jurusan Tersedia

    -
    -
    -
    🌾
    -

    Produksi Pertanian

    -

    Teknik budidaya tanaman modern

    + +
    +

    Bagaimana Cara Kerjanya? šŸ”

    +
    +
    +
    1
    +

    Isi Kuesioner

    +

    Jawab pertanyaan tentang nilai, minat, dan impian kamu secara jujur

    -
    -
    šŸ”¬
    -

    Teknologi Pertanian

    -

    Inovasi teknologi pertanian

    +
    +
    2
    +

    Proses Data

    +

    AI menganalisis profil kamu dengan algoritma machine learning

    -
    -
    šŸ„
    -

    Peternakan

    -

    Manajemen peternakan

    +
    +
    3
    +

    Dapatkan Hasil

    +

    Terima ranking 9 jurusan dengan skor kesesuaian detail

    -
    -
    šŸ’¼
    -

    Manajemen Agribisnis

    -

    Bisnis pertanian

    -
    -
    -
    šŸ’»
    -

    Teknologi Informasi

    -

    Sistem digital

    -
    -
    -
    āš™ļø
    -

    Teknik

    -

    Mesin & sistem teknik

    -
    -
    -
    āš•ļø
    -

    Kesehatan

    -

    Profesi kesehatan

    -
    -
    -
    šŸ—£ļø
    -

    Bahasa & Komunikasi

    -

    Komunasikan & wisata

    -
    -
    -
    šŸ“Š
    -

    Bisnis

    -

    Manajemen bisnis

    +
    +
    4
    +

    Konsultasi Lanjut

    +

    Diskusikan hasil & pertanyaan lanjut dengan AI konselor

    - -
    -

    Cara Kerja Sistem

    -
    -
    -
    1
    -
    -

    Isi Data Diri

    -

    Masukkan nilai akademik, minat, preferensi, prestasi, dan cita-cita

    -
    + +
    +

    Program Studi yang Tersedia

    +

    Jelajahi 9 program studi unggulan kami di Politeknik Negeri Jember

    +
    +
    +
    🌾
    +

    Produksi Pertanian

    +

    Teknik budidaya tanaman modern & berkelanjutan

    -
    -
    2
    -
    -

    Analisis Sistem

    -

    Sistem menganalisis data menggunakan algoritma canggih

    -
    +
    +
    šŸ”¬
    +

    Teknologi Pertanian

    +

    Inovasi teknologi untuk efisiensi pertanian

    -
    -
    3
    -
    -

    Hasil Rekomendasi

    -

    Dapatkan rekomendasi 9 jurusan dengan analisis detail

    -
    +
    +
    šŸ„
    +

    Peternakan

    +

    Manajemen & teknologi peternakan terkini

    -
    -
    4
    -
    -

    Konsultasi Lanjut

    -

    Gunakan chatbot untuk tanya jawab lebih lanjut

    -
    +
    +
    šŸ’¼
    +

    Manajemen Agribisnis

    +

    Bisnis & manajemen di sektor pertanian

    +
    +
    +
    šŸ’»
    +

    Teknologi Informasi

    +

    Sistem digital & aplikasi web modern

    +
    +
    +
    āš™ļø
    +

    Teknik Mesin

    +

    Teknik mesin & sistem otomasi industri

    +
    +
    +
    āš•ļø
    +

    Kesehatan

    +

    Program kesehatan & keselamatan kerja

    +
    +
    +
    šŸ—£ļø
    +

    Komunikasi & Pariwisata

    +

    Program komunikasi & industri pariwisata

    +
    +
    +
    šŸ“Š
    +

    Akuntansi & Bisnis

    +

    Akuntansi, keuangan & manajemen bisnis

    diff --git a/resources/views/emails/reset-password.blade.php b/resources/views/emails/reset-password.blade.php new file mode 100644 index 0000000..0ac126e --- /dev/null +++ b/resources/views/emails/reset-password.blade.php @@ -0,0 +1,258 @@ + + + + + + Reset Password - Sistem Pemilihan Jurusan + + + +
    + +
    +

    šŸ” POLIJE

    +

    Sistem Pemilihan Jurusan

    +
    + + +
    +

    Halo, {{ $user->name }}! šŸ‘‹

    + +

    + Kami menerima permintaan untuk mengatur ulang password akunmu. Jika kamu yang melakukan ini, ikuti langkah-langkah di bawah untuk membuat password baru. +

    + +
    + ā° Penting: Link reset password ini hanya berlaku selama {{ $expiresIn }} menit. Jika sudah kadaluarsa, silakan minta link baru di halaman "Lupa Password". +
    + + + + + +
    +

    šŸ“‹ Langkah-Langkah:

    +
    +
    1
    +
    Klik tombol "RESET PASSWORD" di atas atau copy-paste link ke browser
    +
    +
    +
    2
    +
    Isi form dengan password baru yang kuat (minimal 8 karakter)
    +
    +
    +
    3
    +
    Konfirmasi password baru dengan memasukkan ulang
    +
    +
    +
    4
    +
    Klik tombol "Simpan Password"
    +
    +
    +
    5
    +
    Login dengan password baru kamu di Sistem Pemilihan Jurusan
    +
    +
    + + +
    + āš ļø Keamanan: Jika kamu tidak meminta reset password ini atau tidak mengenali aktivitas ini, abaikan email ini dan segera hubungi admin kami. +
    +
    + + + +
    + + diff --git a/resources/views/history/chat.blade.php b/resources/views/history/chat.blade.php index 7644d67..feec2d6 100644 --- a/resources/views/history/chat.blade.php +++ b/resources/views/history/chat.blade.php @@ -7,19 +7,19 @@ @@ -55,193 +55,192 @@ -
    +
    {{-- Success Message --}} @if (session('status') === 'profile-updated') -
    +
    āœ… Profil berhasil diperbarui!
    @endif {{-- ========== PROFILE HEADER CARD - HORIZONTAL ========== --}} -
    -
    +
    +
    @if($user->foto) - Foto Profil + Foto Profil @else -
    +
    {{ strtoupper(substr($user->name, 0, 1)) }}
    - + @endif
    -

    {{ $user->name }}

    -
    +

    {{ $user->name }}

    +
    -

    NIS

    -

    {{ $user->nis ?? '-' }}

    +

    NIS

    +

    {{ $user->nis ?? '-' }}

    -

    Email

    -

    {{ $user->email }}

    +

    Email

    +

    {{ $user->email }}

    -

    Kelompok

    +

    Kelompok

    @if($user->kelompok_asal) - + {{ $user->kelompok_asal }} @else -

    -

    +

    -

    @endif
    -

    Terdaftar

    -

    {{ $user->created_at->format('d M Y') }}

    +

    Terdaftar

    +

    {{ $user->created_at->format('d M Y') }}

    - {{-- ========== INFORMASI PROFIL ========== --}} -
    -

    šŸ“ Edit Informasi Profil

    -

    Perbarui data diri dan foto profil Anda.

    + {{-- ========== FORMS CONTAINER - 2 KOLOM ========== --}} +
    -
    - @csrf - @method('patch') + {{-- ========== INFORMASI PROFIL ========== --}} +
    +

    šŸ“ Edit Profil

    +

    Perbarui data diri Anda.

    - {{-- Foto Profil Upload --}} -
    - -
    -
    - -

    Format: JPG, PNG, GIF. Maks 2MB.

    -
    -
    - @error('foto') -

    {{ $message }}

    - @enderror -
    + + @csrf + @method('patch') -
    - {{-- Nama --}} -
    - - - @error('name') + {{-- Foto Profil Upload --}} +
    + + +

    JPG, PNG, GIF. Maks 2MB.

    + @error('foto')

    {{ $message }}

    @enderror
    +
    + {{-- Nama --}} +
    + + + @error('name') +

    {{ $message }}

    + @enderror +
    + {{-- Email --}} -
    - +
    + + class="input-focus w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-0"> @error('email') -

    {{ $message }}

    +

    {{ $message }}

    @enderror
    {{-- NIS --}}
    - + + class="input-focus w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-0" placeholder="12345678"> @error('nis') -

    {{ $message }}

    +

    {{ $message }}

    @enderror
    {{-- Kelompok Asal --}}
    - + @error('kelompok_asal') -

    {{ $message }}

    +

    {{ $message }}

    @enderror
    - - -
    + +
    - {{-- ========== UBAH PASSWORD ========== --}} -
    -

    šŸ” Ubah Password

    -

    Pastikan akun Anda menggunakan password yang kuat dan aman.

    + {{-- ========== UBAH PASSWORD ========== --}} +
    +

    šŸ” Ubah Password

    +

    Gunakan password yang kuat dan aman.

    - @if (session('status') === 'password-updated') -
    - āœ… Password berhasil diubah! -
    - @endif + @if (session('status') === 'password-updated') +
    + āœ… Password berhasil diubah! +
    + @endif -
    - @csrf - @method('put') + + @csrf + @method('put') -
    +
    - +
    - + class="input-focus w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-0" style="padding-right: 30px;"> +
    @error('current_password', 'updatePassword') -

    {{ $message }}

    +

    {{ $message }}

    @enderror
    - +
    - + class="input-focus w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-0" style="padding-right: 30px;"> +
    @error('password', 'updatePassword') -

    {{ $message }}

    +

    {{ $message }}

    @enderror
    -
    - +
    +
    - + class="input-focus w-full border border-gray-300 rounded px-2 py-1 text-xs focus:ring-0" style="padding-right: 30px;"> +
    @error('password_confirmation', 'updatePassword') -

    {{ $message }}

    +

    {{ $message }}

    @enderror
    -
    +
    - - + +
    diff --git a/resources/views/rekomendasi/hasil.blade.php b/resources/views/rekomendasi/hasil.blade.php index 28e47e2..6fe963a 100644 --- a/resources/views/rekomendasi/hasil.blade.php +++ b/resources/views/rekomendasi/hasil.blade.php @@ -7,19 +7,19 @@ @@ -40,33 +40,42 @@ -
    - -
    -

    Hasil Analisis

    -

    - Berikut adalah hasil analisis sistem terhadap profil Anda. Jurusan diurutkan berdasarkan skor kesesuaian dari yang tertinggi. -

    +
    + +
    +
    +
    šŸŽ‰
    +
    +

    Hasil Analisis Siap!

    +

    + Sistem AI kami telah menganalisis profil kamu secara menyeluruh. Berikut adalah 9 program studi yang kami rekomendasikan, diurutkan dari yang paling sesuai dengan potensi dan minat kamu. Setiap jurusan memiliki skor kesesuaian yang menunjukkan tingkat kecocokan dengan profil kamu. +

    +
    +

    šŸ’” Tip: Cek beberapa program teratas dan diskusikan dengan konselor atau orang tua untuk memastikan pilihan terbaik untuk masa depanmu

    +
    +
    +
    -
    -

    Data Profil Anda

    -
    -
    -

    Nilai Akademik

    -

    {{ $katNilai }}

    -

    Rata-rata: {{ number_format($average, 1) }}

    +
    +

    šŸ“Š Profil Analisismu

    +
    +
    +

    šŸ“š Nilai Akademik

    +

    {{ number_format($average, 1) }}

    +

    Rata-rata Rapor

    -
    -

    Preferensi Studi

    -

    {{ $prefStudi ?? '-' }}

    +
    +

    šŸŽÆ Preferensi Studi

    +

    {{ $prefStudi ?? '-' }}

    +

    Gaya Belajar

    -
    -

    Prestasi

    -

    +

    +

    šŸ† Prestasi

    +

    @if(!($isPrestasiFilled ?? true)) - Tidak Dihitung + Belum Ada @elseif($prestasiScore >= 0.8) Tinggi @elseif($prestasiScore >= 0.6) @@ -77,18 +86,22 @@ Belum Ada @endif

    +

    Pencapaian

    -
    -

    Skor Nilai

    -

    {{ number_format($average, 1) }}%

    +
    +

    šŸ’Æ Skor Nilai

    +

    {{ number_format($average, 1) }}%

    +

    Nilai Rata-rata

    -
    +

    Minat

    {{ $minatMapped ?? '-' }}

    +

    Input: {{ ucfirst($minatRaw ?? '-') }}

    Cita-cita

    {{ $citaMapped ?? '-' }}

    +

    Input: {{ ucfirst($citaRaw ?? '-') }}

    @@ -175,7 +188,7 @@
    -

    Nilai Akademik — P(nilai|H) × w=0.40

    +

    Nilai Akademik — P(nilai|H) × w=0.156

    {{ number_format(($detail['nilai'] ?? 0) * 100, 1) }}%
    @@ -185,7 +198,7 @@
    -

    Minat & Bakat — P(minat|H) × w=0.35

    +

    Minat & Bakat — P(minat|H) × w=0.456

    {{ number_format(($detail['minat'] ?? 0) * 100, 1) }}%
    @@ -195,7 +208,7 @@
    -

    Preferensi Studi — P(pref|H) × w=0.15

    +

    Preferensi Studi — P(pref|H) × w=0.256

    {{ number_format(($detail['pref'] ?? 0) * 100, 1) }}%
    @@ -205,7 +218,7 @@
    -

    Cita-cita — P(cita|H) × w=0.05

    +

    Cita-cita — P(cita|H) × w=0.090

    {{ number_format(($detail['cita'] ?? 0) * 100, 1) }}% @@ -218,7 +231,7 @@

    - Prestasi — P(prestasi|H) × w={{ ($isPrestasiFilled ?? true) ? '0.05' : '0.00' }} + Prestasi — P(prestasi|H) × w={{ ($isPrestasiFilled ?? true) ? '0.040' : '0.00' }}

    @if(!($isPrestasiFilled ?? true)) @@ -326,7 +339,7 @@

    Scoring per Kriteria:

    - šŸ“Š Nilai (40%) + šŸ“Š Nilai (15.6%) {{ number_format(($rec['detail']['nilai'] ?? 0) * 100, 1) }}%
    @@ -334,7 +347,7 @@
    - ā¤ļø Minat (35%) + ā¤ļø Minat (45.6%) {{ number_format(($rec['detail']['minat'] ?? 0) * 100, 1) }}%
    @@ -342,7 +355,7 @@
    - šŸŽ“ Preferensi (15%) + šŸŽ“ Preferensi (25.6%) {{ number_format(($rec['detail']['pref'] ?? 0) * 100, 1) }}%
    @@ -425,7 +438,7 @@ Ingin tahu mengapa jurusan {{ $hasilAkhir[0]['jurusan'] ?? '' }} direkomendasikan? Konsultasikan dengan AI Konselor BK Virtual untuk penjelasan detail berdasarkan profil Anda.

    - + šŸ’¬ Tanya AI: "Mengapa jurusan ini cocok untukku?"
    @@ -445,7 +458,7 @@

    - Metode: Sistem menggunakan algoritma Weighted Naive Bayes dengan 4 fitur berbobot: Nilai Akademik (w=0.40), Minat (w=0.35), Cita-cita (w=0.15), Prestasi (w=0.10). Jika prestasi tidak diisi, atribut prestasi tidak dihitung (w=0.00) dan bobot atribut lain dinormalisasi. Rumus: P(H|X) ∝ P(H) × ∏ P(Xi|H)wi, kemudian dinormalisasi menggunakan softmax. + Metode: Sistem menggunakan algoritma Weighted Naive Bayes dengan 5 fitur berbobot: Nilai Akademik (w=0.156), Minat (w=0.456), Preferensi Studi (w=0.256), Cita-cita (w=0.090), Prestasi (w=0.040). Jika prestasi tidak diisi, atribut prestasi tidak dihitung (w=0.00) dan bobot atribut lain dinormalisasi. Rumus: P(H|X) ∝ P(H) × ∏ P(Xi|H)wi, kemudian dinormalisasi menggunakan softmax.

    diff --git a/resources/views/rekomendasi/input.blade.php b/resources/views/rekomendasi/input.blade.php index 0260512..9bcb3cd 100644 --- a/resources/views/rekomendasi/input.blade.php +++ b/resources/views/rekomendasi/input.blade.php @@ -7,20 +7,20 @@