feat: CRUD jurusan DB-driven, bobot_mapel per-jurusan, scoring graduated, chatbot bahasa akademik, admin panel lengkap

This commit is contained in:
KakaPatria 2026-02-26 22:38:35 +07:00
parent 42cebd2fc0
commit d602dd3353
64 changed files with 5002 additions and 433 deletions

View File

@ -0,0 +1,399 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Models\PolijeMajor;
use App\Models\Recommendation;
use App\Models\ChatHistory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
class AdminController extends Controller
{
// ============================================
// 1. DASHBOARD
// ============================================
public function dashboard()
{
$totalSiswa = User::where('role', 'siswa')->count();
$totalRekomendasi = Recommendation::count();
$totalChatHistory = ChatHistory::count();
$totalJurusan = PolijeMajor::count();
$recentStudents = User::where('role', 'siswa')
->orderBy('created_at', 'desc')
->take(5)
->get();
$recentRecommendations = Recommendation::with('user')
->orderBy('created_at', 'desc')
->take(5)
->get();
$kelompokStats = User::where('role', 'siswa')
->selectRaw('kelompok_asal, COUNT(*) as count')
->groupBy('kelompok_asal')
->get();
$topMajors = Recommendation::selectRaw("
JSON_EXTRACT(hasil_rekomendasi, '$[0].jurusan') as major_name,
COUNT(*) as count
")
->groupByRaw("JSON_EXTRACT(hasil_rekomendasi, '$[0].jurusan')")
->orderBy('count', 'desc')
->take(5)
->get();
return view('admin.dashboard', compact(
'totalSiswa',
'totalRekomendasi',
'totalChatHistory',
'totalJurusan',
'recentStudents',
'recentRecommendations',
'kelompokStats',
'topMajors'
));
}
// ============================================
// 2. MANAJEMEN DATA SISWA
// ============================================
public function students(Request $request)
{
$query = User::where('role', 'siswa')
->withCount('recommendations', 'chatHistories');
if ($request->filled('search')) {
$search = $request->search;
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('nis', 'like', "%{$search}%");
});
}
if ($request->filled('kelompok')) {
$query->where('kelompok_asal', $request->kelompok);
}
$students = $query->orderBy('created_at', 'desc')->paginate(20);
return view('admin.students.index', compact('students'));
}
public function studentDetail($id)
{
$student = User::findOrFail($id);
$recommendations = Recommendation::where('user_id', $id)
->orderBy('created_at', 'desc')
->get();
$chatHistories = ChatHistory::where('user_id', $id)
->orderBy('created_at', 'desc')
->get();
return view('admin.students.detail', compact('student', 'recommendations', 'chatHistories'));
}
public function chatHistory($id)
{
$user = User::findOrFail($id);
$chatHistories = ChatHistory::where('user_id', $id)
->orderBy('created_at', 'asc')
->get();
return view('admin.chat-history', compact('user', 'chatHistories'));
}
// ============================================
// 3. MANAJEMEN JURUSAN (CRUD dari database)
// ============================================
public function jurusan()
{
$jurusanList = PolijeMajor::orderBy('nama_jurusan')->get();
return view('admin.jurusan.index', compact('jurusanList'));
}
public function jurusanCreate()
{
return view('admin.jurusan.create');
}
public function jurusanStore(Request $request)
{
$request->validate([
'nama_jurusan' => 'required|string|max:255|unique:polije_majors,nama_jurusan',
'deskripsi' => 'nullable|string|max:1000',
'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',
]);
PolijeMajor::create([
'nama_jurusan' => $request->nama_jurusan,
'deskripsi' => $request->deskripsi,
'keywords' => $this->parseTagInput($request->keywords),
'preferensi_studi' => $this->parseTagInput($request->preferensi_studi),
'prospek_kerja' => $request->prospek_kerja,
'bobot_mapel' => $this->parseBobotMapel($request),
]);
return redirect()->route('admin.jurusan')->with('success', 'Jurusan berhasil ditambahkan!');
}
public function jurusanEdit($id)
{
$jurusan = PolijeMajor::findOrFail($id);
return view('admin.jurusan.edit', compact('jurusan'));
}
public function jurusanUpdate(Request $request, $id)
{
$jurusan = PolijeMajor::findOrFail($id);
$request->validate([
'nama_jurusan' => ['required', 'string', 'max:255', Rule::unique('polije_majors', 'nama_jurusan')->ignore($jurusan->id)],
'deskripsi' => 'nullable|string|max:1000',
'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',
]);
$jurusan->update([
'nama_jurusan' => $request->nama_jurusan,
'deskripsi' => $request->deskripsi,
'keywords' => $this->parseTagInput($request->keywords),
'preferensi_studi' => $this->parseTagInput($request->preferensi_studi),
'prospek_kerja' => $request->prospek_kerja,
'bobot_mapel' => $this->parseBobotMapel($request),
]);
return redirect()->route('admin.jurusan')->with('success', 'Jurusan berhasil diperbarui!');
}
public function jurusanDestroy($id)
{
$jurusan = PolijeMajor::findOrFail($id);
$jurusan->delete();
return redirect()->route('admin.jurusan')->with('success', 'Jurusan berhasil dihapus!');
}
/**
* Parse comma-separated tag input into array
*/
private function parseTagInput(?string $input): array
{
if (empty($input)) {
return [];
}
return array_values(array_filter(array_map('trim', explode(',', $input))));
}
/**
* Parse bobot mapel from request into structured 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);
}
}
return $bobot;
}
// ============================================
// 4. MANAJEMEN AKUN GURU BK
// ============================================
public function guruBK()
{
$guruBK = User::where('role', 'bk')->orderBy('created_at', 'desc')->paginate(20);
return view('admin.guru-bk.index', compact('guruBK'));
}
public function guruBKCreate()
{
return view('admin.guru-bk.create');
}
public function guruBKStore(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
'password' => 'required|string|min:8|confirmed',
]);
User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'role' => 'bk',
]);
return redirect()->route('admin.guru-bk')->with('success', 'Akun Guru BK berhasil dibuat!');
}
public function guruBKEdit($id)
{
$guruBK = User::where('role', 'bk')->findOrFail($id);
return view('admin.guru-bk.edit', compact('guruBK'));
}
public function guruBKUpdate(Request $request, $id)
{
$guruBK = User::where('role', 'bk')->findOrFail($id);
$request->validate([
'name' => 'required|string|max:255',
'email' => ['required', 'email', Rule::unique('users')->ignore($guruBK->id)],
'password' => 'nullable|string|min:8|confirmed',
]);
$guruBK->name = $request->name;
$guruBK->email = $request->email;
if ($request->filled('password')) {
$guruBK->password = Hash::make($request->password);
}
$guruBK->save();
return redirect()->route('admin.guru-bk')->with('success', 'Akun Guru BK berhasil diperbarui!');
}
public function guruBKDestroy($id)
{
$guruBK = User::where('role', 'bk')->findOrFail($id);
$guruBK->delete();
return redirect()->route('admin.guru-bk')->with('success', 'Akun Guru BK berhasil dihapus!');
}
// ============================================
// 5. RIWAYAT REKOMENDASI SISWA
// ============================================
public function riwayatRekomendasi(Request $request)
{
$query = Recommendation::with('user');
if ($request->filled('search')) {
$search = $request->search;
$query->whereHas('user', function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%");
});
}
$recommendations = $query->orderBy('created_at', 'desc')->paginate(20);
$uniqueStudents = Recommendation::distinct('user_id')->count('user_id');
// Find top major
$topMajorRow = Recommendation::selectRaw("
JSON_EXTRACT(hasil_rekomendasi, '$[0].jurusan') as major_name,
COUNT(*) as count
")
->groupByRaw("JSON_EXTRACT(hasil_rekomendasi, '$[0].jurusan')")
->orderBy('count', 'desc')
->first();
$topMajor = $topMajorRow ? trim($topMajorRow->major_name, '"') : null;
return view('admin.riwayat-rekomendasi.index', compact('recommendations', 'uniqueStudents', 'topMajor'));
}
// ============================================
// 6. RIWAYAT KONSULTASI CHATBOT
// ============================================
public function riwayatChatbot(Request $request)
{
$query = ChatHistory::with('user');
if ($request->filled('search')) {
$search = $request->search;
$query->where(function ($q) use ($search) {
$q->where('prompt', 'like', "%{$search}%")
->orWhere('response', 'like', "%{$search}%")
->orWhereHas('user', function ($q2) use ($search) {
$q2->where('name', 'like', "%{$search}%");
});
});
}
$chatHistories = $query->orderBy('created_at', 'desc')->paginate(20);
$uniqueStudents = ChatHistory::distinct('user_id')->count('user_id');
$todayCount = ChatHistory::whereDate('created_at', today())->count();
return view('admin.riwayat-chatbot.index', compact('chatHistories', 'uniqueStudents', 'todayCount'));
}
// ============================================
// 7. PROFIL ADMIN
// ============================================
public function profil()
{
$admin = Auth::user();
return view('admin.profil.index', compact('admin'));
}
public function updateProfil(Request $request)
{
$admin = Auth::user();
$request->validate([
'name' => 'required|string|max:255',
'email' => ['required', 'email', Rule::unique('users')->ignore($admin->id)],
]);
$admin->name = $request->name;
$admin->email = $request->email;
$admin->save();
return redirect()->route('admin.profil')->with('success', 'Profil berhasil diperbarui!');
}
public function updatePassword(Request $request)
{
$request->validate([
'current_password' => 'required',
'password' => 'required|string|min:8|confirmed',
]);
$admin = Auth::user();
if (!Hash::check($request->current_password, $admin->password)) {
return back()->withErrors(['current_password' => 'Password lama salah.']);
}
$admin->password = Hash::make($request->password);
$admin->save();
return redirect()->route('admin.profil')->with('success', 'Password berhasil diubah!');
}
}

View File

@ -0,0 +1,187 @@
<?php
namespace App\Http\Controllers;
use App\Models\Alumni;
use Illuminate\Http\Request;
class AlumniController extends Controller
{
/**
* Display alumni data list
*/
public function index()
{
$alumni = Alumni::orderBy('tahun_masuk', 'desc')->paginate(20);
$summary = $this->getAlumniSummary();
return view('alumni.index', compact('alumni', 'summary'));
}
/**
* Show form to input/create new alumni
*/
public function create()
{
return view('admin.alumni.create');
}
/**
* Store new alumni data
*/
public function store(Request $request)
{
$validated = $request->validate([
'nama_alumni' => 'required|string|max:255',
'nis' => 'nullable|string|max:20',
'kelompok_asal' => 'required|in:IPA,IPS',
// Nilai
'mtk' => 'nullable|numeric|min:0|max:100',
'fisika' => 'nullable|numeric|min:0|max:100',
'kimia' => 'nullable|numeric|min:0|max:100',
'biologi' => 'nullable|numeric|min:0|max:100',
'ekonomi' => 'nullable|numeric|min:0|max:100',
'geografi' => 'nullable|numeric|min:0|max:100',
'sosiologi' => 'nullable|numeric|min:0|max:100',
'sejarah' => 'nullable|numeric|min:0|max:100',
// Non-akademik
'minat' => 'nullable|string|max:255',
'cita_cita' => 'nullable|string|max:255',
'preferensi_studi' => 'nullable|in:Praktik_Langsung,DuDi,Project_Based,Blended',
'prestasi' => 'nullable|string|max:255',
// Major & Outcome
'major_masuk' => 'required|string|max:255',
'ranking_saat_rekomendasi' => 'nullable|integer|min:1|max:9',
'success_status' => 'nullable|in:sangat_sukses,sukses,cukup,kurang_sukses',
'catatan' => 'nullable|string|max:500',
]);
Alumni::create($validated);
return redirect()->route('admin.alumni.index')->with('success', 'Alumni berhasil ditambahkan');
}
/**
* Show alumni detail
*/
public function show(Alumni $alumnus)
{
return view('alumni.show', compact('alumnus'));
}
/**
* Show form to edit alumni
*/
public function edit(Alumni $alumni)
{
return view('admin.alumni.edit', compact('alumni'));
}
/**
* Update alumni data
*/
public function update(Request $request, Alumni $alumni)
{
$validated = $request->validate([
'nama_alumni' => 'required|string|max:255',
'nis' => 'nullable|string|max:20',
'kelompok_asal' => 'required|in:IPA,IPS',
'mtk' => 'nullable|numeric|min:0|max:100',
'fisika' => 'nullable|numeric|min:0|max:100',
'kimia' => 'nullable|numeric|min:0|max:100',
'biologi' => 'nullable|numeric|min:0|max:100',
'ekonomi' => 'nullable|numeric|min:0|max:100',
'geografi' => 'nullable|numeric|min:0|max:100',
'sosiologi' => 'nullable|numeric|min:0|max:100',
'sejarah' => 'nullable|numeric|min:0|max:100',
'minat' => 'nullable|string|max:255',
'cita_cita' => 'nullable|string|max:255',
'preferensi_studi' => 'nullable|in:Praktik_Langsung,DuDi,Project_Based,Blended',
'prestasi' => 'nullable|string|max:255',
'major_masuk' => 'required|string|max:255',
'ranking_saat_rekomendasi' => 'nullable|integer|min:1|max:9',
'success_status' => 'nullable|in:sangat_sukses,sukses,cukup,kurang_sukses',
'catatan' => 'nullable|string|max:500',
]);
$alumni->update($validated);
return redirect()->route('admin.alumni.index')->with('success', 'Alumni berhasil diupdate');
}
/**
* Delete alumni
*/
public function destroy(Alumni $alumni)
{
$alumni->delete();
return redirect()->route('admin.alumni.index')->with('success', 'Alumni berhasil dihapus');
}
/**
* Get summary analytics untuk alumni
*/
private function getAlumniSummary()
{
$totalAlumni = Alumni::count();
$byMajor = Alumni::selectRaw('major_masuk, COUNT(*) as count')
->groupBy('major_masuk')
->get();
$bySuccess = Alumni::selectRaw('success_status, COUNT(*) as count')
->groupBy('success_status')
->get();
$prediction_accuracy = $this->calculatePredictionAccuracy();
return [
'total' => $totalAlumni,
'by_major' => $byMajor,
'by_success' => $bySuccess,
'prediction_accuracy' => $prediction_accuracy,
];
}
/**
* Calculate how accurate was our algorithm prediction
* vs actual major the alumni entered
*/
private function calculatePredictionAccuracy()
{
$alumni = Alumni::whereNotNull('ranking_saat_rekomendasi')->get();
if ($alumni->isEmpty()) {
return null;
}
$correctTop1 = 0;
$correctTop3 = 0;
$correctTop5 = 0;
foreach ($alumni as $a) {
if ($a->ranking_saat_rekomendasi == 1) {
$correctTop1++;
}
if ($a->ranking_saat_rekomendasi <= 3) {
$correctTop3++;
}
if ($a->ranking_saat_rekomendasi <= 5) {
$correctTop5++;
}
}
return [
'top_1' => round(($correctTop1 / count($alumni)) * 100, 2),
'top_3' => round(($correctTop3 / count($alumni)) * 100, 2),
'top_5' => round(($correctTop5 / count($alumni)) * 100, 2),
'total_alumni_analyzed' => count($alumni),
];
}
}

View File

