new update
This commit is contained in:
parent
68b3232ef2
commit
f1d9460598
|
|
@ -9,10 +9,36 @@
|
|||
|
||||
class AlumniController extends Controller
|
||||
{
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
$alumni = Alumni::all();
|
||||
return response()->json(['success' => true, 'data' => $alumni], 200);
|
||||
$perPage = (int) $request->input('per_page', 7);
|
||||
$perPage = in_array($perPage, [5, 7]) ? $perPage : 7;
|
||||
|
||||
$search = trim((string) $request->input('search', ''));
|
||||
|
||||
$query = Alumni::query()->orderBy('id', 'desc');
|
||||
|
||||
if ($search !== '') {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('id_alumni', 'like', "%{$search}%")
|
||||
->orWhere('nama_alumni', 'like', "%{$search}%")
|
||||
->orWhere('perguruan_tinggi', 'like', "%{$search}%")
|
||||
->orWhere('jurusan_diambil', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$alumni = $query->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $alumni->items(),
|
||||
'current_page' => $alumni->currentPage(),
|
||||
'last_page' => $alumni->lastPage(),
|
||||
'per_page' => $alumni->perPage(),
|
||||
'total' => $alumni->total(),
|
||||
'from' => $alumni->firstItem(),
|
||||
'to' => $alumni->lastItem(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
|
|
@ -32,9 +58,10 @@ public function store(Request $request)
|
|||
}
|
||||
|
||||
$alumni = Alumni::create($request->all());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Alumni ' . $alumni->id_alumni . ' berhasil ditambahkan.',
|
||||
'success' => true,
|
||||
'message' => 'Alumni ' . $alumni->id_alumni . ' berhasil ditambahkan.',
|
||||
'data' => $alumni
|
||||
], 201);
|
||||
}
|
||||
|
|
@ -42,7 +69,10 @@ public function store(Request $request)
|
|||
public function update(Request $request, $id)
|
||||
{
|
||||
$alumni = Alumni::find($id);
|
||||
if (!$alumni) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$alumni) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'nama_alumni' => 'required|string|max:255',
|
||||
|
|
@ -55,14 +85,23 @@ public function update(Request $request, $id)
|
|||
}
|
||||
|
||||
$alumni->update($request->all());
|
||||
return response()->json(['success' => true, 'message' => 'Data ' . $alumni->id_alumni . ' berhasil diperbarui'], 200);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Data ' . $alumni->id_alumni . ' berhasil diperbarui'
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$alumni = Alumni::find($id);
|
||||
if (!$alumni) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$alumni) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$alumni->delete();
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Data Alumni berhasil dihapus'], 200);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,34 +14,58 @@ class DashboardAdminController extends Controller
|
|||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
// Hitung siswa (fallback ke ID unik di tabel riwayat jika data role user kosong/berbeda)
|
||||
$tableName = (new RiwayatRekomendasi())->getTable();
|
||||
|
||||
$totalSiswa = User::whereIn('role', ['siswa', 'student'])->count();
|
||||
|
||||
if ($totalSiswa === 0) {
|
||||
$totalSiswa = RiwayatRekomendasi::distinct('user_id')->count('user_id');
|
||||
}
|
||||
|
||||
// Hitung total semua prediksi yang sudah dilakukan
|
||||
$totalRekomendasi = RiwayatRekomendasi::count();
|
||||
|
||||
// Cek nilai rata-rata akurasi secara dinamis (default 86.4% jika kolom tidak ditemukan)
|
||||
$rataAkurasi = 86.4;
|
||||
try {
|
||||
$tableName = (new RiwayatRekomendasi())->getTable();
|
||||
if (Schema::hasColumn($tableName, 'probabilitas')) {
|
||||
$avg = RiwayatRekomendasi::avg('probabilitas');
|
||||
if ($avg) $rataAkurasi = round($avg, 2);
|
||||
} elseif (Schema::hasColumn($tableName, 'akurasi')) {
|
||||
$avg = RiwayatRekomendasi::avg('akurasi');
|
||||
if ($avg) $rataAkurasi = round($avg, 2);
|
||||
}
|
||||
} catch (Exception $e) {}
|
||||
|
||||
// Kelompokkan hasil rekomendasi untuk ditampilkan pada grafik
|
||||
$distribusiJurusan = RiwayatRekomendasi::selectRaw('hasil_rekomendasi_jurusan as jurusan, count(*) as total')
|
||||
$totalRekomendasi = RiwayatRekomendasi::count();
|
||||
|
||||
$kolomAkurasi = null;
|
||||
|
||||
if (Schema::hasColumn($tableName, 'akurasi_prediksi')) {
|
||||
$kolomAkurasi = 'akurasi_prediksi';
|
||||
} elseif (Schema::hasColumn($tableName, 'probabilitas')) {
|
||||
$kolomAkurasi = 'probabilitas';
|
||||
} elseif (Schema::hasColumn($tableName, 'akurasi')) {
|
||||
$kolomAkurasi = 'akurasi';
|
||||
}
|
||||
|
||||
$rataAkurasi = null;
|
||||
|
||||
if ($kolomAkurasi) {
|
||||
$avg = RiwayatRekomendasi::whereNotNull($kolomAkurasi)->avg($kolomAkurasi);
|
||||
$rataAkurasi = $avg !== null ? round((float) $avg, 2) : null;
|
||||
}
|
||||
|
||||
$akurasiStatus = $rataAkurasi !== null
|
||||
? 'Berdasarkan rata-rata tingkat keyakinan dari seluruh hasil rekomendasi.'
|
||||
: 'Belum ada nilai keyakinan prediksi yang tersimpan. Lakukan prediksi baru terlebih dahulu.';
|
||||
|
||||
$selectDistribusi = 'hasil_rekomendasi_jurusan as jurusan, COUNT(*) as total';
|
||||
|
||||
if ($kolomAkurasi) {
|
||||
$selectDistribusi .= ', ROUND(AVG(' . $kolomAkurasi . '), 2) as rata_akurasi';
|
||||
}
|
||||
|
||||
$distribusiJurusan = RiwayatRekomendasi::selectRaw($selectDistribusi)
|
||||
->whereNotNull('hasil_rekomendasi_jurusan')
|
||||
->groupBy('hasil_rekomendasi_jurusan')
|
||||
->orderBy('total', 'desc')
|
||||
->get();
|
||||
->limit(10)
|
||||
->get()
|
||||
->map(function ($item) use ($kolomAkurasi) {
|
||||
return [
|
||||
'jurusan' => $item->jurusan,
|
||||
'total' => (int) $item->total,
|
||||
'rata_akurasi' => $kolomAkurasi && $item->rata_akurasi !== null
|
||||
? round((float) $item->rata_akurasi, 2)
|
||||
: null,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
|
@ -49,7 +73,8 @@ public function index(Request $request)
|
|||
'total_siswa' => $totalSiswa,
|
||||
'rekomendasi_diproses' => $totalRekomendasi,
|
||||
'rata_rata_akurasi' => $rataAkurasi,
|
||||
'distribusi_jurusan' => $distribusiJurusan
|
||||
'akurasi_status' => $akurasiStatus,
|
||||
'distribusi_jurusan' => $distribusiJurusan,
|
||||
]
|
||||
], 200);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,34 @@
|
|||
|
||||
class GuruController extends Controller
|
||||
{
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
$guru = Guru::all();
|
||||
return response()->json(['success' => true, 'data' => $guru], 200);
|
||||
$perPage = (int) $request->input('per_page', 7);
|
||||
$perPage = in_array($perPage, [5, 7]) ? $perPage : 7;
|
||||
|
||||
$search = trim((string) $request->input('search', ''));
|
||||
|
||||
$query = Guru::query()->orderBy('id', 'desc');
|
||||
|
||||
if ($search !== '') {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('id_guru', 'like', "%{$search}%")
|
||||
->orWhere('nama_guru', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$guru = $query->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $guru->items(),
|
||||
'current_page' => $guru->currentPage(),
|
||||
'last_page' => $guru->lastPage(),
|
||||
'per_page' => $guru->perPage(),
|
||||
'total' => $guru->total(),
|
||||
'from' => $guru->firstItem(),
|
||||
'to' => $guru->lastItem(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
|
|
@ -28,9 +52,10 @@ public function store(Request $request)
|
|||
}
|
||||
|
||||
$guru = Guru::create($request->all());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Guru ' . $guru->id_guru . ' berhasil ditambahkan.',
|
||||
'success' => true,
|
||||
'message' => 'Guru ' . $guru->id_guru . ' berhasil ditambahkan.',
|
||||
'data' => $guru
|
||||
], 201);
|
||||
}
|
||||
|
|
@ -38,7 +63,10 @@ public function store(Request $request)
|
|||
public function update(Request $request, $id)
|
||||
{
|
||||
$guru = Guru::find($id);
|
||||
if (!$guru) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$guru) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'nama_guru' => 'required|string|max:255',
|
||||
|
|
@ -51,14 +79,23 @@ public function update(Request $request, $id)
|
|||
}
|
||||
|
||||
$guru->update($request->all());
|
||||
return response()->json(['success' => true, 'message' => 'Data ' . $guru->id_guru . ' berhasil diperbarui'], 200);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Data ' . $guru->id_guru . ' berhasil diperbarui'
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$guru = Guru::find($id);
|
||||
if (!$guru) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$guru) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$guru->delete();
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Data Guru berhasil dihapus'], 200);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,35 @@
|
|||
|
||||
class JurusanController extends Controller
|
||||
{
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
$jurusan = Jurusan::all();
|
||||
return response()->json(['success' => true, 'data' => $jurusan], 200);
|
||||
$perPage = (int) $request->input('per_page', 7);
|
||||
$perPage = in_array($perPage, [5, 7]) ? $perPage : 7;
|
||||
|
||||
$search = trim((string) $request->input('search', ''));
|
||||
|
||||
$query = Jurusan::query()->orderBy('id', 'desc');
|
||||
|
||||
if ($search !== '') {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('nama_jurusan', 'like', "%{$search}%")
|
||||
->orWhere('rumpun', 'like', "%{$search}%")
|
||||
->orWhere('prospek_karir', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$jurusan = $query->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $jurusan->items(),
|
||||
'current_page' => $jurusan->currentPage(),
|
||||
'last_page' => $jurusan->lastPage(),
|
||||
'per_page' => $jurusan->perPage(),
|
||||
'total' => $jurusan->total(),
|
||||
'from' => $jurusan->firstItem(),
|
||||
'to' => $jurusan->lastItem(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
|
|
@ -27,31 +52,52 @@ public function store(Request $request)
|
|||
}
|
||||
|
||||
$jurusan = Jurusan::create($request->all());
|
||||
return response()->json(['success' => true, 'message' => 'Jurusan berhasil ditambahkan', 'data' => $jurusan], 201);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Jurusan berhasil ditambahkan',
|
||||
'data' => $jurusan
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$jurusan = Jurusan::find($id);
|
||||
if (!$jurusan) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$jurusan) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'data' => $jurusan], 200);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$jurusan = Jurusan::find($id);
|
||||
if (!$jurusan) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$jurusan) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$jurusan->update($request->all());
|
||||
return response()->json(['success' => true, 'message' => 'Jurusan berhasil diperbarui', 'data' => $jurusan], 200);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Jurusan berhasil diperbarui',
|
||||
'data' => $jurusan
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$jurusan = Jurusan::find($id);
|
||||
if (!$jurusan) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$jurusan) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$jurusan->delete();
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Jurusan berhasil dihapus'], 200);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,38 @@
|
|||
|
||||
class ProspekKerjaController extends Controller
|
||||
{
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
$prospek = ProspekKerja::with('jurusan')->orderBy('id', 'desc')->get();
|
||||
return response()->json(['success' => true, 'data' => $prospek], 200);
|
||||
$perPage = (int) $request->input('per_page', 7);
|
||||
$perPage = in_array($perPage, [5, 7]) ? $perPage : 7;
|
||||
|
||||
$search = trim((string) $request->input('search', ''));
|
||||
|
||||
$query = ProspekKerja::with('jurusan')->orderBy('id', 'desc');
|
||||
|
||||
if ($search !== '') {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('nama_profesi', 'like', "%{$search}%")
|
||||
->orWhere('range_gaji', 'like', "%{$search}%")
|
||||
->orWhere('deskripsi_pekerjaan', 'like', "%{$search}%")
|
||||
->orWhereHas('jurusan', function ($jurusan) use ($search) {
|
||||
$jurusan->where('nama_jurusan', 'like', "%{$search}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$prospek = $query->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $prospek->items(),
|
||||
'current_page' => $prospek->currentPage(),
|
||||
'last_page' => $prospek->lastPage(),
|
||||
'per_page' => $prospek->perPage(),
|
||||
'total' => $prospek->total(),
|
||||
'from' => $prospek->firstItem(),
|
||||
'to' => $prospek->lastItem(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
|
|
@ -29,20 +57,32 @@ public function store(Request $request)
|
|||
}
|
||||
|
||||
$prospek = ProspekKerja::create($request->all());
|
||||
return response()->json(['success' => true, 'message' => 'Prospek Kerja berhasil ditambahkan', 'data' => $prospek], 201);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Prospek Kerja berhasil ditambahkan',
|
||||
'data' => $prospek
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$prospek = ProspekKerja::find($id);
|
||||
if (!$prospek) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$prospek) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'data' => $prospek], 200);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$prospek = ProspekKerja::find($id);
|
||||
if (!$prospek) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$prospek) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'jurusan_id' => 'required|exists:jurusans,id',
|
||||
|
|
@ -56,15 +96,24 @@ public function update(Request $request, $id)
|
|||
}
|
||||
|
||||
$prospek->update($request->all());
|
||||
return response()->json(['success' => true, 'message' => 'Prospek Kerja berhasil diperbarui', 'data' => $prospek], 200);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Prospek Kerja berhasil diperbarui',
|
||||
'data' => $prospek
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$prospek = ProspekKerja::find($id);
|
||||
if (!$prospek) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$prospek) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$prospek->delete();
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Prospek Kerja berhasil dihapus'], 200);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,31 +5,57 @@
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\RiwayatRekomendasi;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Exception;
|
||||
|
||||
class RiwayatController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mengambil semua data riwayat prediksi
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$riwayat = RiwayatRekomendasi::with('user:id,name')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'tanggal' => $item->created_at->format('d M Y'),
|
||||
'nama_siswa' => $item->user ? $item->user->name : 'Siswa Tidak Diketahui',
|
||||
'hasil' => $item->hasil_rekomendasi_jurusan ?? 'Belum ada hasil'
|
||||
];
|
||||
$perPage = (int) $request->input('per_page', 7);
|
||||
$perPage = in_array($perPage, [5, 7]) ? $perPage : 7;
|
||||
|
||||
$search = trim((string) $request->input('search', ''));
|
||||
|
||||
$query = RiwayatRekomendasi::with('user:id,name,email')
|
||||
->orderBy('created_at', 'desc');
|
||||
|
||||
if ($search !== '') {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('hasil_rekomendasi_jurusan', 'like', "%{$search}%")
|
||||
->orWhereHas('user', function ($user) use ($search) {
|
||||
$user->where('name', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$riwayat = $query->paginate($perPage);
|
||||
|
||||
$riwayat->getCollection()->transform(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'tanggal' => $item->created_at ? $item->created_at->format('d M Y') : '-',
|
||||
'jam' => $item->created_at ? $item->created_at->format('H:i') : '-',
|
||||
'nama_siswa' => $item->user ? $item->user->name : 'Siswa Tidak Diketahui',
|
||||
'email_siswa' => $item->user ? $item->user->email : '-',
|
||||
'hasil' => $item->hasil_rekomendasi_jurusan ?? 'Belum ada hasil',
|
||||
'akurasi_prediksi' => $this->getAkurasiPrediksi($item),
|
||||
'minat_utama' => $this->getMinatUtama($item),
|
||||
'rata_nilai' => $this->getRataNilai($item),
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $riwayat
|
||||
'data' => $riwayat->items(),
|
||||
'current_page' => $riwayat->currentPage(),
|
||||
'last_page' => $riwayat->lastPage(),
|
||||
'per_page' => $riwayat->perPage(),
|
||||
'total' => $riwayat->total(),
|
||||
'from' => $riwayat->firstItem(),
|
||||
'to' => $riwayat->lastItem(),
|
||||
], 200);
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
|
@ -40,9 +66,6 @@ public function index(Request $request)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mengambil data detail riwayat prediksi berdasarkan ID
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
try {
|
||||
|
|
@ -52,11 +75,33 @@ public function show($id)
|
|||
'success' => true,
|
||||
'data' => [
|
||||
'id' => $detail->id,
|
||||
'tanggal' => $detail->created_at->format('d M Y H:i:s'),
|
||||
'tanggal' => $detail->created_at ? $detail->created_at->format('d M Y H:i:s') : '-',
|
||||
'nama_siswa' => $detail->user ? $detail->user->name : 'Siswa Tidak Diketahui',
|
||||
'email_siswa' => $detail->user ? $detail->user->email : '-',
|
||||
'hasil_rekomendasi' => $detail->hasil_rekomendasi_jurusan ?? 'Belum ada hasil',
|
||||
'detail_lengkap' => $detail
|
||||
'akurasi_prediksi' => $this->getAkurasiPrediksi($detail),
|
||||
'minat_utama' => $this->getMinatUtama($detail),
|
||||
'rata_nilai' => $this->getRataNilai($detail),
|
||||
'kondisi_ekonomi' => $this->getLabelEkonomi($detail->gaji_ortu),
|
||||
'gaji_ortu' => $detail->gaji_ortu,
|
||||
'nilai_ringkas' => [
|
||||
'Matematika' => $detail->matematika,
|
||||
'B. Indonesia' => $detail->bahasa_indonesia,
|
||||
'B. Inggris' => $detail->bahasa_inggris,
|
||||
'Fisika' => $detail->fisika,
|
||||
'Kimia' => $detail->kimia,
|
||||
'Biologi' => $detail->biologi,
|
||||
'Ekonomi' => $detail->ekonomi_mapel,
|
||||
'Informatika' => $detail->informatika,
|
||||
],
|
||||
'riasec' => [
|
||||
'Realistic' => $detail->riasec_r,
|
||||
'Investigative' => $detail->riasec_i,
|
||||
'Artistic' => $detail->riasec_a,
|
||||
'Social' => $detail->riasec_s,
|
||||
'Enterprising' => $detail->riasec_e,
|
||||
'Conventional' => $detail->riasec_c,
|
||||
],
|
||||
]
|
||||
], 200);
|
||||
|
||||
|
|
@ -67,4 +112,79 @@ public function show($id)
|
|||
], 404);
|
||||
}
|
||||
}
|
||||
|
||||
private function getAkurasiPrediksi($riwayat): ?float
|
||||
{
|
||||
$tableName = (new RiwayatRekomendasi())->getTable();
|
||||
|
||||
if (!Schema::hasColumn($tableName, 'akurasi_prediksi')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($riwayat->akurasi_prediksi === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return round((float) $riwayat->akurasi_prediksi, 2);
|
||||
}
|
||||
|
||||
private function getRataNilai($riwayat): float
|
||||
{
|
||||
$nilai = [
|
||||
$riwayat->agama,
|
||||
$riwayat->pkn,
|
||||
$riwayat->bahasa_indonesia,
|
||||
$riwayat->bahasa_inggris,
|
||||
$riwayat->matematika,
|
||||
$riwayat->fisika,
|
||||
$riwayat->kimia,
|
||||
$riwayat->biologi,
|
||||
$riwayat->sejarah,
|
||||
$riwayat->geografi,
|
||||
$riwayat->ekonomi_mapel,
|
||||
$riwayat->sosiologi,
|
||||
$riwayat->seni_budaya,
|
||||
$riwayat->pjok,
|
||||
$riwayat->informatika,
|
||||
];
|
||||
|
||||
$nilai = array_filter($nilai, fn ($item) => $item !== null);
|
||||
|
||||
if (count($nilai) === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round(array_sum($nilai) / count($nilai), 2);
|
||||
}
|
||||
|
||||
private function getMinatUtama($riwayat): string
|
||||
{
|
||||
$scores = [
|
||||
'Teknis & Lapangan' => (float) $riwayat->riasec_r,
|
||||
'Sains, Riset & Analitis' => (float) $riwayat->riasec_i,
|
||||
'Kreatif & Desain' => (float) $riwayat->riasec_a,
|
||||
'Sosial & Pelayanan' => (float) $riwayat->riasec_s,
|
||||
'Bisnis & Kepemimpinan' => (float) $riwayat->riasec_e,
|
||||
'Administratif & Terstruktur' => (float) $riwayat->riasec_c,
|
||||
];
|
||||
|
||||
arsort($scores);
|
||||
|
||||
return array_key_first($scores) ?? 'Belum teridentifikasi';
|
||||
}
|
||||
|
||||
private function getLabelEkonomi($gaji): string
|
||||
{
|
||||
$gaji = (float) $gaji;
|
||||
|
||||
if ($gaji < 2000000) {
|
||||
return 'Kurang Mampu';
|
||||
}
|
||||
|
||||
if ($gaji <= 5000000) {
|
||||
return 'Menengah';
|
||||
}
|
||||
|
||||
return 'Mampu';
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,44 @@
|
|||
|
||||
class SiswaController extends Controller
|
||||
{
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
$siswa = Siswa::with(['user', 'jurusan'])->get();
|
||||
return response()->json(['success' => true, 'data' => $siswa], 200);
|
||||
$perPage = (int) $request->input('per_page', 7);
|
||||
$perPage = in_array($perPage, [5, 7]) ? $perPage : 7;
|
||||
|
||||
$search = trim((string) $request->input('search', ''));
|
||||
|
||||
$query = Siswa::with(['user', 'jurusan'])->orderBy('id', 'desc');
|
||||
|
||||
if ($search !== '') {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('id_siswa', 'like', "%{$search}%")
|
||||
->orWhere('nama_lengkap', 'like', "%{$search}%")
|
||||
->orWhere('nisn', 'like', "%{$search}%")
|
||||
->orWhere('kelas', 'like', "%{$search}%")
|
||||
->orWhere('peminatan', 'like', "%{$search}%")
|
||||
->orWhereHas('user', function ($user) use ($search) {
|
||||
$user->where('name', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
})
|
||||
->orWhereHas('jurusan', function ($jurusan) use ($search) {
|
||||
$jurusan->where('nama_jurusan', 'like', "%{$search}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$siswa = $query->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $siswa->items(),
|
||||
'current_page' => $siswa->currentPage(),
|
||||
'last_page' => $siswa->lastPage(),
|
||||
'per_page' => $siswa->perPage(),
|
||||
'total' => $siswa->total(),
|
||||
'from' => $siswa->firstItem(),
|
||||
'to' => $siswa->lastItem(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
|
|
@ -25,7 +59,6 @@ public function store(Request $request)
|
|||
'kelas' => 'required|string',
|
||||
'peminatan' => 'required|string',
|
||||
], [
|
||||
// Pesan Error Spesifik
|
||||
'user_id.unique' => 'Gagal! Akun User ID ini sudah terikat dengan data siswa lain.',
|
||||
'nisn.unique' => 'Gagal! NISN ' . $request->nisn . ' sudah terdaftar di sistem.',
|
||||
'nama_lengkap.required' => 'Kolom nama lengkap tidak boleh dikosongkan.',
|
||||
|
|
@ -35,16 +68,17 @@ public function store(Request $request)
|
|||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Validasi gagal',
|
||||
'success' => false,
|
||||
'message' => 'Validasi gagal',
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$siswa = Siswa::create($request->all());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Siswa ' . $siswa->id_siswa . ' (' . $siswa->nama_lengkap . ') berhasil ditambahkan.',
|
||||
'success' => true,
|
||||
'message' => 'Siswa ' . $siswa->id_siswa . ' (' . $siswa->nama_lengkap . ') berhasil ditambahkan.',
|
||||
'data' => $siswa
|
||||
], 201);
|
||||
}
|
||||
|
|
@ -52,14 +86,21 @@ public function store(Request $request)
|
|||
public function show($id)
|
||||
{
|
||||
$siswa = Siswa::find($id);
|
||||
if (!$siswa) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$siswa) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'data' => $siswa], 200);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$siswa = Siswa::find($id);
|
||||
if (!$siswa) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$siswa) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'nama_lengkap' => 'required|string|max:255',
|
||||
|
|
@ -74,14 +115,23 @@ public function update(Request $request, $id)
|
|||
}
|
||||
|
||||
$siswa->update($request->all());
|
||||
return response()->json(['success' => true, 'message' => 'Data ' . $siswa->id_siswa . ' berhasil diperbarui'], 200);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Data ' . $siswa->id_siswa . ' berhasil diperbarui'
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$siswa = Siswa::find($id);
|
||||
if (!$siswa) return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
|
||||
if (!$siswa) {
|
||||
return response()->json(['success' => false, 'message' => 'Data tidak ditemukan'], 404);
|
||||
}
|
||||
|
||||
$siswa->delete();
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Data berhasil dihapus'], 200);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RecommendationApiController extends Controller
|
||||
{
|
||||
|
|
@ -48,10 +49,8 @@ public function hitungPrediksi(Request $request)
|
|||
'gaji_ortu' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
$ekonomiOrangTua = self::getLabelEkonomiSingkat($validated['gaji_ortu']);
|
||||
|
||||
$payload = $validated;
|
||||
$payload['ekonomi_orang_tua'] = $ekonomiOrangTua;
|
||||
$payload['ekonomi_orang_tua'] = self::getLabelEkonomiSingkat($validated['gaji_ortu']);
|
||||
|
||||
try {
|
||||
$response = Http::timeout(15)
|
||||
|
|
@ -83,8 +82,20 @@ public function hitungPrediksi(Request $request)
|
|||
], 500);
|
||||
}
|
||||
|
||||
$riwayat = RiwayatRekomendasi::create([
|
||||
/*
|
||||
* Ambil nilai probabilitas dari Flask.
|
||||
* Contoh respons Flask yang disarankan:
|
||||
* {
|
||||
* "success": true,
|
||||
* "prediksi_jurusan": "Teknik Informatika",
|
||||
* "probabilitas": 86.45
|
||||
* }
|
||||
*/
|
||||
$akurasiPrediksi = self::normalisasiAkurasi($responseData['probabilitas'] ?? null);
|
||||
|
||||
$dataRiwayat = [
|
||||
'user_id' => $user->id,
|
||||
|
||||
'agama' => $validated['agama'],
|
||||
'pkn' => $validated['pkn'],
|
||||
'bahasa_indonesia' => $validated['bahasa_indonesia'],
|
||||
|
|
@ -100,23 +111,38 @@ public function hitungPrediksi(Request $request)
|
|||
'seni_budaya' => $validated['seni_budaya'],
|
||||
'pjok' => $validated['pjok'],
|
||||
'informatika' => $validated['informatika'],
|
||||
|
||||
'riasec_r' => $validated['riasec_r'],
|
||||
'riasec_i' => $validated['riasec_i'],
|
||||
'riasec_a' => $validated['riasec_a'],
|
||||
'riasec_s' => $validated['riasec_s'],
|
||||
'riasec_e' => $validated['riasec_e'],
|
||||
'riasec_c' => $validated['riasec_c'],
|
||||
|
||||
'gaji_ortu' => $validated['gaji_ortu'],
|
||||
'hasil_rekomendasi_jurusan' => $teksJurusan,
|
||||
]);
|
||||
];
|
||||
|
||||
/*
|
||||
* Kolom akurasi_prediksi hanya diisi kalau migration baru sudah dijalankan.
|
||||
* Ini mencegah error kalau kolom belum ada.
|
||||
*/
|
||||
$tableName = (new RiwayatRekomendasi())->getTable();
|
||||
|
||||
if (Schema::hasColumn($tableName, 'akurasi_prediksi')) {
|
||||
$dataRiwayat['akurasi_prediksi'] = $akurasiPrediksi;
|
||||
}
|
||||
|
||||
$riwayat = RiwayatRekomendasi::create($dataRiwayat);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Prediksi berhasil dibuat.',
|
||||
'redirect_url' => route('student.result', ['id' => $riwayat->id]),
|
||||
'prediksi_jurusan' => $teksJurusan,
|
||||
'top_predictions' => $responseData['top_predictions'] ?? null
|
||||
'akurasi_prediksi' => $akurasiPrediksi
|
||||
]);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
|
|
@ -140,6 +166,7 @@ public static function formatHasilData(RiwayatRekomendasi $riwayat): array
|
|||
return [
|
||||
'id' => $riwayat->id,
|
||||
'hasil_rekomendasi_jurusan' => $riwayat->hasil_rekomendasi_jurusan,
|
||||
'akurasi_prediksi' => $riwayat->akurasi_prediksi,
|
||||
'label_ekonomi' => self::getLabelEkonomiDetail($riwayat->gaji_ortu),
|
||||
'minat_utama' => self::getMinatUtamaLabel($riwayat),
|
||||
'prospek_kerja' => self::getProspekKerjaText($riwayat->hasil_rekomendasi_jurusan),
|
||||
|
|
@ -155,6 +182,43 @@ public static function formatHasilData(RiwayatRekomendasi $riwayat): array
|
|||
];
|
||||
}
|
||||
|
||||
private static function normalisasiAkurasi($value): ?float
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$value = str_replace('%', '', $value);
|
||||
$value = str_replace(',', '.', $value);
|
||||
$value = trim($value);
|
||||
}
|
||||
|
||||
if (!is_numeric($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$number = (float) $value;
|
||||
|
||||
/*
|
||||
* Jika Flask mengirim 0.86, ubah menjadi 86.
|
||||
* Jika Flask mengirim 86.45, tetap 86.45.
|
||||
*/
|
||||
if ($number <= 1) {
|
||||
$number = $number * 100;
|
||||
}
|
||||
|
||||
if ($number < 0) {
|
||||
$number = 0;
|
||||
}
|
||||
|
||||
if ($number > 100) {
|
||||
$number = 100;
|
||||
}
|
||||
|
||||
return round($number, 2);
|
||||
}
|
||||
|
||||
public static function getLabelEkonomiSingkat($gaji): string
|
||||
{
|
||||
$gaji = (float) $gaji;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,68 @@ class RiwayatRekomendasi extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
protected $table = 'riwayat_rekomendasis';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
|
||||
'agama',
|
||||
'pkn',
|
||||
'bahasa_indonesia',
|
||||
'bahasa_inggris',
|
||||
'matematika',
|
||||
'fisika',
|
||||
'kimia',
|
||||
'biologi',
|
||||
'sejarah',
|
||||
'geografi',
|
||||
'ekonomi_mapel',
|
||||
'sosiologi',
|
||||
'seni_budaya',
|
||||
'pjok',
|
||||
'informatika',
|
||||
|
||||
'riasec_r',
|
||||
'riasec_i',
|
||||
'riasec_a',
|
||||
'riasec_s',
|
||||
'riasec_e',
|
||||
'riasec_c',
|
||||
|
||||
'gaji_ortu',
|
||||
'hasil_rekomendasi_jurusan',
|
||||
'akurasi_prediksi',
|
||||
'detail_probabilitas',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'agama' => 'float',
|
||||
'pkn' => 'float',
|
||||
'bahasa_indonesia' => 'float',
|
||||
'bahasa_inggris' => 'float',
|
||||
'matematika' => 'float',
|
||||
'fisika' => 'float',
|
||||
'kimia' => 'float',
|
||||
'biologi' => 'float',
|
||||
'sejarah' => 'float',
|
||||
'geografi' => 'float',
|
||||
'ekonomi_mapel' => 'float',
|
||||
'sosiologi' => 'float',
|
||||
'seni_budaya' => 'float',
|
||||
'pjok' => 'float',
|
||||
'informatika' => 'float',
|
||||
|
||||
'riasec_r' => 'float',
|
||||
'riasec_i' => 'float',
|
||||
'riasec_a' => 'float',
|
||||
'riasec_s' => 'float',
|
||||
'riasec_e' => 'float',
|
||||
'riasec_c' => 'float',
|
||||
|
||||
'gaji_ortu' => 'float',
|
||||
'akurasi_prediksi' => 'float',
|
||||
'detail_probabilitas' => 'array',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
# %% [1] IMPORT LIBRARIES & SETUP
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
print("--- [STEP 1] Memuat Data 15 Mapel & Kuesioner RIASEC ---")
|
||||
# Set seed agar hasil random selalu sama setiap kali dijalankan
|
||||
np.random.seed(42)
|
||||
|
||||
# %% [2] FUNGSI GENERATOR DATA REALISTIS (30 JURUSAN & RIASEC)
|
||||
def generate_realistic_data(n_samples_per_class=200):
|
||||
rows = []
|
||||
|
||||
# Konfigurasi 30 Jurusan dengan fokus Mapel dan profil RIASEC dominan
|
||||
jurusan_config = {
|
||||
'Kedokteran': {'mapel': ['biologi', 'kimia', 'matematika'], 'riasec': ['I', 'S'], 'eko_min': ['Mampu', 'Menengah']},
|
||||
'Farmasi': {'mapel': ['kimia', 'biologi', 'matematika'], 'riasec': ['I', 'C'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Kesehatan Masyarakat': {'mapel': ['sosiologi', 'biologi'], 'riasec': ['S', 'E'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Ilmu Gizi': {'mapel': ['biologi', 'kimia'], 'riasec': ['I', 'S'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Keperawatan': {'mapel': ['biologi', 'sosiologi'], 'riasec': ['S', 'R'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
|
||||
'Teknik Sipil': {'mapel': ['matematika', 'fisika'], 'riasec': ['R', 'C'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Teknik Mesin': {'mapel': ['fisika', 'matematika'], 'riasec': ['R', 'I'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Teknik Industri': {'mapel': ['fisika', 'ekonomi_mapel'], 'riasec': ['E', 'I'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Teknik Pertambangan': {'mapel': ['fisika', 'geografi', 'matematika'], 'riasec': ['R', 'E'], 'eko_min': ['Mampu', 'Menengah']},
|
||||
'Teknik Geologi': {'mapel': ['geografi', 'fisika', 'kimia'], 'riasec': ['I', 'R'], 'eko_min': ['Mampu', 'Menengah']},
|
||||
|
||||
'Teknik Informatika': {'mapel': ['informatika', 'matematika'], 'riasec': ['I', 'C'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Sistem Informasi': {'mapel': ['informatika', 'ekonomi_mapel'], 'riasec': ['C', 'I'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
|
||||
'Pertanian': {'mapel': ['biologi', 'geografi'], 'riasec': ['R', 'I'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Peternakan': {'mapel': ['biologi', 'ekonomi_mapel'], 'riasec': ['R', 'E'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Agribisnis': {'mapel': ['ekonomi_mapel', 'biologi'], 'riasec': ['E', 'R'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
|
||||
'Akuntansi': {'mapel': ['ekonomi_mapel', 'matematika'], 'riasec': ['C', 'E'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Manajemen': {'mapel': ['ekonomi_mapel', 'sosiologi'], 'riasec': ['E', 'C'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Ilmu Ekonomi': {'mapel': ['ekonomi_mapel', 'matematika', 'sejarah'], 'riasec': ['I', 'C'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
|
||||
'Ilmu Hukum': {'mapel': ['pkn', 'sosiologi', 'bahasa_indonesia'], 'riasec': ['E', 'S'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Hubungan Internasional': {'mapel': ['bahasa_inggris', 'sejarah', 'pkn'], 'riasec': ['E', 'S'], 'eko_min': ['Mampu', 'Menengah']},
|
||||
'Ilmu Komunikasi': {'mapel': ['bahasa_indonesia', 'sosiologi', 'bahasa_inggris'], 'riasec': ['E', 'A'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Psikologi': {'mapel': ['sosiologi', 'bahasa_indonesia', 'biologi'], 'riasec': ['S', 'I'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Administrasi Negara': {'mapel': ['pkn', 'sosiologi'], 'riasec': ['C', 'E'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
|
||||
'Desain Komunikasi Visual': {'mapel': ['seni_budaya', 'informatika'], 'riasec': ['A', 'E'], 'eko_min': ['Mampu', 'Menengah']},
|
||||
'Arsitektur': {'mapel': ['seni_budaya', 'fisika', 'matematika'], 'riasec': ['A', 'I'], 'eko_min': ['Mampu', 'Menengah']},
|
||||
'Sastra Inggris': {'mapel': ['bahasa_inggris', 'sejarah', 'seni_budaya'], 'riasec': ['A', 'S'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
|
||||
'Pendidikan Guru SD': {'mapel': ['pkn', 'bahasa_indonesia', 'seni_budaya'], 'riasec': ['S', 'C'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Pendidikan Matematika': {'mapel': ['matematika', 'pkn'], 'riasec': ['S', 'I'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
'Pendidikan Jasmani': {'mapel': ['pjok', 'biologi'], 'riasec': ['S', 'R'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']},
|
||||
|
||||
'Pariwisata': {'mapel': ['bahasa_inggris', 'geografi', 'seni_budaya'], 'riasec': ['E', 'S'], 'eko_min': ['Mampu', 'Menengah', 'Kurang Mampu']}
|
||||
}
|
||||
|
||||
siswa_id = 1
|
||||
|
||||
for jurusan, config in jurusan_config.items():
|
||||
for _ in range(n_samples_per_class):
|
||||
# 1. Base Scores Mapel (Acak Normal 75-84)
|
||||
scores = {k: np.random.randint(75, 85) for k in [
|
||||
'agama', 'pkn', 'bahasa_indonesia', 'bahasa_inggris', 'matematika',
|
||||
'fisika', 'kimia', 'biologi', 'sejarah', 'geografi',
|
||||
'ekonomi_mapel', 'sosiologi', 'seni_budaya', 'pjok', 'informatika'
|
||||
]}
|
||||
# Naikkan skor mapel utama jurusan tersebut
|
||||
for m in config['mapel']:
|
||||
scores[m] = np.random.randint(88, 98)
|
||||
|
||||
# 2. Base Scores Kuesioner RIASEC (Acak Normal 50-70)
|
||||
riasec = {k: np.random.randint(50, 71) for k in ['R', 'I', 'A', 'S', 'E', 'C']}
|
||||
# Naikkan skor RIASEC dominan jurusan tersebut
|
||||
for r_key in config['riasec']:
|
||||
riasec[r_key] = np.random.randint(85, 98)
|
||||
|
||||
# 3. Ekonomi Orang Tua
|
||||
eko = np.random.choice(config['eko_min'])
|
||||
|
||||
# 4. Susun Baris Data
|
||||
row = [f"SW{siswa_id:05d}", f"Siswa_{siswa_id}"] + \
|
||||
list(scores.values()) + \
|
||||
[riasec['R'], riasec['I'], riasec['A'], riasec['S'], riasec['E'], riasec['C']] + \
|
||||
[eko, jurusan]
|
||||
|
||||
rows.append(row)
|
||||
siswa_id += 1
|
||||
|
||||
columns = ['id_siswa', 'nama_lengkap',
|
||||
'agama', 'pkn', 'bahasa_indonesia', 'bahasa_inggris', 'matematika',
|
||||
'fisika', 'kimia', 'biologi', 'sejarah', 'geografi',
|
||||
'ekonomi_mapel', 'sosiologi', 'seni_budaya', 'pjok', 'informatika',
|
||||
'riasec_r', 'riasec_i', 'riasec_a', 'riasec_s', 'riasec_e', 'riasec_c',
|
||||
'ekonomi_orang_tua', 'rekomendasi_jurusan']
|
||||
|
||||
# Acak urutan baris data agar tidak terurut per jurusan saat di training
|
||||
df = pd.DataFrame(rows, columns=columns)
|
||||
df = df.sample(frac=1, random_state=42).reset_index(drop=True)
|
||||
return df
|
||||
|
||||
# %% [3] EKSEKUSI DAN SIMPAN DATA
|
||||
# Mengambil 200 sampel per jurusan x 30 jurusan = 6.000 data latih
|
||||
df_synthetic = generate_realistic_data(n_samples_per_class=200)
|
||||
df_synthetic.to_csv('dataset_15mapel_bersih.csv', index=False)
|
||||
print(f"Dataset berhasil dibuat dengan total {len(df_synthetic)} baris!")
|
||||
print("Fitur baru: Skor RIASEC ditambahkan untuk 30 Jurusan.")
|
||||
# %%
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,80 +0,0 @@
|
|||
# %% [1] IMPORT LIBRARIES & LOAD DATA
|
||||
import pandas as pd
|
||||
import seaborn as sns
|
||||
import matplotlib.pyplot as plt
|
||||
from sklearn.preprocessing import LabelEncoder
|
||||
import joblib
|
||||
|
||||
# Mengatur tema visualisasi agar terlihat profesional untuk laporan TA
|
||||
plt.style.use('seaborn-v0_8-whitegrid')
|
||||
|
||||
print("--- [STEP 2] EDA, Visualisasi, & Preprocessing (Integrasi RIASEC) ---")
|
||||
df = pd.read_csv('dataset_15mapel_bersih.csv')
|
||||
|
||||
# %% [2] VISUALISASI 1: DISTRIBUSI KELAS (SEBELUM ENCODING)
|
||||
print("\nMenampilkan Grafik Distribusi Jurusan...")
|
||||
plt.figure(figsize=(12, 10))
|
||||
# Membuat grafik batang horizontal agar 30 nama jurusan terbaca jelas
|
||||
sns.countplot(y='rekomendasi_jurusan', data=df,
|
||||
order=df['rekomendasi_jurusan'].value_counts().index,
|
||||
palette='mako')
|
||||
plt.title('Distribusi Jumlah Siswa per Jurusan (Memastikan Data Seimbang)', fontsize=16, fontweight='bold')
|
||||
plt.xlabel('Jumlah Siswa', fontsize=12)
|
||||
plt.ylabel('Jurusan', fontsize=12)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
# %% [3] PREPROCESSING TAHAP 1: HAPUS KOLOM TEKS (IDENTITAS)
|
||||
print("\nMelakukan Preprocessing Dasar...")
|
||||
|
||||
# Kolom kategori teks yang akan kita pertahankan untuk di-encode
|
||||
kolom_kategori_penting = ['ekonomi_orang_tua', 'rekomendasi_jurusan']
|
||||
|
||||
# Hapus kolom id_siswa dan nama_lengkap karena mesin tidak belajar dari nama/ID
|
||||
for col in df.select_dtypes(include=['object']).columns:
|
||||
if col not in kolom_kategori_penting:
|
||||
df = df.drop(columns=[col])
|
||||
print(f"✅ Kolom '{col}' berhasil dibuang.")
|
||||
|
||||
df = df.dropna()
|
||||
|
||||
# %% [4] PREPROCESSING TAHAP 2: LABEL ENCODING
|
||||
print("\nMelakukan Encoding (Mengubah Teks menjadi Angka)...")
|
||||
encoders = {}
|
||||
|
||||
for col in kolom_kategori_penting:
|
||||
if col in df.columns:
|
||||
le = LabelEncoder()
|
||||
df[col] = le.fit_transform(df[col])
|
||||
encoders[col] = le
|
||||
print(f"✅ Kamus '{col}' tersimpan. Jumlah kelas: {len(le.classes_)}")
|
||||
|
||||
# Simpan encoder untuk digunakan di web/aplikasi (sangat penting untuk decode di PHP Laravel)
|
||||
joblib.dump(encoders, 'encoders_15mapel.pkl')
|
||||
|
||||
# %% [5] VISUALISASI 2: HEATMAP KORELASI (SETELAH ENCODING)
|
||||
print("\nMenampilkan Heatmap Korelasi Antar Fitur...")
|
||||
plt.figure(figsize=(22, 18)) # Ukuran diperbesar karena fiturnya banyak (21 fitur)
|
||||
|
||||
# Membuat korelasi pearson
|
||||
corr_matrix = df.corr()
|
||||
|
||||
# Menampilkan Heatmap
|
||||
sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='coolwarm',
|
||||
vmin=-1, vmax=1, square=True, linewidths=.5, annot_kws={"size": 8})
|
||||
plt.title('Heatmap Korelasi: 15 Mapel, 6 Skor RIASEC, Ekonomi, dan Target Jurusan', fontsize=18, fontweight='bold')
|
||||
plt.xticks(rotation=45, ha='right', fontsize=10)
|
||||
plt.yticks(fontsize=10)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
# %% [6] MENYIMPAN DAFTAR FITUR & DATASET BERSIH
|
||||
print("\nMenyimpan urutan kolom (features) agar saat testing di Laravel tidak tertukar posisinya...")
|
||||
feature_columns = [col for col in df.columns if col != 'rekomendasi_jurusan']
|
||||
joblib.dump(feature_columns, 'feature_columns_15mapel.pkl')
|
||||
print(f"✅ Urutan fitur tersimpan: {feature_columns}")
|
||||
|
||||
# Simpan data yang sudah berupa angka semua (termasuk 6 kolom riasec)
|
||||
df.to_csv('dataset_siswa_siap_model.csv', index=False)
|
||||
print("\nData siap latih berhasil disimpan sebagai 'dataset_siswa_siap_model.csv'!")
|
||||
# %%
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
# %% [1] IMPORT LIBRARIES & LOAD DATA
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.ensemble import RandomForestClassifier
|
||||
from sklearn.metrics import classification_report, accuracy_score
|
||||
import joblib
|
||||
|
||||
print("--- [STEP 3] Pelatihan Model Random Forest 30 Jurusan ---")
|
||||
print("Memuat dataset yang sudah diproses...")
|
||||
df = pd.read_csv('dataset_siswa_siap_model.csv')
|
||||
|
||||
# %% [2] MEMISAHKAN FITUR DAN TARGET (DATA SPLIT)
|
||||
print("Membagi fitur dan target...")
|
||||
X = df.drop('rekomendasi_jurusan', axis=1)
|
||||
y = df['rekomendasi_jurusan']
|
||||
|
||||
print("Membagi data menjadi data latih (80%) dan data uji (20%)...")
|
||||
# Karena ada 30 kelas, stratify sangat wajib agar distribusi per kelas merata
|
||||
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
|
||||
print(f"Jumlah Data Latih: {X_train.shape[0]} | Jumlah Data Uji: {X_test.shape[0]}")
|
||||
|
||||
# %% [3] TRAINING MODEL RANDOM FOREST (HYPERPARAMETER ANTI-OVERFIT)
|
||||
print("\nMelatih model Random Forest...")
|
||||
# Dengan data dimensi RIASEC, kita bisa membebaskan pohon sedikit lebih dalam
|
||||
rf_model = RandomForestClassifier(
|
||||
n_estimators=200, # 200 pohon sudah sangat cukup untuk 30 jurusan
|
||||
max_depth=12, # Cegah menghafal terlalu dalam (Anti Overfitting)
|
||||
min_samples_split=4, # Split akan terjadi jika minimal ada 4 sampel
|
||||
min_samples_leaf=2, # Daun terakhir minimal berisi 2 sampel
|
||||
max_features='sqrt',
|
||||
class_weight='balanced',
|
||||
random_state=42,
|
||||
n_jobs=-1
|
||||
)
|
||||
|
||||
rf_model.fit(X_train, y_train)
|
||||
print("Proses pelatihan selesai!")
|
||||
|
||||
# %% [4] EVALUASI MODEL
|
||||
print("\nMengevaluasi performa model pada data uji...")
|
||||
y_pred = rf_model.predict(X_test)
|
||||
|
||||
akurasi = accuracy_score(y_test, y_pred)
|
||||
print(f"\n======================================")
|
||||
print(f"AKURASI MODEL 30 JURUSAN: {akurasi * 100:.2f}%")
|
||||
print(f"======================================")
|
||||
|
||||
print("\nLaporan Detail Prediksi Keseluruhan:")
|
||||
print(classification_report(y_test, y_pred))
|
||||
|
||||
# Cek Feature Importances (Membuktikan bahwa RIASEC akan memiliki bobot besar)
|
||||
importances = rf_model.feature_importances_
|
||||
feature_names = X.columns
|
||||
feature_imp_df = pd.DataFrame({'Fitur': feature_names, 'Bobot': importances}).sort_values('Bobot', ascending=False)
|
||||
|
||||
plt.figure(figsize=(10, 8))
|
||||
sns.barplot(x='Bobot', y='Fitur', data=feature_imp_df, palette='magma')
|
||||
plt.title('Tingkat Kepentingan Fitur (Feature Importances)', fontsize=14)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
# %% [5] SIMPAN MODEL
|
||||
print("\nMenyimpan struktur model yang sudah berhasil dilatih...")
|
||||
joblib.dump(rf_model, 'model_rekomendasi_15mapel.pkl')
|
||||
print("Model berhasil diamankan ke dalam format 'model_rekomendasi_15mapel.pkl'!")
|
||||
# %%
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?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
|
||||
{
|
||||
if (!Schema::hasColumn('riwayat_rekomendasis', 'akurasi_prediksi')) {
|
||||
Schema::table('riwayat_rekomendasis', function (Blueprint $table) {
|
||||
$table->decimal('akurasi_prediksi', 5, 2)
|
||||
->nullable()
|
||||
->after('hasil_rekomendasi_jurusan');
|
||||
});
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('riwayat_rekomendasis', 'detail_probabilitas')) {
|
||||
Schema::table('riwayat_rekomendasis', function (Blueprint $table) {
|
||||
$table->json('detail_probabilitas')
|
||||
->nullable()
|
||||
->after('akurasi_prediksi');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('riwayat_rekomendasis', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('riwayat_rekomendasis', 'detail_probabilitas')) {
|
||||
$table->dropColumn('detail_probabilitas');
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('riwayat_rekomendasis', 'akurasi_prediksi')) {
|
||||
$table->dropColumn('akurasi_prediksi');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
flask
|
||||
flask-cors
|
||||
pandas
|
||||
numpy
|
||||
scikit-learn==1.6.1
|
||||
joblib
|
||||
openpyxl
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
|
|
@ -3,12 +3,19 @@
|
|||
================================ */
|
||||
|
||||
:root {
|
||||
--primary-green: #2D7A47; /* Hijau SMAN 4 */
|
||||
--primary-green: #2D7A47;
|
||||
--dark-green: #1B4D2E;
|
||||
--accent-yellow: #FFD700; /* Kuning Emas Logo */
|
||||
--accent-yellow: #FFD700;
|
||||
--bg-light: #f8fafc;
|
||||
--white: #ffffff;
|
||||
--sidebar-width: 260px;
|
||||
--text-dark: #0f172a;
|
||||
--text-muted: #64748b;
|
||||
--border-soft: #e2e8f0;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
|
|
@ -16,6 +23,7 @@ body {
|
|||
font-family: 'Figtree', sans-serif;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
/* Layout Container */
|
||||
|
|
@ -24,7 +32,7 @@ .admin-wrapper {
|
|||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* --- PERBAIKAN SIDEBAR --- */
|
||||
/* Sidebar */
|
||||
.sidebar-modern {
|
||||
width: var(--sidebar-width);
|
||||
background-color: var(--primary-green);
|
||||
|
|
@ -32,39 +40,46 @@ .sidebar-modern {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
height: 100vh; /* Mengunci tinggi sidebar sesuai tinggi layar */
|
||||
box-shadow: 4px 0 10px rgba(0,0,0,0.1);
|
||||
height: 100vh;
|
||||
box-shadow: 4px 0 18px rgba(15, 23, 42, 0.12);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 30px 20px;
|
||||
padding: 26px 20px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
flex-shrink: 0; /* Header tidak akan mengecil saat di-scroll */
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-header img {
|
||||
width: 60px;
|
||||
width: 64px;
|
||||
height: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Area Menu dengan Fitur Scroll */
|
||||
.sidebar-menu {
|
||||
padding: 20px 15px;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto; /* Mengaktifkan scroll vertikal */
|
||||
scrollbar-width: thin; /* Untuk Firefox */
|
||||
scrollbar-color: rgba(255,255,255,0.2) transparent;
|
||||
.sidebar-header h5,
|
||||
.sidebar-header h4,
|
||||
.sidebar-header p {
|
||||
color: var(--white);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Area Menu Scroll */
|
||||
.sidebar-menu {
|
||||
padding: 18px 14px;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.28) transparent;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar untuk Chrome/Edge/Safari */
|
||||
.sidebar-menu::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.sidebar-menu::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
background-color: rgba(255, 255, 255, 0.28);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
|
@ -75,8 +90,8 @@ .sidebar-menu::-webkit-scrollbar-track {
|
|||
.menu-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
color: rgba(255,255,255,0.5);
|
||||
font-weight: 800;
|
||||
color: rgba(255, 255, 255, 0.58);
|
||||
margin: 20px 0 10px 10px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
|
@ -84,30 +99,37 @@ .menu-label {
|
|||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
color: var(--white);
|
||||
padding: 12px 14px;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
text-decoration: none;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 5px;
|
||||
transition: all 0.3s;
|
||||
font-weight: 500;
|
||||
border-radius: 14px;
|
||||
margin-bottom: 6px;
|
||||
transition: all 0.25s ease;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-item i {
|
||||
width: 25px;
|
||||
font-size: 18px;
|
||||
font-size: 17px;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
color: var(--accent-yellow);
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background-color: var(--white);
|
||||
color: var(--primary-green);
|
||||
font-weight: 700;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.nav-item.active i {
|
||||
color: var(--primary-green);
|
||||
}
|
||||
|
||||
/* Main Content Area */
|
||||
|
|
@ -116,30 +138,36 @@ .main-content {
|
|||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0; /* Mencegah overflow pada elemen flex */
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Topbar */
|
||||
.topbar {
|
||||
height: 70px;
|
||||
height: 72px;
|
||||
background: var(--white);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 30px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 4px 18px rgba(15, 23, 42, 0.05);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
background: #f1f5f9;
|
||||
border-radius: 10px;
|
||||
padding: 8px 15px;
|
||||
border-radius: 14px;
|
||||
padding: 9px 15px;
|
||||
width: 350px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.search-bar i {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.search-bar input {
|
||||
|
|
@ -148,58 +176,98 @@ .search-bar input {
|
|||
margin-left: 10px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.search-bar input::placeholder {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
/* Area Isi Halaman */
|
||||
.content-wrapper,
|
||||
.admin-content {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
/* Stats Cards */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 25px;
|
||||
gap: 24px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--white);
|
||||
padding: 25px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.02);
|
||||
border-radius: 22px;
|
||||
box-shadow: 0 14px 35px rgba(15, 23, 42, 0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid #f1f5f9;
|
||||
border: 1px solid #eef2f7;
|
||||
transition: 0.25s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 18px 42px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.stat-info h3 {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
font-size: 30px;
|
||||
font-weight: 900;
|
||||
color: #1e293b;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.stat-info p {
|
||||
margin: 0;
|
||||
margin: 0 0 6px;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
border-radius: 15px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.icon-siswa { background: #eef2ff; color: #4f46e5; }
|
||||
.icon-rekom { background: #ecfdf5; color: var(--primary-green); }
|
||||
.icon-akurasi { background: #faf5ff; color: #9333ea; }
|
||||
.icon-siswa {
|
||||
background: #eef2ff;
|
||||
color: #4f46e5;
|
||||
}
|
||||
|
||||
.icon-rekom {
|
||||
background: #ecfdf5;
|
||||
color: var(--primary-green);
|
||||
}
|
||||
|
||||
.icon-akurasi {
|
||||
background: #faf5ff;
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
/* Dashboard Section */
|
||||
.dashboard-section {
|
||||
padding: 0 30px 30px 30px;
|
||||
margin: 30px;
|
||||
padding: 28px;
|
||||
background: var(--white);
|
||||
border-radius: 24px;
|
||||
border: 1px solid #eef2f7;
|
||||
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
/* Jika dashboard-section berada setelah stats-grid, jaraknya lebih rapi */
|
||||
.stats-grid + .dashboard-section {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
|
|
@ -208,4 +276,63 @@ .chart-container {
|
|||
padding: 25px;
|
||||
border: 1px solid #f1f5f9;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 992px) {
|
||||
:root {
|
||||
--sidebar-width: 240px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
padding: 24px;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.dashboard-section {
|
||||
margin: 24px;
|
||||
padding: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sidebar-modern {
|
||||
width: 230px;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
height: auto;
|
||||
min-height: 68px;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
padding: 14px 18px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.dashboard-section {
|
||||
margin: 18px;
|
||||
padding: 18px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
/* ================================
|
||||
DASHBOARD ADMIN - MODERN STYLE
|
||||
================================ */
|
||||
|
||||
.dashboard-modern {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.dashboard-hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 30px;
|
||||
padding: 28px;
|
||||
background:
|
||||
radial-gradient(circle at 10% 10%, rgba(255, 215, 0, 0.18), transparent 28%),
|
||||
radial-gradient(circle at 90% 20%, rgba(34, 197, 94, 0.16), transparent 30%),
|
||||
linear-gradient(135deg, #0f3d25 0%, #2D7A47 55%, #3fa263 100%);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 24px 55px rgba(45, 122, 71, 0.22);
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.dashboard-hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 260px;
|
||||
height: 260px;
|
||||
right: -70px;
|
||||
bottom: -95px;
|
||||
background: rgba(255, 255, 255, 0.13);
|
||||
border-radius: 999px;
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.dashboard-hero-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hero-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.4px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
margin: 0;
|
||||
font-size: 30px;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.7px;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
margin: 10px 0 0;
|
||||
max-width: 760px;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
line-height: 1.7;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hero-date-card {
|
||||
min-width: 210px;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
border-radius: 22px;
|
||||
padding: 18px;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.hero-date-card span {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.hero-date-card strong {
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.modern-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 22px;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.modern-stat-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e8eef5;
|
||||
border-radius: 26px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 18px 46px rgba(15, 23, 42, 0.06);
|
||||
transition: 0.25s ease;
|
||||
}
|
||||
|
||||
.modern-stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.09);
|
||||
}
|
||||
|
||||
.modern-stat-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
right: -48px;
|
||||
top: -48px;
|
||||
background: #ecfdf5;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.stat-card-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
margin: 0 0 8px;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
margin: 0;
|
||||
color: #0f172a;
|
||||
font-size: 34px;
|
||||
font-weight: 950;
|
||||
letter-spacing: -0.8px;
|
||||
}
|
||||
|
||||
.stat-note {
|
||||
margin: 8px 0 0;
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modern-stat-icon {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
border-radius: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
flex: 0 0 58px;
|
||||
}
|
||||
|
||||
.icon-blue {
|
||||
background: #eef2ff;
|
||||
color: #4f46e5;
|
||||
}
|
||||
|
||||
.icon-green {
|
||||
background: #dcfce7;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.accuracy-ring {
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: conic-gradient(#2D7A47 0deg, #e2e8f0 0deg);
|
||||
padding: 5px;
|
||||
flex: 0 0 62px;
|
||||
}
|
||||
|
||||
.accuracy-ring-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #2D7A47;
|
||||
font-weight: 900;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e8eef5;
|
||||
border-radius: 28px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 18px 46px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.dashboard-chart-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-header-modern {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-title-modern {
|
||||
margin: 0;
|
||||
color: #0f172a;
|
||||
font-weight: 900;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
.card-subtitle-modern {
|
||||
margin: 6px 0 0;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.chart-badge {
|
||||
background: #ecfdf5;
|
||||
color: #15803d;
|
||||
border: 1px solid #bbf7d0;
|
||||
border-radius: 999px;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chart-wrapper-modern {
|
||||
height: 390px;
|
||||
border-radius: 22px;
|
||||
border: 1px solid #edf2f7;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||
padding: 18px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dashboard-placeholder {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dashboard-placeholder i {
|
||||
font-size: 38px;
|
||||
color: #cbd5e1;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.dashboard-placeholder p {
|
||||
color: #64748b;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.modern-stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-modern {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.dashboard-hero {
|
||||
border-radius: 24px;
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.modern-stat-card,
|
||||
.dashboard-card {
|
||||
border-radius: 22px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chart-wrapper-modern {
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
.card-header-modern {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hero-date-card {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,155 +1,612 @@
|
|||
/* ================================
|
||||
FITUR ADMIN - TABLE, FORM, SEARCH, PAGINATION
|
||||
================================ */
|
||||
|
||||
:root {
|
||||
--primary-green: #2D7A47;
|
||||
--primary-green-dark: #1B4D2E;
|
||||
--soft-green: #ecfdf5;
|
||||
--accent-yellow: #FFD700;
|
||||
--danger-red: #ef4444;
|
||||
--danger-red-dark: #dc2626;
|
||||
--warning-yellow: #f59e0b;
|
||||
--text-dark: #0f172a;
|
||||
--text-muted: #64748b;
|
||||
--border-soft: #e2e8f0;
|
||||
--border-table: #d5dee8;
|
||||
--bg-soft: #f8fafc;
|
||||
--white: #ffffff;
|
||||
}
|
||||
|
||||
/* Header halaman master */
|
||||
.master-page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.master-page-title {
|
||||
margin: 0;
|
||||
font-weight: 900;
|
||||
color: var(--text-dark);
|
||||
letter-spacing: -0.4px;
|
||||
}
|
||||
|
||||
.master-page-subtitle {
|
||||
margin: 6px 0 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Tombol tambah data */
|
||||
.btn-add-modern {
|
||||
border: none;
|
||||
background: linear-gradient(135deg, var(--primary-green), var(--primary-green-dark));
|
||||
color: #ffffff !important;
|
||||
padding: 12px 18px;
|
||||
border-radius: 14px;
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 12px 24px rgba(45, 122, 71, 0.22);
|
||||
transition: 0.25s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-add-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 16px 30px rgba(45, 122, 71, 0.28);
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Container tabel */
|
||||
.table-container-modern {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 22px;
|
||||
overflow: hidden;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 16px 36px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
/* Toolbar search dan jumlah data */
|
||||
.table-toolbar {
|
||||
padding: 20px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-wrapper-modern {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
.search-wrapper-modern i {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-input-modern {
|
||||
width: 100%;
|
||||
height: 46px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 14px;
|
||||
padding: 0 16px 0 42px;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
color: var(--text-dark);
|
||||
background: #ffffff;
|
||||
transition: 0.25s ease;
|
||||
}
|
||||
|
||||
.search-input-modern::placeholder {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.search-input-modern:focus {
|
||||
border-color: var(--primary-green);
|
||||
box-shadow: 0 0 0 4px rgba(45, 122, 71, 0.12);
|
||||
}
|
||||
|
||||
.per-page-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.per-page-select {
|
||||
height: 46px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 14px;
|
||||
padding: 0 12px;
|
||||
outline: none;
|
||||
color: var(--text-dark);
|
||||
font-weight: 800;
|
||||
background: #ffffff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.per-page-select:focus {
|
||||
border-color: var(--primary-green);
|
||||
box-shadow: 0 0 0 4px rgba(45, 122, 71, 0.12);
|
||||
}
|
||||
|
||||
/* Tabel modern */
|
||||
.table-responsive {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table-modern {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
margin-bottom: 0 !important;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
/* Header tabel */
|
||||
.table-modern thead th {
|
||||
background-color: #f8fafc;
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
color: #334155;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 700;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
border-right: 1px solid #e2e8f0; /* Garis antar kolom */
|
||||
letter-spacing: 0.7px;
|
||||
font-weight: 900;
|
||||
padding: 16px 18px;
|
||||
border-bottom: 1px solid var(--border-table) !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-modern thead th:last-child {
|
||||
border-right: none;
|
||||
/* Garis pemisah antar kolom bagian header */
|
||||
.table-modern thead th:not(:last-child) {
|
||||
border-right: 1px solid var(--border-table) !important;
|
||||
}
|
||||
|
||||
/* Isi tabel */
|
||||
.table-modern tbody td {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
padding: 17px 18px;
|
||||
border-bottom: 1px solid #e8eef5 !important;
|
||||
vertical-align: middle;
|
||||
border-right: 1px solid #f1f5f9; /* Garis antar kolom */
|
||||
color: var(--text-dark);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Garis pemisah antar kolom bagian isi tabel */
|
||||
.table-modern tbody td:not(:last-child) {
|
||||
border-right: 1px solid var(--border-table) !important;
|
||||
}
|
||||
|
||||
/* Baris terakhir tidak perlu garis bawah */
|
||||
.table-modern tbody tr:last-child td {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.table-modern tbody tr {
|
||||
transition: 0.22s ease;
|
||||
}
|
||||
|
||||
.table-modern tbody tr:hover {
|
||||
background-color: #f0fdf4;
|
||||
}
|
||||
|
||||
/* Kolom nomor dan opsi lebih rapi */
|
||||
.table-modern th.text-center,
|
||||
.table-modern td.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Kolom opsi dibuat lebih lebar agar teks tombol tidak sempit */
|
||||
.table-modern thead th:last-child,
|
||||
.table-modern tbody td:last-child {
|
||||
border-right: none;
|
||||
min-width: 190px;
|
||||
}
|
||||
|
||||
/* 2. Opsi Tombol Aksi (Edit & Hapus) */
|
||||
/* Cell user / nama */
|
||||
.table-user-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.avatar-circle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ecfdf5;
|
||||
color: var(--primary-green-dark);
|
||||
font-weight: 900;
|
||||
flex: 0 0 40px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.avatar-circle.blue {
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.avatar-circle.purple {
|
||||
background: #faf5ff;
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.avatar-circle.orange {
|
||||
background: #fff7ed;
|
||||
color: #ea580c;
|
||||
}
|
||||
|
||||
.text-cell-muted {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Badge */
|
||||
.badge-soft {
|
||||
border-radius: 999px;
|
||||
padding: 8px 13px;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid transparent;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge-kelas {
|
||||
background: #ecfdf5;
|
||||
color: #15803d;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
.badge-saintek {
|
||||
background-color: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
border: 1px solid #bfdbfe;
|
||||
}
|
||||
|
||||
.badge-soshum {
|
||||
background-color: #fef3c7;
|
||||
color: #b45309;
|
||||
border: 1px solid #fde68a;
|
||||
}
|
||||
|
||||
.badge-bk {
|
||||
background-color: #ecfdf5;
|
||||
color: #15803d;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
.badge-result {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
/* Tetap mendukung class badge Bootstrap / lama */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.45em 0.75em;
|
||||
font-size: 0.75em;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: 50rem;
|
||||
}
|
||||
|
||||
/* Opsi tombol aksi */
|
||||
.opsi-wrapper {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
/* Tombol edit dan hapus dibuat jelas dengan teks */
|
||||
.btn-action {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
min-width: 84px;
|
||||
height: 38px;
|
||||
padding: 0 13px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
gap: 7px;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
color: #ffffff;
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
color: #ffffff !important;
|
||||
transition: 0.22s ease;
|
||||
cursor: pointer;
|
||||
text-decoration: none !important;
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.btn-action i {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-action:hover {
|
||||
transform: scale(1.1);
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
color: #ffffff !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-edit { background-color: #4f46e5; }
|
||||
.btn-delete { background-color: #ef4444; }
|
||||
.btn-edit {
|
||||
background-color: #22c55e;
|
||||
}
|
||||
|
||||
/* 3. Pagination (Menyatu di bawah tabel) */
|
||||
.btn-edit:hover {
|
||||
background-color: #16a34a;
|
||||
box-shadow: 0 10px 18px rgba(34, 197, 94, 0.22);
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background-color: #dc2626;
|
||||
box-shadow: 0 10px 18px rgba(239, 68, 68, 0.22);
|
||||
}
|
||||
|
||||
/* Menambahkan tulisan otomatis pada tombol yang di Blade masih hanya ikon */
|
||||
.btn-edit::after {
|
||||
content: "Edit";
|
||||
}
|
||||
|
||||
.btn-delete::after {
|
||||
content: "Hapus";
|
||||
}
|
||||
|
||||
/* Tombol detail riwayat */
|
||||
.btn-detail {
|
||||
border: none;
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
border-radius: 999px;
|
||||
padding: 9px 14px;
|
||||
font-weight: 900;
|
||||
transition: 0.22s ease;
|
||||
}
|
||||
|
||||
.btn-detail:hover {
|
||||
background: #2563eb;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
.pagination-container {
|
||||
padding: 15px 20px;
|
||||
padding: 18px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
border-top: 1px solid var(--border-soft);
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.page-link-custom {
|
||||
padding: 6px 14px;
|
||||
border: 1px solid #e2e8f0;
|
||||
min-width: 38px;
|
||||
height: 38px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--border-soft);
|
||||
background-color: #ffffff;
|
||||
border-radius: 6px;
|
||||
color: #64748b;
|
||||
border-radius: 12px;
|
||||
color: #475569;
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: 0.22s ease;
|
||||
}
|
||||
|
||||
.page-link-custom:hover:not(:disabled) {
|
||||
background-color: #ecfdf5;
|
||||
color: var(--primary-green);
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
|
||||
.page-link-custom.active {
|
||||
background-color: #007bff;
|
||||
background-color: var(--primary-green);
|
||||
color: #ffffff;
|
||||
border-color: #007bff;
|
||||
border-color: var(--primary-green);
|
||||
}
|
||||
|
||||
.page-link-custom:disabled {
|
||||
background-color: #f8fafc;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* 4. Form Styling (Satu Baris Satu Input) */
|
||||
/* Loading dan data kosong */
|
||||
.empty-state-modern {
|
||||
padding: 36px 20px !important;
|
||||
text-align: center;
|
||||
color: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
.empty-state-modern i {
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.loading-state-modern {
|
||||
padding: 36px 20px !important;
|
||||
text-align: center;
|
||||
color: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
.loading-state-modern i {
|
||||
color: var(--primary-green);
|
||||
}
|
||||
|
||||
/* Form Styling */
|
||||
.form-group-custom {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group-custom label {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
font-weight: 800;
|
||||
color: #475569;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input-custom {
|
||||
border-radius: 10px !important;
|
||||
border-radius: 12px !important;
|
||||
border: 1px solid #e2e8f0 !important;
|
||||
padding: 12px 15px !important;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.input-custom:focus {
|
||||
border-color: var(--primary-green) !important;
|
||||
box-shadow: 0 0 0 3px rgba(45, 122, 71, 0.1) !important;
|
||||
box-shadow: 0 0 0 4px rgba(45, 122, 71, 0.12) !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Badge Rumpun Jurusan */
|
||||
.badge-saintek {
|
||||
background-color: #e0f2fe; /* Warna Biru Muda */
|
||||
color: #0369a1; /* Warna Biru Tua */
|
||||
border: 1px solid #bae6fd;
|
||||
/* Card umum untuk form */
|
||||
.form-card-modern {
|
||||
background: #ffffff;
|
||||
border-radius: 22px;
|
||||
border: 1px solid var(--border-soft);
|
||||
box-shadow: 0 16px 36px rgba(15, 23, 42, 0.06);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.badge-soshum {
|
||||
background-color: #fef3c7; /* Warna Kuning Muda */
|
||||
color: #92400e; /* Warna Cokelat/Oren */
|
||||
border: 1px solid #fde68a;
|
||||
/* Button umum form */
|
||||
.btn-save-modern {
|
||||
border: none;
|
||||
background: linear-gradient(135deg, var(--primary-green), var(--primary-green-dark));
|
||||
color: #ffffff;
|
||||
padding: 11px 18px;
|
||||
border-radius: 12px;
|
||||
font-weight: 900;
|
||||
transition: 0.22s ease;
|
||||
}
|
||||
|
||||
/* Base style untuk badge jika belum ada */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.35em 0.65em;
|
||||
font-size: 0.75em;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: 50rem;
|
||||
.btn-save-modern:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 24px rgba(45, 122, 71, 0.22);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-back-modern {
|
||||
border: 1px solid var(--border-soft);
|
||||
background: #ffffff;
|
||||
color: #475569;
|
||||
padding: 11px 18px;
|
||||
border-radius: 12px;
|
||||
font-weight: 900;
|
||||
text-decoration: none;
|
||||
transition: 0.22s ease;
|
||||
}
|
||||
|
||||
.btn-back-modern:hover {
|
||||
background: #f8fafc;
|
||||
color: var(--primary-green);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 992px) {
|
||||
.btn-action {
|
||||
min-width: 78px;
|
||||
padding: 0 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.table-modern thead th:last-child,
|
||||
.table-modern tbody td:last-child {
|
||||
min-width: 175px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.master-page-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn-add-modern {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-wrapper-modern {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.per-page-wrapper {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.per-page-select {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-modern thead th,
|
||||
.table-modern tbody td {
|
||||
padding: 14px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.avatar-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 12px;
|
||||
flex-basis: 36px;
|
||||
}
|
||||
|
||||
.opsi-wrapper {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
min-width: 78px;
|
||||
height: 34px;
|
||||
font-size: 12px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
/* ================================
|
||||
RIWAYAT ADMIN - KHUSUS HALAMAN RIWAYAT
|
||||
================================ */
|
||||
|
||||
.riwayat-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.riwayat-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #ecfdf5;
|
||||
color: #15803d;
|
||||
border: 1px solid #bbf7d0;
|
||||
border-radius: 999px;
|
||||
padding: 8px 13px;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.riwayat-date-main {
|
||||
color: #0f172a;
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.riwayat-date-sub {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.riwayat-user-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.riwayat-avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 16px;
|
||||
background: #ecfdf5;
|
||||
color: #166534;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 900;
|
||||
flex: 0 0 42px;
|
||||
}
|
||||
|
||||
.riwayat-user-name {
|
||||
color: #0f172a;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.riwayat-user-email {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.riwayat-input-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.confidence-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border-radius: 999px;
|
||||
padding: 8px 12px;
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
border: 1px solid #bfdbfe;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detail-result-card {
|
||||
border-radius: 22px;
|
||||
padding: 18px;
|
||||
background:
|
||||
radial-gradient(circle at 10% 10%, rgba(255, 215, 0, 0.14), transparent 25%),
|
||||
linear-gradient(135deg, #ecfdf5 0%, #ffffff 100%);
|
||||
border: 1px solid #bbf7d0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-result-card small {
|
||||
display: block;
|
||||
color: #15803d;
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.detail-result-card h4 {
|
||||
margin: 0;
|
||||
color: #0f172a;
|
||||
font-size: 22px;
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.detail-info-card {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e8eef5;
|
||||
border-radius: 18px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.detail-info-card span {
|
||||
display: block;
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.4px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.detail-info-card strong {
|
||||
display: block;
|
||||
color: #0f172a;
|
||||
font-size: 15px;
|
||||
font-weight: 900;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
margin: 18px 0 12px;
|
||||
color: #0f172a;
|
||||
font-size: 15px;
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
.mini-data-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mini-data-item {
|
||||
border-radius: 14px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e8eef5;
|
||||
padding: 11px;
|
||||
}
|
||||
|
||||
.mini-data-item span {
|
||||
display: block;
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.mini-data-item strong {
|
||||
color: #0f172a;
|
||||
font-size: 14px;
|
||||
font-weight: 950;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 24px !important;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.mini-data-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.riwayat-header {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.detail-grid,
|
||||
.mini-data-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.riwayat-user-cell {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
|
|
@ -5,22 +5,40 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<div class="dashboard-section">
|
||||
<div class="mb-4 d-flex justify-content-between align-items-center">
|
||||
<div class="master-page-header">
|
||||
<div>
|
||||
<h4 class="fw-bold text-dark">Manajemen Data Alumni</h4>
|
||||
<p class="text-muted small">Kelola data historis lulusan untuk kebutuhan dataset sistem prediksi.</p>
|
||||
<h4 class="master-page-title">Manajemen Data Alumni</h4>
|
||||
<p class="master-page-subtitle">Kelola data historis lulusan untuk kebutuhan dataset sistem prediksi.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.alumni.create') }}" class="btn text-white px-4 shadow-sm" style="background-color: var(--primary-green); border-radius: 10px;">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Data Alumni
|
||||
|
||||
<a href="{{ route('admin.alumni.create') }}" class="btn-add-modern">
|
||||
<i class="fas fa-plus"></i>
|
||||
Tambah Data Alumni
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-container-modern shadow-sm">
|
||||
<div class="table-toolbar">
|
||||
<div class="search-wrapper-modern">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="searchInput" class="search-input-modern" placeholder="Cari nama alumni, perguruan tinggi, atau jurusan...">
|
||||
</div>
|
||||
|
||||
<div class="per-page-wrapper">
|
||||
<span>Tampilkan</span>
|
||||
<select id="perPageSelect" class="per-page-select">
|
||||
<option value="7" selected>7 data</option>
|
||||
<option value="5">5 data</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-modern mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center" width="100">ID Alumni</th>
|
||||
<th class="text-center" width="70">No</th>
|
||||
<th class="text-center" width="120">ID Alumni</th>
|
||||
<th>Nama Alumni</th>
|
||||
<th>Perguruan Tinggi</th>
|
||||
<th class="text-center">Jurusan Diambil</th>
|
||||
|
|
@ -28,47 +46,125 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody id="alumni-table-body">
|
||||
<tr><td colspan="5" class="text-center py-5 text-muted">Memuat data alumni...</td></tr>
|
||||
<tr>
|
||||
<td colspan="6" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data alumni...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pagination-container">
|
||||
<div class="text-muted small" id="pagination-info">
|
||||
Menampilkan 0 sampai 0 dari 0 data
|
||||
</div>
|
||||
<div class="d-flex gap-1" id="pagination-controls">
|
||||
</div>
|
||||
<div class="d-flex gap-1 flex-wrap" id="pagination-controls"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentSearch = '';
|
||||
let perPage = 7;
|
||||
let searchTimer = null;
|
||||
|
||||
function escapeHtml(value) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function getInitial(name) {
|
||||
const cleanName = name || 'A';
|
||||
return cleanName.charAt(0).toUpperCase();
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
document.getElementById('alumni-table-body').innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data alumni...
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
async function fetchAlumni(page = 1) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const tbody = document.getElementById('alumni-table-body');
|
||||
|
||||
if (!token) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
currentPage = page;
|
||||
showLoading();
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: page,
|
||||
per_page: perPage
|
||||
});
|
||||
|
||||
if (currentSearch !== '') {
|
||||
params.append('search', currentSearch);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/alumni?page=${page}`, {
|
||||
headers: {
|
||||
const response = await fetch(`/api/admin/alumni?${params.toString()}`, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
const tbody = document.getElementById('alumni-table-body');
|
||||
|
||||
if (!response.ok || result.success === false) {
|
||||
throw new Error(result.message || 'Gagal memuat data alumni.');
|
||||
}
|
||||
|
||||
const data = result.data || [];
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if(!result.data || result.data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-4">Tidak ada data alumni.</td></tr>';
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="empty-state-modern">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Tidak ada data alumni yang ditemukan.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
updatePaginationInfo(null);
|
||||
renderPagination({ current_page: 1, last_page: 1, total: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
result.data.forEach((item) => {
|
||||
data.forEach((item, index) => {
|
||||
tbody.innerHTML += `
|
||||
<tr>
|
||||
<td class="text-center fw-bold text-muted">${item.id_alumni}</td>
|
||||
<td class="fw-bold text-dark">${item.nama_alumni}</td>
|
||||
<td class="text-muted small">${item.perguruan_tinggi}</td>
|
||||
<td class="text-center"><span class="badge bg-light text-dark border px-3 py-2" style="border-radius:8px">${item.jurusan_diambil}</span></td>
|
||||
<td class="text-center text-muted fw-bold">${(result.from || 1) + index}</td>
|
||||
<td class="text-center fw-bold text-muted">${escapeHtml(item.id_alumni)}</td>
|
||||
<td>
|
||||
<div class="table-user-cell">
|
||||
<div class="avatar-circle blue">${getInitial(item.nama_alumni)}</div>
|
||||
<div>
|
||||
<div class="fw-bold text-dark">${escapeHtml(item.nama_alumni)}</div>
|
||||
<div class="text-cell-muted">Data alumni</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-cell-muted">${escapeHtml(item.perguruan_tinggi)}</td>
|
||||
<td class="text-center">
|
||||
<span class="badge-soft badge-result">${escapeHtml(item.jurusan_diambil)}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="opsi-wrapper">
|
||||
<a href="/admin/alumni/${item.id}/edit" class="btn-action btn-edit" title="Edit">
|
||||
|
|
@ -79,66 +175,134 @@
|
|||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
document.getElementById('pagination-info').innerText =
|
||||
`Menampilkan ${result.from || 0} sampai ${result.to || 0} dari ${result.total || 0} data`;
|
||||
|
||||
updatePaginationInfo(result);
|
||||
renderPagination(result);
|
||||
} catch (error) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-danger">Gagal memuat data.</td></tr>';
|
||||
|
||||
} catch (error) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="empty-state-modern text-danger">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
Gagal memuat data alumni.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePaginationInfo(data) {
|
||||
const info = document.getElementById('pagination-info');
|
||||
|
||||
if (!data || !data.total) {
|
||||
info.innerText = 'Menampilkan 0 sampai 0 dari 0 data';
|
||||
return;
|
||||
}
|
||||
|
||||
info.innerText = `Menampilkan ${data.from || 0} sampai ${data.to || 0} dari ${data.total || 0} data`;
|
||||
}
|
||||
|
||||
function renderPagination(data) {
|
||||
const controls = document.getElementById('pagination-controls');
|
||||
controls.innerHTML = '';
|
||||
|
||||
const prevBtn = document.createElement('button');
|
||||
prevBtn.className = 'page-link-custom';
|
||||
prevBtn.innerHTML = '« Previous';
|
||||
prevBtn.disabled = data.current_page === 1;
|
||||
prevBtn.onclick = () => fetchAlumni(data.current_page - 1);
|
||||
controls.appendChild(prevBtn);
|
||||
const current = Number(data.current_page || 1);
|
||||
const last = Number(data.last_page || 1);
|
||||
const total = Number(data.total || 0);
|
||||
|
||||
const pageBtn = document.createElement('button');
|
||||
pageBtn.className = 'page-link-custom active';
|
||||
pageBtn.innerText = data.current_page;
|
||||
controls.appendChild(pageBtn);
|
||||
if (total === 0) return;
|
||||
|
||||
const nextBtn = document.createElement('button');
|
||||
nextBtn.className = 'page-link-custom';
|
||||
nextBtn.innerHTML = 'Next »';
|
||||
nextBtn.disabled = data.current_page === data.last_page;
|
||||
nextBtn.onclick = () => fetchAlumni(data.current_page + 1);
|
||||
controls.appendChild(nextBtn);
|
||||
const createButton = (label, page, disabled = false, active = false) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `page-link-custom ${active ? 'active' : ''}`;
|
||||
btn.innerHTML = label;
|
||||
btn.disabled = disabled;
|
||||
btn.onclick = () => {
|
||||
if (!disabled && !active) fetchAlumni(page);
|
||||
};
|
||||
return btn;
|
||||
};
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-left"></i>', current - 1, current === 1));
|
||||
|
||||
let pages = [];
|
||||
if (last <= 5) {
|
||||
for (let i = 1; i <= last; i++) pages.push(i);
|
||||
} else {
|
||||
pages.push(1);
|
||||
if (current > 3) pages.push('...');
|
||||
const start = Math.max(2, current - 1);
|
||||
const end = Math.min(last - 1, current + 1);
|
||||
for (let i = start; i <= end; i++) pages.push(i);
|
||||
if (current < last - 2) pages.push('...');
|
||||
pages.push(last);
|
||||
}
|
||||
|
||||
pages.forEach(page => {
|
||||
if (page === '...') {
|
||||
controls.appendChild(createButton('...', current, true));
|
||||
} else {
|
||||
controls.appendChild(createButton(page, page, false, page === current));
|
||||
}
|
||||
});
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-right"></i>', current + 1, current === last));
|
||||
}
|
||||
|
||||
async function deleteAlumni(id) {
|
||||
const res = await Swal.fire({
|
||||
const result = await Swal.fire({
|
||||
title: 'Hapus Data?',
|
||||
text: "Data alumni akan dihapus secara permanen!",
|
||||
text: 'Data alumni akan dihapus secara permanen!',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#ef4444',
|
||||
confirmButtonText: 'Ya, Hapus'
|
||||
cancelButtonColor: '#64748b',
|
||||
confirmButtonText: 'Ya, Hapus',
|
||||
cancelButtonText: 'Batal'
|
||||
});
|
||||
|
||||
if (res.isConfirmed) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
try {
|
||||
const response = await fetch('/api/admin/alumni/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
if(response.ok) {
|
||||
Swal.fire('Terhapus!', 'Data berhasil dihapus.', 'success');
|
||||
fetchAlumni();
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/alumni/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
} catch (error) { Swal.fire('Error!', 'Koneksi gagal.', 'error'); }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
Swal.fire('Terhapus!', 'Data alumni berhasil dihapus.', 'success');
|
||||
fetchAlumni(currentPage);
|
||||
} else {
|
||||
Swal.fire('Gagal!', 'Data alumni gagal dihapus.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
Swal.fire('Error!', 'Koneksi ke server gagal.', 'error');
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => fetchAlumni(1));
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
fetchAlumni(1);
|
||||
|
||||
document.getElementById('searchInput').addEventListener('input', function () {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => {
|
||||
currentSearch = this.value.trim();
|
||||
fetchAlumni(1);
|
||||
}, 350);
|
||||
});
|
||||
|
||||
document.getElementById('perPageSelect').addEventListener('change', function () {
|
||||
perPage = this.value;
|
||||
fetchAlumni(1);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -1,70 +1,284 @@
|
|||
@extends('admin.layout')
|
||||
|
||||
@section('content')
|
||||
<div class="mb-4">
|
||||
<h4 class="fw-bold text-dark">Overview Statistik Aplikasi</h4>
|
||||
<p class="text-muted small">Selamat datang di panel kendali sistem rekomendasi jurusan SMAN 4 Jember.</p>
|
||||
</div>
|
||||
<link rel="stylesheet" href="{{ asset('css/dashboard-admin.css') }}">
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-info">
|
||||
<p>Total Siswa</p>
|
||||
<h3 id="stat-total-siswa">0</h3>
|
||||
</div>
|
||||
<div class="stat-icon icon-siswa">
|
||||
<i class="fas fa-user-graduate"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-modern">
|
||||
<section class="dashboard-hero">
|
||||
<div class="dashboard-hero-content">
|
||||
<div>
|
||||
<div class="hero-label">
|
||||
<i class="fas fa-chart-pie"></i>
|
||||
Dashboard Admin
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-info">
|
||||
<p>Rekomendasi Diproses</p>
|
||||
<h3 id="stat-total-rekomendasi">0</h3>
|
||||
</div>
|
||||
<div class="stat-icon icon-rekom">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="hero-title">Overview Statistik Aplikasi</h2>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-info">
|
||||
<p>Rata-rata Akurasi</p>
|
||||
<h3 id="stat-rata-akurasi">0%</h3>
|
||||
</div>
|
||||
<div class="stat-icon icon-akurasi">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hero-subtitle">
|
||||
Pantau jumlah siswa, rekomendasi yang sudah diproses, rata-rata tingkat keyakinan prediksi,
|
||||
serta distribusi jurusan yang paling banyak muncul pada sistem rekomendasi jurusan kuliah SMAN 4 Jember.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-section mt-4">
|
||||
<div class="chart-container">
|
||||
<h5 class="fw-bold mb-4 d-flex align-items-center gap-2">
|
||||
<div style="width: 4px; height: 20px; background: var(--primary-green); border-radius: 10px;"></div>
|
||||
Distribusi Rekomendasi Jurusan SMAN 4 Jember
|
||||
</h5>
|
||||
|
||||
<div id="chart-wrapper" style="height: 350px; display: flex; align-items: center; justify-content: center; background: #f8fafc; border-radius: 15px; border: 2px dashed #e2e8f0; padding: 20px;">
|
||||
<canvas id="distribusiChart" class="d-none w-100 h-100"></canvas>
|
||||
<div id="chart-placeholder" class="text-center">
|
||||
<i class="fas fa-chart-pie fa-3x text-light mb-3"></i>
|
||||
<p class="text-muted">Memuat grafik distribusi...</p>
|
||||
<div class="hero-date-card">
|
||||
<span>Terakhir diperbarui</span>
|
||||
<strong id="last-updated">Memuat...</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="modern-stats-grid">
|
||||
<div class="modern-stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div>
|
||||
<p class="stat-label">Total Siswa</p>
|
||||
<h3 class="stat-value" id="stat-total-siswa">0</h3>
|
||||
<p class="stat-note">Jumlah akun siswa yang terdaftar.</p>
|
||||
</div>
|
||||
|
||||
<div class="modern-stat-icon icon-blue">
|
||||
<i class="fas fa-user-graduate"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modern-stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div>
|
||||
<p class="stat-label">Rekomendasi Diproses</p>
|
||||
<h3 class="stat-value" id="stat-total-rekomendasi">0</h3>
|
||||
<p class="stat-note">Jumlah riwayat rekomendasi yang tersimpan.</p>
|
||||
</div>
|
||||
|
||||
<div class="modern-stat-icon icon-green">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modern-stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div>
|
||||
<p class="stat-label">Rata-rata Keyakinan Prediksi</p>
|
||||
<h3 class="stat-value" id="stat-rata-akurasi">0%</h3>
|
||||
<p class="stat-note" id="akurasi-status">Menunggu data keyakinan prediksi.</p>
|
||||
</div>
|
||||
|
||||
<div class="accuracy-ring" id="accuracy-ring">
|
||||
<div class="accuracy-ring-inner" id="accuracy-ring-text">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="dashboard-card dashboard-chart-full">
|
||||
<div class="card-header-modern">
|
||||
<div>
|
||||
<h5 class="card-title-modern">Distribusi Rekomendasi Jurusan</h5>
|
||||
<p class="card-subtitle-modern">
|
||||
Grafik ini menampilkan jumlah hasil rekomendasi berdasarkan jurusan yang muncul dari riwayat prediksi siswa.
|
||||
Detail siswa dan hasil lengkap dapat dilihat pada menu <b>Riwayat Prediksi</b>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<span class="chart-badge">
|
||||
<i class="fas fa-chart-column me-1"></i>
|
||||
Grafik Jurusan
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="chart-wrapper-modern">
|
||||
<canvas id="distribusiChart" style="display: none;"></canvas>
|
||||
|
||||
<div id="chart-placeholder" class="dashboard-placeholder">
|
||||
<div>
|
||||
<i class="fas fa-chart-pie"></i>
|
||||
<p>Memuat grafik distribusi...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
let distribusiChartInstance = null;
|
||||
|
||||
function setText(id, value) {
|
||||
const element = document.getElementById(id);
|
||||
|
||||
if (element) {
|
||||
element.innerText = value;
|
||||
}
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
const number = Number(value || 0);
|
||||
return new Intl.NumberFormat('id-ID').format(number);
|
||||
}
|
||||
|
||||
function formatPercent(value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '0%';
|
||||
}
|
||||
|
||||
const number = Number(value);
|
||||
|
||||
if (Number.isNaN(number)) {
|
||||
return '0%';
|
||||
}
|
||||
|
||||
return `${number.toFixed(number % 1 === 0 ? 0 : 2)}%`;
|
||||
}
|
||||
|
||||
function updateAccuracyRing(value) {
|
||||
const ring = document.getElementById('accuracy-ring');
|
||||
const ringText = document.getElementById('accuracy-ring-text');
|
||||
|
||||
const number = Number(value || 0);
|
||||
const degree = Math.max(0, Math.min(100, number)) * 3.6;
|
||||
|
||||
if (ring) {
|
||||
ring.style.background = `conic-gradient(#2D7A47 ${degree}deg, #e2e8f0 ${degree}deg)`;
|
||||
}
|
||||
|
||||
if (ringText) {
|
||||
ringText.innerText = formatPercent(number);
|
||||
}
|
||||
}
|
||||
|
||||
function updateLastUpdated() {
|
||||
const now = new Date();
|
||||
|
||||
setText('last-updated', now.toLocaleString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}));
|
||||
}
|
||||
|
||||
function renderDistribusiChart(distribusi) {
|
||||
const placeholder = document.getElementById('chart-placeholder');
|
||||
const canvas = document.getElementById('distribusiChart');
|
||||
|
||||
if (!placeholder || !canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!distribusi || distribusi.length === 0) {
|
||||
placeholder.style.display = 'flex';
|
||||
canvas.style.display = 'none';
|
||||
placeholder.querySelector('p').innerText = 'Belum ada data riwayat rekomendasi.';
|
||||
return;
|
||||
}
|
||||
|
||||
placeholder.style.display = 'none';
|
||||
canvas.style.display = 'block';
|
||||
|
||||
const labels = distribusi.map(item => item.jurusan || 'Tidak diketahui');
|
||||
const counts = distribusi.map(item => Number(item.total || 0));
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if (distribusiChartInstance) {
|
||||
distribusiChartInstance.destroy();
|
||||
}
|
||||
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 360);
|
||||
gradient.addColorStop(0, 'rgba(45, 122, 71, 0.95)');
|
||||
gradient.addColorStop(1, 'rgba(45, 122, 71, 0.55)');
|
||||
|
||||
distribusiChartInstance = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Jumlah Rekomendasi',
|
||||
data: counts,
|
||||
backgroundColor: gradient,
|
||||
borderColor: 'rgba(45, 122, 71, 1)',
|
||||
borderWidth: 1,
|
||||
borderRadius: 14,
|
||||
borderSkipped: false,
|
||||
maxBarThickness: 74
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
layout: {
|
||||
padding: {
|
||||
top: 12,
|
||||
right: 12,
|
||||
bottom: 0,
|
||||
left: 4
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: '#0f172a',
|
||||
titleColor: '#ffffff',
|
||||
bodyColor: '#e2e8f0',
|
||||
padding: 12,
|
||||
cornerRadius: 12,
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
return ` ${context.raw} rekomendasi`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
color: '#64748b',
|
||||
font: {
|
||||
size: 11,
|
||||
weight: '700'
|
||||
},
|
||||
maxRotation: 0,
|
||||
minRotation: 0
|
||||
}
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: '#edf2f7'
|
||||
},
|
||||
ticks: {
|
||||
color: '#64748b',
|
||||
precision: 0,
|
||||
stepSize: 1,
|
||||
font: {
|
||||
size: 11,
|
||||
weight: '700'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadDashboardStats() {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
if (!token) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
updateLastUpdated();
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/dashboard-stats', {
|
||||
headers: {
|
||||
|
|
@ -74,65 +288,42 @@
|
|||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
const data = result.data;
|
||||
|
||||
document.getElementById('stat-total-siswa').innerText = data.total_siswa;
|
||||
document.getElementById('stat-total-rekomendasi').innerText = data.rekomendasi_diproses;
|
||||
document.getElementById('stat-rata-akurasi').innerText = data.rata_rata_akurasi + '%';
|
||||
|
||||
const distribusi = data.distribusi_jurusan;
|
||||
const placeholder = document.getElementById('chart-placeholder');
|
||||
const canvas = document.getElementById('distribusiChart');
|
||||
const wrapper = document.getElementById('chart-wrapper');
|
||||
|
||||
if (distribusi && distribusi.length > 0) {
|
||||
placeholder.classList.add('d-none');
|
||||
canvas.classList.remove('d-none');
|
||||
wrapper.style.border = 'none';
|
||||
wrapper.style.background = 'transparent';
|
||||
|
||||
const labels = distribusi.map(item => item.jurusan);
|
||||
const counts = distribusi.map(item => item.total);
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: 'Jumlah Rekomendasi',
|
||||
data: counts,
|
||||
backgroundColor: 'rgba(45, 122, 71, 0.8)',
|
||||
borderColor: 'rgba(45, 122, 71, 1)',
|
||||
borderWidth: 1,
|
||||
borderRadius: 5
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { stepSize: 1 }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
placeholder.querySelector('p').innerText = 'Belum ada data riwayat rekomendasi.';
|
||||
}
|
||||
if (!response.ok || !result.success) {
|
||||
throw new Error(result.message || 'Gagal memuat data dashboard.');
|
||||
}
|
||||
|
||||
const data = result.data || {};
|
||||
const rataAkurasi = data.rata_rata_akurasi;
|
||||
|
||||
setText('stat-total-siswa', formatNumber(data.total_siswa));
|
||||
setText('stat-total-rekomendasi', formatNumber(data.rekomendasi_diproses));
|
||||
setText('stat-rata-akurasi', formatPercent(rataAkurasi));
|
||||
setText('akurasi-status', data.akurasi_status || 'Data keyakinan prediksi berhasil dimuat.');
|
||||
|
||||
updateAccuracyRing(rataAkurasi || 0);
|
||||
renderDistribusiChart(data.distribusi_jurusan || []);
|
||||
} catch (error) {
|
||||
console.error('Gagal memuat data dashboard:', error);
|
||||
document.getElementById('chart-placeholder').querySelector('p').innerText = 'Gagal memuat data.';
|
||||
|
||||
setText('akurasi-status', 'Gagal memuat data dashboard.');
|
||||
updateAccuracyRing(0);
|
||||
|
||||
const placeholder = document.getElementById('chart-placeholder');
|
||||
const canvas = document.getElementById('distribusiChart');
|
||||
|
||||
if (placeholder) {
|
||||
placeholder.style.display = 'flex';
|
||||
placeholder.querySelector('p').innerText = 'Gagal memuat grafik distribusi.';
|
||||
}
|
||||
|
||||
if (canvas) {
|
||||
canvas.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', loadDashboardStats);
|
||||
</script>
|
||||
@endpush
|
||||
@endsection
|
||||
|
|
@ -5,69 +5,158 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<div class="dashboard-section">
|
||||
<div class="mb-4 d-flex justify-content-between align-items-center">
|
||||
<div class="master-page-header">
|
||||
<div>
|
||||
<h4 class="fw-bold text-dark">Data Guru BK</h4>
|
||||
<p class="text-muted small">Kelola daftar administrator dan guru pembimbing SMAN 4 Jember.</p>
|
||||
<h4 class="master-page-title">Data Guru BK</h4>
|
||||
<p class="master-page-subtitle">Kelola daftar administrator dan guru pembimbing SMAN 4 Jember.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.guru.create') }}" class="btn text-white px-4 shadow-sm" style="background-color: var(--primary-green); border-radius: 10px;">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Guru BK
|
||||
|
||||
<a href="{{ route('admin.guru.create') }}" class="btn-add-modern">
|
||||
<i class="fas fa-plus"></i>
|
||||
Tambah Guru BK
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-container-modern shadow-sm">
|
||||
<div class="table-toolbar">
|
||||
<div class="search-wrapper-modern">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="searchInput" class="search-input-modern" placeholder="Cari nama guru atau ID guru...">
|
||||
</div>
|
||||
|
||||
<div class="per-page-wrapper">
|
||||
<span>Tampilkan</span>
|
||||
<select id="perPageSelect" class="per-page-select">
|
||||
<option value="7" selected>7 data</option>
|
||||
<option value="5">5 data</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-modern mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center" width="100">ID Guru</th>
|
||||
<th class="text-center" width="70">No</th>
|
||||
<th class="text-center" width="120">ID Guru</th>
|
||||
<th>Nama Lengkap & Gelar</th>
|
||||
<th class="text-center" width="150">Opsi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="guru-table-body">
|
||||
<tr><td colspan="3" class="text-center py-5 text-muted">Memuat data...</td></tr>
|
||||
<tr>
|
||||
<td colspan="4" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data guru...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pagination-container">
|
||||
<div class="text-muted small" id="pagination-info">
|
||||
Menampilkan 0 sampai 0 dari 0 data
|
||||
</div>
|
||||
<div class="d-flex gap-1" id="pagination-controls">
|
||||
</div>
|
||||
<div class="d-flex gap-1 flex-wrap" id="pagination-controls"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentSearch = '';
|
||||
let perPage = 7;
|
||||
let searchTimer = null;
|
||||
|
||||
function escapeHtml(value) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function getInitial(name) {
|
||||
const cleanName = name || 'G';
|
||||
return cleanName.charAt(0).toUpperCase();
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
document.getElementById('guru-table-body').innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data guru...
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
async function fetchGuru(page = 1) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const tbody = document.getElementById('guru-table-body');
|
||||
|
||||
if (!token) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
currentPage = page;
|
||||
showLoading();
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: page,
|
||||
per_page: perPage
|
||||
});
|
||||
|
||||
if (currentSearch !== '') {
|
||||
params.append('search', currentSearch);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/guru?page=${page}`, {
|
||||
headers: {
|
||||
const response = await fetch(`/api/admin/guru?${params.toString()}`, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
const tbody = document.getElementById('guru-table-body');
|
||||
|
||||
if (!response.ok || result.success === false) {
|
||||
throw new Error(result.message || 'Gagal memuat data guru.');
|
||||
}
|
||||
|
||||
const data = result.data || [];
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if(!result.data || result.data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="text-center py-4">Tidak ada data guru.</td></tr>';
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" class="empty-state-modern">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Tidak ada data guru yang ditemukan.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
updatePaginationInfo(null);
|
||||
renderPagination({ current_page: 1, last_page: 1, total: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
result.data.forEach((item) => {
|
||||
data.forEach((item, index) => {
|
||||
tbody.innerHTML += `
|
||||
<tr>
|
||||
<td class="text-center fw-bold text-muted">${item.id_guru}</td>
|
||||
<td class="text-center text-muted fw-bold">${(result.from || 1) + index}</td>
|
||||
<td class="text-center fw-bold text-muted">${escapeHtml(item.id_guru)}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<span class="badge badge-bk rounded-circle d-flex align-items-center justify-content-center" style="width: 30px; height: 30px;">BK</span>
|
||||
<b class="text-dark">${item.nama_guru}</b>
|
||||
<div class="table-user-cell">
|
||||
<div class="avatar-circle orange">${getInitial(item.nama_guru)}</div>
|
||||
<div>
|
||||
<div class="fw-bold text-dark">${escapeHtml(item.nama_guru)}</div>
|
||||
<div class="text-cell-muted">Guru Bimbingan Konseling</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
|
|
@ -80,66 +169,134 @@
|
|||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
document.getElementById('pagination-info').innerText =
|
||||
`Menampilkan ${result.from || 0} sampai ${result.to || 0} dari ${result.total || 0} data`;
|
||||
|
||||
updatePaginationInfo(result);
|
||||
renderPagination(result);
|
||||
} catch (error) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="text-center text-danger">Gagal memuat data.</td></tr>';
|
||||
|
||||
} catch (error) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" class="empty-state-modern text-danger">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
Gagal memuat data guru.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePaginationInfo(data) {
|
||||
const info = document.getElementById('pagination-info');
|
||||
|
||||
if (!data || !data.total) {
|
||||
info.innerText = 'Menampilkan 0 sampai 0 dari 0 data';
|
||||
return;
|
||||
}
|
||||
|
||||
info.innerText = `Menampilkan ${data.from || 0} sampai ${data.to || 0} dari ${data.total || 0} data`;
|
||||
}
|
||||
|
||||
function renderPagination(data) {
|
||||
const controls = document.getElementById('pagination-controls');
|
||||
controls.innerHTML = '';
|
||||
|
||||
const prevBtn = document.createElement('button');
|
||||
prevBtn.className = 'page-link-custom';
|
||||
prevBtn.innerHTML = '« Previous';
|
||||
prevBtn.disabled = data.current_page === 1;
|
||||
prevBtn.onclick = () => fetchGuru(data.current_page - 1);
|
||||
controls.appendChild(prevBtn);
|
||||
const current = Number(data.current_page || 1);
|
||||
const last = Number(data.last_page || 1);
|
||||
const total = Number(data.total || 0);
|
||||
|
||||
const pageBtn = document.createElement('button');
|
||||
pageBtn.className = 'page-link-custom active';
|
||||
pageBtn.innerText = data.current_page || 1;
|
||||
controls.appendChild(pageBtn);
|
||||
if (total === 0) return;
|
||||
|
||||
const nextBtn = document.createElement('button');
|
||||
nextBtn.className = 'page-link-custom';
|
||||
nextBtn.innerHTML = 'Next »';
|
||||
nextBtn.disabled = data.current_page === data.last_page;
|
||||
nextBtn.onclick = () => fetchGuru(data.current_page + 1);
|
||||
controls.appendChild(nextBtn);
|
||||
const createButton = (label, page, disabled = false, active = false) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `page-link-custom ${active ? 'active' : ''}`;
|
||||
btn.innerHTML = label;
|
||||
btn.disabled = disabled;
|
||||
btn.onclick = () => {
|
||||
if (!disabled && !active) fetchGuru(page);
|
||||
};
|
||||
return btn;
|
||||
};
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-left"></i>', current - 1, current === 1));
|
||||
|
||||
let pages = [];
|
||||
if (last <= 5) {
|
||||
for (let i = 1; i <= last; i++) pages.push(i);
|
||||
} else {
|
||||
pages.push(1);
|
||||
if (current > 3) pages.push('...');
|
||||
const start = Math.max(2, current - 1);
|
||||
const end = Math.min(last - 1, current + 1);
|
||||
for (let i = start; i <= end; i++) pages.push(i);
|
||||
if (current < last - 2) pages.push('...');
|
||||
pages.push(last);
|
||||
}
|
||||
|
||||
pages.forEach(page => {
|
||||
if (page === '...') {
|
||||
controls.appendChild(createButton('...', current, true));
|
||||
} else {
|
||||
controls.appendChild(createButton(page, page, false, page === current));
|
||||
}
|
||||
});
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-right"></i>', current + 1, current === last));
|
||||
}
|
||||
|
||||
async function deleteGuru(id) {
|
||||
const res = await Swal.fire({
|
||||
const result = await Swal.fire({
|
||||
title: 'Hapus Guru?',
|
||||
text: "Data guru akan dihapus permanen!",
|
||||
text: 'Data guru akan dihapus permanen!',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#ef4444',
|
||||
confirmButtonText: 'Ya, Hapus'
|
||||
cancelButtonColor: '#64748b',
|
||||
confirmButtonText: 'Ya, Hapus',
|
||||
cancelButtonText: 'Batal'
|
||||
});
|
||||
|
||||
if (res.isConfirmed) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
try {
|
||||
const response = await fetch('/api/admin/guru/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
if(response.ok) {
|
||||
Swal.fire('Terhapus!', 'Data guru berhasil dihapus.', 'success');
|
||||
fetchGuru();
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/guru/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
} catch (error) { Swal.fire('Error!', 'Koneksi gagal.', 'error'); }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
Swal.fire('Terhapus!', 'Data guru berhasil dihapus.', 'success');
|
||||
fetchGuru(currentPage);
|
||||
} else {
|
||||
Swal.fire('Gagal!', 'Data guru gagal dihapus.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
Swal.fire('Error!', 'Koneksi ke server gagal.', 'error');
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => fetchGuru(1));
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
fetchGuru(1);
|
||||
|
||||
document.getElementById('searchInput').addEventListener('input', function () {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => {
|
||||
currentSearch = this.value.trim();
|
||||
fetchGuru(1);
|
||||
}, 350);
|
||||
});
|
||||
|
||||
document.getElementById('perPageSelect').addEventListener('change', function () {
|
||||
perPage = this.value;
|
||||
fetchGuru(1);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -5,22 +5,39 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<div class="dashboard-section">
|
||||
<div class="mb-4 d-flex justify-content-between align-items-center">
|
||||
<div class="master-page-header">
|
||||
<div>
|
||||
<h4 class="fw-bold text-dark">Data Master Jurusan</h4>
|
||||
<p class="text-muted small">Kelola daftar jurusan kuliah yang tersedia dalam sistem prediksi.</p>
|
||||
<h4 class="master-page-title">Data Master Jurusan</h4>
|
||||
<p class="master-page-subtitle">Kelola daftar jurusan kuliah yang tersedia dalam sistem prediksi.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.jurusan.create') }}" class="btn text-white px-4 shadow-sm" style="background-color: var(--primary-green); border-radius: 10px;">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Jurusan
|
||||
|
||||
<a href="{{ route('admin.jurusan.create') }}" class="btn-add-modern">
|
||||
<i class="fas fa-plus"></i>
|
||||
Tambah Jurusan
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-container-modern shadow-sm">
|
||||
<div class="table-toolbar">
|
||||
<div class="search-wrapper-modern">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="searchInput" class="search-input-modern" placeholder="Cari nama jurusan, rumpun, atau prospek karir...">
|
||||
</div>
|
||||
|
||||
<div class="per-page-wrapper">
|
||||
<span>Tampilkan</span>
|
||||
<select id="perPageSelect" class="per-page-select">
|
||||
<option value="7" selected>7 data</option>
|
||||
<option value="5">5 data</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-modern mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center" width="60">No</th>
|
||||
<th class="text-center" width="70">No</th>
|
||||
<th>Nama Jurusan</th>
|
||||
<th class="text-center">Rumpun</th>
|
||||
<th>Prospek Karir</th>
|
||||
|
|
@ -28,54 +45,124 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody id="jurusan-table-body">
|
||||
<tr><td colspan="5" class="text-center py-5 text-muted">Memuat data...</td></tr>
|
||||
<tr>
|
||||
<td colspan="5" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data jurusan...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pagination-container">
|
||||
<div class="text-muted small" id="pagination-info">
|
||||
Menampilkan 0 sampai 0 dari 0 data
|
||||
</div>
|
||||
<div class="d-flex gap-1" id="pagination-controls">
|
||||
</div>
|
||||
<div class="d-flex gap-1 flex-wrap" id="pagination-controls"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentSearch = '';
|
||||
let perPage = 7;
|
||||
let searchTimer = null;
|
||||
|
||||
function escapeHtml(value) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
document.getElementById('jurusan-table-body').innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data jurusan...
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
async function fetchJurusan(page = 1) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const tbody = document.getElementById('jurusan-table-body');
|
||||
|
||||
if (!token) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
currentPage = page;
|
||||
showLoading();
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: page,
|
||||
per_page: perPage
|
||||
});
|
||||
|
||||
if (currentSearch !== '') {
|
||||
params.append('search', currentSearch);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/jurusan?page=${page}`, {
|
||||
headers: {
|
||||
const response = await fetch(`/api/admin/jurusan?${params.toString()}`, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
const tbody = document.getElementById('jurusan-table-body');
|
||||
|
||||
if (!response.ok || result.success === false) {
|
||||
throw new Error(result.message || 'Gagal memuat data jurusan.');
|
||||
}
|
||||
|
||||
const data = result.data || [];
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if(!result.data || result.data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-4">Tidak ada data jurusan.</td></tr>';
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="empty-state-modern">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Tidak ada data jurusan yang ditemukan.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
updatePaginationInfo(null);
|
||||
renderPagination({ current_page: 1, last_page: 1, total: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
result.data.forEach((item, index) => {
|
||||
data.forEach((item, index) => {
|
||||
const rumpun = item.rumpun || '-';
|
||||
const rumpunClass = rumpun.toLowerCase() === 'saintek' ? 'badge-saintek' : 'badge-soshum';
|
||||
|
||||
const rumpunClass = item.rumpun.toLowerCase() === 'saintek' ? 'badge-saintek' : 'badge-soshum';
|
||||
|
||||
const row = `
|
||||
tbody.innerHTML += `
|
||||
<tr>
|
||||
<td class="text-center text-muted">${(result.current_page - 1) * result.per_page + (index + 1)}</td>
|
||||
<td class="fw-bold text-dark text-uppercase">${item.nama_jurusan}</td>
|
||||
<td class="text-center">
|
||||
<span class="badge rounded-pill px-3 py-2 ${rumpunClass}">
|
||||
${item.rumpun}
|
||||
</span>
|
||||
<td class="text-center text-muted fw-bold">${(result.from || 1) + index}</td>
|
||||
<td>
|
||||
<div class="table-user-cell">
|
||||
<div class="avatar-circle purple">
|
||||
<i class="fas fa-graduation-cap"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold text-dark text-uppercase">${escapeHtml(item.nama_jurusan)}</div>
|
||||
<div class="text-cell-muted">Data master jurusan</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="small text-muted">${item.prospek_karir || '-'}</td>
|
||||
<td class="text-center">
|
||||
<span class="badge-soft ${rumpunClass}">${escapeHtml(rumpun)}</span>
|
||||
</td>
|
||||
<td class="text-cell-muted">${escapeHtml(item.prospek_karir)}</td>
|
||||
<td class="text-center">
|
||||
<div class="opsi-wrapper">
|
||||
<a href="/admin/jurusan/${item.id}/edit" class="btn-action btn-edit" title="Edit">
|
||||
|
|
@ -86,68 +173,134 @@
|
|||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
tbody.innerHTML += row;
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
document.getElementById('pagination-info').innerText =
|
||||
`Menampilkan ${result.from || 0} sampai ${result.to || 0} dari ${result.total || 0} data`;
|
||||
|
||||
updatePaginationInfo(result);
|
||||
renderPagination(result);
|
||||
} catch (error) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-danger">Gagal memuat data.</td></tr>';
|
||||
|
||||
} catch (error) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="empty-state-modern text-danger">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
Gagal memuat data jurusan.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePaginationInfo(data) {
|
||||
const info = document.getElementById('pagination-info');
|
||||
|
||||
if (!data || !data.total) {
|
||||
info.innerText = 'Menampilkan 0 sampai 0 dari 0 data';
|
||||
return;
|
||||
}
|
||||
|
||||
info.innerText = `Menampilkan ${data.from || 0} sampai ${data.to || 0} dari ${data.total || 0} data`;
|
||||
}
|
||||
|
||||
function renderPagination(data) {
|
||||
const controls = document.getElementById('pagination-controls');
|
||||
controls.innerHTML = '';
|
||||
|
||||
const prevBtn = document.createElement('button');
|
||||
prevBtn.className = 'page-link-custom';
|
||||
prevBtn.innerHTML = '« Previous';
|
||||
prevBtn.disabled = data.current_page === 1;
|
||||
prevBtn.onclick = () => fetchJurusan(data.current_page - 1);
|
||||
controls.appendChild(prevBtn);
|
||||
const current = Number(data.current_page || 1);
|
||||
const last = Number(data.last_page || 1);
|
||||
const total = Number(data.total || 0);
|
||||
|
||||
const pageBtn = document.createElement('button');
|
||||
pageBtn.className = 'page-link-custom active';
|
||||
pageBtn.innerText = data.current_page;
|
||||
controls.appendChild(pageBtn);
|
||||
if (total === 0) return;
|
||||
|
||||
const nextBtn = document.createElement('button');
|
||||
nextBtn.className = 'page-link-custom';
|
||||
nextBtn.innerHTML = 'Next »';
|
||||
nextBtn.disabled = data.current_page === data.last_page;
|
||||
nextBtn.onclick = () => fetchJurusan(data.current_page + 1);
|
||||
controls.appendChild(nextBtn);
|
||||
const createButton = (label, page, disabled = false, active = false) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `page-link-custom ${active ? 'active' : ''}`;
|
||||
btn.innerHTML = label;
|
||||
btn.disabled = disabled;
|
||||
btn.onclick = () => {
|
||||
if (!disabled && !active) fetchJurusan(page);
|
||||
};
|
||||
return btn;
|
||||
};
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-left"></i>', current - 1, current === 1));
|
||||
|
||||
let pages = [];
|
||||
if (last <= 5) {
|
||||
for (let i = 1; i <= last; i++) pages.push(i);
|
||||
} else {
|
||||
pages.push(1);
|
||||
if (current > 3) pages.push('...');
|
||||
const start = Math.max(2, current - 1);
|
||||
const end = Math.min(last - 1, current + 1);
|
||||
for (let i = start; i <= end; i++) pages.push(i);
|
||||
if (current < last - 2) pages.push('...');
|
||||
pages.push(last);
|
||||
}
|
||||
|
||||
pages.forEach(page => {
|
||||
if (page === '...') {
|
||||
controls.appendChild(createButton('...', current, true));
|
||||
} else {
|
||||
controls.appendChild(createButton(page, page, false, page === current));
|
||||
}
|
||||
});
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-right"></i>', current + 1, current === last));
|
||||
}
|
||||
|
||||
async function deleteJurusan(id) {
|
||||
const res = await Swal.fire({
|
||||
const result = await Swal.fire({
|
||||
title: 'Hapus data?',
|
||||
text: "Data yang dihapus tidak dapat dikembalikan!",
|
||||
text: 'Data jurusan yang dihapus tidak dapat dikembalikan!',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#ef4444',
|
||||
confirmButtonText: 'Ya, Hapus'
|
||||
cancelButtonColor: '#64748b',
|
||||
confirmButtonText: 'Ya, Hapus',
|
||||
cancelButtonText: 'Batal'
|
||||
});
|
||||
|
||||
if (res.isConfirmed) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
try {
|
||||
const response = await fetch('/api/admin/jurusan/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
if(response.ok) {
|
||||
Swal.fire('Terhapus!', 'Data berhasil dihapus.', 'success');
|
||||
fetchJurusan();
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/jurusan/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
} catch (error) { Swal.fire('Error!', 'Koneksi gagal.', 'error'); }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
Swal.fire('Terhapus!', 'Data jurusan berhasil dihapus.', 'success');
|
||||
fetchJurusan(currentPage);
|
||||
} else {
|
||||
Swal.fire('Gagal!', 'Data jurusan gagal dihapus.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
Swal.fire('Error!', 'Koneksi ke server gagal.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => fetchJurusan(1));
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
fetchJurusan(1);
|
||||
|
||||
document.getElementById('searchInput').addEventListener('input', function () {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => {
|
||||
currentSearch = this.value.trim();
|
||||
fetchJurusan(1);
|
||||
}, 350);
|
||||
});
|
||||
|
||||
document.getElementById('perPageSelect').addEventListener('change', function () {
|
||||
perPage = this.value;
|
||||
fetchJurusan(1);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -13,82 +13,186 @@
|
|||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{{ asset('css/admin.css') }}" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
.sidebar-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-logo-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/*
|
||||
Style logo dibuat spesifik dan memakai !important
|
||||
agar tidak tertimpa style dari file admin.css.
|
||||
Bagian background putih, border-radius, padding, border, dan box-shadow
|
||||
dihapus supaya logo tidak berbentuk oval.
|
||||
*/
|
||||
.sidebar-modern .sidebar-header .sidebar-logo-wrapper img.sidebar-school-logo {
|
||||
width: 105px !important;
|
||||
height: 105px !important;
|
||||
object-fit: contain !important;
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
border-radius: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
display: block !important;
|
||||
margin: 0 auto !important;
|
||||
filter: drop-shadow(0 6px 8px rgba(0, 0, 0, 0.22));
|
||||
}
|
||||
|
||||
.sidebar-brand-text {
|
||||
letter-spacing: 0.5px;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.sidebar-brand-sub {
|
||||
letter-spacing: 0.4px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.admin-avatar {
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
|
||||
@stack('styles')
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="admin-wrapper">
|
||||
|
||||
<aside class="sidebar-modern">
|
||||
|
||||
<div class="sidebar-header">
|
||||
<img src="{{ asset('images/logo.png') }}" alt="Logo SMAN 4 Jember" onerror="this.src='https://ui-avatars.com/api/?name=S4&background=2D7A47&color=fff'">
|
||||
<div class="sidebar-brand-text fw-bold text-white">SMAN 4 JEMBER</div>
|
||||
<div class="sidebar-brand-sub small" style="opacity: 0.8;">SISTEM REKOMENDASI</div>
|
||||
|
||||
<!-- Logo SMAN 4 Jember -->
|
||||
<div class="sidebar-logo-wrapper">
|
||||
<img src="{{ asset('image/smapa-removebg-preview.png') }}?v=3"
|
||||
alt="Logo SMAN 4 Jember"
|
||||
class="sidebar-school-logo"
|
||||
onerror="this.onerror=null; this.src='https://ui-avatars.com/api/?name=S4&background=2D7A47&color=fff&rounded=true';">
|
||||
</div>
|
||||
|
||||
<div class="sidebar-brand-text fw-bold text-white">
|
||||
SMAN 4 JEMBER
|
||||
</div>
|
||||
|
||||
<div class="sidebar-brand-sub small text-white" style="opacity: 0.85;">
|
||||
SISTEM REKOMENDASI
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-menu">
|
||||
|
||||
<div class="menu-label">Utama</div>
|
||||
|
||||
<a href="{{ route('admin.dashboard') }}" class="nav-item {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
||||
<i class="fas fa-th-large"></i> Dashboard
|
||||
<i class="fas fa-th-large"></i>
|
||||
Dashboard
|
||||
</a>
|
||||
|
||||
<div class="menu-label">Manajemen Data</div>
|
||||
|
||||
<a href="{{ route('admin.siswa') }}" class="nav-item {{ request()->routeIs('admin.siswa*') ? 'active' : '' }}">
|
||||
<i class="fas fa-user-graduate"></i> Data Siswa
|
||||
<i class="fas fa-user-graduate"></i>
|
||||
Data Siswa
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.jurusan') }}" class="nav-item {{ request()->routeIs('admin.jurusan*') ? 'active' : '' }}">
|
||||
<i class="fas fa-university"></i> Data Jurusan
|
||||
<i class="fas fa-university"></i>
|
||||
Data Jurusan
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.prospek-kerja') }}" class="nav-item {{ request()->routeIs('admin.prospek-kerja*') ? 'active' : '' }}">
|
||||
<i class="fas fa-briefcase"></i> Data Prospek Kerja
|
||||
<i class="fas fa-briefcase"></i>
|
||||
Data Prospek Kerja
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.guru') }}" class="nav-item {{ request()->routeIs('admin.guru*') ? 'active' : '' }}">
|
||||
<i class="fas fa-user-tie"></i> Guru BK
|
||||
<i class="fas fa-user-tie"></i>
|
||||
Guru BK
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.alumni') }}" class="nav-item {{ request()->routeIs('admin.alumni*') ? 'active' : '' }}">
|
||||
<i class="fas fa-graduation-cap"></i> Data Alumni
|
||||
<i class="fas fa-graduation-cap"></i>
|
||||
Data Alumni
|
||||
</a>
|
||||
|
||||
<div class="menu-label">Analisis & Riwayat</div>
|
||||
|
||||
<a href="{{ route('admin.riwayat') }}" class="nav-item {{ request()->routeIs('admin.riwayat*') ? 'active' : '' }}">
|
||||
<i class="fas fa-history"></i> Riwayat Prediksi
|
||||
</a>
|
||||
<a href="{{ route('admin.latih-model') }}" class="nav-item {{ request()->routeIs('admin.latih-model') ? 'active' : '' }}">
|
||||
<i class="fas fa-brain"></i> Latih Model (RF)
|
||||
<i class="fas fa-history"></i>
|
||||
Riwayat Prediksi
|
||||
</a>
|
||||
|
||||
<!-- <a href="{{ route('admin.latih-model') }}" class="nav-item {{ request()->routeIs('admin.latih-model') ? 'active' : '' }}">
|
||||
<i class="fas fa-brain"></i>
|
||||
Latih Model (RF)
|
||||
</a> -->
|
||||
|
||||
<div class="mt-4 pt-4 border-top border-white border-opacity-10">
|
||||
<a href="{{ route('landingpage') }}" class="nav-item text-white opacity-75">
|
||||
<i class="fas fa-sign-out-alt"></i> Keluar Ke Landing
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
Keluar Ke Landing
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
</aside>
|
||||
|
||||
<div class="main-content">
|
||||
|
||||
<header class="topbar">
|
||||
|
||||
<div class="search-bar">
|
||||
<i class="fas fa-search text-muted"></i>
|
||||
<input type="text" placeholder="Cari data...">
|
||||
</div>
|
||||
|
||||
<div class="user-profile d-flex align-items-center gap-3">
|
||||
|
||||
<div class="text-end d-none d-sm-block">
|
||||
<div class="fw-bold small">Administrator</div>
|
||||
<div class="text-muted" style="font-size: 11px;">GURU BK</div>
|
||||
<div class="fw-bold small">
|
||||
Administrator
|
||||
</div>
|
||||
|
||||
<div class="text-muted" style="font-size: 11px;">
|
||||
GURU BK
|
||||
</div>
|
||||
</div>
|
||||
<img src="https://ui-avatars.com/api/?name=Admin+BK&background=2D7A47&color=fff&rounded=true" width="40" height="40">
|
||||
|
||||
<img src="https://ui-avatars.com/api/?name=Admin+BK&background=2D7A47&color=fff&rounded=true"
|
||||
width="40"
|
||||
height="40"
|
||||
alt="Admin BK"
|
||||
class="admin-avatar">
|
||||
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<main class="p-4">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
@stack('scripts')
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -5,22 +5,39 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<div class="dashboard-section">
|
||||
<div class="mb-4 d-flex justify-content-between align-items-center">
|
||||
<div class="master-page-header">
|
||||
<div>
|
||||
<h4 class="fw-bold text-dark">Data Master Prospek Kerja</h4>
|
||||
<p class="text-muted small">Kelola daftar karir dan estimasi gaji untuk setiap jurusan.</p>
|
||||
<h4 class="master-page-title">Data Master Prospek Kerja</h4>
|
||||
<p class="master-page-subtitle">Kelola daftar karir dan estimasi gaji untuk setiap jurusan.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.prospek-kerja.create') }}" class="btn text-white px-4 shadow-sm" style="background-color: var(--primary-green); border-radius: 10px;">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Prospek
|
||||
|
||||
<a href="{{ route('admin.prospek-kerja.create') }}" class="btn-add-modern">
|
||||
<i class="fas fa-plus"></i>
|
||||
Tambah Prospek
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-container-modern shadow-sm">
|
||||
<div class="table-toolbar">
|
||||
<div class="search-wrapper-modern">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="searchInput" class="search-input-modern" placeholder="Cari profesi, jurusan, atau estimasi gaji...">
|
||||
</div>
|
||||
|
||||
<div class="per-page-wrapper">
|
||||
<span>Tampilkan</span>
|
||||
<select id="perPageSelect" class="per-page-select">
|
||||
<option value="7" selected>7 data</option>
|
||||
<option value="5">5 data</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-modern mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center" width="60">No</th>
|
||||
<th class="text-center" width="70">No</th>
|
||||
<th>Nama Profesi</th>
|
||||
<th>Asal Jurusan</th>
|
||||
<th>Estimasi Gaji</th>
|
||||
|
|
@ -28,41 +45,123 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody id="prospek-table-body">
|
||||
<tr><td colspan="5" class="text-center py-5 text-muted">Memuat data...</td></tr>
|
||||
<tr>
|
||||
<td colspan="5" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data prospek kerja...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-container">
|
||||
<div class="text-muted small" id="pagination-info">
|
||||
Menampilkan 0 sampai 0 dari 0 data
|
||||
</div>
|
||||
<div class="d-flex gap-1 flex-wrap" id="pagination-controls"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function fetchProspek() {
|
||||
let currentPage = 1;
|
||||
let currentSearch = '';
|
||||
let perPage = 7;
|
||||
let searchTimer = null;
|
||||
|
||||
function escapeHtml(value) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
document.getElementById('prospek-table-body').innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data prospek kerja...
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
async function fetchProspek(page = 1) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const tbody = document.getElementById('prospek-table-body');
|
||||
|
||||
if (!token) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
currentPage = page;
|
||||
showLoading();
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: page,
|
||||
per_page: perPage
|
||||
});
|
||||
|
||||
if (currentSearch !== '') {
|
||||
params.append('search', currentSearch);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/prospek-kerja`, {
|
||||
headers: {
|
||||
const response = await fetch(`/api/admin/prospek-kerja?${params.toString()}`, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
const tbody = document.getElementById('prospek-table-body');
|
||||
|
||||
if (!response.ok || result.success === false) {
|
||||
throw new Error(result.message || 'Gagal memuat data prospek kerja.');
|
||||
}
|
||||
|
||||
const data = result.data || [];
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if(!result.data || result.data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-4">Tidak ada data prospek kerja.</td></tr>';
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="empty-state-modern">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Tidak ada data prospek kerja yang ditemukan.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
updatePaginationInfo(null);
|
||||
renderPagination({ current_page: 1, last_page: 1, total: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
result.data.forEach((item, index) => {
|
||||
const namaJurusan = item.jurusan ? item.jurusan.nama_jurusan : '<span class="text-danger">Jurusan Dihapus</span>';
|
||||
|
||||
const row = `
|
||||
data.forEach((item, index) => {
|
||||
const namaJurusan = item.jurusan ? escapeHtml(item.jurusan.nama_jurusan) : 'Jurusan Dihapus';
|
||||
|
||||
tbody.innerHTML += `
|
||||
<tr>
|
||||
<td class="text-center text-muted">${index + 1}</td>
|
||||
<td class="fw-bold text-dark">${item.nama_profesi}</td>
|
||||
<td><span class="badge bg-light text-dark border px-3 py-2">${namaJurusan}</span></td>
|
||||
<td class="small text-success fw-bold">${item.range_gaji || 'Belum diatur'}</td>
|
||||
<td class="text-center text-muted fw-bold">${(result.from || 1) + index}</td>
|
||||
<td>
|
||||
<div class="table-user-cell">
|
||||
<div class="avatar-circle blue">
|
||||
<i class="fas fa-briefcase"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold text-dark">${escapeHtml(item.nama_profesi)}</div>
|
||||
<div class="text-cell-muted">Prospek kerja</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge-soft badge-result">${namaJurusan}</span>
|
||||
</td>
|
||||
<td class="fw-bold text-success">${escapeHtml(item.range_gaji || 'Belum diatur')}</td>
|
||||
<td class="text-center">
|
||||
<div class="opsi-wrapper">
|
||||
<a href="/admin/prospek-kerja/${item.id}/edit" class="btn-action btn-edit" title="Edit">
|
||||
|
|
@ -73,39 +172,134 @@
|
|||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
tbody.innerHTML += row;
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
} catch (error) {
|
||||
document.getElementById('prospek-table-body').innerHTML = '<tr><td colspan="5" class="text-center text-danger">Gagal memuat data.</td></tr>';
|
||||
|
||||
updatePaginationInfo(result);
|
||||
renderPagination(result);
|
||||
|
||||
} catch (error) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="empty-state-modern text-danger">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
Gagal memuat data prospek kerja.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePaginationInfo(data) {
|
||||
const info = document.getElementById('pagination-info');
|
||||
|
||||
if (!data || !data.total) {
|
||||
info.innerText = 'Menampilkan 0 sampai 0 dari 0 data';
|
||||
return;
|
||||
}
|
||||
|
||||
info.innerText = `Menampilkan ${data.from || 0} sampai ${data.to || 0} dari ${data.total || 0} data`;
|
||||
}
|
||||
|
||||
function renderPagination(data) {
|
||||
const controls = document.getElementById('pagination-controls');
|
||||
controls.innerHTML = '';
|
||||
|
||||
const current = Number(data.current_page || 1);
|
||||
const last = Number(data.last_page || 1);
|
||||
const total = Number(data.total || 0);
|
||||
|
||||
if (total === 0) return;
|
||||
|
||||
const createButton = (label, page, disabled = false, active = false) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `page-link-custom ${active ? 'active' : ''}`;
|
||||
btn.innerHTML = label;
|
||||
btn.disabled = disabled;
|
||||
btn.onclick = () => {
|
||||
if (!disabled && !active) fetchProspek(page);
|
||||
};
|
||||
return btn;
|
||||
};
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-left"></i>', current - 1, current === 1));
|
||||
|
||||
let pages = [];
|
||||
if (last <= 5) {
|
||||
for (let i = 1; i <= last; i++) pages.push(i);
|
||||
} else {
|
||||
pages.push(1);
|
||||
if (current > 3) pages.push('...');
|
||||
const start = Math.max(2, current - 1);
|
||||
const end = Math.min(last - 1, current + 1);
|
||||
for (let i = start; i <= end; i++) pages.push(i);
|
||||
if (current < last - 2) pages.push('...');
|
||||
pages.push(last);
|
||||
}
|
||||
|
||||
pages.forEach(page => {
|
||||
if (page === '...') {
|
||||
controls.appendChild(createButton('...', current, true));
|
||||
} else {
|
||||
controls.appendChild(createButton(page, page, false, page === current));
|
||||
}
|
||||
});
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-right"></i>', current + 1, current === last));
|
||||
}
|
||||
|
||||
async function deleteProspek(id) {
|
||||
const res = await Swal.fire({
|
||||
const result = await Swal.fire({
|
||||
title: 'Hapus data?',
|
||||
text: "Data yang dihapus tidak dapat dikembalikan!",
|
||||
text: 'Data prospek kerja yang dihapus tidak dapat dikembalikan!',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#ef4444',
|
||||
confirmButtonText: 'Ya, Hapus'
|
||||
cancelButtonColor: '#64748b',
|
||||
confirmButtonText: 'Ya, Hapus',
|
||||
cancelButtonText: 'Batal'
|
||||
});
|
||||
|
||||
if (res.isConfirmed) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
try {
|
||||
const response = await fetch('/api/admin/prospek-kerja/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
if(response.ok) {
|
||||
Swal.fire('Terhapus!', 'Data berhasil dihapus.', 'success');
|
||||
fetchProspek();
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/prospek-kerja/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
} catch (error) { Swal.fire('Error!', 'Koneksi gagal.', 'error'); }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
Swal.fire('Terhapus!', 'Data prospek kerja berhasil dihapus.', 'success');
|
||||
fetchProspek(currentPage);
|
||||
} else {
|
||||
Swal.fire('Gagal!', 'Data prospek kerja gagal dihapus.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
Swal.fire('Error!', 'Koneksi ke server gagal.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', fetchProspek);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
fetchProspek(1);
|
||||
|
||||
document.getElementById('searchInput').addEventListener('input', function () {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => {
|
||||
currentSearch = this.value.trim();
|
||||
fetchProspek(1);
|
||||
}, 350);
|
||||
});
|
||||
|
||||
document.getElementById('perPageSelect').addEventListener('change', function () {
|
||||
perPage = this.value;
|
||||
fetchProspek(1);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -1,46 +1,94 @@
|
|||
@extends('admin.layout')
|
||||
|
||||
@section('content')
|
||||
<div class="mb-4">
|
||||
<h4 class="fw-bold text-dark">Riwayat Prediksi Jurusan</h4>
|
||||
<p class="text-muted small">Hasil prediksi algoritma Random Forest berdasarkan profil siswa.</p>
|
||||
</div>
|
||||
<link rel="stylesheet" href="{{ asset('css/fitur-admin.css') }}">
|
||||
<link rel="stylesheet" href="{{ asset('css/riwayat-admin.css') }}">
|
||||
|
||||
<div class="dashboard-section">
|
||||
<div class="riwayat-header">
|
||||
<div>
|
||||
<div class="riwayat-label">
|
||||
<i class="fas fa-history"></i>
|
||||
Riwayat Prediksi
|
||||
</div>
|
||||
|
||||
<h4 class="master-page-title">Riwayat Prediksi Jurusan</h4>
|
||||
|
||||
<p class="master-page-subtitle">
|
||||
Halaman ini menampilkan seluruh hasil rekomendasi siswa beserta informasi pendukung seperti waktu prediksi,
|
||||
hasil rekomendasi, tingkat keyakinan, minat utama, dan rata-rata nilai.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container-modern shadow-sm">
|
||||
<div class="table-toolbar">
|
||||
<div class="search-wrapper-modern">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="searchInput" class="search-input-modern" placeholder="Cari nama siswa, email, atau hasil rekomendasi...">
|
||||
</div>
|
||||
|
||||
<div class="per-page-wrapper">
|
||||
<span>Tampilkan</span>
|
||||
<select id="perPageSelect" class="per-page-select">
|
||||
<option value="7" selected>7 data</option>
|
||||
<option value="5">5 data</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm rounded-4">
|
||||
<div class="card-body p-4">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<table class="table table-modern mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-3 px-4">Tanggal</th>
|
||||
<th class="py-3">Nama Siswa</th>
|
||||
<th class="py-3">Hasil Rekomendasi</th>
|
||||
<th class="py-3 text-center">Aksi</th>
|
||||
<th class="text-center" width="70">No</th>
|
||||
<th width="170">Waktu</th>
|
||||
<th>Data Siswa</th>
|
||||
<th>Hasil Rekomendasi</th>
|
||||
<th>Ringkasan Input</th>
|
||||
<th class="text-center" width="140">Keyakinan</th>
|
||||
<th class="text-center" width="130">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody id="table-riwayat">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-4 text-muted">
|
||||
<td colspan="7" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data riwayat...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-container">
|
||||
<div class="text-muted small" id="pagination-info">
|
||||
Menampilkan 0 sampai 0 dari 0 data
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-1 flex-wrap" id="pagination-controls"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header border-bottom-0 pb-0">
|
||||
<h5 class="modal-title fw-bold" id="detailModalLabel">Detail Prediksi Jurusan</h5>
|
||||
<div class="modal-header border-bottom-0 px-4 pt-4">
|
||||
<div>
|
||||
<h5 class="modal-title fw-bold" id="detailModalLabel">Detail Prediksi Jurusan</h5>
|
||||
<p class="text-muted small mb-0">Informasi ringkas dari hasil rekomendasi siswa.</p>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-4" id="modal-body-content">
|
||||
</div>
|
||||
<div class="modal-footer border-top-0 pt-0">
|
||||
<button type="button" class="btn btn-secondary px-4 rounded-pill shadow-none" data-bs-dismiss="modal">Tutup</button>
|
||||
|
||||
<div class="modal-body px-4 pb-4" id="modal-body-content"></div>
|
||||
|
||||
<div class="modal-footer border-top-0 px-4 pb-4 pt-0">
|
||||
<button type="button" class="btn btn-secondary px-4 rounded-pill shadow-none" data-bs-dismiss="modal">
|
||||
Tutup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -48,20 +96,84 @@
|
|||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
let currentPage = 1;
|
||||
let currentSearch = '';
|
||||
let perPage = 7;
|
||||
let searchTimer = null;
|
||||
|
||||
function escapeHtml(value) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
|
||||
return String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function formatPercent(value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const number = Number(value);
|
||||
|
||||
if (Number.isNaN(number)) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return `${number.toFixed(number % 1 === 0 ? 0 : 2)}%`;
|
||||
}
|
||||
|
||||
function formatRupiah(value) {
|
||||
const number = Number(value || 0);
|
||||
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
maximumFractionDigits: 0
|
||||
}).format(number);
|
||||
}
|
||||
|
||||
function getInitial(name) {
|
||||
const cleanName = name || 'S';
|
||||
return cleanName.charAt(0).toUpperCase();
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
document.getElementById('table-riwayat').innerHTML = `
|
||||
<tr>
|
||||
<td colspan="7" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data riwayat...
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
async function fetchRiwayat(page = 1) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
// Cek sesi login
|
||||
const tbody = document.getElementById('table-riwayat');
|
||||
|
||||
if (!token) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
const tbody = document.getElementById('table-riwayat');
|
||||
currentPage = page;
|
||||
showLoading();
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: page,
|
||||
per_page: perPage
|
||||
});
|
||||
|
||||
if (currentSearch !== '') {
|
||||
params.append('search', currentSearch);
|
||||
}
|
||||
|
||||
try {
|
||||
// Memanggil API riwayat
|
||||
const response = await fetch('/api/admin/riwayat', {
|
||||
const response = await fetch(`/api/admin/riwayat?${params.toString()}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Accept': 'application/json'
|
||||
|
|
@ -69,59 +181,187 @@
|
|||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
const data = result.data;
|
||||
|
||||
// Jika data kosong
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="text-center py-4 text-muted">Belum ada riwayat prediksi yang dilakukan siswa.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Render data riwayat ke dalam tabel
|
||||
tbody.innerHTML = data.map(item => `
|
||||
if (!response.ok || result.success === false) {
|
||||
throw new Error(result.message || 'Gagal memuat data riwayat.');
|
||||
}
|
||||
|
||||
const data = result.data || [];
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td class="px-4 text-muted">${item.tanggal}</td>
|
||||
<td class="fw-bold">${item.nama_siswa}</td>
|
||||
<td><span class="badge bg-success px-3 py-2 rounded-pill shadow-none">${item.hasil}</span></td>
|
||||
<td colspan="7" class="empty-state-modern">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Tidak ada riwayat prediksi yang ditemukan.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
updatePaginationInfo(null);
|
||||
renderPagination({ current_page: 1, last_page: 1, total: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
data.forEach((item, index) => {
|
||||
const confidence = formatPercent(item.akurasi_prediksi);
|
||||
|
||||
tbody.innerHTML += `
|
||||
<tr>
|
||||
<td class="text-center text-muted fw-bold">${(result.from || 1) + index}</td>
|
||||
|
||||
<td>
|
||||
<div class="riwayat-date-main">${escapeHtml(item.tanggal)}</div>
|
||||
<div class="riwayat-date-sub">Pukul ${escapeHtml(item.jam)}</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="riwayat-user-cell">
|
||||
<div class="riwayat-avatar">${escapeHtml(getInitial(item.nama_siswa))}</div>
|
||||
<div>
|
||||
<div class="riwayat-user-name">${escapeHtml(item.nama_siswa)}</div>
|
||||
<div class="riwayat-user-email">${escapeHtml(item.email_siswa)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="badge-soft badge-result">${escapeHtml(item.hasil)}</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div class="riwayat-input-summary">
|
||||
<span>Minat: ${escapeHtml(item.minat_utama)}</span>
|
||||
<span>Rata-rata nilai: ${escapeHtml(item.rata_nilai)}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="text-center">
|
||||
<button class="btn btn-sm btn-light px-3 rounded-pill text-primary fw-medium" onclick="showDetail(${item.id})">
|
||||
<span class="confidence-badge">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
${escapeHtml(confidence)}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="text-center">
|
||||
<button class="btn-detail" onclick="showDetail(${item.id})">
|
||||
<i class="fas fa-file-alt me-1"></i> Detail
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
} else {
|
||||
tbody.innerHTML = `<tr><td colspan="4" class="text-center py-4 text-danger">Gagal memuat data: ${result.message}</td></tr>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching riwayat:', error);
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="text-center py-4 text-danger">Terjadi kesalahan jaringan atau server.</td></tr>';
|
||||
}
|
||||
});
|
||||
`;
|
||||
});
|
||||
|
||||
updatePaginationInfo(result);
|
||||
renderPagination(result);
|
||||
|
||||
} catch (error) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="7" class="empty-state-modern text-danger">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
Gagal memuat data riwayat.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePaginationInfo(data) {
|
||||
const info = document.getElementById('pagination-info');
|
||||
|
||||
if (!data || !data.total) {
|
||||
info.innerText = 'Menampilkan 0 sampai 0 dari 0 data';
|
||||
return;
|
||||
}
|
||||
|
||||
info.innerText = `Menampilkan ${data.from || 0} sampai ${data.to || 0} dari ${data.total || 0} data`;
|
||||
}
|
||||
|
||||
function renderPagination(data) {
|
||||
const controls = document.getElementById('pagination-controls');
|
||||
controls.innerHTML = '';
|
||||
|
||||
const current = Number(data.current_page || 1);
|
||||
const last = Number(data.last_page || 1);
|
||||
const total = Number(data.total || 0);
|
||||
|
||||
if (total === 0) return;
|
||||
|
||||
const createButton = (label, page, disabled = false, active = false) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `page-link-custom ${active ? 'active' : ''}`;
|
||||
btn.innerHTML = label;
|
||||
btn.disabled = disabled;
|
||||
|
||||
btn.onclick = () => {
|
||||
if (!disabled && !active) fetchRiwayat(page);
|
||||
};
|
||||
|
||||
return btn;
|
||||
};
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-left"></i>', current - 1, current === 1));
|
||||
|
||||
let pages = [];
|
||||
|
||||
if (last <= 5) {
|
||||
for (let i = 1; i <= last; i++) pages.push(i);
|
||||
} else {
|
||||
pages.push(1);
|
||||
|
||||
if (current > 3) pages.push('...');
|
||||
|
||||
const start = Math.max(2, current - 1);
|
||||
const end = Math.min(last - 1, current + 1);
|
||||
|
||||
for (let i = start; i <= end; i++) pages.push(i);
|
||||
|
||||
if (current < last - 2) pages.push('...');
|
||||
|
||||
pages.push(last);
|
||||
}
|
||||
|
||||
pages.forEach(page => {
|
||||
if (page === '...') {
|
||||
controls.appendChild(createButton('...', current, true));
|
||||
} else {
|
||||
controls.appendChild(createButton(page, page, false, page === current));
|
||||
}
|
||||
});
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-right"></i>', current + 1, current === last));
|
||||
}
|
||||
|
||||
function renderObjectGrid(data, className) {
|
||||
if (!data) return '';
|
||||
|
||||
return Object.entries(data).map(([label, value]) => {
|
||||
return `
|
||||
<div class="${className}">
|
||||
<span>${escapeHtml(label)}</span>
|
||||
<strong>${escapeHtml(value)}</strong>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mengambil dan menampilkan detail riwayat di dalam Modal
|
||||
*/
|
||||
async function showDetail(id) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const modalBody = document.getElementById('modal-body-content');
|
||||
|
||||
// Tampilkan modal dengan state loading
|
||||
|
||||
const detailModal = new bootstrap.Modal(document.getElementById('detailModal'));
|
||||
detailModal.show();
|
||||
|
||||
|
||||
modalBody.innerHTML = `
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-spinner fa-spin text-primary fs-3 mb-2"></i>
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-spinner fa-spin text-success fs-3 mb-2"></i>
|
||||
<p class="text-muted m-0">Mengambil detail...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
// Memanggil API detail riwayat
|
||||
const response = await fetch(`/api/admin/riwayat/${id}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
|
|
@ -133,34 +373,85 @@
|
|||
|
||||
if (result.success) {
|
||||
const data = result.data;
|
||||
|
||||
// Render detail ke dalam modal
|
||||
|
||||
modalBody.innerHTML = `
|
||||
<div class="mb-3 border-bottom pb-2">
|
||||
<span class="text-muted d-block small mb-1">Nama Lengkap Siswa</span>
|
||||
<span class="fw-bold text-dark fs-5">${data.nama_siswa}</span>
|
||||
<div class="detail-result-card">
|
||||
<small>Hasil Rekomendasi Random Forest</small>
|
||||
<h4>${escapeHtml(data.hasil_rekomendasi)}</h4>
|
||||
</div>
|
||||
<div class="mb-3 border-bottom pb-2">
|
||||
<span class="text-muted d-block small mb-1">Email Siswa</span>
|
||||
<span class="text-dark">${data.email_siswa}</span>
|
||||
|
||||
<div class="detail-grid">
|
||||
<div class="detail-info-card">
|
||||
<span>Nama Siswa</span>
|
||||
<strong>${escapeHtml(data.nama_siswa)}</strong>
|
||||
</div>
|
||||
|
||||
<div class="detail-info-card">
|
||||
<span>Email Siswa</span>
|
||||
<strong>${escapeHtml(data.email_siswa)}</strong>
|
||||
</div>
|
||||
|
||||
<div class="detail-info-card">
|
||||
<span>Waktu Prediksi</span>
|
||||
<strong>${escapeHtml(data.tanggal)}</strong>
|
||||
</div>
|
||||
|
||||
<div class="detail-info-card">
|
||||
<span>Tingkat Keyakinan</span>
|
||||
<strong>${escapeHtml(formatPercent(data.akurasi_prediksi))}</strong>
|
||||
</div>
|
||||
|
||||
<div class="detail-info-card">
|
||||
<span>Minat Utama</span>
|
||||
<strong>${escapeHtml(data.minat_utama)}</strong>
|
||||
</div>
|
||||
|
||||
<div class="detail-info-card">
|
||||
<span>Rata-rata Nilai</span>
|
||||
<strong>${escapeHtml(data.rata_nilai)}</strong>
|
||||
</div>
|
||||
|
||||
<div class="detail-info-card">
|
||||
<span>Kondisi Ekonomi</span>
|
||||
<strong>${escapeHtml(data.kondisi_ekonomi)} · ${escapeHtml(formatRupiah(data.gaji_ortu))}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 border-bottom pb-2">
|
||||
<span class="text-muted d-block small mb-1">Waktu Prediksi Dibuat</span>
|
||||
<span class="text-dark">${data.tanggal}</span>
|
||||
|
||||
<h6 class="detail-section-title">Nilai Rapor Ringkas</h6>
|
||||
<div class="mini-data-grid">
|
||||
${renderObjectGrid(data.nilai_ringkas, 'mini-data-item')}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="text-muted d-block small mb-2">Hasil Rekomendasi Random Forest</span>
|
||||
<span class="badge bg-success px-3 py-2 rounded-pill fs-6 shadow-none">${data.hasil_rekomendasi}</span>
|
||||
|
||||
<h6 class="detail-section-title">Skor Minat RIASEC</h6>
|
||||
<div class="mini-data-grid">
|
||||
${renderObjectGrid(data.riasec, 'mini-data-item')}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
modalBody.innerHTML = `<div class="alert alert-danger m-0">Gagal memuat detail: ${result.message}</div>`;
|
||||
modalBody.innerHTML = `<div class="alert alert-danger m-0">Gagal memuat detail: ${escapeHtml(result.message)}</div>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching detail:', error);
|
||||
modalBody.innerHTML = `<div class="alert alert-danger m-0">Terjadi kesalahan jaringan saat mengambil detail.</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
fetchRiwayat(1);
|
||||
|
||||
document.getElementById('searchInput').addEventListener('input', function () {
|
||||
clearTimeout(searchTimer);
|
||||
|
||||
searchTimer = setTimeout(() => {
|
||||
currentSearch = this.value.trim();
|
||||
fetchRiwayat(1);
|
||||
}, 350);
|
||||
});
|
||||
|
||||
document.getElementById('perPageSelect').addEventListener('change', function () {
|
||||
perPage = this.value;
|
||||
fetchRiwayat(1);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endsection
|
||||
|
|
@ -5,22 +5,40 @@
|
|||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<div class="dashboard-section">
|
||||
<div class="mb-4 d-flex justify-content-between align-items-center">
|
||||
<div class="master-page-header">
|
||||
<div>
|
||||
<h4 class="fw-bold text-dark">Manajemen Data Siswa</h4>
|
||||
<p class="text-muted small">Daftar siswa SMAN 4 Jember yang terdaftar di sistem.</p>
|
||||
<h4 class="master-page-title">Manajemen Data Siswa</h4>
|
||||
<p class="master-page-subtitle">Daftar siswa SMAN 4 Jember yang terdaftar di sistem.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.siswa.create') }}" class="btn text-white px-4 shadow-sm" style="background-color: var(--primary-green); border-radius: 10px;">
|
||||
<i class="fas fa-plus me-2"></i>Tambah Siswa
|
||||
|
||||
<a href="{{ route('admin.siswa.create') }}" class="btn-add-modern">
|
||||
<i class="fas fa-plus"></i>
|
||||
Tambah Siswa
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="table-container-modern shadow-sm">
|
||||
<div class="table-toolbar">
|
||||
<div class="search-wrapper-modern">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="searchInput" class="search-input-modern" placeholder="Cari nama siswa, NISN, kelas, atau peminatan...">
|
||||
</div>
|
||||
|
||||
<div class="per-page-wrapper">
|
||||
<span>Tampilkan</span>
|
||||
<select id="perPageSelect" class="per-page-select">
|
||||
<option value="7" selected>7 data</option>
|
||||
<option value="5">5 data</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-modern mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center" width="80">ID</th>
|
||||
<th class="text-center" width="70">No</th>
|
||||
<th class="text-center" width="110">ID Siswa</th>
|
||||
<th>Nama Lengkap</th>
|
||||
<th>NISN</th>
|
||||
<th class="text-center">Kelas</th>
|
||||
|
|
@ -28,51 +46,125 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody id="siswa-table-body">
|
||||
</tbody>
|
||||
<tr>
|
||||
<td colspan="6" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data siswa...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pagination-container">
|
||||
<div class="text-muted small" id="pagination-info">
|
||||
Menampilkan 0 sampai 0 dari 0 data
|
||||
</div>
|
||||
<div class="d-flex gap-1" id="pagination-controls">
|
||||
</div>
|
||||
<div class="d-flex gap-1 flex-wrap" id="pagination-controls"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentSearch = '';
|
||||
let perPage = 7;
|
||||
let searchTimer = null;
|
||||
|
||||
function escapeHtml(value) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function getInitial(name) {
|
||||
const cleanName = name || 'S';
|
||||
return cleanName.charAt(0).toUpperCase();
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
document.getElementById('siswa-table-body').innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="loading-state-modern">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i> Memuat data siswa...
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
async function fetchSiswa(page = 1) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const tbody = document.getElementById('siswa-table-body');
|
||||
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-5">Memuat data...</td></tr>';
|
||||
|
||||
if (!token) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
currentPage = page;
|
||||
showLoading();
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: page,
|
||||
per_page: perPage
|
||||
});
|
||||
|
||||
if (currentSearch !== '') {
|
||||
params.append('search', currentSearch);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/siswa?page=${page}`, {
|
||||
const response = await fetch(`/api/admin/siswa?${params.toString()}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || result.success === false) {
|
||||
throw new Error(result.message || 'Gagal memuat data siswa.');
|
||||
}
|
||||
|
||||
const data = result.data || [];
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (!result.data || result.data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-5">Belum ada data siswa.</td></tr>';
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="empty-state-modern">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Tidak ada data siswa yang ditemukan.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
updatePaginationInfo(null);
|
||||
renderPagination({ current_page: 1, last_page: 1, total: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
result.data.forEach((item) => {
|
||||
const row = `
|
||||
data.forEach((item, index) => {
|
||||
tbody.innerHTML += `
|
||||
<tr>
|
||||
<td class="text-center fw-bold text-muted">${item.id_siswa}</td>
|
||||
<td class="fw-bold text-dark">${item.nama_lengkap}</td>
|
||||
<td class="text-muted">${item.nisn}</td>
|
||||
<td class="text-center text-muted fw-bold">${(result.from || 1) + index}</td>
|
||||
<td class="text-center fw-bold text-muted">${escapeHtml(item.id_siswa)}</td>
|
||||
<td>
|
||||
<div class="table-user-cell">
|
||||
<div class="avatar-circle">${getInitial(item.nama_lengkap)}</div>
|
||||
<div>
|
||||
<div class="fw-bold text-dark">${escapeHtml(item.nama_lengkap)}</div>
|
||||
<div class="text-cell-muted">Data siswa aktif</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-muted fw-semibold">${escapeHtml(item.nisn)}</td>
|
||||
<td class="text-center">
|
||||
<span class="badge rounded-pill badge-kelas">${item.kelas}</span>
|
||||
<span class="badge-soft badge-kelas">${escapeHtml(item.kelas)}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="opsi-wrapper">
|
||||
|
|
@ -84,69 +176,134 @@
|
|||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
tbody.innerHTML += row;
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
document.getElementById('pagination-info').innerText =
|
||||
`Menampilkan ${result.from || 0} sampai ${result.to || 0} dari ${result.total || 0} data`;
|
||||
|
||||
updatePaginationInfo(result);
|
||||
renderPagination(result);
|
||||
|
||||
} catch (error) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-5 text-danger">Gagal memuat data.</td></tr>';
|
||||
} catch (error) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="empty-state-modern text-danger">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
Gagal memuat data siswa.
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePaginationInfo(data) {
|
||||
const info = document.getElementById('pagination-info');
|
||||
|
||||
if (!data || !data.total) {
|
||||
info.innerText = 'Menampilkan 0 sampai 0 dari 0 data';
|
||||
return;
|
||||
}
|
||||
|
||||
info.innerText = `Menampilkan ${data.from || 0} sampai ${data.to || 0} dari ${data.total || 0} data`;
|
||||
}
|
||||
|
||||
function renderPagination(data) {
|
||||
const controls = document.getElementById('pagination-controls');
|
||||
controls.innerHTML = '';
|
||||
|
||||
const prevBtn = document.createElement('button');
|
||||
prevBtn.className = 'page-link-custom';
|
||||
prevBtn.innerHTML = '« Previous';
|
||||
prevBtn.disabled = data.current_page === 1;
|
||||
prevBtn.onclick = () => fetchSiswa(data.current_page - 1);
|
||||
controls.appendChild(prevBtn);
|
||||
const current = Number(data.current_page || 1);
|
||||
const last = Number(data.last_page || 1);
|
||||
const total = Number(data.total || 0);
|
||||
|
||||
const pageBtn = document.createElement('button');
|
||||
pageBtn.className = 'page-link-custom active';
|
||||
pageBtn.innerText = data.current_page || 1;
|
||||
controls.appendChild(pageBtn);
|
||||
if (total === 0) return;
|
||||
|
||||
const nextBtn = document.createElement('button');
|
||||
nextBtn.className = 'page-link-custom';
|
||||
nextBtn.innerHTML = 'Next »';
|
||||
nextBtn.disabled = data.current_page === data.last_page;
|
||||
nextBtn.onclick = () => fetchSiswa(data.current_page + 1);
|
||||
controls.appendChild(nextBtn);
|
||||
const createButton = (label, page, disabled = false, active = false) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `page-link-custom ${active ? 'active' : ''}`;
|
||||
btn.innerHTML = label;
|
||||
btn.disabled = disabled;
|
||||
btn.onclick = () => {
|
||||
if (!disabled && !active) fetchSiswa(page);
|
||||
};
|
||||
return btn;
|
||||
};
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-left"></i>', current - 1, current === 1));
|
||||
|
||||
let pages = [];
|
||||
if (last <= 5) {
|
||||
for (let i = 1; i <= last; i++) pages.push(i);
|
||||
} else {
|
||||
pages.push(1);
|
||||
if (current > 3) pages.push('...');
|
||||
const start = Math.max(2, current - 1);
|
||||
const end = Math.min(last - 1, current + 1);
|
||||
for (let i = start; i <= end; i++) pages.push(i);
|
||||
if (current < last - 2) pages.push('...');
|
||||
pages.push(last);
|
||||
}
|
||||
|
||||
pages.forEach(page => {
|
||||
if (page === '...') {
|
||||
controls.appendChild(createButton('...', current, true));
|
||||
} else {
|
||||
controls.appendChild(createButton(page, page, false, page === current));
|
||||
}
|
||||
});
|
||||
|
||||
controls.appendChild(createButton('<i class="fas fa-chevron-right"></i>', current + 1, current === last));
|
||||
}
|
||||
|
||||
async function deleteSiswa(id) {
|
||||
Swal.fire({
|
||||
const result = await Swal.fire({
|
||||
title: 'Hapus data?',
|
||||
text: "Data akan dihapus permanen!",
|
||||
text: 'Data siswa akan dihapus permanen!',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#ef4444',
|
||||
confirmButtonText: 'Ya, Hapus'
|
||||
}).then(async (result) => {
|
||||
if (result.isConfirmed) {
|
||||
const token = localStorage.getItem('access_token');
|
||||
try {
|
||||
const response = await fetch('/api/admin/siswa/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': 'Bearer ' + token, 'Accept': 'application/json' }
|
||||
});
|
||||
if (response.ok) {
|
||||
Swal.fire('Berhasil!', 'Data telah dihapus.', 'success');
|
||||
fetchSiswa();
|
||||
}
|
||||
} catch (error) { Swal.fire('Error!', 'Koneksi gagal.', 'error'); }
|
||||
}
|
||||
cancelButtonColor: '#64748b',
|
||||
confirmButtonText: 'Ya, Hapus',
|
||||
cancelButtonText: 'Batal'
|
||||
});
|
||||
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/siswa/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
Swal.fire('Berhasil!', 'Data siswa berhasil dihapus.', 'success');
|
||||
fetchSiswa(currentPage);
|
||||
} else {
|
||||
Swal.fire('Gagal!', 'Data siswa gagal dihapus.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
Swal.fire('Error!', 'Koneksi ke server gagal.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => fetchSiswa(1));
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
fetchSiswa(1);
|
||||
|
||||
document.getElementById('searchInput').addEventListener('input', function () {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => {
|
||||
currentSearch = this.value.trim();
|
||||
fetchSiswa(1);
|
||||
}, 350);
|
||||
});
|
||||
|
||||
document.getElementById('perPageSelect').addEventListener('change', function () {
|
||||
perPage = this.value;
|
||||
fetchSiswa(1);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -1,179 +1,643 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<html lang="id" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ASGARD - Rekomendasi Jurusan SMAN 4 Jember</title>
|
||||
|
||||
|
||||
<!-- Library -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="{{ asset('css/landingpage.css') }}" rel="stylesheet">
|
||||
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
|
||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body { font-family: 'Plus Jakarta Sans', sans-serif; }
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding-top: 95px;
|
||||
}
|
||||
|
||||
[x-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Background halaman */
|
||||
.soft-bg {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(16, 185, 129, 0.14), transparent 35%),
|
||||
radial-gradient(circle at bottom right, rgba(234, 179, 8, 0.10), transparent 35%),
|
||||
#ffffff;
|
||||
}
|
||||
|
||||
/* Teks gradasi */
|
||||
.text-gradient {
|
||||
background: linear-gradient(to right, #10b981, #059669);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
/* Jarak section */
|
||||
.section-space {
|
||||
padding-top: 90px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.section-space {
|
||||
padding-top: 70px;
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animasi gambar */
|
||||
.student-animation {
|
||||
animation: floatStudent 3.5s ease-in-out infinite;
|
||||
filter: drop-shadow(0 18px 18px rgba(6, 44, 34, 0.14));
|
||||
}
|
||||
|
||||
@keyframes floatStudent {
|
||||
0% {
|
||||
transform: translateY(0) rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-16px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0) rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Hover seluruh card */
|
||||
.full-hover-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.35s ease;
|
||||
}
|
||||
|
||||
.full-hover-card:hover {
|
||||
transform: translateY(-10px) scale(1.015);
|
||||
box-shadow: 0 25px 50px rgba(6, 44, 34, 0.16);
|
||||
}
|
||||
|
||||
/* Hover card putih */
|
||||
.card-white:hover {
|
||||
background-color: #ecfdf5 !important;
|
||||
border-color: #22c55e !important;
|
||||
}
|
||||
|
||||
/* Hover card hijau */
|
||||
.card-dark:hover {
|
||||
background-color: #022c22 !important;
|
||||
border-color: #34d399 !important;
|
||||
}
|
||||
|
||||
/* Hover card kuning */
|
||||
.card-yellow:hover {
|
||||
background-color: #facc15 !important;
|
||||
border-color: #eab308 !important;
|
||||
}
|
||||
|
||||
/* Efek isi card */
|
||||
.number-box,
|
||||
.card-icon,
|
||||
.card-title,
|
||||
.card-text {
|
||||
transition: all 0.35s ease;
|
||||
}
|
||||
|
||||
.full-hover-card:hover .number-box {
|
||||
background-color: #10b981;
|
||||
color: white;
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.full-hover-card:hover .card-icon {
|
||||
transform: scale(1.15) rotate(-6deg);
|
||||
}
|
||||
|
||||
.full-hover-card:hover .card-title {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.full-hover-card:hover .card-text {
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.card-dark:hover .card-title {
|
||||
color: #86efac;
|
||||
}
|
||||
|
||||
.card-dark:hover .card-text {
|
||||
color: #d1fae5;
|
||||
}
|
||||
|
||||
.card-yellow:hover .card-title {
|
||||
color: #064e3b;
|
||||
}
|
||||
|
||||
.card-yellow:hover .card-text {
|
||||
color: #064e3b;
|
||||
}
|
||||
|
||||
/* Hover seluruh FAQ */
|
||||
.faq-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.35s ease;
|
||||
}
|
||||
|
||||
.faq-card:hover {
|
||||
background-color: #ecfdf5 !important;
|
||||
border-color: #22c55e !important;
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 22px 40px rgba(6, 44, 34, 0.12);
|
||||
}
|
||||
|
||||
.faq-card:hover button {
|
||||
color: #059669;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body x-data="{ atTop: true }" @scroll.window="atTop = (window.pageYOffset > 50 ? false : true)" class="flex flex-col min-h-screen">
|
||||
|
||||
<nav class="fixed w-full z-50 transition-all duration-500 px-6 py-4" :class="atTop ? 'bg-transparent' : 'bg-white/90 backdrop-blur-xl shadow-lg border-b border-green-100'">
|
||||
<body
|
||||
x-data="{ atTop: true, openMenu: false }"
|
||||
@scroll.window="atTop = (window.pageYOffset > 50 ? false : true)"
|
||||
class="flex flex-col min-h-screen bg-white"
|
||||
>
|
||||
|
||||
<!-- NAVBAR -->
|
||||
<nav
|
||||
class="fixed w-full z-50 transition-all duration-500 px-6 py-4"
|
||||
:class="atTop ? 'bg-transparent' : 'bg-white/95 backdrop-blur-xl shadow-lg border-b border-green-100'"
|
||||
>
|
||||
<div class="max-w-7xl mx-auto flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="font-black text-2xl tracking-tighter text-emerald-900 italic">ASGARD<span class="text-green-500">.</span></span>
|
||||
</div>
|
||||
|
||||
<!-- Logo -->
|
||||
<a href="{{ route('landingpage') }}" class="flex items-center space-x-2">
|
||||
<span class="font-black text-2xl tracking-tighter text-emerald-900 italic">
|
||||
ASGARD<span class="text-green-500">.</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- Menu desktop -->
|
||||
<div class="hidden md:flex space-x-10 text-[11px] font-bold uppercase tracking-widest text-emerald-900">
|
||||
<a href="#tentang" class="hover:text-green-600 transition">Tentang</a>
|
||||
<a href="#fitur" class="hover:text-green-600 transition">Variabel</a>
|
||||
<a href="#faq" class="hover:text-green-600 transition">FAQ</a>
|
||||
<a href="{{ route('landingpage') }}#tentang" class="hover:text-green-600 transition">
|
||||
Tentang
|
||||
</a>
|
||||
|
||||
<a href="{{ route('landingpage') }}#cara-kerja" class="hover:text-green-600 transition">
|
||||
Cara Kerja
|
||||
</a>
|
||||
|
||||
<a href="{{ route('landingpage') }}#faq" class="hover:text-green-600 transition">
|
||||
FAQ
|
||||
</a>
|
||||
</div>
|
||||
<a href="{{ route('login') }}" class="bg-emerald-900 text-white px-8 py-3 rounded-full font-bold hover:bg-black hover:scale-105 transition-all">LOGIN</a>
|
||||
|
||||
<!-- Login desktop -->
|
||||
<div class="hidden md:block">
|
||||
<a
|
||||
href="{{ route('login') }}"
|
||||
class="bg-emerald-900 text-white px-8 py-3 rounded-full font-bold hover:bg-black hover:scale-105 transition-all"
|
||||
>
|
||||
LOGIN
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tombol mobile -->
|
||||
<button
|
||||
@click="openMenu = !openMenu"
|
||||
class="md:hidden text-emerald-900 font-black text-2xl"
|
||||
type="button"
|
||||
>
|
||||
☰
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Menu mobile -->
|
||||
<div
|
||||
x-show="openMenu"
|
||||
x-transition
|
||||
x-cloak
|
||||
class="md:hidden mt-4 bg-white rounded-3xl shadow-xl border border-green-100 p-5 space-y-4 text-sm font-bold uppercase tracking-widest text-emerald-900"
|
||||
>
|
||||
<a href="{{ route('landingpage') }}#tentang" @click="openMenu = false" class="block hover:text-green-600">
|
||||
Tentang
|
||||
</a>
|
||||
|
||||
<a href="{{ route('landingpage') }}#cara-kerja" @click="openMenu = false" class="block hover:text-green-600">
|
||||
Cara Kerja
|
||||
</a>
|
||||
|
||||
<a href="{{ route('landingpage') }}#faq" @click="openMenu = false" class="block hover:text-green-600">
|
||||
FAQ
|
||||
</a>
|
||||
|
||||
<a href="{{ route('login') }}" class="block text-center bg-emerald-900 text-white px-6 py-3 rounded-full">
|
||||
LOGIN
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="flex-grow">
|
||||
<header class="pt-32 pb-20 overflow-hidden">
|
||||
|
||||
<!-- HERO -->
|
||||
<header class="soft-bg pt-32 pb-24 overflow-hidden">
|
||||
<div class="max-w-7xl mx-auto px-6">
|
||||
|
||||
<div class="swiper heroSwiper" data-aos="fade-up">
|
||||
<div class="swiper-wrapper">
|
||||
|
||||
<div class="swiper-slide text-center py-12">
|
||||
<h1 class="text-6xl md:text-8xl font-black text-emerald-950 mb-6 leading-none tracking-tighter">
|
||||
Wujudkan Impianmu <br> Bersama <span class="text-gradient">ASGARD.</span>
|
||||
<h1 class="text-5xl md:text-8xl font-black text-emerald-950 mb-6 leading-none tracking-tighter">
|
||||
Wujudkan Impianmu <br>
|
||||
Bersama <span class="text-gradient">ASGARD.</span>
|
||||
</h1>
|
||||
|
||||
<p class="max-w-2xl mx-auto text-emerald-900/60 text-lg mb-10 font-medium">
|
||||
Sistem cerdas penentu masa depan siswa SMAN 4 Jember.
|
||||
Sistem rekomendasi jurusan kuliah untuk membantu siswa SMAN 4 Jember menentukan pilihan jurusan berdasarkan profil siswa.
|
||||
</p>
|
||||
<a href="{{ route('login') }}" class="bg-green-600 text-white px-12 py-5 rounded-2xl font-bold uppercase text-xs tracking-widest shadow-2xl active:scale-95 transition-all">Mulai Temukan Jurusan</a>
|
||||
|
||||
<a
|
||||
href="{{ route('login') }}"
|
||||
class="inline-block bg-green-600 text-white px-12 py-5 rounded-2xl font-bold uppercase text-xs tracking-widest shadow-2xl hover:bg-emerald-900 hover:-translate-y-1 active:scale-95 transition-all"
|
||||
>
|
||||
Mulai Temukan Jurusan
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="swiper-slide text-center py-12">
|
||||
<h2 class="text-5xl font-black text-emerald-950 mb-6">Kenapa ASGARD?</h2>
|
||||
<h2 class="text-5xl font-black text-emerald-950 mb-6">
|
||||
Kenali Potensimu Lebih Awal
|
||||
</h2>
|
||||
|
||||
<p class="max-w-xl mx-auto text-emerald-900/60 mb-8 text-lg italic leading-relaxed">
|
||||
"Membantu 87% mahasiswa yang sering merasa salah jurusan dengan bimbingan berbasis data objektif."
|
||||
ASGARD membantu siswa melihat kecocokan jurusan berdasarkan data nilai, minat, bakat, ekonomi, dan data pendukung alumni.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="swiper-pagination"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section id="tentang" class="py-24 px-6">
|
||||
<!-- TENTANG -->
|
||||
<section id="tentang" class="section-space px-6 bg-white">
|
||||
<div class="max-w-7xl mx-auto grid md:grid-cols-2 gap-16 items-center">
|
||||
|
||||
<div data-aos="fade-right">
|
||||
<h2 class="text-4xl font-black text-emerald-950 mb-8 uppercase italic tracking-tighter">Apa itu ASGARD?</h2>
|
||||
<p class="text-green-600 font-black uppercase tracking-[0.3em] text-xs mb-4">
|
||||
Tentang Sistem
|
||||
</p>
|
||||
|
||||
<h2 class="text-4xl font-black text-emerald-950 mb-8 uppercase italic tracking-tighter">
|
||||
Apa itu ASGARD?
|
||||
</h2>
|
||||
|
||||
<p class="text-slate-600 leading-relaxed text-lg mb-6">
|
||||
<strong>ASGARD</strong> adalah singkatan dari <em>AI System for Graduation & Academic Recommendation</em>. Ini adalah platform rekomendasi jurusan kuliah yang dirancang khusus untuk memandu siswa SMAN 4 Jember menemukan jalur karir terbaik.
|
||||
<strong>ASGARD</strong> adalah sistem rekomendasi jurusan kuliah yang dirancang untuk membantu siswa SMAN 4 Jember dalam menentukan pilihan jurusan yang sesuai dengan profil siswa.
|
||||
</p>
|
||||
|
||||
<p class="text-slate-600 leading-relaxed text-lg font-medium border-l-4 border-green-500 pl-6">
|
||||
Sistem ini tidak hanya menebak, tapi menghitung potensi diri berdasarkan variabel nyata yang ada pada profilmu.
|
||||
Sistem ini memanfaatkan data siswa sebagai dasar analisis, sehingga rekomendasi yang diberikan tidak hanya berdasarkan perkiraan, tetapi berdasarkan pola data yang telah diproses oleh sistem.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Gambar animasi -->
|
||||
<div class="flex justify-center" data-aos="zoom-in">
|
||||
<img src="https://illustrations.popsy.co/emerald/student-going-to-school.svg" alt="Student" class="w-full max-w-sm animate-float">
|
||||
<img
|
||||
src="https://illustrations.popsy.co/emerald/student-going-to-school.svg"
|
||||
alt="Ilustrasi siswa"
|
||||
class="student-animation w-full max-w-sm"
|
||||
>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="fitur" class="py-24 px-6 bg-green-50/50">
|
||||
<!-- CARA KERJA -->
|
||||
<section id="cara-kerja" class="section-space px-6 bg-green-50/50">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl font-black text-emerald-950 italic uppercase">Variabel Analisis Kami</h2>
|
||||
<p class="text-emerald-900/50 font-bold tracking-widest text-[10px] mt-2 uppercase">Dasar Prediksi Rekomendasi</p>
|
||||
|
||||
<!-- Judul cara kerja -->
|
||||
<div class="text-center mb-14">
|
||||
<p class="text-green-600 font-black uppercase tracking-[0.3em] text-xs mb-4">
|
||||
Alur Sistem
|
||||
</p>
|
||||
|
||||
<h2 class="text-4xl font-black text-emerald-950 italic uppercase">
|
||||
Cara Kerja ASGARD
|
||||
</h2>
|
||||
|
||||
<p class="max-w-2xl mx-auto text-emerald-900/60 font-medium mt-4">
|
||||
Sistem bekerja dengan mengolah data profil siswa, kemudian menghasilkan rekomendasi jurusan kuliah yang paling sesuai.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Card alur sistem -->
|
||||
<div class="grid md:grid-cols-3 gap-6 mb-16">
|
||||
|
||||
<div class="full-hover-card card-white bg-white rounded-3xl p-8 shadow-sm border border-emerald-100" data-aos="fade-up">
|
||||
<div class="number-box w-12 h-12 rounded-2xl bg-green-100 text-green-700 flex items-center justify-center font-black mb-6">
|
||||
1
|
||||
</div>
|
||||
|
||||
<h3 class="card-title font-black text-emerald-950 text-xl mb-3">
|
||||
Siswa Mengisi Data
|
||||
</h3>
|
||||
|
||||
<p class="card-text text-slate-500 leading-relaxed text-sm">
|
||||
Siswa mengisi data yang dibutuhkan, seperti nilai rapor, minat dan bakat, serta informasi pendukung lainnya.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="full-hover-card card-dark bg-emerald-900 rounded-3xl p-8 shadow-xl text-white border border-emerald-900" data-aos="fade-up" data-aos-delay="100">
|
||||
<div class="number-box w-12 h-12 rounded-2xl bg-green-500 text-white flex items-center justify-center font-black mb-6">
|
||||
2
|
||||
</div>
|
||||
|
||||
<h3 class="card-title font-black text-xl mb-3 text-white">
|
||||
Sistem Memproses Data
|
||||
</h3>
|
||||
|
||||
<p class="card-text text-emerald-100/70 leading-relaxed text-sm">
|
||||
Data yang sudah dimasukkan akan diproses oleh sistem menggunakan model Random Forest untuk membaca pola kecocokan jurusan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="full-hover-card card-white bg-white rounded-3xl p-8 shadow-sm border border-emerald-100" data-aos="fade-up" data-aos-delay="200">
|
||||
<div class="number-box w-12 h-12 rounded-2xl bg-green-100 text-green-700 flex items-center justify-center font-black mb-6">
|
||||
3
|
||||
</div>
|
||||
|
||||
<h3 class="card-title font-black text-emerald-950 text-xl mb-3">
|
||||
Rekomendasi Ditampilkan
|
||||
</h3>
|
||||
|
||||
<p class="card-text text-slate-500 leading-relaxed text-sm">
|
||||
Sistem menampilkan hasil rekomendasi jurusan kuliah yang dapat digunakan siswa sebagai bahan pertimbangan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Judul data analisis -->
|
||||
<div class="text-center mb-10">
|
||||
<h3 class="text-2xl font-black text-emerald-950 italic uppercase">
|
||||
Data yang Dianalisis
|
||||
</h3>
|
||||
|
||||
<p class="text-emerald-900/50 font-bold tracking-widest text-[10px] mt-2 uppercase">
|
||||
Dasar Pembentukan Rekomendasi
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Card data analisis -->
|
||||
<div class="grid md:grid-cols-4 gap-6">
|
||||
<div class="bg-white rounded-3xl p-10 flex flex-col justify-between h-80 shadow-sm border border-emerald-100" data-aos="fade-up">
|
||||
<span class="text-4xl">📝</span>
|
||||
|
||||
<div class="full-hover-card card-white bg-white rounded-3xl p-10 flex flex-col justify-between h-80 shadow-sm border border-emerald-100" data-aos="fade-up">
|
||||
<span class="card-icon text-4xl">📝</span>
|
||||
|
||||
<div>
|
||||
<h4 class="font-black text-emerald-950 mb-2 uppercase">Nilai Rapor</h4>
|
||||
<p class="text-sm text-slate-500 leading-relaxed">Analisis performa akademikmu selama 5 semester terakhir.</p>
|
||||
<h4 class="card-title font-black text-emerald-950 mb-2 uppercase">
|
||||
Nilai Rapor
|
||||
</h4>
|
||||
|
||||
<p class="card-text text-sm text-slate-500 leading-relaxed">
|
||||
Digunakan untuk melihat kemampuan akademik siswa pada beberapa mata pelajaran.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-emerald-900 rounded-3xl p-10 flex flex-col justify-between h-80 text-white shadow-xl" data-aos="fade-up" data-aos-delay="100">
|
||||
<span class="text-4xl">🧠</span>
|
||||
|
||||
<div class="full-hover-card card-dark bg-emerald-900 rounded-3xl p-10 flex flex-col justify-between h-80 text-white shadow-xl border border-emerald-900" data-aos="fade-up" data-aos-delay="100">
|
||||
<span class="card-icon text-4xl">🧠</span>
|
||||
|
||||
<div>
|
||||
<h4 class="font-black mb-2 uppercase text-green-400">Minat & Bakat</h4>
|
||||
<p class="text-sm text-emerald-100/60 leading-relaxed">Memetakan kecenderungan bakatmu melalui kuesioner psikologi.</p>
|
||||
<h4 class="card-title font-black mb-2 uppercase text-green-400">
|
||||
Minat & Bakat
|
||||
</h4>
|
||||
|
||||
<p class="card-text text-sm text-emerald-100/60 leading-relaxed">
|
||||
Digunakan untuk mengetahui kecenderungan minat siswa terhadap bidang tertentu.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-yellow-400 rounded-3xl p-10 flex flex-col justify-between h-80 shadow-sm" data-aos="fade-up" data-aos-delay="200">
|
||||
<span class="text-4xl">💰</span>
|
||||
|
||||
<div class="full-hover-card card-yellow bg-yellow-400 rounded-3xl p-10 flex flex-col justify-between h-80 shadow-sm border border-yellow-400" data-aos="fade-up" data-aos-delay="200">
|
||||
<span class="card-icon text-4xl">💰</span>
|
||||
|
||||
<div>
|
||||
<h4 class="font-black text-emerald-900 mb-2 uppercase">Kondisi Ekonomi</h4>
|
||||
<p class="text-sm text-emerald-900/60 leading-relaxed font-bold">Menyesuaikan pilihan jurusan dengan kesiapan finansial keluarga.</p>
|
||||
<h4 class="card-title font-black text-emerald-900 mb-2 uppercase">
|
||||
Ekonomi
|
||||
</h4>
|
||||
|
||||
<p class="card-text text-sm text-emerald-900/70 leading-relaxed font-bold">
|
||||
Digunakan sebagai data pendukung dalam menyesuaikan pilihan jurusan dengan kondisi keluarga.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-3xl p-10 flex flex-col justify-between h-80 shadow-sm border border-emerald-100" data-aos="fade-up" data-aos-delay="300">
|
||||
<span class="text-4xl">🎓</span>
|
||||
|
||||
<div class="full-hover-card card-white bg-white rounded-3xl p-10 flex flex-col justify-between h-80 shadow-sm border border-emerald-100" data-aos="fade-up" data-aos-delay="300">
|
||||
<span class="card-icon text-4xl">🎓</span>
|
||||
|
||||
<div>
|
||||
<h4 class="font-black text-emerald-950 mb-2 uppercase">Data Alumni</h4>
|
||||
<p class="text-sm text-slate-500 leading-relaxed">Dicocokkan dengan tingkat keberhasilan alumni SMAPA di berbagai PTN.</p>
|
||||
<h4 class="card-title font-black text-emerald-950 mb-2 uppercase">
|
||||
Data Alumni
|
||||
</h4>
|
||||
|
||||
<p class="card-text text-sm text-slate-500 leading-relaxed">
|
||||
Digunakan sebagai data pendukung untuk melihat pola jurusan dari alumni sebelumnya.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="faq" class="py-24 px-6">
|
||||
<!-- FAQ -->
|
||||
<section id="faq" class="section-space px-6 bg-white">
|
||||
<div class="max-w-3xl mx-auto" x-data="{ active: 1 }">
|
||||
<h2 class="text-center text-4xl font-black text-emerald-950 mb-16 italic">Pertanyaan Umum</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-white rounded-3xl border border-green-100 overflow-hidden shadow-sm">
|
||||
<button @click="active = (active === 1 ? null : 1)" class="w-full px-8 py-6 text-left font-bold text-emerald-900 flex justify-between items-center">
|
||||
<span>Bagaimana cara kerja ASGARD?</span>
|
||||
<svg class="w-5 h-5 transition-transform" :class="active === 1 ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M19 9l-7 7-7-7"></path></svg>
|
||||
</button>
|
||||
<div x-show="active === 1" class="px-8 pb-6 text-slate-500 leading-relaxed">ASGARD memproses semua variabel profilmu menggunakan algoritma AI untuk menghasilkan persentase kecocokan jurusan yang paling optimal.</div>
|
||||
</div>
|
||||
|
||||
<!-- Judul FAQ -->
|
||||
<div class="text-center mb-14">
|
||||
<p class="text-green-600 font-black uppercase tracking-[0.3em] text-xs mb-4">
|
||||
FAQ
|
||||
</p>
|
||||
|
||||
<h2 class="text-4xl font-black text-emerald-950 italic">
|
||||
Pertanyaan Umum
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Isi FAQ -->
|
||||
<div class="space-y-4">
|
||||
|
||||
<div class="faq-card bg-white rounded-3xl border border-green-100 overflow-hidden shadow-sm">
|
||||
<button
|
||||
@click="active = (active === 1 ? null : 1)"
|
||||
class="w-full px-8 py-6 text-left font-bold text-emerald-900 flex justify-between items-center transition"
|
||||
type="button"
|
||||
>
|
||||
<span>Bagaimana cara kerja ASGARD?</span>
|
||||
|
||||
<svg class="w-5 h-5 transition-transform" :class="active === 1 ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div
|
||||
x-show="active === 1"
|
||||
x-transition
|
||||
class="px-8 pb-6 text-slate-500 leading-relaxed"
|
||||
>
|
||||
ASGARD memproses data profil siswa, seperti nilai rapor, minat dan bakat, ekonomi, serta data pendukung alumni. Data tersebut kemudian dianalisis oleh sistem untuk menghasilkan rekomendasi jurusan kuliah.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="faq-card bg-white rounded-3xl border border-green-100 overflow-hidden shadow-sm">
|
||||
<button
|
||||
@click="active = (active === 2 ? null : 2)"
|
||||
class="w-full px-8 py-6 text-left font-bold text-emerald-900 flex justify-between items-center transition"
|
||||
type="button"
|
||||
>
|
||||
<span>Apakah hasil rekomendasi harus diikuti sepenuhnya?</span>
|
||||
|
||||
<svg class="w-5 h-5 transition-transform" :class="active === 2 ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div
|
||||
x-show="active === 2"
|
||||
x-transition
|
||||
class="px-8 pb-6 text-slate-500 leading-relaxed"
|
||||
>
|
||||
Tidak. Hasil rekomendasi digunakan sebagai bahan pertimbangan. Siswa tetap dapat berdiskusi dengan guru BK, orang tua, atau wali kelas sebelum menentukan pilihan jurusan.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="faq-card bg-white rounded-3xl border border-green-100 overflow-hidden shadow-sm">
|
||||
<button
|
||||
@click="active = (active === 3 ? null : 3)"
|
||||
class="w-full px-8 py-6 text-left font-bold text-emerald-900 flex justify-between items-center transition"
|
||||
type="button"
|
||||
>
|
||||
<span>Siapa yang dapat menggunakan sistem ini?</span>
|
||||
|
||||
<svg class="w-5 h-5 transition-transform" :class="active === 3 ? 'rotate-180' : ''" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div
|
||||
x-show="active === 3"
|
||||
x-transition
|
||||
class="px-8 pb-6 text-slate-500 leading-relaxed"
|
||||
>
|
||||
Sistem ini digunakan oleh siswa dan admin sekolah. Siswa dapat melihat rekomendasi jurusan, sedangkan admin dapat mengelola data yang dibutuhkan oleh sistem.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="bg-[#062C22] text-white py-16 px-6">
|
||||
<div class="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">ASGARD.</span>
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">
|
||||
ASGARD.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="text-slate-300 text-sm leading-relaxed max-w-xs">
|
||||
Sistem pakar rekomendasi jurusan kuliah SMAN 4 Jember berbasis data-driven.
|
||||
Sistem rekomendasi jurusan kuliah SMAN 4 Jember berbasis data.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6 italic">Informasi</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6 italic">
|
||||
Informasi
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li><a href="{{ route('panduan') }}" class="hover:text-white transition">Panduan Sistem</a></li>
|
||||
<li><a href="#" class="hover:text-white transition">Kebijakan Privasi</a></li>
|
||||
<li>
|
||||
<a href="{{ route('panduan') }}" class="hover:text-white transition">
|
||||
Panduan Sistem
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="{{ route('landingpage') }}#faq" class="hover:text-white transition">
|
||||
FAQ
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6 italic">Sekolah</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6 italic">
|
||||
Sekolah
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li>SMAN 4 Jember</li>
|
||||
<li>Jl. Wolter Monginsidi No.24</li>
|
||||
<li>Jember, Jawa Timur</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto mt-16 pt-8 border-t border-white/10 text-center">
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.5em] text-slate-400">© 2026 GALANG SEFIAN ADJI - POLITEKNIK NEGERI JEMBER</p>
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.5em] text-slate-400">
|
||||
© 2026 GALANG SEFIAN ADJI - POLITEKNIK NEGERI JEMBER
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Script animasi -->
|
||||
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
|
||||
|
||||
<!-- Script slider -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
AOS.init({ duration: 1000, once: true });
|
||||
// Animasi saat scroll
|
||||
AOS.init({
|
||||
duration: 1000,
|
||||
once: true
|
||||
});
|
||||
|
||||
// Slider pada hero
|
||||
new Swiper(".heroSwiper", {
|
||||
pagination: { el: ".swiper-pagination", clickable: true },
|
||||
autoplay: { delay: 4000 },
|
||||
pagination: {
|
||||
el: ".swiper-pagination",
|
||||
clickable: true
|
||||
},
|
||||
autoplay: {
|
||||
delay: 4000
|
||||
},
|
||||
loop: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -4,153 +4,250 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Panduan Sistem - ASGARD</title>
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #f8fafc; }
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col min-h-screen">
|
||||
|
||||
<!-- NAVBAR -->
|
||||
<nav class="bg-white border-b border-slate-100 px-6 py-4 mb-10 relative z-50">
|
||||
<div class="max-w-7xl mx-auto flex justify-between items-center">
|
||||
<a href="{{ url('/') }}" class="beranda-link flex items-center gap-4 group">
|
||||
<div class="w-10 h-10 bg-emerald-100 rounded-xl flex items-center justify-center text-emerald-600 font-bold transition-all duration-300 group-hover:bg-emerald-600 group-hover:text-white">AS</div>
|
||||
<span class="font-black text-xl tracking-tighter text-emerald-950 uppercase italic">ASGARD.</span>
|
||||
</a>
|
||||
|
||||
<div class="flex items-center gap-8">
|
||||
<div class="hidden md:flex gap-8 text-[11px] font-bold uppercase tracking-widest text-slate-400">
|
||||
<a href="{{ url('/') }}" class="beranda-link hover:text-emerald-600 transition-colors">Beranda</a>
|
||||
<a href="{{ route('panduan') }}" class="text-emerald-600">Panduan</a>
|
||||
|
||||
<!-- Logo ASGARD -->
|
||||
<a href="{{ url('/') }}" id="logo-link" class="flex items-center gap-4 group">
|
||||
<div class="w-10 h-10 bg-emerald-100 rounded-xl flex items-center justify-center text-emerald-600 font-bold transition-all duration-300 group-hover:bg-emerald-600 group-hover:text-white">
|
||||
AS
|
||||
</div>
|
||||
<a href="{{ route('login') }}" id="login-btn" class="bg-emerald-600 text-white text-[11px] font-bold uppercase tracking-widest px-6 py-3 rounded-xl hover:bg-emerald-700 hover:-translate-y-0.5 transition-all duration-300 shadow-lg shadow-emerald-600/30">
|
||||
|
||||
<span class="font-black text-xl tracking-tighter text-emerald-950 uppercase italic">
|
||||
ASGARD.
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
|
||||
<div class="hidden md:flex items-center gap-4 text-[11px] font-bold uppercase tracking-widest">
|
||||
|
||||
<!-- Tombol kembali ke landing page: putih, hover hijau -->
|
||||
<a href="{{ url('/') }}" id="landing-menu" class="bg-white text-emerald-700 border border-emerald-600 px-6 py-3 rounded-xl hover:bg-emerald-600 hover:text-white hover:-translate-y-0.5 transition-all duration-300 shadow-md shadow-emerald-900/10">
|
||||
Kembali ke Landing Page
|
||||
</a>
|
||||
|
||||
<!-- Tombol beranda muncul jika pengguna sudah login -->
|
||||
<a href="#" id="beranda-menu" class="hidden bg-white text-emerald-700 border border-emerald-600 px-6 py-3 rounded-xl hover:bg-emerald-600 hover:text-white hover:-translate-y-0.5 transition-all duration-300 shadow-md shadow-emerald-900/10">
|
||||
Beranda
|
||||
</a>
|
||||
|
||||
<!-- Menu aktif halaman panduan -->
|
||||
<!-- <a href="{{ route('panduan') }}" class="text-emerald-600 px-2 py-3">
|
||||
Panduan
|
||||
</a> -->
|
||||
</div>
|
||||
|
||||
<!-- Tombol masuk sistem: hijau, hover putih -->
|
||||
<a href="{{ route('login') }}" id="login-btn" class="bg-emerald-600 text-white border border-emerald-600 text-[11px] font-bold uppercase tracking-widest px-6 py-3 rounded-xl hover:bg-white hover:text-emerald-700 hover:-translate-y-0.5 transition-all duration-300 shadow-lg shadow-emerald-600/30">
|
||||
Masuk Sistem →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div class="max-w-4xl mx-auto px-6 flex-grow mb-20 w-full relative z-10">
|
||||
|
||||
|
||||
<div class="mb-12 text-center md:text-left">
|
||||
<div class="inline-flex items-center gap-2 bg-slate-50 text-emerald-700 px-4 py-2 rounded-full font-bold text-xs uppercase tracking-widest mb-4 border border-slate-200">
|
||||
<span>📚</span> Informasi Terbuka
|
||||
</div>
|
||||
<h1 class="text-4xl md:text-5xl font-black text-slate-900 tracking-tight mb-4">Panduan Penggunaan ASGARD</h1>
|
||||
|
||||
<h1 class="text-4xl md:text-5xl font-black text-slate-900 tracking-tight mb-4">
|
||||
Panduan Penggunaan ASGARD
|
||||
</h1>
|
||||
|
||||
<p class="text-slate-500 text-lg leading-relaxed">
|
||||
ASGARD hadir untuk membantu memetakan karir masa depan siswa SMAN 4 Jember. Pelajari 9 langkah alur kerjanya di bawah ini.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-5">
|
||||
|
||||
|
||||
<!-- Langkah 1 -->
|
||||
<div class="group bg-white border border-slate-200 p-8 rounded-3xl flex flex-col md:flex-row gap-6 transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-900/5 hover:-translate-y-1 cursor-default">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">1</div>
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">
|
||||
1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">Pendataan oleh Guru BK (Admin)</h3>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">
|
||||
Pendataan oleh Guru BK (Admin)
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-500 leading-relaxed transition-colors duration-300 group-hover:text-slate-600">
|
||||
Siswa tidak perlu melakukan registrasi akun secara mandiri. Data dasar siswa dan pembuatan akun akan dilakukan sepenuhnya oleh Guru BK (Admin) ke dalam sistem untuk menjaga validitas data.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Langkah 2 -->
|
||||
<div class="group bg-white border border-slate-200 p-8 rounded-3xl flex flex-col md:flex-row gap-6 transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-900/5 hover:-translate-y-1 cursor-default">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">2</div>
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">
|
||||
2
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">Akses & Login Akun Siswa</h3>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">
|
||||
Akses & Login Akun Siswa
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-500 leading-relaxed transition-colors duration-300 group-hover:text-slate-600">
|
||||
Siswa dapat masuk ke sistem melalui menu <strong>Login</strong> menggunakan kredensial (seperti email/NISN) dan kata sandi yang telah diberikan oleh Guru BK.
|
||||
Siswa dapat masuk ke sistem melalui menu <strong>Login</strong> menggunakan email/NISN dan kata sandi yang telah diberikan oleh Guru BK.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Langkah 3 -->
|
||||
<div class="group bg-white border border-slate-200 p-8 rounded-3xl flex flex-col md:flex-row gap-6 transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-900/5 hover:-translate-y-1 cursor-default">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">3</div>
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">
|
||||
3
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">Pengisian Data Ekonomi Keluarga</h3>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">
|
||||
Pengisian Data Ekonomi Keluarga
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-500 leading-relaxed transition-colors duration-300 group-hover:text-slate-600">
|
||||
Langkah pertama setelah masuk adalah menginput rentang penghasilan (gaji) orang tua. Sistem membutuhkan data ini untuk memetakan kelompok kemampuan ekonomi agar rekomendasi jurusan sesuai dengan kesiapan finansial.
|
||||
Langkah pertama setelah masuk adalah menginput rentang penghasilan orang tua. Sistem membutuhkan data ini untuk memetakan kelompok kemampuan ekonomi agar rekomendasi jurusan sesuai dengan kesiapan finansial.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Langkah 4 -->
|
||||
<div class="group bg-white border border-slate-200 p-8 rounded-3xl flex flex-col md:flex-row gap-6 transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-900/5 hover:-translate-y-1 cursor-default">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">4</div>
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">
|
||||
4
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">Pengisian Kuesioner Minat & Bakat</h3>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">
|
||||
Pengisian Kuesioner Minat & Bakat
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-500 leading-relaxed transition-colors duration-300 group-hover:text-slate-600">
|
||||
Selanjutnya, siswa diwajibkan untuk mengisi instrumen kuesioner psikologi untuk memetakan kecenderungan minat dan bakat secara jujur. Data ini dijamin kerahasiaannya.
|
||||
Selanjutnya, siswa mengisi kuesioner minat dan bakat untuk membantu sistem mengetahui kecenderungan bidang yang sesuai dengan ketertarikan siswa.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Langkah 5 -->
|
||||
<div class="group bg-white border border-slate-200 p-8 rounded-3xl flex flex-col md:flex-row gap-6 transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-900/5 hover:-translate-y-1 cursor-default">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">5</div>
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">
|
||||
5
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">Pengisian Form Nilai Rapor</h3>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">
|
||||
Pengisian Form Nilai Rapor
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-500 leading-relaxed transition-colors duration-300 group-hover:text-slate-600">
|
||||
Tahap input terakhir adalah mengisi rata-rata nilai dari 15 mata pelajaran selama semester 1 hingga semester 5 dengan akurat sebagai indikator performa akademik utama.
|
||||
Tahap input terakhir adalah mengisi rata-rata nilai dari 15 mata pelajaran selama semester 1 hingga semester 5 dengan akurat sebagai indikator kemampuan akademik siswa.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Langkah 6 -->
|
||||
<div class="group bg-white border border-slate-200 p-8 rounded-3xl flex flex-col md:flex-row gap-6 transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-900/5 hover:-translate-y-1 cursor-default">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">6</div>
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">
|
||||
6
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800 flex items-center gap-2">
|
||||
Pemrosesan Algoritma Random Forest <span class="text-xl group-hover:animate-pulse">🤖</span>
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-500 leading-relaxed transition-colors duration-300 group-hover:text-slate-600">
|
||||
Sistem mulai bekerja menganalisis seluruh variabel data. Algoritma <em>Machine Learning</em> akan mencocokkan profilmu dengan database historis alumni SMAPA yang telah teruji tingkat kesuksesannya.
|
||||
Sistem mulai menganalisis data ekonomi, minat dan bakat, serta nilai rapor siswa. Data tersebut kemudian diproses menggunakan algoritma Random Forest untuk menghasilkan rekomendasi jurusan.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Langkah 7 -->
|
||||
<div class="group bg-white border border-slate-200 p-8 rounded-3xl flex flex-col md:flex-row gap-6 transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-900/5 hover:-translate-y-1 cursor-default">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">7</div>
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">
|
||||
7
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">Penerbitan Hasil Rekomendasi</h3>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">
|
||||
Penerbitan Hasil Rekomendasi
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-500 leading-relaxed transition-colors duration-300 group-hover:text-slate-600">
|
||||
Sistem akan menampilkan hasil berupa daftar program studi atau jurusan di Perguruan Tinggi yang memiliki persentase kecocokan paling tinggi dengan profil pribadimu.
|
||||
Sistem akan menampilkan hasil berupa jurusan kuliah yang paling sesuai dengan profil siswa berdasarkan data yang telah diisi sebelumnya.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Langkah 8 -->
|
||||
<div class="group bg-white border border-slate-200 p-8 rounded-3xl flex flex-col md:flex-row gap-6 transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-900/5 hover:-translate-y-1 cursor-default">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">8</div>
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">
|
||||
8
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">Eksplorasi Prospek Karir</h3>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">
|
||||
Eksplorasi Prospek Karir
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-500 leading-relaxed transition-colors duration-300 group-hover:text-slate-600">
|
||||
Kamu tidak hanya mendapat nama jurusan, tapi juga dapat melihat informasi lengkap terkait peluang kerja, jalur profesi, dan relevansi industri dari jurusan yang direkomendasikan tersebut.
|
||||
Siswa tidak hanya mendapatkan nama jurusan, tetapi juga dapat melihat informasi mengenai peluang kerja dan prospek karir dari jurusan yang direkomendasikan.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Langkah 9 -->
|
||||
<div class="group bg-white border border-slate-200 p-8 rounded-3xl flex flex-col md:flex-row gap-6 transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-900/5 hover:-translate-y-1 cursor-default">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">9</div>
|
||||
<div class="w-14 h-14 bg-slate-50 border border-slate-100 text-slate-400 rounded-2xl flex items-center justify-center text-2xl font-black transition-all duration-300 group-hover:bg-emerald-600 group-hover:border-emerald-600 group-hover:text-white group-hover:shadow-lg group-hover:shadow-emerald-600/30">
|
||||
9
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">Konseling & Tindak Lanjut</h3>
|
||||
<h3 class="text-xl font-bold text-slate-800 mb-2 transition-colors duration-300 group-hover:text-emerald-800">
|
||||
Konseling & Tindak Lanjut
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-500 leading-relaxed transition-colors duration-300 group-hover:text-slate-600">
|
||||
Hasil prediksi AI ini bersifat sebagai panduan objektif. Langkah terakhir, sangat disarankan untuk membawa hasil laporan sistem ini ke ruang BK untuk sesi konsultasi tatap muka guna memantapkan strategi.
|
||||
Hasil rekomendasi dapat digunakan sebagai bahan pertimbangan awal. Siswa tetap disarankan melakukan konsultasi dengan Guru BK untuk memantapkan pilihan jurusan kuliah.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -158,54 +255,164 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="bg-[#062C22] text-white py-16 px-6 mt-auto w-full relative z-10">
|
||||
<div class="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">ASGARD.</span>
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">
|
||||
ASGARD.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="text-slate-300 text-sm leading-relaxed max-w-xs">
|
||||
Sistem pakar rekomendasi jurusan kuliah SMAN 4 Jember berbasis data-driven.
|
||||
Sistem rekomendasi jurusan kuliah SMAN 4 Jember berbasis data.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">Informasi</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">
|
||||
Informasi
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li><a href="{{ route('panduan') }}" class="text-emerald-400 font-bold hover:text-white transition-colors">Panduan Sistem</a></li>
|
||||
<li><a href="#" class="hover:text-white transition-colors">Kebijakan Privasi</a></li>
|
||||
<li>
|
||||
<a href="{{ route('panduan') }}" class="text-emerald-400 font-bold hover:text-white transition-colors">
|
||||
Panduan Sistem
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- <li>
|
||||
<a href="#" class="hover:text-white transition-colors">
|
||||
Kebijakan Privasi
|
||||
</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">Sekolah</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">
|
||||
Sekolah
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li>SMAN 4 Jember</li>
|
||||
<li>Jl. Wolter Monginsidi No.24</li>
|
||||
<li>Jember, Jawa Timur</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto mt-16 pt-8 border-t border-white/10 text-center">
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.5em] text-slate-400">© 2026 GALANG SEFIAN ADJI - POLITEKNIK NEGERI JEMBER</p>
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.5em] text-slate-400">
|
||||
© 2026 GALANG SEFIAN ADJI - POLITEKNIK NEGERI JEMBER
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- SCRIPT NAVBAR LOGIN CHECK -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const berandaLinks = document.querySelectorAll('.beranda-link');
|
||||
|
||||
const role =
|
||||
localStorage.getItem('role') ||
|
||||
localStorage.getItem('user_role') ||
|
||||
getRoleFromUserObject();
|
||||
|
||||
const logoLink = document.getElementById('logo-link');
|
||||
const landingMenu = document.getElementById('landing-menu');
|
||||
const berandaMenu = document.getElementById('beranda-menu');
|
||||
const loginBtn = document.getElementById('login-btn');
|
||||
|
||||
const landingUrl = "{{ url('/') }}";
|
||||
const loginUrl = "{{ route('login') }}";
|
||||
|
||||
// URL dashboard siswa
|
||||
const siswaDashboardUrl = "{{ route('student.profile') }}";
|
||||
|
||||
// URL dashboard admin
|
||||
// Jika dashboard admin kamu berbeda, cukup ubah bagian ini saja.
|
||||
const adminDashboardUrl = "{{ url('/admin/dashboard') }}";
|
||||
|
||||
let dashboardUrl = siswaDashboardUrl;
|
||||
|
||||
if (role && role.toLowerCase() === 'admin') {
|
||||
dashboardUrl = adminDashboardUrl;
|
||||
}
|
||||
|
||||
if (token) {
|
||||
berandaLinks.forEach(link => {
|
||||
link.href = "{{ route('student.profile') }}";
|
||||
});
|
||||
|
||||
// Jika sudah login:
|
||||
// tombol kembali ke landing page disembunyikan
|
||||
if (landingMenu) {
|
||||
landingMenu.classList.add('hidden');
|
||||
}
|
||||
|
||||
// menu beranda muncul dan menuju dashboard sesuai role
|
||||
if (berandaMenu) {
|
||||
berandaMenu.classList.remove('hidden');
|
||||
berandaMenu.href = dashboardUrl;
|
||||
}
|
||||
|
||||
// logo menuju dashboard
|
||||
if (logoLink) {
|
||||
logoLink.href = dashboardUrl;
|
||||
}
|
||||
|
||||
// tombol masuk sistem berubah menjadi ke dashboard
|
||||
if (loginBtn) {
|
||||
loginBtn.innerText = "Ke Dashboard →";
|
||||
loginBtn.href = "{{ route('student.profile') }}";
|
||||
loginBtn.href = dashboardUrl;
|
||||
|
||||
loginBtn.className = "bg-emerald-600 text-white border border-emerald-600 text-[11px] font-bold uppercase tracking-widest px-6 py-3 rounded-xl hover:bg-white hover:text-emerald-700 hover:-translate-y-0.5 transition-all duration-300 shadow-lg shadow-emerald-600/30";
|
||||
}
|
||||
} else {
|
||||
// Jika belum login:
|
||||
// tombol kembali ke landing page ditampilkan
|
||||
if (landingMenu) {
|
||||
landingMenu.classList.remove('hidden');
|
||||
landingMenu.href = landingUrl;
|
||||
}
|
||||
|
||||
// menu beranda disembunyikan
|
||||
if (berandaMenu) {
|
||||
berandaMenu.classList.add('hidden');
|
||||
berandaMenu.removeAttribute('href');
|
||||
}
|
||||
|
||||
// logo menuju landing page
|
||||
if (logoLink) {
|
||||
logoLink.href = landingUrl;
|
||||
}
|
||||
|
||||
// tombol masuk sistem menuju login
|
||||
if (loginBtn) {
|
||||
loginBtn.innerText = "Masuk Sistem →";
|
||||
loginBtn.href = loginUrl;
|
||||
|
||||
loginBtn.className = "bg-emerald-600 text-white border border-emerald-600 text-[11px] font-bold uppercase tracking-widest px-6 py-3 rounded-xl hover:bg-white hover:text-emerald-700 hover:-translate-y-0.5 transition-all duration-300 shadow-lg shadow-emerald-600/30";
|
||||
}
|
||||
}
|
||||
|
||||
function getRoleFromUserObject() {
|
||||
try {
|
||||
const userData = localStorage.getItem('user');
|
||||
|
||||
if (!userData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = JSON.parse(userData);
|
||||
|
||||
return user.role || null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -4,144 +4,405 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ASGARD - Dashboard Siswa</title>
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<style>
|
||||
body { font-family: 'Plus Jakarta Sans', sans-serif; background-color: #f8fafc; }
|
||||
.card-shadow { box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.02); }
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
background:
|
||||
radial-gradient(circle at 8% 12%, rgba(16, 185, 129, 0.10), transparent 32%),
|
||||
radial-gradient(circle at 92% 8%, rgba(59, 130, 246, 0.08), transparent 30%),
|
||||
linear-gradient(180deg, #f8fafc 0%, #f3f7fb 48%, #f8fafc 100%);
|
||||
}
|
||||
|
||||
nav {
|
||||
box-shadow: 0 10px 35px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
nav a {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav a::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -10px;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background: #10b981;
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
nav a:hover::after,
|
||||
nav a.text-emerald-600::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
main > header {
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
border: 1px solid rgba(226, 232, 240, 0.9);
|
||||
border-radius: 32px;
|
||||
padding: 34px;
|
||||
box-shadow: 0 18px 55px rgba(15, 23, 42, 0.06);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
main > header h1 {
|
||||
letter-spacing: -0.045em;
|
||||
}
|
||||
|
||||
.card-shadow {
|
||||
box-shadow: 0 18px 50px rgba(15, 23, 42, 0.07);
|
||||
}
|
||||
|
||||
.card-hover {
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease, background-color 0.25s ease;
|
||||
}
|
||||
|
||||
.card-hover:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.11);
|
||||
border-color: rgba(16, 185, 129, 0.20);
|
||||
}
|
||||
|
||||
main section.bg-white,
|
||||
main section > div.bg-white,
|
||||
main .bg-white.p-8,
|
||||
main .bg-white.p-8.md\:p-10 {
|
||||
background: rgba(255, 255, 255, 0.93);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
#activity-list a,
|
||||
#activity-list-large a {
|
||||
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.035);
|
||||
}
|
||||
|
||||
#activity-list a:hover,
|
||||
#activity-list-large a:hover {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
#trendChart {
|
||||
filter: drop-shadow(0 12px 24px rgba(16, 185, 129, 0.10));
|
||||
}
|
||||
|
||||
footer {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
main > header {
|
||||
padding: 24px;
|
||||
border-radius: 26px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col min-h-screen">
|
||||
|
||||
<nav class="bg-white border-b border-slate-100 px-6 py-4 mb-10">
|
||||
<div class="max-w-7xl mx-auto flex justify-between items-center">
|
||||
<body class="flex flex-col min-h-screen text-slate-800">
|
||||
|
||||
<!-- Navbar -->
|
||||
<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 justify-between items-center 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 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="flex items-center gap-8">
|
||||
<div class="hidden md:flex gap-8 text-[11px] font-bold uppercase tracking-widest text-slate-400">
|
||||
<a href="{{ route('student.profile') }}" class="text-emerald-600">Beranda</a>
|
||||
<a href="{{ route('student.prediction') }}" class="hover:text-emerald-600 transition">Cek Rekomendasi</a>
|
||||
<a href="#" class="hover:text-emerald-600 transition">Kontak BK</a>
|
||||
<a href="{{ route('student.profile') }}" class="text-emerald-600">
|
||||
Beranda
|
||||
</a>
|
||||
|
||||
<a href="{{ route('student.prediction') }}" class="hover:text-emerald-600 transition">
|
||||
Cek Rekomendasi
|
||||
</a>
|
||||
|
||||
<!-- Menu ini membuka hasil rekomendasi terakhir -->
|
||||
<a href="#" onclick="goToLastResult(event)" class="hover:text-emerald-600 transition">
|
||||
Hasil Rekomendasi
|
||||
</a>
|
||||
|
||||
<!-- <a href="#" class="hover:text-emerald-600 transition">
|
||||
Kontak BK
|
||||
</a> -->
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-3 bg-emerald-50 px-4 py-2 rounded-full border border-emerald-100">
|
||||
<div class="hidden sm:flex items-center gap-3 bg-emerald-50 px-4 py-2 rounded-full border border-emerald-100">
|
||||
<div class="w-2 h-2 bg-emerald-500 rounded-full animate-pulse"></div>
|
||||
<span class="text-[10px] font-bold text-emerald-700 uppercase">Galang Sefian</span>
|
||||
<span id="student-name-label" class="text-[10px] font-bold text-emerald-700 uppercase">
|
||||
Siswa
|
||||
</span>
|
||||
</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>
|
||||
<span class="text-[10px] font-bold uppercase tracking-widest">
|
||||
Logout
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-6 flex-grow mb-20">
|
||||
<main class="max-w-7xl mx-auto px-6 flex-grow py-10">
|
||||
|
||||
<!-- Header -->
|
||||
<header class="mb-10">
|
||||
<h1 class="text-4xl font-extrabold text-slate-900 tracking-tight">Halo, Siswa SMAPA! 👋</h1>
|
||||
<p class="text-slate-500 font-medium mt-2">Senang melihat Anda kembali! Mari terus pantau masa depan Anda. ✨</p>
|
||||
<h1 class="text-4xl font-extrabold text-slate-900 tracking-tight">
|
||||
Halo, Siswa SMAPA! 👋
|
||||
</h1>
|
||||
|
||||
<p class="text-slate-500 font-medium mt-2 max-w-3xl">
|
||||
Dashboard ini menampilkan ringkasan aktivitas rekomendasi jurusan, riwayat analisis, dan perkembangan nilai yang pernah kamu masukkan.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
|
||||
<div class="bg-white p-8 rounded-3xl card-shadow border border-slate-100">
|
||||
<h4 class="text-slate-400 text-xs font-bold uppercase tracking-widest mb-4">Total Aktivitas Rekomendasi</h4>
|
||||
<p id="stat-total" class="text-6xl font-black text-emerald-600 tracking-tighter">0</p>
|
||||
<p class="text-[11px] text-slate-400 mt-6 font-medium leading-relaxed">Jumlah total diagnosis & prediksi jurusan kuliah kamu.</p>
|
||||
<!-- Statistik -->
|
||||
<section class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
|
||||
<div class="bg-white p-8 rounded-3xl card-shadow border border-slate-100 card-hover">
|
||||
<h4 class="text-slate-400 text-xs font-bold uppercase tracking-widest mb-4">
|
||||
Total Rekomendasi
|
||||
</h4>
|
||||
|
||||
<p id="stat-total" class="text-6xl font-black text-emerald-600 tracking-tighter">
|
||||
0
|
||||
</p>
|
||||
|
||||
<p class="text-[12px] text-slate-500 mt-6 font-medium leading-relaxed">
|
||||
Jumlah rekomendasi jurusan yang pernah diproses melalui sistem.
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-white p-8 rounded-3xl card-shadow border border-slate-100">
|
||||
<h4 class="text-slate-400 text-xs font-bold uppercase tracking-widest mb-4">Rekomendasi Terakhir</h4>
|
||||
<p id="stat-last" class="text-sm font-bold text-slate-700 italic leading-relaxed">Memuat data...</p>
|
||||
|
||||
<div class="bg-white p-8 rounded-3xl card-shadow border border-slate-100 card-hover">
|
||||
<h4 class="text-slate-400 text-xs font-bold uppercase tracking-widest mb-4">
|
||||
Rekomendasi Terakhir
|
||||
</h4>
|
||||
|
||||
<p id="stat-last" class="text-lg font-extrabold text-slate-800 leading-relaxed">
|
||||
Memuat data...
|
||||
</p>
|
||||
|
||||
<p class="text-[12px] text-slate-500 mt-6 leading-relaxed">
|
||||
Detail hasil terakhir dapat dibuka melalui menu <b>Hasil Rekomendasi</b> pada navbar.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-8 rounded-3xl card-shadow border border-slate-100">
|
||||
<h4 class="text-slate-400 text-xs font-bold uppercase tracking-widest mb-4">Aktivitas Terbaru</h4>
|
||||
<h4 class="text-slate-400 text-xs font-bold uppercase tracking-widest mb-4">
|
||||
Riwayat Terbaru
|
||||
</h4>
|
||||
|
||||
<div id="activity-list" class="space-y-3 py-2">
|
||||
<p class="text-xs text-slate-300 italic">Belum ada aktivitas baru.</p>
|
||||
<p class="text-xs text-slate-400 italic">
|
||||
Belum ada riwayat rekomendasi.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Akses cepat -->
|
||||
<section class="bg-white p-8 md:p-10 rounded-[2.5rem] card-shadow border border-slate-100 mb-10">
|
||||
<div class="mb-8">
|
||||
<h3 class="text-xl font-black text-slate-900">
|
||||
Akses Cepat
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-slate-500 mt-2">
|
||||
Gunakan menu berikut untuk melanjutkan aktivitas pada sistem.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-10 rounded-[2.5rem] card-shadow border border-slate-100 mb-10">
|
||||
<h3 class="text-lg font-black text-emerald-950 mb-8 uppercase italic tracking-wider">Akses Cepat</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<a href="{{ route('student.prediction') }}" class="flex items-center p-6 bg-blue-50 rounded-2xl border border-blue-100 hover:scale-[1.02] transition-all">
|
||||
<div class="w-12 h-12 bg-blue-500 text-white rounded-xl flex items-center justify-center text-xl mr-5 shadow-lg shadow-blue-200">🤖</div>
|
||||
<a href="{{ route('student.prediction') }}"
|
||||
class="flex items-center p-6 bg-blue-50 rounded-2xl border border-blue-100 card-hover">
|
||||
<div class="w-12 h-12 bg-blue-500 text-white rounded-xl flex items-center justify-center text-xl mr-5 shadow-lg shadow-blue-100">
|
||||
📝
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="font-black text-blue-900 text-sm italic">Mulai Analisis Jurusan Baru</h4>
|
||||
<p class="text-[10px] text-blue-600 font-bold mt-1">Dapatkan Rekomendasi AI Sekarang.</p>
|
||||
<h4 class="font-black text-blue-900 text-sm">
|
||||
Cek Rekomendasi Baru
|
||||
</h4>
|
||||
<p class="text-[11px] text-blue-700 font-semibold mt-1">
|
||||
Isi data kembali untuk mendapatkan hasil terbaru.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="#" class="flex items-center p-6 bg-yellow-50 rounded-2xl border border-yellow-100 hover:scale-[1.02] transition-all">
|
||||
<div class="w-12 h-12 bg-yellow-400 text-white rounded-xl flex items-center justify-center text-xl mr-5 shadow-lg shadow-yellow-200">📊</div>
|
||||
|
||||
<a href="#" onclick="goToLastResult(event)"
|
||||
class="flex items-center p-6 bg-emerald-50 rounded-2xl border border-emerald-100 card-hover">
|
||||
<div class="w-12 h-12 bg-emerald-500 text-white rounded-xl flex items-center justify-center text-xl mr-5 shadow-lg shadow-emerald-100">
|
||||
📄
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="font-black text-yellow-900 text-sm italic">Lengkapi Data Rapor</h4>
|
||||
<p class="text-[10px] text-yellow-700 font-bold mt-1">Input variabel nilai semester 1-5.</p>
|
||||
<h4 class="font-black text-emerald-900 text-sm">
|
||||
Hasil Rekomendasi
|
||||
</h4>
|
||||
<p class="text-[11px] text-emerald-700 font-semibold mt-1">
|
||||
Buka kembali hasil rekomendasi terakhir.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="#" class="flex items-center p-6 bg-purple-50 rounded-2xl border border-purple-100 hover:scale-[1.02] transition-all">
|
||||
<div class="w-12 h-12 bg-purple-500 text-white rounded-xl flex items-center justify-center text-xl mr-5 shadow-lg shadow-purple-200">🎓</div>
|
||||
|
||||
<a href="#riwayat-section"
|
||||
class="flex items-center p-6 bg-violet-50 rounded-2xl border border-violet-100 card-hover">
|
||||
<div class="w-12 h-12 bg-violet-500 text-white rounded-xl flex items-center justify-center text-xl mr-5 shadow-lg shadow-violet-100">
|
||||
🕘
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="font-black text-purple-900 text-sm italic">Riwayat Rekomendasi</h4>
|
||||
<p class="text-[10px] text-purple-600 font-bold mt-1">Pantau progres pilihan kuliah.</p>
|
||||
<h4 class="font-black text-violet-900 text-sm">
|
||||
Riwayat Rekomendasi
|
||||
</h4>
|
||||
<p class="text-[11px] text-violet-700 font-semibold mt-1">
|
||||
Lihat daftar rekomendasi yang pernah dibuat.
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Grafik -->
|
||||
<section class="bg-white p-8 md:p-10 rounded-[2.5rem] card-shadow border border-slate-100 mb-10">
|
||||
<h3 class="text-xl font-black text-slate-900 mb-2">
|
||||
Perkembangan Rata-Rata Nilai
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-slate-500 font-medium mb-8">
|
||||
Grafik ini menampilkan perubahan rata-rata nilai berdasarkan data yang pernah kamu masukkan saat melakukan rekomendasi.
|
||||
</p>
|
||||
|
||||
<div class="bg-white p-10 rounded-[2.5rem] card-shadow border border-slate-100 mb-10">
|
||||
<h3 class="text-lg font-black text-emerald-950 mb-2 uppercase italic tracking-wider">Tren Prestasi Akademik Anda</h3>
|
||||
<p class="text-xs text-slate-400 font-medium italic mb-10">Grafik ini menunjukkan perkembangan rata-rata nilai Anda seiring waktu.</p>
|
||||
<div id="chart-container" class="h-80 w-full">
|
||||
<canvas id="trendChart"></canvas>
|
||||
</div>
|
||||
<p id="chart-placeholder" class="hidden text-center text-slate-400 italic text-sm py-20">Input minimal 2 sesi rekomendasi untuk melihat tren di sini.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-indigo-600 p-12 rounded-[3.5rem] text-white relative overflow-hidden shadow-2xl mb-16">
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<span class="text-3xl">🎓</span>
|
||||
<h2 class="text-3xl font-black italic tracking-tighter uppercase leading-none">Masa Depan Anda Prioritas Kami</h2>
|
||||
</div>
|
||||
<p class="text-indigo-100 max-w-3xl mb-12 leading-relaxed font-medium">
|
||||
Tahukah kamu? <span class="font-black text-white italic underline">87% mahasiswa merasa salah jurusan</span>. ASGARD hadir membantu siswa SMAN 4 Jember menghindari risiko tersebut melalui analisis profil yang objektif menggunakan Random Forest.
|
||||
</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="bg-white/10 backdrop-blur-md p-8 rounded-[2rem] border border-white/20">
|
||||
<h4 class="font-black text-sm uppercase italic mb-3 tracking-widest">Anti-Blacklist SNBP</h4>
|
||||
<p class="text-xs text-indigo-100 leading-relaxed italic">Hindari risiko sekolah masuk blacklist perguruan tinggi karena pembatalan kelulusan.</p>
|
||||
<p id="chart-placeholder" class="hidden text-center text-slate-400 italic text-sm py-20">
|
||||
Grafik akan muncul setelah terdapat data rekomendasi.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Panduan -->
|
||||
<section id="panduan-section" class="bg-white p-8 md:p-10 rounded-[2.5rem] card-shadow border border-slate-100 mb-10">
|
||||
<div class="flex flex-col lg:flex-row gap-8">
|
||||
<div class="lg:w-1/3">
|
||||
<div class="w-14 h-14 rounded-2xl bg-emerald-100 text-emerald-700 flex items-center justify-center text-2xl mb-5">
|
||||
📌
|
||||
</div>
|
||||
<div class="bg-white/10 backdrop-blur-md p-8 rounded-[2rem] border border-white/20">
|
||||
<h4 class="font-black text-sm uppercase italic mb-3 tracking-widest">Prospek Karir AI</h4>
|
||||
<p class="text-xs text-indigo-100 leading-relaxed italic">Dapatkan informasi jalur karir yang dapat ditempuh sesuai rekomendasi jurusan.</p>
|
||||
|
||||
<h3 class="text-2xl font-black text-slate-900">
|
||||
Panduan Membaca Hasil
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-slate-500 mt-3 leading-relaxed">
|
||||
Hasil rekomendasi digunakan sebagai bahan pertimbangan awal. Keputusan akhir tetap perlu disesuaikan dengan minat, kemampuan, dan arahan Guru BK.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="lg:w-2/3 grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="rounded-3xl bg-slate-50 border border-slate-100 p-6">
|
||||
<h4 class="font-extrabold text-slate-800 mb-3">
|
||||
1. Periksa Data
|
||||
</h4>
|
||||
|
||||
<p class="text-sm text-slate-500 leading-relaxed">
|
||||
Pastikan nilai rapor, minat, dan kondisi ekonomi sudah sesuai dengan data sebenarnya.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl bg-slate-50 border border-slate-100 p-6">
|
||||
<h4 class="font-extrabold text-slate-800 mb-3">
|
||||
2. Pelajari Jurusan
|
||||
</h4>
|
||||
|
||||
<p class="text-sm text-slate-500 leading-relaxed">
|
||||
Cari informasi tentang mata kuliah, biaya kuliah, peluang kerja, dan kampus yang menyediakan jurusan tersebut.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl bg-slate-50 border border-slate-100 p-6">
|
||||
<h4 class="font-extrabold text-slate-800 mb-3">
|
||||
3. Konsultasi BK
|
||||
</h4>
|
||||
|
||||
<p class="text-sm text-slate-500 leading-relaxed">
|
||||
Diskusikan hasil rekomendasi dengan Guru BK agar pilihan jurusan lebih matang.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-12 text-center text-[10px] font-black uppercase tracking-[0.2em] italic opacity-80 border-t border-white/10 pt-8 text-white">Penting untuk berkonsultasi dengan Guru BK setelah mendapatkan hasil rekomendasi AI ❤️</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Riwayat -->
|
||||
<section id="riwayat-section" class="bg-white p-8 md:p-10 rounded-[2.5rem] card-shadow border border-slate-100 mb-16">
|
||||
<div class="mb-6">
|
||||
<h3 class="text-xl font-black text-slate-900">
|
||||
Riwayat Rekomendasi
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-slate-500 mt-2">
|
||||
Riwayat terbaru akan muncul pada daftar berikut.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="activity-list-large" class="space-y-3">
|
||||
<p class="text-sm text-slate-400 italic">
|
||||
Belum ada riwayat rekomendasi.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-[#062C22] text-white py-16 px-6">
|
||||
<div class="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">ASGARD.</span>
|
||||
</div>
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">
|
||||
ASGARD.
|
||||
</span>
|
||||
|
||||
<p class="text-slate-300 text-sm leading-relaxed max-w-xs">
|
||||
Sistem pakar rekomendasi jurusan kuliah SMAN 4 Jember berbasis data-driven.
|
||||
Sistem rekomendasi jurusan kuliah SMAN 4 Jember berbasis profil siswa dan data akademik.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">Informasi</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">
|
||||
Informasi
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li><a href="{{ route('panduan') }}" class="hover:text-white transition">Panduan Sistem</a></li>
|
||||
<li><a href="#" class="hover:text-white transition">Kebijakan Privasi</a></li>
|
||||
<li>
|
||||
<a href="{{ route('panduan') }}" class="hover:text-white transition">
|
||||
Panduan Sistem
|
||||
</a>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<a href="#" class="hover:text-white transition">
|
||||
Kebijakan Privasi
|
||||
</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">Sekolah</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">
|
||||
Sekolah
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li>SMAN 4 Jember</li>
|
||||
<li>Jl. Wolter Monginsidi No.24</li>
|
||||
|
|
@ -149,14 +410,118 @@
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto mt-16 pt-8 border-t border-white/10 text-center">
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.5em] text-slate-400">© 2026 GALANG SEFIAN ADJI - POLITEKNIK NEGERI JEMBER</p>
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.35em] text-slate-400">
|
||||
© 2026 GALANG SEFIAN ADJI - POLITEKNIK NEGERI JEMBER
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
let trendChartInstance = null;
|
||||
|
||||
const routeLogin = @json(route('login'));
|
||||
const routeDashboard = @json(route('student.profile'));
|
||||
const routePrediction = @json(route('student.prediction'));
|
||||
const routeAdminDashboard = @json(route('admin.dashboard'));
|
||||
|
||||
function getAccessToken() {
|
||||
return localStorage.getItem('access_token');
|
||||
}
|
||||
|
||||
function getUserRole() {
|
||||
return localStorage.getItem('user_role');
|
||||
}
|
||||
|
||||
function redirectByRole(role) {
|
||||
if (role === 'admin') {
|
||||
window.location.href = routeAdminDashboard;
|
||||
return;
|
||||
}
|
||||
|
||||
if (role === 'siswa') {
|
||||
window.location.href = routeDashboard;
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = routeLogin;
|
||||
}
|
||||
|
||||
function getStoredLastResult() {
|
||||
try {
|
||||
const saved = localStorage.getItem('asgard_last_result');
|
||||
return saved ? JSON.parse(saved) : null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function saveStoredLastResult(data) {
|
||||
if (!data || !data.jurusan) return;
|
||||
|
||||
const oldData = getStoredLastResult() || {};
|
||||
|
||||
const nextData = {
|
||||
id: data.id || oldData.id || '',
|
||||
jurusan: data.jurusan || oldData.jurusan || '',
|
||||
tanggal: data.tanggal || oldData.tanggal || '',
|
||||
url: data.url || oldData.url || ''
|
||||
};
|
||||
|
||||
localStorage.setItem('asgard_last_result', JSON.stringify(nextData));
|
||||
}
|
||||
|
||||
function goToLastResult(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const saved = getStoredLastResult();
|
||||
|
||||
if (saved && saved.url) {
|
||||
window.location.href = saved.url;
|
||||
return;
|
||||
}
|
||||
|
||||
if (saved && saved.id) {
|
||||
window.location.href = `/student/result/${saved.id}`;
|
||||
return;
|
||||
}
|
||||
|
||||
alert('Belum ada hasil rekomendasi yang bisa dibuka. Silakan lakukan cek rekomendasi terlebih dahulu.');
|
||||
window.location.href = routePrediction;
|
||||
}
|
||||
|
||||
function getHistoryId(item) {
|
||||
if (!item || typeof item !== 'object') return '';
|
||||
|
||||
return item.id
|
||||
|| item.riwayat_id
|
||||
|| item.riwayat_rekomendasi_id
|
||||
|| item.rekomendasi_id
|
||||
|| item.result_id
|
||||
|| item.hasil_id
|
||||
|| '';
|
||||
}
|
||||
|
||||
function makeResultUrl(id) {
|
||||
return id ? `/student/result/${id}` : '';
|
||||
}
|
||||
|
||||
function isValidRecommendationName(value) {
|
||||
if (!value) return false;
|
||||
|
||||
const text = String(value).trim().toLowerCase();
|
||||
|
||||
return text !== ''
|
||||
&& text !== '-'
|
||||
&& text !== 'null'
|
||||
&& text !== 'undefined'
|
||||
&& !text.includes('belum');
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const token = getAccessToken();
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
await fetch('/api/logout', {
|
||||
|
|
@ -169,18 +534,171 @@
|
|||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
}
|
||||
localStorage.removeItem('access_token');
|
||||
}
|
||||
window.location.href = '/';
|
||||
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('user_role');
|
||||
localStorage.removeItem('asgard_last_result');
|
||||
window.location.href = routeLogin;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (!token) {
|
||||
window.location.href = '/login';
|
||||
function renderActivityList(activities) {
|
||||
const smallList = document.getElementById('activity-list');
|
||||
const largeList = document.getElementById('activity-list-large');
|
||||
|
||||
if (!Array.isArray(activities) || activities.length === 0) {
|
||||
smallList.innerHTML = `
|
||||
<p class="text-xs text-slate-400 italic">
|
||||
Belum ada riwayat rekomendasi.
|
||||
</p>
|
||||
`;
|
||||
|
||||
largeList.innerHTML = `
|
||||
<p class="text-sm text-slate-400 italic">
|
||||
Belum ada riwayat rekomendasi.
|
||||
</p>
|
||||
`;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
smallList.innerHTML = activities.slice(0, 5).map((act) => {
|
||||
const id = getHistoryId(act);
|
||||
const url = makeResultUrl(id);
|
||||
const jurusan = act.jurusan || act.hasil_rekomendasi_jurusan || 'Rekomendasi Jurusan';
|
||||
const tanggal = act.tanggal || act.created_at || '';
|
||||
|
||||
return `
|
||||
<a href="${url || '#'}"
|
||||
class="flex items-center justify-between bg-slate-50 hover:bg-emerald-50 p-3 rounded-xl border border-slate-100 hover:border-emerald-100 transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-lg">📄</span>
|
||||
<span class="text-[11px] font-bold text-slate-700 uppercase tracking-tight">
|
||||
${jurusan}
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-[9px] font-black text-slate-400 uppercase">
|
||||
${tanggal}
|
||||
</span>
|
||||
</a>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
largeList.innerHTML = activities.map((act) => {
|
||||
const id = getHistoryId(act);
|
||||
const url = makeResultUrl(id);
|
||||
const jurusan = act.jurusan || act.hasil_rekomendasi_jurusan || 'Rekomendasi Jurusan';
|
||||
const tanggal = act.tanggal || act.created_at || 'Tanggal tidak tersedia';
|
||||
|
||||
return `
|
||||
<a href="${url || '#'}"
|
||||
class="flex flex-col md:flex-row md:items-center md:justify-between gap-3 rounded-2xl border border-slate-100 bg-slate-50 hover:bg-emerald-50 hover:border-emerald-100 p-5 transition">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-11 h-11 rounded-2xl bg-white flex items-center justify-center border border-slate-100">
|
||||
📄
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-extrabold text-slate-800 uppercase">
|
||||
${jurusan}
|
||||
</h4>
|
||||
<p class="text-xs text-slate-500 mt-1">
|
||||
${tanggal}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-xs font-bold text-emerald-700">
|
||||
${url ? 'Lihat detail →' : 'Detail tidak tersedia'}
|
||||
</span>
|
||||
</a>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderTrendChart(tren) {
|
||||
const canvas = document.getElementById('trendChart');
|
||||
const placeholder = document.getElementById('chart-placeholder');
|
||||
|
||||
if (!Array.isArray(tren) || tren.length === 0) {
|
||||
canvas.classList.add('hidden');
|
||||
placeholder.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.classList.remove('hidden');
|
||||
placeholder.classList.add('hidden');
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if (trendChartInstance) {
|
||||
trendChartInstance.destroy();
|
||||
}
|
||||
|
||||
trendChartInstance = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: tren.map(t => t.label),
|
||||
datasets: [{
|
||||
label: 'Rata-rata Nilai',
|
||||
data: tren.map(t => t.nilai),
|
||||
borderColor: '#10b981',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.12)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 5,
|
||||
pointBackgroundColor: '#ffffff',
|
||||
pointBorderColor: '#10b981',
|
||||
pointBorderWidth: 3
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
ticks: {
|
||||
color: '#64748b'
|
||||
},
|
||||
grid: {
|
||||
color: '#f1f5f9'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
ticks: {
|
||||
color: '#64748b'
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const token = getAccessToken();
|
||||
const role = getUserRole();
|
||||
|
||||
if (!token) {
|
||||
window.location.href = routeLogin;
|
||||
return;
|
||||
}
|
||||
|
||||
if (role !== 'siswa') {
|
||||
redirectByRole(role);
|
||||
return;
|
||||
}
|
||||
|
||||
const savedName = localStorage.getItem('user_name') || localStorage.getItem('name') || 'Siswa';
|
||||
document.getElementById('student-name-label').innerText = savedName;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/student/dashboard-stats', {
|
||||
headers: {
|
||||
|
|
@ -189,66 +707,57 @@
|
|||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
const data = result.data;
|
||||
|
||||
// Update Statistik
|
||||
document.getElementById('stat-total').innerText = data.total_aktivitas;
|
||||
document.getElementById('stat-last').innerText = data.rekomendasi_terakhir;
|
||||
|
||||
// Update List Aktivitas
|
||||
const listContainer = document.getElementById('activity-list');
|
||||
if (data.aktivitas_terbaru.length > 0) {
|
||||
listContainer.innerHTML = data.aktivitas_terbaru.map(act => `
|
||||
<div class="flex items-center justify-between bg-slate-50 p-3 rounded-xl border border-slate-100">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-lg">📋</span>
|
||||
<span class="text-[11px] font-bold text-slate-700 uppercase tracking-tight">${act.jurusan}</span>
|
||||
</div>
|
||||
<span class="text-[9px] font-black text-slate-400 uppercase italic">${act.tanggal}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Render Chart
|
||||
const tren = data.tren_nilai;
|
||||
if (tren.length >= 1) {
|
||||
const ctx = document.getElementById('trendChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: tren.map(t => t.label),
|
||||
datasets: [{
|
||||
label: 'Rata-rata Nilai',
|
||||
data: tren.map(t => t.nilai),
|
||||
borderColor: '#10b981',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 6,
|
||||
pointBackgroundColor: '#fff',
|
||||
pointBorderColor: '#10b981',
|
||||
pointBorderWidth: 3
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
y: { min: 0, max: 100, grid: { display: false } },
|
||||
x: { grid: { display: false } }
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
document.getElementById('trendChart').classList.add('hidden');
|
||||
document.getElementById('chart-placeholder').classList.remove('hidden');
|
||||
}
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('user_role');
|
||||
window.location.href = routeLogin;
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
document.getElementById('stat-total').innerText = 0;
|
||||
document.getElementById('stat-last').innerText = 'Belum ada rekomendasi';
|
||||
renderActivityList([]);
|
||||
renderTrendChart([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = result.data || {};
|
||||
const activities = Array.isArray(data.aktivitas_terbaru) ? data.aktivitas_terbaru : [];
|
||||
const firstActivity = activities.length > 0 ? activities[0] : null;
|
||||
|
||||
document.getElementById('stat-total').innerText = data.total_aktivitas ?? 0;
|
||||
|
||||
const lastName = data.rekomendasi_terakhir || '';
|
||||
|
||||
document.getElementById('stat-last').innerText = isValidRecommendationName(lastName)
|
||||
? lastName
|
||||
: 'Belum ada rekomendasi';
|
||||
|
||||
// Simpan hasil terakhir dari data dashboard jika API mengirim id riwayat
|
||||
const firstId = getHistoryId(firstActivity);
|
||||
|
||||
if (isValidRecommendationName(lastName) && firstId) {
|
||||
saveStoredLastResult({
|
||||
id: firstId,
|
||||
jurusan: lastName,
|
||||
tanggal: firstActivity?.tanggal || '',
|
||||
url: makeResultUrl(firstId)
|
||||
});
|
||||
}
|
||||
|
||||
renderActivityList(activities);
|
||||
renderTrendChart(data.tren_nilai || []);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Gagal memuat data dashboard:', error);
|
||||
|
||||
document.getElementById('stat-total').innerText = 0;
|
||||
document.getElementById('stat-last').innerText = 'Data belum dapat dimuat';
|
||||
renderActivityList([]);
|
||||
renderTrendChart([]);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,42 +3,146 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hasil Analisis - ASGARD</title>
|
||||
<title>Hasil Rekomendasi - ASGARD</title>
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<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-color: #f8fafc;
|
||||
background:
|
||||
radial-gradient(circle at 8% 12%, rgba(16, 185, 129, 0.10), transparent 32%),
|
||||
radial-gradient(circle at 92% 8%, rgba(59, 130, 246, 0.08), transparent 30%),
|
||||
linear-gradient(180deg, #f8fafc 0%, #f3f7fb 50%, #f8fafc 100%);
|
||||
}
|
||||
|
||||
nav {
|
||||
box-shadow: 0 10px 35px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
nav a {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav a::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -10px;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background: #10b981;
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
nav a:hover::after,
|
||||
nav a.text-emerald-600::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-shadow {
|
||||
box-shadow: 0 14px 40px rgba(15, 23, 42, 0.08);
|
||||
box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.card-hover {
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease, background-color 0.25s ease;
|
||||
}
|
||||
|
||||
.card-hover:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.11);
|
||||
border-color: rgba(16, 185, 129, 0.22);
|
||||
}
|
||||
|
||||
main > div > .text-center {
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(226, 232, 240, 0.85);
|
||||
border-radius: 32px;
|
||||
padding: 34px 24px;
|
||||
box-shadow: 0 18px 55px rgba(15, 23, 42, 0.055);
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
|
||||
main section.bg-gradient-to-br {
|
||||
box-shadow: 0 28px 75px rgba(16, 185, 129, 0.23);
|
||||
}
|
||||
|
||||
main section.bg-white,
|
||||
main div.bg-white {
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
main .bg-slate-50 {
|
||||
transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
|
||||
}
|
||||
|
||||
main .bg-slate-50:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 16px 35px rgba(15, 23, 42, 0.06);
|
||||
border-color: rgba(16, 185, 129, 0.18);
|
||||
}
|
||||
|
||||
footer {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
main > div > .text-center {
|
||||
border-radius: 26px;
|
||||
padding: 26px 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col min-h-screen text-slate-800">
|
||||
|
||||
<!-- Navbar -->
|
||||
<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 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="#" class="hover:text-emerald-600 transition">Kontak BK</a>
|
||||
<a href="{{ route('student.profile') }}" class="hover:text-emerald-600 transition">
|
||||
Beranda
|
||||
</a>
|
||||
|
||||
<a href="{{ route('student.prediction') }}" class="hover:text-emerald-600 transition">
|
||||
Cek Rekomendasi
|
||||
</a>
|
||||
|
||||
<!-- Menu aktif halaman hasil -->
|
||||
<a href="#" onclick="goToLastResult(event)" class="text-emerald-600">
|
||||
Hasil Rekomendasi
|
||||
</a>
|
||||
|
||||
<!-- <a href="#" class="hover:text-emerald-600 transition">
|
||||
Kontak BK
|
||||
</a> -->
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="hidden sm:flex items-center gap-3 bg-emerald-50 px-4 py-2 rounded-full border border-emerald-100">
|
||||
<div class="w-2 h-2 bg-emerald-500 rounded-full animate-pulse"></div>
|
||||
<span class="text-[10px] font-bold text-emerald-700 uppercase">Siswa Terdaftar</span>
|
||||
<span class="text-[10px] font-bold text-emerald-700 uppercase">
|
||||
Siswa
|
||||
</span>
|
||||
</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>
|
||||
<span class="text-[10px] font-bold uppercase tracking-widest">
|
||||
Logout
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -46,19 +150,24 @@
|
|||
|
||||
<main class="flex-grow px-6 py-10">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-8">
|
||||
<div class="inline-flex items-center gap-2 bg-emerald-100 text-emerald-700 px-4 py-2 rounded-full text-xs font-bold">
|
||||
Analisis Random Forest Selesai ✅
|
||||
Hasil rekomendasi berhasil diproses ✅
|
||||
</div>
|
||||
|
||||
<h1 class="mt-5 text-4xl md:text-5xl font-extrabold tracking-tight text-slate-900">
|
||||
Laporan Rekomendasi AI
|
||||
Hasil Rekomendasi Jurusan
|
||||
</h1>
|
||||
<p class="text-slate-500 mt-3">
|
||||
Berdasarkan data akademik dan minat bakat, berikut diagnosis masa depanmu.
|
||||
|
||||
<p class="text-slate-500 mt-3 max-w-2xl mx-auto leading-relaxed">
|
||||
Hasil ini tersimpan sebagai rekomendasi terakhir. Setelah kembali ke dashboard, menu <b>Hasil Rekomendasi</b> tetap bisa digunakan untuk membuka halaman ini.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-br from-emerald-600 via-emerald-500 to-teal-600 rounded-[2.5rem] p-8 md:p-14 text-white card-shadow mb-8 relative overflow-hidden">
|
||||
<!-- Hasil utama -->
|
||||
<section class="bg-gradient-to-br from-emerald-600 via-emerald-500 to-teal-600 rounded-[2.5rem] p-8 md:p-14 text-white card-shadow mb-8 relative overflow-hidden">
|
||||
<div class="absolute inset-0 opacity-10">
|
||||
<div class="absolute -top-10 -left-10 w-40 h-40 bg-white rounded-full blur-3xl"></div>
|
||||
<div class="absolute -bottom-10 -right-10 w-56 h-56 bg-white rounded-full blur-3xl"></div>
|
||||
|
|
@ -66,38 +175,57 @@
|
|||
|
||||
<div class="relative z-10 text-center">
|
||||
<p class="text-[11px] md:text-sm font-bold uppercase tracking-[0.35em] text-emerald-100 mb-5">
|
||||
Sangat Cocok di Bidang
|
||||
Rekomendasi Jurusan
|
||||
</p>
|
||||
|
||||
<h2 class="text-3xl md:text-6xl font-black uppercase leading-tight tracking-tight mb-8">
|
||||
{{ $riwayat->hasil_rekomendasi_jurusan }}
|
||||
</h2>
|
||||
|
||||
<p class="max-w-3xl mx-auto text-emerald-50 leading-relaxed mb-8">
|
||||
Jurusan ini diperoleh dari pengolahan data nilai rapor, kecenderungan minat, dan kondisi ekonomi yang telah dimasukkan.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-3 justify-center">
|
||||
<a href="{{ route('student.prediction') }}"
|
||||
class="inline-flex items-center justify-center bg-emerald-700/70 hover:bg-emerald-800 text-white font-bold py-4 px-8 rounded-2xl transition border border-white/10">
|
||||
↻ Ulangi Analisis
|
||||
<!-- Tombol kembali, hasil tetap tersimpan di localStorage -->
|
||||
<a href="{{ route('student.profile') }}"
|
||||
onclick="saveLastRecommendationResult()"
|
||||
class="inline-flex items-center justify-center bg-white hover:bg-slate-100 text-emerald-700 font-bold py-4 px-8 rounded-2xl transition">
|
||||
Kembali ke Dashboard
|
||||
</a>
|
||||
|
||||
<a href="{{ route('student.profile') }}"
|
||||
class="inline-flex items-center justify-center bg-white hover:bg-slate-100 text-emerald-700 font-bold py-4 px-8 rounded-2xl transition">
|
||||
Kembali ke Dashboard →
|
||||
<a href="{{ route('student.prediction') }}"
|
||||
onclick="saveLastRecommendationResult()"
|
||||
class="inline-flex items-center justify-center bg-emerald-800/50 hover:bg-emerald-800 text-white font-bold py-4 px-8 rounded-2xl transition border border-white/10">
|
||||
Cek Rekomendasi Baru
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="bg-white rounded-[2rem] border border-slate-100 card-shadow p-6 md:p-8 mb-8">
|
||||
<!-- Rekapan data -->
|
||||
<section class="bg-white rounded-[2rem] border border-slate-100 card-shadow p-6 md:p-8 mb-8">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-10 h-10 rounded-2xl bg-violet-100 flex items-center justify-center text-xl">👤</div>
|
||||
<h3 class="text-2xl font-extrabold text-slate-800">Rekapan Profilmu</h3>
|
||||
<div class="w-10 h-10 rounded-2xl bg-violet-100 flex items-center justify-center text-xl">
|
||||
👤
|
||||
</div>
|
||||
|
||||
<h3 class="text-2xl font-extrabold text-slate-800">
|
||||
Rekapan Data yang Digunakan
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-3xl p-5">
|
||||
<p class="text-[11px] font-bold uppercase tracking-widest text-slate-400 mb-3">Kondisi Ekonomi</p>
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-3xl p-5 card-hover">
|
||||
<p class="text-[11px] font-bold uppercase tracking-widest text-slate-400 mb-3">
|
||||
Kondisi Ekonomi
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl bg-amber-100 flex items-center justify-center text-2xl">💰</div>
|
||||
<div class="w-14 h-14 rounded-2xl bg-amber-100 flex items-center justify-center text-2xl">
|
||||
💰
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-extrabold text-slate-800 leading-snug">
|
||||
{{ $labelEkonomi }}
|
||||
|
|
@ -106,10 +234,16 @@ class="inline-flex items-center justify-center bg-white hover:bg-slate-100 text-
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-3xl p-5">
|
||||
<p class="text-[11px] font-bold uppercase tracking-widest text-slate-400 mb-3">Minat Utama</p>
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-3xl p-5 card-hover">
|
||||
<p class="text-[11px] font-bold uppercase tracking-widest text-slate-400 mb-3">
|
||||
Minat Utama
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl bg-pink-100 flex items-center justify-center text-2xl">🎯</div>
|
||||
<div class="w-14 h-14 rounded-2xl bg-pink-100 flex items-center justify-center text-2xl">
|
||||
🎯
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-extrabold text-slate-800 leading-snug">
|
||||
{{ $minatUtama }}
|
||||
|
|
@ -118,10 +252,16 @@ class="inline-flex items-center justify-center bg-white hover:bg-slate-100 text-
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-3xl p-5">
|
||||
<p class="text-[11px] font-bold uppercase tracking-widest text-slate-400 mb-3">Akademik</p>
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-3xl p-5 card-hover">
|
||||
<p class="text-[11px] font-bold uppercase tracking-widest text-slate-400 mb-3">
|
||||
Data Akademik
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-14 h-14 rounded-2xl bg-cyan-100 flex items-center justify-center text-2xl">📚</div>
|
||||
<div class="w-14 h-14 rounded-2xl bg-cyan-100 flex items-center justify-center text-2xl">
|
||||
📚
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-extrabold text-slate-800 leading-snug">
|
||||
15 Mata Pelajaran
|
||||
|
|
@ -130,48 +270,111 @@ class="inline-flex items-center justify-center bg-white hover:bg-slate-100 text-
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Informasi hasil -->
|
||||
<section class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||
<div class="bg-white rounded-[2rem] border border-slate-100 card-shadow p-6 md:p-8">
|
||||
<h3 class="text-xl font-extrabold text-slate-800 mb-4">Ringkasan Analisis</h3>
|
||||
<h3 class="text-xl font-extrabold text-slate-800 mb-4">
|
||||
Ringkasan Analisis
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-600 leading-relaxed">
|
||||
Berdasarkan perpaduan profil akademik, kecenderungan minat bakat, dan kondisi ekonomi keluarga,
|
||||
sistem menilai bahwa jurusan ini merupakan pilihan yang paling relevan untuk dipertimbangkan pada tahap saat ini.
|
||||
Sistem membaca hubungan antara nilai rapor, minat utama, dan kondisi ekonomi yang diinputkan. Berdasarkan hasil pengolahan tersebut, jurusan
|
||||
<span class="font-bold text-slate-800">{{ $riwayat->hasil_rekomendasi_jurusan }}</span>
|
||||
menjadi pilihan yang paling sesuai untuk dipertimbangkan pada tahap ini.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-[2rem] border border-slate-100 card-shadow p-6 md:p-8">
|
||||
<h3 class="text-xl font-extrabold text-slate-800 mb-4">Peluang Karier</h3>
|
||||
<h3 class="text-xl font-extrabold text-slate-800 mb-4">
|
||||
Peluang Karier
|
||||
</h3>
|
||||
|
||||
<p class="text-slate-600 leading-relaxed">
|
||||
{{ $prospekKerja }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Saran -->
|
||||
<section class="bg-white rounded-[2rem] border border-slate-100 card-shadow p-6 md:p-8">
|
||||
<h3 class="text-xl font-extrabold text-slate-800 mb-5">
|
||||
Langkah Setelah Mendapatkan Hasil
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-3xl p-5">
|
||||
<h4 class="font-extrabold text-slate-800 mb-2">
|
||||
1. Cek Kembali Data
|
||||
</h4>
|
||||
|
||||
<p class="text-sm text-slate-500 leading-relaxed">
|
||||
Pastikan nilai dan jawaban minat yang dimasukkan sudah sesuai dengan kondisi sebenarnya.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-3xl p-5">
|
||||
<h4 class="font-extrabold text-slate-800 mb-2">
|
||||
2. Cari Informasi Jurusan
|
||||
</h4>
|
||||
|
||||
<p class="text-sm text-slate-500 leading-relaxed">
|
||||
Pelajari mata kuliah, biaya, peluang kerja, dan kampus yang menyediakan jurusan tersebut.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-3xl p-5">
|
||||
<h4 class="font-extrabold text-slate-800 mb-2">
|
||||
3. Konsultasi Guru BK
|
||||
</h4>
|
||||
|
||||
<p class="text-sm text-slate-500 leading-relaxed">
|
||||
Gunakan hasil ini sebagai bahan diskusi dengan Guru BK sebelum menentukan pilihan akhir.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-[#062C22] text-white py-16 px-6 mt-16">
|
||||
<div class="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">ASGARD.</span>
|
||||
</div>
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">
|
||||
ASGARD.
|
||||
</span>
|
||||
|
||||
<p class="text-slate-300 text-sm leading-relaxed max-w-xs">
|
||||
Sistem pakar rekomendasi jurusan kuliah SMAN 4 Jember berbasis data-driven.
|
||||
Sistem rekomendasi jurusan kuliah SMAN 4 Jember berbasis profil siswa dan data akademik.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">Informasi</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">
|
||||
Informasi
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li><a href="{{ route('panduan') }}" class="hover:text-white transition">Panduan Sistem</a></li>
|
||||
<li><a href="#" class="hover:text-white transition">Kebijakan Privasi</a></li>
|
||||
<li>
|
||||
<a href="{{ route('panduan') }}" class="hover:text-white transition">
|
||||
Panduan Sistem
|
||||
</a>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<a href="#" class="hover:text-white transition">
|
||||
Kebijakan Privasi
|
||||
</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">Sekolah</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">
|
||||
Sekolah
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li>SMAN 4 Jember</li>
|
||||
<li>Jl. Wolter Monginsidi No.24</li>
|
||||
|
|
@ -181,13 +384,18 @@ class="inline-flex items-center justify-center bg-white hover:bg-slate-100 text-
|
|||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto mt-16 pt-8 border-t border-white/10 text-center">
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.5em] text-slate-400">
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.35em] text-slate-400">
|
||||
© 2026 GALANG SEFIAN ADJI - POLITEKNIK NEGERI JEMBER
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const routeLogin = @json(route('login'));
|
||||
const routeDashboard = @json(route('student.profile'));
|
||||
const routePrediction = @json(route('student.prediction'));
|
||||
const routeAdminDashboard = @json(route('admin.dashboard'));
|
||||
|
||||
function getAccessToken() {
|
||||
return localStorage.getItem('access_token');
|
||||
}
|
||||
|
|
@ -198,16 +406,70 @@ function getUserRole() {
|
|||
|
||||
function redirectByRole(role) {
|
||||
if (role === 'admin') {
|
||||
window.location.href = "{{ route('admin.dashboard') }}";
|
||||
window.location.href = routeAdminDashboard;
|
||||
return;
|
||||
}
|
||||
|
||||
if (role === 'siswa') {
|
||||
window.location.href = "{{ route('student.profile') }}";
|
||||
window.location.href = routeDashboard;
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = "{{ route('login') }}";
|
||||
window.location.href = routeLogin;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
saveLastRecommendationResult();
|
||||
|
||||
const saved = getStoredLastResult();
|
||||
|
||||
if (saved && saved.url) {
|
||||
window.location.href = saved.url;
|
||||
return;
|
||||
}
|
||||
|
||||
if (saved && saved.id) {
|
||||
window.location.href = `/student/result/${saved.id}`;
|
||||
return;
|
||||
}
|
||||
|
||||
alert('Belum ada hasil rekomendasi yang bisa dibuka. Silakan lakukan cek rekomendasi terlebih dahulu.');
|
||||
window.location.href = routePrediction;
|
||||
}
|
||||
|
||||
// Bagian penting: menyimpan hasil terakhir agar setelah klik kembali tetap bisa dibuka dari navbar
|
||||
function saveLastRecommendationResult() {
|
||||
const resultId = @json($riwayat->id ?? '');
|
||||
const resultUrl = resultId ? `/student/result/${resultId}` : window.location.pathname;
|
||||
|
||||
const resultData = {
|
||||
id: resultId,
|
||||
jurusan: @json($riwayat->hasil_rekomendasi_jurusan ?? ''),
|
||||
label_ekonomi: @json($labelEkonomi ?? ''),
|
||||
minat_utama: @json($minatUtama ?? ''),
|
||||
prospek_kerja: @json($prospekKerja ?? ''),
|
||||
url: resultUrl,
|
||||
tanggal: new Date().toLocaleString('id-ID', {
|
||||
day: '2-digit',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
};
|
||||
|
||||
localStorage.setItem('asgard_last_result', JSON.stringify(resultData));
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
|
|
@ -229,21 +491,30 @@ function redirectByRole(role) {
|
|||
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('user_role');
|
||||
window.location.href = "{{ route('login') }}";
|
||||
localStorage.removeItem('asgard_last_result');
|
||||
window.location.href = routeLogin;
|
||||
}
|
||||
|
||||
// bfcache/pageshow membuat hasil tetap tersimpan walaupun user memakai tombol back browser
|
||||
window.addEventListener('pageshow', function () {
|
||||
saveLastRecommendationResult();
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const token = getAccessToken();
|
||||
const role = getUserRole();
|
||||
|
||||
if (!token) {
|
||||
window.location.href = "{{ route('login') }}";
|
||||
window.location.href = routeLogin;
|
||||
return;
|
||||
}
|
||||
|
||||
if (role !== 'siswa') {
|
||||
redirectByRole(role);
|
||||
return;
|
||||
}
|
||||
|
||||
saveLastRecommendationResult();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -4,42 +4,136 @@
|
|||
<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>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
background-color: #f8fafc;
|
||||
background:
|
||||
radial-gradient(circle at 8% 12%, rgba(16, 185, 129, 0.10), transparent 32%),
|
||||
radial-gradient(circle at 92% 8%, rgba(59, 130, 246, 0.08), transparent 30%),
|
||||
linear-gradient(180deg, #f8fafc 0%, #f3f7fb 50%, #f8fafc 100%);
|
||||
}
|
||||
|
||||
nav {
|
||||
box-shadow: 0 10px 35px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
nav a {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nav a::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -10px;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background: #10b981;
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
nav a:hover::after,
|
||||
nav a.text-emerald-600::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-shadow {
|
||||
box-shadow: 0 14px 40px rgba(15, 23, 42, 0.08);
|
||||
box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
main > div > div.bg-white {
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
backdrop-filter: blur(16px);
|
||||
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
main section {
|
||||
transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
|
||||
}
|
||||
|
||||
main section:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 16px 35px rgba(15, 23, 42, 0.055);
|
||||
border-color: rgba(16, 185, 129, 0.18);
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
input:hover,
|
||||
select:hover {
|
||||
border-color: rgba(16, 185, 129, 0.45);
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
select:focus {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#alert-box {
|
||||
box-shadow: 0 14px 35px rgba(239, 68, 68, 0.08);
|
||||
}
|
||||
|
||||
footer {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col min-h-screen text-slate-800">
|
||||
|
||||
<!-- Navbar -->
|
||||
<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 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="#" class="hover:text-emerald-600 transition">Kontak BK</a>
|
||||
<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>
|
||||
|
||||
<!-- Menu untuk membuka hasil terakhir -->
|
||||
<a href="#" onclick="goToLastResult(event)" class="hover:text-emerald-600 transition">
|
||||
Hasil Rekomendasi
|
||||
</a>
|
||||
|
||||
<!-- <a href="#" class="hover:text-emerald-600 transition">
|
||||
Kontak BK
|
||||
</a> -->
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="hidden sm:flex items-center gap-3 bg-emerald-50 px-4 py-2 rounded-full border border-emerald-100">
|
||||
<div class="w-2 h-2 bg-emerald-500 rounded-full animate-pulse"></div>
|
||||
<span class="text-[10px] font-bold text-emerald-700 uppercase">Siswa Terdaftar</span>
|
||||
<span class="text-[10px] font-bold text-emerald-700 uppercase">
|
||||
Siswa
|
||||
</span>
|
||||
</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>
|
||||
<span class="text-[10px] font-bold uppercase tracking-widest">
|
||||
Logout
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -48,21 +142,31 @@
|
|||
<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">DIAGNOSIS JURUSAN</h1>
|
||||
<p class="text-slate-500 mt-3 text-sm md:text-base">
|
||||
Isi data berikut dengan jujur agar sistem dapat memberikan rekomendasi jurusan kuliah yang sesuai.
|
||||
|
||||
<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 jika ingin membuat rekomendasi baru. Jika ingin melihat hasil sebelumnya, klik menu <b>Hasil Rekomendasi</b> pada navbar.
|
||||
</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">
|
||||
|
||||
<!-- Kondisi ekonomi -->
|
||||
<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>
|
||||
<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>
|
||||
|
|
@ -80,14 +184,18 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Minat dan bakat -->
|
||||
<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 & Bakat</h2>
|
||||
<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>
|
||||
|
|
@ -103,6 +211,7 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
<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>
|
||||
|
|
@ -118,6 +227,7 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
<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>
|
||||
|
|
@ -133,6 +243,7 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
<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>
|
||||
|
|
@ -148,6 +259,7 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
<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>
|
||||
|
|
@ -161,8 +273,12 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Nilai rapor -->
|
||||
<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>
|
||||
<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>
|
||||
|
|
@ -172,18 +288,22 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
<label class="block text-xs font-bold text-slate-500 mb-2">Agama</label>
|
||||
<input type="number" id="agama" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">PKN</label>
|
||||
<input type="number" id="pkn" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">B. Indonesia</label>
|
||||
<input type="number" id="bahasa_indonesia" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">B. Inggris</label>
|
||||
<input type="number" id="bahasa_inggris" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">Matematika</label>
|
||||
<input type="number" id="matematika" 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">
|
||||
|
|
@ -193,18 +313,22 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
<label class="block text-xs font-bold text-slate-500 mb-2">Fisika</label>
|
||||
<input type="number" id="fisika" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">Kimia</label>
|
||||
<input type="number" id="kimia" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">Biologi</label>
|
||||
<input type="number" id="biologi" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">Sejarah</label>
|
||||
<input type="number" id="sejarah" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">Geografi</label>
|
||||
<input type="number" id="geografi" 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">
|
||||
|
|
@ -214,18 +338,22 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
<label class="block text-xs font-bold text-slate-500 mb-2">Ekonomi</label>
|
||||
<input type="number" id="ekonomi_mapel" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">Sosiologi</label>
|
||||
<input type="number" id="sosiologi" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">Seni Budaya</label>
|
||||
<input type="number" id="seni_budaya" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">PJOK</label>
|
||||
<input type="number" id="pjok" 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>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">Informatika</label>
|
||||
<input type="number" id="informatika" 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">
|
||||
|
|
@ -233,10 +361,11 @@ class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-4 text-base f
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Tombol -->
|
||||
<div class="flex flex-col sm:flex-row gap-3 pt-2">
|
||||
<a href="{{ route('student.profile') }}"
|
||||
class="w-full sm:w-1/3 text-center bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold py-4 rounded-2xl transition">
|
||||
Batal
|
||||
Kembali
|
||||
</a>
|
||||
|
||||
<button id="btn-submit"
|
||||
|
|
@ -249,27 +378,43 @@ class="w-full sm:w-2/3 bg-emerald-600 hover:bg-emerald-700 text-white font-bold
|
|||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-[#062C22] text-white py-16 px-6 mt-16">
|
||||
<div class="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">ASGARD.</span>
|
||||
</div>
|
||||
<span class="font-black text-2xl tracking-tighter uppercase italic">
|
||||
ASGARD.
|
||||
</span>
|
||||
|
||||
<p class="text-slate-300 text-sm leading-relaxed max-w-xs">
|
||||
Sistem pakar rekomendasi jurusan kuliah SMAN 4 Jember berbasis data-driven.
|
||||
Sistem rekomendasi jurusan kuliah SMAN 4 Jember berbasis profil siswa dan data akademik.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">Informasi</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">
|
||||
Informasi
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li><a href="{{ route('panduan') }}" class="hover:text-white transition">Panduan Sistem</a></li>
|
||||
<li><a href="#" class="hover:text-white transition">Kebijakan Privasi</a></li>
|
||||
<li>
|
||||
<a href="{{ route('panduan') }}" class="hover:text-white transition">
|
||||
Panduan Sistem
|
||||
</a>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<a href="#" class="hover:text-white transition">
|
||||
Kebijakan Privasi
|
||||
</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">Sekolah</h5>
|
||||
<h5 class="font-bold text-xs uppercase tracking-widest text-emerald-400 mb-6">
|
||||
Sekolah
|
||||
</h5>
|
||||
|
||||
<ul class="space-y-4 text-sm text-slate-300">
|
||||
<li>SMAN 4 Jember</li>
|
||||
<li>Jl. Wolter Monginsidi No.24</li>
|
||||
|
|
@ -279,7 +424,7 @@ class="w-full sm:w-2/3 bg-emerald-600 hover:bg-emerald-700 text-white font-bold
|
|||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto mt-16 pt-8 border-t border-white/10 text-center">
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.5em] text-slate-400">
|
||||
<p class="text-[10px] font-medium uppercase tracking-[0.35em] text-slate-400">
|
||||
© 2026 GALANG SEFIAN ADJI - POLITEKNIK NEGERI JEMBER
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -289,10 +434,19 @@ class="w-full sm:w-2/3 bg-emerald-600 hover:bg-emerald-700 text-white font-bold
|
|||
const alertBox = document.getElementById('alert-box');
|
||||
const btnSubmit = document.getElementById('btn-submit');
|
||||
|
||||
const routeLogin = @json(route('login'));
|
||||
const routeDashboard = @json(route('student.profile'));
|
||||
const routePrediction = @json(route('student.prediction'));
|
||||
const routeAdminDashboard = @json(route('admin.dashboard'));
|
||||
|
||||
function showError(message) {
|
||||
alertBox.classList.remove('hidden');
|
||||
alertBox.textContent = message;
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
function hideError() {
|
||||
|
|
@ -314,16 +468,44 @@ function getUserRole() {
|
|||
|
||||
function redirectByRole(role) {
|
||||
if (role === 'admin') {
|
||||
window.location.href = "{{ route('admin.dashboard') }}";
|
||||
window.location.href = routeAdminDashboard;
|
||||
return;
|
||||
}
|
||||
|
||||
if (role === 'siswa') {
|
||||
window.location.href = "{{ route('student.profile') }}";
|
||||
window.location.href = routeDashboard;
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = "{{ route('login') }}";
|
||||
window.location.href = routeLogin;
|
||||
}
|
||||
|
||||
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 = saved.url;
|
||||
return;
|
||||
}
|
||||
|
||||
if (saved && saved.id) {
|
||||
window.location.href = `/student/result/${saved.id}`;
|
||||
return;
|
||||
}
|
||||
|
||||
alert('Belum ada hasil rekomendasi yang bisa dibuka. Silakan lakukan cek rekomendasi terlebih dahulu.');
|
||||
window.location.href = routePrediction;
|
||||
}
|
||||
|
||||
function validateForm(payload) {
|
||||
|
|
@ -341,6 +523,7 @@ function validateForm(payload) {
|
|||
}
|
||||
|
||||
const value = Number(payload[field]);
|
||||
|
||||
if (isNaN(value) || value < 0 || value > 100) {
|
||||
return `Nilai ${field.replaceAll('_', ' ')} harus berada pada rentang 0 sampai 100.`;
|
||||
}
|
||||
|
|
@ -355,6 +538,7 @@ function validateForm(payload) {
|
|||
}
|
||||
|
||||
const questionIds = ['q1', 'q2', 'q3', 'q4', 'q5'];
|
||||
|
||||
for (const id of questionIds) {
|
||||
if (!document.getElementById(id).value) {
|
||||
return 'Semua pertanyaan minat dan bakat wajib diisi.';
|
||||
|
|
@ -365,11 +549,20 @@ function validateForm(payload) {
|
|||
}
|
||||
|
||||
function calculateRiasecScores() {
|
||||
const scores = { r: 0, i: 0, a: 0, s: 0, e: 0, c: 0 };
|
||||
const scores = {
|
||||
r: 0,
|
||||
i: 0,
|
||||
a: 0,
|
||||
s: 0,
|
||||
e: 0,
|
||||
c: 0
|
||||
};
|
||||
|
||||
const questionIds = ['q1', 'q2', 'q3', 'q4', 'q5'];
|
||||
|
||||
questionIds.forEach(id => {
|
||||
const value = document.getElementById(id).value;
|
||||
|
||||
if (value && scores.hasOwnProperty(value)) {
|
||||
scores[value] += 20;
|
||||
}
|
||||
|
|
@ -397,7 +590,8 @@ function calculateRiasecScores() {
|
|||
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('user_role');
|
||||
window.location.href = "{{ route('login') }}";
|
||||
localStorage.removeItem('asgard_last_result');
|
||||
window.location.href = routeLogin;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
|
@ -405,12 +599,13 @@ function calculateRiasecScores() {
|
|||
const role = getUserRole();
|
||||
|
||||
if (!token) {
|
||||
window.location.href = "{{ route('login') }}";
|
||||
window.location.href = routeLogin;
|
||||
return;
|
||||
}
|
||||
|
||||
if (role !== 'siswa') {
|
||||
redirectByRole(role);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -422,9 +617,11 @@ function calculateRiasecScores() {
|
|||
|
||||
if (!token) {
|
||||
showError('Sesi login tidak ditemukan. Silakan login kembali.');
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = "{{ route('login') }}";
|
||||
window.location.href = routeLogin;
|
||||
}, 1200);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -433,7 +630,7 @@ function calculateRiasecScores() {
|
|||
return;
|
||||
}
|
||||
|
||||
btnSubmit.innerHTML = 'Memeriksa Rekomendasi...';
|
||||
btnSubmit.innerHTML = 'Memproses Rekomendasi...';
|
||||
btnSubmit.disabled = true;
|
||||
|
||||
const scores = calculateRiasecScores();
|
||||
|
|
@ -464,9 +661,10 @@ function calculateRiasecScores() {
|
|||
};
|
||||
|
||||
const validationError = validateForm(payload);
|
||||
|
||||
if (validationError) {
|
||||
showError(validationError);
|
||||
btnSubmit.innerHTML = 'Cek Rekomendasi';
|
||||
btnSubmit.innerHTML = 'Proses Rekomendasi';
|
||||
btnSubmit.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
|
@ -496,8 +694,9 @@ function calculateRiasecScores() {
|
|||
localStorage.removeItem('user_role');
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.href = "{{ route('login') }}";
|
||||
window.location.href = routeLogin;
|
||||
}, 1200);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -507,10 +706,11 @@ function calculateRiasecScores() {
|
|||
}
|
||||
|
||||
showError(result.message || 'Terjadi kesalahan saat memproses rekomendasi.');
|
||||
|
||||
} catch (error) {
|
||||
showError('Kesalahan jaringan. Pastikan Laravel dan API Flask sedang berjalan.');
|
||||
} finally {
|
||||
btnSubmit.innerHTML = 'Cek Rekomendasi';
|
||||
btnSubmit.innerHTML = 'Proses Rekomendasi';
|
||||
btnSubmit.disabled = false;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue