diff --git a/public/.htaccess b/.htaccess similarity index 100% rename from public/.htaccess rename to .htaccess diff --git a/app/Http/Controllers/Admin/AdminController.php b/app/Http/Controllers/Admin/AdminController.php index 63a8a45..147fd66 100644 --- a/app/Http/Controllers/Admin/AdminController.php +++ b/app/Http/Controllers/Admin/AdminController.php @@ -7,16 +7,43 @@ use App\Models\Kelas; use App\Models\Mapel; use App\Models\Challenge; +use App\Models\Leaderboard; use Illuminate\Support\Facades\Auth; class AdminController extends Controller { public function dashboard(){ - $totalGuru = Guru::count(); + $totalGuru = Guru::count(); $totalSiswa = Siswa::count(); $totalKelas = Kelas::count(); $totalMapel = Mapel::count(); - $chartData = Kelas::withCount('siswa')->get(); + $chartData = Kelas::withCount('siswa')->get(); $latestChallenges = Challenge::latest()->take(3)->get(); - return view('admin.dashboard', compact('totalGuru','totalSiswa','totalKelas','totalMapel','chartData','latestChallenges')); + + // Leaderboard top 10 + $leaderboard = Leaderboard::with(['siswa', 'kelas']) + ->orderBy('total_exp', 'desc') + ->take(10) + ->get() + ->map(function ($lb, $i) { + return [ + 'ranking' => $i + 1, + 'nama' => optional($lb->siswa)->nama ?? '-', + 'kelas' => optional($lb->kelas)->nama_kelas ?? '-', + 'total_exp' => $lb->total_exp, + 'foto' => optional($lb->siswa)->foto_profil, + 'semester' => $lb->semester, + 'tahun' => $lb->tahun_ajaran, + ]; + }); + + $firstLb = Leaderboard::orderBy('total_exp', 'desc')->first(); + $semester = $firstLb->semester ?? '-'; + $tahunAjaran = $firstLb->tahun_ajaran ?? '-'; + + return view('admin.dashboard', compact( + 'totalGuru','totalSiswa','totalKelas','totalMapel', + 'chartData','latestChallenges', + 'leaderboard','semester','tahunAjaran' + )); } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/ChallengeController.php b/app/Http/Controllers/Admin/ChallengeController.php index 7d88cc0..07fc77d 100644 --- a/app/Http/Controllers/Admin/ChallengeController.php +++ b/app/Http/Controllers/Admin/ChallengeController.php @@ -4,188 +4,48 @@ use App\Http\Controllers\Controller; use App\Models\Challenge; -use App\Models\SoalChallenge; -use App\Models\Kelas; -use App\Models\Badge; +use App\Models\PesertaChallenge; +use App\Models\Guru; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\DB; class ChallengeController extends Controller { public function index(Request $request) - { - $query = Challenge::with(['kelas', 'soal']) - ->withCount('soal'); +{ + $query = Challenge::with(['guru', 'kelas']) + ->withCount('soal as soal_count'); - if ($request->filled('search')) { - $query->where('judul_challenge', 'like', '%' . $request->search . '%'); - } - - $challenges = $query->orderBy('created_at', 'desc') - ->paginate(10) - ->appends($request->all()); - - $kelas = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get(); - $badges = Badge::all(); - - return view('admin.challenge.index', compact('challenges', 'kelas', 'badges')); + // Filter by guru + if ($request->filled('id_guru')) { + $query->where('id_guru', $request->id_guru); } - public function store(Request $request) - { - $request->validate([ - 'judul_challenge' => 'required|string|max:200', - 'deskripsi' => 'nullable|string', - 'exp' => 'required|integer|min:0', - 'tenggat_waktu' => 'required|date|after:now', - 'durasi_pengerjaan' => 'required|integer|min:1|max:360', - 'id_kelas' => 'required|array|min:1', - 'id_kelas.*' => 'exists:kelas,id_kelas', - // Soal - 'pertanyaan' => 'required|array|min:1', - 'pertanyaan.*' => 'required|string', - 'opsi_a.*' => 'required|string', - 'opsi_b.*' => 'required|string', - 'opsi_c.*' => 'required|string', - 'opsi_d.*' => 'required|string', - 'jawaban_benar.*' => 'required|in:A,B,C,D', - 'exp_per_soal.*' => 'required|integer|min:0', - ], [ - 'tenggat_waktu.after' => 'Tenggat waktu harus lebih dari sekarang.', - 'pertanyaan.required' => 'Minimal harus ada 1 soal.', - 'id_kelas.required' => 'Pilih minimal 1 kelas.', - ]); - - DB::transaction(function () use ($request) { - $admin = Auth::guard('admin')->user(); - - $challenge = Challenge::create([ - 'id_admin' => $admin->id_admin, - 'judul_challenge' => $request->judul_challenge, - 'deskripsi' => $request->deskripsi, - 'exp' => $request->exp, - 'id_badge' => $request->id_badge, - 'tenggat_waktu' => $request->tenggat_waktu, - 'durasi_pengerjaan' => $request->durasi_pengerjaan, - ]); - - // Attach ke kelas - $challenge->kelas()->sync($request->id_kelas); - - $challenge->soal()->delete(); - - // Simpan soal - foreach ($request->pertanyaan as $i => $pertanyaan) { - SoalChallenge::create([ - 'id_challenge' => $challenge->id_challenge, - 'pertanyaan' => $pertanyaan, - 'opsi_a' => $request->opsi_a[$i], - 'opsi_b' => $request->opsi_b[$i], - 'opsi_c' => $request->opsi_c[$i], - 'opsi_d' => $request->opsi_d[$i], - 'jawaban_benar' => $request->jawaban_benar[$i], - 'exp_per_soal' => $request->exp_per_soal[$i], - ]); - } - }); - - return redirect()->route('admin.challenge.index') - ->with('success', 'Challenge berhasil dibuat!'); + // Search judul + if ($request->filled('search')) { + $query->where('judul_challenge', 'like', '%' . $request->search . '%'); } + $challenges = $query->latest() + ->paginate(10) + ->appends($request->all()); // ganti withQueryString() → appends() + + $guruList = Guru::orderBy('nama')->get(); // ambil semua guru, pakai model import + + return view('admin.challenge.index', compact('challenges', 'guruList')); +} + public function show($id) { - $challenge = Challenge::with(['kelas', 'soal'])->findOrFail($id); + $challenge = Challenge::with(['kelas', 'soal', 'guru', 'peserta.siswa']) + ->findOrFail($id); + return view('admin.challenge.show', compact('challenge')); } - public function edit($id) - { - $challenge = Challenge::with(['kelas', 'soal'])->findOrFail($id); - $kelas = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get(); - $badges = Badge::all(); - return view('admin.challenge.edit', compact('challenge', 'kelas', 'badges')); - } - - public function update(Request $request, $id) - { - $challenge = Challenge::findOrFail($id); - - $request->validate([ - 'judul_challenge' => 'required|string|max:200', - 'deskripsi' => 'nullable|string', - 'exp' => 'required|integer|min:0', - 'tenggat_waktu' => 'required|date', - 'durasi_pengerjaan' => 'required|integer|min:1|max:360', - 'id_kelas' => 'required|array|min:1', - 'id_kelas.*' => 'exists:kelas,id_kelas', - 'pertanyaan' => 'required|array|min:1', - 'pertanyaan.*' => 'required|string', - 'opsi_a.*' => 'required|string', - 'opsi_b.*' => 'required|string', - 'opsi_c.*' => 'required|string', - 'opsi_d.*' => 'required|string', - 'jawaban_benar.*' => 'required|in:A,B,C,D', - 'exp_per_soal.*' => 'required|integer|min:0', - ]); - - DB::transaction(function () use ($request, $challenge) { - $challenge->update([ - 'judul_challenge' => $request->judul_challenge, - 'deskripsi' => $request->deskripsi, - 'exp' => $request->exp, - 'id_badge' => $request->id_badge, - 'tenggat_waktu' => $request->tenggat_waktu, - 'durasi_pengerjaan' => $request->durasi_pengerjaan, - ]); - - $challenge->kelas()->sync($request->id_kelas); - - // Hapus soal lama, insert ulang - SoalChallenge::where('id_challenge', $challenge->id_challenge)->delete(); - - foreach ($request->pertanyaan as $i => $pertanyaan) { - SoalChallenge::create([ - 'id_challenge' => $challenge->id_challenge, - 'pertanyaan' => $pertanyaan, - 'opsi_a' => $request->opsi_a[$i], - 'opsi_b' => $request->opsi_b[$i], - 'opsi_c' => $request->opsi_c[$i], - 'opsi_d' => $request->opsi_d[$i], - 'jawaban_benar' => $request->jawaban_benar[$i], - 'exp_per_soal' => $request->exp_per_soal[$i], - ]); - } - }); - - return redirect()->route('admin.challenge.index') - ->with('success', 'Challenge berhasil diupdate!'); - } - public function destroy($id) { Challenge::findOrFail($id)->delete(); - return redirect()->route('admin.challenge.index') ->with('success', 'Challenge berhasil dihapus.'); } - - /** - * AJAX — return data challenge untuk modal edit - */ - public function editData($id) - { - $challenge = Challenge::with(['kelas', 'soal'])->findOrFail($id); - - return response()->json([ - 'judul_challenge' => $challenge->judul_challenge, - 'deskripsi' => $challenge->deskripsi, - 'exp' => $challenge->exp, - 'tenggat_waktu' => $challenge->tenggat_waktu, - 'durasi_pengerjaan' => $challenge->durasi_pengerjaan, - 'kelas' => $challenge->kelas->pluck('id_kelas'), - 'soal' => $challenge->soal, - ]); - } } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/GuruController.php b/app/Http/Controllers/Admin/GuruController.php index 4f4a375..db25eb3 100644 --- a/app/Http/Controllers/Admin/GuruController.php +++ b/app/Http/Controllers/Admin/GuruController.php @@ -161,10 +161,22 @@ public function downloadPdf(Request $request) } $gurus = $query->get(); - $pdf = Pdf::loadView('admin.guru.pdf', compact('gurus')) - ->setPaper('a4', 'landscape'); + // Override DomPDF options langsung + $options = new \Dompdf\Options(); + $options->setChroot(base_path('public')); + $options->setIsRemoteEnabled(true); - return $pdf->download('daftar-guru-' . date('Ymd') . '.pdf'); + $dompdf = new \Dompdf\Dompdf($options); + + $html = view('admin.guru.pdf', compact('gurus'))->render(); + $dompdf->loadHtml($html); + $dompdf->setPaper('A4', 'landscape'); + $dompdf->render(); + + return response($dompdf->output(), 200, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => 'attachment; filename="daftar-guru-' . date('Ymd') . '.pdf"', + ]); } public function downloadExcel(Request $request) diff --git a/app/Http/Controllers/Admin/KelasController.php b/app/Http/Controllers/Admin/KelasController.php index d2d124a..d747428 100644 --- a/app/Http/Controllers/Admin/KelasController.php +++ b/app/Http/Controllers/Admin/KelasController.php @@ -90,7 +90,7 @@ public function destroy($id_kelas) ->with('success', 'Data kelas berhasil dihapus!'); } - public function downloadPdf(Request $request) +public function downloadPdf(Request $request) { $query = Kelas::query(); if ($request->filled('search')) { @@ -99,11 +99,20 @@ public function downloadPdf(Request $request) ->orWhere('id_kelas', 'like', "%$search%"); } $kelass = $query->get(); - - $pdf = Pdf::loadView('admin.kelas.pdf', compact('kelass')) - ->setPaper('a4', 'portrait'); - - return $pdf->download('daftar-kelas-' . date('Ymd') . '.pdf'); + + $options = new \Dompdf\Options(); + $options->setChroot(base_path('public')); + $options->setIsRemoteEnabled(true); + + $dompdf = new \Dompdf\Dompdf($options); + $dompdf->loadHtml(view('admin.kelas.pdf', compact('kelass'))->render()); + $dompdf->setPaper('A4', 'portrait'); + $dompdf->render(); + + return response($dompdf->output(), 200, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => 'attachment; filename="daftar-kelas-' . date('Ymd') . '.pdf"', + ]); } public function downloadExcel(Request $request) diff --git a/app/Http/Controllers/Admin/LeaderboardController.php b/app/Http/Controllers/Admin/LeaderboardController.php index 20a27c2..f63bf06 100644 --- a/app/Http/Controllers/Admin/LeaderboardController.php +++ b/app/Http/Controllers/Admin/LeaderboardController.php @@ -7,9 +7,72 @@ use App\Models\Kelas; use Carbon\Carbon; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; class LeaderboardController extends Controller { + private function hitungExpSiswa(\App\Models\Siswa $s, Carbon $now): int + { + // ── 1. EXP dari challenge ───────────────────────────────────────── + $expChallenge = 0; + $pesertaList = DB::table('peserta_challenges') + ->where('id_siswa', $s->id_siswa) + ->get(); + + foreach ($pesertaList as $peserta) { + $jawaban = json_decode($peserta->jawaban, true) ?? []; + $soalList = DB::table('soal_challenge') + ->where('id_challenge', $peserta->id_challenge) + ->get(); + + foreach ($soalList as $soal) { + $jwb = $jawaban[$soal->id_soal] ?? null; + if ($jwb && strtoupper($jwb) === strtoupper($soal->jawaban_benar)) { + $expChallenge += 1; // +1 per soal benar + } + } + } + + // ── 2. EXP dari tugas ───────────────────────────────────────────── + $semuaTugasIdKelas = DB::table('tugas') + ->join('mengajars', 'tugas.id_mengajar', '=', 'mengajars.id_mengajar') + ->where('mengajars.id_kelas', $s->id_kelas) + ->select('tugas.id_tugas', 'tugas.deadline') + ->get(); + + $expTugas = 0; + foreach ($semuaTugasIdKelas as $tugas) { + $deadline = Carbon::parse($tugas->deadline); + $pengumpulan = DB::table('pengumpulan_tugas') + ->where('id_tugas', $tugas->id_tugas) + ->where('id_siswa', $s->id_siswa) + ->first(); + + if ($pengumpulan && $pengumpulan->lampiran_tugas !== null) { + $tanggalSubmit = Carbon::parse($pengumpulan->tanggal_submit); + + if ($tanggalSubmit->lessThanOrEqualTo($deadline)) { + $expTugas += 10; // Tepat waktu → +10 + } else { + $hariTerlambat = $deadline->diffInDays($tanggalSubmit); // ← dibalik + + if ($hariTerlambat <= 1) { + $expTugas += 5; // Terlambat ≤ 1 hari → +5 + } elseif ($hariTerlambat <= 3) { + $expTugas += 1; // Terlambat 2-3 hari → +3 + } else { + $expTugas -= 5; // Lewat grace period > 3 hari → dianggap tidak kumpul + } + } + } elseif ($now->greaterThan($deadline)) { + // Tidak kumpul sama sekali & deadline lewat → -5 + $expTugas -= 5; + } + } + + return max(0, $expChallenge + $expTugas); + } + public function index(Request $request) { $now = Carbon::now(); @@ -17,35 +80,38 @@ public function index(Request $request) $tahunAjaran = $request->input('tahun_ajaran', $now->month >= 7 ? $now->year . '/' . ($now->year + 1) : ($now->year - 1) . '/' . $now->year); - $idKelas = $request->input('id_kelas'); + $idKelas = $request->input('id_kelas'); $kelasList = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get(); - $query = Leaderboard::with(['siswa', 'kelas']) - ->where('semester', $semester) - ->where('tahun_ajaran', $tahunAjaran); - - if ($idKelas) { - $query->where('id_kelas', $idKelas); - } - - $leaderboard = $query->orderBy('total_exp', 'desc')->get() - ->map(function ($item, $i) { - return [ - 'ranking' => $i + 1, - 'nama' => optional($item->siswa)->nama ?? '-', - 'nisn' => optional($item->siswa)->nisn ?? '-', - 'nama_kelas' => optional($item->kelas)->nama_kelas ?? '-', - 'exp' => $item->total_exp, - ]; - }); - - // Tahun ajaran list untuk dropdown (5 tahun ke belakang) $tahunList = []; for ($y = $now->year; $y >= $now->year - 4; $y--) { $tahunList[] = $y . '/' . ($y + 1); } + $query = \App\Models\Siswa::with('kelas'); + if ($idKelas) { + $query->where('id_kelas', $idKelas); + } + $semuaSiswa = $query->get(); + + $leaderboard = $semuaSiswa->map(function ($s) use ($now) { + return [ + 'id_siswa' => $s->id_siswa, + 'nama' => $s->nama, + 'nisn' => $s->nisn, + 'foto_profil' => $s->foto_profil, + 'nama_kelas' => $s->kelas->nama_kelas ?? '-', + 'exp' => $this->hitungExpSiswa($s, $now), + ]; + }) + ->sortByDesc('exp') + ->values() + ->map(function ($item, $i) { + $item['ranking'] = $i + 1; + return $item; + }); + return view('admin.leaderboard.index', compact( 'leaderboard', 'kelasList', 'semester', 'tahunAjaran', 'idKelas', 'tahunList' )); diff --git a/app/Http/Controllers/Admin/LoginController.php b/app/Http/Controllers/Admin/LoginController.php index 5348f78..57b385d 100644 --- a/app/Http/Controllers/Admin/LoginController.php +++ b/app/Http/Controllers/Admin/LoginController.php @@ -33,6 +33,11 @@ public function loginAdmin(Request $request) ])->withInput($request->except('password')); } + public function __construct() + { + $this->middleware('guest:admin')->only(['showLoginForm', 'loginAdmin']); + } + public function logout(Request $request) { Auth::guard('admin')->logout(); diff --git a/app/Http/Controllers/Admin/MapelController.php b/app/Http/Controllers/Admin/MapelController.php index 08d957a..e8052b8 100644 --- a/app/Http/Controllers/Admin/MapelController.php +++ b/app/Http/Controllers/Admin/MapelController.php @@ -130,7 +130,7 @@ public function destroy($id) ->with('success', 'Data mata pelajaran berhasil dihapus!'); } - public function downloadPdf(Request $request) +public function downloadPdf(Request $request) { $query = Mapel::with('kelas'); if ($request->filled('search')) { @@ -145,11 +145,20 @@ public function downloadPdf(Request $request) }); } $mapels = $query->get(); - - $pdf = Pdf::loadView('admin.mapel.pdf', compact('mapels')) - ->setPaper('a4', 'portrait'); - - return $pdf->download('daftar-mapel-' . date('Ymd') . '.pdf'); + + $options = new \Dompdf\Options(); + $options->setChroot(base_path('public')); + $options->setIsRemoteEnabled(true); + + $dompdf = new \Dompdf\Dompdf($options); + $dompdf->loadHtml(view('admin.mapel.pdf', compact('mapels'))->render()); + $dompdf->setPaper('A4', 'portrait'); + $dompdf->render(); + + return response($dompdf->output(), 200, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => 'attachment; filename="daftar-mapel-' . date('Ymd') . '.pdf"', + ]); } public function downloadExcel(Request $request) diff --git a/app/Http/Controllers/Admin/NotifikasiController.php b/app/Http/Controllers/Admin/NotifikasiController.php index ddf9e0d..3907e5d 100644 --- a/app/Http/Controllers/Admin/NotifikasiController.php +++ b/app/Http/Controllers/Admin/NotifikasiController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Controller; use App\Models\Materi; use App\Models\Tugas; +use App\Models\PesertaChallenge; use Carbon\Carbon; class NotifikasiController extends Controller @@ -41,7 +42,23 @@ public function index() 'time_raw'=> $t->created_at->toIso8601String(), ]); - $notifications = $materiBaru->concat($tugasBaru) + // Siswa yang mengumpulkan challenge + $challengeKumpul = PesertaChallenge::with(['siswa', 'challenge']) + ->where('waktu_submit', '>=', $since) + ->orderBy('waktu_submit', 'desc') + ->get() + ->map(fn($p) => [ + 'type' => 'challenge', + 'title' => 'Challenge Dikumpulkan', + 'message' => optional($p->siswa)->nama . ' mengumpulkan challenge', + 'sub' => optional($p->challenge)->judul_challenge ?? '-', + 'time' => Carbon::parse($p->waktu_submit)->diffForHumans(), + 'time_raw'=> Carbon::parse($p->waktu_submit)->toIso8601String(), + ]); + + $notifications = $materiBaru + ->concat($tugasBaru) + ->concat($challengeKumpul) ->sortByDesc('time_raw') ->values(); diff --git a/app/Http/Controllers/Admin/ProfileController.php b/app/Http/Controllers/Admin/ProfileController.php index 8341ba1..aeb37db 100644 --- a/app/Http/Controllers/Admin/ProfileController.php +++ b/app/Http/Controllers/Admin/ProfileController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Storage; @@ -16,47 +17,45 @@ public function edit() return view('admin.profile.edit', compact('admin')); } - public function updateAjax(Request $request) - { - $admin = Auth::guard('admin')->user(); +public function updateAjax(Request $request) +{ + $admin = Auth::guard('admin')->user(); - $request->validate([ - 'username' => 'required|string|max:100|unique:admins,username,' . $admin->id_admin . ',id_admin', - 'password' => 'nullable|min:6|confirmed', - 'password_confirmation' => 'nullable', - 'foto_profil' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048', - ], [ - 'username.required' => 'Username wajib diisi.', - 'username.unique' => 'Username sudah digunakan.', - 'password.min' => 'Password minimal 6 karakter.', - 'password.confirmed' => 'Konfirmasi password tidak cocok.', - 'foto_profil.image' => 'File harus berupa gambar.', - 'foto_profil.max' => 'Ukuran foto maksimal 2MB.', - ]); + $request->validate([ + 'password' => 'nullable|min:6|confirmed', + 'password_confirmation' => 'nullable', + 'foto_profil' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048', + ], [ + 'password.min' => 'Password minimal 6 karakter.', + 'password.confirmed' => 'Konfirmasi password tidak cocok.', + 'foto_profil.image' => 'File harus berupa gambar.', + 'foto_profil.max' => 'Ukuran foto maksimal 2MB.', + ]); - $data = ['username' => $request->username]; + $data = []; + $fotoUrl = null; - if ($request->filled('password')) { - $data['password'] = Hash::make($request->password); - } - - $fotoUrl = null; - if ($request->hasFile('foto_profil')) { - if ($admin->foto_profil && Storage::disk('public')->exists($admin->foto_profil)) { - Storage::disk('public')->delete($admin->foto_profil); - } - $path = $request->file('foto_profil')->store('foto_profil/admin', 'public'); - $data['foto_profil'] = $path; - $fotoUrl = Storage::url($path); - } - - $admin->update($data); - - return response()->json([ - 'success' => true, - 'message' => 'Profil berhasil diperbarui!', - 'foto_url' => $fotoUrl ?? ($admin->foto_profil ? Storage::url($admin->foto_profil) : null), - 'username' => $admin->fresh()->username, - ]); + if ($request->filled('password')) { + $data['password'] = Hash::make($request->password); } + + if ($request->hasFile('foto_profil')) { + if ($admin->foto_profil && Storage::disk('public')->exists($admin->foto_profil)) { + Storage::disk('public')->delete($admin->foto_profil); + } + $path = $request->file('foto_profil')->store('foto_profil/admin', 'public'); + $data['foto_profil'] = $path; + $fotoUrl = '/E31230356/storage/app/public/' . $path; + } + + if (!empty($data)) { + $admin->update($data); + } + + return response()->json([ + 'success' => true, + 'message' => 'Profil berhasil diperbarui!', + 'foto_url' => $fotoUrl ?? ($admin->foto_profil ? '/E31230356/storage/app/public/' . $admin->foto_profil : null), + ]); +} } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/SiswaController.php b/app/Http/Controllers/Admin/SiswaController.php index ca34179..984f0ca 100644 --- a/app/Http/Controllers/Admin/SiswaController.php +++ b/app/Http/Controllers/Admin/SiswaController.php @@ -7,6 +7,7 @@ use App\Models\Kelas; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Validator; use Barryvdh\DomPDF\Facade\Pdf; class SiswaController extends Controller @@ -15,25 +16,21 @@ public function index(Request $request) { $query = Siswa::with('kelas'); - // SEARCH - if ($request->has('search')) { + if ($request->filled('search')) { $search = $request->search; - $query->where('nama', 'like', "%$search%") + $query->where(function($q) use ($search) { + $q->where('nama', 'like', "%$search%") ->orWhere('nisn', 'like', "%$search%"); + }); } - // FILTER BY KELAS - if ($request->has('filter_kelas') && $request->filter_kelas != '') { + if ($request->filled('filter_kelas')) { $query->where('id_kelas', $request->filter_kelas); } - // SHOW PER PAGE $perPage = $request->get('perPage', 10); - - $siswas = $query->paginate($perPage)->appends($request->all()); - - // Ambil semua kelas untuk dropdown filter - $kelass = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get(); + $siswas = $query->paginate($perPage)->appends($request->all()); + $kelass = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get(); return view('admin.siswa.index', compact('siswas', 'kelass')); } @@ -41,31 +38,31 @@ public function index(Request $request) public function store(Request $request) { $validated = $request->validate([ - 'nisn' => 'required|string|max:20|unique:siswas,nisn', - 'nama' => 'required|string|max:100', - 'tempat_lahir' => 'required|string|max:50', + 'nisn' => 'required|string|max:20|unique:siswas,nisn', + 'nama' => 'required|string|max:100', + 'tempat_lahir' => 'required|string|max:50', 'tanggal_lahir' => 'required|date', - 'id_kelas' => 'required|exists:kelas,id_kelas', - 'password' => 'required|string|min:6', + 'id_kelas' => 'required|exists:kelas,id_kelas', + 'password' => 'required|string|min:6', ], [ - 'nisn.required' => 'NISN wajib diisi', - 'nisn.unique' => 'NISN sudah terdaftar', - 'nama.required' => 'Nama wajib diisi', - 'tempat_lahir.required' => 'Tempat lahir wajib diisi', + 'nisn.required' => 'NISN wajib diisi', + 'nisn.unique' => 'NISN sudah terdaftar', + 'nama.required' => 'Nama wajib diisi', + 'tempat_lahir.required' => 'Tempat lahir wajib diisi', 'tanggal_lahir.required' => 'Tanggal lahir wajib diisi', - 'id_kelas.required' => 'Kelas wajib dipilih', - 'id_kelas.exists' => 'Kelas tidak valid', - 'password.required' => 'Password wajib diisi', - 'password.min' => 'Password minimal 6 karakter', + 'id_kelas.required' => 'Kelas wajib dipilih', + 'id_kelas.exists' => 'Kelas tidak valid', + 'password.required' => 'Password wajib diisi', + 'password.min' => 'Password minimal 6 karakter', ]); Siswa::create([ - 'nisn' => $validated['nisn'], - 'nama' => $validated['nama'], - 'tempat_lahir' => $validated['tempat_lahir'], + 'nisn' => $validated['nisn'], + 'nama' => $validated['nama'], + 'tempat_lahir' => $validated['tempat_lahir'], 'tanggal_lahir' => $validated['tanggal_lahir'], - 'id_kelas' => $validated['id_kelas'], - 'password' => Hash::make($validated['password']), + 'id_kelas' => $validated['id_kelas'], + 'password' => Hash::make($validated['password']), ]); return redirect()->route('admin.siswa.index') @@ -76,28 +73,45 @@ public function update(Request $request, $id) { $siswa = Siswa::findOrFail($id); - $validated = $request->validate([ - 'nama' => 'required|string|max:100', - 'tempat_lahir' => 'required|string|max:50', + $validator = Validator::make($request->all(), [ + 'nama' => 'required|string|max:100', + 'tempat_lahir' => 'required|string|max:50', 'tanggal_lahir' => 'required|date', - 'id_kelas' => 'required|exists:kelas,id_kelas', - 'password' => 'nullable|string|min:6', + 'id_kelas' => 'required|exists:kelas,id_kelas', + 'password' => 'nullable|string|min:6', ], [ - 'nama.required' => 'Nama wajib diisi', - 'tempat_lahir.required' => 'Tempat lahir wajib diisi', + 'nama.required' => 'Nama wajib diisi', + 'tempat_lahir.required' => 'Tempat lahir wajib diisi', 'tanggal_lahir.required' => 'Tanggal lahir wajib diisi', - 'id_kelas.required' => 'Kelas wajib dipilih', - 'id_kelas.exists' => 'Kelas tidak valid', - 'password.min' => 'Password minimal 6 karakter', + 'id_kelas.required' => 'Kelas wajib dipilih', + 'id_kelas.exists' => 'Kelas tidak valid', + 'password.min' => 'Password minimal 6 karakter', ]); - $siswa->nama = $validated['nama']; - $siswa->tempat_lahir = $validated['tempat_lahir']; - $siswa->tanggal_lahir = $validated['tanggal_lahir']; - $siswa->id_kelas = $validated['id_kelas']; + if ($validator->fails()) { + return redirect()->back() + ->withErrors($validator) + ->withInput() + ->with('error_from', 'edit') + // Simpan data siswa yang sedang diedit ke session + // supaya JavaScript bisa mengisi form edit secara otomatis + ->with('edit_siswa', [ + 'id_siswa' => $siswa->id_siswa, + 'nisn' => $siswa->nisn, + 'nama' => $request->nama ?? $siswa->nama, + 'tempat_lahir' => $request->tempat_lahir ?? $siswa->tempat_lahir, + 'tanggal_lahir' => $request->tanggal_lahir ?? $siswa->tanggal_lahir, + 'id_kelas' => $request->id_kelas ?? $siswa->id_kelas, + ]); + } + + $siswa->nama = $request->nama; + $siswa->tempat_lahir = $request->tempat_lahir; + $siswa->tanggal_lahir = $request->tanggal_lahir; + $siswa->id_kelas = $request->id_kelas; if ($request->filled('password')) { - $siswa->password = Hash::make($validated['password']); + $siswa->password = Hash::make($request->password); } $siswa->save(); @@ -116,60 +130,69 @@ public function destroy($id) } public function downloadPdf(Request $request) -{ - $query = Siswa::with('kelas'); - if ($request->filled('search')) { - $search = $request->search; - $query->where('nama', 'like', "%$search%") - ->orWhere('nisn', 'like', "%$search%"); - } - if ($request->filled('filter_kelas')) { - $query->where('id_kelas', $request->filter_kelas); - } - $siswas = $query->get(); - - $pdf = Pdf::loadView('admin.siswa.pdf', compact('siswas')) - ->setPaper('a4', 'landscape'); - - return $pdf->download('daftar-siswa-' . date('Ymd') . '.pdf'); -} - -public function downloadExcel(Request $request) -{ - $query = Siswa::with('kelas'); - if ($request->filled('search')) { - $search = $request->search; - $query->where('nama', 'like', "%$search%") - ->orWhere('nisn', 'like', "%$search%"); - } - if ($request->filled('filter_kelas')) { - $query->where('id_kelas', $request->filter_kelas); - } - $siswas = $query->get(); - - $filename = 'daftar-siswa-' . date('Ymd') . '.csv'; - $headers = [ - 'Content-Type' => 'text/csv', - 'Content-Disposition' => "attachment; filename=\"$filename\"", - ]; - - $callback = function () use ($siswas) { - $file = fopen('php://output', 'w'); - fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF)); - fputcsv($file, ['No', 'NISN', 'Nama', 'Tempat Lahir', 'Tanggal Lahir', 'Kelas']); - foreach ($siswas as $i => $siswa) { - fputcsv($file, [ - $i + 1, - $siswa->nisn, - $siswa->nama, - $siswa->tempat_lahir, - \Carbon\Carbon::parse($siswa->tanggal_lahir)->format('d M Y'), - $siswa->kelas->tingkat . ' - ' . $siswa->kelas->nama_kelas, - ]); + { + $query = Siswa::with('kelas'); + if ($request->filled('search')) { + $search = $request->search; + $query->where('nama', 'like', "%$search%") + ->orWhere('nisn', 'like', "%$search%"); } - fclose($file); - }; + if ($request->filled('filter_kelas')) { + $query->where('id_kelas', $request->filter_kelas); + } + $siswas = $query->get(); - return response()->stream($callback, 200, $headers); -} + $options = new \Dompdf\Options(); + $options->setChroot(base_path('public')); + $options->setIsRemoteEnabled(true); + + $dompdf = new \Dompdf\Dompdf($options); + $dompdf->loadHtml(view('admin.siswa.pdf', compact('siswas'))->render()); + $dompdf->setPaper('A4', 'landscape'); + $dompdf->render(); + + return response($dompdf->output(), 200, [ + 'Content-Type' => 'application/pdf', + 'Content-Disposition' => 'attachment; filename="daftar-siswa-' . date('Ymd') . '.pdf"', + ]); + } + + public function downloadExcel(Request $request) + { + $query = Siswa::with('kelas'); + if ($request->filled('search')) { + $search = $request->search; + $query->where('nama', 'like', "%$search%") + ->orWhere('nisn', 'like', "%$search%"); + } + if ($request->filled('filter_kelas')) { + $query->where('id_kelas', $request->filter_kelas); + } + $siswas = $query->get(); + + $filename = 'daftar-siswa-' . date('Ymd') . '.csv'; + $headers = [ + 'Content-Type' => 'text/csv', + 'Content-Disposition' => "attachment; filename=\"$filename\"", + ]; + + $callback = function () use ($siswas) { + $file = fopen('php://output', 'w'); + fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF)); + fputcsv($file, ['No', 'NISN', 'Nama', 'Tempat Lahir', 'Tanggal Lahir', 'Kelas']); + foreach ($siswas as $i => $siswa) { + fputcsv($file, [ + $i + 1, + $siswa->nisn, + $siswa->nama, + $siswa->tempat_lahir, + \Carbon\Carbon::parse($siswa->tanggal_lahir)->format('d M Y'), + $siswa->kelas->tingkat . ' - ' . $siswa->kelas->nama_kelas, + ]); + } + fclose($file); + }; + + return response()->stream($callback, 200, $headers); + } } \ No newline at end of file diff --git a/app/Http/Controllers/Guru/ChallengeController.php b/app/Http/Controllers/Guru/ChallengeController.php new file mode 100644 index 0000000..e769f1e --- /dev/null +++ b/app/Http/Controllers/Guru/ChallengeController.php @@ -0,0 +1,190 @@ +user(); + + $query = Challenge::with(['kelas', 'soal']) + ->withCount('soal') + ->where('id_guru', $guru->id_guru); + + if ($request->filled('search')) { + $query->where('judul_challenge', 'like', '%' . $request->search . '%'); + } + + $challenges = $query->orderBy('created_at', 'desc') + ->paginate(10) + ->appends($request->all()); + + $kelas = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get(); + $badges = Badge::all(); + + return view('guru.challenge.index', compact('challenges', 'kelas', 'badges')); + } + + public function store(Request $request) + { + $request->validate([ + 'judul_challenge' => 'required|string|max:200', + 'deskripsi' => 'nullable|string', + 'tenggat_waktu' => 'required|date|after:now', + 'durasi_pengerjaan' => 'required|integer|min:1|max:360', + 'id_kelas' => 'required|array|min:1', + 'id_kelas.*' => 'exists:kelas,id_kelas', + 'pertanyaan' => 'required|array|min:1', + 'pertanyaan.*' => 'required|string', + 'opsi_a.*' => 'required|string', + 'opsi_b.*' => 'required|string', + 'opsi_c.*' => 'required|string', + 'opsi_d.*' => 'required|string', + 'jawaban_benar.*' => 'required|in:A,B,C,D', + ], [ + 'tenggat_waktu.after' => 'Tenggat waktu harus lebih dari sekarang.', + 'pertanyaan.required' => 'Minimal harus ada 1 soal.', + 'id_kelas.required' => 'Pilih minimal 1 kelas.', + ]); + + DB::transaction(function () use ($request) { + $guru = Auth::guard('guru')->user(); + + $challenge = Challenge::create([ + 'id_guru' => $guru->id_guru, + 'judul_challenge' => $request->judul_challenge, + 'deskripsi' => $request->deskripsi, + 'id_badge' => $request->id_badge, + 'tenggat_waktu' => $request->tenggat_waktu, + 'durasi_pengerjaan' => $request->durasi_pengerjaan, + ]); + + $challenge->kelas()->sync($request->id_kelas); + + foreach ($request->pertanyaan as $i => $pertanyaan) { + SoalChallenge::create([ + 'id_challenge' => $challenge->id_challenge, + 'pertanyaan' => $pertanyaan, + 'opsi_a' => $request->opsi_a[$i], + 'opsi_b' => $request->opsi_b[$i], + 'opsi_c' => $request->opsi_c[$i], + 'opsi_d' => $request->opsi_d[$i], + 'jawaban_benar' => $request->jawaban_benar[$i], + ]); + } + }); + + return redirect()->route('guru.challenge.index') + ->with('success', 'Challenge berhasil dibuat!'); + } + + public function show($id) + { + $guru = Auth::guard('guru')->user(); + $challenge = Challenge::with(['kelas', 'soal']) + ->where('id_guru', $guru->id_guru) + ->findOrFail($id); + + return view('guru.challenge.show', compact('challenge')); + } + + public function edit($id) + { + $guru = Auth::guard('guru')->user(); + $challenge = Challenge::with(['kelas', 'soal']) + ->where('id_guru', $guru->id_guru) + ->findOrFail($id); + + $kelas = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get(); + $badges = Badge::all(); + + return view('guru.challenge.edit', compact('challenge', 'kelas', 'badges')); + } + + public function update(Request $request, $id) + { + $guru = Auth::guard('guru')->user(); + $challenge = Challenge::where('id_guru', $guru->id_guru)->findOrFail($id); + + $request->validate([ + 'judul_challenge' => 'required|string|max:200', + 'deskripsi' => 'nullable|string', + 'tenggat_waktu' => 'required|date', + 'durasi_pengerjaan' => 'required|integer|min:1|max:360', + 'id_kelas' => 'required|array|min:1', + 'id_kelas.*' => 'exists:kelas,id_kelas', + 'pertanyaan' => 'required|array|min:1', + 'pertanyaan.*' => 'required|string', + 'opsi_a.*' => 'required|string', + 'opsi_b.*' => 'required|string', + 'opsi_c.*' => 'required|string', + 'opsi_d.*' => 'required|string', + 'jawaban_benar.*' => 'required|in:A,B,C,D', + ]); + + DB::transaction(function () use ($request, $challenge) { + $challenge->update([ + 'judul_challenge' => $request->judul_challenge, + 'deskripsi' => $request->deskripsi, + 'id_badge' => $request->id_badge, + 'tenggat_waktu' => $request->tenggat_waktu, + 'durasi_pengerjaan' => $request->durasi_pengerjaan, + ]); + + $challenge->kelas()->sync($request->id_kelas); + + SoalChallenge::where('id_challenge', $challenge->id_challenge)->delete(); + + foreach ($request->pertanyaan as $i => $pertanyaan) { + SoalChallenge::create([ + 'id_challenge' => $challenge->id_challenge, + 'pertanyaan' => $pertanyaan, + 'opsi_a' => $request->opsi_a[$i], + 'opsi_b' => $request->opsi_b[$i], + 'opsi_c' => $request->opsi_c[$i], + 'opsi_d' => $request->opsi_d[$i], + 'jawaban_benar' => $request->jawaban_benar[$i], + ]); + } + }); + + return redirect()->route('guru.challenge.index') + ->with('success', 'Challenge berhasil diupdate!'); + } + + public function destroy($id) + { + $guru = Auth::guard('guru')->user(); + Challenge::where('id_guru', $guru->id_guru)->findOrFail($id)->delete(); + + return redirect()->route('guru.challenge.index') + ->with('success', 'Challenge berhasil dihapus.'); + } + + public function editData($id) + { + $guru = Auth::guard('guru')->user(); + $challenge = Challenge::with(['kelas', 'soal']) + ->where('id_guru', $guru->id_guru) + ->findOrFail($id); + + return response()->json([ + 'judul_challenge' => $challenge->judul_challenge, + 'deskripsi' => $challenge->deskripsi, + 'tenggat_waktu' => $challenge->tenggat_waktu, + 'durasi_pengerjaan' => $challenge->durasi_pengerjaan, + 'kelas' => $challenge->kelas->pluck('id_kelas'), + 'soal' => $challenge->soal, + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Guru/DashboardController.php b/app/Http/Controllers/Guru/DashboardController.php index ed7d154..2ccd8fd 100644 --- a/app/Http/Controllers/Guru/DashboardController.php +++ b/app/Http/Controllers/Guru/DashboardController.php @@ -6,7 +6,9 @@ use App\Models\Mengajar; use App\Models\Siswa; use App\Models\Tugas; +use App\Models\Leaderboard; use Illuminate\Support\Facades\Auth; +use Carbon\Carbon; class DashboardController extends Controller { @@ -21,42 +23,85 @@ public function index() $totalMapel = Mengajar::where('id_guru', $guru->id_guru) ->distinct('id_mapel')->count('id_mapel'); - $kelasIds = Mengajar::where('id_guru', $guru->id_guru) - ->pluck('id_kelas')->unique(); + $kelasIds = Mengajar::where('id_guru', $guru->id_guru) + ->pluck('id_kelas')->unique()->values()->toArray(); // fix: toArray() $totalSiswa = Siswa::whereIn('id_kelas', $kelasIds)->count(); - // Chart: pengumpulan tugas per tugas (6 tugas terbaru) - $idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar'); + // ── Chart: Grouped Bar ── + // Tampilkan semua kombinasi mapel-kelas meskipun belum ada pengumpulan + $mengajars = Mengajar::with([ + 'mapel', + 'kelas', + 'tugas.pengumpulanTugas' // relasi sudah ditambahkan di model + ])->where('id_guru', $guru->id_guru)->get(); - $tugasList = Tugas::with(['mengajar.mapel', 'pengumpulanTugas']) - ->whereIn('id_mengajar', $idMengajars) - ->latest()->take(6)->get(); + $chartLabels = []; + $chartTepat = []; + $chartTerlambat = []; - $chartLabels = []; - $chartSudah = []; - $chartBelum = []; + foreach ($mengajars as $m) { + $namaMapel = optional($m->mapel)->nama_mapel ?? 'Mapel'; + $namaKelas = optional($m->kelas)->nama_kelas ?? 'Kelas'; + $labelPendek = (strlen($namaMapel) > 12 + ? substr($namaMapel, 0, 12) . '…' + : $namaMapel) . ' · ' . $namaKelas; - foreach ($tugasList as $tugas) { - $namaMapel = optional($tugas->mengajar->mapel)->nama_mapel ?? 'Mapel'; - $chartLabels[] = strlen($namaMapel) > 14 ? substr($namaMapel, 0, 14) . '…' : $namaMapel; - $sudah = $tugas->pengumpulanTugas->count(); - $chartSudah[] = $sudah; - $chartBelum[] = max(0, $totalSiswa - $sudah); + $tepat = 0; + $terlambat = 0; + + foreach ($m->tugas as $tugas) { + foreach ($tugas->pengumpulanTugas as $p) { + if ($p->status === 'dikumpulkan') $tepat++; + elseif ($p->status === 'terlambat') $terlambat++; + } + } + + // Selalu push meskipun tepat=0 dan terlambat=0 + $chartLabels[] = $labelPendek; + $chartTepat[] = $tepat; + $chartTerlambat[] = $terlambat; } + // ── Leaderboard ── + $leaderboard = Leaderboard::with(['siswa', 'kelas']) + ->whereIn('id_kelas', $kelasIds) // fix: sudah jadi array + ->orderBy('total_exp', 'desc') + ->take(10) + ->get() + ->map(function ($lb, $i) { + return [ + 'ranking' => $i + 1, + 'nama' => optional($lb->siswa)->nama ?? '-', + 'kelas' => optional($lb->kelas)->nama_kelas ?? '-', + 'total_exp' => $lb->total_exp, + 'foto' => optional($lb->siswa)->foto_profil, + 'semester' => $lb->semester, + 'tahun' => $lb->tahun_ajaran, + ]; + }); + + $firstLb = Leaderboard::whereIn('id_kelas', $kelasIds) + ->orderBy('total_exp', 'desc')->first(); + $semester = $firstLb->semester ?? '-'; + $tahunAjaran = $firstLb->tahun_ajaran ?? '-'; + } catch (\Exception $e) { - $totalKelas = 0; - $totalMapel = 0; - $totalSiswa = 0; - $chartLabels = []; - $chartSudah = []; - $chartBelum = []; + $totalKelas = 0; + $totalMapel = 0; + $totalSiswa = 0; + $chartLabels = []; + $chartTepat = []; + $chartTerlambat = []; + $leaderboard = collect(); + $semester = '-'; + $tahunAjaran = '-'; } return view('guru.dashboard', compact( 'totalKelas', 'totalMapel', 'totalSiswa', - 'chartLabels', 'chartSudah', 'chartBelum' + 'chartLabels', 'chartTepat', 'chartTerlambat', + 'leaderboard', 'semester', 'tahunAjaran' )); } } \ No newline at end of file diff --git a/app/Http/Controllers/Guru/LeaderboardController.php b/app/Http/Controllers/Guru/LeaderboardController.php index e8e034e..140d755 100644 --- a/app/Http/Controllers/Guru/LeaderboardController.php +++ b/app/Http/Controllers/Guru/LeaderboardController.php @@ -3,15 +3,79 @@ namespace App\Http\Controllers\Guru; use App\Http\Controllers\Controller; -use App\Models\Leaderboard; use App\Models\Kelas; use App\Models\Mengajar; +use App\Models\Siswa; use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; class LeaderboardController extends Controller { + private function hitungExpSiswa(int $idSiswa, int $idKelas): int + { + $now = Carbon::now(); + + // ── 1. EXP dari challenge ───────────────────────────────────────── + $expChallenge = 0; + $pesertaList = DB::table('peserta_challenges') + ->where('id_siswa', $idSiswa) + ->get(); + + foreach ($pesertaList as $peserta) { + $jawaban = json_decode($peserta->jawaban, true) ?? []; + $soalList = DB::table('soal_challenge') + ->where('id_challenge', $peserta->id_challenge) + ->get(); + + foreach ($soalList as $soal) { + $jwb = $jawaban[$soal->id_soal] ?? null; + if ($jwb && strtoupper($jwb) === strtoupper($soal->jawaban_benar)) { + $expChallenge += 1; // +1 per soal benar + } + } + } + + // ── 2. EXP dari tugas ───────────────────────────────────────────── + $tugasList = DB::table('tugas') + ->join('mengajars', 'tugas.id_mengajar', '=', 'mengajars.id_mengajar') + ->where('mengajars.id_kelas', $idKelas) + ->select('tugas.id_tugas', 'tugas.deadline') + ->get(); + + $expTugas = 0; + foreach ($tugasList as $tugas) { + $deadline = Carbon::parse($tugas->deadline); + $pengumpulan = DB::table('pengumpulan_tugas') + ->where('id_tugas', $tugas->id_tugas) + ->where('id_siswa', $idSiswa) + ->first(); + + if ($pengumpulan && $pengumpulan->lampiran_tugas !== null) { + $tanggalSubmit = Carbon::parse($pengumpulan->tanggal_submit); + if ($tanggalSubmit->lessThanOrEqualTo($deadline)) { + $expTugas += 10; // Tepat waktu → +10 + } else { + $hariTerlambat = $deadline->diffInDays($tanggalSubmit); // ← dibalik + + if ($hariTerlambat <= 1) { + $expTugas += 5; // Terlambat ≤ 1 hari → +5 + } elseif ($hariTerlambat <= 3) { + $expTugas += 1; // Terlambat 2-3 hari → +3 + } else { + $expTugas -= 5; // Lewat grace period > 3 hari → dianggap tidak kumpul + } + } + } elseif ($now->greaterThan($deadline)) { + // Tidak kumpul sama sekali & deadline lewat → -5 + $expTugas -= 5; + } + } + + return max(0, $expChallenge + $expTugas); + } + public function index(Request $request) { /** @var \App\Models\Guru $guru */ @@ -23,14 +87,12 @@ public function index(Request $request) ? $now->year . '/' . ($now->year + 1) : ($now->year - 1) . '/' . $now->year); - // Kelas yang diajar guru ini $idKelasList = Mengajar::where('id_guru', $guru->id_guru) ->pluck('id_kelas') ->unique() ->toArray(); - $idKelas = $request->input('id_kelas', $idKelasList[0] ?? null); - + $idKelas = $request->input('id_kelas', $idKelasList[0] ?? null); $kelasList = Kelas::whereIn('id_kelas', $idKelasList) ->orderBy('tingkat')->orderBy('nama_kelas') ->get(); @@ -38,21 +100,24 @@ public function index(Request $request) $leaderboard = collect(); if ($idKelas) { - $leaderboard = Leaderboard::with(['siswa', 'kelas']) - ->where('id_kelas', $idKelas) - ->where('semester', $semester) - ->where('tahun_ajaran', $tahunAjaran) - ->orderBy('total_exp', 'desc') - ->get() - ->map(function ($item, $i) { - return [ - 'ranking' => $i + 1, - 'nama' => optional($item->siswa)->nama ?? '-', - 'nisn' => optional($item->siswa)->nisn ?? '-', - 'nama_kelas' => optional($item->kelas)->nama_kelas ?? '-', - 'exp' => $item->total_exp, - ]; - }); + $semuaSiswa = Siswa::where('id_kelas', $idKelas)->get(); + + $leaderboard = $semuaSiswa->map(function ($s) { + return [ + 'id_siswa' => $s->id_siswa, + 'nama' => $s->nama, + 'nisn' => $s->nisn, + 'foto_profil' => $s->foto_profil, + 'nama_kelas' => $s->kelas->nama_kelas ?? '-', + 'exp' => $this->hitungExpSiswa($s->id_siswa, $s->id_kelas), + ]; + }) + ->sortByDesc('exp') + ->values() + ->map(function ($item, $i) { + $item['ranking'] = $i + 1; + return $item; + }); } $tahunList = []; diff --git a/app/Http/Controllers/Guru/MapelController.php b/app/Http/Controllers/Guru/MapelController.php index ac91899..8cd5627 100644 --- a/app/Http/Controllers/Guru/MapelController.php +++ b/app/Http/Controllers/Guru/MapelController.php @@ -122,6 +122,59 @@ public function historyMateri() return view('guru.materi.history', compact('materiList')); } + public function editMateri($id) +{ + $guru = Auth::guard('guru')->user(); + + $idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar'); + + $materi = Materi::whereIn('id_mengajar', $idMengajars) + ->where('id_materi', $id) + ->firstOrFail(); + + $mengajars = Mengajar::with(['mapel', 'kelas']) + ->where('id_guru', $guru->id_guru) + ->get(); + + return view('guru.materi.edit', compact('materi', 'mengajars')); +} + +public function updateMateri(Request $request, $id) +{ + $guru = Auth::guard('guru')->user(); + $idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar'); + + $materi = Materi::whereIn('id_mengajar', $idMengajars) + ->where('id_materi', $id) + ->firstOrFail(); + + $request->validate([ + // id_mengajar TIDAK divalidate karena tidak ada di form edit + 'judul_materi' => 'required|string|max:200', + 'deskripsi' => 'nullable|string', + 'lampiran_materi' => 'nullable|file|mimes:pdf,doc,docx,jpg,jpeg,png,ppt,pptx|max:10240', + ], [ + 'lampiran_materi.mimes' => 'Format file: pdf, doc, docx, jpg, png, ppt, pptx.', + 'lampiran_materi.max' => 'Ukuran file maksimal 10MB.', + ]); + + if ($request->hasFile('lampiran_materi')) { + if ($materi->lampiran_materi && \Storage::disk('public')->exists($materi->lampiran_materi)) { + \Storage::disk('public')->delete($materi->lampiran_materi); + } + $file = $request->file('lampiran_materi'); + $filename = 'materi_' . $guru->id_guru . '_' . time() . '.' . $file->getClientOriginalExtension(); + $materi->lampiran_materi = $file->storeAs('materi', $filename, 'public'); + } + + // id_mengajar TIDAK diupdate — kelas asal tetap + $materi->judul_materi = $request->judul_materi; + $materi->deskripsi = $request->deskripsi; + $materi->save(); + + return redirect()->route('guru.materi.history') + ->with('success', 'Materi berhasil diperbarui.'); +} /** * Hapus materi */ @@ -184,6 +237,58 @@ public function detailTugas($id) return view('guru.tugas.detail', compact('tugas')); } + public function editTugas($id) +{ + $guru = Auth::guard('guru')->user(); + $idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar'); + + $tugas = Tugas::whereIn('id_mengajar', $idMengajars) + ->where('id_tugas', $id) + ->firstOrFail(); + + $mengajars = Mengajar::with(['mapel', 'kelas']) + ->where('id_guru', $guru->id_guru) + ->get(); + + return view('guru.tugas.edit', compact('tugas', 'mengajars')); +} + +public function updateTugas(Request $request, $id) +{ + $guru = Auth::guard('guru')->user(); + $idMengajars = Mengajar::where('id_guru', $guru->id_guru)->pluck('id_mengajar'); + + $tugas = Tugas::whereIn('id_mengajar', $idMengajars) + ->where('id_tugas', $id) + ->firstOrFail(); + + $request->validate([ + 'judul_tugas' => 'required|string|max:200', + 'keterangan' => 'nullable|string', + 'deadline' => 'required|date', // ← hapus after:now biar edit deadline lama tetap bisa + 'lampiran_tugas' => 'nullable|file|max:10240', + ], [ + 'lampiran_tugas.max' => 'Ukuran file maksimal 10MB.', + ]); + + if ($request->hasFile('lampiran_tugas')) { + if ($tugas->lampiran && \Storage::disk('public')->exists($tugas->lampiran)) { + \Storage::disk('public')->delete($tugas->lampiran); + } + $file = $request->file('lampiran_tugas'); + $filename = 'tugas_' . $guru->id_guru . '_' . time() . '.' . $file->getClientOriginalExtension(); + $tugas->lampiran = $file->storeAs('tugas', $filename, 'public'); + } + + $tugas->judul_tugas = $request->judul_tugas; + $tugas->keterangan = $request->keterangan; + $tugas->deadline = $request->deadline; + $tugas->save(); + + return redirect()->route('guru.tugas.history') + ->with('success', 'Tugas berhasil diperbarui.'); +} + /** * Hapus tugas */ diff --git a/app/Http/Controllers/Guru/ProfileController.php b/app/Http/Controllers/Guru/ProfileController.php index 17f1f19..52b2ae8 100644 --- a/app/Http/Controllers/Guru/ProfileController.php +++ b/app/Http/Controllers/Guru/ProfileController.php @@ -38,7 +38,7 @@ public function updateAjax(Request $request) } $path = $request->file('foto_profil')->store('foto_profil/guru', 'public'); $data['foto_profil'] = $path; - $fotoUrl = Storage::url($path); + $fotoUrl = '/E31230356/storage/app/public/' . $path; // ← diubah } if (!empty($data)) { @@ -48,7 +48,7 @@ public function updateAjax(Request $request) return response()->json([ 'success' => true, 'message' => 'Profil berhasil diperbarui!', - 'foto_url' => $fotoUrl ?? ($guru->foto_profil ? Storage::url($guru->foto_profil) : null), + 'foto_url' => $fotoUrl ?? ($guru->foto_profil ? '/E31230356/storage/app/public/' . $guru->foto_profil : null), // ← diubah ]); } } \ No newline at end of file diff --git a/app/Http/Controllers/Siswa/ChallengeController.php b/app/Http/Controllers/Siswa/ChallengeController.php index adb57c3..f245da7 100644 --- a/app/Http/Controllers/Siswa/ChallengeController.php +++ b/app/Http/Controllers/Siswa/ChallengeController.php @@ -93,14 +93,19 @@ public function submit(Request $request, $id_challenge) } $jawaban = $request->input('jawaban', []); - $totalExp = 0; $jawabanJson = []; + // ============================================= + // PERUBAHAN: +1 poin per soal yang dijawab BENAR + // (tidak lagi pakai exp_per_soal dari database) + // ============================================= + $totalExp = 0; foreach ($challenge->soal as $soal) { $jwb = $jawaban[$soal->id_soal] ?? null; $jawabanJson[$soal->id_soal] = $jwb; + if ($jwb && strtoupper($jwb) === strtoupper($soal->jawaban_benar)) { - $totalExp += $soal->exp_per_soal; + $totalExp += 1; // +1 poin per soal benar } } @@ -110,7 +115,7 @@ public function submit(Request $request, $id_challenge) ? $now->year . '/' . ($now->year + 1) : ($now->year - 1) . '/' . $now->year; - // Snapshot badge sebelum submit — untuk deteksi badge baru + // Snapshot badge sebelum submit $badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa) ->pluck('id_badge') ->toArray(); @@ -137,6 +142,7 @@ public function submit(Request $request, $id_challenge) $lb->increment('total_exp', $totalExp); + // Update ranking semua siswa di kelas ini Leaderboard::where('id_kelas', $siswa->id_kelas) ->where('semester', $semester) ->where('tahun_ajaran', $tahunAjaran) @@ -148,14 +154,13 @@ public function submit(Request $request, $id_challenge) // Cek & berikan badge challenge app(BadgeService::class)->checkChallengeBadges($siswa->id_siswa); - // Deteksi badge yang baru didapat (selisih sebelum & sesudah) + // Deteksi badge baru $badgeSesudah = SiswaBadge::where('id_siswa', $siswa->id_siswa) ->pluck('id_badge') ->toArray(); $idBadgeBaru = array_diff($badgeSesudah, $badgeSebelum); - // Simpan ke session agar bisa ditampilkan di halaman hasil if (!empty($idBadgeBaru)) { $badgeBaru = Badge::whereIn('id_badge', $idBadgeBaru)->get(); session()->flash('badge_baru', $badgeBaru); @@ -185,7 +190,6 @@ public function hasil($id_challenge) $totalSoal = $challenge->soal->count(); $persentase = $totalSoal > 0 ? round(($benar / $totalSoal) * 100) : 0; - // Ambil badge baru dari session (hanya ada jika baru saja submit) $badgeBaru = session('badge_baru', collect()); return view('siswa.challenge.hasil', compact( diff --git a/app/Http/Controllers/Siswa/DashboardController.php b/app/Http/Controllers/Siswa/DashboardController.php index 641ad60..a8a2dc0 100644 --- a/app/Http/Controllers/Siswa/DashboardController.php +++ b/app/Http/Controllers/Siswa/DashboardController.php @@ -25,8 +25,7 @@ public function index() /** @var \App\Models\Siswa $siswa */ $siswa = Auth::guard('siswa')->user(); - // tugas - + // Tugas pending (belum dikumpulkan, belum lewat deadline) $sudahDikumpulkan = PengumpulanTugas::where('id_siswa', $siswa->id_siswa) ->pluck('id_tugas'); @@ -49,40 +48,35 @@ public function index() $namaMapel = optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-'; $tugasList[$tgl][] = [ - 'id_tugas' => $tugas->id_tugas, // tambah ini - 'jam' => Carbon::parse($tugas->deadline)->format('H.i'), - 'nama' => $tugas->judul_tugas, - 'mapel' => 'Belum · ' . $namaMapel, + 'id_tugas' => $tugas->id_tugas, + 'jam' => Carbon::parse($tugas->deadline)->format('H.i'), + 'nama' => $tugas->judul_tugas, + 'mapel' => 'Belum · ' . $namaMapel, ]; } - // challenge mingguan - - $startMinggu = Carbon::now()->startOfWeek(); - $endMinggu = Carbon::now()->endOfWeek(); - - // Challenge untuk kelas siswa yang masih aktif minggu ini - $challengeTotal = Challenge::whereHas('kelas', function ($q) use ($siswa) { - $q->where('challenge_kelas.id_kelas', $siswa->id_kelas); + // Progress bar tugas (semua tugas dari guru untuk kelas ini) + $tugasTotal = Tugas::whereHas('mengajar', function ($q) use ($siswa) { + $q->where('id_kelas', $siswa->id_kelas); }) - ->where('tenggat_waktu', '>=', Carbon::now()) - ->whereBetween('created_at', [$startMinggu, $endMinggu]) + ->where('deadline', '>=', Carbon::now()) ->count(); - // Challenge yang sudah selesai dikerjakan siswa minggu ini - $challengeDone = PesertaChallenge::where('id_siswa', $siswa->id_siswa) - ->where('status', 'selesai') - ->whereBetween('waktu_submit', [$startMinggu, $endMinggu]) + $tugasDikumpulkan = PengumpulanTugas::where('id_siswa', $siswa->id_siswa) + ->whereIn('id_tugas', Tugas::whereHas('mengajar', function ($q) use ($siswa) { + $q->where('id_kelas', $siswa->id_kelas); + })->where('deadline', '>=', Carbon::now())->pluck('id_tugas')) ->count(); - // mascot - + // Mascot + $startMinggu = Carbon::now()->startOfWeek(); + $endMinggu = Carbon::now()->endOfWeek(); $tugasSelesai = PengumpulanTugas::where('id_siswa', $siswa->id_siswa) ->whereIn('status', ['dikumpulkan', 'terlambat']) ->whereBetween('tanggal_submit', [$startMinggu, $endMinggu]) ->count(); - // leaderboard + // Leaderboard $leaderboardRaw = Leaderboard::with('siswa') ->where('id_kelas', $siswa->id_kelas) ->orderBy('total_exp', 'desc') @@ -99,8 +93,8 @@ public function index() return view('siswa.dashboard', compact( 'tugasList', - 'challengeDone', - 'challengeTotal', + 'tugasTotal', + 'tugasDikumpulkan', 'tugasSelesai', 'leaderboard', )); diff --git a/app/Http/Controllers/Siswa/LeaderboardController.php b/app/Http/Controllers/Siswa/LeaderboardController.php index c8b1fe6..94f9803 100644 --- a/app/Http/Controllers/Siswa/LeaderboardController.php +++ b/app/Http/Controllers/Siswa/LeaderboardController.php @@ -4,15 +4,102 @@ use App\Http\Controllers\Controller; use App\Models\Leaderboard; +use App\Models\Siswa; use App\Models\SiswaBadge; use App\Services\BadgeService; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\DB; use Carbon\Carbon; class LeaderboardController extends Controller { - private function getData() + /** + * Hitung total EXP siswa secara dinamis langsung dari database. + * + * Formula: + * + 1 poin per soal benar di setiap challenge + * +10 poin per tugas yang dikumpulkan tepat waktu + * + 5 poin per tugas yang terlambat < 1 hari (grace period) + * + 3 poin per tugas yang terlambat 2–3 hari + * - 5 poin per tugas yang terlambat > 3 hari + * - 5 poin per tugas yang tidak dikumpulkan sama sekali (setelah deadline) + */ + private function hitungExpSiswa(int $idSiswa, int $idKelas): int + { + $now = Carbon::now(); + + // ── 1. EXP dari challenge (soal yang dijawab benar) ────────────── + $expChallenge = 0; + + $pesertaList = DB::table('peserta_challenges') + ->where('id_siswa', $idSiswa) + ->get(); + + foreach ($pesertaList as $peserta) { + $jawaban = json_decode($peserta->jawaban, true) ?? []; + $soalList = DB::table('soal_challenge') + ->where('id_challenge', $peserta->id_challenge) + ->get(); + + foreach ($soalList as $soal) { + $jwb = $jawaban[$soal->id_soal] ?? null; + if ($jwb && strtoupper($jwb) === strtoupper($soal->jawaban_benar)) { + $expChallenge += 1; // +1 per soal benar + } + } + } + + // ── 2. EXP dari tugas ──────────────────────────────────────────── + $semuaTugasIdKelas = DB::table('tugas') + ->join('mengajars', 'tugas.id_mengajar', '=', 'mengajars.id_mengajar') + ->where('mengajars.id_kelas', $idKelas) + ->select('tugas.id_tugas', 'tugas.deadline') + ->get(); + + $expTugas = 0; + + foreach ($semuaTugasIdKelas as $tugas) { + $deadline = Carbon::parse($tugas->deadline); + $pengumpulan = DB::table('pengumpulan_tugas') + ->where('id_tugas', $tugas->id_tugas) + ->where('id_siswa', $idSiswa) + ->first(); + + if ($pengumpulan && $pengumpulan->lampiran_tugas !== null) { + $tanggalSubmit = Carbon::parse($pengumpulan->tanggal_submit); + + if ($tanggalSubmit->lessThanOrEqualTo($deadline)) { + $expTugas += 10; // Tepat waktu → +10 + } else { + $hariTerlambat = $deadline->diffInDays($tanggalSubmit); // ← dibalik + + if ($hariTerlambat <= 1) { + $expTugas += 5; // Terlambat ≤ 1 hari → +5 + } elseif ($hariTerlambat <= 3) { + $expTugas += 1; // Terlambat 2-3 hari → +3 + } else { + $expTugas -= 5; // Lewat grace period > 3 hari → dianggap tidak kumpul + } + } + } else { + // Belum mengumpulkan file sama sekali + if ($now->greaterThan($deadline)) { + // Deadline sudah lewat → penalti -5 + $expTugas -= 5; + } + // Deadline belum lewat → tidak ada penalti, tidak ada poin + } + } + + // Total EXP tidak bisa negatif + return max(0, $expChallenge + $expTugas); + } + + /** + * Ambil data leaderboard dengan EXP yang dihitung dinamis. + */ + private function getData(): array { $siswa = Auth::guard('siswa')->user(); @@ -22,24 +109,42 @@ private function getData() ? $now->year . '/' . ($now->year + 1) : ($now->year - 1) . '/' . $now->year; - $leaderboard = Leaderboard::with('siswa') - ->where('id_kelas', $siswa->id_kelas) - ->where('semester', $semester) - ->where('tahun_ajaran', $tahunAjaran) - ->orderBy('total_exp', 'desc') - ->get() - ->map(function ($item, $i) { - $fotoProfil = optional($item->siswa)->foto_profil; - return [ - 'ranking' => $i + 1, - 'nama' => optional($item->siswa)->nama ?? '-', - 'nisn' => optional($item->siswa)->nisn ?? '-', - 'exp' => $item->total_exp, - 'id_siswa' => $item->id_siswa, - 'foto_profil' => $fotoProfil, - 'foto_url' => $fotoProfil ? Storage::url($fotoProfil) : null, - ]; - }); + // Ambil semua siswa di kelas yang sama + $semuaSiswa = Siswa::where('id_kelas', $siswa->id_kelas)->get(); + + // Hitung EXP tiap siswa secara dinamis + $leaderboard = $semuaSiswa->map(function ($s) { + return [ + 'id_siswa' => $s->id_siswa, + 'nama' => $s->nama, + 'nisn' => $s->nisn, + 'foto_profil' => $s->foto_profil, + 'foto_url' => $s->foto_profil ? Storage::url($s->foto_profil) : null, + 'exp' => $this->hitungExpSiswa($s->id_siswa, $s->id_kelas), + ]; + }) + ->sortByDesc('exp') + ->values() + ->map(function ($item, $i) { + $item['ranking'] = $i + 1; + return $item; + }); + + // Sinkronisasi ke tabel leaderboards agar badge bisa dicek + foreach ($leaderboard as $item) { + Leaderboard::updateOrCreate( + [ + 'id_siswa' => $item['id_siswa'], + 'id_kelas' => $siswa->id_kelas, + 'semester' => $semester, + 'tahun_ajaran' => $tahunAjaran, + ], + [ + 'total_exp' => $item['exp'], + 'ranking' => $item['ranking'], + ] + ); + } $myRank = $leaderboard->firstWhere('id_siswa', $siswa->id_siswa); @@ -55,22 +160,18 @@ public function index() /** * Endpoint JSON untuk polling real-time. - * Mengevaluasi badge leaderboard siswa, lalu return badge yang dimiliki - * saat ini agar JS bisa mendeteksi badge baru via localStorage. */ public function json() { $data = $this->getData(); $siswa = $data['siswa']; - // Evaluasi badge leaderboard (grant/revoke) + // Evaluasi badge leaderboard berdasarkan data terbaru app(BadgeService::class)->checkLeaderboardBadges( $siswa->id_siswa, $siswa->id_kelas ); - // Ambil semua badge leaderboard yang dimiliki siswa saat ini - // beserta detail badge (icon, nama, deskripsi) untuk ditampilkan di pop-up $badgeSiswa = SiswaBadge::with('badge') ->where('id_siswa', $siswa->id_siswa) ->whereHas('badge', fn($q) => $q->whereIn('syarat', ['leaderboard_top5', 'leaderboard_top1'])) diff --git a/app/Http/Controllers/Siswa/NotifikasiController.php b/app/Http/Controllers/Siswa/NotifikasiController.php index 238874b..18c4f5d 100644 --- a/app/Http/Controllers/Siswa/NotifikasiController.php +++ b/app/Http/Controllers/Siswa/NotifikasiController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Siswa; use App\Http\Controllers\Controller; +use App\Models\Challenge; use App\Models\Materi; use App\Models\Tugas; use App\Models\Mengajar; @@ -48,7 +49,23 @@ public function index() 'time_raw'=> $t->created_at->toIso8601String(), ]); - $notifications = $materiBaru->concat($tugasBaru) + // Challenge baru untuk kelas siswa + $challengeBaru = Challenge::whereHas('kelas', fn($q) => + $q->where('challenge_kelas.id_kelas', $siswa->id_kelas)) + ->where('created_at', '>=', $since) + ->orderBy('created_at', 'desc') + ->get() + ->map(fn($c) => [ + 'type' => 'challenge', + 'title' => 'Challenge Baru!', + 'message' => $c->judul_challenge, + 'time' => $c->created_at->diffForHumans(), + 'time_raw'=> $c->created_at->toIso8601String(), + ]); + + $notifications = $materiBaru + ->concat($tugasBaru) + ->concat($challengeBaru) ->sortByDesc('time_raw') ->values(); diff --git a/app/Http/Controllers/Siswa/ProfileController.php b/app/Http/Controllers/Siswa/ProfileController.php index 2736418..6373fe7 100644 --- a/app/Http/Controllers/Siswa/ProfileController.php +++ b/app/Http/Controllers/Siswa/ProfileController.php @@ -61,7 +61,7 @@ public function updateAjax(Request $request) } $path = $request->file('foto_profil')->store('foto_profil/siswa', 'public'); $data['foto_profil'] = $path; - $fotoUrl = Storage::url($path); + $fotoUrl = '/E31230356/storage/app/public/' . $path; } if (!empty($data)) { @@ -71,7 +71,7 @@ public function updateAjax(Request $request) return response()->json([ 'success' => true, 'message' => 'Profil berhasil diperbarui!', - 'foto_url' => $fotoUrl ?? ($siswa->foto_profil ? Storage::url($siswa->foto_profil) : null), + 'foto_url' => $fotoUrl ?? ($siswa->foto_profil ? '/E31230356/storage/app/public/' . $siswa->foto_profil : null), ]); } } \ No newline at end of file diff --git a/app/Http/Controllers/Siswa/TugasController.php b/app/Http/Controllers/Siswa/TugasController.php index d402356..144b803 100644 --- a/app/Http/Controllers/Siswa/TugasController.php +++ b/app/Http/Controllers/Siswa/TugasController.php @@ -4,13 +4,14 @@ use App\Http\Controllers\Controller; use App\Models\Badge; +use App\Models\PengumpulanTugas; use App\Models\SiswaBadge; use App\Services\BadgeService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Storage; use Carbon\Carbon; use App\Models\Tugas; -use App\Models\PengumpulanTugas; class TugasController extends Controller { @@ -19,9 +20,6 @@ public function __construct() $this->middleware('auth:siswa'); } - /** - * Daftar semua tugas untuk kelas siswa - */ public function index() { $siswa = Auth::guard('siswa')->user(); @@ -33,18 +31,23 @@ public function index() ->orderBy('deadline', 'asc') ->get(); - $tugasList = $semuaTugas->map(function ($tugas) use ($siswa) { + $now = Carbon::now(); + + $tugasList = $semuaTugas->map(function ($tugas) use ($siswa, $now) { $pengumpulan = PengumpulanTugas::where('id_tugas', $tugas->id_tugas) ->where('id_siswa', $siswa->id_siswa) ->first(); - $now = Carbon::now(); $deadline = Carbon::parse($tugas->deadline); - if ($pengumpulan) { - $status = $pengumpulan->status; + if ($pengumpulan && $pengumpulan->lampiran_tugas !== null) { + $status = $pengumpulan->tanggal_submit && Carbon::parse($pengumpulan->tanggal_submit)->lessThanOrEqualTo($deadline) + ? 'dikumpulkan' + : 'terlambat'; + $sudahKumpul = true; } else { - $status = $now->greaterThan($deadline) ? 'terlambat' : 'belum'; + $status = $now->greaterThan($deadline) ? 'terlambat' : 'belum'; + $sudahKumpul = false; } return [ @@ -55,7 +58,7 @@ public function index() 'nama_mapel' => optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-', 'nama_guru' => optional(optional($tugas->mengajar)->guru)->nama ?? '-', 'status' => $status, - 'sudah_kumpul' => !is_null($pengumpulan), + 'sudah_kumpul' => $sudahKumpul, 'lampiran' => $pengumpulan?->lampiran_tugas, 'exp' => $pengumpulan?->exp ?? 0, 'file_tugas' => $tugas->lampiran, @@ -69,9 +72,6 @@ public function index() return view('siswa.tugas.index', compact('tugasBelum', 'tugasTerlambat', 'tugasSelesai')); } - /** - * Detail tugas + form submit - */ public function show($id_tugas) { $siswa = Auth::guard('siswa')->user(); @@ -87,14 +87,18 @@ public function show($id_tugas) ->where('id_siswa', $siswa->id_siswa) ->first(); - $sudahKumpul = !is_null($pengumpulan); + $sudahKumpul = $pengumpulan && $pengumpulan->lampiran_tugas !== null; $terlambat = Carbon::now()->greaterThan(Carbon::parse($tugas->deadline)); return view('siswa.tugas.show', compact('tugas', 'pengumpulan', 'sudahKumpul', 'terlambat')); } /** - * Submit / upload jawaban tugas + * Submit tugas. + * + * EXP tidak lagi diupdate ke leaderboard di sini. + * EXP dihitung dinamis di LeaderboardController::hitungExpSiswa() + * berdasarkan tanggal_submit vs deadline secara real-time. */ public function submit(Request $request, $id_tugas) { @@ -116,6 +120,7 @@ public function submit(Request $request, $id_tugas) $sudahAda = PengumpulanTugas::where('id_tugas', $id_tugas) ->where('id_siswa', $siswa->id_siswa) + ->whereNotNull('lampiran_tugas') ->exists(); if ($sudahAda) { @@ -126,45 +131,75 @@ public function submit(Request $request, $id_tugas) $filename = 'tugas_' . $siswa->id_siswa . '_' . $id_tugas . '_' . time() . '.' . $file->getClientOriginalExtension(); $path = $file->storeAs('pengumpulan_tugas', $filename, 'public'); - $now = Carbon::now(); - $deadline = Carbon::parse($tugas->deadline); - $status = $now->greaterThan($deadline) ? 'terlambat' : 'dikumpulkan'; + $now = Carbon::now(); + $deadline = Carbon::parse($tugas->deadline); + $terlambat = $now->greaterThan($deadline); + $status = $terlambat ? 'terlambat' : 'dikumpulkan'; - PengumpulanTugas::create([ - 'id_tugas' => $id_tugas, - 'id_siswa' => $siswa->id_siswa, - 'lampiran_tugas' => $path, - 'tanggal_submit' => $now, - 'exp' => 0, - 'status' => $status, - ]); - - // Cek & berikan badge tugas hanya jika tepat waktu - if ($status === 'dikumpulkan') { - // Snapshot badge sebelum pengecekan - $badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa) - ->pluck('id_badge') - ->toArray(); + PengumpulanTugas::updateOrCreate( + ['id_tugas' => $id_tugas, 'id_siswa' => $siswa->id_siswa], + [ + 'lampiran_tugas' => $path, + 'tanggal_submit' => $now, + 'exp' => $terlambat ? -5 : 10, // catatan saja, tidak dipakai di leaderboard + 'status' => $status, + ] + ); + if (!$terlambat) { + $badgeSebelum = SiswaBadge::where('id_siswa', $siswa->id_siswa)->pluck('id_badge')->toArray(); app(BadgeService::class)->checkTugasBadges($siswa->id_siswa); - - // Deteksi badge yang baru didapat - $badgeSesudah = SiswaBadge::where('id_siswa', $siswa->id_siswa) - ->pluck('id_badge') - ->toArray(); - - $idBadgeBaru = array_diff($badgeSesudah, $badgeSebelum); - + $badgeSesudah = SiswaBadge::where('id_siswa', $siswa->id_siswa)->pluck('id_badge')->toArray(); + $idBadgeBaru = array_diff($badgeSesudah, $badgeSebelum); if (!empty($idBadgeBaru)) { - $badgeBaru = Badge::whereIn('id_badge', $idBadgeBaru)->get(); - session()->flash('badge_baru', $badgeBaru); + session()->flash('badge_baru', Badge::whereIn('id_badge', $idBadgeBaru)->get()); } } - $pesan = $status === 'terlambat' - ? 'Tugas berhasil dikumpulkan (terlambat).' - : 'Tugas berhasil dikumpulkan! 🎉'; + $pesan = $terlambat + ? 'Tugas berhasil dikumpulkan (terlambat). Kamu mendapat penalti -5 poin. 😔' + : 'Tugas berhasil dikumpulkan! Kamu mendapat +10 poin! 🎉'; return redirect()->route('siswa.tugas.index')->with('success', $pesan); } + + public function gantiFile(Request $request, $id_tugas) + { + $siswa = Auth::guard('siswa')->user(); + + $request->validate([ + 'lampiran_tugas' => 'required|file|mimes:pdf,doc,docx,jpg,jpeg,png|max:5120', + ]); + + $tugas = Tugas::whereHas('mengajar', function ($q) use ($siswa) { + $q->where('id_kelas', $siswa->id_kelas); + }) + ->where('id_tugas', $id_tugas) + ->firstOrFail(); + + if (Carbon::now()->greaterThan(Carbon::parse($tugas->deadline))) { + return back()->with('error_ganti', 'Deadline sudah lewat, file tidak dapat diganti.'); + } + + $pengumpulan = PengumpulanTugas::where('id_tugas', $id_tugas) + ->where('id_siswa', $siswa->id_siswa) + ->firstOrFail(); + + if ($pengumpulan->lampiran_tugas) { + Storage::disk('public')->delete($pengumpulan->lampiran_tugas); + } + + $file = $request->file('lampiran_tugas'); + $filename = 'tugas_' . $siswa->id_siswa . '_' . $id_tugas . '_' . time() . '.' . $file->getClientOriginalExtension(); + $path = $file->storeAs('pengumpulan_tugas', $filename, 'public'); + + $pengumpulan->update([ + 'lampiran_tugas' => $path, + 'tanggal_submit' => Carbon::now(), + 'status' => 'dikumpulkan', + 'exp' => 10, + ]); + + return back()->with('success', 'File jawaban berhasil diganti! ✅'); + } } \ No newline at end of file diff --git a/app/Http/Middleware/AdminAuthenticate.php b/app/Http/Middleware/AdminAuthenticate.php new file mode 100644 index 0000000..249b64e --- /dev/null +++ b/app/Http/Middleware/AdminAuthenticate.php @@ -0,0 +1,19 @@ +check()) { + return redirect()->route('admin.login'); + } + + return $next($request); + } +} \ No newline at end of file diff --git a/app/Models/Challenge.php b/app/Models/Challenge.php index e108ac1..95bbca4 100644 --- a/app/Models/Challenge.php +++ b/app/Models/Challenge.php @@ -4,6 +4,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use App\Models\Guru; +use App\Models\PesertaChallenge; class Challenge extends Model { @@ -14,7 +16,7 @@ class Challenge extends Model protected $primaryKey = 'id_challenge'; protected $fillable = [ - 'id_admin', + 'id_guru', 'judul_challenge', 'deskripsi', 'exp', @@ -41,4 +43,14 @@ public function soal() return $this->hasMany(SoalChallenge::class, 'id_challenge'); } +public function peserta() +{ + return $this->hasMany(PesertaChallenge::class, 'id_challenge'); +} + +public function guru() +{ + return $this->belongsTo(Guru::class, 'id_guru', 'id_guru'); +} + } \ No newline at end of file diff --git a/app/Models/Guru.php b/app/Models/Guru.php index 91af307..e535afc 100644 --- a/app/Models/Guru.php +++ b/app/Models/Guru.php @@ -9,25 +9,35 @@ class Guru extends Authenticatable { use HasFactory; - protected $table = 'gurus'; + protected $table = 'gurus'; protected $primaryKey = 'id_guru'; - public $incrementing = true; - protected $keyType = 'int'; + public $incrementing = true; + protected $keyType = 'int'; protected $fillable = [ 'nip', 'nama', 'password', - 'foto_profil' + 'foto_profil', + 'remember_token', ]; protected $hidden = [ 'password', + 'remember_token', + ]; + + protected $casts = [ + 'password' => 'hashed', ]; - // Relasi ke Mengajar public function mengajars() { return $this->hasMany(Mengajar::class, 'id_guru', 'id_guru'); } -} + public function challenges() + + { + return $this->hasMany(Challenge::class, 'id_guru', 'id_guru'); + } +} \ No newline at end of file diff --git a/app/Models/Mengajar.php b/app/Models/Mengajar.php index 2062ea9..dc3c816 100644 --- a/app/Models/Mengajar.php +++ b/app/Models/Mengajar.php @@ -35,4 +35,10 @@ public function kelas() { return $this->belongsTo(Kelas::class, 'id_kelas', 'id_kelas'); } + + // Relasi ke Tugas + public function tugas() + { + return $this->hasMany(Tugas::class, 'id_mengajar', 'id_mengajar'); + } } \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index a8b0e5a..3b239e8 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -15,5 +15,6 @@ public function register(): void public function boot(): void { Paginator::defaultView('vendor.pagination.bootstrap-5'); + app()->usePublicPath(base_path('public')); } } \ No newline at end of file diff --git a/app/Services/BadgeService.php b/app/Services/BadgeService.php index 44e080a..9b2c637 100644 --- a/app/Services/BadgeService.php +++ b/app/Services/BadgeService.php @@ -14,15 +14,14 @@ class BadgeService { /** * Syarat (kolom `syarat` di tabel badges) yang dikenali sistem. - * Nilai ini harus sama persis dengan isi kolom `syarat` di DB. * * CHALLENGE * challenge_1 → selesaikan 1 challenge * challenge_3 → selesaikan 3 challenge * * TUGAS - * tugas_1 → kumpulkan 1 tugas (tepat waktu) - * tugas_3 → kumpulkan 3 tugas (tepat waktu) + * tugas_1 → kumpulkan 1 tugas tepat waktu (tanggal_submit ≤ deadline) + * tugas_3 → kumpulkan 3 tugas tepat waktu (tanggal_submit ≤ deadline) * * LEADERBOARD (dicek & dicabut secara real-time) * leaderboard_top5 → masuk top 5 leaderboard kelas aktif @@ -35,7 +34,6 @@ class BadgeService /** * Dipanggil setelah siswa submit challenge. - * Mengecek & memberikan badge challenge. */ public function checkChallengeBadges(int $idSiswa): void { @@ -49,24 +47,28 @@ public function checkChallengeBadges(int $idSiswa): void /** * Dipanggil setelah siswa submit tugas. - * Mengecek & memberikan badge tugas (hanya status 'dikumpulkan' / tepat waktu). + * Badge hanya diberikan jika tugas dikumpulkan TEPAT WAKTU + * (tanggal_submit ≤ deadline tugas). */ public function checkTugasBadges(int $idSiswa): void { - $jumlahKumpul = PengumpulanTugas::where('id_siswa', $idSiswa) - ->where('status', 'dikumpulkan') + // Join pengumpulan_tugas dengan tugas untuk membandingkan + // tanggal_submit dengan deadline secara langsung di DB. + $jumlahTepatWaktu = DB::table('pengumpulan_tugas') + ->join('tugas', 'pengumpulan_tugas.id_tugas', '=', 'tugas.id_tugas') + ->where('pengumpulan_tugas.id_siswa', $idSiswa) + ->whereNotNull('pengumpulan_tugas.lampiran_tugas') + ->whereColumn('pengumpulan_tugas.tanggal_submit', '<=', 'tugas.deadline') ->count(); - $this->grantIfEligible($idSiswa, 'tugas_1', $jumlahKumpul >= 1); - $this->grantIfEligible($idSiswa, 'tugas_3', $jumlahKumpul >= 3); + $this->grantIfEligible($idSiswa, 'tugas_1', $jumlahTepatWaktu >= 1); + $this->grantIfEligible($idSiswa, 'tugas_3', $jumlahTepatWaktu >= 3); } /** * Dipanggil dari endpoint JSON leaderboard (polling real-time). * Badge leaderboard DICABUT jika siswa tidak lagi memenuhi syarat, * dan DIBERIKAN KEMBALI jika kembali memenuhi syarat. - * - * Hanya leaderboard semester & tahun ajaran aktif yang dievaluasi. */ public function checkLeaderboardBadges(int $idSiswa, int $idKelas): void { @@ -80,11 +82,9 @@ public function checkLeaderboardBadges(int $idSiswa, int $idKelas): void $ranking = $lb?->ranking ?? 0; - // top5: ranking 1-5, top1: hanya ranking 1 $isTop5 = $ranking >= 1 && $ranking <= 5; $isTop1 = $ranking === 1; - // Untuk badge leaderboard: grant jika eligible, revoke jika tidak $this->grantOrRevoke($idSiswa, 'leaderboard_top5', $isTop5); $this->grantOrRevoke($idSiswa, 'leaderboard_top1', $isTop1); } @@ -105,10 +105,9 @@ private function grantIfEligible(int $idSiswa, string $syarat, bool $eligible): $badge = Badge::where('syarat', $syarat)->first(); if (!$badge) { - return; // badge belum di-seed di DB, skip + return; } - // Idempoten: cek dulu sebelum insert $sudahPunya = SiswaBadge::where('id_siswa', $idSiswa) ->where('id_badge', $badge->id_badge) ->exists(); @@ -138,22 +137,18 @@ private function grantOrRevoke(int $idSiswa, string $syarat, bool $eligible): vo ->first(); if ($eligible && !$record) { - // Berikan badge SiswaBadge::create([ 'id_siswa' => $idSiswa, 'id_badge' => $badge->id_badge, 'tanggal_diberikan' => Carbon::now(), ]); } elseif (!$eligible && $record) { - // Cabut badge $record->delete(); } - // Jika sudah punya & masih eligible, atau tidak punya & tidak eligible → tidak perlu apa-apa } /** - * Mengembalikan [semester, tahun_ajaran] berdasarkan tanggal sekarang, - * konsisten dengan logika di ChallengeController. + * Mengembalikan [semester, tahun_ajaran] berdasarkan tanggal sekarang. */ private function semesterAktif(): array { diff --git a/bootstrap/app.php b/bootstrap/app.php index c183276..b310f45 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -10,8 +10,10 @@ commands: __DIR__.'/../routes/console.php', health: '/up', ) - ->withMiddleware(function (Middleware $middleware): void { - // + ->withMiddleware(function (Middleware $middleware) { + $middleware->alias([ + 'admin.auth' => \App\Http\Middleware\AdminAuthenticate::class, + ]); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/build/assets/app-CXDpL9bK.js b/build/assets/app-CXDpL9bK.js new file mode 100644 index 0000000..0337fc2 --- /dev/null +++ b/build/assets/app-CXDpL9bK.js @@ -0,0 +1,10 @@ +function Xn(e,t){return function(){return e.apply(t,arguments)}}const{toString:Fi}=Object.prototype,{getPrototypeOf:Ht}=Object,{iterator:et,toStringTag:Gn}=Symbol,tt=(e=>t=>{const n=Fi.call(t);return e[n]||(e[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),k=e=>(e=e.toLowerCase(),t=>tt(t)===e),nt=e=>t=>typeof t===e,{isArray:fe}=Array,ce=nt("undefined");function Te(e){return e!==null&&!ce(e)&&e.constructor!==null&&!ce(e.constructor)&&N(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const Yn=k("ArrayBuffer");function Li(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&Yn(e.buffer),t}const Mi=nt("string"),N=nt("function"),Zn=nt("number"),Ce=e=>e!==null&&typeof e=="object",ji=e=>e===!0||e===!1,ze=e=>{if(tt(e)!=="object")return!1;const t=Ht(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Gn in e)&&!(et in e)},ki=e=>{if(!Ce(e)||Te(e))return!1;try{return Object.keys(e).length===0&&Object.getPrototypeOf(e)===Object.prototype}catch{return!1}},Ii=k("Date"),Bi=k("File"),Di=k("Blob"),$i=k("FileList"),Ui=e=>Ce(e)&&N(e.pipe),qi=e=>{let t;return e&&(typeof FormData=="function"&&e instanceof FormData||N(e.append)&&((t=tt(e))==="formdata"||t==="object"&&N(e.toString)&&e.toString()==="[object FormData]"))},Hi=k("URLSearchParams"),[zi,Ki,Ji,Wi]=["ReadableStream","Request","Response","Headers"].map(k),Vi=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function Pe(e,t,{allOwnKeys:n=!1}={}){if(e===null||typeof e>"u")return;let r,i;if(typeof e!="object"&&(e=[e]),fe(e))for(r=0,i=e.length;r0;)if(i=n[r],t===i.toLowerCase())return i;return null}const G=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,er=e=>!ce(e)&&e!==G;function xt(){const{caseless:e,skipUndefined:t}=er(this)&&this||{},n={},r=(i,s)=>{const o=e&&Qn(n,s)||s;ze(n[o])&&ze(i)?n[o]=xt(n[o],i):ze(i)?n[o]=xt({},i):fe(i)?n[o]=i.slice():(!t||!ce(i))&&(n[o]=i)};for(let i=0,s=arguments.length;i(Pe(t,(i,s)=>{n&&N(i)?e[s]=Xn(i,n):e[s]=i},{allOwnKeys:r}),e),Gi=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),Yi=(e,t,n,r)=>{e.prototype=Object.create(t.prototype,r),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},Zi=(e,t,n,r)=>{let i,s,o;const a={};if(t=t||{},e==null)return t;do{for(i=Object.getOwnPropertyNames(e),s=i.length;s-- >0;)o=i[s],(!r||r(o,e,t))&&!a[o]&&(t[o]=e[o],a[o]=!0);e=n!==!1&&Ht(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},Qi=(e,t,n)=>{e=String(e),(n===void 0||n>e.length)&&(n=e.length),n-=t.length;const r=e.indexOf(t,n);return r!==-1&&r===n},es=e=>{if(!e)return null;if(fe(e))return e;let t=e.length;if(!Zn(t))return null;const n=new Array(t);for(;t-- >0;)n[t]=e[t];return n},ts=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&Ht(Uint8Array)),ns=(e,t)=>{const r=(e&&e[et]).call(e);let i;for(;(i=r.next())&&!i.done;){const s=i.value;t.call(e,s[0],s[1])}},rs=(e,t)=>{let n;const r=[];for(;(n=e.exec(t))!==null;)r.push(n);return r},is=k("HTMLFormElement"),ss=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,r,i){return r.toUpperCase()+i}),bn=(({hasOwnProperty:e})=>(t,n)=>e.call(t,n))(Object.prototype),os=k("RegExp"),tr=(e,t)=>{const n=Object.getOwnPropertyDescriptors(e),r={};Pe(n,(i,s)=>{let o;(o=t(i,s,e))!==!1&&(r[s]=o||i)}),Object.defineProperties(e,r)},as=e=>{tr(e,(t,n)=>{if(N(e)&&["arguments","caller","callee"].indexOf(n)!==-1)return!1;const r=e[n];if(N(r)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error("Can not rewrite read-only method '"+n+"'")})}})},cs=(e,t)=>{const n={},r=i=>{i.forEach(s=>{n[s]=!0})};return fe(e)?r(e):r(String(e).split(t)),n},us=()=>{},ls=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t;function fs(e){return!!(e&&N(e.append)&&e[Gn]==="FormData"&&e[et])}const ds=e=>{const t=new Array(10),n=(r,i)=>{if(Ce(r)){if(t.indexOf(r)>=0)return;if(Te(r))return r;if(!("toJSON"in r)){t[i]=r;const s=fe(r)?[]:{};return Pe(r,(o,a)=>{const c=n(o,i+1);!ce(c)&&(s[a]=c)}),t[i]=void 0,s}}return r};return n(e,0)},ps=k("AsyncFunction"),hs=e=>e&&(Ce(e)||N(e))&&N(e.then)&&N(e.catch),nr=((e,t)=>e?setImmediate:t?((n,r)=>(G.addEventListener("message",({source:i,data:s})=>{i===G&&s===n&&r.length&&r.shift()()},!1),i=>{r.push(i),G.postMessage(n,"*")}))(`axios@${Math.random()}`,[]):n=>setTimeout(n))(typeof setImmediate=="function",N(G.postMessage)),_s=typeof queueMicrotask<"u"?queueMicrotask.bind(G):typeof process<"u"&&process.nextTick||nr,ms=e=>e!=null&&N(e[et]),f={isArray:fe,isArrayBuffer:Yn,isBuffer:Te,isFormData:qi,isArrayBufferView:Li,isString:Mi,isNumber:Zn,isBoolean:ji,isObject:Ce,isPlainObject:ze,isEmptyObject:ki,isReadableStream:zi,isRequest:Ki,isResponse:Ji,isHeaders:Wi,isUndefined:ce,isDate:Ii,isFile:Bi,isBlob:Di,isRegExp:os,isFunction:N,isStream:Ui,isURLSearchParams:Hi,isTypedArray:ts,isFileList:$i,forEach:Pe,merge:xt,extend:Xi,trim:Vi,stripBOM:Gi,inherits:Yi,toFlatObject:Zi,kindOf:tt,kindOfTest:k,endsWith:Qi,toArray:es,forEachEntry:ns,matchAll:rs,isHTMLForm:is,hasOwnProperty:bn,hasOwnProp:bn,reduceDescriptors:tr,freezeMethods:as,toObjectSet:cs,toCamelCase:ss,noop:us,toFiniteNumber:ls,findKey:Qn,global:G,isContextDefined:er,isSpecCompliantForm:fs,toJSONObject:ds,isAsyncFn:ps,isThenable:hs,setImmediate:nr,asap:_s,isIterable:ms};function y(e,t,n,r,i){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack,this.message=e,this.name="AxiosError",t&&(this.code=t),n&&(this.config=n),r&&(this.request=r),i&&(this.response=i,this.status=i.status?i.status:null)}f.inherits(y,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:f.toJSONObject(this.config),code:this.code,status:this.status}}});const rr=y.prototype,ir={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(e=>{ir[e]={value:e}});Object.defineProperties(y,ir);Object.defineProperty(rr,"isAxiosError",{value:!0});y.from=(e,t,n,r,i,s)=>{const o=Object.create(rr);f.toFlatObject(e,o,function(u){return u!==Error.prototype},l=>l!=="isAxiosError");const a=e&&e.message?e.message:"Error",c=t==null&&e?e.code:t;return y.call(o,a,c,n,r,i),e&&o.cause==null&&Object.defineProperty(o,"cause",{value:e,configurable:!0}),o.name=e&&e.name||"Error",s&&Object.assign(o,s),o};const gs=null;function Et(e){return f.isPlainObject(e)||f.isArray(e)}function sr(e){return f.endsWith(e,"[]")?e.slice(0,-2):e}function wn(e,t,n){return e?e.concat(t).map(function(i,s){return i=sr(i),!n&&s?"["+i+"]":i}).join(n?".":""):t}function ys(e){return f.isArray(e)&&!e.some(Et)}const bs=f.toFlatObject(f,{},null,function(t){return/^is[A-Z]/.test(t)});function rt(e,t,n){if(!f.isObject(e))throw new TypeError("target must be an object");t=t||new FormData,n=f.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(m,d){return!f.isUndefined(d[m])});const r=n.metaTokens,i=n.visitor||u,s=n.dots,o=n.indexes,c=(n.Blob||typeof Blob<"u"&&Blob)&&f.isSpecCompliantForm(t);if(!f.isFunction(i))throw new TypeError("visitor must be a function");function l(p){if(p===null)return"";if(f.isDate(p))return p.toISOString();if(f.isBoolean(p))return p.toString();if(!c&&f.isBlob(p))throw new y("Blob is not supported. Use a Buffer instead.");return f.isArrayBuffer(p)||f.isTypedArray(p)?c&&typeof Blob=="function"?new Blob([p]):Buffer.from(p):p}function u(p,m,d){let g=p;if(p&&!d&&typeof p=="object"){if(f.endsWith(m,"{}"))m=r?m:m.slice(0,-2),p=JSON.stringify(p);else if(f.isArray(p)&&ys(p)||(f.isFileList(p)||f.endsWith(m,"[]"))&&(g=f.toArray(p)))return m=sr(m),g.forEach(function(w,E){!(f.isUndefined(w)||w===null)&&t.append(o===!0?wn([m],E,s):o===null?m:m+"[]",l(w))}),!1}return Et(p)?!0:(t.append(wn(d,m,s),l(p)),!1)}const h=[],_=Object.assign(bs,{defaultVisitor:u,convertValue:l,isVisitable:Et});function b(p,m){if(!f.isUndefined(p)){if(h.indexOf(p)!==-1)throw Error("Circular reference detected in "+m.join("."));h.push(p),f.forEach(p,function(g,x){(!(f.isUndefined(g)||g===null)&&i.call(t,g,f.isString(x)?x.trim():x,m,_))===!0&&b(g,m?m.concat(x):[x])}),h.pop()}}if(!f.isObject(e))throw new TypeError("data must be an object");return b(e),t}function xn(e){const t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,function(r){return t[r]})}function zt(e,t){this._pairs=[],e&&rt(e,this,t)}const or=zt.prototype;or.append=function(t,n){this._pairs.push([t,n])};or.toString=function(t){const n=t?function(r){return t.call(this,r,xn)}:xn;return this._pairs.map(function(i){return n(i[0])+"="+n(i[1])},"").join("&")};function ws(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function ar(e,t,n){if(!t)return e;const r=n&&n.encode||ws;f.isFunction(n)&&(n={serialize:n});const i=n&&n.serialize;let s;if(i?s=i(t,n):s=f.isURLSearchParams(t)?t.toString():new zt(t,n).toString(r),s){const o=e.indexOf("#");o!==-1&&(e=e.slice(0,o)),e+=(e.indexOf("?")===-1?"?":"&")+s}return e}class En{constructor(){this.handlers=[]}use(t,n,r){return this.handlers.push({fulfilled:t,rejected:n,synchronous:r?r.synchronous:!1,runWhen:r?r.runWhen:null}),this.handlers.length-1}eject(t){this.handlers[t]&&(this.handlers[t]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(t){f.forEach(this.handlers,function(r){r!==null&&t(r)})}}const cr={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},xs=typeof URLSearchParams<"u"?URLSearchParams:zt,Es=typeof FormData<"u"?FormData:null,Ss=typeof Blob<"u"?Blob:null,As={isBrowser:!0,classes:{URLSearchParams:xs,FormData:Es,Blob:Ss},protocols:["http","https","file","blob","url","data"]},Kt=typeof window<"u"&&typeof document<"u",St=typeof navigator=="object"&&navigator||void 0,Os=Kt&&(!St||["ReactNative","NativeScript","NS"].indexOf(St.product)<0),vs=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",Rs=Kt&&window.location.href||"http://localhost",Ts=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:Kt,hasStandardBrowserEnv:Os,hasStandardBrowserWebWorkerEnv:vs,navigator:St,origin:Rs},Symbol.toStringTag,{value:"Module"})),T={...Ts,...As};function Cs(e,t){return rt(e,new T.classes.URLSearchParams,{visitor:function(n,r,i,s){return T.isNode&&f.isBuffer(n)?(this.append(r,n.toString("base64")),!1):s.defaultVisitor.apply(this,arguments)},...t})}function Ps(e){return f.matchAll(/\w+|\[(\w*)]/g,e).map(t=>t[0]==="[]"?"":t[1]||t[0])}function Ns(e){const t={},n=Object.keys(e);let r;const i=n.length;let s;for(r=0;r=n.length;return o=!o&&f.isArray(i)?i.length:o,c?(f.hasOwnProp(i,o)?i[o]=[i[o],r]:i[o]=r,!a):((!i[o]||!f.isObject(i[o]))&&(i[o]=[]),t(n,r,i[o],s)&&f.isArray(i[o])&&(i[o]=Ns(i[o])),!a)}if(f.isFormData(e)&&f.isFunction(e.entries)){const n={};return f.forEachEntry(e,(r,i)=>{t(Ps(r),i,n,0)}),n}return null}function Fs(e,t,n){if(f.isString(e))try{return(t||JSON.parse)(e),f.trim(e)}catch(r){if(r.name!=="SyntaxError")throw r}return(n||JSON.stringify)(e)}const Ne={transitional:cr,adapter:["xhr","http","fetch"],transformRequest:[function(t,n){const r=n.getContentType()||"",i=r.indexOf("application/json")>-1,s=f.isObject(t);if(s&&f.isHTMLForm(t)&&(t=new FormData(t)),f.isFormData(t))return i?JSON.stringify(ur(t)):t;if(f.isArrayBuffer(t)||f.isBuffer(t)||f.isStream(t)||f.isFile(t)||f.isBlob(t)||f.isReadableStream(t))return t;if(f.isArrayBufferView(t))return t.buffer;if(f.isURLSearchParams(t))return n.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),t.toString();let a;if(s){if(r.indexOf("application/x-www-form-urlencoded")>-1)return Cs(t,this.formSerializer).toString();if((a=f.isFileList(t))||r.indexOf("multipart/form-data")>-1){const c=this.env&&this.env.FormData;return rt(a?{"files[]":t}:t,c&&new c,this.formSerializer)}}return s||i?(n.setContentType("application/json",!1),Fs(t)):t}],transformResponse:[function(t){const n=this.transitional||Ne.transitional,r=n&&n.forcedJSONParsing,i=this.responseType==="json";if(f.isResponse(t)||f.isReadableStream(t))return t;if(t&&f.isString(t)&&(r&&!this.responseType||i)){const o=!(n&&n.silentJSONParsing)&&i;try{return JSON.parse(t,this.parseReviver)}catch(a){if(o)throw a.name==="SyntaxError"?y.from(a,y.ERR_BAD_RESPONSE,this,null,this.response):a}}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:T.classes.FormData,Blob:T.classes.Blob},validateStatus:function(t){return t>=200&&t<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};f.forEach(["delete","get","head","post","put","patch"],e=>{Ne.headers[e]={}});const Ls=f.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),Ms=e=>{const t={};let n,r,i;return e&&e.split(` +`).forEach(function(o){i=o.indexOf(":"),n=o.substring(0,i).trim().toLowerCase(),r=o.substring(i+1).trim(),!(!n||t[n]&&Ls[n])&&(n==="set-cookie"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+", "+r:r)}),t},Sn=Symbol("internals");function we(e){return e&&String(e).trim().toLowerCase()}function Ke(e){return e===!1||e==null?e:f.isArray(e)?e.map(Ke):String(e)}function js(e){const t=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let r;for(;r=n.exec(e);)t[r[1]]=r[2];return t}const ks=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function pt(e,t,n,r,i){if(f.isFunction(r))return r.call(this,t,n);if(i&&(t=n),!!f.isString(t)){if(f.isString(r))return t.indexOf(r)!==-1;if(f.isRegExp(r))return r.test(t)}}function Is(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,n,r)=>n.toUpperCase()+r)}function Bs(e,t){const n=f.toCamelCase(" "+t);["get","set","has"].forEach(r=>{Object.defineProperty(e,r+n,{value:function(i,s,o){return this[r].call(this,t,i,s,o)},configurable:!0})})}let F=class{constructor(t){t&&this.set(t)}set(t,n,r){const i=this;function s(a,c,l){const u=we(c);if(!u)throw new Error("header name must be a non-empty string");const h=f.findKey(i,u);(!h||i[h]===void 0||l===!0||l===void 0&&i[h]!==!1)&&(i[h||c]=Ke(a))}const o=(a,c)=>f.forEach(a,(l,u)=>s(l,u,c));if(f.isPlainObject(t)||t instanceof this.constructor)o(t,n);else if(f.isString(t)&&(t=t.trim())&&!ks(t))o(Ms(t),n);else if(f.isObject(t)&&f.isIterable(t)){let a={},c,l;for(const u of t){if(!f.isArray(u))throw TypeError("Object iterator must return a key-value pair");a[l=u[0]]=(c=a[l])?f.isArray(c)?[...c,u[1]]:[c,u[1]]:u[1]}o(a,n)}else t!=null&&s(n,t,r);return this}get(t,n){if(t=we(t),t){const r=f.findKey(this,t);if(r){const i=this[r];if(!n)return i;if(n===!0)return js(i);if(f.isFunction(n))return n.call(this,i,r);if(f.isRegExp(n))return n.exec(i);throw new TypeError("parser must be boolean|regexp|function")}}}has(t,n){if(t=we(t),t){const r=f.findKey(this,t);return!!(r&&this[r]!==void 0&&(!n||pt(this,this[r],r,n)))}return!1}delete(t,n){const r=this;let i=!1;function s(o){if(o=we(o),o){const a=f.findKey(r,o);a&&(!n||pt(r,r[a],a,n))&&(delete r[a],i=!0)}}return f.isArray(t)?t.forEach(s):s(t),i}clear(t){const n=Object.keys(this);let r=n.length,i=!1;for(;r--;){const s=n[r];(!t||pt(this,this[s],s,t,!0))&&(delete this[s],i=!0)}return i}normalize(t){const n=this,r={};return f.forEach(this,(i,s)=>{const o=f.findKey(r,s);if(o){n[o]=Ke(i),delete n[s];return}const a=t?Is(s):String(s).trim();a!==s&&delete n[s],n[a]=Ke(i),r[a]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){const n=Object.create(null);return f.forEach(this,(r,i)=>{r!=null&&r!==!1&&(n[i]=t&&f.isArray(r)?r.join(", "):r)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,n])=>t+": "+n).join(` +`)}getSetCookie(){return this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...n){const r=new this(t);return n.forEach(i=>r.set(i)),r}static accessor(t){const r=(this[Sn]=this[Sn]={accessors:{}}).accessors,i=this.prototype;function s(o){const a=we(o);r[a]||(Bs(i,o),r[a]=!0)}return f.isArray(t)?t.forEach(s):s(t),this}};F.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);f.reduceDescriptors(F.prototype,({value:e},t)=>{let n=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(r){this[n]=r}}});f.freezeMethods(F);function ht(e,t){const n=this||Ne,r=t||n,i=F.from(r.headers);let s=r.data;return f.forEach(e,function(a){s=a.call(n,s,i.normalize(),t?t.status:void 0)}),i.normalize(),s}function lr(e){return!!(e&&e.__CANCEL__)}function de(e,t,n){y.call(this,e??"canceled",y.ERR_CANCELED,t,n),this.name="CanceledError"}f.inherits(de,y,{__CANCEL__:!0});function fr(e,t,n){const r=n.config.validateStatus;!n.status||!r||r(n.status)?e(n):t(new y("Request failed with status code "+n.status,[y.ERR_BAD_REQUEST,y.ERR_BAD_RESPONSE][Math.floor(n.status/100)-4],n.config,n.request,n))}function Ds(e){const t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}function $s(e,t){e=e||10;const n=new Array(e),r=new Array(e);let i=0,s=0,o;return t=t!==void 0?t:1e3,function(c){const l=Date.now(),u=r[s];o||(o=l),n[i]=c,r[i]=l;let h=s,_=0;for(;h!==i;)_+=n[h++],h=h%e;if(i=(i+1)%e,i===s&&(s=(s+1)%e),l-o{n=u,i=null,s&&(clearTimeout(s),s=null),e(...l)};return[(...l)=>{const u=Date.now(),h=u-n;h>=r?o(l,u):(i=l,s||(s=setTimeout(()=>{s=null,o(i)},r-h)))},()=>i&&o(i)]}const Xe=(e,t,n=3)=>{let r=0;const i=$s(50,250);return Us(s=>{const o=s.loaded,a=s.lengthComputable?s.total:void 0,c=o-r,l=i(c),u=o<=a;r=o;const h={loaded:o,total:a,progress:a?o/a:void 0,bytes:c,rate:l||void 0,estimated:l&&a&&u?(a-o)/l:void 0,event:s,lengthComputable:a!=null,[t?"download":"upload"]:!0};e(h)},n)},An=(e,t)=>{const n=e!=null;return[r=>t[0]({lengthComputable:n,total:e,loaded:r}),t[1]]},On=e=>(...t)=>f.asap(()=>e(...t)),qs=T.hasStandardBrowserEnv?((e,t)=>n=>(n=new URL(n,T.origin),e.protocol===n.protocol&&e.host===n.host&&(t||e.port===n.port)))(new URL(T.origin),T.navigator&&/(msie|trident)/i.test(T.navigator.userAgent)):()=>!0,Hs=T.hasStandardBrowserEnv?{write(e,t,n,r,i,s){const o=[e+"="+encodeURIComponent(t)];f.isNumber(n)&&o.push("expires="+new Date(n).toGMTString()),f.isString(r)&&o.push("path="+r),f.isString(i)&&o.push("domain="+i),s===!0&&o.push("secure"),document.cookie=o.join("; ")},read(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove(e){this.write(e,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function zs(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}function Ks(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""):e}function dr(e,t,n){let r=!zs(t);return e&&(r||n==!1)?Ks(e,t):t}const vn=e=>e instanceof F?{...e}:e;function re(e,t){t=t||{};const n={};function r(l,u,h,_){return f.isPlainObject(l)&&f.isPlainObject(u)?f.merge.call({caseless:_},l,u):f.isPlainObject(u)?f.merge({},u):f.isArray(u)?u.slice():u}function i(l,u,h,_){if(f.isUndefined(u)){if(!f.isUndefined(l))return r(void 0,l,h,_)}else return r(l,u,h,_)}function s(l,u){if(!f.isUndefined(u))return r(void 0,u)}function o(l,u){if(f.isUndefined(u)){if(!f.isUndefined(l))return r(void 0,l)}else return r(void 0,u)}function a(l,u,h){if(h in t)return r(l,u);if(h in e)return r(void 0,l)}const c={url:s,method:s,data:s,baseURL:o,transformRequest:o,transformResponse:o,paramsSerializer:o,timeout:o,timeoutMessage:o,withCredentials:o,withXSRFToken:o,adapter:o,responseType:o,xsrfCookieName:o,xsrfHeaderName:o,onUploadProgress:o,onDownloadProgress:o,decompress:o,maxContentLength:o,maxBodyLength:o,beforeRedirect:o,transport:o,httpAgent:o,httpsAgent:o,cancelToken:o,socketPath:o,responseEncoding:o,validateStatus:a,headers:(l,u,h)=>i(vn(l),vn(u),h,!0)};return f.forEach(Object.keys({...e,...t}),function(u){const h=c[u]||i,_=h(e[u],t[u],u);f.isUndefined(_)&&h!==a||(n[u]=_)}),n}const pr=e=>{const t=re({},e);let{data:n,withXSRFToken:r,xsrfHeaderName:i,xsrfCookieName:s,headers:o,auth:a}=t;if(t.headers=o=F.from(o),t.url=ar(dr(t.baseURL,t.url,t.allowAbsoluteUrls),e.params,e.paramsSerializer),a&&o.set("Authorization","Basic "+btoa((a.username||"")+":"+(a.password?unescape(encodeURIComponent(a.password)):""))),f.isFormData(n)){if(T.hasStandardBrowserEnv||T.hasStandardBrowserWebWorkerEnv)o.setContentType(void 0);else if(f.isFunction(n.getHeaders)){const c=n.getHeaders(),l=["content-type","content-length"];Object.entries(c).forEach(([u,h])=>{l.includes(u.toLowerCase())&&o.set(u,h)})}}if(T.hasStandardBrowserEnv&&(r&&f.isFunction(r)&&(r=r(t)),r||r!==!1&&qs(t.url))){const c=i&&s&&Hs.read(s);c&&o.set(i,c)}return t},Js=typeof XMLHttpRequest<"u",Ws=Js&&function(e){return new Promise(function(n,r){const i=pr(e);let s=i.data;const o=F.from(i.headers).normalize();let{responseType:a,onUploadProgress:c,onDownloadProgress:l}=i,u,h,_,b,p;function m(){b&&b(),p&&p(),i.cancelToken&&i.cancelToken.unsubscribe(u),i.signal&&i.signal.removeEventListener("abort",u)}let d=new XMLHttpRequest;d.open(i.method.toUpperCase(),i.url,!0),d.timeout=i.timeout;function g(){if(!d)return;const w=F.from("getAllResponseHeaders"in d&&d.getAllResponseHeaders()),R={data:!a||a==="text"||a==="json"?d.responseText:d.response,status:d.status,statusText:d.statusText,headers:w,config:e,request:d};fr(function(C){n(C),m()},function(C){r(C),m()},R),d=null}"onloadend"in d?d.onloadend=g:d.onreadystatechange=function(){!d||d.readyState!==4||d.status===0&&!(d.responseURL&&d.responseURL.indexOf("file:")===0)||setTimeout(g)},d.onabort=function(){d&&(r(new y("Request aborted",y.ECONNABORTED,e,d)),d=null)},d.onerror=function(E){const R=E&&E.message?E.message:"Network Error",L=new y(R,y.ERR_NETWORK,e,d);L.event=E||null,r(L),d=null},d.ontimeout=function(){let E=i.timeout?"timeout of "+i.timeout+"ms exceeded":"timeout exceeded";const R=i.transitional||cr;i.timeoutErrorMessage&&(E=i.timeoutErrorMessage),r(new y(E,R.clarifyTimeoutError?y.ETIMEDOUT:y.ECONNABORTED,e,d)),d=null},s===void 0&&o.setContentType(null),"setRequestHeader"in d&&f.forEach(o.toJSON(),function(E,R){d.setRequestHeader(R,E)}),f.isUndefined(i.withCredentials)||(d.withCredentials=!!i.withCredentials),a&&a!=="json"&&(d.responseType=i.responseType),l&&([_,p]=Xe(l,!0),d.addEventListener("progress",_)),c&&d.upload&&([h,b]=Xe(c),d.upload.addEventListener("progress",h),d.upload.addEventListener("loadend",b)),(i.cancelToken||i.signal)&&(u=w=>{d&&(r(!w||w.type?new de(null,e,d):w),d.abort(),d=null)},i.cancelToken&&i.cancelToken.subscribe(u),i.signal&&(i.signal.aborted?u():i.signal.addEventListener("abort",u)));const x=Ds(i.url);if(x&&T.protocols.indexOf(x)===-1){r(new y("Unsupported protocol "+x+":",y.ERR_BAD_REQUEST,e));return}d.send(s||null)})},Vs=(e,t)=>{const{length:n}=e=e?e.filter(Boolean):[];if(t||n){let r=new AbortController,i;const s=function(l){if(!i){i=!0,a();const u=l instanceof Error?l:this.reason;r.abort(u instanceof y?u:new de(u instanceof Error?u.message:u))}};let o=t&&setTimeout(()=>{o=null,s(new y(`timeout ${t} of ms exceeded`,y.ETIMEDOUT))},t);const a=()=>{e&&(o&&clearTimeout(o),o=null,e.forEach(l=>{l.unsubscribe?l.unsubscribe(s):l.removeEventListener("abort",s)}),e=null)};e.forEach(l=>l.addEventListener("abort",s));const{signal:c}=r;return c.unsubscribe=()=>f.asap(a),c}},Xs=function*(e,t){let n=e.byteLength;if(n{const i=Gs(e,t);let s=0,o,a=c=>{o||(o=!0,r&&r(c))};return new ReadableStream({async pull(c){try{const{done:l,value:u}=await i.next();if(l){a(),c.close();return}let h=u.byteLength;if(n){let _=s+=h;n(_)}c.enqueue(new Uint8Array(u))}catch(l){throw a(l),l}},cancel(c){return a(c),i.return()}},{highWaterMark:2})},Tn=64*1024,{isFunction:Be}=f,Zs=(({Request:e,Response:t})=>({Request:e,Response:t}))(f.global),{ReadableStream:Cn,TextEncoder:Pn}=f.global,Nn=(e,...t)=>{try{return!!e(...t)}catch{return!1}},Qs=e=>{e=f.merge.call({skipUndefined:!0},Zs,e);const{fetch:t,Request:n,Response:r}=e,i=t?Be(t):typeof fetch=="function",s=Be(n),o=Be(r);if(!i)return!1;const a=i&&Be(Cn),c=i&&(typeof Pn=="function"?(p=>m=>p.encode(m))(new Pn):async p=>new Uint8Array(await new n(p).arrayBuffer())),l=s&&a&&Nn(()=>{let p=!1;const m=new n(T.origin,{body:new Cn,method:"POST",get duplex(){return p=!0,"half"}}).headers.has("Content-Type");return p&&!m}),u=o&&a&&Nn(()=>f.isReadableStream(new r("").body)),h={stream:u&&(p=>p.body)};i&&["text","arrayBuffer","blob","formData","stream"].forEach(p=>{!h[p]&&(h[p]=(m,d)=>{let g=m&&m[p];if(g)return g.call(m);throw new y(`Response type '${p}' is not supported`,y.ERR_NOT_SUPPORT,d)})});const _=async p=>{if(p==null)return 0;if(f.isBlob(p))return p.size;if(f.isSpecCompliantForm(p))return(await new n(T.origin,{method:"POST",body:p}).arrayBuffer()).byteLength;if(f.isArrayBufferView(p)||f.isArrayBuffer(p))return p.byteLength;if(f.isURLSearchParams(p)&&(p=p+""),f.isString(p))return(await c(p)).byteLength},b=async(p,m)=>{const d=f.toFiniteNumber(p.getContentLength());return d??_(m)};return async p=>{let{url:m,method:d,data:g,signal:x,cancelToken:w,timeout:E,onDownloadProgress:R,onUploadProgress:L,responseType:C,headers:ye,withCredentials:oe="same-origin",fetchOptions:je}=pr(p),hn=t||fetch;C=C?(C+"").toLowerCase():"text";let ke=Vs([x,w&&w.toAbortSignal()],E),be=null;const V=ke&&ke.unsubscribe&&(()=>{ke.unsubscribe()});let _n;try{if(L&&l&&d!=="get"&&d!=="head"&&(_n=await b(ye,g))!==0){let H=new n(m,{method:"POST",body:g,duplex:"half"}),ae;if(f.isFormData(g)&&(ae=H.headers.get("content-type"))&&ye.setContentType(ae),H.body){const[dt,Ie]=An(_n,Xe(On(L)));g=Rn(H.body,Tn,dt,Ie)}}f.isString(oe)||(oe=oe?"include":"omit");const B=s&&"credentials"in n.prototype,mn={...je,signal:ke,method:d.toUpperCase(),headers:ye.normalize().toJSON(),body:g,duplex:"half",credentials:B?oe:void 0};be=s&&new n(m,mn);let q=await(s?hn(be,je):hn(m,mn));const gn=u&&(C==="stream"||C==="response");if(u&&(R||gn&&V)){const H={};["status","statusText","headers"].forEach(yn=>{H[yn]=q[yn]});const ae=f.toFiniteNumber(q.headers.get("content-length")),[dt,Ie]=R&&An(ae,Xe(On(R),!0))||[];q=new r(Rn(q.body,Tn,dt,()=>{Ie&&Ie(),V&&V()}),H)}C=C||"text";let Ni=await h[f.findKey(h,C)||"text"](q,p);return!gn&&V&&V(),await new Promise((H,ae)=>{fr(H,ae,{data:Ni,headers:F.from(q.headers),status:q.status,statusText:q.statusText,config:p,request:be})})}catch(B){throw V&&V(),B&&B.name==="TypeError"&&/Load failed|fetch/i.test(B.message)?Object.assign(new y("Network Error",y.ERR_NETWORK,p,be),{cause:B.cause||B}):y.from(B,B&&B.code,p,be)}}},eo=new Map,hr=e=>{let t=e?e.env:{};const{fetch:n,Request:r,Response:i}=t,s=[r,i,n];let o=s.length,a=o,c,l,u=eo;for(;a--;)c=s[a],l=u.get(c),l===void 0&&u.set(c,l=a?new Map:Qs(t)),u=l;return l};hr();const At={http:gs,xhr:Ws,fetch:{get:hr}};f.forEach(At,(e,t)=>{if(e){try{Object.defineProperty(e,"name",{value:t})}catch{}Object.defineProperty(e,"adapterName",{value:t})}});const Fn=e=>`- ${e}`,to=e=>f.isFunction(e)||e===null||e===!1,_r={getAdapter:(e,t)=>{e=f.isArray(e)?e:[e];const{length:n}=e;let r,i;const s={};for(let o=0;o`adapter ${c} `+(l===!1?"is not supported by the environment":"is not available in the build"));let a=n?o.length>1?`since : +`+o.map(Fn).join(` +`):" "+Fn(o[0]):"as no adapter specified";throw new y("There is no suitable adapter to dispatch the request "+a,"ERR_NOT_SUPPORT")}return i},adapters:At};function _t(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new de(null,e)}function Ln(e){return _t(e),e.headers=F.from(e.headers),e.data=ht.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),_r.getAdapter(e.adapter||Ne.adapter,e)(e).then(function(r){return _t(e),r.data=ht.call(e,e.transformResponse,r),r.headers=F.from(r.headers),r},function(r){return lr(r)||(_t(e),r&&r.response&&(r.response.data=ht.call(e,e.transformResponse,r.response),r.response.headers=F.from(r.response.headers))),Promise.reject(r)})}const mr="1.12.2",it={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{it[e]=function(r){return typeof r===e||"a"+(t<1?"n ":" ")+e}});const Mn={};it.transitional=function(t,n,r){function i(s,o){return"[Axios v"+mr+"] Transitional option '"+s+"'"+o+(r?". "+r:"")}return(s,o,a)=>{if(t===!1)throw new y(i(o," has been removed"+(n?" in "+n:"")),y.ERR_DEPRECATED);return n&&!Mn[o]&&(Mn[o]=!0,console.warn(i(o," has been deprecated since v"+n+" and will be removed in the near future"))),t?t(s,o,a):!0}};it.spelling=function(t){return(n,r)=>(console.warn(`${r} is likely a misspelling of ${t}`),!0)};function no(e,t,n){if(typeof e!="object")throw new y("options must be an object",y.ERR_BAD_OPTION_VALUE);const r=Object.keys(e);let i=r.length;for(;i-- >0;){const s=r[i],o=t[s];if(o){const a=e[s],c=a===void 0||o(a,s,e);if(c!==!0)throw new y("option "+s+" must be "+c,y.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new y("Unknown option "+s,y.ERR_BAD_OPTION)}}const Je={assertOptions:no,validators:it},D=Je.validators;let Z=class{constructor(t){this.defaults=t||{},this.interceptors={request:new En,response:new En}}async request(t,n){try{return await this._request(t,n)}catch(r){if(r instanceof Error){let i={};Error.captureStackTrace?Error.captureStackTrace(i):i=new Error;const s=i.stack?i.stack.replace(/^.+\n/,""):"";try{r.stack?s&&!String(r.stack).endsWith(s.replace(/^.+\n.+\n/,""))&&(r.stack+=` +`+s):r.stack=s}catch{}}throw r}}_request(t,n){typeof t=="string"?(n=n||{},n.url=t):n=t||{},n=re(this.defaults,n);const{transitional:r,paramsSerializer:i,headers:s}=n;r!==void 0&&Je.assertOptions(r,{silentJSONParsing:D.transitional(D.boolean),forcedJSONParsing:D.transitional(D.boolean),clarifyTimeoutError:D.transitional(D.boolean)},!1),i!=null&&(f.isFunction(i)?n.paramsSerializer={serialize:i}:Je.assertOptions(i,{encode:D.function,serialize:D.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),Je.assertOptions(n,{baseUrl:D.spelling("baseURL"),withXsrfToken:D.spelling("withXSRFToken")},!0),n.method=(n.method||this.defaults.method||"get").toLowerCase();let o=s&&f.merge(s.common,s[n.method]);s&&f.forEach(["delete","get","head","post","put","patch","common"],p=>{delete s[p]}),n.headers=F.concat(o,s);const a=[];let c=!0;this.interceptors.request.forEach(function(m){typeof m.runWhen=="function"&&m.runWhen(n)===!1||(c=c&&m.synchronous,a.unshift(m.fulfilled,m.rejected))});const l=[];this.interceptors.response.forEach(function(m){l.push(m.fulfilled,m.rejected)});let u,h=0,_;if(!c){const p=[Ln.bind(this),void 0];for(p.unshift(...a),p.push(...l),_=p.length,u=Promise.resolve(n);h<_;)u=u.then(p[h++],p[h++]);return u}_=a.length;let b=n;for(;h<_;){const p=a[h++],m=a[h++];try{b=p(b)}catch(d){m.call(this,d);break}}try{u=Ln.call(this,b)}catch(p){return Promise.reject(p)}for(h=0,_=l.length;h<_;)u=u.then(l[h++],l[h++]);return u}getUri(t){t=re(this.defaults,t);const n=dr(t.baseURL,t.url,t.allowAbsoluteUrls);return ar(n,t.params,t.paramsSerializer)}};f.forEach(["delete","get","head","options"],function(t){Z.prototype[t]=function(n,r){return this.request(re(r||{},{method:t,url:n,data:(r||{}).data}))}});f.forEach(["post","put","patch"],function(t){function n(r){return function(s,o,a){return this.request(re(a||{},{method:t,headers:r?{"Content-Type":"multipart/form-data"}:{},url:s,data:o}))}}Z.prototype[t]=n(),Z.prototype[t+"Form"]=n(!0)});let ro=class gr{constructor(t){if(typeof t!="function")throw new TypeError("executor must be a function.");let n;this.promise=new Promise(function(s){n=s});const r=this;this.promise.then(i=>{if(!r._listeners)return;let s=r._listeners.length;for(;s-- >0;)r._listeners[s](i);r._listeners=null}),this.promise.then=i=>{let s;const o=new Promise(a=>{r.subscribe(a),s=a}).then(i);return o.cancel=function(){r.unsubscribe(s)},o},t(function(s,o,a){r.reason||(r.reason=new de(s,o,a),n(r.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const n=this._listeners.indexOf(t);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const t=new AbortController,n=r=>{t.abort(r)};return this.subscribe(n),t.signal.unsubscribe=()=>this.unsubscribe(n),t.signal}static source(){let t;return{token:new gr(function(i){t=i}),cancel:t}}};function io(e){return function(n){return e.apply(null,n)}}function so(e){return f.isObject(e)&&e.isAxiosError===!0}const Ot={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Ot).forEach(([e,t])=>{Ot[t]=e});function yr(e){const t=new Z(e),n=Xn(Z.prototype.request,t);return f.extend(n,Z.prototype,t,{allOwnKeys:!0}),f.extend(n,t,null,{allOwnKeys:!0}),n.create=function(i){return yr(re(e,i))},n}const O=yr(Ne);O.Axios=Z;O.CanceledError=de;O.CancelToken=ro;O.isCancel=lr;O.VERSION=mr;O.toFormData=rt;O.AxiosError=y;O.Cancel=O.CanceledError;O.all=function(t){return Promise.all(t)};O.spread=io;O.isAxiosError=so;O.mergeConfig=re;O.AxiosHeaders=F;O.formToJSON=e=>ur(f.isHTMLForm(e)?new FormData(e):e);O.getAdapter=_r.getAdapter;O.HttpStatusCode=Ot;O.default=O;const{Axios:yc,AxiosError:bc,CanceledError:wc,isCancel:xc,CancelToken:Ec,VERSION:Sc,all:Ac,Cancel:Oc,isAxiosError:vc,spread:Rc,toFormData:Tc,AxiosHeaders:Cc,HttpStatusCode:Pc,formToJSON:Nc,getAdapter:Fc,mergeConfig:Lc}=O;window.axios=O;window.axios.defaults.headers.common["X-Requested-With"]="XMLHttpRequest";var vt=!1,Rt=!1,Q=[],Tt=-1;function oo(e){ao(e)}function ao(e){Q.includes(e)||Q.push(e),uo()}function co(e){let t=Q.indexOf(e);t!==-1&&t>Tt&&Q.splice(t,1)}function uo(){!Rt&&!vt&&(vt=!0,queueMicrotask(lo))}function lo(){vt=!1,Rt=!0;for(let e=0;ee.effect(t,{scheduler:n=>{Ct?oo(n):n()}}),br=e.raw}function jn(e){se=e}function ho(e){let t=()=>{};return[r=>{let i=se(r);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(s=>s())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),he(i))},i},()=>{t()}]}function wr(e,t){let n=!0,r,i=se(()=>{let s=e();JSON.stringify(s),n?r=s:queueMicrotask(()=>{t(s,r),r=s}),n=!1});return()=>he(i)}var xr=[],Er=[],Sr=[];function _o(e){Sr.push(e)}function Jt(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Er.push(t))}function Ar(e){xr.push(e)}function Or(e,t,n){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(n)}function vr(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([n,r])=>{(t===void 0||t.includes(n))&&(r.forEach(i=>i()),delete e._x_attributeCleanups[n])})}function mo(e){for(e._x_effects?.forEach(co);e._x_cleanups?.length;)e._x_cleanups.pop()()}var Wt=new MutationObserver(Yt),Vt=!1;function Xt(){Wt.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),Vt=!0}function Rr(){go(),Wt.disconnect(),Vt=!1}var xe=[];function go(){let e=Wt.takeRecords();xe.push(()=>e.length>0&&Yt(e));let t=xe.length;queueMicrotask(()=>{if(xe.length===t)for(;xe.length>0;)xe.shift()()})}function A(e){if(!Vt)return e();Rr();let t=e();return Xt(),t}var Gt=!1,Ge=[];function yo(){Gt=!0}function bo(){Gt=!1,Yt(Ge),Ge=[]}function Yt(e){if(Gt){Ge=Ge.concat(e);return}let t=[],n=new Set,r=new Map,i=new Map;for(let s=0;s{o.nodeType===1&&o._x_marker&&n.add(o)}),e[s].addedNodes.forEach(o=>{if(o.nodeType===1){if(n.has(o)){n.delete(o);return}o._x_marker||t.push(o)}})),e[s].type==="attributes")){let o=e[s].target,a=e[s].attributeName,c=e[s].oldValue,l=()=>{r.has(o)||r.set(o,[]),r.get(o).push({name:a,value:o.getAttribute(a)})},u=()=>{i.has(o)||i.set(o,[]),i.get(o).push(a)};o.hasAttribute(a)&&c===null?l():o.hasAttribute(a)?(u(),l()):u()}i.forEach((s,o)=>{vr(o,s)}),r.forEach((s,o)=>{xr.forEach(a=>a(o,s))});for(let s of n)t.some(o=>o.contains(s))||Er.forEach(o=>o(s));for(let s of t)s.isConnected&&Sr.forEach(o=>o(s));t=null,n=null,r=null,i=null}function Tr(e){return Le(ue(e))}function Fe(e,t,n){return e._x_dataStack=[t,...ue(n||e)],()=>{e._x_dataStack=e._x_dataStack.filter(r=>r!==t)}}function ue(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?ue(e.host):e.parentNode?ue(e.parentNode):[]}function Le(e){return new Proxy({objects:e},wo)}var wo={ownKeys({objects:e}){return Array.from(new Set(e.flatMap(t=>Object.keys(t))))},has({objects:e},t){return t==Symbol.unscopables?!1:e.some(n=>Object.prototype.hasOwnProperty.call(n,t)||Reflect.has(n,t))},get({objects:e},t,n){return t=="toJSON"?xo:Reflect.get(e.find(r=>Reflect.has(r,t))||{},t,n)},set({objects:e},t,n,r){const i=e.find(o=>Object.prototype.hasOwnProperty.call(o,t))||e[e.length-1],s=Object.getOwnPropertyDescriptor(i,t);return s?.set&&s?.get?s.set.call(r,n)||!0:Reflect.set(i,t,n)}};function xo(){return Reflect.ownKeys(this).reduce((t,n)=>(t[n]=Reflect.get(this,n),t),{})}function Cr(e){let t=r=>typeof r=="object"&&!Array.isArray(r)&&r!==null,n=(r,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(r)).forEach(([s,{value:o,enumerable:a}])=>{if(a===!1||o===void 0||typeof o=="object"&&o!==null&&o.__v_skip)return;let c=i===""?s:`${i}.${s}`;typeof o=="object"&&o!==null&&o._x_interceptor?r[s]=o.initialize(e,c,s):t(o)&&o!==r&&!(o instanceof Element)&&n(o,c)})};return n(e)}function Pr(e,t=()=>{}){let n={initialValue:void 0,_x_interceptor:!0,initialize(r,i,s){return e(this.initialValue,()=>Eo(r,i),o=>Pt(r,i,o),i,s)}};return t(n),r=>{if(typeof r=="object"&&r!==null&&r._x_interceptor){let i=n.initialize.bind(n);n.initialize=(s,o,a)=>{let c=r.initialize(s,o,a);return n.initialValue=c,i(s,o,a)}}else n.initialValue=r;return n}}function Eo(e,t){return t.split(".").reduce((n,r)=>n[r],e)}function Pt(e,t,n){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=n;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),Pt(e[t[0]],t.slice(1),n)}}var Nr={};function I(e,t){Nr[e]=t}function Nt(e,t){let n=So(t);return Object.entries(Nr).forEach(([r,i])=>{Object.defineProperty(e,`$${r}`,{get(){return i(t,n)},enumerable:!1})}),e}function So(e){let[t,n]=Ir(e),r={interceptor:Pr,...t};return Jt(e,n),r}function Ao(e,t,n,...r){try{return n(...r)}catch(i){Re(i,e,t)}}function Re(e,t,n=void 0){e=Object.assign(e??{message:"No error message given."},{el:t,expression:n}),console.warn(`Alpine Expression Error: ${e.message} + +${n?'Expression: "'+n+`" + +`:""}`,t),setTimeout(()=>{throw e},0)}var We=!0;function Fr(e){let t=We;We=!1;let n=e();return We=t,n}function ee(e,t,n={}){let r;return P(e,t)(i=>r=i,n),r}function P(...e){return Lr(...e)}var Lr=Mr;function Oo(e){Lr=e}function Mr(e,t){let n={};Nt(n,e);let r=[n,...ue(e)],i=typeof t=="function"?vo(r,t):To(r,t,e);return Ao.bind(null,e,t,i)}function vo(e,t){return(n=()=>{},{scope:r={},params:i=[],context:s}={})=>{let o=t.apply(Le([r,...e]),i);Ye(n,o)}}var mt={};function Ro(e,t){if(mt[e])return mt[e];let n=Object.getPrototypeOf(async function(){}).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,s=(()=>{try{let o=new n(["__self","scope"],`with (scope) { __self.result = ${r} }; __self.finished = true; return __self.result;`);return Object.defineProperty(o,"name",{value:`[Alpine] ${e}`}),o}catch(o){return Re(o,t,e),Promise.resolve()}})();return mt[e]=s,s}function To(e,t,n){let r=Ro(t,n);return(i=()=>{},{scope:s={},params:o=[],context:a}={})=>{r.result=void 0,r.finished=!1;let c=Le([s,...e]);if(typeof r=="function"){let l=r.call(a,r,c).catch(u=>Re(u,n,t));r.finished?(Ye(i,r.result,c,o,n),r.result=void 0):l.then(u=>{Ye(i,u,c,o,n)}).catch(u=>Re(u,n,t)).finally(()=>r.result=void 0)}}}function Ye(e,t,n,r,i){if(We&&typeof t=="function"){let s=t.apply(n,r);s instanceof Promise?s.then(o=>Ye(e,o,n,r)).catch(o=>Re(o,i,t)):e(s)}else typeof t=="object"&&t instanceof Promise?t.then(s=>e(s)):e(t)}var Zt="x-";function _e(e=""){return Zt+e}function Co(e){Zt=e}var Ze={};function v(e,t){return Ze[e]=t,{before(n){if(!Ze[n]){console.warn(String.raw`Cannot find directive \`${n}\`. \`${e}\` will use the default order of execution`);return}const r=Y.indexOf(n);Y.splice(r>=0?r:Y.indexOf("DEFAULT"),0,e)}}}function Po(e){return Object.keys(Ze).includes(e)}function Qt(e,t,n){if(t=Array.from(t),e._x_virtualDirectives){let s=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),o=jr(s);s=s.map(a=>o.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(s)}let r={};return t.map($r((s,o)=>r[s]=o)).filter(qr).map(Lo(r,n)).sort(Mo).map(s=>Fo(e,s))}function jr(e){return Array.from(e).map($r()).filter(t=>!qr(t))}var Ft=!1,Ae=new Map,kr=Symbol();function No(e){Ft=!0;let t=Symbol();kr=t,Ae.set(t,[]);let n=()=>{for(;Ae.get(t).length;)Ae.get(t).shift()();Ae.delete(t)},r=()=>{Ft=!1,n()};e(n),r()}function Ir(e){let t=[],n=a=>t.push(a),[r,i]=ho(e);return t.push(i),[{Alpine:Me,effect:r,cleanup:n,evaluateLater:P.bind(P,e),evaluate:ee.bind(ee,e)},()=>t.forEach(a=>a())]}function Fo(e,t){let n=()=>{},r=Ze[t.type]||n,[i,s]=Ir(e);Or(e,t.original,s);let o=()=>{e._x_ignore||e._x_ignoreSelf||(r.inline&&r.inline(e,t,i),r=r.bind(r,e,t,i),Ft?Ae.get(kr).push(r):r())};return o.runCleanups=s,o}var Br=(e,t)=>({name:n,value:r})=>(n.startsWith(e)&&(n=n.replace(e,t)),{name:n,value:r}),Dr=e=>e;function $r(e=()=>{}){return({name:t,value:n})=>{let{name:r,value:i}=Ur.reduce((s,o)=>o(s),{name:t,value:n});return r!==t&&e(r,t),{name:r,value:i}}}var Ur=[];function en(e){Ur.push(e)}function qr({name:e}){return Hr().test(e)}var Hr=()=>new RegExp(`^${Zt}([^:^.]+)\\b`);function Lo(e,t){return({name:n,value:r})=>{let i=n.match(Hr()),s=n.match(/:([a-zA-Z0-9\-_:]+)/),o=n.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[n]||n;return{type:i?i[1]:null,value:s?s[1]:null,modifiers:o.map(c=>c.replace(".","")),expression:r,original:a}}}var Lt="DEFAULT",Y=["ignore","ref","data","id","anchor","bind","init","for","model","modelable","transition","show","if",Lt,"teleport"];function Mo(e,t){let n=Y.indexOf(e.type)===-1?Lt:e.type,r=Y.indexOf(t.type)===-1?Lt:t.type;return Y.indexOf(n)-Y.indexOf(r)}function Oe(e,t,n={}){e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0,cancelable:!0}))}function ie(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>ie(i,t));return}let n=!1;if(t(e,()=>n=!0),n)return;let r=e.firstElementChild;for(;r;)ie(r,t),r=r.nextElementSibling}function M(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var kn=!1;function jo(){kn&&M("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),kn=!0,document.body||M("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` tags. + * + * ==== IMPORTANT ==== Enabling this for documents you do not trust (e.g. arbitrary remote html pages) + * is a security risk. + * Embedded scripts are run with the same level of system access available to dompdf. + * Set this option to false (recommended) if you wish to process untrusted documents. + * This setting may increase the risk of system exploit. + * Do not change this settings without understanding the consequences. + * Additional documentation is available on the dompdf wiki at: + * https://github.com/dompdf/dompdf/wiki + * + * @var bool + */ + 'enable_php' => false, + + /** + * Enable inline JavaScript + * + * If this setting is set to true then DOMPDF will automatically insert JavaScript code contained + * within tags as written into the PDF. + * NOTE: This is PDF-based JavaScript to be executed by the PDF viewer, + * not browser-based JavaScript executed by Dompdf. + * + * @var bool + */ + 'enable_javascript' => true, + + /** + * Enable remote file access + * + * If this setting is set to true, DOMPDF will access remote sites for + * images and CSS files as required. + * + * ==== IMPORTANT ==== + * This can be a security risk, in particular in combination with isPhpEnabled and + * allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...); + * This allows anonymous users to download legally doubtful internet content which on + * tracing back appears to being downloaded by your server, or allows malicious php code + * in remote html pages to be executed by your server with your account privileges. + * + * This setting may increase the risk of system exploit. Do not change + * this settings without understanding the consequences. Additional + * documentation is available on the dompdf wiki at: + * https://github.com/dompdf/dompdf/wiki + * + * @var bool + */ + 'enable_remote' => false, + + /** + * List of allowed remote hosts + * + * Each value of the array must be a valid hostname. + * + * This will be used to filter which resources can be loaded in combination with + * isRemoteEnabled. If enable_remote is FALSE, then this will have no effect. + * + * Leave to NULL to allow any remote host. + * + * @var array|null + */ + 'allowed_remote_hosts' => null, + + /** + * A ratio applied to the fonts height to be more like browsers' line height + */ + 'font_height_ratio' => 1.1, + + /** + * Use the HTML5 Lib parser + * + * @deprecated This feature is now always on in dompdf 2.x + * + * @var bool + */ + 'enable_html5_parser' => true, + ], + +]; diff --git a/public/css/login.css b/css/login.css similarity index 59% rename from public/css/login.css rename to css/login.css index bf1aae5..5ed09d0 100644 --- a/public/css/login.css +++ b/css/login.css @@ -8,6 +8,74 @@ padding: 0; } +/* ══════════════════════════════════════════════ + CSS VARIABLES — default: SISWA (biru) +══════════════════════════════════════════════ */ +:root { + --blob1: rgba(100, 180, 255, 0.12); + --blob2: rgba(120, 80, 255, 0.1); + --badge-dot: #69f0ae; + --role-color: #1a5fb8; + --underline-from: #60b4ff; + --underline-to: #a78bfa; + --submit-from: #2b8ef3; + --submit-to: #1a7ae0; + --submit-shadow: rgba(43, 142, 243, 0.35); + --submit-shadow-hover: rgba(43, 142, 243, 0.45); + --check-color: #2b8ef3; + --focus-border: #2b8ef3; + --focus-shadow: rgba(43, 142, 243, 0.12); + --link-color: #1a5fb8; + --link-hover-bg: #e8f4ff; +} + +/* ══════════════════════════════════════════════ + GURU — Hijau (sesuai portal landing page) + Referensi: .pc-green / .bp-green / #accent2 +══════════════════════════════════════════════ */ +body.login-guru { + --blob1: rgba(34, 197, 94, 0.14); + --blob2: rgba(134, 239, 172, 0.12); + --badge-dot: #4ade80; + --role-color: #15803d; + --underline-from: #22c55e; + --underline-to: #86efac; + --submit-from: #22c55e; + --submit-to: #16a34a; + --submit-shadow: rgba(34, 197, 94, 0.35); + --submit-shadow-hover: rgba(34, 197, 94, 0.45); + --check-color: #22c55e; + --focus-border: #22c55e; + --focus-shadow: rgba(34, 197, 94, 0.12); + --link-color: #15803d; + --link-hover-bg: #f0fdf4; +} + +/* ══════════════════════════════════════════════ + ADMIN — Orange (sesuai portal landing page) + Referensi: .pc-orange / .bp-orange / #accent1 +══════════════════════════════════════════════ */ +body.login-admin { + --blob1: rgba(249, 115, 22, 0.14); + --blob2: rgba(251, 146, 60, 0.12); + --badge-dot: #fb923c; + --role-color: #c2410c; + --underline-from: #f97316; + --underline-to: #fdba74; + --submit-from: #f97316; + --submit-to: #ea580c; + --submit-shadow: rgba(249, 115, 22, 0.35); + --submit-shadow-hover: rgba(249, 115, 22, 0.45); + --check-color: #f97316; + --focus-border: #f97316; + --focus-shadow: rgba(249, 115, 22, 0.12); + --link-color: #c2410c; + --link-hover-bg: #fff7ed; +} + +/* ══════════════════════════════════════════════ + BASE — background SISWA (biru navy) +══════════════════════════════════════════════ */ body { font-family: "Plus Jakarta Sans", sans-serif; min-height: 100vh; @@ -26,12 +94,41 @@ body { justify-content: center; } +/* ══════════════════════════════════════════════ + GURU — background hijau tua yang dalam & padu + Nada: forest / emerald, lebih kaya dari sekadar gelap +══════════════════════════════════════════════ */ +body.login-guru { + background: linear-gradient( + 135deg, + #052e16 0%, + #064e2a 30%, + #166534 60%, + #065f2d 85%, + #052e16 100% + ) !important; +} + +/* ══════════════════════════════════════════════ + ADMIN — background oranye tua yang hangat & padu + Nada: ember / mahogany, lebih kaya & tidak terlalu merah +══════════════════════════════════════════════ */ +body.login-admin { + background: linear-gradient( + 135deg, + #431407 0%, + #6b2109 30%, + #9a3412 60%, + #7c2d12 85%, + #431407 100% + ) !important; +} + /* ── DECORATIVE SHAPES ── */ .shape { position: fixed; pointer-events: none; } - .shape-1 { width: 320px; height: 320px; @@ -41,7 +138,6 @@ .shape-1 { left: 80px; transform: rotate(20deg); } - .shape-2 { width: 220px; height: 220px; @@ -51,7 +147,6 @@ .shape-2 { left: 260px; transform: rotate(-15deg); } - .shape-3 { width: 180px; height: 180px; @@ -61,7 +156,6 @@ .shape-3 { right: 320px; transform: rotate(30deg); } - .shape-4 { width: 400px; height: 400px; @@ -71,7 +165,6 @@ .shape-4 { right: -80px; transform: rotate(12deg); } - .shape-5 { width: 100px; height: 100px; @@ -88,24 +181,22 @@ .blob { pointer-events: none; filter: blur(80px); } - .blob-1 { width: 500px; height: 500px; - background: rgba(100, 180, 255, 0.12); + background: var(--blob1); top: -200px; left: -100px; } - .blob-2 { width: 350px; height: 350px; - background: rgba(120, 80, 255, 0.1); + background: var(--blob2); bottom: -100px; right: 200px; } -/* ── MAIN LAYOUT ── */ +/* ── LAYOUT ── */ .page-wrap { width: 100%; max-width: 1100px; @@ -137,7 +228,6 @@ .school-tag { align-items: center; gap: 10px; } - .school-tag::before { content: ""; display: block; @@ -158,7 +248,6 @@ .left-logo { justify-content: center; margin-bottom: 32px; } - .left-logo img { width: 38px; height: 38px; @@ -175,7 +264,6 @@ .left-welcome { letter-spacing: -1px; color: white; } - .left-welcome span { display: block; color: rgba(255, 255, 255, 0.35); @@ -216,7 +304,6 @@ .btn-learn { cursor: pointer; transition: all 0.2s; } - .btn-learn:hover { background: rgba(255, 255, 255, 0.2); } @@ -235,6 +322,24 @@ .form-card { color: #1a1a2e; } +/* GURU — card putih kehijauan lembut */ +body.login-guru .form-card { + background: #f0fdf4; + border-color: rgba(134, 239, 172, 0.35); + box-shadow: + 0 32px 64px rgba(0, 0, 0, 0.22), + 0 0 0 1px rgba(255, 255, 255, 0.75); +} + +/* ADMIN — card putih keoranyean lembut */ +body.login-admin .form-card { + background: #fff7ed; + border-color: rgba(253, 186, 116, 0.35); + box-shadow: + 0 32px 64px rgba(0, 0, 0, 0.22), + 0 0 0 1px rgba(255, 255, 255, 0.75); +} + .card-title-wrap { text-align: center; margin-bottom: 28px; @@ -251,19 +356,27 @@ .role-badge { padding: 5px 14px; font-size: 10px; font-weight: 700; - color: #5b3fc0; + color: var(--role-color); letter-spacing: 1.5px; text-transform: uppercase; font-family: "Sora", sans-serif; margin-bottom: 14px; } +body.login-guru .role-badge { + border-color: rgba(134, 239, 172, 0.45); +} + +body.login-admin .role-badge { + border-color: rgba(253, 186, 116, 0.45); +} + .badge-dot { width: 6px; height: 6px; border-radius: 50%; - background: #69f0ae; - box-shadow: 0 0 6px #69f0ae; + background: var(--badge-dot); + box-shadow: 0 0 6px var(--badge-dot); } .card-title { @@ -279,7 +392,11 @@ .title-underline { display: inline-block; width: 36px; height: 3px; - background: linear-gradient(90deg, #60b4ff, #a78bfa); + background: linear-gradient( + 90deg, + var(--underline-from), + var(--underline-to) + ); border-radius: 99px; margin-top: 2px; } @@ -288,7 +405,6 @@ .title-underline { .field-group { margin-bottom: 14px; } - .field-label { display: block; font-size: 13px; @@ -296,13 +412,11 @@ .field-label { color: #4a5568; margin-bottom: 7px; } - .field-wrap { position: relative; display: flex; align-items: center; } - .field-wrap .field-input { padding-right: 44px; } @@ -321,15 +435,22 @@ .field-input { transition: all 0.2s; } +body.login-guru .field-input { + border-color: #bbf7d0; +} +body.login-admin .field-input { + border-color: #fed7aa; +} + .field-input::placeholder { color: #bbb5cc; font-weight: 400; } .field-input:focus { - border-color: #7c5cbf; + border-color: var(--focus-border); background: white; - box-shadow: 0 0 0 3px rgba(124, 92, 191, 0.1); + box-shadow: 0 0 0 3px var(--focus-shadow); } .toggle-password { @@ -342,7 +463,6 @@ .toggle-password { display: flex; align-items: center; } - .eye-icon { width: 17px; height: 17px; @@ -350,7 +470,6 @@ .eye-icon { pointer-events: none; transition: opacity 0.2s; } - .toggle-password:hover .eye-icon { opacity: 0.6; } @@ -375,10 +494,16 @@ .remember-check { position: relative; flex-shrink: 0; } +body.login-guru .remember-check { + border-color: #86efac; +} +body.login-admin .remember-check { + border-color: #fdba74; +} .remember-check:checked { - background: #7c5cbf; - border-color: #7c5cbf; + background: var(--check-color); + border-color: var(--check-color); } .remember-check:checked::after { @@ -393,7 +518,6 @@ .remember-check:checked::after { border-left: none; transform: rotate(45deg); } - .remember-text { font-size: 13px; color: #6b7280; @@ -405,7 +529,7 @@ .remember-text { /* ── SUBMIT BUTTON ── */ .submit-btn { width: 100%; - background: linear-gradient(135deg, #7c5cbf, #5b3fc0); + background: linear-gradient(135deg, #2b8ef3, #1a7ae0); color: white; border: none; border-radius: 12px; @@ -415,25 +539,47 @@ .submit-btn { font-family: "Plus Jakarta Sans", sans-serif; cursor: pointer; transition: all 0.25s; - box-shadow: 0 8px 24px rgba(91, 63, 192, 0.3); + box-shadow: 0 8px 24px rgba(43, 142, 243, 0.35); letter-spacing: 0.3px; } - .submit-btn:hover { transform: translateY(-2px); - box-shadow: 0 12px 30px rgba(91, 63, 192, 0.4); + box-shadow: 0 12px 30px rgba(43, 142, 243, 0.45); } - .submit-btn:active { transform: translateY(0); } +/* GURU — tombol hijau */ +body.login-guru .submit-btn { + background: linear-gradient(135deg, #22c55e, #16a34a); + box-shadow: 0 8px 24px rgba(34, 197, 94, 0.35); +} +body.login-guru .submit-btn:hover { + box-shadow: 0 12px 30px rgba(34, 197, 94, 0.45); +} + +/* ADMIN — tombol orange */ +body.login-admin .submit-btn { + background: linear-gradient(135deg, #f97316, #ea580c); + box-shadow: 0 8px 24px rgba(249, 115, 22, 0.35); +} +body.login-admin .submit-btn:hover { + box-shadow: 0 12px 30px rgba(249, 115, 22, 0.45); +} + /* ── DIVIDER ── */ .card-divider { border: none; border-top: 1px solid #ede9fb; margin: 20px 0 16px; } +body.login-guru .card-divider { + border-top-color: #bbf7d0; +} +body.login-admin .card-divider { + border-top-color: #fed7aa; +} /* ── OTHER PORTALS ── */ .other-portals { @@ -445,9 +591,8 @@ .other-portals { justify-content: center; gap: 6px; } - .other-portals a { - color: #5b3fc0; + color: var(--link-color); font-weight: 700; text-decoration: none; padding: 5px 12px; @@ -455,9 +600,8 @@ .other-portals a { font-size: 15px; transition: background 0.2s; } - .other-portals a:hover { - background: #ede9fb; + background: var(--link-hover-bg); } /* ── TOAST ── */ @@ -475,7 +619,6 @@ .toast-error { toastIn 0.3s ease both, toastOut 0.4s ease 3.5s forwards; } - @keyframes toastIn { from { opacity: 0; @@ -508,7 +651,6 @@ .back-link { transition: color 0.2s; font-weight: 500; } - .back-link:hover { color: rgba(255, 255, 255, 0.7); } diff --git a/public/css/style.css b/css/style.css similarity index 100% rename from public/css/style.css rename to css/style.css diff --git a/database/migrations/2026_04_25_143601_reset_exp_system.php b/database/migrations/2026_04_25_143601_reset_exp_system.php new file mode 100644 index 0000000..88fa2f3 --- /dev/null +++ b/database/migrations/2026_04_25_143601_reset_exp_system.php @@ -0,0 +1,24 @@ + '2343762663300043', 'nama' => 'Barizatul Kamilah'], + ['nip' => '2635762664200062', 'nama' => 'Beny Setiawan'], + ['nip' => '2460770671130082', 'nama' => 'Hasan'], + ['nip' => '6535747649200022', 'nama' => 'Fibrian Kartika Nuswanto'], + ['nip' => '8637774675130152', 'nama' => 'Andy Nur Rachman'], + ['nip' => '4447762663130173', 'nama' => 'Edi Hariyanto'], + ['nip' => '3739766667130182', 'nama' => 'Fitriyah Arizkiyanti'], + ['nip' => '4743763664300082', 'nama' => 'Uswatun Hasanah'], + ['nip' => '6433772673230272', 'nama' => 'Alvi Hidayati'], + ['nip' => '9651770671130122', 'nama' => 'Haris Nurtanio'], + ['nip' => '8152760661300073', 'nama' => 'Firlya Citra Kurniawati'], + ['nip' => '1957769670130032', 'nama' => 'Ahmad Timbul Sholeh'], + ['nip' => '1949759659200002', 'nama' => 'Muhammad Sudarmaji'], + ['nip' => '3044771672130253', 'nama' => 'Vishal Rahmat Dharmawan'], + ['nip' => '5641769670230172', 'nama' => 'Ayu Ridhawati'], + ['nip' => '8558757658200002', 'nama' => 'Adi Sumantri'], + ['nip' => '4944762664200052', 'nama' => 'Sutin Rofikah'], + ['nip' => '0043764665130213', 'nama' => 'Suwarno Arieska'], + ['nip' => '8955757659300062', 'nama' => 'Ratnaningtyas Trisnasanti'], + ]; + + foreach ($gurus as $guru) { + Guru::create([ + 'nip' => $guru['nip'], + 'nama' => $guru['nama'], + 'password' => Hash::make('password'), + ]); + } + } +} \ No newline at end of file diff --git a/database/seeders/SiswaSeeder1.php b/database/seeders/SiswaSeeder1.php new file mode 100644 index 0000000..d6f0429 --- /dev/null +++ b/database/seeders/SiswaSeeder1.php @@ -0,0 +1,75 @@ + '0092118525', 'nama' => 'Andika Pranata', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0094253436', 'nama' => 'Andra Sattiawan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0092333773', 'nama' => 'Aryo Mahesa Tegar Hermawan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0071760338', 'nama' => 'Dafid Anjasmara', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0091809817', 'nama' => 'Dirga Duwi Kusuma', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0095281507', 'nama' => 'Elena Risky Ananta', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0107707867', 'nama' => 'Fitriatul Jannah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0104289644', 'nama' => 'Michkel Stevano Afftafian', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0088998591', 'nama' => 'MOCH. Fido Haris Avian Kafabi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0095338610', 'nama' => 'Mohammad Aril Fatoni', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0099906862', 'nama' => 'Mohammad Felan Madali', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0107699211', 'nama' => 'Mohammad Vicky Safarudin Nisar', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0098431453', 'nama' => 'Muhammad Ardiansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0097601928', 'nama' => 'Muhammad Fiki Ardiyanto', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '3107025595', 'nama' => 'Muhammad Haikal', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0089936758', 'nama' => 'Muhammad Kamil', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '3105815399', 'nama' => 'Panji Permana Putra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0091910408', 'nama' => 'Rizki Bachtiar Nadiansyah Ataullah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0099891074', 'nama' => 'Rofal Ardiyansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 1], + ['nisn' => '0095014424', 'nama' => 'Ahmad Yali', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0099370321', 'nama' => 'Ana Septian Nuraini Fajrih', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0103667815', 'nama' => 'Andini Arifatul Hasana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0091939551', 'nama' => 'Arkan Sakti Nusantara', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0117055954', 'nama' => 'Bela Ananda', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0099568560', 'nama' => 'Dewi Nur Arika', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0108033407', 'nama' => 'Dinda Febriani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0083728858', 'nama' => 'Ella Nuryanti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '3095151919', 'nama' => 'Enisa', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0108283354', 'nama' => 'Fina Ferianti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0095581495', 'nama' => 'Gabriel Ubaydillah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '3098215305', 'nama' => 'Halimatul Azkiah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0106567109', 'nama' => 'Innani Fajariyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0092219128', 'nama' => 'Kevin Ananda Pratama', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0098856597', 'nama' => 'Keysha Queen Salsabila', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0101865258', 'nama' => 'Lailatun Nasilah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0094527645', 'nama' => 'Ludfia Ayu Lestari', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0102553007', 'nama' => 'Maulia Feby Fajriyanti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '3090856561', 'nama' => 'Meilandari Putri', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0092397923', 'nama' => 'Muchammad Jaka Firmansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0092084214', 'nama' => 'Nurul Aulia Ul Hasanah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0109110353', 'nama' => 'Qurrotul Aini', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0084601470', 'nama' => 'Saiful Rohman', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0106960081', 'nama' => 'Selawati', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '3092586488', 'nama' => 'Sitti Nasila', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0105183313', 'nama' => 'Valent Editya Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 2], + ['nisn' => '0091977438', 'nama' => 'Abdul Muavi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0082929164', 'nama' => 'Abimanyu Restu Darmawan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ]; + + foreach ($siswas as $siswa) { + Siswa::create([ + 'nisn' => $siswa['nisn'], + 'nama' => $siswa['nama'], + 'tempat_lahir' => $siswa['tempat_lahir'], + 'tanggal_lahir' => $siswa['tanggal_lahir'], + 'id_kelas' => $siswa['id_kelas'], + 'password' => Hash::make('dummy'), + ]); + } + } +} \ No newline at end of file diff --git a/database/seeders/SiswaSeeder2.php b/database/seeders/SiswaSeeder2.php new file mode 100644 index 0000000..9eeaf69 --- /dev/null +++ b/database/seeders/SiswaSeeder2.php @@ -0,0 +1,75 @@ + '0091379890', 'nama' => 'Auli Maulidiya Khair', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0084775056', 'nama' => 'Balqies Nurcahyani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0078290039', 'nama' => 'Bima Ramando Fitriah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0086803610', 'nama' => 'Bramasta Baramuli Manulang', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0099480755', 'nama' => 'Desta Bramasta Finzhando', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0099455196', 'nama' => 'Desty Alika Gusman', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0085225993', 'nama' => 'Diaz Dwi Putra Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0084181311', 'nama' => 'Moh Aldi Yansah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0087864310', 'nama' => 'Mohammad Raihan Alfarezi Ramadan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0096867243', 'nama' => 'Mohammad Rendi Hardiansah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0085980153', 'nama' => 'Muhammad Kevin Ardiansah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0092255148', 'nama' => 'Muhammad Khalid Al Mahbubillah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0081831303', 'nama' => 'Muhammad Naufal Arif Ardiyansah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0096212311', 'nama' => 'Muhammad Noer Alief', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0093508530', 'nama' => 'Muhammad Randy', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0089492385', 'nama' => 'Raihan Azammi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0084546336', 'nama' => 'Reztanza Tunggal Pragasta', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0086792897', 'nama' => 'Sri Quratul Aini', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0089736250', 'nama' => 'Teguh Raka Saputra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 3], + ['nisn' => '0082223414', 'nama' => 'Afrizal Malik Haromain', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0084044726', 'nama' => 'Aisyah Oktafia', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '3091019404', 'nama' => 'Alfa Nuril Hasanah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0095563022', 'nama' => 'Aprilia Suci Andini', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0096938889', 'nama' => 'Aruni Alfa Zulfikar', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0095427183', 'nama' => 'Fheby Isni Wagina', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0088298252', 'nama' => 'Halimatus Sadiya', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0084144682', 'nama' => 'Khofifah Dwi Ningsih', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0096432735', 'nama' => 'Kholifatun Nabilah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0089220766', 'nama' => 'Lina Hikmatul Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0082068509', 'nama' => 'Mochammad Farrel Raihan Firdaus', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0061008438', 'nama' => 'Mohammad Anggi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0098849761', 'nama' => 'Mohammad Arif Saifurrahman', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '3087033551', 'nama' => 'Mohammad Faisal Sefti Andika', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0086748054', 'nama' => 'Muhammad Bayu Purnama Putra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0096262155', 'nama' => 'Muhammad Izil Karim', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0095339031', 'nama' => 'Muhammad Maulana Riskianto', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0085951656', 'nama' => 'Muhammad Novan Abdurahman', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0085808002', 'nama' => 'Muhammad Ridlo Rofius Syan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0098590833', 'nama' => 'Nafiqotul Riskiyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0084753667', 'nama' => 'Nila Septi Ramadani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0087109093', 'nama' => 'Oktafia Fitri Ramadani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0071526400', 'nama' => 'Putri Wulandari', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0086502762', 'nama' => 'Rahmad Dwi Ramadhani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '3072074045', 'nama' => 'Siti Rofik Atul Amalia', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0071345906', 'nama' => 'Suryani', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0095924117', 'nama' => 'Velani Egilika Elsa Putri', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0099036913', 'nama' => 'Vita Febri Virgiyanti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ]; + + foreach ($siswas as $siswa) { + Siswa::create([ + 'nisn' => $siswa['nisn'], + 'nama' => $siswa['nama'], + 'tempat_lahir' => $siswa['tempat_lahir'], + 'tanggal_lahir' => $siswa['tanggal_lahir'], + 'id_kelas' => $siswa['id_kelas'], + 'password' => Hash::make('dummy'), + ]); + } + } +} \ No newline at end of file diff --git a/database/seeders/SiswaSeeder3.php b/database/seeders/SiswaSeeder3.php new file mode 100644 index 0000000..9779d2d --- /dev/null +++ b/database/seeders/SiswaSeeder3.php @@ -0,0 +1,75 @@ + '0087804840', 'nama' => 'Zaky Rahmad Romadhon', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 4], + ['nisn' => '0089349277', 'nama' => 'Abdul Abi Muit', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0085016633', 'nama' => 'Achmad Rifaldyn', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0072334482', 'nama' => 'Alan Okvan Prasetyo', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '3082900200', 'nama' => 'Bagas Dwi Adiansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0067011212', 'nama' => 'Dwi Alfian Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0085892435', 'nama' => 'Maulana Malik Ibrohim', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0072928557', 'nama' => 'Mohammad Ramadhani Nasrollah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0078577984', 'nama' => 'Mohammad Roni Hardiyanto', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0087456391', 'nama' => 'Mohammad Sobri', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0089852561', 'nama' => 'Mohammad Yusuf', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0083326995', 'nama' => 'Muhammad Ainur Rizki', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0088090959', 'nama' => 'Muhammad Erik Saputra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0073155064', 'nama' => 'Muhammad Fikrul Ilmi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0082286825', 'nama' => 'Muhammad Giovanni Irsyad Daffa', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0142201337', 'nama' => 'Muhammad Niki', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0064715822', 'nama' => 'Reno Saputra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0073090750', 'nama' => 'Rici Candra', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0075636251', 'nama' => 'Triyan Saiful Hidayah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0089983035', 'nama' => 'Yusril Andika', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 5], + ['nisn' => '0086002173', 'nama' => 'Aditya Rizky Maulana', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0088596027', 'nama' => 'Ahmad Alfarizi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0072890707', 'nama' => 'Ahmad Farisi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0082953576', 'nama' => 'Alvin Kurniawan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0078134661', 'nama' => 'Atikah Faizah Lailatus Saadah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0075610319', 'nama' => 'Barneth Ajib', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0074835788', 'nama' => 'Candra Ardiansyah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0078383354', 'nama' => 'Citra Megawati', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0079070926', 'nama' => 'Desti Kiki Amelia', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0073492593', 'nama' => 'Desy Arisanty', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0078177505', 'nama' => 'Dwi Rohmatun Naysila', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0086141267', 'nama' => 'Dymas Pratama Soeprapto', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0085573222', 'nama' => 'Herfina', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0067256961', 'nama' => 'Husnan Mutawakkil Ridwan', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0082153364', 'nama' => 'Masturoh', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0077401869', 'nama' => 'Miftahul Arifin', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0081945314', 'nama' => 'Muhammad Adam Septian', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0073353544', 'nama' => 'Muhammad Haris Maksum', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0072499877', 'nama' => 'Muhammad Taufiqur Rohim', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0077906903', 'nama' => 'Naora Octa Cantika', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0072860449', 'nama' => 'Nurul Amera', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0076195649', 'nama' => 'Rasya Luluk Rahmawati', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0072695717', 'nama' => 'Raudatus Sarifah', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0073232486', 'nama' => 'Sefina Dwi Arisandi', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0071174401', 'nama' => 'Siti Fatmiati', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0088603747', 'nama' => 'Siti Maimuna', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ['nisn' => '0082235835', 'nama' => 'Suci Susanti', 'tempat_lahir' => '-', 'tanggal_lahir' => '2000-01-01', 'id_kelas' => 6], + ]; + + foreach ($siswas as $siswa) { + Siswa::create([ + 'nisn' => $siswa['nisn'], + 'nama' => $siswa['nama'], + 'tempat_lahir' => $siswa['tempat_lahir'], + 'tanggal_lahir' => $siswa['tanggal_lahir'], + 'id_kelas' => $siswa['id_kelas'], + 'password' => Hash::make('dummy'), + ]); + } + } +} \ No newline at end of file diff --git a/public/favicon.ico b/favicon.ico similarity index 100% rename from public/favicon.ico rename to favicon.ico diff --git a/public/icons/hide.svg b/icons/hide.svg similarity index 100% rename from public/icons/hide.svg rename to icons/hide.svg diff --git a/public/icons/password.svg b/icons/password.svg similarity index 100% rename from public/icons/password.svg rename to icons/password.svg diff --git a/public/icons/show.svg b/icons/show.svg similarity index 100% rename from public/icons/show.svg rename to icons/show.svg diff --git a/public/icons/username.svg b/icons/username.svg similarity index 100% rename from public/icons/username.svg rename to icons/username.svg diff --git a/public/images/badges/challenge_1.png b/images/badges/challenge_1.png similarity index 100% rename from public/images/badges/challenge_1.png rename to images/badges/challenge_1.png diff --git a/public/images/badges/challenge_3.png b/images/badges/challenge_3.png similarity index 100% rename from public/images/badges/challenge_3.png rename to images/badges/challenge_3.png diff --git a/images/badges/challenge_5.png b/images/badges/challenge_5.png new file mode 100644 index 0000000..168f43a Binary files /dev/null and b/images/badges/challenge_5.png differ diff --git a/public/images/badges/top_1.png b/images/badges/top_1.png similarity index 100% rename from public/images/badges/top_1.png rename to images/badges/top_1.png diff --git a/public/images/badges/top_5.png b/images/badges/top_5.png similarity index 100% rename from public/images/badges/top_5.png rename to images/badges/top_5.png diff --git a/public/images/badges/tugas_1.png b/images/badges/tugas_1.png similarity index 100% rename from public/images/badges/tugas_1.png rename to images/badges/tugas_1.png diff --git a/public/images/badges/tugas_3.png b/images/badges/tugas_3.png similarity index 100% rename from public/images/badges/tugas_3.png rename to images/badges/tugas_3.png diff --git a/images/badges/tugas_5.png b/images/badges/tugas_5.png new file mode 100644 index 0000000..5fb53d8 Binary files /dev/null and b/images/badges/tugas_5.png differ diff --git a/public/images/icon/gurud/1.png b/images/icon/gurud/1.png similarity index 100% rename from public/images/icon/gurud/1.png rename to images/icon/gurud/1.png diff --git a/public/images/icon/gurud/2.png b/images/icon/gurud/2.png similarity index 100% rename from public/images/icon/gurud/2.png rename to images/icon/gurud/2.png diff --git a/public/images/icon/gurud/3.png b/images/icon/gurud/3.png similarity index 100% rename from public/images/icon/gurud/3.png rename to images/icon/gurud/3.png diff --git a/public/images/icon/gurud/add-file.png b/images/icon/gurud/add-file.png similarity index 100% rename from public/images/icon/gurud/add-file.png rename to images/icon/gurud/add-file.png diff --git a/public/images/icon/gurud/alarm.png b/images/icon/gurud/alarm.png similarity index 100% rename from public/images/icon/gurud/alarm.png rename to images/icon/gurud/alarm.png diff --git a/public/images/icon/gurud/alert.png b/images/icon/gurud/alert.png similarity index 100% rename from public/images/icon/gurud/alert.png rename to images/icon/gurud/alert.png diff --git a/public/images/icon/gurud/bell.png b/images/icon/gurud/bell.png similarity index 100% rename from public/images/icon/gurud/bell.png rename to images/icon/gurud/bell.png diff --git a/public/images/icon/gurud/bin.png b/images/icon/gurud/bin.png similarity index 100% rename from public/images/icon/gurud/bin.png rename to images/icon/gurud/bin.png diff --git a/public/images/icon/gurud/buku1.png b/images/icon/gurud/buku1.png similarity index 100% rename from public/images/icon/gurud/buku1.png rename to images/icon/gurud/buku1.png diff --git a/public/images/icon/gurud/buku2.png b/images/icon/gurud/buku2.png similarity index 100% rename from public/images/icon/gurud/buku2.png rename to images/icon/gurud/buku2.png diff --git a/public/images/icon/gurud/cloud.png b/images/icon/gurud/cloud.png similarity index 100% rename from public/images/icon/gurud/cloud.png rename to images/icon/gurud/cloud.png diff --git a/public/images/icon/gurud/confetti.png b/images/icon/gurud/confetti.png similarity index 100% rename from public/images/icon/gurud/confetti.png rename to images/icon/gurud/confetti.png diff --git a/public/images/icon/gurud/crown.png b/images/icon/gurud/crown.png similarity index 100% rename from public/images/icon/gurud/crown.png rename to images/icon/gurud/crown.png diff --git a/public/images/icon/gurud/doc.png b/images/icon/gurud/doc.png similarity index 100% rename from public/images/icon/gurud/doc.png rename to images/icon/gurud/doc.png diff --git a/public/images/icon/gurud/download.png b/images/icon/gurud/download.png similarity index 100% rename from public/images/icon/gurud/download.png rename to images/icon/gurud/download.png diff --git a/public/images/icon/gurud/eye.png b/images/icon/gurud/eye.png similarity index 100% rename from public/images/icon/gurud/eye.png rename to images/icon/gurud/eye.png diff --git a/public/images/icon/gurud/file.png b/images/icon/gurud/file.png similarity index 100% rename from public/images/icon/gurud/file.png rename to images/icon/gurud/file.png diff --git a/public/images/icon/gurud/file2.png b/images/icon/gurud/file2.png similarity index 100% rename from public/images/icon/gurud/file2.png rename to images/icon/gurud/file2.png diff --git a/public/images/icon/gurud/guru.png b/images/icon/gurud/guru.png similarity index 100% rename from public/images/icon/gurud/guru.png rename to images/icon/gurud/guru.png diff --git a/public/images/icon/gurud/history.blade.php b/images/icon/gurud/history.blade.php similarity index 100% rename from public/images/icon/gurud/history.blade.php rename to images/icon/gurud/history.blade.php diff --git a/public/images/icon/gurud/history.png b/images/icon/gurud/history.png similarity index 100% rename from public/images/icon/gurud/history.png rename to images/icon/gurud/history.png diff --git a/public/images/icon/gurud/image.png b/images/icon/gurud/image.png similarity index 100% rename from public/images/icon/gurud/image.png rename to images/icon/gurud/image.png diff --git a/public/images/icon/gurud/jam-pasir.png b/images/icon/gurud/jam-pasir.png similarity index 100% rename from public/images/icon/gurud/jam-pasir.png rename to images/icon/gurud/jam-pasir.png diff --git a/public/images/icon/gurud/jml-m.png b/images/icon/gurud/jml-m.png similarity index 100% rename from public/images/icon/gurud/jml-m.png rename to images/icon/gurud/jml-m.png diff --git a/public/images/icon/gurud/lb.png b/images/icon/gurud/lb.png similarity index 100% rename from public/images/icon/gurud/lb.png rename to images/icon/gurud/lb.png diff --git a/public/images/icon/gurud/link.png b/images/icon/gurud/link.png similarity index 100% rename from public/images/icon/gurud/link.png rename to images/icon/gurud/link.png diff --git a/public/images/icon/gurud/mailbox.png b/images/icon/gurud/mailbox.png similarity index 100% rename from public/images/icon/gurud/mailbox.png rename to images/icon/gurud/mailbox.png diff --git a/public/images/icon/gurud/mapel.png b/images/icon/gurud/mapel.png similarity index 100% rename from public/images/icon/gurud/mapel.png rename to images/icon/gurud/mapel.png diff --git a/public/images/icon/gurud/medal-pita.png b/images/icon/gurud/medal-pita.png similarity index 100% rename from public/images/icon/gurud/medal-pita.png rename to images/icon/gurud/medal-pita.png diff --git a/public/images/icon/gurud/notif.png b/images/icon/gurud/notif.png similarity index 100% rename from public/images/icon/gurud/notif.png rename to images/icon/gurud/notif.png diff --git a/public/images/icon/gurud/pdf.png b/images/icon/gurud/pdf.png similarity index 100% rename from public/images/icon/gurud/pdf.png rename to images/icon/gurud/pdf.png diff --git a/public/images/icon/gurud/pencil.png b/images/icon/gurud/pencil.png similarity index 100% rename from public/images/icon/gurud/pencil.png rename to images/icon/gurud/pencil.png diff --git a/public/images/icon/gurud/pengumpulan.png b/images/icon/gurud/pengumpulan.png similarity index 100% rename from public/images/icon/gurud/pengumpulan.png rename to images/icon/gurud/pengumpulan.png diff --git a/public/images/icon/gurud/piala.png b/images/icon/gurud/piala.png similarity index 100% rename from public/images/icon/gurud/piala.png rename to images/icon/gurud/piala.png diff --git a/public/images/icon/gurud/save.png b/images/icon/gurud/save.png similarity index 100% rename from public/images/icon/gurud/save.png rename to images/icon/gurud/save.png diff --git a/public/images/icon/gurud/school.png b/images/icon/gurud/school.png similarity index 100% rename from public/images/icon/gurud/school.png rename to images/icon/gurud/school.png diff --git a/public/images/icon/gurud/siswa.png b/images/icon/gurud/siswa.png similarity index 100% rename from public/images/icon/gurud/siswa.png rename to images/icon/gurud/siswa.png diff --git a/public/images/icon/gurud/stacked.png b/images/icon/gurud/stacked.png similarity index 100% rename from public/images/icon/gurud/stacked.png rename to images/icon/gurud/stacked.png diff --git a/public/images/icon/gurud/star.png b/images/icon/gurud/star.png similarity index 100% rename from public/images/icon/gurud/star.png rename to images/icon/gurud/star.png diff --git a/public/images/icon/gurud/target.png b/images/icon/gurud/target.png similarity index 100% rename from public/images/icon/gurud/target.png rename to images/icon/gurud/target.png diff --git a/public/images/icon/gurud/upload.png b/images/icon/gurud/upload.png similarity index 100% rename from public/images/icon/gurud/upload.png rename to images/icon/gurud/upload.png diff --git a/public/images/icon/gurud/v.png b/images/icon/gurud/v.png similarity index 100% rename from public/images/icon/gurud/v.png rename to images/icon/gurud/v.png diff --git a/public/images/icon/gurud/wavinghand.png b/images/icon/gurud/wavinghand.png similarity index 100% rename from public/images/icon/gurud/wavinghand.png rename to images/icon/gurud/wavinghand.png diff --git a/public/images/icon/gurud/x.png b/images/icon/gurud/x.png similarity index 100% rename from public/images/icon/gurud/x.png rename to images/icon/gurud/x.png diff --git a/public/images/icon/lp/admin.png b/images/icon/lp/admin.png similarity index 100% rename from public/images/icon/lp/admin.png rename to images/icon/lp/admin.png diff --git a/public/images/icon/lp/buku1.png b/images/icon/lp/buku1.png similarity index 100% rename from public/images/icon/lp/buku1.png rename to images/icon/lp/buku1.png diff --git a/public/images/icon/lp/buku2.png b/images/icon/lp/buku2.png similarity index 100% rename from public/images/icon/lp/buku2.png rename to images/icon/lp/buku2.png diff --git a/public/images/icon/lp/guru.png b/images/icon/lp/guru.png similarity index 100% rename from public/images/icon/lp/guru.png rename to images/icon/lp/guru.png diff --git a/public/images/icon/lp/lb.png b/images/icon/lp/lb.png similarity index 100% rename from public/images/icon/lp/lb.png rename to images/icon/lp/lb.png diff --git a/public/images/icon/lp/medal1.png b/images/icon/lp/medal1.png similarity index 100% rename from public/images/icon/lp/medal1.png rename to images/icon/lp/medal1.png diff --git a/public/images/icon/lp/medal2.png b/images/icon/lp/medal2.png similarity index 100% rename from public/images/icon/lp/medal2.png rename to images/icon/lp/medal2.png diff --git a/public/images/icon/lp/pc.png b/images/icon/lp/pc.png similarity index 100% rename from public/images/icon/lp/pc.png rename to images/icon/lp/pc.png diff --git a/public/images/icon/lp/piala.png b/images/icon/lp/piala.png similarity index 100% rename from public/images/icon/lp/piala.png rename to images/icon/lp/piala.png diff --git a/public/images/icon/lp/rocket.png b/images/icon/lp/rocket.png similarity index 100% rename from public/images/icon/lp/rocket.png rename to images/icon/lp/rocket.png diff --git a/public/images/icon/lp/shield.png b/images/icon/lp/shield.png similarity index 100% rename from public/images/icon/lp/shield.png rename to images/icon/lp/shield.png diff --git a/public/images/icon/lp/siswa.png b/images/icon/lp/siswa.png similarity index 100% rename from public/images/icon/lp/siswa.png rename to images/icon/lp/siswa.png diff --git a/public/images/icon/lp/star.png b/images/icon/lp/star.png similarity index 100% rename from public/images/icon/lp/star.png rename to images/icon/lp/star.png diff --git a/public/images/icon/lp/wavinghand.png b/images/icon/lp/wavinghand.png similarity index 100% rename from public/images/icon/lp/wavinghand.png rename to images/icon/lp/wavinghand.png diff --git a/public/images/icon/main/add.png b/images/icon/main/add.png similarity index 100% rename from public/images/icon/main/add.png rename to images/icon/main/add.png diff --git a/public/images/icon/main/del.png b/images/icon/main/del.png similarity index 100% rename from public/images/icon/main/del.png rename to images/icon/main/del.png diff --git a/public/images/icon/main/delete.png b/images/icon/main/delete.png similarity index 100% rename from public/images/icon/main/delete.png rename to images/icon/main/delete.png diff --git a/public/images/icon/main/download.png b/images/icon/main/download.png similarity index 100% rename from public/images/icon/main/download.png rename to images/icon/main/download.png diff --git a/public/images/icon/main/edit.png b/images/icon/main/edit.png similarity index 100% rename from public/images/icon/main/edit.png rename to images/icon/main/edit.png diff --git a/public/images/icon/main/hi.png b/images/icon/main/hi.png similarity index 100% rename from public/images/icon/main/hi.png rename to images/icon/main/hi.png diff --git a/public/images/icon/main/search.png b/images/icon/main/search.png similarity index 100% rename from public/images/icon/main/search.png rename to images/icon/main/search.png diff --git a/public/images/icon/mascot/main_mascot.png b/images/icon/mascot/main_mascot.png similarity index 100% rename from public/images/icon/mascot/main_mascot.png rename to images/icon/mascot/main_mascot.png diff --git a/public/images/icon/mascot/main_mascott.png b/images/icon/mascot/main_mascott.png similarity index 100% rename from public/images/icon/mascot/main_mascott.png rename to images/icon/mascot/main_mascott.png diff --git a/public/images/icon/mascot/mascot-hi.png b/images/icon/mascot/mascot-hi.png similarity index 100% rename from public/images/icon/mascot/mascot-hi.png rename to images/icon/mascot/mascot-hi.png diff --git a/public/images/icon/sidebar/challenge.png b/images/icon/sidebar/challenge.png similarity index 100% rename from public/images/icon/sidebar/challenge.png rename to images/icon/sidebar/challenge.png diff --git a/public/images/icon/sidebar/guru.png b/images/icon/sidebar/guru.png similarity index 100% rename from public/images/icon/sidebar/guru.png rename to images/icon/sidebar/guru.png diff --git a/public/images/icon/sidebar/home.png b/images/icon/sidebar/home.png similarity index 100% rename from public/images/icon/sidebar/home.png rename to images/icon/sidebar/home.png diff --git a/public/images/icon/sidebar/house.png b/images/icon/sidebar/house.png similarity index 100% rename from public/images/icon/sidebar/house.png rename to images/icon/sidebar/house.png diff --git a/public/images/icon/sidebar/kelas.png b/images/icon/sidebar/kelas.png similarity index 100% rename from public/images/icon/sidebar/kelas.png rename to images/icon/sidebar/kelas.png diff --git a/public/images/icon/sidebar/lb.png b/images/icon/sidebar/lb.png similarity index 100% rename from public/images/icon/sidebar/lb.png rename to images/icon/sidebar/lb.png diff --git a/public/images/icon/sidebar/mapel.png b/images/icon/sidebar/mapel.png similarity index 100% rename from public/images/icon/sidebar/mapel.png rename to images/icon/sidebar/mapel.png diff --git a/public/images/icon/sidebar/notif.png b/images/icon/sidebar/notif.png similarity index 100% rename from public/images/icon/sidebar/notif.png rename to images/icon/sidebar/notif.png diff --git a/public/images/icon/sidebar/profil.png b/images/icon/sidebar/profil.png similarity index 100% rename from public/images/icon/sidebar/profil.png rename to images/icon/sidebar/profil.png diff --git a/public/images/icon/sidebar/siswa.png b/images/icon/sidebar/siswa.png similarity index 100% rename from public/images/icon/sidebar/siswa.png rename to images/icon/sidebar/siswa.png diff --git a/public/images/icon/siswac/alarm.png b/images/icon/siswac/alarm.png similarity index 100% rename from public/images/icon/siswac/alarm.png rename to images/icon/siswac/alarm.png diff --git a/public/images/icon/siswac/alert.png b/images/icon/siswac/alert.png similarity index 100% rename from public/images/icon/siswac/alert.png rename to images/icon/siswac/alert.png diff --git a/public/images/icon/siswac/api.png b/images/icon/siswac/api.png similarity index 100% rename from public/images/icon/siswac/api.png rename to images/icon/siswac/api.png diff --git a/public/images/icon/siswac/buku1.png b/images/icon/siswac/buku1.png similarity index 100% rename from public/images/icon/siswac/buku1.png rename to images/icon/siswac/buku1.png diff --git a/public/images/icon/siswac/confetti.png b/images/icon/siswac/confetti.png similarity index 100% rename from public/images/icon/siswac/confetti.png rename to images/icon/siswac/confetti.png diff --git a/public/images/icon/siswac/index.blade.php b/images/icon/siswac/index.blade.php similarity index 100% rename from public/images/icon/siswac/index.blade.php rename to images/icon/siswac/index.blade.php diff --git a/public/images/icon/siswac/lb.png b/images/icon/siswac/lb.png similarity index 100% rename from public/images/icon/siswac/lb.png rename to images/icon/siswac/lb.png diff --git a/public/images/icon/siswac/muscle.png b/images/icon/siswac/muscle.png similarity index 100% rename from public/images/icon/siswac/muscle.png rename to images/icon/siswac/muscle.png diff --git a/public/images/icon/siswac/piala.png b/images/icon/siswac/piala.png similarity index 100% rename from public/images/icon/siswac/piala.png rename to images/icon/siswac/piala.png diff --git a/public/images/icon/siswac/rocket.png b/images/icon/siswac/rocket.png similarity index 100% rename from public/images/icon/siswac/rocket.png rename to images/icon/siswac/rocket.png diff --git a/public/images/icon/siswac/sip.png b/images/icon/siswac/sip.png similarity index 100% rename from public/images/icon/siswac/sip.png rename to images/icon/siswac/sip.png diff --git a/public/images/icon/siswac/star.png b/images/icon/siswac/star.png similarity index 100% rename from public/images/icon/siswac/star.png rename to images/icon/siswac/star.png diff --git a/public/images/icon/siswac/target.png b/images/icon/siswac/target.png similarity index 100% rename from public/images/icon/siswac/target.png rename to images/icon/siswac/target.png diff --git a/public/images/icon/siswac/v.png b/images/icon/siswac/v.png similarity index 100% rename from public/images/icon/siswac/v.png rename to images/icon/siswac/v.png diff --git a/public/images/icon/siswac/x.png b/images/icon/siswac/x.png similarity index 100% rename from public/images/icon/siswac/x.png rename to images/icon/siswac/x.png diff --git a/public/images/icon/siswadb/1.png b/images/icon/siswadb/1.png similarity index 100% rename from public/images/icon/siswadb/1.png rename to images/icon/siswadb/1.png diff --git a/public/images/icon/siswadb/2.png b/images/icon/siswadb/2.png similarity index 100% rename from public/images/icon/siswadb/2.png rename to images/icon/siswadb/2.png diff --git a/public/images/icon/siswadb/3.png b/images/icon/siswadb/3.png similarity index 100% rename from public/images/icon/siswadb/3.png rename to images/icon/siswadb/3.png diff --git a/public/images/icon/siswadb/buku.png b/images/icon/siswadb/buku.png similarity index 100% rename from public/images/icon/siswadb/buku.png rename to images/icon/siswadb/buku.png diff --git a/public/images/icon/siswadb/confetti.png b/images/icon/siswadb/confetti.png similarity index 100% rename from public/images/icon/siswadb/confetti.png rename to images/icon/siswadb/confetti.png diff --git a/public/images/icon/siswadb/muscle.png b/images/icon/siswadb/muscle.png similarity index 100% rename from public/images/icon/siswadb/muscle.png rename to images/icon/siswadb/muscle.png diff --git a/public/images/icon/siswadb/notif.png b/images/icon/siswadb/notif.png similarity index 100% rename from public/images/icon/siswadb/notif.png rename to images/icon/siswadb/notif.png diff --git a/public/images/icon/siswadb/starw.png b/images/icon/siswadb/starw.png similarity index 100% rename from public/images/icon/siswadb/starw.png rename to images/icon/siswadb/starw.png diff --git a/public/images/icon/siswadb/wavinghand.png b/images/icon/siswadb/wavinghand.png similarity index 100% rename from public/images/icon/siswadb/wavinghand.png rename to images/icon/siswadb/wavinghand.png diff --git a/public/images/icon/siswal/1.png b/images/icon/siswal/1.png similarity index 100% rename from public/images/icon/siswal/1.png rename to images/icon/siswal/1.png diff --git a/public/images/icon/siswal/2.png b/images/icon/siswal/2.png similarity index 100% rename from public/images/icon/siswal/2.png rename to images/icon/siswal/2.png diff --git a/public/images/icon/siswal/3.png b/images/icon/siswal/3.png similarity index 100% rename from public/images/icon/siswal/3.png rename to images/icon/siswal/3.png diff --git a/public/images/icon/siswal/buku1.png b/images/icon/siswal/buku1.png similarity index 100% rename from public/images/icon/siswal/buku1.png rename to images/icon/siswal/buku1.png diff --git a/public/images/icon/siswal/crown.png b/images/icon/siswal/crown.png similarity index 100% rename from public/images/icon/siswal/crown.png rename to images/icon/siswal/crown.png diff --git a/public/images/icon/siswal/jam-pasir.png b/images/icon/siswal/jam-pasir.png similarity index 100% rename from public/images/icon/siswal/jam-pasir.png rename to images/icon/siswal/jam-pasir.png diff --git a/public/images/icon/siswal/lb.png b/images/icon/siswal/lb.png similarity index 100% rename from public/images/icon/siswal/lb.png rename to images/icon/siswal/lb.png diff --git a/public/images/icon/siswal/medal-pita.png b/images/icon/siswal/medal-pita.png similarity index 100% rename from public/images/icon/siswal/medal-pita.png rename to images/icon/siswal/medal-pita.png diff --git a/public/images/icon/siswal/star.png b/images/icon/siswal/star.png similarity index 100% rename from public/images/icon/siswal/star.png rename to images/icon/siswal/star.png diff --git a/public/images/icon/siswal/target.png b/images/icon/siswal/target.png similarity index 100% rename from public/images/icon/siswal/target.png rename to images/icon/siswal/target.png diff --git a/public/images/icon/siswam/doc.png b/images/icon/siswam/doc.png similarity index 100% rename from public/images/icon/siswam/doc.png rename to images/icon/siswam/doc.png diff --git a/public/images/icon/siswam/download.png b/images/icon/siswam/download.png similarity index 100% rename from public/images/icon/siswam/download.png rename to images/icon/siswam/download.png diff --git a/public/images/icon/siswam/image.png b/images/icon/siswam/image.png similarity index 100% rename from public/images/icon/siswam/image.png rename to images/icon/siswam/image.png diff --git a/public/images/icon/siswam/jml-m.png b/images/icon/siswam/jml-m.png similarity index 100% rename from public/images/icon/siswam/jml-m.png rename to images/icon/siswam/jml-m.png diff --git a/public/images/icon/siswam/link.png b/images/icon/siswam/link.png similarity index 100% rename from public/images/icon/siswam/link.png rename to images/icon/siswam/link.png diff --git a/public/images/icon/siswam/mailbox.png b/images/icon/siswam/mailbox.png similarity index 100% rename from public/images/icon/siswam/mailbox.png rename to images/icon/siswam/mailbox.png diff --git a/public/images/icon/siswam/mapel.png b/images/icon/siswam/mapel.png similarity index 100% rename from public/images/icon/siswam/mapel.png rename to images/icon/siswam/mapel.png diff --git a/public/images/icon/siswam/pdf.png b/images/icon/siswam/pdf.png similarity index 100% rename from public/images/icon/siswam/pdf.png rename to images/icon/siswam/pdf.png diff --git a/public/images/icon/siswam/stacked.png b/images/icon/siswam/stacked.png similarity index 100% rename from public/images/icon/siswam/stacked.png rename to images/icon/siswam/stacked.png diff --git a/public/images/icon/siswat/alarm.png b/images/icon/siswat/alarm.png similarity index 100% rename from public/images/icon/siswat/alarm.png rename to images/icon/siswat/alarm.png diff --git a/public/images/icon/siswat/alert.png b/images/icon/siswat/alert.png similarity index 100% rename from public/images/icon/siswat/alert.png rename to images/icon/siswat/alert.png diff --git a/public/images/icon/siswat/buku1.png b/images/icon/siswat/buku1.png similarity index 100% rename from public/images/icon/siswat/buku1.png rename to images/icon/siswat/buku1.png diff --git a/public/images/icon/siswat/cloud.png b/images/icon/siswat/cloud.png similarity index 100% rename from public/images/icon/siswat/cloud.png rename to images/icon/siswat/cloud.png diff --git a/public/images/icon/siswat/confetti.png b/images/icon/siswat/confetti.png similarity index 100% rename from public/images/icon/siswat/confetti.png rename to images/icon/siswat/confetti.png diff --git a/public/images/icon/siswat/guru.png b/images/icon/siswat/guru.png similarity index 100% rename from public/images/icon/siswat/guru.png rename to images/icon/siswat/guru.png diff --git a/public/images/icon/siswat/mailbox.png b/images/icon/siswat/mailbox.png similarity index 100% rename from public/images/icon/siswat/mailbox.png rename to images/icon/siswat/mailbox.png diff --git a/public/images/icon/siswat/stacked.png b/images/icon/siswat/stacked.png similarity index 100% rename from public/images/icon/siswat/stacked.png rename to images/icon/siswat/stacked.png diff --git a/public/images/icon/siswat/star.png b/images/icon/siswat/star.png similarity index 100% rename from public/images/icon/siswat/star.png rename to images/icon/siswat/star.png diff --git a/public/images/icon/siswat/upload.png b/images/icon/siswat/upload.png similarity index 100% rename from public/images/icon/siswat/upload.png rename to images/icon/siswat/upload.png diff --git a/public/images/icon/siswat/v.png b/images/icon/siswat/v.png similarity index 100% rename from public/images/icon/siswat/v.png rename to images/icon/siswat/v.png diff --git a/public/images/icon/siswat/x.png b/images/icon/siswat/x.png similarity index 100% rename from public/images/icon/siswat/x.png rename to images/icon/siswat/x.png diff --git a/public/images/logo/logosmk.png b/images/logo/logosmk.png similarity index 100% rename from public/images/logo/logosmk.png rename to images/logo/logosmk.png diff --git a/resources/views/siswa/siswa/index.blade.php b/index.blade.php similarity index 100% rename from resources/views/siswa/siswa/index.blade.php rename to index.blade.php diff --git a/public/index.php b/index.php similarity index 91% rename from public/index.php rename to index.php index 2c90301..2504b66 100644 --- a/public/index.php +++ b/index.php @@ -1,6 +1,8 @@ make('Illuminate\Contracts\Console\Kernel')->bootstrap(); + +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\DB; + +// Reset semua guru sekaligus berdasarkan NIP — lebih aman dari ID +$nipBermasalah = [ + '6535747649200022', // Fibrian Kartika Nuswanto + '4447762663130173', // Edi Hariyanto + '3739766667130182', // Fitriyah Arizkiyanti + '4743763664300082', // Uswatun Hasanah + '9651770671130122', // Haris Nurtanio + '8152760661300073', // Firlya Citra Kurniawati + '1957769670130032', // Ahmad Timbul Sholeh + '1949759659200002', // Muhammad Sudarmaji + '5641769670230172', // Ayu Ridhawati + '8558757658200002', // Adi Sumantri + '0043764665130213', // Suwarno Arieska + '8955757659300062', // Ratnaningtyas Trisnasanti +]; + +$newPassword = Hash::make('password'); +$berhasil = 0; + +foreach ($nipBermasalah as $nip) { + $affected = DB::table('gurus') + ->where('nip', $nip) + ->update(['password' => $newPassword]); + + if ($affected) { + $nama = DB::table('gurus')->where('nip', $nip)->value('nama'); + echo "✅ Reset: {$nama} ({$nip})
"; + $berhasil++; + } else { + echo "❌ NIP tidak ditemukan: {$nip}
"; + } +} + +echo "
Selesai! {$berhasil} password berhasil direset."; +echo "
Login dengan password: password"; +?> \ No newline at end of file diff --git a/public/publish_dompdf.php b/public/publish_dompdf.php new file mode 100644 index 0000000..be8da79 --- /dev/null +++ b/public/publish_dompdf.php @@ -0,0 +1,9 @@ +make('Illuminate\Contracts\Console\Kernel')->bootstrap(); +\Illuminate\Support\Facades\Artisan::call('vendor:publish', [ + '--provider' => 'Barryvdh\DomPDF\ServiceProvider' +]); +echo 'DomPDF config published!'; +?> \ No newline at end of file diff --git a/public/run_seeder.php b/public/run_seeder.php new file mode 100644 index 0000000..7105037 --- /dev/null +++ b/public/run_seeder.php @@ -0,0 +1,7 @@ +make('Illuminate\Contracts\Console\Kernel')->bootstrap(); +\Illuminate\Support\Facades\Artisan::call('db:seed', ['--class' => 'GuruSeeder']); +echo 'GuruSeeder berhasil dijalankan!'; +?> \ No newline at end of file diff --git a/public/run_siswa_seeder1.php b/public/run_siswa_seeder1.php new file mode 100644 index 0000000..10afe76 --- /dev/null +++ b/public/run_siswa_seeder1.php @@ -0,0 +1,7 @@ +make('Illuminate\Contracts\Console\Kernel')->bootstrap(); +\Illuminate\Support\Facades\Artisan::call('db:seed', ['--class' => 'SiswaSeeder1']); +echo 'SiswaSeeder1 berhasil dijalankan!'; +?> \ No newline at end of file diff --git a/public/run_siswa_seeder2.php b/public/run_siswa_seeder2.php new file mode 100644 index 0000000..000fd20 --- /dev/null +++ b/public/run_siswa_seeder2.php @@ -0,0 +1,7 @@ +make('Illuminate\Contracts\Console\Kernel')->bootstrap(); +\Illuminate\Support\Facades\Artisan::call('db:seed', ['--class' => 'SiswaSeeder2']); +echo 'SiswaSeeder2 berhasil dijalankan!'; +?> \ No newline at end of file diff --git a/public/run_siswa_seeder3.php b/public/run_siswa_seeder3.php new file mode 100644 index 0000000..a150479 --- /dev/null +++ b/public/run_siswa_seeder3.php @@ -0,0 +1,7 @@ +make('Illuminate\Contracts\Console\Kernel')->bootstrap(); +\Illuminate\Support\Facades\Artisan::call('db:seed', ['--class' => 'SiswaSeeder3']); +echo 'SiswaSeeder3 berhasil dijalankan!'; +?> \ No newline at end of file diff --git a/public/storage_link.php b/public/storage_link.php new file mode 100644 index 0000000..88a7a72 --- /dev/null +++ b/public/storage_link.php @@ -0,0 +1,7 @@ +make('Illuminate\Contracts\Console\Kernel')->bootstrap(); +\Illuminate\Support\Facades\Artisan::call('storage:link'); +echo 'Storage link berhasil dibuat!'; +?> \ No newline at end of file diff --git a/resources/views/admin/challenge/index.blade.php b/resources/views/admin/challenge/index.blade.php index 69b0c5b..6861f56 100644 --- a/resources/views/admin/challenge/index.blade.php +++ b/resources/views/admin/challenge/index.blade.php @@ -1,339 +1,134 @@ -{{-- - FILE: resources/views/admin/challenge/index.blade.php - PERUBAHAN: - - Tambah field "Durasi Pengerjaan (menit)" di form Tambah & Edit - - Kolom durasi di tabel - - Logika timer siswa berubah: pakai durasi, bukan tenggat ---}} - @extends('admin.layouts.app') -@section('title', 'Daftar Challenge') +@section('title', 'History Challenge') @section('content') -

DAFTAR CHALLENGE

+

HISTORY CHALLENGE

@if(session('success'))
@@ -343,636 +138,121 @@ @endif
-
- -
+ + {{-- TOP BAR: dropdown | search | total --}} + +
+ + {{-- Dropdown Filter Guru --}} + + + {{-- Search Judul Challenge --}} - -
- - - - - - - - - - - - - - - @forelse($challenges as $i => $ch) - @php $isLewat = \Carbon\Carbon::parse($ch->tenggat_waktu)->isPast(); @endphp - - - - - - - {{-- Kolom durasi baru --}} - + + + @empty + + + + @endforelse + +
NoJudul ChallengeKelasSoalEXPDurasiTenggatAksi
{{ $challenges->firstItem() + $i }} -
{{ $ch->judul_challenge }}
- @if($ch->deskripsi) -
{{ Str::limit($ch->deskripsi, 50) }}
- @endif -
- @foreach($ch->kelas as $k) - {{ $k->tingkat }} {{ $k->nama_kelas }} - @endforeach - {{ $ch->soal_count }} soal{{ $ch->exp }} EXP - @if($ch->durasi_pengerjaan) - - ⏱ {{ $ch->durasi_pengerjaan }} menit + {{-- Reset (muncul jika ada filter aktif) --}} + @if(request('search') || request('guru_id')) + ✕ Reset + @endif + + {{-- Total di kanan --}} + Total: {{ $challenges->total() }} challenge + + + + + {{-- TABEL --}} +
+ + + + + + + + + + + + + + + @forelse($challenges as $i => $ch) + @php $isLewat = \Carbon\Carbon::parse($ch->tenggat_waktu)->isPast(); @endphp + + + + + + + + - - - - @empty - - - - @endforelse - -
NoPembuatJudul ChallengeKelasSoalDurasiTenggatAksi
{{ $challenges->firstItem() + $i }} + {{ optional($ch->guru)->nama ?? 'Admin' }} + +
{{ $ch->judul_challenge }}
+ @if($ch->deskripsi) +
{{ Str::limit($ch->deskripsi, 40) }}
+ @endif +
+ @foreach($ch->kelas as $k) + {{ $k->tingkat }} {{ $k->nama_kelas }} + @endforeach + {{ $ch->soal_count }} soal + @if($ch->durasi_pengerjaan) + ⏱ {{ $ch->durasi_pengerjaan }} mnt + @else + + @endif + + + {{ $isLewat ? 'Lewat' : 'Aktif' }} - @else - - @endif - - - @if($isLewat) Lewat @else Aktif @endif - -
- {{ \Carbon\Carbon::parse($ch->tenggat_waktu)->format('d M Y, H:i') }} -
-
- - - Lihat - -
- @csrf @method('DELETE') - -
-
Belum ada challenge.
-
{{ $challenges->links() }}
-
- - -{{-- ============================================================ - MODAL TAMBAH -============================================================ --}} -
+ + Lihat + +
+ @csrf @method('DELETE') + +
+
Belum ada challenge.
-
-
- -{{-- ============================================================ - MODAL EDIT -============================================================ --}} -