@ -30,117 +30,123 @@ public function index()
public function proses(Request $request)
{
// --- 1. PREPROCESSING NILAI (Kriteria 1: Akademik) ---
$scores = $request->only(['mtk', 'fisika', 'kimia', 'biologi', 'ekonomi', 'geografi', 'sosiologi', 'sejarah']);
$validScores = array_filter($scores);
$average = count($validScores) > 0 ? array_sum($validScores) / count($validScores) : 0;
// --- VALIDATION ---
// Tentukan kelompok asal siswa
$user = Auth::user();
$kelompok = $user->kelompok_asal ?? 'IPS';
// Kategorisasi Nilai berdasarkan config
$nilaiCategories = config('polije.nilai_category', []);
$katNilai = 'Rendah';
foreach ($nilaiCategories as $category => $range) {
if ($average >= $range['min'] && $average <= $range['max']) {
$katNilai = $category;
break;
}
}
// Validasi berbeda untuk IPA dan IPS
$baseRules = [
'minat' => 'required|string|max:255',
'cita_cita' => 'required|string|max:255',
'pref_studi' => 'required|in:Sains & Teknologi,Pertanian & Lingkungan,Kesehatan & Ilmu Hayat,Bisnis & Manajemen,Sosial & Humaniora',
'prestasi' => 'nullable|string|max:255',
];
// --- 2. ANALISIS MINAT (Kriteria 2) ---
$minatRaw = strtolower($request->minat ?? '');
$minatMapped = $this->mapMinat($minatRaw);
// --- 3. ANALISIS CITA-CITA (Kriteria 3) ---
$citaRaw = strtolower($request->cita_cita ?? '');
$citaMapped = $this->mapCitaCita($citaRaw);
// --- 4. PEMETAAN PREFERENSI STUDI (Kriteria 4) ---
$prefStudi = $request->pref_studi ?? 'Blended';
$prefMapping = config('polije.pref_mapping', []);
// --- 5. ANALISIS PRESTASI (Kriteria 5) ---
$prestasiRaw = strtolower($request->prestasi ?? '');
$prestasiScore = $this->scorePrestasiScore($prestasiRaw);
// --- 6. PERHITUNGAN NAIVE BAYES BERBOBOT ---
$cfg = config('polije.criteria', []);
$logPosteriors = [];
$epsilon = 1e-9;
foreach ($cfg as $jurusan => $c) {
// Prior: uniform
$prior = 1 / count($cfg);
$logPrior = log(max($prior, $epsilon));
// Weights dan match probabilities
$weights = $c['weights'] ?? ['nilai' => 0.40, 'minat' => 0.35, 'pref' => 0.15, 'prestasi' => 0.05, 'cita_cita' => 0.05];
$matchProb = $c['match_prob'] ?? ['nilai' => 0.80, 'minat' => 0.90, 'pref' => 0.85, 'prestasi' => 0.65, 'cita_cita' => 0.85];
// 1. Likelihood untuk Nilai
$p_nilai = ($katNilai == ($c['nilai'] ?? 'Sedang')) ? $matchProb['nilai'] : max(1 - $matchProb['nilai'], $epsilon);
// 2. Likelihood untuk Minat
$p_minat = ($minatMapped == ($c['minat'] ?? 'Umum')) ? $matchProb['minat'] : max(1 - $matchProb['minat'], $epsilon);
// 3. Likelihood untuk Preferensi Studi
$prefList = $c['pref'] ?? ['Praktik Langsung', 'DuDi', 'Project Based'];
if (!is_array($prefList)) {
$prefList = [$prefList];
}
$p_pref = in_array($prefStudi, $prefList) ? $matchProb['pref'] : max(1 - $matchProb['pref'], $epsilon);
// 4. Likelihood untuk Cita-cita
$citaCitaKeywords = $c['cita_cita_keywords'] ?? [];
$matchCitaCita = false;
if (!empty($citaCitaKeywords)) {
foreach ($citaCitaKeywords as $keyword) {
if (stripos($citaMapped, $keyword) !== false) {
$matchCitaCita = true;
break;
}
}
}
$p_cita_cita = $matchCitaCita ? $matchProb['cita_cita'] : max(1 - $matchProb['cita_cita'], $epsilon);
// 5. Likelihood untuk Prestasi (boost jika ada prestasi)
$p_prestasi = ($prestasiScore > 0.5) ? $matchProb['prestasi'] : max(1 - $matchProb['prestasi'], $epsilon);
// Hitung log-likelihood dengan bobot
$logLikelihood =
($weights['nilai'] ?? 0) * log(max($p_nilai, $epsilon)) +
($weights['minat'] ?? 0) * log(max($p_minat, $epsilon)) +
($weights['pref'] ?? 0) * log(max($p_pref, $epsilon)) +
($weights['cita_cita'] ?? 0) * log(max($p_cita_cita, $epsilon)) +
($weights['prestasi'] ?? 0) * log(max($p_prestasi, $epsilon));
$logPosteriors[$jurusan] = $logPrior + $logLikelihood;
}
// Convert log-posteriors ke probabilitas (softmax)
$maxLog = max($logPosteriors);
$expVals = [];
$sumExp = 0.0;
foreach ($logPosteriors as $jurusan => $lv) {
$expVals[$jurusan] = exp($lv - $maxLog);
$sumExp += $expVals[$jurusan];
}
$hasilAkhir = [];
foreach ($expVals as $jurusan => $val) {
$prob = $val / max($sumExp, $epsilon);
$hasilAkhir[] = [
'jurusan' => $jurusan,
'skor' => round($prob, 4),
'kecocokan_nilai' => $katNilai,
'kecocokan_minat' => $minatMapped,
'kecocokan_pref' => $prefStudi,
if ($kelompok === 'IPA') {
$nilaiRules = [
'mtk' => 'required|numeric|between:0,100',
'fisika' => 'required|numeric|between:0,100',
'kimia' => 'required|numeric|between:0,100',
'biologi' => 'required|numeric|between:0,100',
];
} else {
$nilaiRules = [
'ekonomi' => 'required|numeric|between:0,100',
'geografi' => 'required|numeric|between:0,100',
'sosiologi' => 'required|numeric|between:0,100',
'sejarah' => 'required|numeric|between:0,100',
];
}
// Sort hasil berdasarkan skor (tertinggi dulu)
$request->validate(array_merge($baseRules, $nilaiRules));
// --- 1. SKOR NILAI AKADEMIK (40%) - dikumpulkan dulu, dihitung per jurusan ---
if ($kelompok === 'IPA') {
$scores = $request->only(['mtk', 'fisika', 'kimia', 'biologi']);
} else {
$scores = $request->only(['ekonomi', 'geografi', 'sosiologi', 'sejarah']);
}
$validScores = array_filter($scores, fn($v) => !is_null($v) && $v !== '');
$average = count($validScores) > 0 ? array_sum($validScores) / count($validScores) : 0;
// Label nilai untuk tampilan
if ($average >= 85) {
$katNilai = 'Tinggi';
} elseif ($average >= 70) {
$katNilai = 'Sedang';
} else {
$katNilai = 'Rendah';
}
// --- 2. INPUT SISWA ---
$minatRaw = strtolower(trim($request->minat ?? ''));
$citaRaw = strtolower(trim($request->cita_cita ?? ''));
$prefStudi = $request->pref_studi ?? 'Sains & Teknologi';
$prestasiRaw = strtolower(trim($request->prestasi ?? ''));
$prestasiScore = $this->scorePrestasiScore($prestasiRaw);
// --- 3. GRADUATED SCORING PER JURUSAN ---
$jurusanList = PolijeMajor::all();
$hasilAkhir = [];
// Bobot kriteria
$W_NILAI = 0.40;
$W_MINAT = 0.35;
$W_PREF = 0.15;
$W_CITA = 0.05;
$W_PRESTASI = 0.05;
foreach ($jurusanList as $jurusan) {
$keywords = $jurusan->keywords ?? [];
$prefList = $jurusan->preferensi_studi ?? [];
$bobotMapel = $jurusan->bobot_mapel ?? [];
// --- Skor Nilai: per-jurusan weighted ---
$skorNilai = $this->hitungSkorNilaiPerJurusan($scores, $bobotMapel, $average);
// --- Skor Minat: partial keyword matching ---
$skorMinat = $this->hitungKecocokanKeyword($minatRaw, $keywords);
// --- Skor Cita-cita: partial keyword matching ---
$skorCita = $this->hitungKecocokanKeyword($citaRaw, $keywords);
// --- Skor Preferensi Studi ---
if (in_array($prefStudi, $prefList)) {
$skorPref = 1.0;
} elseif (!empty($prefList)) {
$skorPref = 0.3; // Tidak cocok tapi jurusan punya preferensi
} else {
$skorPref = 0.5; // Jurusan tidak mendefinisikan preferensi
}
// --- Skor Prestasi (sama untuk semua jurusan) ---
$skorPrestasi = $prestasiScore;
// --- Hitung skor akhir ---
$skorAkhir = ($W_NILAI * $skorNilai) +
($W_MINAT * $skorMinat) +
($W_PREF * $skorPref) +
($W_CITA * $skorCita) +
($W_PRESTASI * $skorPrestasi);
$hasilAkhir[] = [
'jurusan' => $jurusan->nama_jurusan,
'skor' => round($skorAkhir, 4),
'detail' => [
'nilai' => round($skorNilai, 4),
'minat' => round($skorMinat, 4),
'pref' => round($skorPref, 4),
'cita' => round($skorCita, 4),
'prestasi' => round($skorPrestasi, 4),
],
];
}
// Sort berdasarkan skor tertinggi
usort($hasilAkhir, fn($a, $b) => $b['skor'] <=> $a['skor']);
// Simpan data rekomendasi ke database
$user = Auth::user();
// Simpan ke database
if ($user) {
Recommendation::create([
'user_id' => $user->id,
@ -160,7 +166,7 @@ public function proses(Request $request)
]);
}
// Simpan data rekomendasi ke session untuk chatbot
// Simpan ke session untuk chatbot
if (count($hasilAkhir) > 0) {
$topResult = $hasilAkhir[0];
session([
@ -168,41 +174,91 @@ public function proses(Request $request)
'jurusan' => $topResult['jurusan'],
'skor' => $topResult['skor'],
'nilai' => $katNilai,
'minat' => $minatMapped,
'minat' => $request->minat,
'pref_studi' => $prefStudi,
]
]);
}
return view('rekomendasi.hasil', compact('hasilAkhir', 'katNilai', 'minatMapped', 'citaMapped', 'prefStudi', 'prestasiScore'));
}
/**
* Pemetaan minat ke kategori yang dipahami sistem
*/
private function mapMinat(string $minatRaw): string
{
if (preg_match('/(coding|komputer|laptop|web|aplikasi|logika|programming|software|development)/', $minatRaw)) {
return 'Logika & Komputer';
} elseif (preg_match('/(tanam|kebun|sawah|hewan|ternak|alam|pertanian|agri)/', $minatRaw)) {
return 'Alam & Tanaman';
} elseif (preg_match('/(obat|sakit|rawat|medis|gizi|sehat|kesehatan|perawat|dokter)/', $minatRaw)) {
return 'Pelayanan & Kesehatan';
} elseif (preg_match('/(bisnis|uang|jual|kantor|hitung|ekonomi|dagang|usaha|entrepreneur)/', $minatRaw)) {
return 'Manajemen & Bisnis';
} elseif (preg_match('/(mesin|bengkel|listrik|las|robot|motor|teknik|otomasi|elektronik)/', $minatRaw)) {
return 'Mesin & Listrik';
// Load top jurusan from DB for deskripsi & prospek_kerja
$topJurusan = null;
if (count($hasilAkhir) > 0) {
$topJurusan = PolijeMajor::where('nama_jurusan', $hasilAkhir[0]['jurusan'])->first();
}
return 'Umum';
return view('rekomendasi.hasil', compact('hasilAkhir', 'katNilai', 'average', 'prefStudi', 'prestasiScore', 'topJurusan'));
}
/**
* Pemetaan cita-cita ke kategori jurusan
* Hitung skor nilai akademik per jurusan dengan bobot mapel
* Jika jurusan punya bobot_mapel, hitung weighted average
* Jika tidak, gunakan rata-rata biasa
*/
private function mapCitaCita(string $citaRaw): string
private function hitungSkorNilaiPerJurusan(array $scores, array $bobotMapel, float $averageFallback): float
{
// Return raw mapped text untuk matching dengan keywords
return $citaRaw;
// Jika tidak ada bobot khusus, pakai rata-rata biasa
if (empty($bobotMapel)) {
return min($averageFallback / 100, 1.0);
}
$weightedSum = 0;
$totalWeight = 0;
foreach ($bobotMapel as $mapel => $bobot) {
$nilai = floatval($scores[$mapel] ?? 0);
$weightedSum += $nilai * $bobot;
$totalWeight += $bobot;
}
// Untuk mapel yang ada di scores tapi tidak di bobot, beri bobot kecil
foreach ($scores as $mapel => $nilai) {
if (!isset($bobotMapel[$mapel]) && !is_null($nilai) && $nilai !== '') {
$weightedSum += floatval($nilai) * 0.1;
$totalWeight += 0.1;
}
}
if ($totalWeight <= 0) {
return min($averageFallback / 100, 1.0);
}
$weightedAvg = $weightedSum / $totalWeight;
return min($weightedAvg / 100, 1.0);
}
/**
* Hitung kecocokan teks input dengan array keywords jurusan (graduated)
* Returns 0.0 - 1.0
*/
private function hitungKecocokanKeyword(string $inputText, array $keywords): float
{
if (empty($keywords) || empty($inputText)) {
return 0.0;
}
$matchCount = 0;
$inputWords = preg_split('/[\s,;.\/\-]+/', $inputText);
foreach ($keywords as $keyword) {
$kw = strtolower(trim($keyword));
if (empty($kw)) continue;
// Check if keyword appears in any input word (partial match)
foreach ($inputWords as $word) {
if (empty($word)) continue;
// Match if input word contains keyword or keyword contains input word (min 3 chars)
if (stripos($inputText, $kw) !== false ||
(strlen($word) >= 3 && stripos($kw, $word) !== false)) {
$matchCount++;
break;
}
}
}
// Graduated score: ratio of matched keywords
// Use sqrt to give more credit for partial matches
$ratio = $matchCount / count($keywords);
return min(sqrt($ratio) * 0.9 + ($matchCount > 0 ? 0.1 : 0), 1.0);
}
/**

View File

@ -63,5 +63,8 @@ class Kernel extends HttpKernel
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'isAdmin' => \App\Http\Middleware\IsAdmin::class,
'isBK' => \App\Http\Middleware\IsBK::class,
'roleRedirect' => \App\Http\Middleware\RedirectBasedOnRole::class,
];
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class IsAdmin
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (auth()->check() && auth()->user()->role === 'admin') {
return $next($request);
}
return redirect('/dashboard')->with('error', 'Anda tidak memiliki akses ke panel admin.');
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class IsBK
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (auth()->check() && auth()->user()->role === 'bk') {
return $next($request);
}
return redirect('/dashboard')->with('error', 'Anda tidak memiliki akses ke panel BK.');
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class RedirectBasedOnRole
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// Redirect ke dashboard berdasarkan role setelah login
if (auth()->check()) {
$user = auth()->user();
// Jika sudah di /dashboard, redirect ke panel yang sesuai
if ($request->path() === 'dashboard') {
if ($user->role === 'admin') {
return redirect('/admin/dashboard');
} elseif ($user->role === 'bk') {
return redirect('/bk/dashboard');
}
// siswa tetap di /dashboard
}
}
return $next($request);
}
}

77
app/Models/Alumni.php Normal file
View File

@ -0,0 +1,77 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Alumni extends Model
{
use HasFactory;
protected $table = 'alumni';
protected $fillable = [
'nama_alumni',
'nis',
'kelompok_asal',
'mtk',
'fisika',
'kimia',
'biologi',
'ekonomi',
'geografi',
'sosiologi',
'sejarah',
'minat',
'cita_cita',
'preferensi_studi',
'prestasi',
'major_masuk',
'ranking_saat_rekomendasi',
'success_status',
'catatan',
];
protected $casts = [
'mtk' => 'float',
'fisika' => 'float',
'kimia' => 'float',
'biologi' => 'float',
'ekonomi' => 'float',
'geografi' => 'float',
'sosiologi' => 'float',
'sejarah' => 'float',
'nilai_rata_rata' => 'float',
'ipk_lulus' => 'float',
'predicted_score' => '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;
});
}
}

View File

@ -9,6 +9,18 @@ class PolijeMajor extends Model
{
use HasFactory;
// Tambahkan baris ini agar data bisa masuk
protected $fillable = ['nama_jurusan', 'deskripsi', 'prospek_kerja'];
protected $fillable = [
'nama_jurusan',
'deskripsi',
'keywords',
'preferensi_studi',
'bobot_mapel',
'prospek_kerja',
];
protected $casts = [
'keywords' => 'array',
'preferensi_studi' => 'array',
'bobot_mapel' => 'array',
];
}

View File

@ -4,6 +4,7 @@
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use App\Models\PolijeMajor;
class GeminiService
{
@ -124,11 +125,11 @@ protected function getFallbackResponse($message, $context = [])
$messageLower = strtolower($message);
if (strpos($messageLower, 'halo') !== false || strpos($messageLower, 'hai') !== false || strpos($messageLower, 'hallo') !== false || strpos($messageLower, 'hi') !== false) {
$greeting = "Halo! 👋 Saya konselor BK virtual SMA Bima Ambulu. Saya siap membantu kamu soal pemilihan jurusan kuliah. ";
$greeting = "Selamat datang. Saya adalah konselor BK virtual SMA Bima Ambulu yang siap membantu Anda dalam pemilihan jurusan kuliah. ";
if ($hasRecommendation) {
$greeting .= "Saya lihat kamu sudah dapat rekomendasi jurusan \"{$jurusan}\" dengan skor {$score}%. Mau bahas lebih lanjut tentang jurusan itu, atau ada pertanyaan lain?";
$greeting .= "Berdasarkan data yang tersedia, Anda telah memperoleh rekomendasi jurusan \"{$jurusan}\" dengan skor kesesuaian {$score}%. Apakah Anda ingin membahas lebih lanjut mengenai jurusan tersebut, atau ada pertanyaan lain yang ingin disampaikan?";
} else {
$greeting .= "Kamu bisa tanya apa saja tentang jurusan kuliah, prospek karir, atau tips memilih jurusan yang tepat. Yuk, mulai!";
$greeting .= "Anda dapat mengajukan pertanyaan seputar jurusan kuliah, prospek karier, maupun panduan dalam memilih jurusan yang tepat. Silakan sampaikan pertanyaan Anda.";
}
return ['success' => true, 'message' => $greeting];
}
@ -137,12 +138,12 @@ protected function getFallbackResponse($message, $context = [])
if ($hasRecommendation) {
return [
'success' => true,
'message' => "Jurusan \"{$jurusan}\" direkomendasikan berdasarkan analisis profil akademik, minat, dan preferensi belajar kamu. Skor kesesuaian {$score}% menunjukkan tingkat kecocokan yang baik antara profil kamu dengan jurusan tersebut. Sistem menghitung ini dari 5 faktor: nilai akademik, minat, preferensi pembelajaran, prestasi, dan cita-cita."
'message' => "Jurusan \"{$jurusan}\" direkomendasikan berdasarkan analisis komprehensif terhadap profil akademik, minat, serta preferensi studi Anda. Skor kesesuaian sebesar {$score}% menunjukkan tingkat kecocokan yang signifikan antara profil Anda dengan karakteristik jurusan tersebut. Sistem menghitung skor ini berdasarkan lima faktor utama, yaitu: nilai akademik, minat dan bakat, preferensi studi lanjutan, prestasi, dan cita-cita."
];
}
return [
'success' => true,
'message' => "Untuk menjawab pertanyaan \"mengapa\", sebaiknya kamu lakukan analisis rekomendasi dulu ya. Dari situ, sistem akan mencocokkan profil kamu dengan 9 jurusan yang tersedia. Kamu bisa klik menu 'Analisis Rekomendasi' di dashboard."
'message' => "Untuk dapat menjawab pertanyaan tersebut secara akurat, disarankan agar Anda terlebih dahulu melakukan analisis rekomendasi. Melalui proses tersebut, sistem akan mencocokkan profil Anda dengan sembilan jurusan yang tersedia di Polije. Silakan akses menu Analisis Rekomendasi pada halaman dashboard."
];
}
@ -150,19 +151,19 @@ protected function getFallbackResponse($message, $context = [])
if ($hasRecommendation) {
return [
'success' => true,
'message' => "Jurusan \"{$jurusan}\" memiliki prospek karir yang baik. Lulusan dari jurusan ini bisa bekerja di berbagai sektor industri yang relevan. Setiap jurusan di perguruan tinggi menyiapkan lulusannya dengan keahlian praktis yang dibutuhkan dunia kerja. Mau tau lebih detail tentang posisi kerja spesifik?"
'message' => "Jurusan \"{$jurusan}\" memiliki prospek karier yang menjanjikan. Lulusan dari jurusan ini dapat bekerja di berbagai sektor industri yang relevan dengan bidang keahliannya. Setiap program studi di perguruan tinggi dirancang untuk membekali lulusannya dengan kompetensi praktis yang dibutuhkan oleh dunia kerja. Apakah Anda ingin mengetahui lebih detail mengenai posisi pekerjaan spesifik yang dapat ditempuh?"
];
}
return [
'success' => true,
'message' => "Setiap jurusan punya prospek karir yang berbeda-beda. Misalnya, Teknologi Informasi bisa jadi programmer/developer, Kesehatan bisa jadi tenaga medis, Bisnis bisa jadi manajer/entrepreneur. Jurusan mana yang kamu tertarik? Saya bisa jelaskan lebih detail."
'message' => "Setiap jurusan memiliki prospek karier yang berbeda. Sebagai contoh, lulusan Teknologi Informasi dapat berkarier sebagai programmer atau developer, lulusan Kesehatan dapat menjadi tenaga medis profesional, dan lulusan Bisnis dapat menempuh karier di bidang manajerial atau kewirausahaan. Jurusan mana yang ingin Anda ketahui lebih lanjut? Saya akan memberikan informasi yang lebih terperinci."
];
}
if (strpos($messageLower, 'bingung') !== false || strpos($messageLower, 'galau') !== false || strpos($messageLower, 'tidak tahu') !== false || strpos($messageLower, 'gak tau') !== false) {
return [
'success' => true,
'message' => "Wajar kok kalau masih bingung! 😊 Coba jawab pertanyaan ini: 1) Mata pelajaran apa yang paling kamu suka? 2) Kegiatan apa yang bikin kamu semangat? 3) Cita-cita kamu apa? Dari situ kita bisa mulai mencari jurusan yang cocok. Atau kamu juga bisa coba fitur 'Analisis Rekomendasi' di dashboard untuk mendapat rekomendasi otomatis."
'message' => "Perasaan bingung dalam memilih jurusan adalah hal yang wajar dan dialami oleh banyak siswa. Untuk membantu memperjelas arah pilihan Anda, cobalah menjawab beberapa pertanyaan berikut: (1) Mata pelajaran apa yang paling Anda kuasai atau minati? (2) Kegiatan apa yang membuat Anda bersemangat? (3) Apa cita-cita atau tujuan karier Anda? Dari jawaban tersebut, kita dapat mulai mengidentifikasi jurusan yang sesuai. Selain itu, Anda juga dapat memanfaatkan fitur Analisis Rekomendasi di halaman dashboard untuk mendapatkan rekomendasi secara otomatis."
];
}
@ -170,19 +171,19 @@ protected function getFallbackResponse($message, $context = [])
if ($hasRecommendation) {
return [
'success' => true,
'message' => "Untuk sukses di jurusan \"{$jurusan}\", kamu perlu mengembangkan berbagai skill teknis dan non-teknis. Skill teknis tergantung bidang jurusannya, sedangkan skill umum seperti komunikasi, kerja tim, dan problem solving selalu dibutuhkan di semua jurusan. Mau tau skill spesifik yang perlu disiapkan?"
'message' => "Untuk berhasil di jurusan \"{$jurusan}\", Anda perlu mengembangkan berbagai kompetensi, baik teknis maupun non-teknis. Kompetensi teknis akan bergantung pada spesifikasi bidang jurusan yang dipilih, sedangkan kompetensi umum seperti kemampuan komunikasi, kerja sama tim, dan pemecahan masalah sangat dibutuhkan di semua bidang studi. Apakah Anda ingin mengetahui kompetensi spesifik yang perlu dipersiapkan?"
];
}
return [
'success' => true,
'message' => "Setiap jurusan butuh skill yang berbeda. Misalnya: TI butuh logika & coding, Kesehatan butuh ketelitian & empati, Bisnis butuh komunikasi & manajemen. Yang pasti, semua jurusan butuh kemampuan belajar mandiri dan kerja tim. Jurusan mana yang ingin kamu ketahui skill-nya?"
'message' => "Setiap jurusan memerlukan kompetensi yang berbeda. Sebagai contoh, Teknologi Informasi membutuhkan kemampuan logika dan pemrograman, Kesehatan membutuhkan ketelitian dan empati, sedangkan Bisnis memerlukan kemampuan komunikasi dan manajerial. Secara umum, semua jurusan membutuhkan kemampuan belajar mandiri dan kerja sama tim. Jurusan mana yang ingin Anda ketahui kompetensinya secara lebih mendalam?"
];
}
if (strpos($messageLower, 'ipa') !== false || strpos($messageLower, 'ips') !== false) {
return [
'success' => true,
'message' => "Kelompok IPA dan IPS bukan batasan mutlak untuk memilih jurusan kuliah ya. Banyak jurusan yang bisa dimasuki oleh keduanya. Yang penting adalah minat dan kemampuan kamu. Anak IPA bisa masuk bisnis, anak IPS bisa masuk TI. Lakukan analisis rekomendasi untuk melihat jurusan mana yang paling cocok berdasarkan profil lengkap kamu."
'message' => "Perlu dipahami bahwa kelompok IPA dan IPS bukan merupakan batasan mutlak dalam memilih jurusan kuliah. Banyak program studi yang dapat dimasuki oleh siswa dari kedua kelompok tersebut. Faktor yang lebih menentukan adalah minat, kemampuan, dan kompetensi yang Anda miliki. Siswa IPA dapat memilih bidang bisnis, dan sebaliknya siswa IPS dapat menempuh bidang teknologi informasi. Silakan manfaatkan fitur Analisis Rekomendasi untuk melihat jurusan yang paling sesuai berdasarkan profil lengkap Anda."
];
}
@ -190,23 +191,24 @@ protected function getFallbackResponse($message, $context = [])
if ($hasRecommendation) {
return [
'success' => true,
'message' => "Saya konselor BK virtual SMA Bima Ambulu. Berdasarkan analisis, jurusan \"{$jurusan}\" cocok untuk kamu dengan skor {$score}%. Kamu bisa tanya tentang: prospek karir, skill yang dibutuhkan, perbandingan jurusan, atau apapun tentang persiapan kuliah. Saya siap membantu! 😊"
'message' => "Saya adalah konselor BK virtual SMA Bima Ambulu. Berdasarkan hasil analisis, jurusan \"{$jurusan}\" memiliki kesesuaian tertinggi dengan profil Anda, yaitu sebesar {$score}%. Anda dapat berkonsultasi mengenai prospek karier, kompetensi yang dibutuhkan, perbandingan antar jurusan, atau hal lain terkait persiapan pendidikan tinggi. Silakan sampaikan pertanyaan Anda."
];
}
return [
'success' => true,
'message' => "Saya konselor BK virtual SMA Bima Ambulu, siap membantu kamu memilih jurusan kuliah! 😊 Kamu bisa bertanya tentang: jurusan apa yang cocok, prospek karir, skill yang dibutuhkan, atau tips memilih jurusan. Untuk rekomendasi personal, coba fitur 'Analisis Rekomendasi' di dashboard ya."
'message' => "Saya adalah konselor BK virtual SMA Bima Ambulu, siap membantu Anda dalam proses pemilihan jurusan kuliah. Anda dapat bertanya seputar kesesuaian jurusan, prospek karier, kompetensi yang dibutuhkan, maupun panduan dalam menentukan pilihan studi. Untuk mendapatkan rekomendasi yang dipersonalisasi, silakan gunakan fitur Analisis Rekomendasi di halaman dashboard."
];
}
protected function buildSystemPrompt($context)
{
$prompt = "Kamu adalah Pak/Bu Konselor BK (Bimbingan Konseling) di SMA Bima Ambulu. ";
$prompt .= "Kamu adalah guru BK yang HIDUP — bukan robot. ";
$prompt = "Kamu adalah Konselor Bimbingan Konseling (BK) di SMA Bima Ambulu. ";
$prompt .= "Kamu adalah konselor profesional yang memberikan bimbingan secara personal kepada siswa. ";
$prompt .= "Kamu MENGARAHKAN siswa, memberikan ANALISIS LOGIS, dan MEYAKINKAN mereka dengan alasan yang masuk akal. ";
$prompt .= "Kamu juga bisa menjawab pertanyaan umum di luar topik jurusan (seperti pengetahuan umum, tokoh, dll) secara singkat, lalu arahkan kembali ke topik konseling. ";
$prompt .= "Gunakan bahasa Indonesia santai, hangat, tapi tetap berbobot — seperti guru BK favorit yang ngobrol dengan muridnya. ";
$prompt .= "Gunakan bahasa Indonesia yang FORMAL, AKADEMIK, dan SOPAN — seperti seorang konselor profesional berbicara dengan siswa. ";
$prompt .= "DILARANG menggunakan bahasa gaul, slang, atau terlalu santai. Gunakan kalimat yang baku dan terstruktur. ";
// Tambahkan konteks rekomendasi jika ada
if (!empty($context['recommendation'])) {
@ -237,10 +239,21 @@ protected function buildSystemPrompt($context)
}
}
$jurusan = config('polije.criteria', []);
if (!empty($jurusan)) {
$namaJurusan = array_keys($jurusan);
$prompt .= "\n\n9 Jurusan tersedia: " . implode(', ', $namaJurusan) . ". ";
$jurusanList = PolijeMajor::all();
if ($jurusanList->isNotEmpty()) {
$prompt .= "\n\nDAFTAR JURUSAN POLIJE ({$jurusanList->count()} jurusan):";
foreach ($jurusanList as $j) {
$prompt .= "\n- {$j->nama_jurusan}";
if (!empty($j->deskripsi)) {
$prompt .= ": {$j->deskripsi}";
}
if (!empty($j->prospek_kerja)) {
$prompt .= " Prospek kerja: {$j->prospek_kerja}.";
}
if (!empty($j->keywords) && is_array($j->keywords)) {
$prompt .= " Kata kunci: " . implode(', ', array_slice($j->keywords, 0, 10)) . ".";
}
}
}
$prompt .= "\n\nCara kamu merespons:";
@ -252,6 +265,8 @@ protected function buildSystemPrompt($context)
$prompt .= "\n6. Jawab RINGKAS (2-3 paragraf). Jangan terlalu panjang kecuali diminta detail.";
$prompt .= "\n7. Boleh menjawab pertanyaan di luar topik jurusan secara singkat, lalu kembalikan ke konseling.";
$prompt .= "\n8. JANGAN awali setiap respons dengan 'Halo' atau salam — langsung ke inti jawaban (kecuali percakapan baru dimulai).";
$prompt .= "\n9. DILARANG KERAS menggunakan format markdown seperti **, *, #, ##, atau simbol formatting lainnya. Tulis teks biasa (plain text) saja tanpa formatting markdown.";
$prompt .= "\n10. Gunakan bahasa Indonesia baku dan akademik. Hindari bahasa gaul seperti 'kek', 'banget', 'ngobrol', 'ngomongin', 'gampangnya'. Gunakan padanan formal seperti 'sangat', 'berbincang', 'membahas', 'secara sederhana'.";
return $prompt;
}

View File

@ -82,7 +82,7 @@
|
*/
'locale' => 'en',
'locale' => 'id',
/*
|--------------------------------------------------------------------------
@ -95,7 +95,7 @@
|
*/
'fallback_locale' => 'en',
'fallback_locale' => 'id',
/*
|--------------------------------------------------------------------------
@ -108,7 +108,7 @@
|
*/
'faker_locale' => 'en_US',
'faker_locale' => 'id_ID',
/*
|--------------------------------------------------------------------------

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Modify role enum to include 'bk'
Schema::table('users', function (Blueprint $table) {
$table->enum('role', ['admin', 'guru', 'bk', 'siswa'])->default('siswa')->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->enum('role', ['admin', 'guru', 'siswa'])->default('siswa')->change();
});
}
};

View File

@ -0,0 +1,62 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('alumni', function (Blueprint $table) {
$table->id();
$table->string('nama_alumni');
$table->string('nis')->nullable();
$table->enum('kelompok_asal', ['IPA', 'IPS']);
$table->year('tahun_masuk');
// === NILAI SAAT ENTRY ===
$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();
$table->float('nilai_rata_rata')->nullable(); // auto-calculated
// === VARIABEL NON-AKADEMIK SAAT ENTRY ===
$table->string('minat')->nullable();
$table->string('cita_cita')->nullable();
$table->string('preferensi_studi')->nullable(); // Praktik Langsung, DuDi, Project Based, Blended
$table->string('prestasi')->nullable();
// === MAJOR & PREDICTION ===
$table->string('major_masuk');
$table->integer('ranking_saat_rekomendasi')->nullable(); // ranking berapa di list 9 jurusan
$table->float('predicted_score')->nullable(); // score dari algoritma saat itu
// === OUTCOME & PERFORMANCE ===
$table->float('ipk_lulus')->nullable(); // cumulative GPA
$table->year('tahun_lulus')->nullable();
$table->text('karir_outcome')->nullable(); // deskripsi: jadi apa, kerja dimana
$table->enum('success_status', ['sangat_sukses', 'sukses', 'cukup', 'kurang_sukses'])->nullable();
$table->text('catatan')->nullable();
// === METADATA ===
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('alumni');
}
};

View File

@ -0,0 +1,61 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// Drop alumni table dan buat ulang dengan struktur benar
Schema::dropIfExists('alumni');
Schema::create('alumni', function (Blueprint $table) {
$table->id();
// === IDENTITAS ALUMNI SMA ===
$table->string('nama_alumni');
$table->string('nis')->nullable()->unique(); // NIS dari SMA Bima Ambulu
$table->enum('kelompok_asal', ['IPA', 'IPS']);
// === NILAI SAAT SMA (INPUT) ===
// IPA: Matematika, Fisika, Kimia, Biologi
$table->float('mtk')->nullable();
$table->float('fisika')->nullable();
$table->float('kimia')->nullable();
$table->float('biologi')->nullable();
// IPS: Ekonomi, Geografi, Sosiologi, Sejarah
$table->float('ekonomi')->nullable();
$table->float('geografi')->nullable();
$table->float('sosiologi')->nullable();
$table->float('sejarah')->nullable();
$table->float('nilai_rata_rata')->nullable(); // auto-calculated
// === VARIABEL NON-AKADEMIK SMA (INPUT) ===
$table->string('minat')->nullable();
$table->string('cita_cita')->nullable();
$table->enum('preferensi_studi', ['Praktik_Langsung', 'DuDi', 'Project_Based', 'Blended'])->nullable();
$table->text('prestasi')->nullable();
// === HASIL KEPUTUSAN MASUK POLIJE (OUTPUT) ===
$table->string('major_masuk'); // Jurusan yang dipilih di Polije
$table->integer('ranking_saat_rekomendasi')->nullable(); // Ranking rekomendasi 1-9
// === VALIDASI AKURASI EKSTRAKTION ===
// Success = ranking rekomendasi cocok dengan pilihan sebenarnya
$table->enum('success_status', ['sangat_sukses', 'sukses', 'cukup', 'kurang_sukses'])->nullable();
$table->text('catatan')->nullable(); // Notas analyst
// === METADATA ===
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('alumni');
}
};

View File

@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('alumni', function (Blueprint $table) {
// Drop old columns if exist
if (Schema::hasColumn('alumni', 'tahun_masuk')) {
$table->dropColumn('tahun_masuk');
}
if (Schema::hasColumn('alumni', 'preferensi_studi')) {
$table->dropColumn('preferensi_studi');
}
if (Schema::hasColumn('alumni', 'tahun_lulus')) {
$table->dropColumn('tahun_lulus');
}
});
Schema::table('alumni', function (Blueprint $table) {
// === INPUT VARIABLES (dari SMA) ===
$table->year('tahun_masuk_sma')->nullable()->after('kelompok_asal');
$table->year('tahun_lulus_sma')->nullable()->after('tahun_masuk_sma');
// Minat & Karir
$table->string('minat')->nullable()->change();
$table->string('cita_cita')->nullable()->change();
$table->enum('preferensi_studi_lanjutan', ['Praktik_Langsung', 'DuDi', 'Project_Based', 'Blended'])->nullable()->after('cita_cita');
$table->text('prestasi')->nullable()->change();
// === OUTPUT VARIABLES (di Polije) ===
$table->year('tahun_masuk_polije')->nullable()->after('major_masuk');
$table->year('tahun_lulus_polije')->nullable()->after('tahun_masuk_polije');
// === METADATA ===
if (!Schema::hasColumn('alumni', 'notes')) {
$table->text('notes')->nullable()->after('catatan');
}
});
}
public function down(): void
{
Schema::table('alumni', function (Blueprint $table) {
//
});
}
};

View File

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('polije_majors', function (Blueprint $table) {
$table->json('keywords')->nullable()->after('deskripsi');
$table->json('preferensi_studi')->nullable()->after('keywords');
});
}
public function down(): void
{
Schema::table('polije_majors', function (Blueprint $table) {
$table->dropColumn(['keywords', 'preferensi_studi']);
});
}
};

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('recommendations', function (Blueprint $table) {
$table->longText('hasil_rekomendasi')->nullable()->change();
});
}
public function down(): void
{
Schema::table('recommendations', function (Blueprint $table) {
$table->string('hasil_rekomendasi')->nullable()->change();
});
}
};

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('polije_majors', function (Blueprint $table) {
$table->json('bobot_mapel')->nullable()->after('preferensi_studi');
});
}
public function down(): void
{
Schema::table('polije_majors', function (Blueprint $table) {
$table->dropColumn('bobot_mapel');
});
}
};

View File

@ -0,0 +1,43 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class AdminSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Create Admin User
User::firstOrCreate(
['email' => 'admin@gmail.com'],
[
'name' => 'Admin Polije',
'password' => Hash::make('admin123'),
'role' => 'admin',
'email_verified_at' => now(),
]
);
// Create BK (Konselor) User
User::firstOrCreate(
['email' => 'bk@gmail.com'],
[
'name' => 'Konselor BK',
'password' => Hash::make('bk123'),
'role' => 'bk',
'email_verified_at' => now(),
]
);
echo "✅ Admin & BK users created successfully!\n";
echo "Admin: admin@gmail.com / admin123\n";
echo "BK: bk@gmail.com / bk123\n";
}
}

View File

@ -0,0 +1,203 @@
<?php
namespace Database\Seeders;
use App\Models\Alumni;
use Illuminate\Database\Seeder;
class AlumniSeeder extends Seeder
{
public function run(): void
{
// Alumni SMA Bima Ambulu yang MASUK ke Polije
// INPUT: Nilai SMA + Variabel Non-Akademik
// OUTPUT: Jurusan yang dipilih di Polije
// VALIDASI: Ranking rekomendasi cocok? (success = ranking 1-3, fail = ranking >5)
$alumniData = [
// === IPA ===
[
'nama_alumni' => 'Budi Santoso',
'nis' => 'SMA001',
'kelompok_asal' => 'IPA',
'mtk' => 85,
'fisika' => 82,
'kimia' => 88,
'biologi' => 90,
'minat' => 'Teknologi & Robotika',
'cita_cita' => 'Software Developer',
'preferensi_studi' => 'Sains & Teknologi',
'prestasi' => 'Juara 1 Olimpiade Komputer Nasional',
'major_masuk' => 'Teknik Informatika', // Masuk jurusan ini
'ranking_saat_rekomendasi' => 1, // Rekomendasi ranking 1 = COCOK! ✓✓
'success_status' => 'sangat_sukses',
'catatan' => 'Rekomendasi akurat - ranking 1 cocok dengan pilihan',
],
[
'nama_alumni' => 'Siti Nurhaliza',
'nis' => 'SMA002',
'kelompok_asal' => 'IPA',
'mtk' => 90,
'fisika' => 88,
'kimia' => 92,
'biologi' => 94,
'minat' => 'Kesehatan & Bioteknologi',
'cita_cita' => 'Biomedical Engineer',
'preferensi_studi' => 'Kesehatan & Ilmu Hayat',
'prestasi' => 'Beasiswa Penuh Akademik',
'major_masuk' => 'Teknik Biomedis',
'ranking_saat_rekomendasi' => 2, // Cocok ✓
'success_status' => 'sangat_sukses',
'catatan' => 'Rekomendasi akurat - ranking 2 cocok',
],
[
'nama_alumni' => 'Ahmad Wijaya',
'nis' => 'SMA003',
'kelompok_asal' => 'IPA',
'mtk' => 75,
'fisika' => 78,
'kimia' => 80,
'biologi' => 72,
'minat' => 'Teknik Mesin',
'cita_cita' => 'Mechanical Engineer',
'preferensi_studi' => 'Sains & Teknologi',
'prestasi' => 'Sertifikat Kompetisi Robotika',
'major_masuk' => 'Teknik Mesin',
'ranking_saat_rekomendasi' => 3, // Cocok ✓
'success_status' => 'sukses',
'catatan' => 'Rekomendasi cukup akurat - ranking 3 cocok',
],
[
'nama_alumni' => 'Lina Hartini',
'nis' => 'SMA004',
'kelompok_asal' => 'IPA',
'mtk' => 88,
'fisika' => 86,
'kimia' => 90,
'biologi' => 92,
'minat' => 'Riset & Sains Terapan',
'cita_cita' => 'Research Scientist',
'preferensi_studi' => 'Kesehatan & Ilmu Hayat',
'prestasi' => 'Publikasi Paper Research',
'major_masuk' => 'Teknik Biomedis',
'ranking_saat_rekomendasi' => 1, // Cocok ✓✓
'success_status' => 'sangat_sukses',
'catatan' => 'Rekomendasi sangat akurat',
],
[
'nama_alumni' => 'Fajar Maulana',
'nis' => 'SMA005',
'kelompok_asal' => 'IPA',
'mtk' => 72,
'fisika' => 70,
'kimia' => 68,
'biologi' => 65,
'minat' => 'Teknik Elektro',
'cita_cita' => 'Electrical Engineer',
'preferensi_studi' => 'Sains & Teknologi',
'prestasi' => '-',
'major_masuk' => 'Teknik Mesin', // BEDA dari rekomendasi ranking 1
'ranking_saat_rekomendasi' => 6, // Ranking 6 = kurang cocok
'success_status' => 'cukup',
'catatan' => 'Rekomendasi kurang akurat - pilih jurusan berbeda',
],
// === IPS ===
[
'nama_alumni' => 'Rina Handayani',
'nis' => 'SMA006',
'kelompok_asal' => 'IPS',
'ekonomi' => 90,
'geografi' => 87,
'sosiologi' => 88,
'sejarah' => 85,
'minat' => 'Bisnis & Manajemen',
'cita_cita' => 'Business Manager',
'preferensi_studi' => 'Bisnis & Manajemen',
'prestasi' => 'Juara Debat Nasional',
'major_masuk' => 'Manajemen Bisnis',
'ranking_saat_rekomendasi' => 1, // Cocok ✓✓
'success_status' => 'sangat_sukses',
'catatan' => 'Rekomendasi sempurna',
],
[
'nama_alumni' => 'Dewi Prasetya',
'nis' => 'SMA007',
'kelompok_asal' => 'IPS',
'ekonomi' => 85,
'geografi' => 86,
'sosiologi' => 84,
'sejarah' => 88,
'minat' => 'Akuntansi & Keuangan',
'cita_cita' => 'Akuntan Publik',
'preferensi_studi' => 'Bisnis & Manajemen',
'prestasi' => 'Sertifikasi ACCA',
'major_masuk' => 'Akuntansi',
'ranking_saat_rekomendasi' => 2, // Cocok ✓
'success_status' => 'sukses',
'catatan' => 'Rekomendasi akurat',
],
[
'nama_alumni' => 'Rudi Hermawan',
'nis' => 'SMA008',
'kelompok_asal' => 'IPS',
'ekonomi' => 68,
'geografi' => 71,
'sosiologi' => 65,
'sejarah' => 72,
'minat' => 'Pemerintahan & Administrasi',
'cita_cita' => 'PNS',
'preferensi_studi' => 'Sosial & Humaniora',
'prestasi' => '-',
'major_masuk' => 'Administrasi Publik', // RANKING JAUH dari pilihan
'ranking_saat_rekomendasi' => 7, // Ranking 7 = TIDAK COCOK ✗
'success_status' => 'kurang_sukses',
'catatan' => 'Rekomendasi salah - siswa pilih jurusan lain',
],
[
'nama_alumni' => 'Indra Setiawan',
'nis' => 'SMA009',
'kelompok_asal' => 'IPS',
'ekonomi' => 82,
'geografi' => 80,
'sosiologi' => 78,
'sejarah' => 84,
'minat' => 'Marketing & Digital',
'cita_cita' => 'Marketing Manager',
'preferensi_studi' => 'Bisnis & Manajemen',
'prestasi' => 'Kompetisi Business Plan',
'major_masuk' => 'Manajemen Bisnis',
'ranking_saat_rekomendasi' => 2, // Cocok ✓
'success_status' => 'sukses',
'catatan' => 'Rekomendasi akurat',
],
[
'nama_alumni' => 'Maya Suntari',
'nis' => 'SMA010',
'kelompok_asal' => 'IPS',
'ekonomi' => 75,
'geografi' => 73,
'sosiologi' => 76,
'sejarah' => 78,
'minat' => 'Akuntansi & Keuangan',
'cita_cita' => 'Accountant',
'preferensi_studi' => 'Bisnis & Manajemen',
'prestasi' => 'Buku Tahunan Finance Club',
'major_masuk' => 'Akuntansi',
'ranking_saat_rekomendasi' => 3, // Cocok ✓
'success_status' => 'sukses',
'catatan' => 'Rekomendasi cukup akurat - ranking 3',
],
];
foreach ($alumniData as $data) {
Alumni::firstOrCreate(
['nis' => $data['nis']],
$data
);
}
echo "\n✅ AlumniSeeder: " . count($alumniData) . " alumni SMA Bima Ambulu loaded!\n";
echo "📊 Fokus: Validasi akurasi rekomendasi (ranking 1-3 = sukses, >5 = gagal)\n";
}
}

View File

@ -9,27 +9,119 @@ class PolijeMajorSeeder extends Seeder
{
public function run(): void
{
// Bersihkan data agar tidak duplikat
PolijeMajor::truncate();
$jurusans = [
'Produksi Pertanian',
'Teknologi Pertanian',
'Peternakan',
'Manajemen Agribisnis',
'Teknologi Informasi',
'Teknis',
'Kesehatan',
'Bahasa, Komunikasi dan Pariwisata',
'Bisnis',
[
'nama_jurusan' => 'Produksi Pertanian',
'deskripsi' => 'Program studi yang mempelajari teknik budidaya tanaman, pengelolaan lahan pertanian, dan produksi hasil pertanian secara modern.',
'keywords' => ['pertanian', 'petani', 'kebun', 'sawah', 'panen', 'tanaman', 'budidaya', 'agronomi', 'tanam', 'bercocok tanam', 'alam', 'hortikultura', 'pupuk', 'bibit'],
'preferensi_studi' => ['Pertanian & Lingkungan'],
'bobot_mapel' => [
'biologi' => 0.40, 'kimia' => 0.30, 'fisika' => 0.15, 'mtk' => 0.15,
'geografi' => 0.35, 'ekonomi' => 0.30, 'sosiologi' => 0.20, 'sejarah' => 0.15,
],
'prospek_kerja' => 'Petani modern, konsultan pertanian, pengelola perkebunan, peneliti pertanian, agronomis.',
],
[
'nama_jurusan' => 'Teknologi Pertanian',
'deskripsi' => 'Program studi yang mengintegrasikan teknologi dengan pertanian, meliputi mekanisasi pertanian, pengolahan hasil pertanian, dan inovasi teknologi pangan.',
'keywords' => ['teknologi pertanian', 'mesin pertanian', 'inovasi', 'otomasi', 'pengolahan pangan', 'pangan', 'mekanisasi', 'teknologi pangan', 'alat pertanian', 'rekayasa'],
'preferensi_studi' => ['Sains & Teknologi', 'Pertanian & Lingkungan'],
'bobot_mapel' => [
'fisika' => 0.35, 'mtk' => 0.30, 'kimia' => 0.20, 'biologi' => 0.15,
'ekonomi' => 0.30, 'geografi' => 0.30, 'sosiologi' => 0.20, 'sejarah' => 0.20,
],
'prospek_kerja' => 'Teknisi pertanian, ahli mekanisasi, quality control pangan, peneliti teknologi pangan.',
],
[
'nama_jurusan' => 'Peternakan',
'deskripsi' => 'Program studi yang mempelajari pengelolaan dan pemeliharaan ternak, nutrisi hewan, reproduksi, dan pengolahan produk peternakan.',
'keywords' => ['ternak', 'hewan', 'peternakan', 'peternak', 'sapi', 'ayam', 'unggas', 'kambing', 'susu', 'pakan', 'nutrisi hewan', 'veteriner', 'ikan', 'aquaculture'],
'preferensi_studi' => ['Pertanian & Lingkungan', 'Kesehatan & Ilmu Hayat'],
'bobot_mapel' => [
'biologi' => 0.45, 'kimia' => 0.25, 'fisika' => 0.15, 'mtk' => 0.15,
'geografi' => 0.30, 'ekonomi' => 0.30, 'sosiologi' => 0.20, 'sejarah' => 0.20,
],
'prospek_kerja' => 'Peternak profesional, konsultan peternakan, manajer peternakan, ahli nutrisi hewan.',
],
[
'nama_jurusan' => 'Manajemen Agribisnis',
'deskripsi' => 'Program studi yang menggabungkan ilmu pertanian dan bisnis, meliputi pemasaran hasil pertanian, manajemen usaha tani, dan kewirausahaan agribisnis.',
'keywords' => ['bisnis', 'agribisnis', 'usaha', 'entrepreneur', 'pengusaha', 'dagang', 'jual', 'pemasaran', 'kewirausahaan', 'manajemen', 'ekonomi pertanian', 'pasar'],
'preferensi_studi' => ['Bisnis & Manajemen', 'Pertanian & Lingkungan'],
'bobot_mapel' => [
'mtk' => 0.35, 'biologi' => 0.25, 'kimia' => 0.20, 'fisika' => 0.20,
'ekonomi' => 0.45, 'geografi' => 0.20, 'sosiologi' => 0.20, 'sejarah' => 0.15,
],
'prospek_kerja' => 'Manajer agribisnis, entrepreneur pertanian, konsultan pemasaran pertanian, analis pasar komoditas.',
],
[
'nama_jurusan' => 'Teknologi Informasi',
'deskripsi' => 'Program studi yang mempelajari pengembangan perangkat lunak, jaringan komputer, keamanan siber, dan teknologi digital.',
'keywords' => ['programmer', 'developer', 'coding', 'software', 'web', 'aplikasi', 'komputer', 'it', 'jaringan', 'hacker', 'game', 'data', 'ai', 'robot', 'ngoding', 'laptop', 'teknologi', 'digital', 'internet', 'programming', 'desain grafis'],
'preferensi_studi' => ['Sains & Teknologi'],
'bobot_mapel' => [
'mtk' => 0.45, 'fisika' => 0.25, 'kimia' => 0.15, 'biologi' => 0.15,
'ekonomi' => 0.25, 'geografi' => 0.20, 'sosiologi' => 0.25, 'sejarah' => 0.30,
],
'prospek_kerja' => 'Software developer, web developer, network engineer, data analyst, cybersecurity specialist.',
],
[
'nama_jurusan' => 'Teknik',
'deskripsi' => 'Program studi yang mempelajari mesin, kelistrikan, elektronika, dan otomasi industri.',
'keywords' => ['mesin', 'bengkel', 'listrik', 'las', 'robot', 'motor', 'teknik', 'otomasi', 'elektronik', 'instalasi', 'panel', 'mekanik', 'industri', 'manufaktur', 'pabrik', 'bangunan', 'konstruksi', 'sipil', 'energi'],
'preferensi_studi' => ['Sains & Teknologi'],
'bobot_mapel' => [
'fisika' => 0.40, 'mtk' => 0.35, 'kimia' => 0.15, 'biologi' => 0.10,
'ekonomi' => 0.25, 'geografi' => 0.25, 'sosiologi' => 0.20, 'sejarah' => 0.30,
],
'prospek_kerja' => 'Teknisi mesin, ahli listrik, engineer industri, maintenance engineer, kontraktor.',
],
[
'nama_jurusan' => 'Kesehatan',
'deskripsi' => 'Program studi yang mempelajari ilmu kesehatan, gizi, rekam medis, dan pelayanan kesehatan masyarakat.',
'keywords' => ['dokter', 'perawat', 'medis', 'gizi', 'kesehatan', 'pelayanan', 'terapis', 'obat', 'rumah sakit', 'klinik', 'farmasi', 'nutrisi', 'sanitasi', 'rawat', 'sehat'],
'preferensi_studi' => ['Kesehatan & Ilmu Hayat'],
'bobot_mapel' => [
'biologi' => 0.40, 'kimia' => 0.35, 'mtk' => 0.15, 'fisika' => 0.10,
'sosiologi' => 0.30, 'ekonomi' => 0.25, 'geografi' => 0.25, 'sejarah' => 0.20,
],
'prospek_kerja' => 'Ahli gizi, perekam medis, tenaga kesehatan, asisten apoteker, sanitarian.',
],
[
'nama_jurusan' => 'Bahasa, Komunikasi, dan Pariwisata',
'deskripsi' => 'Program studi yang mempelajari bahasa asing, komunikasi, perhotelan, dan industri pariwisata.',
'keywords' => ['bahasa', 'komunikasi', 'pariwisata', 'tour guide', 'hotel', 'jurnalis', 'marketing', 'inggris', 'penerjemah', 'travel', 'wisata', 'hospitality', 'public speaking', 'media', 'broadcasting'],
'preferensi_studi' => ['Sosial & Humaniora', 'Bisnis & Manajemen'],
'bobot_mapel' => [
'biologi' => 0.20, 'kimia' => 0.20, 'fisika' => 0.20, 'mtk' => 0.40,
'sosiologi' => 0.30, 'sejarah' => 0.30, 'geografi' => 0.25, 'ekonomi' => 0.15,
],
'prospek_kerja' => 'Tour guide, staf perhotelan, jurnalis, public relation, penerjemah, staf maskapai.',
],
[
'nama_jurusan' => 'Bisnis',
'deskripsi' => 'Program studi yang mempelajari akuntansi, manajemen bisnis, perbankan, dan administrasi niaga.',
'keywords' => ['manager', 'pimpinan', 'bisnis', 'accounting', 'marketing', 'sales', 'kantor', 'keuangan', 'bank', 'akuntansi', 'hitung', 'administrasi', 'perbankan', 'ekonomi', 'uang', 'investasi', 'pajak'],
'preferensi_studi' => ['Bisnis & Manajemen'],
'bobot_mapel' => [
'mtk' => 0.45, 'fisika' => 0.20, 'kimia' => 0.15, 'biologi' => 0.20,
'ekonomi' => 0.45, 'sosiologi' => 0.20, 'geografi' => 0.15, 'sejarah' => 0.20,
],
'prospek_kerja' => 'Akuntan, staf perbankan, manajer bisnis, marketing executive, analis keuangan.',
],
];
foreach ($jurusans as $jur) {
PolijeMajor::create([
'nama_jurusan' => $jur,
'deskripsi' => 'Program ini berfokus pada keahlian di bidang ' . $jur,
'prospek_kerja' => 'Lulusan dapat berkarir di bidang terkait ' . $jur,
]);
PolijeMajor::updateOrCreate(
['nama_jurusan' => $jur['nama_jurusan']],
[
'deskripsi' => $jur['deskripsi'],
'keywords' => $jur['keywords'],
'preferensi_studi' => $jur['preferensi_studi'],
'bobot_mapel' => $jur['bobot_mapel'],
'prospek_kerja' => $jur['prospek_kerja'],
]
);
}
}
}

17
lang/id/auth.php Normal file
View File

@ -0,0 +1,17 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during authentication for various
| messages that we need to display to the user.
|
*/
'failed' => 'Kredensial yang diberikan tidak sesuai dengan catatan kami.',
'password' => 'Password yang diberikan salah.',
'throttle' => 'Terlalu banyak upaya login. Silakan coba lagi dalam :seconds detik.',
];

86
lang/id/messages.php Normal file
View File

@ -0,0 +1,86 @@
<?php
return [
// Buttons
'login' => 'Masuk',
'logout' => 'Keluar',
'register' => 'Daftar',
'save' => 'Simpan',
'cancel' => 'Batal',
'delete' => 'Hapus',
'edit' => 'Ubah',
'update' => 'Perbarui',
'create' => 'Buat',
'submit' => 'Kirim',
'reset' => 'Reset',
'confirm' => 'Konfirmasi',
'back' => 'Kembali',
'next' => 'Selanjutnya',
'send' => 'Kirim',
'search' => 'Cari',
'filter' => 'Filter',
'download' => 'Unduh',
'upload' => 'Unggah',
'delete_account' => 'Hapus Akun',
'save_changes' => 'Simpan Perubahan',
'change_password' => 'Ubah Password',
'forgot_password' => 'Lupa Password?',
'remember_me' => 'Ingat saya',
'start_analysis' => 'Mulai Analisis',
'register_now' => 'Daftar Sekarang',
// Messages
'welcome' => 'Selamat Datang',
'home' => 'Beranda',
'dashboard' => 'Dashboard',
'profile' => 'Profil',
'settings' => 'Pengaturan',
'help' => 'Bantuan',
'about' => 'Tentang',
'contact' => 'Hubungi Kami',
'success' => 'Berhasil',
'error' => 'Kesalahan',
'warning' => 'Peringatan',
'info' => 'Informasi',
'loading' => 'Sedang memuat...',
'no_data' => 'Tidak ada data',
'are_you_sure' => 'Apakah Anda yakin?',
'saved_successfully' => 'Berhasil disimpan!',
'deleted_successfully' => 'Berhasil dihapus!',
'updated_successfully' => 'Berhasil diperbarui!',
'created_successfully' => 'Berhasil dibuat!',
'something_went_wrong' => 'Terjadi kesalahan. Silakan coba lagi.',
// Authentication
'email' => 'Email',
'password' => 'Password',
'confirm_password' => 'Konfirmasi Password',
'sign_in' => 'Masuk ke Akun',
'sign_up' => 'Buat Akun Baru',
'email_address' => 'Alamat Email',
'full_name' => 'Nama Lengkap',
'forgot_your_password' => 'Lupa Password Anda?',
'check_your_email' => 'Periksa email Anda',
'password_reset_link' => 'Tautan untuk mengatur ulang password telah dikirim ke email Anda.',
'reset_password' => 'Atur Ulang Password',
'email_password_reset_link' => 'Kirim Tautan Atur Ulang Password',
'confirm_your_password' => 'Konfirmasi Password Anda',
// Profile
'profile_information' => 'Informasi Profil',
'update_your_account_profile_information_and_email_address' => 'Perbarui informasi profil akun Anda dan alamat email.',
'update_password' => 'Perbarui Password',
'ensure_your_account_is_using_a_long_random_password' => 'Pastikan akun Anda menggunakan password yang panjang dan acak untuk tetap aman.',
'current_password' => 'Password Saat Ini',
'new_password' => 'Password Baru',
'delete_account_permanently' => 'Hapus Akun Secara Permanen',
'once_your_account_is_deleted' => 'Setelah akun Anda dihapus, semua sumber daya dan data akan dihapus secara permanen.',
'this_action_cannot_be_undone' => 'Tindakan ini tidak dapat dibatalkan.',
'are_you_sure_you_want_to_delete_your_account' => 'Apakah Anda yakin ingin menghapus akun Anda?',
'your_email_address_is_unverified' => 'Alamat email Anda belum diverifikasi.',
'click_here_to_resend_verification_email' => 'Klik di sini untuk mengirim ulang email verifikasi.',
'verification_link_sent' => 'Tautan verifikasi telah dikirim ke email Anda.',
'password_updated' => 'Password berhasil diperbarui!',
'saved' => 'Berhasil disimpan!',
'profile_updated' => 'Profil berhasil diperbarui!',
];

149
lang/id/validation.php Normal file
View File

@ -0,0 +1,149 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class.
|
*/
'accepted' => 'Kolom :attribute harus diterima.',
'accepted_if' => 'Kolom :attribute harus diterima ketika :other adalah :value.',
'active_url' => 'Kolom :attribute harus berupa URL yang valid.',
'after' => 'Kolom :attribute harus berupa tanggal setelah :date.',
'after_or_equal' => 'Kolom :attribute harus berupa tanggal setelah atau sama dengan :date.',
'alpha' => 'Kolom :attribute hanya boleh berisi huruf.',
'alpha_dash' => 'Kolom :attribute hanya boleh berisi huruf, angka, garis bawah, dan garis putus-putus.',
'alpha_num' => 'Kolom :attribute hanya boleh berisi huruf dan angka.',
'array' => 'Kolom :attribute harus berupa larik.',
'ascii' => 'Kolom :attribute hanya boleh berisi karakter dan simbol ASCII satu byte.',
'before' => 'Kolom :attribute harus berupa tanggal sebelum :date.',
'before_or_equal' => 'Kolom :attribute harus berupa tanggal sebelum atau sama dengan :date.',
'between' => [
'numeric' => 'Kolom :attribute harus berada di antara :min dan :max.',
'file' => 'Kolom :attribute harus berada di antara :min dan :max kilobita.',
'string' => 'Kolom :attribute harus berada di antara :min dan :max karakter.',
'array' => 'Kolom :attribute harus memiliki antara :min dan :max item.',
],
'boolean' => 'Kolom :attribute harus benar atau salah.',
'confirmed' => 'Konfirmasi :attribute tidak cocok.',
'current_password' => 'Password salah.',
'date' => 'Kolom :attribute bukan tanggal yang valid.',
'date_equals' => 'Kolom :attribute harus berupa tanggal yang sama dengan :date.',
'date_format' => 'Kolom :attribute tidak sesuai dengan format :format.',
'declined' => 'Kolom :attribute harus ditolak.',
'declined_if' => 'Kolom :attribute harus ditolak ketika :other adalah :value.',
'different' => 'Kolom :attribute dan :other harus berbeda.',
'digits' => 'Kolom :attribute harus terdiri dari :digits digit.',
'digits_between' => 'Kolom :attribute harus berada di antara :min dan :max digit.',
'dimensions' => 'Kolom :attribute memiliki dimensi gambar yang tidak valid.',
'distinct' => 'Kolom :attribute memiliki nilai duplikat.',
'email' => 'Kolom :attribute harus berupa alamat email yang valid.',
'ends_with' => 'Kolom :attribute harus diakhiri dengan salah satu dari berikut: :values.',
'exists' => 'Kolom :attribute yang dipilih tidak valid.',
'file' => 'Kolom :attribute harus berupa file.',
'filled' => 'Kolom :attribute harus memiliki nilai.',
'gt' => [
'numeric' => 'Kolom :attribute harus lebih besar dari :value.',
'file' => 'Kolom :attribute harus lebih besar dari :value kilobita.',
'string' => 'Kolom :attribute harus lebih besar dari :value karakter.',
'array' => 'Kolom :attribute harus memiliki lebih dari :value item.',
],
'gte' => [
'numeric' => 'Kolom :attribute harus lebih besar dari atau sama dengan :value.',
'file' => 'Kolom :attribute harus lebih besar dari atau sama dengan :value kilobita.',
'string' => 'Kolom :attribute harus lebih besar dari atau sama dengan :value karakter.',
'array' => 'Kolom :attribute harus memiliki :value item atau lebih.',
],
'image' => 'Kolom :attribute harus berupa gambar.',
'in' => 'Kolom :attribute yang dipilih tidak valid.',
'in_array' => 'Kolom :attribute harus ada dalam :other.',
'integer' => 'Kolom :attribute harus berupa integer.',
'ip' => 'Kolom :attribute harus berupa alamat IP yang valid.',
'ipv4' => 'Kolom :attribute harus berupa alamat IPv4 yang valid.',
'ipv6' => 'Kolom :attribute harus berupa alamat IPv6 yang valid.',
'json' => 'Kolom :attribute harus berupa string JSON yang valid.',
'lt' => [
'numeric' => 'Kolom :attribute harus kurang dari :value.',
'file' => 'Kolom :attribute harus kurang dari :value kilobita.',
'string' => 'Kolom :attribute harus kurang dari :value karakter.',
'array' => 'Kolom :attribute harus memiliki kurang dari :value item.',
],
'lte' => [
'numeric' => 'Kolom :attribute harus kurang dari atau sama dengan :value.',
'file' => 'Kolom :attribute harus kurang dari atau sama dengan :value kilobita.',
'string' => 'Kolom :attribute harus kurang dari atau sama dengan :value karakter.',
'array' => 'Kolom :attribute tidak boleh memiliki lebih dari :value item.',
],
'max' => [
'numeric' => 'Kolom :attribute tidak boleh lebih besar dari :max.',
'file' => 'Kolom :attribute tidak boleh lebih besar dari :max kilobita.',
'string' => 'Kolom :attribute tidak boleh lebih besar dari :max karakter.',
'array' => 'Kolom :attribute tidak boleh memiliki lebih dari :max item.',
],
'mimes' => 'Kolom :attribute harus berupa file bertipe: :values.',
'mimetypes' => 'Kolom :attribute harus berupa file bertipe: :values.',
'min' => [
'numeric' => 'Kolom :attribute harus minimal :min.',
'file' => 'Kolom :attribute harus minimal :min kilobita.',
'string' => 'Kolom :attribute harus minimal :min karakter.',
'array' => 'Kolom :attribute harus memiliki minimal :min item.',
],
'multiple_of' => 'Kolom :attribute harus merupakan kelipatan dari :value.',
'not_in' => 'Kolom :attribute yang dipilih tidak valid.',
'not_regex' => 'Format kolom :attribute tidak valid.',
'numeric' => 'Kolom :attribute harus berupa angka.',
'password' => 'Password salah.',
'present' => 'Kolom :attribute harus ada.',
'regex' => 'Format kolom :attribute tidak valid.',
'required' => 'Kolom :attribute harus diisi.',
'required_if' => 'Kolom :attribute harus diisi ketika :other adalah :value.',
'required_unless' => 'Kolom :attribute harus diisi kecuali :other adalah :values.',
'required_with' => 'Kolom :attribute harus diisi ketika :values ada.',
'required_with_all' => 'Kolom :attribute harus diisi ketika :values ada.',
'required_without' => 'Kolom :attribute harus diisi ketika :values tidak ada.',
'required_without_all' => 'Kolom :attribute harus diisi ketika tidak ada dari :values yang ada.',
'same' => 'Kolom :attribute dan :other harus cocok.',
'size' => [
'numeric' => 'Kolom :attribute harus berukuran :size.',
'file' => 'Kolom :attribute harus berukuran :size kilobita.',
'string' => 'Kolom :attribute harus terdiri dari :size karakter.',
'array' => 'Kolom :attribute harus berisi :size item.',
],
'starts_with' => 'Kolom :attribute harus dimulai dengan salah satu dari berikut: :values.',
'string' => 'Kolom :attribute harus berupa string.',
'timezone' => 'Kolom :attribute harus berupa zona waktu yang valid.',
'unique' => 'Kolom :attribute sudah ada.',
'uploaded' => 'Kolom :attribute gagal diunggah.',
'url' => 'Format kolom :attribute tidak valid.',
'uuid' => 'Kolom :attribute harus berupa UUID yang valid.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
*/
'attributes' => [
'name' => 'nama',
'email' => 'email',
'password' => 'password',
'password_confirmation' => 'konfirmasi password',
],
];

