sistem_rekomendasi/resources/views/student/prediksi.blade.php

630 lines
26 KiB
PHP

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cek Rekomendasi - ASGARD</title>
<script src="https://cdn.tailwindcss.com"></script>
<meta name="csrf-token" content="{{ csrf_token() }}">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background: linear-gradient(180deg, #f8fafc 0%, #f3f7fb 50%, #f8fafc 100%);
}
nav {
box-shadow: 0 10px 35px rgba(15, 23, 42, 0.05);
}
.card-shadow {
box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}
</style>
</head>
<body class="flex flex-col min-h-screen text-slate-800">
<nav class="sticky top-0 z-50 bg-white/95 backdrop-blur-md border-b border-slate-100 px-6 py-4">
<div class="max-w-7xl mx-auto flex items-center justify-between gap-4">
<div class="flex items-center gap-4">
<div class="w-10 h-10 bg-emerald-100 rounded-xl flex items-center justify-center text-emerald-600 font-bold">
AS
</div>
<span class="font-black text-xl tracking-tighter text-emerald-950 uppercase italic">
ASGARD.
</span>
</div>
<div class="hidden md:flex items-center gap-8 text-[11px] font-bold uppercase tracking-widest text-slate-400">
<a href="{{ route('student.profile') }}" class="hover:text-emerald-600 transition">
Beranda
</a>
<a href="{{ route('student.prediction') }}" class="text-emerald-600">
Cek Rekomendasi
</a>
<a href="#" onclick="goToLastResult(event)" class="hover:text-emerald-600 transition">
Hasil Rekomendasi
</a>
</div>
<button onclick="handleLogout()" class="bg-red-50 hover:bg-red-100 text-red-600 border border-red-100 px-4 py-2 rounded-full transition-all">
<span class="text-[10px] font-bold uppercase tracking-widest">
Logout
</span>
</button>
</div>
</nav>
<main class="flex-grow px-6 py-10">
<div class="max-w-3xl mx-auto">
<div class="bg-white rounded-[2rem] card-shadow border border-slate-100 px-6 md:px-10 py-8 md:py-10">
<div class="text-center mb-8">
<div class="w-12 h-12 mx-auto rounded-2xl bg-emerald-100 flex items-center justify-center text-2xl mb-4">
📝
</div>
<h1 class="text-3xl md:text-4xl font-extrabold tracking-tight text-slate-900">
Form Rekomendasi Jurusan
</h1>
<p class="text-slate-500 mt-3 text-sm md:text-base leading-relaxed">
Isi data berikut untuk mendapatkan rekomendasi jurusan.
</p>
</div>
<div id="alert-box" class="hidden mb-6 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700"></div>
<div class="space-y-6">
<section class="bg-slate-50 border border-slate-100 rounded-3xl p-5 md:p-6">
<h2 class="font-extrabold text-slate-800 mb-1">
💰 Kondisi Ekonomi
</h2>
<p class="text-sm text-slate-500 mb-4">
Masukkan estimasi pendapatan gabungan orang tua per bulan dalam rupiah.
</p>
<input
type="number"
id="gaji_ortu"
min="0"
placeholder="Contoh: 3000000"
class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base font-semibold text-slate-700 outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100">
</section>
<section class="bg-emerald-50/40 border border-emerald-100 rounded-3xl p-5 md:p-6">
<h2 class="font-extrabold text-emerald-800 mb-4">
🎯 Kuesioner Minat dan Bakat
</h2>
<div class="space-y-4">
<div>
<label for="q1" class="block text-sm font-semibold text-slate-700 mb-2">
1. Aktivitas apa yang paling kamu sukai?
</label>
<select id="q1" class="question-select w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-sm text-slate-700 outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100">
<option value="">Pilih jawaban</option>
<option value="r">Memperbaiki alat, praktik lapangan, atau pekerjaan teknis</option>
<option value="i">Meneliti, mengamati, dan memecahkan masalah logika</option>
<option value="a">Menggambar, menulis, mendesain, atau berkarya</option>
<option value="s">Membantu, mengajar, atau mendampingi orang lain</option>
<option value="e">Memimpin tim, negosiasi, atau berwirausaha</option>
<option value="c">Mengatur data, arsip, dokumen, dan pekerjaan terstruktur</option>
</select>
</div>
<div>
<label for="q2" class="block text-sm font-semibold text-slate-700 mb-2">
2. Saat ada tugas kelompok, kamu paling nyaman berperan sebagai apa?
</label>
<select id="q2" class="question-select w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-sm text-slate-700 outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100">
<option value="">Pilih jawaban</option>
<option value="r">Pelaksana teknis atau praktik</option>
<option value="i">Analis data dan pencari solusi</option>
<option value="a">Pembuat ide dan desain</option>
<option value="s">Penjaga kekompakan tim</option>
<option value="e">Pemimpin dan presenter</option>
<option value="c">Penyusun dokumen dan jadwal</option>
</select>
</div>
<div>
<label for="q3" class="block text-sm font-semibold text-slate-700 mb-2">
3. Jenis pekerjaan masa depan yang paling menarik buatmu?
</label>
<select id="q3" class="question-select w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-sm text-slate-700 outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100">
<option value="">Pilih jawaban</option>
<option value="r">Teknisi, operator, atau pekerjaan lapangan</option>
<option value="i">Dokter, peneliti, analis, atau ilmuwan</option>
<option value="a">Desainer, penulis, editor, atau seniman</option>
<option value="s">Guru, konselor, perawat, atau pekerja sosial</option>
<option value="e">Pebisnis, manajer, marketer, atau leader</option>
<option value="c">Administrator, akuntan, atau staf operasional</option>
</select>
</div>
<div>
<label for="q4" class="block text-sm font-semibold text-slate-700 mb-2">
4. Kegiatan sekolah seperti apa yang paling kamu nikmati?
</label>
<select id="q4" class="question-select w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-sm text-slate-700 outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100">
<option value="">Pilih jawaban</option>
<option value="r">Praktikum, keterampilan, atau kegiatan teknis</option>
<option value="i">Eksperimen, olimpiade, atau analisis kasus</option>
<option value="a">Lomba desain, seni, atau karya kreatif</option>
<option value="s">Kegiatan sosial, mentoring, atau organisasi pelayanan</option>
<option value="e">OSIS, lomba bisnis, atau kepanitiaan inti</option>
<option value="c">Administrasi acara, pencatatan, atau dokumentasi</option>
</select>
</div>
<div>
<label for="q5" class="block text-sm font-semibold text-slate-700 mb-2">
5. Kamu paling nyaman bekerja dalam suasana seperti apa?
</label>
<select id="q5" class="question-select w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-sm text-slate-700 outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100">
<option value="">Pilih jawaban</option>
<option value="r">Aktif, praktik, dan langsung di lapangan</option>
<option value="i">Tenang, fokus, dan penuh analisis</option>
<option value="a">Bebas, kreatif, dan tidak kaku</option>
<option value="s">Hangat, kolaboratif, dan banyak interaksi</option>
<option value="e">Dinamis, kompetitif, dan penuh tantangan</option>
<option value="c">Rapi, teratur, dan jelas prosedurnya</option>
</select>
</div>
</div>
</section>
<section class="bg-slate-50 border border-slate-100 rounded-3xl p-5 md:p-6">
<h2 class="font-extrabold text-slate-700 mb-2">
📚 Nilai Rapor
</h2>
<p class="text-sm text-slate-500 mb-4">
Masukkan nilai rapor pada setiap mata pelajaran dengan rentang 0 sampai 100.
</p>
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
@php
$mapel = [
'agama' => 'Agama',
'pkn' => 'PKN',
'bahasa_indonesia' => 'B. Indonesia',
'bahasa_inggris' => 'B. Inggris',
'matematika' => 'Matematika',
'fisika' => 'Fisika',
'kimia' => 'Kimia',
'biologi' => 'Biologi',
'sejarah' => 'Sejarah',
'geografi' => 'Geografi',
'ekonomi_mapel' => 'Ekonomi',
'sosiologi' => 'Sosiologi',
'seni_budaya' => 'Seni Budaya',
'pjok' => 'PJOK',
'informatika' => 'Informatika',
];
@endphp
@foreach ($mapel as $id => $label)
<div>
<label class="block text-xs font-bold text-slate-500 mb-2">
{{ $label }}
</label>
<input
type="number"
id="{{ $id }}"
min="0"
max="100"
placeholder="0 - 100"
class="w-full rounded-xl border border-slate-200 bg-white px-3 py-3 font-semibold outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-100">
</div>
@endforeach
</div>
</section>
<button
type="button"
id="btnSubmit"
class="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-extrabold py-4 px-6 rounded-2xl transition">
Proses Rekomendasi
</button>
</div>
</div>
</div>
</main>
<footer class="bg-[#062C22] text-white py-10 px-6 mt-10">
<div class="max-w-7xl mx-auto text-center">
<p class="text-[10px] font-medium uppercase tracking-[0.35em] text-slate-400">
© 2026 GALANG SEFIAN ADJI - POLITEKNIK NEGERI JEMBER
</p>
</div>
</footer>
<script>
function getAppBaseUrl() {
const origin = window.location.origin;
const segments = window.location.pathname.split('/').filter(Boolean);
const studentIndex = segments.indexOf('student');
if (studentIndex > 0) {
return origin + '/' + segments.slice(0, studentIndex).join('/');
}
return origin;
}
const APP_BASE_URL = getAppBaseUrl();
const routeLogin = `${APP_BASE_URL}/login`;
const routeDashboard = `${APP_BASE_URL}/student/profile`;
const routePrediction = `${APP_BASE_URL}/student/prediction`;
const routeAdminDashboard = `${APP_BASE_URL}/admin/dashboard`;
const apiLogoutUrl = `${APP_BASE_URL}/api/logout`;
const apiProsesRekomendasiUrl = `${APP_BASE_URL}/api/student/proses-rekomendasi`;
const studentResultUrlTemplate = `${APP_BASE_URL}/student/result/__ID__`;
const btnSubmit = document.getElementById('btnSubmit');
const alertBox = document.getElementById('alert-box');
function getAccessToken() {
return localStorage.getItem('access_token') ||
localStorage.getItem('token') ||
sessionStorage.getItem('access_token') ||
sessionStorage.getItem('token') ||
'';
}
function getUserRole() {
return localStorage.getItem('user_role') ||
localStorage.getItem('role') ||
sessionStorage.getItem('user_role') ||
sessionStorage.getItem('role') ||
'';
}
function clearLoginStorage() {
localStorage.removeItem('access_token');
localStorage.removeItem('token');
localStorage.removeItem('user_role');
localStorage.removeItem('role');
localStorage.removeItem('user_name');
localStorage.removeItem('name');
sessionStorage.removeItem('access_token');
sessionStorage.removeItem('token');
sessionStorage.removeItem('user_role');
sessionStorage.removeItem('role');
}
function redirectByRole(role) {
if (role === 'admin') {
window.location.href = routeAdminDashboard;
return;
}
if (role === 'siswa' || role === 'student') {
window.location.href = routeDashboard;
return;
}
window.location.href = routeLogin;
}
function makeResultUrl(id) {
return id ? studentResultUrlTemplate.replace('__ID__', encodeURIComponent(id)) : '';
}
function normalizeAppUrl(url) {
if (!url) return '';
const text = String(url);
if (text.startsWith('http://') || text.startsWith('https://')) {
return text;
}
if (text.startsWith('/')) {
return APP_BASE_URL + text;
}
return `${APP_BASE_URL}/${text}`;
}
async function parseJsonResponse(response) {
const text = await response.text();
try {
return text ? JSON.parse(text) : {};
} catch (error) {
return {
success: false,
message: text && text.trim().startsWith('<')
? 'Server mengembalikan halaman HTML, bukan JSON. Biasanya URL API salah, route API belum terdaftar, atau Flask API belum aktif.'
: (text || response.statusText || 'Respon server tidak valid.')
};
}
}
function getStoredLastResult() {
try {
const saved = localStorage.getItem('asgard_last_result');
return saved ? JSON.parse(saved) : null;
} catch (error) {
return null;
}
}
function goToLastResult(event) {
event.preventDefault();
const saved = getStoredLastResult();
if (saved && saved.url) {
window.location.href = normalizeAppUrl(saved.url);
return;
}
if (saved && saved.id) {
window.location.href = makeResultUrl(saved.id);
return;
}
alert('Belum ada hasil rekomendasi yang bisa dibuka. Silakan lakukan cek rekomendasi terlebih dahulu.');
window.location.href = routePrediction;
}
async function handleLogout() {
const token = getAccessToken();
if (token) {
try {
await fetch(apiLogoutUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
});
} catch (error) {
console.error('Logout error:', error);
}
}
clearLoginStorage();
localStorage.removeItem('asgard_last_result');
window.location.href = routeLogin;
}
function showError(message) {
alertBox.innerText = message;
alertBox.classList.remove('hidden');
}
function hideError() {
alertBox.innerText = '';
alertBox.classList.add('hidden');
}
function getNumberValue(id) {
const value = document.getElementById(id).value;
return value === '' ? null : Number(value);
}
function calculateRiasecScores() {
const scores = {
r: 0,
i: 0,
a: 0,
s: 0,
e: 0,
c: 0
};
['q1', 'q2', 'q3', 'q4', 'q5'].forEach(id => {
const value = document.getElementById(id).value;
if (value && Object.prototype.hasOwnProperty.call(scores, value)) {
scores[value] += 20;
}
});
return scores;
}
function validateForm(payload) {
const scoreFields = [
'agama',
'pkn',
'bahasa_indonesia',
'bahasa_inggris',
'matematika',
'fisika',
'kimia',
'biologi',
'sejarah',
'geografi',
'ekonomi_mapel',
'sosiologi',
'seni_budaya',
'pjok',
'informatika'
];
for (const field of scoreFields) {
const rawValue = document.getElementById(field).value;
if (rawValue === '') {
return `Nilai ${field.replaceAll('_', ' ')} wajib diisi.`;
}
const value = Number(payload[field]);
if (Number.isNaN(value) || value < 0 || value > 100) {
return `Nilai ${field.replaceAll('_', ' ')} harus berada pada rentang 0 sampai 100.`;
}
}
if (document.getElementById('gaji_ortu').value === '') {
return 'Pendapatan gabungan orang tua wajib diisi.';
}
if (Number(payload.gaji_ortu) < 0) {
return 'Pendapatan gabungan orang tua tidak boleh bernilai negatif.';
}
for (const id of ['q1', 'q2', 'q3', 'q4', 'q5']) {
if (!document.getElementById(id).value) {
return 'Semua pertanyaan minat dan bakat wajib diisi.';
}
}
return null;
}
document.addEventListener('DOMContentLoaded', function () {
const token = getAccessToken();
const role = getUserRole();
if (!token) {
window.location.href = routeLogin;
return;
}
if (role !== 'siswa' && role !== 'student') {
redirectByRole(role);
}
});
btnSubmit.addEventListener('click', async function () {
hideError();
const token = getAccessToken();
const role = getUserRole();
if (!token) {
showError('Sesi login tidak ditemukan. Silakan login kembali.');
setTimeout(() => {
window.location.href = routeLogin;
}, 1200);
return;
}
if (role !== 'siswa' && role !== 'student') {
redirectByRole(role);
return;
}
btnSubmit.innerText = 'Memproses Rekomendasi...';
btnSubmit.disabled = true;
const scores = calculateRiasecScores();
const payload = {
agama: getNumberValue('agama'),
pkn: getNumberValue('pkn'),
bahasa_indonesia: getNumberValue('bahasa_indonesia'),
bahasa_inggris: getNumberValue('bahasa_inggris'),
matematika: getNumberValue('matematika'),
fisika: getNumberValue('fisika'),
kimia: getNumberValue('kimia'),
biologi: getNumberValue('biologi'),
sejarah: getNumberValue('sejarah'),
geografi: getNumberValue('geografi'),
ekonomi_mapel: getNumberValue('ekonomi_mapel'),
sosiologi: getNumberValue('sosiologi'),
seni_budaya: getNumberValue('seni_budaya'),
pjok: getNumberValue('pjok'),
informatika: getNumberValue('informatika'),
riasec_r: scores.r,
riasec_i: scores.i,
riasec_a: scores.a,
riasec_s: scores.s,
riasec_e: scores.e,
riasec_c: scores.c,
gaji_ortu: getNumberValue('gaji_ortu')
};
const validationError = validateForm(payload);
if (validationError) {
showError(validationError);
btnSubmit.innerText = 'Proses Rekomendasi';
btnSubmit.disabled = false;
return;
}
try {
const response = await fetch(apiProsesRekomendasiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(payload)
});
const result = await parseJsonResponse(response);
if (response.status === 401 || response.status === 403) {
clearLoginStorage();
showError('Sesi login berakhir. Silakan login kembali.');
setTimeout(() => {
window.location.href = routeLogin;
}, 1200);
return;
}
if (response.ok && result.success) {
const riwayatId = result.riwayat_id || result.id || result.data?.id || '';
const prediksiJurusan = result.prediksi_jurusan || result.hasil_rekomendasi_jurusan || result.data?.hasil_rekomendasi_jurusan || '';
const redirectUrl = normalizeAppUrl(result.redirect_url || makeResultUrl(riwayatId));
if (riwayatId && prediksiJurusan) {
localStorage.setItem('asgard_last_result', JSON.stringify({
id: riwayatId,
jurusan: prediksiJurusan,
url: redirectUrl,
tanggal: new Date().toLocaleString('id-ID')
}));
}
window.location.href = redirectUrl || routeDashboard;
return;
}
showError(result.message || 'Terjadi kesalahan saat memproses rekomendasi.');
} catch (error) {
console.error(error);
showError(error.message || 'Kesalahan jaringan. Pastikan koneksi server dan Flask API PythonAnywhere aktif.');
} finally {
btnSubmit.innerText = 'Proses Rekomendasi';
btnSubmit.disabled = false;
}
});
</script>
</body>
</html>