View File

@ -0,0 +1,7 @@
<?php
return [
'failed' => 'Email atau password yang Anda masukkan tidak sesuai dengan data kami.',
'password' => 'Password yang Anda masukkan salah.',
'throttle' => 'Terlalu banyak upaya login. Silakan coba lagi dalam :seconds detik.',
];

View File

@ -0,0 +1,54 @@
<?php
return [
// Buttons
'save' => 'Simpan',
'cancel' => 'Batal',
'delete' => 'Hapus',
'edit' => 'Edit',
'update' => 'Perbarui',
'create' => 'Buat',
'submit' => 'Kirim',
'confirm' => 'Konfirmasi',
'back' => 'Kembali',
'next' => 'Selanjutnya',
'logout' => 'Keluar',
'login' => 'Masuk',
'register' => 'Daftar',
// Messages
'welcome' => 'Selamat Datang',
'dashboard' => 'Dashboard',
'profile' => 'Profil',
'settings' => 'Pengaturan',
'success' => 'Berhasil',
'error' => 'Kesalahan',
'warning' => 'Peringatan',
'loading' => 'Sedang memuat...',
'no_data' => 'Tidak ada data',
// Auth
'email' => 'Email',
'password' => 'Password',
'confirm_password' => 'Konfirmasi Password',
'sign_in' => 'Masuk',
'sign_up' => 'Daftar',
'forgot_your_password' => 'Lupa password Anda?',
'dont_have_account' => 'Belum punya akun?',
'already_have_account' => 'Sudah punya akun?',
// Profile
'profile_information' => 'Informasi Profil',
'update_password' => 'Perbarui Password',
'current_password' => 'Password Saat Ini',
'new_password' => 'Password Baru',
'delete_account_permanently' => 'Hapus Akun Secara Permanen',
// Feedback
'saved_successfully' => 'Berhasil disimpan!',
'deleted_successfully' => 'Berhasil dihapus!',
'updated_successfully' => 'Berhasil diperbarui!',
'created_successfully' => 'Berhasil dibuat!',
'password_updated' => 'Password berhasil diperbarui!',
'profile_updated' => 'Profil berhasil diperbarui!',
];

View File

@ -0,0 +1,164 @@
<?php
return [
'accepted' => 'Kolom :attribute harus diterima.',
'accepted_if' => 'Kolom :attribute harus diterima ketika :other adalah :value.',
'active_url' => 'Kolom :attribute bukan URL yang valid.',
'after' => 'Kolom :attribute harus berupa tanggal setelah :date.',
'after_or_equal' => 'Kolom :attribute harus berupa tanggal setelah atau sama dengan :date.',
'alpha' => 'Kolom :attribute hanya boleh berisi huruf.',
'alpha_dash' => 'Kolom :attribute hanya boleh berisi huruf, angka, dan garis putus-putus.',
'alpha_num' => 'Kolom :attribute hanya boleh berisi huruf dan angka.',
'array' => 'Kolom :attribute harus berupa array.',
'before' => 'Kolom :attribute harus berupa tanggal sebelum :date.',
'before_or_equal' => 'Kolom :attribute harus berupa tanggal sebelum atau sama dengan :date.',
'between' => [
'numeric' => 'Kolom :attribute harus berada antara :min dan :max.',
'file' => 'Kolom :attribute harus berada antara :min dan :max kilobyte.',
'string' => 'Kolom :attribute harus berada antara :min dan :max karakter.',
'array' => 'Kolom :attribute harus memiliki antara :min dan :max item.',
],
'boolean' => 'Kolom :attribute harus bernilai true atau false.',
'confirmed' => 'Kolom :attribute tidak cocok.',
'current_password' => 'Password salah.',
'date' => 'Kolom :attribute bukan tanggal yang valid.',
'date_equals' => 'Kolom :attribute harus berupa tanggal yang sama dengan :date.',
'date_format' => 'Kolom :attribute tidak cocok dengan format :format.',
'declined' => 'Kolom :attribute harus ditolak.',
'declined_if' => 'Kolom :attribute harus ditolak ketika :other adalah :value.',
'different' => 'Kolom :attribute dan :other harus berbeda.',
'digits' => 'Kolom :attribute harus :digits digit.',
'digits_between' => 'Kolom :attribute harus antara :min dan :max digit.',
'dimensions' => 'Kolom :attribute memiliki dimensi gambar yang tidak valid.',
'distinct' => 'Kolom :attribute memiliki nilai duplikat.',
'email' => 'Kolom :attribute harus berupa alamat email yang valid.',
'ends_with' => 'Kolom :attribute harus diakhiri dengan salah satu dari: :values.',
'exists' => 'Kolom :attribute yang dipilih tidak valid.',
'file' => 'Kolom :attribute harus berupa file.',
'filled' => 'Kolom :attribute harus memiliki nilai.',
'gt' => [
'numeric' => 'Kolom :attribute harus lebih besar dari :value.',
'file' => 'Kolom :attribute harus lebih besar dari :value kilobyte.',
'string' => 'Kolom :attribute harus lebih besar dari :value karakter.',
'array' => 'Kolom :attribute harus memiliki lebih dari :value item.',
],
'gte' => [
'numeric' => 'Kolom :attribute harus lebih besar dari atau sama dengan :value.',
'file' => 'Kolom :attribute harus lebih besar dari atau sama dengan :value kilobyte.',
'string' => 'Kolom :attribute harus lebih besar dari atau sama dengan :value karakter.',
'array' => 'Kolom :attribute harus memiliki :value item atau lebih.',
],
'image' => 'Kolom :attribute harus berupa gambar.',
'in' => 'Kolom :attribute yang dipilih tidak valid.',
'in_array' => 'Kolom :attribute harus ada di :other.',
'integer' => 'Kolom :attribute harus berupa bilangan bulat.',
'ip' => 'Kolom :attribute harus berupa alamat IP yang valid.',
'ipv4' => 'Kolom :attribute harus berupa alamat IPv4 yang valid.',
'ipv6' => 'Kolom :attribute harus berupa alamat IPv6 yang valid.',
'json' => 'Kolom :attribute harus berupa string JSON yang valid.',
'lt' => [
'numeric' => 'Kolom :attribute harus kurang dari :value.',
'file' => 'Kolom :attribute harus kurang dari :value kilobyte.',
'string' => 'Kolom :attribute harus kurang dari :value karakter.',
'array' => 'Kolom :attribute harus memiliki kurang dari :value item.',
],
'lte' => [
'numeric' => 'Kolom :attribute harus kurang dari atau sama dengan :value.',
'file' => 'Kolom :attribute harus kurang dari atau sama dengan :value kilobyte.',
'string' => 'Kolom :attribute harus kurang dari atau sama dengan :value karakter.',
'array' => 'Kolom :attribute tidak boleh memiliki lebih dari :value item.',
],
'max' => [
'numeric' => 'Kolom :attribute tidak boleh lebih besar dari :max.',
'file' => 'Kolom :attribute tidak boleh lebih besar dari :max kilobyte.',
'string' => 'Kolom :attribute tidak boleh lebih dari :max karakter.',
'array' => 'Kolom :attribute tidak boleh memiliki lebih dari :max item.',
],
'mimes' => 'Kolom :attribute harus berupa file bertipe: :values.',
'mimetypes' => 'Kolom :attribute harus berupa file bertipe: :values.',
'min' => [
'numeric' => 'Kolom :attribute harus minimal :min.',
'file' => 'Kolom :attribute harus minimal :min kilobyte.',
'string' => 'Kolom :attribute harus minimal :min karakter.',
'array' => 'Kolom :attribute harus memiliki minimal :min item.',
],
'multiple_of' => 'Kolom :attribute harus merupakan kelipatan dari :value.',
'not_in' => 'Kolom :attribute yang dipilih tidak valid.',
'not_regex' => 'Format kolom :attribute tidak valid.',
'numeric' => 'Kolom :attribute harus berupa angka.',
'password' => 'Password salah.',
'present' => 'Kolom :attribute harus ada.',
'regex' => 'Format kolom :attribute tidak valid.',
'required' => 'Kolom :attribute wajib diisi.',
'required_if' => 'Kolom :attribute wajib diisi ketika :other adalah :value.',
'required_unless' => 'Kolom :attribute wajib diisi kecuali :other adalah :values.',
'required_with' => 'Kolom :attribute wajib diisi ketika :values ada.',
'required_with_all' => 'Kolom :attribute wajib diisi ketika :values ada.',
'required_without' => 'Kolom :attribute wajib diisi ketika :values tidak ada.',
'required_without_all' => 'Kolom :attribute wajib diisi ketika tidak ada dari :values yang ada.',
'same' => 'Kolom :attribute dan :other harus cocok.',
'size' => [
'numeric' => 'Kolom :attribute harus :size.',
'file' => 'Kolom :attribute harus :size kilobyte.',
'string' => 'Kolom :attribute harus :size karakter.',
'array' => 'Kolom :attribute harus mengandung :size item.',
],
'starts_with' => 'Kolom :attribute harus dimulai dengan salah satu dari: :values.',
'string' => 'Kolom :attribute harus berupa string.',
'timezone' => 'Kolom :attribute harus berupa timezone yang valid.',
'unique' => 'Nilai :attribute sudah terdaftar.',
'uploaded' => 'Upload kolom :attribute gagal.',
'url' => 'Format kolom :attribute tidak valid.',
'uuid' => 'Kolom :attribute harus berupa UUID yang valid.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This allows you to quickly
| specify a custom validation message for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap our attribute names with
| something more reader friendly such as Email Address instead of "email".
| This simply helps us make our message more expressive and easier to read.
|
*/
'attributes' => [
'name' => 'nama',
'email' => 'email',
'password' => 'password',
'password_confirmation' => 'konfirmasi password',
'nis' => 'NIS',
'kelompok_asal' => 'kelompok asal',
'minat' => 'minat',
'cita_cita' => 'cita-cita',
'pref_studi' => 'preferensi pembelajaran',
'prestasi' => 'prestasi',
'mtk' => 'nilai matematika',
'fisika' => 'nilai fisika',
'kimia' => 'nilai kimia',
'biologi' => 'nilai biologi',
'ekonomi' => 'nilai ekonomi',
'geografi' => 'nilai geografi',
'sosiologi' => 'nilai sosiologi',
'sejarah' => 'nilai sejarah',
'foto' => 'foto',
'current_password' => 'password saat ini',
],
];

View File

@ -0,0 +1,73 @@
@extends('admin.layouts.app')
@section('title', 'Chat History - ' . ($user->name ?? 'Unknown'))
@section('content')
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon">💬 Chat History</h2>
<p class="text-sm text-gray-500 mt-1">{{ $user->name ?? 'Unknown' }}</p>
</div>
<a href="{{ route('admin.student.detail', $user->id) }}" class="bg-gray-400 text-white font-bold py-2 px-4 rounded-lg hover:bg-gray-500 transition text-sm">
Profil Siswa
</a>
</div>
<!-- Stats -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<div class="bg-white rounded-lg shadow p-4 border-t-4 border-maroon">
<p class="text-gray-600 text-sm font-semibold">Total Chat</p>
<p class="text-2xl font-bold text-maroon mt-1">{{ $chatHistories->count() }}</p>
</div>
<div class="bg-white rounded-lg shadow p-4 border-t-4 border-blue-400">
<p class="text-gray-600 text-sm font-semibold">Pertanyaan</p>
<p class="text-2xl font-bold text-blue-600 mt-1">{{ $chatHistories->count() }}</p>
</div>
<div class="bg-white rounded-lg shadow p-4 border-t-4 border-green-400">
<p class="text-gray-600 text-sm font-semibold">Jawaban</p>
<p class="text-2xl font-bold text-green-600 mt-1">{{ $chatHistories->count() }}</p>
</div>
</div>
<!-- Chat Messages -->
<div class="bg-white rounded-lg shadow p-6 space-y-4 max-w-4xl">
@forelse($chatHistories->sortBy('created_at') as $chat)
<div class="mb-6">
<p class="text-xs text-gray-500 text-center mb-3">
{{ $chat->created_at->format('d/m/Y H:i') }}
</p>
<!-- Question -->
<div class="flex justify-start mb-2">
<div class="bg-blue-100 text-blue-900 rounded-lg p-3 mb-2 max-w-md break-words">
<p class="text-sm font-semibold mb-1">Siswa 📤</p>
<p class="text-sm leading-relaxed">{{ $chat->prompt }}</p>
</div>
</div>
<!-- Answer -->
<div class="flex justify-end mb-2">
<div class="bg-green-100 text-green-900 rounded-lg p-3 mb-2 max-w-2xl break-words">
<p class="text-sm font-semibold mb-1 text-right">Konselor BK 📥</p>
<p class="text-sm leading-relaxed whitespace-pre-wrap">{{ $chat->response }}</p>
</div>
</div>
</div>
@if(!$loop->last)
<hr class="my-4 border-gray-200">
@endif
@empty
<div class="text-center py-12">
<p class="text-gray-500 text-sm">📭 Belum ada chat history untuk siswa ini</p>
</div>
@endforelse
</div>
<!-- Info Box -->
<div class="mt-8 p-4 bg-blue-50 border-l-4 border-blue-400 rounded max-w-4xl">
<p class="text-sm text-blue-800">
<strong> Info:</strong> Chat history menunjukkan interaksi siswa dengan Konselor BK AI
</p>
</div>
@endsection

View File

@ -0,0 +1,136 @@
@extends('admin.layouts.app')
@section('title', 'Dashboard')
@section('content')
<!-- Statistics Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<div class="stat-card bg-white rounded-lg shadow p-6 border-t-4 border-maroon">
<p class="text-gray-600 text-sm font-semibold">👥 Total Siswa</p>
<p class="text-3xl font-bold text-maroon mt-2">{{ $totalSiswa }}</p>
</div>
<div class="stat-card bg-white rounded-lg shadow p-6 border-t-4 border-yellow-400">
<p class="text-gray-600 text-sm font-semibold">🎯 Total Rekomendasi</p>
<p class="text-3xl font-bold mt-2" style="color: #EA580C;">{{ $totalRekomendasi }}</p>
</div>
<div class="stat-card bg-white rounded-lg shadow p-6 border-t-4 border-blue-400">
<p class="text-gray-600 text-sm font-semibold">💬 Chat History</p>
<p class="text-3xl font-bold text-blue-600 mt-2">{{ $totalChatHistory }}</p>
</div>
<div class="stat-card bg-white rounded-lg shadow p-6 border-t-4 border-green-400">
<p class="text-gray-600 text-sm font-semibold">🎓 Jurusan Tersedia</p>
<p class="text-3xl font-bold text-green-600 mt-2">{{ $totalJurusan }}</p>
</div>
</div>
<!-- Kelompok Distribution -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="bg-white rounded-lg shadow p-6 border-l-4 border-maroon">
<h3 class="text-lg font-bold text-maroon mb-4">📊 Siswa per Kelompok</h3>
<div class="space-y-3">
@foreach($kelompokStats as $stat)
<div class="flex items-center gap-3">
<span class="text-sm font-semibold text-gray-700 w-20">{{ $stat->kelompok_asal ?? 'Tidak Ada' }}</span>
<div class="flex-1 h-6 bg-gray-200 rounded">
<div class="h-full rounded flex items-center justify-center text-white text-xs font-bold"
style="width: {{ ($stat->count / $totalSiswa) * 100 }}%; background-color: {{ $stat->kelompok_asal == 'IPA' ? '#0369A1' : '#D97706' }};">
{{ $stat->count }}
</div>
</div>
</div>
@endforeach
</div>
</div>
<!-- Top Recommended Majors -->
<div class="bg-white rounded-lg shadow p-6 border-l-4 border-green-400">
<h3 class="text-lg font-bold text-maroon mb-4">🎯 Top Recommended Majors</h3>
@if($topMajors->isNotEmpty())
<div class="space-y-3">
@foreach($topMajors as $major)
<div class="flex items-center gap-3">
<span class="text-sm font-semibold text-gray-700 flex-1 truncate">{{ $major->major_name }}</span>
<span class="px-3 py-1 rounded bg-green-100 text-green-800 font-bold text-sm">{{ $major->count }}</span>
</div>
@endforeach
</div>
@else
<p class="text-gray-500 text-sm">Belum ada data rekomendasi</p>
@endif
</div>
</div>
<!-- Recent Students -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-yellow-400">
<h3 class="text-lg font-bold text-maroon mb-4">👥 Siswa Terbaru</h3>
@if($recentStudents->isNotEmpty())
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="border-b-2 border-maroon">
<tr>
<th class="text-left px-4 py-2 font-bold text-maroon">Nama</th>
<th class="text-center px-4 py-2 font-bold text-maroon">NIS</th>
<th class="text-center px-4 py-2 font-bold text-maroon">Kelompok</th>
<th class="text-center px-4 py-2 font-bold text-maroon">Aksi</th>
</tr>
</thead>
<tbody class="divide-y">
@foreach($recentStudents as $student)
<tr class="hover:bg-gray-50">
<td class="px-4 py-2 font-semibold text-gray-800">{{ $student->name }}</td>
<td class="px-4 py-2 text-center text-gray-600">{{ $student->nis ?? '-' }}</td>
<td class="px-4 py-2 text-center">
<span class="px-2 py-1 rounded text-xs font-bold" style="{{ $student->kelompok_asal == 'IPA' ? 'background-color: #E0F2FE; color: #0369A1;' : 'background-color: #FEF3C7; color: #92400E;' }}">
{{ $student->kelompok_asal ?? '-' }}
</span>
</td>
<td class="px-4 py-2 text-center">
<a href="{{ route('admin.student.detail', $student->id) }}" class="text-blue-600 hover:text-blue-800 font-semibold text-xs">👁 Lihat</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-gray-500 text-sm">Belum ada siswa terdaftar</p>
@endif
</div>
<!-- Recent Recommendations -->
<div class="bg-white rounded-lg shadow p-6 border-l-4 border-blue-400">
<h3 class="text-lg font-bold text-maroon mb-4">🎯 Rekomendasi Terbaru</h3>
@if($recentRecommendations->isNotEmpty())
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="border-b-2 border-maroon">
<tr>
<th class="text-left px-4 py-2 font-bold text-maroon">Siswa</th>
<th class="text-left px-4 py-2 font-bold text-maroon">Top Rekomendasi</th>
<th class="text-center px-4 py-2 font-bold text-maroon">Skor</th>
<th class="text-center px-4 py-2 font-bold text-maroon">Tanggal</th>
</tr>
</thead>
<tbody class="divide-y">
@foreach($recentRecommendations as $rec)
@php
$topJurusan = $rec->hasil_rekomendasi[0]['jurusan'] ?? '-';
$topSkor = round(($rec->hasil_rekomendasi[0]['skor'] ?? 0) * 100, 1);
@endphp
<tr class="hover:bg-gray-50">
<td class="px-4 py-2 font-semibold text-gray-800">{{ $rec->user->name }}</td>
<td class="px-4 py-2 text-gray-700">{{ $topJurusan }}</td>
<td class="px-4 py-2 text-center">
<span class="px-2 py-1 rounded bg-green-100 text-green-800 font-bold">{{ $topSkor }}%</span>
</td>
<td class="px-4 py-2 text-center text-gray-600">{{ $rec->created_at->format('d M Y') }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-gray-500 text-sm">Belum ada rekomendasi</p>
@endif
</div>
@endsection

View File

@ -0,0 +1,65 @@
@extends('admin.layouts.app')
@section('title', 'Tambah Guru BK')
@section('content')
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon"> Tambah Akun Guru BK</h2>
<p class="text-sm text-gray-500 mt-1">Buat akun baru untuk guru BK</p>
</div>
<a href="{{ route('admin.guru-bk') }}" class="bg-gray-400 text-white font-bold py-2 px-4 rounded-lg hover:bg-gray-500 transition text-sm">
Kembali
</a>
</div>
<div class="bg-white rounded-lg shadow p-6 max-w-2xl">
<form action="{{ route('admin.guru-bk.store') }}" method="POST" class="space-y-4">
@csrf
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Nama Lengkap *</label>
<input type="text" name="name" required value="{{ old('name') }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon" placeholder="Nama guru BK">
@error('name') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Email *</label>
<input type="email" name="email" required value="{{ old('email') }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon" placeholder="email@sekolah.id">
@error('email') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Password *</label>
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="guruBKPassword" name="password" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" placeholder="Minimal 8 karakter" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('guruBKPassword', this)">👁️</button>
</div>
@error('password') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Konfirmasi Password *</label>
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="guruBKPasswordConfirm" name="password_confirmation" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" placeholder="Ulangi password" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('guruBKPasswordConfirm', this)">👁️</button>
</div>
</div>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 gradient-maroon text-white font-bold py-3 px-4 rounded-lg hover:opacity-90 transition">
💾 Simpan
</button>
<a href="{{ route('admin.guru-bk') }}" class="flex-1 bg-gray-400 text-white font-bold py-3 px-4 rounded-lg hover:bg-gray-500 transition text-center">
Batal
</a>
</div>
</form>
</div>
@section('scripts')
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
@endsection
@endsection

View File

@ -0,0 +1,66 @@
@extends('admin.layouts.app')
@section('title', 'Edit Guru BK')
@section('content')
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon">✏️ Edit Akun Guru BK</h2>
<p class="text-sm text-gray-500 mt-1">{{ $guruBK->name }}</p>
</div>
<a href="{{ route('admin.guru-bk') }}" class="bg-gray-400 text-white font-bold py-2 px-4 rounded-lg hover:bg-gray-500 transition text-sm">
Kembali
</a>
</div>
<div class="bg-white rounded-lg shadow p-6 max-w-2xl">
<form action="{{ route('admin.guru-bk.update', $guruBK->id) }}" method="POST" class="space-y-4">
@csrf
@method('PUT')
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Nama Lengkap *</label>
<input type="text" name="name" required value="{{ old('name', $guruBK->name) }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon">
@error('name') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Email *</label>
<input type="email" name="email" required value="{{ old('email', $guruBK->email) }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon">
@error('email') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Password Baru <span class="text-gray-400 font-normal">(kosongkan jika tidak diubah)</span></label>
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="editGuruPassword" name="password" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" placeholder="Minimal 8 karakter" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('editGuruPassword', this)">👁️</button>
</div>
@error('password') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Konfirmasi Password Baru</label>
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="editGuruPasswordConfirm" name="password_confirmation" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" placeholder="Ulangi password baru" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('editGuruPasswordConfirm', this)">👁️</button>
</div>
</div>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 gradient-maroon text-white font-bold py-3 px-4 rounded-lg hover:opacity-90 transition">
💾 Update
</button>
<a href="{{ route('admin.guru-bk') }}" class="flex-1 bg-gray-400 text-white font-bold py-3 px-4 rounded-lg hover:bg-gray-500 transition text-center">
Batal
</a>
</div>
</form>
</div>
@section('scripts')
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
@endsection
@endsection

View File

@ -0,0 +1,62 @@
@extends('admin.layouts.app')
@section('title', 'Manajemen Akun Guru BK')
@section('content')
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon">👨‍🏫 Manajemen Akun Guru BK</h2>
<p class="text-sm text-gray-500 mt-1">Kelola daftar akun guru BK yang memiliki akses sistem</p>
</div>
<a href="{{ route('admin.guru-bk.create') }}" class="gradient-maroon text-white font-bold py-2 px-4 rounded-lg hover:opacity-90 transition text-sm">
+ Tambah Guru BK
</a>
</div>
<!-- Guru BK Table -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<table class="w-full text-sm">
<thead class="gradient-maroon text-white">
<tr>
<th class="px-4 py-3 text-left">No</th>
<th class="px-4 py-3 text-left">Nama</th>
<th class="px-4 py-3 text-left">Email</th>
<th class="px-4 py-3 text-center">Terdaftar</th>
<th class="px-4 py-3 text-center">Aksi</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse($guruBK as $index => $guru)
<tr class="hover:bg-gray-50 transition">
<td class="px-4 py-3 text-gray-600">{{ $guruBK->firstItem() + $index }}</td>
<td class="px-4 py-3 font-semibold text-gray-800">{{ $guru->name }}</td>
<td class="px-4 py-3 text-gray-600">{{ $guru->email }}</td>
<td class="px-4 py-3 text-center text-gray-500 text-xs">{{ $guru->created_at->format('d M Y') }}</td>
<td class="px-4 py-3 text-center space-x-1">
<a href="{{ route('admin.guru-bk.edit', $guru->id) }}" class="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs font-semibold hover:bg-blue-200 transition inline-block">
Edit
</a>
<form method="POST" action="{{ route('admin.guru-bk.destroy', $guru->id) }}" style="display:inline;" onsubmit="return confirm('Hapus akun guru BK ini?')">
@csrf
@method('DELETE')
<button type="submit" class="px-2 py-1 bg-red-100 text-red-700 rounded text-xs font-semibold hover:bg-red-200 transition">
Hapus
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-4 py-6 text-center text-gray-500">
📭 Belum ada akun guru BK. Tekan "+ Tambah Guru BK" untuk menambahkan.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($guruBK->hasPages())
<div class="mt-4">{{ $guruBK->links() }}</div>
@endif
@endsection

View File

@ -0,0 +1,121 @@
@extends('admin.layouts.app')
@section('title', 'Tambah Jurusan')
@section('content')
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon"> Tambah Jurusan Baru</h2>
<p class="text-sm text-gray-500 mt-1">Tambahkan jurusan baru ke dalam sistem rekomendasi</p>
</div>
<a href="{{ route('admin.jurusan') }}" class="bg-gray-400 text-white font-bold py-2 px-4 rounded-lg hover:bg-gray-500 transition text-sm">
Kembali
</a>
</div>
@if($errors->any())
<div class="bg-red-50 border-l-4 border-red-400 p-4 rounded-lg mb-6">
<ul class="text-red-800 text-sm list-disc list-inside">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('admin.jurusan.store') }}" method="POST" class="space-y-6">
@csrf
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-bold text-maroon mb-4">📋 Informasi Jurusan</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Nama Jurusan <span class="text-red-500">*</span></label>
<input type="text" name="nama_jurusan" value="{{ old('nama_jurusan') }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" placeholder="Contoh: Teknologi Informasi" required>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Deskripsi</label>
<textarea name="deskripsi" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" placeholder="Jelaskan singkat tentang jurusan ini">{{ old('deskripsi') }}</textarea>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Keywords (Kata Kunci Minat & Cita-cita)</label>
<textarea name="keywords" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" placeholder="Pisahkan dengan koma, contoh: programmer, developer, coding, software, web">{{ old('keywords') }}</textarea>
<p class="text-xs text-gray-500 mt-1">Keywords digunakan untuk mencocokkan minat dan cita-cita siswa dengan jurusan ini. Pisahkan dengan koma.</p>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Preferensi Studi</label>
<textarea name="preferensi_studi" rows="2" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" placeholder="Pisahkan dengan koma, contoh: Sains & Teknologi, Pertanian & Lingkungan">{{ old('preferensi_studi') }}</textarea>
<p class="text-xs text-gray-500 mt-1">Rumpun bidang studi yang cocok untuk jurusan ini. Pilihan: Sains & Teknologi, Pertanian & Lingkungan, Kesehatan & Ilmu Hayat, Bisnis & Manajemen, Sosial & Humaniora</p>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Prospek Kerja</label>
<textarea name="prospek_kerja" rows="2" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" placeholder="Contoh: Software Developer, Web Developer, Data Analyst">{{ old('prospek_kerja') }}</textarea>
</div>
</div>
</div>
<!-- Bobot Mata Pelajaran -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-bold text-maroon mb-4">⚖️ Bobot Mata Pelajaran</h3>
<p class="text-xs text-gray-500 mb-4">Tentukan bobot setiap mata pelajaran untuk jurusan ini (0.00 - 1.00). Mata pelajaran yang lebih relevan diberi bobot lebih tinggi. Jumlah total tidak harus 1.0.</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<h4 class="text-sm font-bold text-gray-700 mb-3 border-b pb-2">📐 IPA</h4>
<div class="space-y-3">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Matematika</label>
<input type="number" name="bobot_mtk" value="{{ old('bobot_mtk', '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Fisika</label>
<input type="number" name="bobot_fisika" value="{{ old('bobot_fisika', '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Kimia</label>
<input type="number" name="bobot_kimia" value="{{ old('bobot_kimia', '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Biologi</label>
<input type="number" name="bobot_biologi" value="{{ old('bobot_biologi', '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
</div>
</div>
<div>
<h4 class="text-sm font-bold text-gray-700 mb-3 border-b pb-2">📊 IPS</h4>
<div class="space-y-3">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Ekonomi</label>
<input type="number" name="bobot_ekonomi" value="{{ old('bobot_ekonomi', '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Geografi</label>
<input type="number" name="bobot_geografi" value="{{ old('bobot_geografi', '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Sosiologi</label>
<input type="number" name="bobot_sosiologi" value="{{ old('bobot_sosiologi', '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Sejarah</label>
<input type="number" name="bobot_sejarah" value="{{ old('bobot_sejarah', '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
</div>
</div>
</div>
</div>
<div class="flex gap-4">
<button type="submit" class="flex-1 gradient-maroon text-white font-bold py-3 px-4 rounded-lg hover:opacity-90 transition">
💾 Simpan Jurusan
</button>
<a href="{{ route('admin.jurusan') }}" class="flex-1 bg-gray-400 text-white font-bold py-3 px-4 rounded-lg hover:bg-gray-500 transition text-center">
Batal
</a>
</div>
</form>
@endsection

View File

@ -0,0 +1,132 @@
@extends('admin.layouts.app')
@section('title', 'Edit Jurusan')
@section('content')
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon">✏️ Edit Jurusan: {{ $jurusan->nama_jurusan }}</h2>
<p class="text-sm text-gray-500 mt-1">Ubah informasi jurusan</p>
</div>
<a href="{{ route('admin.jurusan') }}" class="bg-gray-400 text-white font-bold py-2 px-4 rounded-lg hover:bg-gray-500 transition text-sm">
Kembali
</a>
</div>
@if($errors->any())
<div class="bg-red-50 border-l-4 border-red-400 p-4 rounded-lg mb-6">
<ul class="text-red-800 text-sm list-disc list-inside">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('admin.jurusan.update', $jurusan->id) }}" method="POST" class="space-y-6">
@csrf
@method('PUT')
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-bold text-maroon mb-4">📋 Informasi Jurusan</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Nama Jurusan <span class="text-red-500">*</span></label>
<input type="text" name="nama_jurusan" value="{{ old('nama_jurusan', $jurusan->nama_jurusan) }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" required>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Deskripsi</label>
<textarea name="deskripsi" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" placeholder="Jelaskan singkat tentang jurusan ini">{{ old('deskripsi', $jurusan->deskripsi) }}</textarea>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Keywords (Kata Kunci Minat & Cita-cita)</label>
<textarea name="keywords" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" placeholder="Pisahkan dengan koma">{{ old('keywords', implode(', ', $jurusan->keywords ?? [])) }}</textarea>
<p class="text-xs text-gray-500 mt-1">Keywords digunakan untuk mencocokkan minat dan cita-cita siswa dengan jurusan ini. Pisahkan dengan koma.</p>
@if(!empty($jurusan->keywords))
<div class="flex flex-wrap gap-1 mt-2">
@foreach($jurusan->keywords as $kw)
<span class="inline-block px-2 py-0.5 rounded bg-blue-100 text-blue-700 text-xs">{{ $kw }}</span>
@endforeach
</div>
@endif
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Preferensi Studi</label>
<textarea name="preferensi_studi" rows="2" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" placeholder="Pisahkan dengan koma">{{ old('preferensi_studi', implode(', ', $jurusan->preferensi_studi ?? [])) }}</textarea>
<p class="text-xs text-gray-500 mt-1">Rumpun bidang studi yang cocok: Sains & Teknologi, Pertanian & Lingkungan, Kesehatan & Ilmu Hayat, Bisnis & Manajemen, Sosial & Humaniora</p>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Prospek Kerja</label>
<textarea name="prospek_kerja" rows="2" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon" placeholder="Contoh: Software Developer, Web Developer, Data Analyst">{{ old('prospek_kerja', $jurusan->prospek_kerja) }}</textarea>
</div>
</div>
</div>
<!-- Bobot Mata Pelajaran -->
@php
$bobot = $jurusan->bobot_mapel ?? [];
@endphp
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-bold text-maroon mb-4">⚖️ Bobot Mata Pelajaran</h3>
<p class="text-xs text-gray-500 mb-4">Tentukan bobot setiap mata pelajaran untuk jurusan ini (0.00 - 1.00). Mata pelajaran yang lebih relevan diberi bobot lebih tinggi. Jumlah total tidak harus 1.0.</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<h4 class="text-sm font-bold text-gray-700 mb-3 border-b pb-2">📐 IPA</h4>
<div class="space-y-3">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Matematika</label>
<input type="number" name="bobot_mtk" value="{{ old('bobot_mtk', $bobot['mtk'] ?? '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Fisika</label>
<input type="number" name="bobot_fisika" value="{{ old('bobot_fisika', $bobot['fisika'] ?? '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Kimia</label>
<input type="number" name="bobot_kimia" value="{{ old('bobot_kimia', $bobot['kimia'] ?? '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Biologi</label>
<input type="number" name="bobot_biologi" value="{{ old('bobot_biologi', $bobot['biologi'] ?? '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
</div>
</div>
<div>
<h4 class="text-sm font-bold text-gray-700 mb-3 border-b pb-2">📊 IPS</h4>
<div class="space-y-3">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Ekonomi</label>
<input type="number" name="bobot_ekonomi" value="{{ old('bobot_ekonomi', $bobot['ekonomi'] ?? '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Geografi</label>
<input type="number" name="bobot_geografi" value="{{ old('bobot_geografi', $bobot['geografi'] ?? '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Sosiologi</label>
<input type="number" name="bobot_sosiologi" value="{{ old('bobot_sosiologi', $bobot['sosiologi'] ?? '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Sejarah</label>
<input type="number" name="bobot_sejarah" value="{{ old('bobot_sejarah', $bobot['sejarah'] ?? '0.25') }}" step="0.05" min="0" max="1" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-maroon text-sm">
</div>
</div>
</div>
</div>
</div>
<div class="flex gap-4">
<button type="submit" class="flex-1 gradient-maroon text-white font-bold py-3 px-4 rounded-lg hover:opacity-90 transition">
💾 Simpan Perubahan
</button>
<a href="{{ route('admin.jurusan') }}" class="flex-1 bg-gray-400 text-white font-bold py-3 px-4 rounded-lg hover:bg-gray-500 transition text-center">
Batal
</a>
</div>
</form>
@endsection

View File

@ -0,0 +1,121 @@
@extends('admin.layouts.app')
@section('title', 'Manajemen Jurusan')
@section('content')
<!-- Page Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-3">
<div>
<h2 class="text-2xl font-bold text-maroon">🎓 Manajemen Jurusan</h2>
<p class="text-sm text-gray-500 mt-1">Kelola data jurusan Polije (tambah, edit, hapus)</p>
</div>
<a href="{{ route('admin.jurusan.create') }}" class="gradient-maroon text-white font-bold py-2 px-4 rounded-lg hover:opacity-90 transition text-sm">
+ Tambah Jurusan
</a>
</div>
@if(session('success'))
<div class="bg-green-50 border-l-4 border-green-400 p-4 rounded-lg mb-6">
<p class="text-green-800 text-sm font-semibold"> {{ session('success') }}</p>
</div>
@endif
@if(session('error'))
<div class="bg-red-50 border-l-4 border-red-400 p-4 rounded-lg mb-6">
<p class="text-red-800 text-sm font-semibold"> {{ session('error') }}</p>
</div>
@endif
<!-- Jurusan Table -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<table class="w-full text-sm">
<thead class="gradient-maroon text-white">
<tr>
<th class="px-4 py-3 text-left">No</th>
<th class="px-4 py-3 text-left">Nama Jurusan</th>
<th class="px-4 py-3 text-left hidden md:table-cell">Keywords</th>
<th class="px-4 py-3 text-left hidden lg:table-cell">Preferensi Studi</th>
<th class="px-4 py-3 text-center">Aksi</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse($jurusanList as $index => $jurusan)
<tr class="hover:bg-gray-50 transition">
<td class="px-4 py-3 text-gray-600">{{ $index + 1 }}</td>
<td class="px-4 py-3">
<p class="font-semibold text-maroon">{{ $jurusan->nama_jurusan }}</p>
@if($jurusan->deskripsi)
<p class="text-xs text-gray-500 mt-1 line-clamp-2">{{ Str::limit($jurusan->deskripsi, 80) }}</p>
@endif
</td>
<td class="px-4 py-3 hidden md:table-cell">
<div class="flex flex-wrap gap-1">
@foreach(array_slice($jurusan->keywords ?? [], 0, 5) as $kw)
<span class="inline-block px-2 py-0.5 rounded bg-blue-100 text-blue-700 text-xs">{{ $kw }}</span>
@endforeach
@if(count($jurusan->keywords ?? []) > 5)
<span class="inline-block px-2 py-0.5 rounded bg-gray-100 text-gray-500 text-xs">+{{ count($jurusan->keywords) - 5 }}</span>
@endif
</div>
</td>
<td class="px-4 py-3 hidden lg:table-cell">
<div class="flex flex-wrap gap-1">
@foreach($jurusan->preferensi_studi ?? [] as $ps)
<span class="inline-block px-2 py-0.5 rounded bg-green-100 text-green-700 text-xs">{{ $ps }}</span>
@endforeach
</div>
</td>
<td class="px-4 py-3 text-center">
<div class="flex items-center justify-center gap-2">
<a href="{{ route('admin.jurusan.edit', $jurusan->id) }}" class="px-3 py-1 bg-blue-100 text-blue-700 rounded text-xs font-semibold hover:bg-blue-200 transition">
✏️ Edit
</a>
<form action="{{ route('admin.jurusan.destroy', $jurusan->id) }}" method="POST" onsubmit="return confirm('Yakin ingin menghapus jurusan {{ $jurusan->nama_jurusan }}?')">
@csrf
@method('DELETE')
<button type="submit" class="px-3 py-1 bg-red-100 text-red-700 rounded text-xs font-semibold hover:bg-red-200 transition">
🗑️ Hapus
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-4 py-6 text-center text-gray-500">
Belum ada data jurusan. <a href="{{ route('admin.jurusan.create') }}" class="text-maroon font-semibold hover:underline">Tambah jurusan pertama</a>.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Variabel Input Info -->
<div class="mt-8 bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-bold text-maroon mb-4">📊 Kriteria Penilaian Rekomendasi</h3>
<p class="text-gray-700 text-sm mb-4">Sistem menggunakan 5 kriteria utama untuk memberikan rekomendasi jurusan yang tepat:</p>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="p-4 bg-blue-50 rounded-lg border-l-4 border-blue-400">
<p class="font-bold text-blue-800 text-sm">📝 Nilai Akademik (40%)</p>
<p class="text-xs text-blue-700 mt-1">IPA: MTK, Fisika, Kimia, Biologi<br>IPS: Ekonomi, Geografi, Sosiologi, Sejarah</p>
</div>
<div class="p-4 bg-green-50 rounded-lg border-l-4 border-green-400">
<p class="font-bold text-green-800 text-sm">💡 Minat & Bakat (35%)</p>
<p class="text-xs text-green-700 mt-1">Dicocokkan dengan keywords jurusan secara graduated</p>
</div>
<div class="p-4 bg-yellow-50 rounded-lg border-l-4 border-yellow-400">
<p class="font-bold text-yellow-800 text-sm">🎯 Preferensi Studi (15%)</p>
<p class="text-xs text-yellow-700 mt-1">Praktik Langsung, DuDi, Project Based, Blended Learning</p>
</div>
<div class="p-4 bg-purple-50 rounded-lg border-l-4 border-purple-400">
<p class="font-bold text-purple-800 text-sm">🏆 Prestasi (5%)</p>
<p class="text-xs text-purple-700 mt-1">Prestasi akademik dan non-akademik siswa</p>
</div>
<div class="p-4 bg-red-50 rounded-lg border-l-4 border-red-400">
<p class="font-bold text-red-800 text-sm">💼 Cita-cita (5%)</p>
<p class="text-xs text-red-700 mt-1">Dicocokkan dengan keywords jurusan secara graduated</p>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,180 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', 'Admin Panel') - SPK Jurusan Kuliah</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon { background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%); }
.text-maroon { color: #5B7B89; }
.border-maroon { border-color: #5B7B89; }
.bg-cream { background-color: #F8FAFC; }
.bg-maroon { background-color: #5B7B89; }
.hover\:bg-maroon:hover { background-color: #7B9BA5; }
.stat-card { transition: all 0.3s ease; }
.stat-card:hover { transform: translateY(-5px); box-shadow: 0 5px 15px rgba(91, 123, 137, 0.1); }
.sidebar-link { transition: all 0.2s ease; }
.sidebar-link:hover { background: rgba(255,255,255,0.1); }
.sidebar-link.active { background: #F0F4F8; color: #5B7B89 !important; }
@media (max-width: 768px) {
.sidebar-desktop { display: none; }
.sidebar-mobile.open { display: block; }
}
</style>
@yield('styles')
</head>
<body class="bg-cream">
<!-- Header -->
<header class="gradient-maroon text-white shadow-lg sticky top-0 z-50">
<div class="container mx-auto px-4 sm:px-6 py-4">
<div class="flex justify-between items-center">
<div class="flex items-center gap-3">
<!-- Mobile menu toggle -->
<button id="mobileMenuBtn" class="md:hidden text-white focus:outline-none">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
<div>
<h1 class="text-lg sm:text-xl md:text-2xl font-bold">🔧 Admin Panel</h1>
<p class="text-xs text-gray-200 font-semibold">Sistem Pemilihan Jurusan Kuliah</p>
</div>
</div>
<div class="relative">
<button id="profileDropdownBtn" class="bg-gray-100 font-bold py-2 px-4 rounded-lg hover:bg-gray-200 transition text-xs sm:text-sm flex items-center gap-2" style="color: #5B7B89;">
👤 {{ Auth::user()->name }}
<svg id="dropdownArrow" class="w-4 h-4 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<div id="profileDropdown" class="absolute right-0 mt-2 w-48 bg-white text-gray-800 rounded-lg shadow-lg hidden z-50">
<a href="{{ route('admin.profil') }}" class="block px-4 py-3 hover:bg-gray-50 text-xs sm:text-sm font-semibold border-b">
👤 Profil Admin
</a>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="block w-full text-left px-4 py-3 hover:bg-gray-50 text-xs sm:text-sm font-semibold text-red-600 rounded-b-lg">
🚪 Logout
</button>
</form>
</div>
</div>
</div>
</div>
</header>
<div class="flex min-h-screen">
<!-- Sidebar Desktop -->
<aside class="hidden md:block w-64 gradient-maroon text-white shadow-lg flex-shrink-0">
<nav class="p-4 space-y-1 sticky top-20">
<p class="text-xs text-gray-300 font-bold uppercase tracking-wider mb-4 px-4">Menu Utama</p>
<a href="{{ route('admin.dashboard') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
📊 Dashboard
</a>
<a href="{{ route('admin.students') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.students*') ? 'active' : '' }}">
👥 Manajemen Data Siswa
</a>
<a href="{{ route('admin.jurusan') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.jurusan*') ? 'active' : '' }}">
🎓 Manajemen Jurusan
</a>
<a href="{{ route('admin.guru-bk') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.guru-bk*') ? 'active' : '' }}">
👨‍🏫 Manajemen Akun Guru BK
</a>
<p class="text-xs text-gray-300 font-bold uppercase tracking-wider mt-6 mb-3 px-4">Riwayat</p>
<a href="{{ route('admin.riwayat-rekomendasi') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.riwayat-rekomendasi*') ? 'active' : '' }}">
🎯 Riwayat Rekomendasi
</a>
<a href="{{ route('admin.riwayat-chatbot') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.riwayat-chatbot*') ? 'active' : '' }}">
💬 Riwayat Konsultasi Chatbot
</a>
</nav>
</aside>
<!-- Mobile Sidebar Overlay -->
<div id="mobileSidebar" class="fixed inset-0 z-40 hidden">
<div class="absolute inset-0 bg-black bg-opacity-50" id="mobileOverlay"></div>
<aside class="relative w-64 h-full gradient-maroon text-white shadow-lg overflow-y-auto">
<div class="p-4 border-b border-white border-opacity-20 flex justify-between items-center">
<span class="font-bold">Menu Admin</span>
<button id="closeMobileMenu" class="text-white"></button>
</div>
<nav class="p-4 space-y-1">
<a href="{{ route('admin.dashboard') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
📊 Dashboard
</a>
<a href="{{ route('admin.students') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.students*') ? 'active' : '' }}">
👥 Manajemen Data Siswa
</a>
<a href="{{ route('admin.jurusan') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.jurusan*') ? 'active' : '' }}">
🎓 Manajemen Jurusan
</a>
<a href="{{ route('admin.guru-bk') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.guru-bk*') ? 'active' : '' }}">
👨‍🏫 Manajemen Akun Guru BK
</a>
<hr class="border-white border-opacity-20 my-3">
<a href="{{ route('admin.riwayat-rekomendasi') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.riwayat-rekomendasi*') ? 'active' : '' }}">
🎯 Riwayat Rekomendasi
</a>
<a href="{{ route('admin.riwayat-chatbot') }}" class="sidebar-link block px-4 py-3 rounded-lg font-semibold text-sm {{ request()->routeIs('admin.riwayat-chatbot*') ? 'active' : '' }}">
💬 Riwayat Konsultasi Chatbot
</a>
</nav>
</aside>
</div>
<!-- Main Content -->
<main class="flex-1 p-4 sm:p-6 lg:p-8 overflow-x-hidden">
<!-- Flash Messages -->
@if(session('success'))
<div class="mb-4 p-4 bg-green-100 border-l-4 border-green-500 text-green-700 rounded">
<p class="font-semibold"> {{ session('success') }}</p>
</div>
@endif
@if(session('error'))
<div class="mb-4 p-4 bg-red-100 border-l-4 border-red-500 text-red-700 rounded">
<p class="font-semibold"> {{ session('error') }}</p>
</div>
@endif
@yield('content')
</main>
</div>
<!-- Scripts -->
<script>
// Profile dropdown
const profileDropdownBtn = document.getElementById('profileDropdownBtn');
const profileDropdown = document.getElementById('profileDropdown');
const dropdownArrow = document.getElementById('dropdownArrow');
profileDropdownBtn.addEventListener('click', function(e) {
e.preventDefault();
profileDropdown.classList.toggle('hidden');
dropdownArrow.style.transform = profileDropdown.classList.contains('hidden') ? 'rotate(0deg)' : 'rotate(180deg)';
});
document.addEventListener('click', function(e) {
if (!profileDropdownBtn.contains(e.target) && !profileDropdown.contains(e.target)) {
profileDropdown.classList.add('hidden');
dropdownArrow.style.transform = 'rotate(0deg)';
}
});
// Mobile sidebar
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
const mobileSidebar = document.getElementById('mobileSidebar');
const mobileOverlay = document.getElementById('mobileOverlay');
const closeMobileMenu = document.getElementById('closeMobileMenu');
mobileMenuBtn.addEventListener('click', () => mobileSidebar.classList.remove('hidden'));
mobileOverlay.addEventListener('click', () => mobileSidebar.classList.add('hidden'));
closeMobileMenu.addEventListener('click', () => mobileSidebar.classList.add('hidden'));
</script>
@yield('scripts')
</body>
</html>

View File

@ -0,0 +1,87 @@
@extends('admin.layouts.app')
@section('title', 'Profil Admin')
@section('content')
<div class="mb-6">
<h2 class="text-2xl font-bold text-maroon">⚙️ Profil Admin</h2>
<p class="text-sm text-gray-500 mt-1">Kelola informasi akun administrator</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Update Profile -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-bold text-maroon mb-4">👤 Informasi Akun</h3>
<form action="{{ route('admin.profil.update') }}" method="POST" class="space-y-4">
@csrf
@method('PUT')
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Nama *</label>
<input type="text" name="name" required value="{{ old('name', $admin->name) }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon">
@error('name') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Email *</label>
<input type="email" name="email" required value="{{ old('email', $admin->email) }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon">
@error('email') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Role</label>
<input type="text" value="Administrator" readonly class="w-full px-4 py-2 border border-gray-200 rounded-lg bg-gray-100 text-gray-500 cursor-not-allowed">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Terdaftar Sejak</label>
<input type="text" value="{{ $admin->created_at->format('d M Y H:i') }}" readonly class="w-full px-4 py-2 border border-gray-200 rounded-lg bg-gray-100 text-gray-500 cursor-not-allowed">
</div>
<button type="submit" class="w-full gradient-maroon text-white font-bold py-3 px-4 rounded-lg hover:opacity-90 transition">
💾 Update Profil
</button>
</form>
</div>
<!-- Change Password -->
<div class="bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-bold text-maroon mb-4">🔒 Ubah Password</h3>
<form action="{{ route('admin.profil.password') }}" method="POST" class="space-y-4">
@csrf
@method('PUT')
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Password Lama *</label>
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="currentPass" name="current_password" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" placeholder="Masukkan password lama" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('currentPass', this)">👁️</button>
</div>
@error('current_password') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Password Baru *</label>
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="newPass" name="password" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" placeholder="Minimal 8 karakter" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('newPass', this)">👁️</button>
</div>
@error('password') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Konfirmasi Password Baru *</label>
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="newPassConfirm" name="password_confirmation" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400" placeholder="Ulangi password baru" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('newPassConfirm', this)">👁️</button>
</div>
</div>
<button type="submit" class="w-full bg-blue-600 text-white font-bold py-3 px-4 rounded-lg hover:bg-blue-700 transition">
🔑 Ubah Password
</button>
</form>
</div>
</div>
@endsection
@section('scripts')
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
@endsection

View File

@ -0,0 +1,92 @@
@extends('admin.layouts.app')
@section('title', 'Riwayat Konsultasi Chatbot')
@section('content')
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon">💬 Riwayat Konsultasi Chatbot</h2>
<p class="text-sm text-gray-500 mt-1">Seluruh riwayat konsultasi siswa dengan AI Konselor BK</p>
</div>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
<div class="bg-white rounded-lg shadow p-4 border-t-4 border-maroon stat-card">
<p class="text-gray-600 text-sm font-semibold">Total Percakapan</p>
<p class="text-2xl font-bold text-maroon mt-1">{{ $chatHistories->total() }}</p>
</div>
<div class="bg-white rounded-lg shadow p-4 border-t-4 border-blue-400 stat-card">
<p class="text-gray-600 text-sm font-semibold">Siswa Unik</p>
<p class="text-2xl font-bold text-blue-600 mt-1">{{ $uniqueStudents }}</p>
</div>
<div class="bg-white rounded-lg shadow p-4 border-t-4 border-green-400 stat-card">
<p class="text-gray-600 text-sm font-semibold">Hari Ini</p>
<p class="text-2xl font-bold text-green-600 mt-1">{{ $todayCount }}</p>
</div>
</div>
<!-- Filter -->
<div class="bg-white rounded-lg shadow p-4 mb-6 border-l-4 border-maroon">
<form method="GET" class="flex gap-3 flex-col sm:flex-row">
<input type="text" name="search" placeholder="Cari nama siswa atau isi percakapan..." class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon" value="{{ request('search') }}">
<button type="submit" class="gradient-maroon text-white font-bold px-6 py-2 rounded-lg hover:opacity-90 transition">
🔍 Cari
</button>
@if(request('search'))
<a href="{{ route('admin.riwayat-chatbot') }}" class="bg-gray-400 text-white font-bold px-4 py-2 rounded-lg hover:bg-gray-500 transition text-center">
Reset
</a>
@endif
</form>
</div>
<!-- Table -->
<div class="bg-white rounded-lg shadow overflow-x-auto">
<table class="w-full text-sm">
<thead class="gradient-maroon text-white">
<tr>
<th class="px-4 py-3 text-left">No</th>
<th class="px-4 py-3 text-left">Nama Siswa</th>
<th class="px-4 py-3 text-left">Pertanyaan</th>
<th class="px-4 py-3 text-left">Jawaban AI</th>
<th class="px-4 py-3 text-center">Tanggal</th>
<th class="px-4 py-3 text-center">Aksi</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse($chatHistories as $idx => $chat)
<tr class="hover:bg-gray-50 transition">
<td class="px-4 py-3 text-gray-600">{{ $chatHistories->firstItem() + $idx }}</td>
<td class="px-4 py-3 font-semibold text-gray-800">{{ $chat->user->name ?? 'Deleted User' }}</td>
<td class="px-4 py-3 text-gray-600 text-xs max-w-xs">
<div class="bg-blue-50 border-l-2 border-blue-400 p-2 rounded">
{{ \Illuminate\Support\Str::limit($chat->prompt, 80) }}
</div>
</td>
<td class="px-4 py-3 text-gray-600 text-xs max-w-xs">
<div class="bg-green-50 border-l-2 border-green-400 p-2 rounded">
{{ \Illuminate\Support\Str::limit($chat->response, 80) }}
</div>
</td>
<td class="px-4 py-3 text-center text-gray-500 text-xs">{{ $chat->created_at->format('d M Y H:i') }}</td>
<td class="px-4 py-3 text-center">
<a href="{{ route('admin.student.detail', $chat->user_id) }}" class="text-blue-600 hover:text-blue-800 font-semibold text-xs">👁 Detail Siswa</a>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-4 py-8 text-center text-gray-500">
Belum ada data konsultasi chatbot
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="mt-6">
{{ $chatHistories->withQueryString()->links() }}
</div>
@endsection

View File

@ -0,0 +1,108 @@
@extends('admin.layouts.app')
@section('title', 'Riwayat Rekomendasi Siswa')
@section('content')
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon">🎯 Riwayat Rekomendasi Siswa</h2>
<p class="text-sm text-gray-500 mt-1">Seluruh hasil rekomendasi jurusan yang pernah dilakukan siswa</p>
</div>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-6">
<div class="bg-white rounded-lg shadow p-4 border-t-4 border-maroon stat-card">
<p class="text-gray-600 text-sm font-semibold">Total Rekomendasi</p>
<p class="text-2xl font-bold text-maroon mt-1">{{ $recommendations->total() }}</p>
</div>
<div class="bg-white rounded-lg shadow p-4 border-t-4 border-blue-400 stat-card">
<p class="text-gray-600 text-sm font-semibold">Siswa Unik</p>
<p class="text-2xl font-bold text-blue-600 mt-1">{{ $uniqueStudents }}</p>
</div>
<div class="bg-white rounded-lg shadow p-4 border-t-4 border-green-400 stat-card">
<p class="text-gray-600 text-sm font-semibold">Jurusan Terpopuler</p>
<p class="text-lg font-bold text-green-600 mt-1">{{ $topMajor ?? '-' }}</p>
</div>
</div>
<!-- Filter -->
<div class="bg-white rounded-lg shadow p-4 mb-6 border-l-4 border-maroon">
<form method="GET" class="flex gap-3 flex-col sm:flex-row">
<input type="text" name="search" placeholder="Cari nama siswa..." class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon" value="{{ request('search') }}">
<button type="submit" class="gradient-maroon text-white font-bold px-6 py-2 rounded-lg hover:opacity-90 transition">
🔍 Cari
</button>
@if(request('search'))
<a href="{{ route('admin.riwayat-rekomendasi') }}" class="bg-gray-400 text-white font-bold px-4 py-2 rounded-lg hover:bg-gray-500 transition text-center">
Reset
</a>
@endif
</form>
</div>
<!-- Table -->
<div class="bg-white rounded-lg shadow overflow-x-auto">
<table class="w-full text-sm">
<thead class="gradient-maroon text-white">
<tr>
<th class="px-4 py-3 text-left">No</th>
<th class="px-4 py-3 text-left">Nama Siswa</th>
<th class="px-4 py-3 text-left">Kelompok</th>
<th class="px-4 py-3 text-left">Minat</th>
<th class="px-4 py-3 text-left">Top 3 Rekomendasi</th>
<th class="px-4 py-3 text-center">Tanggal</th>
<th class="px-4 py-3 text-center">Aksi</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse($recommendations as $idx => $rec)
<tr class="hover:bg-gray-50 transition">
<td class="px-4 py-3 text-gray-600">{{ $recommendations->firstItem() + $idx }}</td>
<td class="px-4 py-3 font-semibold text-gray-800">{{ $rec->user->name ?? 'Deleted User' }}</td>
<td class="px-4 py-3">
@if($rec->user && $rec->user->kelompok_asal)
<span class="px-2 py-1 rounded text-xs font-bold" style="{{ $rec->user->kelompok_asal == 'IPA' ? 'background-color: #E0F2FE; color: #0369A1;' : 'background-color: #FEF3C7; color: #92400E;' }}">
{{ $rec->user->kelompok_asal }}
</span>
@else
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-4 py-3 text-gray-600 text-xs">{{ \Illuminate\Support\Str::limit($rec->minat, 30) }}</td>
<td class="px-4 py-3">
@if($rec->hasil_rekomendasi && is_array($rec->hasil_rekomendasi))
<div class="space-y-1">
@foreach(array_slice($rec->hasil_rekomendasi, 0, 3) as $i => $hasil)
<div class="flex items-center gap-2">
<span class="w-5 h-5 flex items-center justify-center rounded-full text-xs font-bold {{ $i === 0 ? 'bg-yellow-400 text-yellow-900' : 'bg-gray-200 text-gray-600' }}">{{ $i + 1 }}</span>
<span class="text-xs text-gray-700">{{ $hasil['jurusan'] ?? 'N/A' }}</span>
<span class="text-xs font-semibold {{ $i === 0 ? 'text-blue-600' : 'text-gray-400' }}">{{ round(($hasil['skor'] ?? 0) * 100, 1) }}%</span>
</div>
@endforeach
</div>
@else
<span class="text-gray-400 text-xs">-</span>
@endif
</td>
<td class="px-4 py-3 text-center text-gray-500 text-xs">{{ $rec->created_at->format('d M Y H:i') }}</td>
<td class="px-4 py-3 text-center">
<a href="{{ route('admin.student.detail', $rec->user_id) }}" class="text-blue-600 hover:text-blue-800 font-semibold text-xs">👁 Detail Siswa</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-8 text-center text-gray-500">
Belum ada data rekomendasi
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="mt-6">
{{ $recommendations->withQueryString()->links() }}
</div>
@endsection

View File

@ -0,0 +1,131 @@
@extends('admin.layouts.app')
@section('title', 'Detail Siswa - ' . $student->name)
@section('content')
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon">👤 Detail Siswa</h2>
<p class="text-sm text-gray-500 mt-1">{{ $student->name }}</p>
</div>
<a href="{{ route('admin.students') }}" class="bg-gray-400 text-white font-bold py-2 px-4 rounded-lg hover:bg-gray-500 transition text-sm">
Kembali
</a>
</div>
<!-- Profile Card -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-6 border-l-4 border-maroon">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<p class="text-gray-600 text-sm font-semibold">Nama</p>
<p class="text-xl font-bold text-maroon mt-1">{{ $student->name }}</p>
</div>
<div>
<p class="text-gray-600 text-sm font-semibold">Email</p>
<p class="text-gray-800 mt-1">{{ $student->email }}</p>
</div>
<div>
<p class="text-gray-600 text-sm font-semibold">NIS</p>
<p class="text-gray-800 font-semibold mt-1">{{ $student->nis ?? '-' }}</p>
</div>
<div>
<p class="text-gray-600 text-sm font-semibold">Kelompok Asal</p>
@if($student->kelompok_asal)
<p class="mt-1">
<span class="px-3 py-1 rounded text-sm font-bold" style="{{ $student->kelompok_asal == 'IPA' ? 'background-color: #E0F2FE; color: #0369A1;' : 'background-color: #FEF3C7; color: #92400E;' }}">
{{ $student->kelompok_asal }}
</span>
</p>
@else
<p class="text-gray-500 mt-1">-</p>
@endif
</div>
<div>
<p class="text-gray-600 text-sm font-semibold">Foto Profil</p>
@if($student->foto)
<img src="{{ Storage::url($student->foto) }}" alt="{{ $student->name }}" class="w-24 h-24 rounded-lg object-cover mt-2 border-2 border-maroon">
@else
<p class="text-gray-500 mt-1">-</p>
@endif
</div>
<div>
<p class="text-gray-600 text-sm font-semibold">Terdaftar</p>
<p class="text-gray-800 mt-1">{{ $student->created_at->format('d M Y H:i') }}</p>
</div>
</div>
</div>
<!-- Rekomendasi -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-green-400">
<h3 class="text-lg font-bold text-maroon mb-4">🎯 Riwayat Rekomendasi ({{ count($recommendations) }})</h3>
@if($recommendations->isNotEmpty())
<div class="space-y-4">
@foreach($recommendations as $rec)
<div class="border border-gray-200 rounded-lg p-4 hover:bg-gray-50 transition">
<div class="flex justify-between items-start mb-3">
<p class="text-sm text-gray-500">{{ $rec->created_at->format('d M Y H:i') }}</p>
<span class="px-2 py-1 rounded text-xs font-bold bg-blue-100 text-blue-800">Rekomendasi #{{ $loop->index + 1 }}</span>
</div>
@if($rec->hasil_rekomendasi && is_array($rec->hasil_rekomendasi))
<div class="mt-3">
<p class="text-xs font-semibold text-gray-600 mb-2">Top 3 Rekomendasi:</p>
<div class="space-y-2">
@foreach(array_slice($rec->hasil_rekomendasi, 0, 3) as $idx => $hasil)
<div class="flex items-center justify-between">
<span class="text-sm text-gray-700">{{ $idx + 1 }}. {{ $hasil['jurusan'] ?? 'N/A' }}</span>
<span class="px-2 py-1 rounded text-xs font-bold" style="background-color: {{ $idx === 0 ? '#DBEAFE' : '#F3F4F6' }}; color: {{ $idx === 0 ? '#1e40af' : '#6B7280' }};">
{{ round(($hasil['skor'] ?? 0) * 100, 1) }}%
</span>
</div>
@endforeach
</div>
</div>
@endif
<div class="mt-3 p-3 bg-gray-50 rounded text-xs text-gray-600">
<p><strong>Minat:</strong> {{ $rec->minat ?? '-' }} | <strong>Cita-cita:</strong> {{ $rec->cita_cita ?? '-' }}</p>
</div>
</div>
@endforeach
</div>
@else
<p class="text-gray-500 text-sm">Siswa belum melakukan rekomendasi</p>
@endif
</div>
<!-- Chat History -->
<div class="bg-white rounded-lg shadow p-6 border-l-4 border-blue-400">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold text-maroon">💬 Chat History ({{ count($chatHistories) }})</h3>
@if(count($chatHistories) > 0)
<a href="{{ route('admin.student.chat', $student->id) }}" class="bg-blue-500 text-white font-semibold py-2 px-3 rounded-lg hover:bg-blue-600 transition text-xs">
Lihat Semua
</a>
@endif
</div>
@if($chatHistories->isNotEmpty())
<div class="space-y-3 max-h-96 overflow-y-auto">
@foreach($chatHistories as $chat)
<div class="border-b pb-3 last:border-b-0">
<div class="flex justify-between items-start mb-2">
<p class="text-xs font-semibold text-gray-600">{{ $chat->created_at->format('d M Y H:i') }}</p>
</div>
<div class="bg-blue-50 border-l-4 border-blue-400 p-3 rounded mb-2">
<p class="text-xs font-semibold text-gray-700 mb-1">👤 Pertanyaan Siswa:</p>
<p class="text-sm text-gray-800">{{ \Illuminate\Support\Str::limit($chat->prompt, 150) }}</p>
</div>
<div class="bg-green-50 border-l-4 border-green-400 p-3 rounded">
<p class="text-xs font-semibold text-gray-700 mb-1">🤖 Jawaban AI:</p>
<p class="text-sm text-gray-800">{{ \Illuminate\Support\Str::limit($chat->response, 150) }}</p>
</div>
</div>
@endforeach
</div>
@else
<p class="text-gray-500 text-sm">Siswa belum melakukan chat dengan AI</p>
@endif
</div>
@endsection

View File

@ -0,0 +1,91 @@
@extends('admin.layouts.app')
@section('title', 'Manajemen Data Siswa')
@section('content')
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<div>
<h2 class="text-2xl font-bold text-maroon">👥 Manajemen Data Siswa</h2>
<p class="text-sm text-gray-500 mt-1">Total: {{ $students->total() }} Siswa</p>
</div>
</div>
<!-- Search & Filter -->
<div class="bg-white rounded-lg shadow p-4 mb-6 border-l-4 border-maroon">
<form method="GET" class="flex gap-3 flex-col sm:flex-row">
<input type="text" name="search" placeholder="Cari nama atau NIS..." class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon" value="{{ request('search') }}">
<select name="kelompok" class="px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-maroon">
<option value="">-- Semua Kelompok --</option>
<option value="IPA" {{ request('kelompok') == 'IPA' ? 'selected' : '' }}>IPA</option>
<option value="IPS" {{ request('kelompok') == 'IPS' ? 'selected' : '' }}>IPS</option>
</select>
<button type="submit" class="gradient-maroon text-white font-bold px-6 py-2 rounded-lg hover:opacity-90 transition">
🔍 Cari
</button>
@if(request('search') || request('kelompok'))
<a href="{{ route('admin.students') }}" class="bg-gray-400 text-white font-bold px-4 py-2 rounded-lg hover:bg-gray-500 transition text-center">
Reset
</a>
@endif
</form>
</div>
<!-- Students Table -->
<div class="bg-white rounded-lg shadow overflow-x-auto">
<table class="w-full text-sm">
<thead class="gradient-maroon text-white">
<tr>
<th class="px-4 py-3 text-left">Nama</th>
<th class="px-4 py-3 text-left">Email</th>
<th class="px-4 py-3 text-center">NIS</th>
<th class="px-4 py-3 text-center">Kelompok</th>
<th class="px-4 py-3 text-center">Rekomendasi</th>
<th class="px-4 py-3 text-center">Chat</th>
<th class="px-4 py-3 text-center">Aksi</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse($students as $student)
<tr class="hover:bg-gray-50 transition">
<td class="px-4 py-3 font-semibold text-gray-800">{{ $student->name }}</td>
<td class="px-4 py-3 text-gray-600 text-xs">{{ $student->email }}</td>
<td class="px-4 py-3 text-center text-gray-600">{{ $student->nis ?? '-' }}</td>
<td class="px-4 py-3 text-center">
@if($student->kelompok_asal)
<span class="px-2 py-1 rounded text-xs font-bold" style="{{ $student->kelompok_asal == 'IPA' ? 'background-color: #E0F2FE; color: #0369A1;' : 'background-color: #FEF3C7; color: #92400E;' }}">
{{ $student->kelompok_asal }}
</span>
@else
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-4 py-3 text-center">
<span class="px-3 py-1 rounded text-xs font-bold" style="{{ $student->recommendations_count > 0 ? 'background-color: #DBEAFE; color: #1e40af;' : 'background-color: #F3F4F6; color: #9CA3AF;' }}">
{{ $student->recommendations_count }}
</span>
</td>
<td class="px-4 py-3 text-center">
<span class="px-3 py-1 rounded text-xs font-bold" style="{{ $student->chat_histories_count > 0 ? 'background-color: #D1FAE5; color: #065F46;' : 'background-color: #F3F4F6; color: #9CA3AF;' }}">
{{ $student->chat_histories_count }}
</span>
</td>
<td class="px-4 py-3 text-center">
<a href="{{ route('admin.student.detail', $student->id) }}" class="text-blue-600 hover:text-blue-800 font-semibold text-xs">👁 Lihat</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-6 text-center text-gray-500">
Tidak ada siswa yang ditemukan
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="mt-6">
{{ $students->withQueryString()->links() }}
</div>
@endsection

View File

@ -0,0 +1,236 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Input Alumni Baru - Sistem Pemilihan Jurusan</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #5B7B89;
}
.border-maroon {
border-color: #5B7B89;
}
.bg-cream {
background-color: #F8FAFC;
}
.focus-maroon:focus {
border-color: #5B7B89;
box-shadow: 0 0 0 3px rgba(107, 44, 44, 0.1);
}
</style>
</head>
<body class="bg-cream">
<!-- Header -->
<header class="gradient-maroon text-white shadow-lg sticky top-0 z-50">
<div class="container mx-auto px-4 sm:px-6 py-4 sm:py-6 flex justify-between items-center">
<div>
<h1 class="text-xl sm:text-2xl md:text-3xl font-bold">Input Alumni Baru</h1>
<p class="text-xs sm:text-sm text-yellow-300 font-semibold mt-1">Tambah data alumni untuk validasi algoritma</p>
</div>
<a href="{{ route('alumni.index') }}" class="bg-yellow-400 text-maroon font-bold py-2 px-4 rounded-lg hover:bg-yellow-300 transition text-xs sm:text-sm">
Kembali
</a>
</div>
</header>
<!-- Main Content -->
<div class="container mx-auto px-4 sm:px-6 py-6 sm:py-12">
@if($errors->any())
<div class="mb-6 p-4 bg-red-100 border-l-4 border-red-500 text-red-700 rounded">
<p class="font-bold mb-2"> Ada kesalahan validasi:</p>
<ul class="list-disc pl-5 text-sm">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('alumni.store') }}" method="POST" class="max-w-4xl mx-auto">
@csrf
<!-- Section 1: Data Dasar Alumni -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-maroon">
<h2 class="text-lg font-bold text-maroon mb-4">📋 Data Dasar Alumni</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Nama Alumni *</label>
<input type="text" name="nama_alumni" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('nama_alumni') }}" placeholder="Nama lengkap">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">NIS</label>
<input type="text" name="nis" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('nis') }}" placeholder="12345">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Kelompok Asal *</label>
<select name="kelompok_asal" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
<option value="">-- Pilih --</option>
<option value="IPA" {{ old('kelompok_asal') == 'IPA' ? 'selected' : '' }}>IPA</option>
<option value="IPS" {{ old('kelompok_asal') == 'IPS' ? 'selected' : '' }}>IPS</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Tahun Masuk *</label>
<input type="number" name="tahun_masuk" required min="2020" max="{{ date('Y') }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('tahun_masuk') }}" placeholder="2023">
</div>
</div>
</div>
<!-- Section 2: Nilai Saat Entry -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-yellow-400">
<h2 class="text-lg font-bold text-maroon mb-4">📊 Nilai Saat Entry (Rapor)</h2>
<div id="nilaiFields" class="grid grid-cols-2 md:grid-cols-4 gap-3">
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Matematika</label>
<input type="number" name="mtk" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('mtk') }}" placeholder="85">
</div>
<div id="ipa-fields" style="display: none;" class="contents">
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Fisika</label>
<input type="number" name="fisika" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('fisika') }}" placeholder="78">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Kimia</label>
<input type="number" name="kimia" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('kimia') }}" placeholder="72">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Biologi</label>
<input type="number" name="biologi" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('biologi') }}" placeholder="80">
</div>
</div>
<div id="ips-fields" style="display: none;" class="contents">
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Ekonomi</label>
<input type="number" name="ekonomi" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('ekonomi') }}" placeholder="82">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Geografi</label>
<input type="number" name="geografi" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('geografi') }}" placeholder="76">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Sosiologi</label>
<input type="number" name="sosiologi" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('sosiologi') }}" placeholder="74">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Sejarah</label>
<input type="number" name="sejarah" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('sejarah') }}" placeholder="70">
</div>
</div>
</div>
</div>
<!-- Section 3: Variabel Non-Akademik -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-blue-400">
<h2 class="text-lg font-bold text-maroon mb-4">💡 Variabel Non-Akademik</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Minat</label>
<input type="text" name="minat" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('minat') }}" placeholder="Contoh: coding, bercocok tanam">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Cita-cita / Profesi</label>
<input type="text" name="cita_cita" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('cita_cita') }}" placeholder="Contoh: Programmer, Dokter">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Preferensi Studi</label>
<select name="preferensi_studi" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
<option value="">-- Pilih --</option>
<option value="Praktik Langsung" {{ old('preferensi_studi') == 'Praktik Langsung' ? 'selected' : '' }}>Praktik Langsung</option>
<option value="DuDi" {{ old('preferensi_studi') == 'DuDi' ? 'selected' : '' }}>DuDi (Dunia Usaha & Industri)</option>
<option value="Project Based" {{ old('preferensi_studi') == 'Project Based' ? 'selected' : '' }}>Project Based</option>
<option value="Blended" {{ old('preferensi_studi') == 'Blended' ? 'selected' : '' }}>Blended (Teori + Praktik)</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Prestasi (opsional)</label>
<input type="text" name="prestasi" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('prestasi') }}" placeholder="Contoh: Juara lomba, sertifikat">
</div>
</div>
</div>
<!-- Section 4: Rekomendasi & Hasil -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-green-400">
<h2 class="text-lg font-bold text-maroon mb-4">🎯 Rekomendasi & Hasil Algoritma</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Jurusan Masuk *</label>
<input type="text" name="major_masuk" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('major_masuk') }}" placeholder="Contoh: Teknologi Informasi">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Ranking Rekomendasi (saat input)</label>
<input type="number" name="ranking_saat_rekomendasi" min="1" max="9" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('ranking_saat_rekomendasi') }}" placeholder="1-9">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Score Prediksi</label>
<input type="number" name="predicted_score" step="0.01" min="0" max="1" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('predicted_score') }}" placeholder="0.95">
</div>
</div>
</div>
<!-- Section 5: Outcome Alumni -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-purple-400">
<h2 class="text-lg font-bold text-maroon mb-4">🎓 Outcome Alumni</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">IPK Lulus</label>
<input type="number" name="ipk_lulus" step="0.01" min="0" max="4" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('ipk_lulus') }}" placeholder="3.65">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Tahun Lulus</label>
<input type="number" name="tahun_lulus" min="2020" max="{{ date('Y') }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('tahun_lulus') }}" placeholder="2026">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-semibold text-gray-700 mb-2">Karir Outcome (deskripsi)</label>
<textarea name="karir_outcome" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" placeholder="Contoh: Bekerja di PT ABC sebagai Software Developer">{{ old('karir_outcome') }}</textarea>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Status Kesuksesan</label>
<select name="success_status" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
<option value="">-- Pilih --</option>
<option value="sangat_sukses" {{ old('success_status') == 'sangat_sukses' ? 'selected' : '' }}> Sangat Sukses</option>
<option value="sukses" {{ old('success_status') == 'sukses' ? 'selected' : '' }}> Sukses</option>
<option value="cukup" {{ old('success_status') == 'cukup' ? 'selected' : '' }}> Cukup</option>
<option value="kurang_sukses" {{ old('success_status') == 'kurang_sukses' ? 'selected' : '' }}> Kurang Sukses</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Catatan Tambahan</label>
<textarea name="catatan" rows="2" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" placeholder="Catatan / observasi tambahan">{{ old('catatan') }}</textarea>
</div>
</div>
</div>
<!-- Submit Buttons -->
<div class="flex gap-4 justify-end">
<a href="{{ route('alumni.index') }}" class="px-6 py-2 rounded-lg font-bold bg-gray-300 text-gray-700 hover:bg-gray-400 transition">
Batal
</a>
<button type="submit" class="px-6 py-2 rounded-lg font-bold gradient-maroon text-white hover:opacity-90 transition">
💾 Simpan Alumni
</button>
</div>
</form>
</div>
<script>
// Tampilkan field nilai berdasarkan kelompok asal
const kelompokSelect = document.querySelector('select[name="kelompok_asal"]');
const ipaFields = document.getElementById('ipa-fields');
const ipsFields = document.getElementById('ips-fields');
function updateNilaiFields() {
const kelompok = kelompokSelect.value;
ipaFields.style.display = kelompok === 'IPA' ? 'contents' : 'none';
ipsFields.style.display = kelompok === 'IPS' ? 'contents' : 'none';
}
kelompokSelect.addEventListener('change', updateNilaiFields);
updateNilaiFields(); // Call on load
</script>
</body>
</html>

View File

@ -0,0 +1,235 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Alumni - Sistem Pemilihan Jurusan</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #5B7B89;
}
.border-maroon {
border-color: #5B7B89;
}
.bg-cream {
background-color: #F8FAFC;
}
.focus-maroon:focus {
border-color: #5B7B89;
box-shadow: 0 0 0 3px rgba(107, 44, 44, 0.1);
}
</style>
</head>
<body class="bg-cream">
<!-- Header -->
<header class="gradient-maroon text-white shadow-lg sticky top-0 z-50">
<div class="container mx-auto px-4 sm:px-6 py-4 sm:py-6 flex justify-between items-center">
<div>
<h1 class="text-xl sm:text-2xl md:text-3xl font-bold">Edit Alumni</h1>
<p class="text-xs sm:text-sm text-yellow-300 font-semibold mt-1">{{ $alumnus->nama_alumni }}</p>
</div>
<a href="{{ route('alumni.index') }}" class="bg-yellow-400 text-maroon font-bold py-2 px-4 rounded-lg hover:bg-yellow-300 transition text-xs sm:text-sm">
Kembali
</a>
</div>
</header>
<!-- Main Content -->
<div class="container mx-auto px-4 sm:px-6 py-6 sm:py-12">
@if($errors->any())
<div class="mb-6 p-4 bg-red-100 border-l-4 border-red-500 text-red-700 rounded">
<p class="font-bold mb-2"> Ada kesalahan validasi:</p>
<ul class="list-disc pl-5 text-sm">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('alumni.update', $alumnus->id) }}" method="POST" class="max-w-4xl mx-auto">
@csrf @method('PUT')
<!-- Section 1: Data Dasar Alumni -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-maroon">
<h2 class="text-lg font-bold text-maroon mb-4">📋 Data Dasar Alumni</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Nama Alumni *</label>
<input type="text" name="nama_alumni" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('nama_alumni', $alumnus->nama_alumni) }}" placeholder="Nama lengkap">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">NIS</label>
<input type="text" name="nis" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('nis', $alumnus->nis) }}" placeholder="12345">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Kelompok Asal *</label>
<select name="kelompok_asal" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
<option value="">-- Pilih --</option>
<option value="IPA" {{ old('kelompok_asal', $alumnus->kelompok_asal) == 'IPA' ? 'selected' : '' }}>IPA</option>
<option value="IPS" {{ old('kelompok_asal', $alumnus->kelompok_asal) == 'IPS' ? 'selected' : '' }}>IPS</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Tahun Masuk *</label>
<input type="number" name="tahun_masuk" required min="2020" max="{{ date('Y') }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('tahun_masuk', $alumnus->tahun_masuk) }}" placeholder="2023">
</div>
</div>
</div>
<!-- Section 2: Nilai Saat Entry -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-yellow-400">
<h2 class="text-lg font-bold text-maroon mb-4">📊 Nilai Saat Entry (Rapor)</h2>
<div id="nilaiFields" class="grid grid-cols-2 md:grid-cols-4 gap-3">
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Matematika</label>
<input type="number" name="mtk" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('mtk', $alumnus->mtk) }}" placeholder="85">
</div>
<div id="ipa-fields" style="display: none;" class="contents">
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Fisika</label>
<input type="number" name="fisika" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('fisika', $alumnus->fisika) }}" placeholder="78">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Kimia</label>
<input type="number" name="kimia" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('kimia', $alumnus->kimia) }}" placeholder="72">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Biologi</label>
<input type="number" name="biologi" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('biologi', $alumnus->biologi) }}" placeholder="80">
</div>
</div>
<div id="ips-fields" style="display: none;" class="contents">
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Ekonomi</label>
<input type="number" name="ekonomi" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('ekonomi', $alumnus->ekonomi) }}" placeholder="82">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Geografi</label>
<input type="number" name="geografi" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('geografi', $alumnus->geografi) }}" placeholder="76">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Sosiologi</label>
<input type="number" name="sosiologi" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('sosiologi', $alumnus->sosiologi) }}" placeholder="74">
</div>
<div>
<label class="block text-xs font-semibold text-gray-700 mb-1">Sejarah</label>
<input type="number" name="sejarah" step="0.01" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('sejarah', $alumnus->sejarah) }}" placeholder="70">
</div>
</div>
</div>
</div>
<!-- Section 3: Variabel Non-Akademik -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-blue-400">
<h2 class="text-lg font-bold text-maroon mb-4">💡 Variabel Non-Akademik</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Minat</label>
<input type="text" name="minat" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('minat', $alumnus->minat) }}" placeholder="Contoh: coding, bercocok tanam">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Cita-cita / Profesi</label>
<input type="text" name="cita_cita" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('cita_cita', $alumnus->cita_cita) }}" placeholder="Contoh: Programmer, Dokter">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Preferensi Studi</label>
<select name="preferensi_studi" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
<option value="">-- Pilih --</option>
<option value="Praktik Langsung" {{ old('preferensi_studi', $alumnus->preferensi_studi) == 'Praktik Langsung' ? 'selected' : '' }}>Praktik Langsung</option>
<option value="DuDi" {{ old('preferensi_studi', $alumnus->preferensi_studi) == 'DuDi' ? 'selected' : '' }}>DuDi (Dunia Usaha & Industri)</option>
<option value="Project Based" {{ old('preferensi_studi', $alumnus->preferensi_studi) == 'Project Based' ? 'selected' : '' }}>Project Based</option>
<option value="Blended" {{ old('preferensi_studi', $alumnus->preferensi_studi) == 'Blended' ? 'selected' : '' }}>Blended (Teori + Praktik)</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Prestasi (opsional)</label>
<input type="text" name="prestasi" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('prestasi', $alumnus->prestasi) }}" placeholder="Contoh: Juara lomba, sertifikat">
</div>
</div>
</div>
<!-- Section 4: Rekomendasi & Hasil -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-green-400">
<h2 class="text-lg font-bold text-maroon mb-4">🎯 Rekomendasi & Hasil Algoritma</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Jurusan Masuk *</label>
<input type="text" name="major_masuk" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('major_masuk', $alumnus->major_masuk) }}" placeholder="Contoh: Teknologi Informasi">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Ranking Rekomendasi (saat input)</label>
<input type="number" name="ranking_saat_rekomendasi" min="1" max="9" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('ranking_saat_rekomendasi', $alumnus->ranking_saat_rekomendasi) }}" placeholder="1-9">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Score Prediksi</label>
<input type="number" name="predicted_score" step="0.01" min="0" max="1" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('predicted_score', $alumnus->predicted_score) }}" placeholder="0.95">
</div>
</div>
</div>
<!-- Section 5: Outcome Alumni -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-purple-400">
<h2 class="text-lg font-bold text-maroon mb-4">🎓 Outcome Alumni</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">IPK Lulus</label>
<input type="number" name="ipk_lulus" step="0.01" min="0" max="4" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('ipk_lulus', $alumnus->ipk_lulus) }}" placeholder="3.65">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Tahun Lulus</label>
<input type="number" name="tahun_lulus" min="2020" max="{{ date('Y') }}" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" value="{{ old('tahun_lulus', $alumnus->tahun_lulus) }}" placeholder="2026">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-semibold text-gray-700 mb-2">Karir Outcome (deskripsi)</label>
<textarea name="karir_outcome" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" placeholder="Contoh: Bekerja di PT ABC sebagai Software Developer">{{ old('karir_outcome', $alumnus->karir_outcome) }}</textarea>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Status Kesuksesan</label>
<select name="success_status" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
<option value="">-- Pilih --</option>
<option value="sangat_sukses" {{ old('success_status', $alumnus->success_status) == 'sangat_sukses' ? 'selected' : '' }}> Sangat Sukses</option>
<option value="sukses" {{ old('success_status', $alumnus->success_status) == 'sukses' ? 'selected' : '' }}> Sukses</option>
<option value="cukup" {{ old('success_status', $alumnus->success_status) == 'cukup' ? 'selected' : '' }}> Cukup</option>
<option value="kurang_sukses" {{ old('success_status', $alumnus->success_status) == 'kurang_sukses' ? 'selected' : '' }}> Kurang Sukses</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">Catatan Tambahan</label>
<textarea name="catatan" rows="2" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm" placeholder="Catatan / observasi tambahan">{{ old('catatan', $alumnus->catatan) }}</textarea>
</div>
</div>
</div>
<!-- Submit Buttons -->
<div class="flex gap-4 justify-end">
<a href="{{ route('alumni.index') }}" class="px-6 py-2 rounded-lg font-bold bg-gray-300 text-gray-700 hover:bg-gray-400 transition">
Batal
</a>
<button type="submit" class="px-6 py-2 rounded-lg font-bold gradient-maroon text-white hover:opacity-90 transition">
💾 Update Alumni
</button>
</div>
</form>
</div>
<script>
const kelompokSelect = document.querySelector('select[name="kelompok_asal"]');
const ipaFields = document.getElementById('ipa-fields');
const ipsFields = document.getElementById('ips-fields');
function updateNilaiFields() {
const kelompok = kelompokSelect.value;
ipaFields.style.display = kelompok === 'IPA' ? 'contents' : 'none';
ipsFields.style.display = kelompok === 'IPS' ? 'contents' : 'none';
}
kelompokSelect.addEventListener('change', updateNilaiFields);
updateNilaiFields();
</script>
</body>
</html>

View File

@ -0,0 +1,182 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data Alumni - Sistem Pemilihan Jurusan</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #5B7B89;
}
.border-maroon {
border-color: #5B7B89;
}
.bg-cream {
background-color: #F8FAFC;
}
</style>
</head>
<body class="bg-cream">
<!-- Header -->
<header class="gradient-maroon text-white shadow-lg sticky top-0 z-50">
<div class="container mx-auto px-4 sm:px-6 py-4 sm:py-6">
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4">
<div>
<h1 class="text-xl sm:text-2xl md:text-3xl font-bold">Data Alumni</h1>
<p class="text-xs sm:text-sm text-yellow-300 font-semibold mt-1">Validasi & Analisis Bobot Algoritma</p>
</div>
<a href="{{ route('alumni.create') }}" class="bg-yellow-400 text-maroon font-bold py-2 px-4 rounded-lg hover:bg-yellow-300 transition text-xs sm:text-sm text-center">
Input Alumni Baru
</a>
</div>
</div>
</header>
<!-- Main Content -->
<div class="container mx-auto px-4 sm:px-6 py-6 sm:py-12">
<!-- Success Alert -->
@if(session('success'))
<div class="mb-6 p-4 bg-green-100 border-l-4 border-green-500 text-green-700 rounded">
{{ session('success') }}
</div>
@endif
<!-- Summary Stats -->
@if($summary)
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<div class="bg-white rounded-lg shadow p-6 border-t-4 border-maroon">
<p class="text-gray-600 text-sm font-semibold">Total Alumni</p>
<p class="text-3xl font-bold text-maroon mt-2">{{ $summary['total'] }}</p>
</div>
@if($summary['prediction_accuracy'])
<div class="bg-white rounded-lg shadow p-6 border-t-4 border-yellow-400">
<p class="text-gray-600 text-sm font-semibold">Top-1 Accuracy</p>
<p class="text-3xl font-bold mt-2" style="color: #EA580C;">{{ $summary['prediction_accuracy']['top_1'] }}%</p>
</div>
<div class="bg-white rounded-lg shadow p-6 border-t-4 border-blue-400">
<p class="text-gray-600 text-sm font-semibold">Top-3 Accuracy</p>
<p class="text-3xl font-bold text-blue-600 mt-2">{{ $summary['prediction_accuracy']['top_3'] }}%</p>
</div>
<div class="bg-white rounded-lg shadow p-6 border-t-4 border-green-400">
<p class="text-gray-600 text-sm font-semibold">Top-5 Accuracy</p>
<p class="text-3xl font-bold text-green-600 mt-2">{{ $summary['prediction_accuracy']['top_5'] }}%</p>
</div>
@endif
</div>
<!-- Distribution by Major -->
@if($summary['by_major']->isNotEmpty())
<div class="bg-white rounded-lg shadow p-6 mb-8 border-l-4 border-maroon">
<h3 class="text-lg font-bold text-maroon mb-4">Distribusi Alumni per Jurusan</h3>
<div class="space-y-2">
@foreach($summary['by_major'] as $major)
<div class="flex items-center gap-3">
<span class="text-sm font-semibold text-gray-700 w-48">{{ $major->major_masuk }}</span>
<div class="flex-1 h-6 bg-gray-200 rounded">
<div class="h-full bg-gradient-to-r from-maroon to-yellow-400 rounded flex items-center justify-center text-white text-xs font-bold" style="width: {{ ($major->count / $summary['total']) * 100 }}%">
{{ $major->count }}
</div>
</div>
</div>
@endforeach
</div>
</div>
@endif
@endif
<!-- Alumni Table -->
<div class="bg-white rounded-lg shadow overflow-x-auto">
<table class="w-full text-sm">
<thead class="gradient-maroon text-white">
<tr>
<th class="px-4 py-3 text-left">Nama</th>
<th class="px-4 py-3 text-left">NIS</th>
<th class="px-4 py-3 text-center">Kelompok</th>
<th class="px-4 py-3 text-center">Nilai Rata</th>
<th class="px-4 py-3 text-left">Major</th>
<th class="px-4 py-3 text-center">Ranking</th>
<th class="px-4 py-3 text-center">Success</th>
<th class="px-4 py-3 text-center">Aksi</th>
</tr>
</thead>
<tbody class="divide-y">
@forelse($alumni as $a)
<tr class="hover:bg-gray-50 transition">
<td class="px-4 py-3 font-semibold text-gray-800">{{ $a->nama_alumni }}</td>
<td class="px-4 py-3 text-gray-600">{{ $a->nis ?? '-' }}</td>
<td class="px-4 py-3 text-center">
<span class="px-2 py-1 rounded text-xs font-bold" style="{{ $a->kelompok_asal == 'IPA' ? 'background-color: #E0F2FE; color: #0369A1;' : 'background-color: #FEF3C7; color: #92400E;' }}">
{{ $a->kelompok_asal }}
</span>
</td>
<td class="px-4 py-3 text-center font-bold text-maroon">{{ $a->nilai_rata_rata ? number_format($a->nilai_rata_rata, 2) : '-' }}</td>
<td class="px-4 py-3 text-gray-700">{{ $a->major_masuk }}</td>
<td class="px-4 py-3 text-center">
@if($a->ranking_saat_rekomendasi)
<span class="px-2 py-1 rounded text-xs font-bold bg-blue-100 text-blue-800">#{{ $a->ranking_saat_rekomendasi }}</span>
@else
<span class="text-gray-400 text-xs">-</span>
@endif
</td>
<td class="px-4 py-3 text-center text-xs">
@if($a->success_status)
@switch($a->success_status)
@case('sangat_sukses')
<span class="px-2 py-1 rounded bg-green-100 text-green-800 font-bold"> Sangat</span>
@break
@case('sukses')
<span class="px-2 py-1 rounded bg-green-50 text-green-700"> Sukses</span>
@break
@case('cukup')
<span class="px-2 py-1 rounded bg-yellow-100 text-yellow-800"> Cukup</span>
@break
@case('kurang_sukses')
<span class="px-2 py-1 rounded bg-red-100 text-red-800"> Kurang</span>
@break
@endswitch
@else
<span class="text-gray-400 text-xs">-</span>
@endif
</td>
<td class="px-4 py-3 text-center gap-2 flex justify-center">
<a href="{{ route('alumni.show', $a->id) }}" class="text-blue-600 hover:text-blue-800 font-semibold text-xs">👁 Lihat</a>
<a href="{{ route('alumni.edit', $a->id) }}" class="text-yellow-600 hover:text-yellow-800 font-semibold text-xs"> Edit</a>
<form action="{{ route('alumni.destroy', $a->id) }}" method="POST" class="inline" onsubmit="return confirm('Yakin hapus?')">
@csrf @method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-800 font-semibold text-xs">🗑 Hapus</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="px-4 py-6 text-center text-gray-500">
Belum ada data alumni. <a href="{{ route('alumni.create') }}" class="text-maroon font-bold">Tambah sekarang</a>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="mt-6">
{{ $alumni->links() }}
</div>
<!-- Info Box -->
<div class="mt-8 p-4 bg-blue-50 border-l-4 border-blue-400 rounded">
<p class="text-sm text-blue-800">
<strong>📊 Data Alumni digunakan untuk:</strong><br>
1. Validasi akurasi algoritma Naive Bayes<br>
2. Analisis faktor-faktor mana yang paling berpengaruh terhadap kesuksesan<br>
3. Re-weighting: menyesuaikan bobot jika data menunjukkan faktor lain lebih penting
</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,229 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Detail Alumni - {{ $alumnus->nama_alumni }}</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #5B7B89;
}
.bg-cream {
background-color: #F8FAFC;
}
</style>
</head>
<body class="bg-cream">
<!-- Header -->
<header class="gradient-maroon text-white shadow-lg sticky top-0 z-50">
<div class="container mx-auto px-4 sm:px-6 py-4 sm:py-6 flex justify-between items-center">
<div>
<h1 class="text-xl sm:text-2xl md:text-3xl font-bold">Detail Alumni</h1>
<p class="text-xs sm:text-sm text-yellow-300 font-semibold mt-1">{{ $alumnus->nama_alumni }}</p>
</div>
<a href="{{ route('alumni.index') }}" class="bg-yellow-400 text-maroon font-bold py-2 px-4 rounded-lg hover:bg-yellow-300 transition text-xs sm:text-sm">
Kembali
</a>
</div>
</header>
<!-- Main Content -->
<div class="container mx-auto px-4 sm:px-6 py-6 sm:py-12">
<div class="max-w-4xl mx-auto">
<!-- Profile Card -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-6 border-l-4 border-maroon">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<p class="text-gray-600 text-sm">Nama</p>
<p class="text-xl font-bold text-maroon">{{ $alumnus->nama_alumni }}</p>
</div>
<div>
<p class="text-gray-600 text-sm">NIS</p>
<p class="text-lg font-semibold text-gray-800">{{ $alumnus->nis ?? '-' }}</p>
</div>
<div>
<p class="text-gray-600 text-sm">Kelompok Asal</p>
<p class="text-lg font-semibold">
<span class="px-3 py-1 rounded text-sm font-bold" style="{{ $alumnus->kelompok_asal == 'IPA' ? 'background-color: #E0F2FE; color: #0369A1;' : 'background-color: #FEF3C7; color: #92400E;' }}">
{{ $alumnus->kelompok_asal }}
</span>
</p>
</div>
<div>
<p class="text-gray-600 text-sm">Tahun Masuk</p>
<p class="text-lg font-semibold text-gray-800">{{ $alumnus->tahun_masuk }}</p>
</div>
</div>
</div>
<!-- Nilai & Variabel Input -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-yellow-400">
<h3 class="text-lg font-bold text-maroon mb-4">📊 Input Data Saat Entry</h3>
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
@if($alumnus->mtk)
<div>
<p class="text-gray-600 text-sm">Matematika</p>
<p class="text-lg font-bold text-maroon">{{ $alumnus->mtk }}</p>
</div>
@endif
@if($alumnus->fisika)
<div>
<p class="text-gray-600 text-sm">Fisika</p>
<p class="text-lg font-bold">{{ $alumnus->fisika }}</p>
</div>
@endif
@if($alumnus->kimia)
<div>
<p class="text-gray-600 text-sm">Kimia</p>
<p class="text-lg font-bold">{{ $alumnus->kimia }}</p>
</div>
@endif
@if($alumnus->biologi)
<div>
<p class="text-gray-600 text-sm">Biologi</p>
<p class="text-lg font-bold">{{ $alumnus->biologi }}</p>
</div>
@endif
@if($alumnus->ekonomi)
<div>
<p class="text-gray-600 text-sm">Ekonomi</p>
<p class="text-lg font-bold">{{ $alumnus->ekonomi }}</p>
</div>
@endif
@if($alumnus->geografi)
<div>
<p class="text-gray-600 text-sm">Geografi</p>
<p class="text-lg font-bold">{{ $alumnus->geografi }}</p>
</div>
@endif
@if($alumnus->sosiologi)
<div>
<p class="text-gray-600 text-sm">Sosiologi</p>
<p class="text-lg font-bold">{{ $alumnus->sosiologi }}</p>
</div>
@endif
@if($alumnus->sejarah)
<div>
<p class="text-gray-600 text-sm">Sejarah</p>
<p class="text-lg font-bold">{{ $alumnus->sejarah }}</p>
</div>
@endif
</div>
<div class="mt-4 pt-4 border-t">
<p class="text-gray-600 text-sm">Nilai Rata-rata</p>
<p class="text-2xl font-bold text-maroon">{{ $alumnus->nilai_rata_rata ? number_format($alumnus->nilai_rata_rata, 2) : '-' }}</p>
</div>
</div>
<!-- Non-Akademik -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-blue-400">
<h3 class="text-lg font-bold text-maroon mb-4">💡 Variabel Non-Akademik</h3>
<div class="space-y-3">
<div>
<p class="text-gray-600 text-sm">Minat</p>
<p class="text-gray-800 font-semibold">{{ $alumnus->minat ?? '-' }}</p>
</div>
<div>
<p class="text-gray-600 text-sm">Cita-cita</p>
<p class="text-gray-800 font-semibold">{{ $alumnus->cita_cita ?? '-' }}</p>
</div>
<div>
<p class="text-gray-600 text-sm">Preferensi Studi</p>
<p class="text-gray-800 font-semibold">{{ $alumnus->preferensi_studi ?? '-' }}</p>
</div>
<div>
<p class="text-gray-600 text-sm">Prestasi</p>
<p class="text-gray-800 font-semibold">{{ $alumnus->prestasi ?? '-' }}</p>
</div>
</div>
</div>
<!-- Hasil & Outcome -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-green-400">
<h3 class="text-lg font-bold text-maroon mb-4">🎯 Hasil Rekomendasi & Outcome</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-4">
<div>
<p class="text-gray-600 text-sm">Jurusan Masuk</p>
<p class="text-lg font-bold text-maroon">{{ $alumnus->major_masuk }}</p>
</div>
<div>
<p class="text-gray-600 text-sm">Ranking Saat Rekomendasi</p>
@if($alumnus->ranking_saat_rekomendasi)
<p class="text-lg font-bold"><span class="px-3 py-1 rounded bg-blue-100 text-blue-800">{{ $alumnus->ranking_saat_rekomendasi }} / 9</span></p>
@else
<p class="text-gray-500">-</p>
@endif
</div>
</div>
<div>
<p class="text-gray-600 text-sm">Score Prediksi</p>
<p class="text-lg font-bold">{{ $alumnus->predicted_score ? round($alumnus->predicted_score * 100) . '%' : '-' }}</p>
</div>
</div>
<!-- Outcome Alumni -->
<div class="bg-white rounded-lg shadow p-6 mb-6 border-l-4 border-purple-400">
<h3 class="text-lg font-bold text-maroon mb-4">🎓 Outcome Alumni</h3>
<div class="space-y-3">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p class="text-gray-600 text-sm">IPK Lulus</p>
<p class="text-lg font-bold text-maroon">{{ $alumnus->ipk_lulus ?? '-' }}</p>
</div>
<div>
<p class="text-gray-600 text-sm">Tahun Lulus</p>
<p class="text-lg font-bold">{{ $alumnus->tahun_lulus ?? '-' }}</p>
</div>
</div>
<div>
<p class="text-gray-600 text-sm">Status Kesuksesan</p>
@if($alumnus->success_status)
@switch($alumnus->success_status)
@case('sangat_sukses')
<span class="inline-block px-3 py-1 rounded bg-green-100 text-green-800 font-bold"> Sangat Sukses</span>
@break
@case('sukses')
<span class="inline-block px-3 py-1 rounded bg-green-50 text-green-700"> Sukses</span>
@break
@case('cukup')
<span class="inline-block px-3 py-1 rounded bg-yellow-100 text-yellow-800"> Cukup</span>
@break
@case('kurang_sukses')
<span class="inline-block px-3 py-1 rounded bg-red-100 text-red-800"> Kurang Sukses</span>
@break
@endswitch
@else
<p class="text-gray-500">-</p>
@endif
</div>
<div>
<p class="text-gray-600 text-sm">Karir Outcome</p>
<p class="text-gray-800">{{ $alumnus->karir_outcome ?? '-' }}</p>
</div>
<div>
<p class="text-gray-600 text-sm">Catatan</p>
<p class="text-gray-800">{{ $alumnus->catatan ?? '-' }}</p>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex gap-4 justify-end">
<a href="{{ route('alumni.edit', $alumnus->id) }}" class="px-6 py-2 rounded-lg font-bold bg-yellow-400 text-maroon hover:bg-yellow-300 transition">
Edit
</a>
<form action="{{ route('alumni.destroy', $alumnus->id) }}" method="POST" class="inline" onsubmit="return confirm('Yakin hapus alumni ini?')">
@csrf @method('DELETE')
<button type="submit" class="px-6 py-2 rounded-lg font-bold bg-red-500 text-white hover:bg-red-600 transition">
🗑 Hapus
</button>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,6 +1,6 @@
<x-guest-layout>
<div class="mb-4 text-sm text-gray-600">
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
{{ __('Ini adalah area aman aplikasi. Silakan konfirmasi password Anda sebelum melanjutkan.') }}
</div>
<form method="POST" action="{{ route('password.confirm') }}">
@ -10,18 +10,28 @@
<div>
<x-input-label for="password" :value="__('Password')" />
<x-text-input id="password" class="block mt-1 w-full"
type="password"
name="password"
required autocomplete="current-password" />
<div style="position: relative; display: flex; align-items: center;">
<input id="password" class="block mt-1 w-full border border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500" type="password" name="password" required autocomplete="current-password" placeholder="Enter password" style="padding-right: 45px;" />
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('password', this)">👁️</button>
</div>
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<div class="flex justify-end mt-4">
<x-primary-button>
{{ __('Confirm') }}
{{ __('Konfirmasi') }}
</x-primary-button>
</div>
</form>
@push('scripts')
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
@endpush
</x-guest-layout>

View File

@ -1,6 +1,6 @@
<x-guest-layout>
<div class="mb-4 text-sm text-gray-600">
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
{{ __('Lupa password Anda? Tidak apa-apa. Beri tahu kami alamat email Anda dan kami akan mengirimkan tautan pengaturan ulang password yang akan memungkinkan Anda memilih yang baru.') }}
</div>
<!-- Session Status -->
@ -18,7 +18,7 @@
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Email Password Reset Link') }}
{{ __('Kirim Tautan Atur Ulang Password') }}
</x-primary-button>
</div>
</form>

View File

@ -19,11 +19,11 @@
display: flex;
height: 100vh;
width: 100%;
background-color: #6B2C2C;
background-color: #5B7B89;
}
.left-section {
width: 50%;
background-color: #6B2C2C;
background-color: #5B7B89;
display: flex;
align-items: center;
justify-content: center;
@ -234,14 +234,18 @@
placeholder="Masukkan email Anda" />
</div>
<div class="form-group">
<div class="form-group" style="position: relative;">
<label for="password">Password</label>
<input
id="password"
type="password"
name="password"
required
placeholder="Masukkan password Anda" />
<div style="position: relative; display: flex; align-items: center;">
<input
id="password"
type="password"
name="password"
required
placeholder="Masukkan password Anda"
style="width: 100%; padding-right: 45px;" />
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('password', this)">👁️</button>
</div>
</div>
<div class="form-links">
@ -258,5 +262,13 @@
</div>
</div>
</div>
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
</body>
</html>

View File

@ -19,11 +19,11 @@
flex-direction: column;
min-height: 100vh;
width: 100%;
background-color: #6B2C2C;
background-color: #5B7B89;
}
.left-section {
width: 100%;
background-color: #6B2C2C;
background-color: #5B7B89;
display: flex;
align-items: center;
justify-content: center;
@ -305,14 +305,18 @@
placeholder="Masukkan email Anda" />
</div>
<div class="form-group">
<div class="form-group" style="position: relative;">
<label for="password">Password</label>
<input
id="password"
type="password"
name="password"
required
placeholder="Masukkan password Anda" />
<div style="position: relative; display: flex; align-items: center;">
<input
id="password"
type="password"
name="password"
required
placeholder="Masukkan password Anda"
style="padding-right: 45px;" />
<button type="button" id="togglePassword" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px; padding: 5px;" onclick="togglePasswordVisibility('password', 'togglePassword')">👁️</button>
</div>
</div>
<div class="form-links">
@ -327,5 +331,13 @@
</div>
</div>
</div>
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
</body>
</html>

View File

@ -19,11 +19,11 @@
flex-direction: column;
min-height: 100vh;
width: 100%;
background-color: #6B2C2C;
background-color: #5B7B89;
}
.left-section {
width: 100%;
background-color: #6B2C2C;
background-color: #5B7B89;
display: flex;
align-items: center;
justify-content: center;
@ -353,25 +353,33 @@
</div>
<!-- Password -->
<div class="form-group">
<div class="form-group" style="position: relative;">
<label for="password">Password</label>
<input
id="password"
type="password"
name="password"
required
placeholder="Minimal 8 karakter" />
<div style="position: relative; display: flex; align-items: center;">
<input
id="password"
type="password"
name="password"
required
placeholder="Minimal 8 karakter"
style="width: 100%; padding-right: 45px;" />
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('password', this)">👁️</button>
</div>
</div>
<!-- Confirm Password -->
<div class="form-group">
<div class="form-group" style="position: relative;">
<label for="password_confirmation">Konfirmasi Password</label>
<input
id="password_confirmation"
type="password"
name="password_confirmation"
required
placeholder="Masukkan ulang password Anda" />
<div style="position: relative; display: flex; align-items: center;">
<input
id="password_confirmation"
type="password"
name="password_confirmation"
required
placeholder="Masukkan ulang password Anda"
style="width: 100%; padding-right: 45px;" />
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('password_confirmation', this)">👁️</button>
</div>
</div>
<div class="form-links">
@ -383,5 +391,13 @@
</div>
</div>
</div>
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
</body>
</html>

View File

@ -15,25 +15,37 @@
<!-- Password -->
<div class="mt-4">
<x-input-label for="password" :value="__('Password')" />
<x-text-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
<div style="position: relative; display: flex; align-items: center;">
<input id="password" class="block mt-1 w-full border border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500" type="password" name="password" required autocomplete="new-password" placeholder="Enter new password" style="padding-right: 45px;" />
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('password', this)">👁️</button>
</div>
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<!-- Confirm Password -->
<div class="mt-4">
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
<x-text-input id="password_confirmation" class="block mt-1 w-full"
type="password"
name="password_confirmation" required autocomplete="new-password" />
<div style="position: relative; display: flex; align-items: center;">
<input id="password_confirmation" class="block mt-1 w-full border border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500" type="password" name="password_confirmation" required autocomplete="new-password" placeholder="Confirm password" style="padding-right: 45px;" />
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('password_confirmation', this)">👁️</button>
</div>
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
</div>
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Reset Password') }}
{{ __('Atur Ulang Password') }}
</x-primary-button>
</div>
</form>
@push('scripts')
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
@endpush
</x-guest-layout>

View File

@ -1,11 +1,11 @@
<x-guest-layout>
<div class="mb-4 text-sm text-gray-600">
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
{{ __('Terima kasih telah mendaftar! Sebelum mulai, dapatkah Anda memverifikasi alamat email Anda dengan mengklik tautan yang baru saja kami kirimkan kepada Anda? Jika Anda tidak menerima email, kami akan dengan senang hati mengirimkan yang lain.') }}
</div>
@if (session('status') == 'verification-link-sent')
<div class="mb-4 font-medium text-sm text-green-600">
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
{{ __('Tautan verifikasi baru telah dikirim ke alamat email yang Anda berikan saat pendaftaran.') }}
</div>
@endif
@ -15,7 +15,7 @@
<div>
<x-primary-button>
{{ __('Resend Verification Email') }}
{{ __('Kirim Ulang Email Verifikasi') }}
</x-primary-button>
</div>
</form>
@ -24,7 +24,7 @@
@csrf
<button type="submit" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
{{ __('Log Out') }}
{{ __('Keluar') }}
</button>
</form>
</div>

View File

@ -7,16 +7,16 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #6B2C2C 0%, #8B3E3E 100%);
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #6B2C2C;
color: #5B7B89;
}
.border-maroon {
border-color: #6B2C2C;
border-color: #5B7B89;
}
.bg-cream {
background-color: #FFF9F5;
background-color: #F8FAFC;
}
.chat-container {
height: 400px;
@ -51,7 +51,7 @@
}
}
.user-msg {
background-color: #6B2C2C;
background-color: #5B7B89;
color: white;
padding: 10px 12px;
border-radius: 12px 12px 0 12px;
@ -146,7 +146,7 @@
<div class="chat-container flex-1 mb-3 sm:mb-4" id="chatContainer">
<div class="message ai">
<div class="ai-msg">
<p>Halo! 👋 Saya konselor BK virtual SMA Bima Ambulu. Saya siap membantu kamu tentang pemilihan jurusan kuliah, prospek karir, atau apa pun yang kamu mau tanyakan. Yuk, mulai curhat! 😊</p>
<p>Selamat datang. Saya adalah konselor BK virtual SMA Bima Ambulu. Saya siap membantu Anda dalam pemilihan jurusan kuliah, informasi prospek karier, maupun konsultasi lainnya terkait pendidikan tinggi. Silakan sampaikan pertanyaan Anda.</p>
</div>
</div>
</div>
@ -217,9 +217,15 @@ class="gradient-maroon text-white font-bold py-2 px-4 sm:px-6 rounded-lg hover:o
const data = await response.json();
if (data.success) {
addMessage(data.message, 'ai');
// Strip markdown formatting (**, *, #, etc.)
let cleanMessage = data.message
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
.replace(/^#{1,6}\s+/gm, '')
.replace(/`(.*?)`/g, '$1');
addMessage(cleanMessage, 'ai');
// Add AI response to history
conversationHistory.push({ role: 'ai', text: data.message });
conversationHistory.push({ role: 'ai', text: cleanMessage });
} else {
addMessage(data.message || 'Maaf, terjadi kesalahan.', 'ai');
}

View File

@ -7,16 +7,16 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #6B2C2C 0%, #8B3E3E 100%);
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #6B2C2C;
color: #5B7B89;
}
.border-maroon {
border-color: #6B2C2C;
border-color: #5B7B89;
}
.bg-cream {
background-color: #FFF9F5;
background-color: #F8FAFC;
}
.card-hover {
transition: all 0.3s ease;
@ -38,16 +38,25 @@
</div>
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-2 sm:gap-4">
<span class="text-xs sm:text-sm md:text-base text-yellow-200">Selamat datang, <strong>{{ Auth::user()->name }}</strong>!</span>
<div class="flex gap-2 w-full sm:w-auto">
<a href="{{ route('profile.edit') }}" class="flex-1 sm:flex-none block sm:inline-block text-center bg-white font-bold py-2 px-4 rounded-lg hover:bg-gray-100 transition text-xs sm:text-sm" style="color: #6B2C2C;">
<div class="relative">
<button id="profileDropdownBtn" class="bg-gray-100 text-maroon font-bold py-2 px-4 rounded-lg hover:bg-gray-200 transition text-xs sm:text-sm flex items-center gap-2" style="color: #5B7B89;">
👤 Profil
</a>
<form method="POST" action="{{ route('logout') }}" class="flex-1 sm:flex-none">
@csrf
<button type="submit" class="block sm:inline-block w-full sm:w-auto bg-yellow-400 text-maroon font-bold py-2 px-4 rounded-lg hover:bg-yellow-300 transition text-xs sm:text-sm text-center">
Logout
</button>
</form>
<svg id="dropdownArrow" class="w-4 h-4 transition-transform duration-300" style="transform: rotate(0deg);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
</svg>
</button>
<div id="profileDropdown" class="absolute right-0 mt-2 w-48 bg-white text-gray-800 rounded-lg shadow-lg hidden z-50">
<a href="{{ route('profile.edit') }}" class="block px-4 py-3 hover:bg-gray-50 text-xs sm:text-sm font-semibold border-b border-gray-100 rounded-t-lg">
👤 Lihat Profil
</a>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="block w-full text-left px-4 py-3 hover:bg-gray-50 text-xs sm:text-sm font-semibold text-red-600 rounded-b-lg">
🚪 Logout
</button>
</form>
</div>
</div>
</div>
</div>
@ -58,13 +67,41 @@
<div class="container mx-auto px-4 sm:px-6 py-6 sm:py-12">
<!-- Info Box -->
<div class="bg-white border-2 border-maroon rounded-lg p-4 sm:p-6 mb-6 sm:mb-8 shadow-md">
<h2 class="text-lg sm:text-xl md:text-2xl font-bold text-maroon mb-2 sm:mb-3">Selamat Datang</h2>
<p class="text-xs sm:text-sm md:text-base text-gray-700 mb-2 sm:mb-3">
Sistem ini membantu Anda menemukan jurusan yang sesuai dengan profil akademik, minat, dan preferensi pembelajaran Anda.
<h2 class="text-lg sm:text-xl md:text-2xl font-bold text-maroon mb-2 sm:mb-3">Selamat Datang di Sistem Pemilihan Jurusan</h2>
<p class="text-xs sm:text-sm md:text-base text-gray-700 mb-3 sm:mb-4">
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.
</p>
<p class="text-xs sm:text-sm md:text-base text-gray-700">
Sistem menganalisis 5 faktor: nilai akademik, minat, preferensi pembelajaran, prestasi, dan cita-cita.
Berdasarkan analisis tersebut, sistem memberikan rekomendasi dari 9 pilihan jurusan yang tersedia.
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-3 sm:p-4 mb-4 rounded">
<p class="text-xs sm:text-sm md:text-base text-gray-800">
<strong>Bagaimana Sistem Ini Bekerja?</strong> 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.
</p>
</div>
<p class="text-xs sm:text-sm md:text-base text-gray-700 mb-3 sm:mb-4">
<strong>Fitur-Fitur yang Tersedia:</strong>
</p>
<ul class="text-xs sm:text-sm md:text-base text-gray-700 space-y-2 mb-4">
<li class="flex gap-2">
<span class="text-maroon"></span>
<span><strong>Analisis Rekomendasi:</strong> Isi kuesioner singkat dan dapatkan rekomendasi 9 jurusan yang disesuaikan dengan profil Anda</span>
</li>
<li class="flex gap-2">
<span class="text-maroon"></span>
<span><strong>Konsultasi dengan AI:</strong> Chat dengan konselor BK virtual yang siap menjawab pertanyaan tentang jurusan, prospek karir, dan tips sukses kuliah</span>
</li>
<li class="flex gap-2">
<span class="text-maroon"></span>
<span><strong>Riwayat Analisis:</strong> Lihat kembali semua analisis dan chat history Anda kapan saja untuk referensi</span>
</li>
<li class="flex gap-2">
<span class="text-maroon"></span>
<span><strong>Profil Pribadi:</strong> Kelola data diri, foto profil, dan informasi akademik Anda</span>
</li>
</ul>
<p class="text-xs sm:text-sm md:text-base text-gray-700 text-italic">
💡 <strong>Tips:</strong> Untuk hasil yang akurat, jawab semua pertanyaan dengan jujur dan detail. Semakin detail profil Anda, semakin akurat rekomendasi yang kami berikan.
</p>
</div>
@ -260,5 +297,35 @@
<p class="text-xs sm:text-sm text-yellow-200">Sistem Pemilihan Jurusan © 2026 | SMA Bima Ambulu</p>
</div>
</footer>
<script>
// Dropdown menu toggle with arrow animation
const profileDropdownBtn = document.getElementById('profileDropdownBtn');
const profileDropdown = document.getElementById('profileDropdown');
const dropdownArrow = document.getElementById('dropdownArrow');
profileDropdownBtn.addEventListener('click', function(e) {
e.preventDefault();
const isHidden = profileDropdown.classList.contains('hidden');
if (isHidden) {
// Show dropdown and rotate arrow
profileDropdown.classList.remove('hidden');
dropdownArrow.style.transform = 'rotate(180deg)';
} else {
// Hide dropdown and reset arrow
profileDropdown.classList.add('hidden');
dropdownArrow.style.transform = 'rotate(0deg)';
}
});
// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
if (!profileDropdownBtn.contains(e.target) && !profileDropdown.contains(e.target)) {
profileDropdown.classList.add('hidden');
dropdownArrow.style.transform = 'rotate(0deg)';
}
});
</script>
</body>
</html>

View File

@ -7,19 +7,19 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #6B2C2C 0%, #8B3E3E 100%);
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #6B2C2C;
color: #5B7B89;
}
.border-maroon {
border-color: #6B2C2C;
border-color: #5B7B89;
}
.bg-cream {
background-color: #FFF9F5;
background-color: #F8FAFC;
}
.user-message {
background-color: #6B2C2C;
background-color: #5B7B89;
color: white;
border-radius: 12px 12px 0 12px;
}
@ -77,7 +77,7 @@
<!-- AI Response -->
<div class="flex justify-start">
<div class="ai-message max-w-xs sm:max-w-md lg:max-w-lg xl:max-w-xl p-3 sm:p-4">
<p class="text-xs sm:text-sm">{{ $chat->response }}</p>
<p class="text-xs sm:text-sm">{{ preg_replace(['/\*\*(.*?)\*\*/s', '/\*(.*?)\*/s', '/^#{1,6}\s+/m', '/`(.*?)`/s'], ['$1', '$1', '', '$1'], $chat->response) }}</p>
</div>
</div>
</div>

View File

@ -7,16 +7,16 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #6B2C2C 0%, #8B3E3E 100%);
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #6B2C2C;
color: #5B7B89;
}
.border-maroon {
border-color: #6B2C2C;
border-color: #5B7B89;
}
.bg-cream {
background-color: #FFF9F5;
background-color: #F8FAFC;
}
</style>
</head>

View File

@ -7,30 +7,30 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #6B2C2C 0%, #8B3E3E 100%);
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #6B2C2C;
color: #5B7B89;
}
.border-maroon {
border-color: #6B2C2C;
border-color: #5B7B89;
}
.bg-cream {
background-color: #FFF9F5;
background-color: #F8FAFC;
}
.btn-maroon {
background: linear-gradient(135deg, #6B2C2C 0%, #8B3E3E 100%);
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
color: #fff;
transition: all 0.3s ease;
}
.btn-maroon:hover {
opacity: 0.9;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(107, 44, 44, 0.3);
box-shadow: 0 4px 12px rgba(91, 123, 137, 0.3);
}
.input-focus:focus {
border-color: #6B2C2C;
box-shadow: 0 0 0 3px rgba(107, 44, 44, 0.15);
border-color: #5B7B89;
box-shadow: 0 0 0 3px rgba(91, 123, 137, 0.15);
outline: none;
}
</style>
@ -46,7 +46,7 @@
</div>
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-2 sm:gap-4">
<span class="text-xs sm:text-sm md:text-base text-yellow-200">{{ Auth::user()->name }}</span>
<a href="{{ route('dashboard') }}" class="block sm:inline-block w-full sm:w-auto bg-yellow-400 text-center font-bold py-2 px-4 rounded-lg hover:bg-yellow-300 transition text-xs sm:text-sm" style="color: #6B2C2C;">
<a href="{{ route('dashboard') }}" class="block sm:inline-block w-full sm:w-auto bg-yellow-400 text-center font-bold py-2 px-4 rounded-lg hover:bg-yellow-300 transition text-xs sm:text-sm" style="color: #5B7B89;">
Kembali ke Dashboard
</a>
</div>
@ -162,8 +162,11 @@ class="input-focus w-full border border-gray-300 rounded-lg px-4 py-2 text-sm fo
<div class="mb-4">
<label for="current_password" class="block text-sm font-semibold text-maroon mb-1">Password Saat Ini</label>
<input type="password" id="current_password" name="current_password"
class="input-focus w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-0">
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="current_password" name="current_password"
class="input-focus w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-0" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('current_password', this)">👁️</button>
</div>
@error('current_password', 'updatePassword')
<p class="text-red-600 text-xs mt-1">{{ $message }}</p>
@enderror
@ -171,8 +174,11 @@ class="input-focus w-full border border-gray-300 rounded-lg px-4 py-2 text-sm fo
<div class="mb-4">
<label for="password" class="block text-sm font-semibold text-maroon mb-1">Password Baru</label>
<input type="password" id="password" name="password"
class="input-focus w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-0">
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="password" name="password"
class="input-focus w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-0" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('password', this)">👁️</button>
</div>
@error('password', 'updatePassword')
<p class="text-red-600 text-xs mt-1">{{ $message }}</p>
@enderror
@ -180,8 +186,11 @@ class="input-focus w-full border border-gray-300 rounded-lg px-4 py-2 text-sm fo
<div class="mb-6">
<label for="password_confirmation" class="block text-sm font-semibold text-maroon mb-1">Konfirmasi Password Baru</label>
<input type="password" id="password_confirmation" name="password_confirmation"
class="input-focus w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-0">
<div style="position: relative; display: flex; align-items: center;">
<input type="password" id="password_confirmation" name="password_confirmation"
class="input-focus w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-0" style="padding-right: 45px;">
<button type="button" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;" onclick="togglePasswordVisibility('password_confirmation', this)">👁️</button>
</div>
@error('password_confirmation', 'updatePassword')
<p class="text-red-600 text-xs mt-1">{{ $message }}</p>
@enderror
@ -253,6 +262,12 @@ function previewFoto(input) {
reader.readAsDataURL(input.files[0]);
}
}
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
</body>
</html>

View File

@ -1,18 +1,18 @@
<section class="space-y-6">
<header>
<h2 class="text-lg font-medium text-gray-900">
{{ __('Delete Account') }}
{{ __('Hapus Akun') }}
</h2>
<p class="mt-1 text-sm text-gray-600">
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }}
{{ __('Setelah akun Anda dihapus, semua sumber daya dan data akan dihapus secara permanen. Silakan unduh data atau informasi apa pun yang ingin Anda pertahankan sebelum menghapus akun Anda.') }}
</p>
</header>
<x-danger-button
x-data=""
x-on:click.prevent="$dispatch('open-modal', 'confirm-user-deletion')"
>{{ __('Delete Account') }}</x-danger-button>
>{{ __('Hapus Akun') }}</x-danger-button>
<x-modal name="confirm-user-deletion" :show="$errors->userDeletion->isNotEmpty()" focusable>
<form method="post" action="{{ route('profile.destroy') }}" class="p-6">
@ -20,36 +20,48 @@
@method('delete')
<h2 class="text-lg font-medium text-gray-900">
{{ __('Are you sure you want to delete your account?') }}
{{ __('Apakah Anda yakin ingin menghapus akun Anda?') }}
</h2>
<p class="mt-1 text-sm text-gray-600">
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }}
{{ __('Setelah akun Anda dihapus, semua sumber daya dan data akan dihapus secara permanen. Silakan masukkan password Anda untuk mengonfirmasi bahwa Anda ingin menghapus akun Anda secara permanen.') }}
</p>
<div class="mt-6">
<x-input-label for="password" value="{{ __('Password') }}" class="sr-only" />
<x-text-input
id="password"
name="password"
type="password"
class="mt-1 block w-3/4"
placeholder="{{ __('Password') }}"
/>
<div style="position: relative; display: flex; align-items: center; max-width: 75%;\">\n <input
id="password"
name="password"
type="password"
class="mt-1 block w-full border border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"
placeholder="{{ __('Password') }}"
style="padding-right: 45px;" />
<button type="button\" style="position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px;\" onclick="togglePasswordVisibility('password', this)\">👁️</button>
</div>
<x-input-error :messages="$errors->userDeletion->get('password')" class="mt-2" />
</div>
<div class="mt-6 flex justify-end">
<x-secondary-button x-on:click="$dispatch('close')">
{{ __('Cancel') }}
{{ __('Batal') }}
</x-secondary-button>
<x-danger-button class="ms-3">
{{ __('Delete Account') }}
{{ __('Hapus Akun') }}
</x-danger-button>
</div>
</form>
</x-modal>
</section>
@push('scripts')
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
@endpush

View File

@ -1,11 +1,11 @@
<section>
<header>
<h2 class="text-lg font-medium text-gray-900">
{{ __('Update Password') }}
{{ __('Perbarui Password') }}
</h2>
<p class="mt-1 text-sm text-gray-600">
{{ __('Ensure your account is using a long, random password to stay secure.') }}
{{ __('Pastikan akun Anda menggunakan password yang panjang dan acak untuk tetap aman.') }}
</p>
</header>
@ -15,19 +15,28 @@
<div>
<x-input-label for="update_password_current_password" :value="__('Current Password')" />
<x-text-input id="update_password_current_password" name="current_password" type="password" class="mt-1 block w-full" autocomplete="current-password" />
<div style="position: relative; display: flex; align-items: center;">
<input id="update_password_current_password" name="current_password" type="password" class="mt-1 block w-full border border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500" autocomplete="current-password" placeholder="Enter current password" style="padding-right: 45px;" />
<button type=\"button\" style=\"position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px; margin-top: 5px;\" onclick=\"togglePasswordVisibility('update_password_current_password', this)\">👁️</button>
</div>
<x-input-error :messages="$errors->updatePassword->get('current_password')" class="mt-2" />
</div>
<div>
<x-input-label for="update_password_password" :value="__('New Password')" />
<x-text-input id="update_password_password" name="password" type="password" class="mt-1 block w-full" autocomplete="new-password" />
<div style="position: relative; display: flex; align-items: center;">
<input id="update_password_password" name="password" type="password" class="mt-1 block w-full border border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500" autocomplete="new-password" placeholder="Enter new password" style="padding-right: 45px;" />
<button type=\"button\" style=\"position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px; margin-top: 5px;\" onclick=\"togglePasswordVisibility('update_password_password', this)\">👁️</button>
</div>
<x-input-error :messages="$errors->updatePassword->get('password')" class="mt-2" />
</div>
<div>
<x-input-label for="update_password_password_confirmation" :value="__('Confirm Password')" />
<x-text-input id="update_password_password_confirmation" name="password_confirmation" type="password" class="mt-1 block w-full" autocomplete="new-password" />
<div style="position: relative; display: flex; align-items: center;">
<input id="update_password_password_confirmation" name="password_confirmation" type="password" class="mt-1 block w-full border border-gray-300 rounded-lg shadow-sm focus:border-indigo-500 focus:ring-indigo-500" autocomplete="new-password" placeholder="Confirm password" style="padding-right: 45px;" />
<button type=\"button\" style=\"position: absolute; right: 12px; background: none; border: none; cursor: pointer; color: #5B7B89; font-size: 18px; margin-top: 5px;\" onclick=\"togglePasswordVisibility('update_password_password_confirmation', this)\">👁️</button>
</div>
<x-input-error :messages="$errors->updatePassword->get('password_confirmation')" class="mt-2" />
</div>
@ -46,3 +55,13 @@ class="text-sm text-gray-600"
</div>
</form>
</section>
@push('scripts')
<script>
function togglePasswordVisibility(inputId, buttonElement) {
const input = document.getElementById(inputId);
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
}
</script>
@endpush

View File

@ -1,11 +1,11 @@
<section>
<header>
<h2 class="text-lg font-medium text-gray-900">
{{ __('Profile Information') }}
{{ __('Informasi Profil') }}
</h2>
<p class="mt-1 text-sm text-gray-600">
{{ __("Update your account's profile information and email address.") }}
{{ __('Perbarui informasi profil dan alamat email akun Anda.') }}
</p>
</header>
@ -77,7 +77,7 @@
</div>
<div class="flex items-center gap-4">
<x-primary-button>{{ __('Save') }}</x-primary-button>
<x-primary-button>{{ __('Simpan') }}</x-primary-button>
@if (session('status') === 'profile-updated')
<p
@ -86,7 +86,7 @@
x-transition
x-init="setTimeout(() => show = false, 2000)"
class="text-sm text-gray-600"
>{{ __('Saved.') }}</p>
>{{ __('Berhasil disimpan!') }}</p>
@endif
</div>
</form>

View File

@ -7,19 +7,19 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #6B2C2C 0%, #8B3E3E 100%);
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #6B2C2C;
color: #5B7B89;
}
.border-maroon {
border-color: #6B2C2C;
border-color: #5B7B89;
}
.bg-cream {
background-color: #FFF9F5;
background-color: #F8FAFC;
}
.bg-maroon-light {
background-color: rgba(107, 44, 44, 0.1);
background-color: rgba(91, 123, 137, 0.1);
}
</style>
</head>
@ -56,27 +56,30 @@
<div class="bg-maroon-light p-3 sm:p-4 rounded-lg">
<p class="text-xs sm:text-sm text-gray-600">Nilai Akademik</p>
<p class="text-lg sm:text-xl font-bold text-maroon">{{ $katNilai }}</p>
<p class="text-xs text-gray-500">Rata-rata: {{ number_format($average, 1) }}</p>
</div>
<div class="bg-yellow-50 p-3 sm:p-4 rounded-lg">
<p class="text-xs sm:text-sm text-gray-600">Minat</p>
<p class="text-sm sm:text-lg font-bold text-maroon">{{ $minatMapped }}</p>
</div>
<div class="bg-maroon-light p-3 sm:p-4 rounded-lg">
<p class="text-xs sm:text-sm text-gray-600">Preferensi Belajar</p>
<p class="text-xs sm:text-sm text-gray-600">Preferensi Studi</p>
<p class="text-sm sm:text-lg font-bold text-maroon">{{ $prefStudi }}</p>
</div>
<div class="bg-yellow-50 p-3 sm:p-4 rounded-lg">
<div class="bg-maroon-light p-3 sm:p-4 rounded-lg">
<p class="text-xs sm:text-sm text-gray-600">Prestasi</p>
<p class="text-sm sm:text-lg font-bold text-maroon">
@if($prestasiScore >= 0.8)
Tinggi
@elseif($prestasiScore >= 0.6)
Sedang
@elseif($prestasiScore > 0)
Cukup
@else
Minimal
Belum Ada
@endif
</p>
</div>
<div class="bg-yellow-50 p-3 sm:p-4 rounded-lg">
<p class="text-xs sm:text-sm text-gray-600">Skor Nilai</p>
<p class="text-sm sm:text-lg font-bold text-maroon">{{ number_format($average / 100 * 100, 1) }}%</p>
</div>
</div>
</div>
@ -139,7 +142,7 @@
<div class="bg-white rounded-lg shadow-lg p-5 sm:p-8 mb-6 sm:mb-8 border-l-4 border-yellow-400">
@php
$topRecommendation = $hasilAkhir[0];
$criteria = config('polije.criteria.' . $topRecommendation['jurusan'], []);
$detail = $topRecommendation['detail'] ?? [];
@endphp
<div class="flex flex-col sm:flex-row items-start gap-3 sm:gap-4 mb-4 sm:mb-6">
@ -149,6 +152,9 @@
<p class="text-sm sm:text-lg text-gray-700">
Skor Kesesuaian: <span class="font-bold text-maroon">{{ number_format($topRecommendation['skor'] * 100, 1) }}%</span>
</p>
@if($topJurusan && $topJurusan->deskripsi)
<p class="text-xs sm:text-sm text-gray-600 mt-2">{{ $topJurusan->deskripsi }}</p>
@endif
</div>
</div>
@ -160,48 +166,50 @@
<div>
<div class="flex justify-between items-center mb-1">
<p class="text-xs sm:text-sm font-semibold text-gray-700">Nilai Akademik (40%)</p>
<span class="text-xs sm:text-sm font-bold text-maroon">{{ $katNilai }}</span>
<span class="text-xs sm:text-sm font-bold text-maroon">{{ number_format(($detail['nilai'] ?? 0) * 100, 1) }}%</span>
</div>
<div class="w-full bg-gray-300 rounded-full h-2">
<div class="gradient-maroon h-2 rounded-full" style="width: 80%"></div>
<div class="gradient-maroon h-2 rounded-full" style="width: {{ number_format(($detail['nilai'] ?? 0) * 100, 1) }}%"></div>
</div>
</div>
<div>
<div class="flex justify-between items-center mb-1">
<p class="text-xs sm:text-sm font-semibold text-gray-700">Minat (35%)</p>
<span class="text-xs sm:text-sm font-bold text-maroon">{{ $minatMapped }}</span>
<p class="text-xs sm:text-sm font-semibold text-gray-700">Minat & Bakat (35%)</p>
<span class="text-xs sm:text-sm font-bold text-maroon">{{ number_format(($detail['minat'] ?? 0) * 100, 1) }}%</span>
</div>
<div class="w-full bg-gray-300 rounded-full h-2">
<div class="bg-yellow-400 h-2 rounded-full" style="width: 85%"></div>
<div class="bg-yellow-400 h-2 rounded-full" style="width: {{ number_format(($detail['minat'] ?? 0) * 100, 1) }}%"></div>
</div>
</div>
<div>
<div class="flex justify-between items-center mb-1">
<p class="text-xs sm:text-sm font-semibold text-gray-700">Preferensi Belajar (15%)</p>
<span class="text-xs sm:text-sm font-bold text-maroon">{{ $prefStudi }}</span>
<p class="text-xs sm:text-sm font-semibold text-gray-700">Preferensi Studi (15%)</p>
<span class="text-xs sm:text-sm font-bold text-maroon">{{ number_format(($detail['pref'] ?? 0) * 100, 1) }}%</span>
</div>
<div class="w-full bg-gray-300 rounded-full h-2">
<div class="gradient-maroon h-2 rounded-full" style="width: 80%"></div>
<div class="gradient-maroon h-2 rounded-full" style="width: {{ number_format(($detail['pref'] ?? 0) * 100, 1) }}%"></div>
</div>
</div>
<div>
<div class="flex justify-between items-center mb-1">
<p class="text-xs sm:text-sm font-semibold text-gray-700">Cita-cita (5%)</p>
<span class="text-xs sm:text-sm font-bold text-maroon">{{ number_format(($detail['cita'] ?? 0) * 100, 1) }}%</span>
</div>
<div class="w-full bg-gray-300 rounded-full h-2">
<div class="bg-yellow-400 h-2 rounded-full" style="width: 85%"></div>
<div class="bg-yellow-400 h-2 rounded-full" style="width: {{ number_format(($detail['cita'] ?? 0) * 100, 1) }}%"></div>
</div>
</div>
<div>
<div class="flex justify-between items-center mb-1">
<p class="text-xs sm:text-sm font-semibold text-gray-700">Prestasi (5%)</p>
<span class="text-xs sm:text-sm font-bold text-maroon">{{ number_format(($detail['prestasi'] ?? 0) * 100, 1) }}%</span>
</div>
<div class="w-full bg-gray-300 rounded-full h-2">
<div class="gradient-maroon h-2 rounded-full" style="width: {{ $prestasiScore * 100 }}%"></div>
<div class="gradient-maroon h-2 rounded-full" style="width: {{ number_format(($detail['prestasi'] ?? 0) * 100, 1) }}%"></div>
</div>
</div>
</div>
@ -211,25 +219,18 @@
<div class="p-3 sm:p-4 bg-yellow-50 rounded-lg border-l-4 border-yellow-400 mb-3 sm:mb-4">
<h4 class="font-bold text-maroon mb-2 text-sm sm:text-base">Penjelasan:</h4>
<p class="text-gray-700 text-xs sm:text-sm leading-relaxed">
Berdasarkan profil Anda dengan <strong>nilai akademik {{ $katNilai }}</strong>,
<strong>minat di bidang {{ $minatMapped }}</strong>, dan
<strong>preferensi belajar {{ $prefStudi }}</strong>,
Berdasarkan profil Anda dengan <strong>nilai akademik {{ $katNilai }} (rata-rata {{ number_format($average, 1) }})</strong>
dan <strong>preferensi studi {{ $prefStudi }}</strong>,
sistem menganalisis bahwa <strong>{{ $topRecommendation['jurusan'] }}</strong>
adalah pilihan yang paling sesuai dengan skor {{ number_format($topRecommendation['skor'] * 100, 1) }}%.
</p>
</div>
<!-- Skills Required -->
@if(isset($criteria['skills_required']))
<!-- Prospek Kerja -->
@if($topJurusan && $topJurusan->prospek_kerja)
<div class="mb-3 sm:mb-4">
<p class="text-xs sm:text-sm font-bold text-maroon mb-2">Skills yang Diperlukan:</p>
<div class="flex flex-wrap gap-2">
@foreach($criteria['skills_required'] as $skill)
<span class="inline-block px-2 sm:px-3 py-1 rounded text-xs sm:text-sm font-medium bg-maroon-light text-maroon border border-maroon">
{{ $skill }}
</span>
@endforeach
</div>
<p class="text-xs sm:text-sm font-bold text-maroon mb-2">Prospek Kerja:</p>
<p class="text-xs sm:text-sm text-gray-700">{{ $topJurusan->prospek_kerja }}</p>
</div>
@endif
</div>
@ -265,7 +266,7 @@
<!-- Info Metode -->
<div class="mt-6 sm:mt-8 p-3 sm:p-4 bg-white rounded-lg border border-gray-200 shadow-sm">
<p class="text-xs sm:text-sm text-gray-600">
<strong>Metode:</strong> Sistem menggunakan Weighted Naive Bayes dengan 5 kriteria: Nilai (40%), Minat (35%), Preferensi (15%), Cita-cita (5%), Prestasi (5%).
<strong>Metode:</strong> Sistem menggunakan Graduated Scoring dengan 5 kriteria: Nilai Akademik (40%), Minat & Bakat (35%), Preferensi Studi (15%), Cita-cita (5%), Prestasi (5%). Setiap kriteria dihitung secara proporsional (0-100%) berdasarkan kecocokan keyword.
</p>
</div>
</div>

View File

@ -7,20 +7,20 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #6B2C2C 0%, #8B3E3E 100%);
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #6B2C2C;
color: #5B7B89;
}
.border-maroon {
border-color: #6B2C2C;
border-color: #5B7B89;
}
.bg-cream {
background-color: #FFF9F5;
background-color: #F8FAFC;
}
.focus-maroon:focus {
border-color: #6B2C2C;
box-shadow: 0 0 0 3px rgba(107, 44, 44, 0.1);
border-color: #5B7B89;
box-shadow: 0 0 0 3px rgba(91, 123, 137, 0.1);
}
</style>
</head>
@ -29,7 +29,7 @@
<header class="gradient-maroon text-white shadow-lg sticky top-0 z-50">
<div class="container mx-auto px-4 sm:px-6 py-4 sm:py-6 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 sm:gap-4">
<div>
<h1 class="text-xl sm:text-2xl md:text-3xl font-bold">Input Data Profil</h1>
<h1 class="text-xl sm:text-2xl md:text-3xl font-bold">Input Data Rekomendasi</h1>
<p class="text-xs sm:text-sm text-yellow-300 font-semibold mt-1">Sistem Pemilihan Jurusan</p>
</div>
<div class="flex items-center gap-2 sm:gap-4 w-full sm:w-auto">
@ -52,7 +52,7 @@
Sistem akan menganalisis data Anda dan menampilkan ranking <strong>9 jurusan</strong> yang tersedia di Politeknik Negeri Jember.
</p>
</div>
<!-- Form Card -->
<div class="bg-white rounded-lg shadow-lg p-5 sm:p-8 border-l-4 border-maroon">
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4 mb-4 sm:mb-6">
@ -63,100 +63,182 @@
</div>
</div>
@if ($errors->any())
<div class="bg-red-50 border-l-4 border-red-500 p-4 sm:p-5 rounded-lg mb-6 shadow-sm">
<h3 class="text-red-700 font-bold text-sm sm:text-base mb-3"> Kesalahan Validasi:</h3>
<ul class="list-disc list-inside space-y-1 text-red-600 text-xs sm:text-sm">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('rekomendasi.proses') }}" method="POST" class="space-y-4 sm:space-y-6">
@csrf
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
<!-- Kolom Nilai -->
<div class="p-4 sm:p-5 rounded-lg border-2 border-gray-200 bg-gray-50">
<h3 class="font-bold text-maroon text-base sm:text-lg mb-1 sm:mb-2">1. Nilai Rapor (0-100)</h3>
<p class="text-xs text-gray-600 mb-3 sm:mb-4">Masukkan nilai rata-rata mata pelajaran Anda.</p>
{{-- ============================================ --}}
{{-- KRITERIA 1: NILAI MATA PELAJARAN --}}
{{-- ============================================ --}}
<div class="p-4 sm:p-5 rounded-lg border-2 border-gray-200 bg-gray-50">
<h3 class="font-bold text-maroon text-base sm:text-lg mb-1 sm:mb-2">1. Nilai Mata Pelajaran <span class="text-red-500">*</span></h3>
<p class="text-xs text-gray-600 mb-3 sm:mb-4">
@if(isset($student) && $student->kelompok_asal == 'IPA')
Siswa <strong>IPA</strong> Masukkan nilai rapor (0-100): <strong>Matematika, Fisika, Kimia, Biologi</strong>.
@else
Siswa <strong>IPS</strong> Masukkan nilai rapor (0-100): <strong>Ekonomi, Geografi, Sosiologi, Sejarah</strong>.
@endif
</p>
<div class="space-y-3 sm:space-y-4">
@if(isset($student) && $student->kelompok_asal == 'IPA')
{{-- SISWA IPA: Matematika, Fisika, Kimia, Biologi --}}
<div class="grid grid-cols-2 lg:grid-cols-4 gap-2 sm:gap-3">
<div>
<label for="mtk" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Matematika</label>
<input id="mtk" type="number" name="mtk" min="0" max="100" placeholder="Contoh: 85"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
<label for="mtk" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Matematika <span class="text-red-500">*</span></label>
<input id="mtk" type="number" name="mtk" min="0" max="100" value="{{ old('mtk') }}" placeholder="85" required
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('mtk') border-red-500 @enderror">
@error('mtk')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
<div>
<label for="fisika" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Fisika <span class="text-red-500">*</span></label>
<input id="fisika" type="number" name="fisika" min="0" max="100" value="{{ old('fisika') }}" placeholder="78" required
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('fisika') border-red-500 @enderror">
@error('fisika')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
<div>
<label for="kimia" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Kimia <span class="text-red-500">*</span></label>
<input id="kimia" type="number" name="kimia" min="0" max="100" value="{{ old('kimia') }}" placeholder="72" required
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('kimia') border-red-500 @enderror">
@error('kimia')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
<div>
<label for="biologi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Biologi <span class="text-red-500">*</span></label>
<input id="biologi" type="number" name="biologi" min="0" max="100" value="{{ old('biologi') }}" placeholder="80" required
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('biologi') border-red-500 @enderror">
@error('biologi')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
</div>
@else
{{-- SISWA IPS: Ekonomi, Geografi, Sosiologi, Sejarah --}}
<div class="grid grid-cols-2 lg:grid-cols-4 gap-2 sm:gap-3">
<div>
<label for="ekonomi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Ekonomi <span class="text-red-500">*</span></label>
<input id="ekonomi" type="number" name="ekonomi" min="0" max="100" value="{{ old('ekonomi') }}" placeholder="82" required
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('ekonomi') border-red-500 @enderror">
@error('ekonomi')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
<div>
<label for="geografi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Geografi <span class="text-red-500">*</span></label>
<input id="geografi" type="number" name="geografi" min="0" max="100" value="{{ old('geografi') }}" placeholder="76" required
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('geografi') border-red-500 @enderror">
@error('geografi')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
<div>
<label for="sosiologi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Sosiologi <span class="text-red-500">*</span></label>
<input id="sosiologi" type="number" name="sosiologi" min="0" max="100" value="{{ old('sosiologi') }}" placeholder="74" required
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('sosiologi') border-red-500 @enderror">
@error('sosiologi')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
<div>
<label for="sejarah" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Sejarah <span class="text-red-500">*</span></label>
<input id="sejarah" type="number" name="sejarah" min="0" max="100" value="{{ old('sejarah') }}" placeholder="70" required
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('sejarah') border-red-500 @enderror">
@error('sejarah')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
</div>
@endif
</div>
@if(isset($student) && $student->kelompok_asal == 'IPA')
<div class="grid grid-cols-2 gap-2 sm:gap-3">
<div>
<label for="fisika" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Fisika</label>
<input id="fisika" type="number" name="fisika" min="0" max="100" placeholder="78"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
<div>
<label for="kimia" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Kimia</label>
<input id="kimia" type="number" name="kimia" min="0" max="100" placeholder="72"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
<div class="col-span-2">
<label for="biologi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Biologi</label>
<input id="biologi" type="number" name="biologi" min="0" max="100" placeholder="80"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
</div>
@else
<div class="grid grid-cols-2 gap-2 sm:gap-3">
<div>
<label for="ekonomi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Ekonomi</label>
<input id="ekonomi" type="number" name="ekonomi" min="0" max="100" placeholder="82"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
<div>
<label for="geografi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Geografi</label>
<input id="geografi" type="number" name="geografi" min="0" max="100" placeholder="76"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
<div>
<label for="sosiologi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Sosiologi</label>
<input id="sosiologi" type="number" name="sosiologi" min="0" max="100" placeholder="74"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
<div>
<label for="sejarah" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Sejarah</label>
<input id="sejarah" type="number" name="sejarah" min="0" max="100" placeholder="70"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
</div>
@endif
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
{{-- ============================================ --}}
{{-- KRITERIA 2: MINAT SISWA --}}
{{-- ============================================ --}}
<div class="p-4 sm:p-5 rounded-lg border-2 border-gray-200 bg-gray-50">
<h3 class="font-bold text-maroon text-base sm:text-lg mb-1 sm:mb-2">2. Minat Siswa <span class="text-red-500">*</span></h3>
<p class="text-xs text-gray-600 mb-3 sm:mb-4">Tuliskan bidang atau kegiatan yang Anda minati / sukai.</p>
<div>
<label for="minat" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Bidang Minat</label>
<input id="minat" type="text" name="minat" value="{{ old('minat') }}" placeholder="Contoh: coding, komputer, bisnis, pertanian"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('minat') border-red-500 @enderror" required>
<p class="text-xs text-gray-500 mt-1">Pisahkan dengan koma jika lebih dari satu minat</p>
@error('minat')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
</div>
<!-- Kolom Minat -->
{{-- ============================================ --}}
{{-- KRITERIA 3: PREFERENSI STUDI LANJUTAN --}}
{{-- ============================================ --}}
<div class="p-4 sm:p-5 rounded-lg border-2 border-gray-200 bg-gray-50">
<h3 class="font-bold text-maroon text-base sm:text-lg mb-1 sm:mb-2">2. Minat & Preferensi</h3>
<p class="text-xs text-gray-600 mb-3 sm:mb-4">Deskripsikan minat dan preferensi belajar Anda.</p>
<h3 class="font-bold text-maroon text-base sm:text-lg mb-1 sm:mb-2">3. Preferensi Studi Lanjutan <span class="text-red-500">*</span></h3>
<p class="text-xs text-gray-600 mb-3 sm:mb-4">
Preferensi studi lanjutan adalah kecenderungan pilihan Anda terhadap rumpun bidang studi yang ingin ditempuh setelah lulus, berdasarkan pertimbangan minat, bakat, kemampuan, dan prospek karir di masa depan.
</p>
<div>
<label for="pref_studi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Rumpun Bidang Studi</label>
<select id="pref_studi" name="pref_studi" class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('pref_studi') border-red-500 @enderror" required>
<option value="">-- Pilih Rumpun Bidang Studi --</option>
<option value="Sains & Teknologi" {{ old('pref_studi') == 'Sains & Teknologi' ? 'selected' : '' }}>Sains & Teknologi</option>
<option value="Pertanian & Lingkungan" {{ old('pref_studi') == 'Pertanian & Lingkungan' ? 'selected' : '' }}>Pertanian & Lingkungan</option>
<option value="Kesehatan & Ilmu Hayat" {{ old('pref_studi') == 'Kesehatan & Ilmu Hayat' ? 'selected' : '' }}>Kesehatan & Ilmu Hayat</option>
<option value="Bisnis & Manajemen" {{ old('pref_studi') == 'Bisnis & Manajemen' ? 'selected' : '' }}>Bisnis & Manajemen</option>
<option value="Sosial & Humaniora" {{ old('pref_studi') == 'Sosial & Humaniora' ? 'selected' : '' }}>Sosial, Bahasa & Humaniora</option>
</select>
<p class="text-xs text-gray-500 mt-1">Pilih rumpun studi yang paling mendekati kecenderungan Anda</p>
@error('pref_studi')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
</div>
<div class="space-y-3 sm:space-y-4">
<div>
<label for="minat" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Bidang yang Anda Sukai</label>
<input id="minat" type="text" name="minat" placeholder="Contoh: ngoding, bercocok tanam"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
{{-- ============================================ --}}
{{-- KRITERIA 4: CITA-CITA / PREFERENSI KARIR --}}
{{-- ============================================ --}}
<div class="p-4 sm:p-5 rounded-lg border-2 border-gray-200 bg-gray-50">
<h3 class="font-bold text-maroon text-base sm:text-lg mb-1 sm:mb-2">4. Cita-cita / Preferensi Karir <span class="text-red-500">*</span></h3>
<p class="text-xs text-gray-600 mb-3 sm:mb-4">Tuliskan profesi atau karir yang Anda impikan.</p>
<div>
<label for="cita_cita" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Cita-cita</label>
<input id="cita_cita" type="text" name="cita_cita" value="{{ old('cita_cita') }}" placeholder="Contoh: programmer, dokter, pengusaha"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('cita_cita') border-red-500 @enderror" required>
<p class="text-xs text-gray-500 mt-1">Bisa lebih dari satu, pisahkan dengan koma</p>
@error('cita_cita')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
</div>
<div>
<label for="cita_cita" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Cita-cita / Profesi</label>
<input id="cita_cita" type="text" name="cita_cita" placeholder="Contoh: Teknisi, Dokter, Pengusaha"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
<div>
<label for="pref_studi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Preferensi Pembelajaran</label>
<select name="pref_studi" class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
<option value="Praktik Langsung">Praktik Langsung (Workshop / Lab)</option>
<option value="DuDi">Kerja Sama dengan Industri (DuDi)</option>
<option value="Project Based">Project-Based Learning</option>
<option value="Blended">Teori + Praktik Seimbang</option>
</select>
</div>
<div>
<label for="prestasi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Prestasi (opsional)</label>
<input id="prestasi" type="text" name="prestasi" placeholder="Contoh: Juara lomba, sertifikat"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm">
</div>
{{-- ============================================ --}}
{{-- KRITERIA 5: PRESTASI AKADEMIK / NON-AKADEMIK --}}
{{-- ============================================ --}}
<div class="p-4 sm:p-5 rounded-lg border-2 border-gray-200 bg-gray-50">
<h3 class="font-bold text-maroon text-base sm:text-lg mb-1 sm:mb-2">5. Prestasi Akademik / Non-Akademik</h3>
<p class="text-xs text-gray-600 mb-3 sm:mb-4">Tuliskan prestasi yang pernah diraih (opsional).</p>
<div>
<label for="prestasi" class="block text-xs sm:text-sm font-semibold text-gray-700 mb-1">Prestasi</label>
<input id="prestasi" type="text" name="prestasi" value="{{ old('prestasi') }}" placeholder="Contoh: Juara 1 olimpiade MTK, sertifikat web design"
class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-maroon focus:outline-none text-sm @error('prestasi') border-red-500 @enderror">
<p class="text-xs text-gray-500 mt-1">Kosongkan jika belum ada prestasi</p>
@error('prestasi')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
</div>
</div>
@ -176,7 +258,7 @@ class="block w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus-ma
<!-- Info Metode -->
<div class="mt-6 sm:mt-8 p-3 sm:p-4 bg-white rounded-lg border border-gray-200 shadow-sm">
<p class="text-xs sm:text-sm text-gray-600">
<strong>Metode:</strong> Sistem menggunakan Weighted Naive Bayes dengan 5 kriteria: Nilai (40%), Minat (35%), Preferensi (15%), Cita-cita (5%), Prestasi (5%).
<strong>Metode:</strong> Sistem menggunakan Graduated Scoring dengan 5 kriteria: Nilai Akademik (40%), Minat & Bakat (35%), Preferensi Studi (15%), Cita-cita (5%), Prestasi (5%).
</p>
</div>
</div>

View File

@ -7,16 +7,16 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<style>
.gradient-maroon {
background: linear-gradient(135deg, #6B2C2C 0%, #8B3E3E 100%);
background: linear-gradient(135deg, #5B7B89 0%, #7B9BA5 100%);
}
.text-maroon {
color: #6B2C2C;
color: #5B7B89;
}
.border-maroon {
border-color: #6B2C2C;
border-color: #5B7B89;
}
.bg-cream {
background-color: #FFF9F5;
background-color: #F8FAFC;
}
.mobile-menu-toggle {
display: none;
@ -39,7 +39,7 @@
<div class="container mx-auto px-4 sm:px-6 py-4 flex justify-between items-center">
<div>
<h1 class="text-xl sm:text-2xl md:text-3xl font-bold">Sistem Pemilihan Jurusan</h1>
<p class="text-xs sm:text-sm text-yellow-300 font-semibold mt-1">Pilih Jurusan yang Tepat.</p>
<p class="text-xs sm:text-sm text-gray-200 font-semibold mt-1">Pilih Jurusan yang Tepat.</p>
</div>
<button class="mobile-menu-toggle text-white text-2xl" id="menuToggle"></button>
<div class="mobile-menu hidden md:flex space-x-2 sm:space-x-4 absolute md:relative top-16 md:top-0 left-0 md:left-auto right-0 md:right-0 bg-gradient-maroon md:bg-transparent p-4 md:p-0 flex flex-col md:flex-row gap-2 md:gap-4 w-full md:w-auto">

View File

@ -3,7 +3,9 @@
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\RekomendasiController;
use App\Http\Controllers\ChatbotController;
use App\Http\Controllers\AdminController;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Auth;
Route::get('/', function () {
return view('welcome');
@ -18,7 +20,7 @@
'recommendationCount' => $recommendationCount,
'chatCount' => $chatCount
]);
})->middleware(['auth', 'verified'])->name('dashboard');
})->middleware(['auth', 'verified', 'roleRedirect'])->name('dashboard');
Route::middleware('auth')->group(function () {
// Profile Routes
@ -39,4 +41,42 @@
Route::get('/history/chat', [ChatbotController::class, 'historyChat'])->name('history.chat');
});
// Admin Routes (role-based access control)
Route::middleware(['auth', 'verified', 'isAdmin'])->prefix('admin')->name('admin.')->group(function () {
// 1. Dashboard
Route::get('/dashboard', [AdminController::class, 'dashboard'])->name('dashboard');
// 2. Manajemen Data Siswa
Route::get('/students', [AdminController::class, 'students'])->name('students');
Route::get('/students/{id}', [AdminController::class, 'studentDetail'])->name('student.detail');
Route::get('/students/{id}/chat', [AdminController::class, 'chatHistory'])->name('student.chat');
// 3. Manajemen Jurusan (CRUD dari database)
Route::get('/jurusan', [AdminController::class, 'jurusan'])->name('jurusan');
Route::get('/jurusan/create', [AdminController::class, 'jurusanCreate'])->name('jurusan.create');
Route::post('/jurusan', [AdminController::class, 'jurusanStore'])->name('jurusan.store');
Route::get('/jurusan/{id}/edit', [AdminController::class, 'jurusanEdit'])->name('jurusan.edit');
Route::put('/jurusan/{id}', [AdminController::class, 'jurusanUpdate'])->name('jurusan.update');
Route::delete('/jurusan/{id}', [AdminController::class, 'jurusanDestroy'])->name('jurusan.destroy');
// 4. Manajemen Akun Guru BK
Route::get('/guru-bk', [AdminController::class, 'guruBK'])->name('guru-bk');
Route::get('/guru-bk/create', [AdminController::class, 'guruBKCreate'])->name('guru-bk.create');
Route::post('/guru-bk', [AdminController::class, 'guruBKStore'])->name('guru-bk.store');
Route::get('/guru-bk/{id}/edit', [AdminController::class, 'guruBKEdit'])->name('guru-bk.edit');
Route::put('/guru-bk/{id}', [AdminController::class, 'guruBKUpdate'])->name('guru-bk.update');
Route::delete('/guru-bk/{id}', [AdminController::class, 'guruBKDestroy'])->name('guru-bk.destroy');
// 5. Riwayat Rekomendasi Siswa
Route::get('/riwayat-rekomendasi', [AdminController::class, 'riwayatRekomendasi'])->name('riwayat-rekomendasi');
// 6. Riwayat Konsultasi Chatbot
Route::get('/riwayat-chatbot', [AdminController::class, 'riwayatChatbot'])->name('riwayat-chatbot');
// 7. Profil Admin
Route::get('/profil', [AdminController::class, 'profil'])->name('profil');
Route::put('/profil', [AdminController::class, 'updateProfil'])->name('profil.update');
Route::put('/profil/password', [AdminController::class, 'updatePassword'])->name('profil.password');
});
require __DIR__.'/auth.php';