diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index b6a3016..fdbf565 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -64,7 +64,8 @@ public function sendOtp(Request $request) 'email' => 'required|email|exists:users,email', ]); - $user = User::where('email', $request->email)->first(); + // ✅ FIX: tambah with('guru') agar relasi ter-load + $user = User::with('guru')->where('email', $request->email)->first(); } else { $request->validate([ @@ -76,7 +77,8 @@ public function sendOtp(Request $request) $no = '62' . substr($no, 1); } - $user = User::whereHas('guru', function ($q) use ($no) { + // ✅ FIX: tambah with('guru') agar relasi ter-load + $user = User::with('guru')->whereHas('guru', function ($q) use ($no) { $q->whereRaw("REGEXP_REPLACE(telepon, '[^0-9]', '') LIKE ?", ["%{$no}%"]); })->orWhereHas('siswa', function ($q) use ($no) { $q->whereRaw("REGEXP_REPLACE(telepon, '[^0-9]', '') LIKE ?", ["%{$no}%"]); @@ -87,6 +89,11 @@ public function sendOtp(Request $request) } } + // ✅ FIX: blokir pengiriman OTP untuk guru nonaktif + if ($user->role === 'guru' && $user->guru && $user->guru->status === 'nonaktif') { + return back()->with('error', 'Akun Anda telah dinonaktifkan. Hubungi administrator.'); + } + // Kirim OTP sesuai method — email SAJA atau WA SAJA app(\App\Services\OtpService::class)->send($user, $method); @@ -107,7 +114,8 @@ public function loginOtp(Request $request) 'otp' => 'nullable|digits:6', ]); - $user = User::where('email', $request->email)->first(); + // ✅ FIX: tambah with('guru') agar relasi ter-load + $user = User::with('guru')->where('email', $request->email)->first(); if (!$user) { return back()->with('error', 'Email tidak ditemukan.'); @@ -134,6 +142,14 @@ public function loginOtp(Request $request) // ================================================= if (in_array($user->role, ['guru', 'siswa'])) { + // ✅ FIX: cek status nonaktif sebelum proses OTP + if ($user->role === 'guru') { + $user->load('guru'); // reload fresh dari DB + if (!$user->guru || $user->guru->status === 'nonaktif') { + return back()->with('error', 'Akun Anda telah dinonaktifkan. Hubungi administrator.'); + } + } + if (empty($request->otp)) { return back()->with('error', 'OTP wajib diisi.'); } @@ -227,34 +243,35 @@ public function logout(Request $request) } // ================= VERIFY OTP (halaman terpisah jika ada) ================= - // ================= VERIFY OTP (lupa password) ================= -public function showVerifyOtp() -{ - return view('auth.verify-otp'); -} + public function showVerifyOtp() + { + return view('auth.verify-otp'); + } -public function verifyOtp(Request $request) + public function verifyOtp(Request $request) { - $user = User::find(session('otp_user')); + $request->validate([ + 'otp' => 'required|digits:6' + ]); + + $user = User::where('email', session('email'))->first(); if (!$user) { - return back()->with('error', 'Sesi tidak valid, ulangi dari awal.'); + return back()->with('error', 'User tidak ditemukan'); } - // Cek OTP dari kolom database, bukan session + // cek OTP if ((string)$request->otp !== (string)$user->otp) { - return back()->with('error', 'Kode OTP salah.'); + return back()->with('error', 'OTP salah'); } + // cek expired if (!$user->otp_expired_at || now()->gt($user->otp_expired_at)) { - return back()->with('error', 'OTP sudah kadaluarsa, kirim ulang.'); + return back()->with('error', 'OTP sudah kadaluarsa'); } - // OTP valid — hapus OTP, redirect ke reset password - $user->update([ - 'otp' => null, - 'otp_expired_at' => null, - ]); + // simpan user reset password + session(['otp_user' => $user->id]); return redirect()->route('reset.password.form'); } @@ -314,29 +331,36 @@ private function redirectByRole($user) }; } - // ================= KIRIM OTP LUPA PASSWORD ================= -// ================= KIRIM OTP LUPA PASSWORD ================= -public function sendForgotPasswordOtp(Request $request) + // ================= FORGOT PASSWORD OTP ================= + public function sendForgotPasswordOtp(Request $request) { $request->validate([ - 'email' => 'required|email|exists:users,email', - ], [ - 'email.exists' => 'Email tidak terdaftar dalam sistem.', + 'email' => 'required|email' ]); $user = User::where('email', $request->email)->first(); - // Kirim OTP via email menggunakan OtpService yang sudah ada - app(\App\Services\OtpService::class)->send($user, 'email'); + if (!$user) { + return back()->with('error', 'Email tidak ditemukan'); + } - // Simpan ke session untuk dipakai saat verifikasi & reset - session([ - 'otp_user' => $user->id, - 'email' => $user->email, + $otp = rand(100000, 999999); + + $user->update([ + 'otp' => $otp, + 'otp_expired_at' => now()->addMinutes(5), ]); - // Redirect ke halaman verifikasi OTP - return redirect()->route('verify.otp') - ->with('success', 'OTP telah dikirim ke ' . $user->email); + Mail::raw("Kode OTP reset password Anda adalah: $otp", function ($message) use ($user) { + $message->to($user->email) + ->subject('OTP Reset Password'); + }); + + session([ + 'email' => $user->email + ]); + + return redirect()->route('verify.otp.form') + ->with('success', 'OTP reset password berhasil dikirim'); } } \ No newline at end of file diff --git a/app/Http/Controllers/auth/OtpController.php b/app/Http/Controllers/auth/OtpController.php index b453b7a..7560611 100644 --- a/app/Http/Controllers/auth/OtpController.php +++ b/app/Http/Controllers/auth/OtpController.php @@ -14,15 +14,13 @@ public function sendOtp(Request $request) $method = $request->input('method', 'email'); if ($method === 'email') { - // ── EMAIL ── $request->validate([ 'email' => 'required|email|exists:users,email', ]); - $user = User::where('email', $request->email)->first(); + $user = User::with(['guru', 'siswa'])->where('email', $request->email)->first(); } else { - // ── WHATSAPP ── $request->validate([ 'telepon' => 'required|string', ]); @@ -33,7 +31,7 @@ public function sendOtp(Request $request) } $no08 = '0' . substr($no, 2); - $user = User::whereHas('guru', function ($q) use ($no, $no08) { + $user = User::with(['guru', 'siswa'])->whereHas('guru', function ($q) use ($no, $no08) { $q->where('telepon', $no) ->orWhere('telepon', $no08) ->orWhere('telepon', 'like', '%' . substr($no, -9) . '%'); @@ -48,13 +46,21 @@ public function sendOtp(Request $request) } } - // Kirim OTP sesuai method (email SAJA atau WA SAJA) + // ✅ FIX: blokir guru nonaktif + if ($user->role === 'guru' && $user->guru && $user->guru->status === 'nonaktif') { + return back()->with('error', 'Akun Anda telah dinonaktifkan. Hubungi administrator.'); + } + + // ✅ FIX BARU: blokir siswa nonaktif + if ($user->role === 'siswa' && $user->siswa && $user->siswa->status === 'nonaktif') { + return back()->with('error', 'Akun Anda telah dinonaktifkan. Hubungi administrator.'); + } + app(\App\Services\OtpService::class)->send($user, $method); - // Simpan email ke session untuk prefill form login session(['email' => $user->email]); - return back()->with('otp_sent', 'OTP berhasil dikirim!'); + return back()->with('otp_sent', 'OTP berhasil dikirim!'); } // ================= LOGIN DENGAN OTP ================= @@ -66,7 +72,7 @@ public function loginOtp(Request $request) 'otp' => 'nullable|digits:6', ]); - $user = User::where('email', $request->email)->first(); + $user = User::with(['guru', 'siswa'])->where('email', $request->email)->first(); if (!$user) { return back()->with('error', 'User tidak ditemukan'); @@ -76,6 +82,22 @@ public function loginOtp(Request $request) return back()->with('error', 'Password salah'); } + // ✅ FIX: cek status nonaktif untuk guru + if ($user->role === 'guru') { + $user->load('guru'); + if (!$user->guru || $user->guru->status === 'nonaktif') { + return back()->with('error', 'Akun Anda telah dinonaktifkan. Hubungi administrator.'); + } + } + + // ✅ FIX BARU: cek status nonaktif untuk siswa + if ($user->role === 'siswa') { + $user->load('siswa'); + if (!$user->siswa || $user->siswa->status === 'nonaktif') { + return back()->with('error', 'Akun Anda telah dinonaktifkan. Hubungi administrator.'); + } + } + // Guru & Siswa wajib OTP if (in_array($user->role, ['guru', 'siswa'])) { @@ -99,7 +121,6 @@ public function loginOtp(Request $request) \Auth::login($user); $request->session()->regenerate(); - // Hapus OTP setelah dipakai if (in_array($user->role, ['guru', 'siswa'])) { $user->update(['otp' => null, 'otp_expired_at' => null]); } diff --git a/app/Http/Controllers/guru/TugasController.php b/app/Http/Controllers/guru/TugasController.php index 1f0ada4..cd5a0c8 100644 --- a/app/Http/Controllers/guru/TugasController.php +++ b/app/Http/Controllers/guru/TugasController.php @@ -88,7 +88,7 @@ public function store(Request $request) 'kelas_id' => 'required', 'judul' => 'required', 'deadline' => 'required', - 'file' => 'nullable|mimes:pdf,doc,docx,ppt,pptx|max:2048', + 'file' => 'nullable|max:10240', ]); $filePath = null; @@ -139,27 +139,42 @@ public function edit($id) return view('guru.tugas.edit', compact('tugas', 'kelas')); } - public function update(Request $request, $id) - { - $request->validate([ - 'judul' => 'required', - 'kelas_id' => 'required', - 'deadline' => 'required', - ]); + // SESUDAH +public function update(Request $request, $id) +{ + $request->validate([ + 'judul' => 'required', + 'kelas_id' => 'required', + 'deadline' => 'required', + 'file' => 'nullable|max:10240', // ✅ validasi file + ], [ + 'file.max' => 'Ukuran file maksimal 10MB.', + ]); - $tugas = Tugas::findOrFail($id); + $tugas = Tugas::findOrFail($id); - $tugas->update([ - 'judul' => $request->judul, - 'kelas_id' => $request->kelas_id, - 'deskripsi' => $request->deskripsi, - 'deadline' => $request->deadline, - ]); + $updateData = [ + 'judul' => $request->judul, + 'kelas_id' => $request->kelas_id, + 'deskripsi' => $request->deskripsi, + 'deadline' => $request->deadline, + ]; - return redirect()->route('guru.tugas.index') - ->with('success', 'Tugas berhasil diupdate'); + // ✅ Jika ada file baru diupload + if ($request->hasFile('file')) { + // Hapus file lama jika ada + if ($tugas->file && Storage::disk('public')->exists($tugas->file)) { + Storage::disk('public')->delete($tugas->file); + } + // Simpan file baru + $updateData['file'] = $request->file('file')->store('tugas_file', 'public'); } + $tugas->update($updateData); + + return redirect()->route('guru.tugas.index') + ->with('success', 'Tugas berhasil diupdate'); +} public function download($id) { $tugas = Tugas::findOrFail($id); diff --git a/app/Http/Controllers/siswa/UjianController.php b/app/Http/Controllers/siswa/UjianController.php index 45adbfe..ebb8522 100644 --- a/app/Http/Controllers/siswa/UjianController.php +++ b/app/Http/Controllers/siswa/UjianController.php @@ -12,9 +12,8 @@ class UjianController extends Controller { public function index() { - $siswa = auth()->user()->siswa; + $siswa = auth()->user()->siswa; - // Hanya tampilkan ujian yang dikirim ke kelas siswa ini & status terkirim $ujians = Ujian::whereHas('kelas', fn($q) => $q->where('kelas.id', $siswa->kelas_id)) ->where('status_kirim', 'terkirim') ->latest() @@ -26,64 +25,89 @@ public function index() $hasilSiswa = Hasil::where('siswa_id', $siswa->id) ->get()->keyBy('ujian_id'); - return view('siswa.ujian.index', compact('ujians', 'sudahDikerjakan', 'hasilSiswa')); + // ✅ FIX BARU: tandai ujian yang sudah lewat waktu + $sudahBerakhir = $ujians + ->filter(fn($u) => $u->selesai && \Carbon\Carbon::parse($u->selesai)->lt(now())) + ->pluck('id') + ->toArray(); + + return view('siswa.ujian.index', compact('ujians', 'sudahDikerjakan', 'hasilSiswa', 'sudahBerakhir')); +} + public function kerjakan($id) +{ + $siswa = auth()->user()->siswa; + + $sudah = Hasil::where('siswa_id', $siswa->id) + ->where('ujian_id', $id) + ->exists(); + + if ($sudah) { + return redirect()->route('siswa.ujian.index') + ->with('warning', 'Kamu sudah mengerjakan ujian ini.'); + } + + $ujian = Ujian::findOrFail($id); + + // ✅ FIX: parse eksplisit dengan Carbon + $mulai = $ujian->mulai ? \Carbon\Carbon::parse($ujian->mulai) : null; + $selesai = $ujian->selesai ? \Carbon\Carbon::parse($ujian->selesai) : null; + + if ($mulai && now()->lt($mulai)) { + return redirect()->route('siswa.ujian.index') + ->with('warning', 'Ujian belum dimulai. Silakan tunggu waktu ujian.'); + } + + if ($selesai && now()->gt($selesai)) { + return redirect()->route('siswa.ujian.index') + ->with('warning', 'Waktu ujian sudah berakhir, kamu tidak dapat mengerjakan ujian ini.'); + } + + $soals = Soal::where('ujian_id', $id)->get(); + + return view('siswa.ujian.kerjakan', compact('ujian', 'soals')); } - public function kerjakan($id) - { - $siswa = auth()->user()->siswa; +public function submit(Request $request, $id) +{ + $siswa = auth()->user()->siswa; - $sudah = Hasil::where('siswa_id', $siswa->id) - ->where('ujian_id', $id) - ->exists(); + $sudah = Hasil::where('siswa_id', $siswa->id) + ->where('ujian_id', $id) + ->exists(); - if ($sudah) { - return redirect()->route('siswa.ujian.index') - ->with('warning', 'Kamu sudah mengerjakan ujian ini.'); - } - - $ujian = Ujian::findOrFail($id); - $soals = Soal::where('ujian_id', $id)->get(); - - return view('siswa.ujian.kerjakan', compact('ujian', 'soals')); + if ($sudah) { + return redirect()->route('siswa.ujian.index') + ->with('warning', 'Kamu sudah mengerjakan ujian ini.'); } - public function submit(Request $request, $id) - { - $siswa = auth()->user()->siswa; + $ujian = Ujian::findOrFail($id); + $selesai = $ujian->selesai ? \Carbon\Carbon::parse($ujian->selesai) : null; - // Cegah submit ulang - $sudah = Hasil::where('siswa_id', $siswa->id) - ->where('ujian_id', $id) - ->exists(); - - if ($sudah) { - return redirect()->route('siswa.ujian.index') - ->with('warning', 'Kamu sudah mengerjakan ujian ini.'); - } - - $soals = Soal::where('ujian_id', $id)->get(); - $ujian = Ujian::findOrFail($id); - - $benar = 0; - $total = $soals->count(); - - foreach ($soals as $s) { - $jawabanUser = $request->jawaban[$s->id] ?? null; - if ($jawabanUser == $s->jawaban_benar) { - $benar++; - } - } - - $nilai = round(($benar / $total) * 100); - - // Simpan ke tabel hasils (hanya kolom yang ada) - Hasil::create([ - 'siswa_id' => $siswa->id, - 'ujian_id' => $id, - 'nilai' => $nilai, - ]); - - return view('siswa.ujian.hasil', compact('nilai', 'benar', 'total', 'ujian')); + // ✅ FIX: Cegah submit jika waktu sudah habis + if ($selesai && now()->gt($selesai)) { + return redirect()->route('siswa.ujian.index') + ->with('warning', 'Waktu ujian sudah berakhir, jawaban tidak dapat dikumpulkan.'); } + + $soals = Soal::where('ujian_id', $id)->get(); + $benar = 0; + $total = $soals->count(); + + foreach ($soals as $s) { + $jawabanUser = $request->jawaban[$s->id] ?? null; + if ($jawabanUser == $s->jawaban_benar) { + $benar++; + } + } + + $nilai = round(($benar / $total) * 100); + + Hasil::create([ + 'siswa_id' => $siswa->id, + 'ujian_id' => $id, + 'nilai' => $nilai, + ]); + + return view('siswa.ujian.hasil', compact('nilai', 'benar', 'total', 'ujian')); +} } \ No newline at end of file diff --git a/public/uploads/1779301204.PNG b/public/uploads/1779301204.PNG new file mode 100644 index 0000000..c773722 Binary files /dev/null and b/public/uploads/1779301204.PNG differ diff --git a/public/uploads/1779301496.jpg b/public/uploads/1779301496.jpg new file mode 100644 index 0000000..1b44835 Binary files /dev/null and b/public/uploads/1779301496.jpg differ diff --git a/resources/views/admin/kelas/index.blade.php b/resources/views/admin/kelas/index.blade.php index bdf6cb5..9561a07 100644 --- a/resources/views/admin/kelas/index.blade.php +++ b/resources/views/admin/kelas/index.blade.php @@ -2,156 +2,345 @@ @section('content') - + .sw-btn-danger { + background: var(--danger-light); + color: var(--danger); + border: 1px solid #FECACA; + } -
+ .sw-btn-danger:hover { + background: var(--danger); + color: #fff; + } - {{-- TOPBAR --}} -
-
-

Data Kelas

-

Pengelolaan data kelas SMA Negeri 1 Bondowoso

+ .sw-btn-sm { + padding: 6px 12px; + font-size: 12.5px; + border-radius: 8px; + } + + .sw-alert { + display: flex; + align-items: center; + gap: 10px; + padding: 13px 16px; + border-radius: var(--radius-md); + font-size: 13.5px; + font-weight: 500; + margin-bottom: 20px; + background: var(--success-light); + color: var(--success); + border: 1px solid #A7F3D0; + } + + .sw-card { + background: var(--surface); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); + border: 1px solid var(--border); + } + + .sw-table-card { + overflow: hidden; + } + + .sw-table-wrap { + overflow-x: auto; + } + + table.sw-table { + width: 100%; + border-collapse: collapse; + font-size: 13.5px; + } + + table.sw-table thead tr { + background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%); + border-bottom: 2px solid #DDD6FE; + } + + table.sw-table thead th { + padding: 13px 16px; + font-weight: 700; + font-size: 12px; + color: #5B21B6; + text-transform: uppercase; + letter-spacing: .6px; + white-space: nowrap; + border: none; + } + + table.sw-table tbody tr { + border-bottom: 1px solid var(--border); + transition: background .15s; + } + + table.sw-table tbody tr:last-child { + border-bottom: none; + } + + table.sw-table tbody tr:hover { + background: #FAFAFF; + } + + table.sw-table td { + padding: 13px 16px; + color: var(--text-mid); + border: none; + vertical-align: middle; + } + + table.sw-table td.center { + text-align: center; + } + + table.sw-table td.name-cell { + font-weight: 600; + color: var(--text-dark); + } + + .sw-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + border-radius: 99px; + font-size: 11.5px; + font-weight: 600; + letter-spacing: .1px; + } + + .sw-badge-info { + background: var(--info-light); + color: var(--info); + } + + .sw-action-cell { + display: flex; + gap: 6px; + align-items: center; + justify-content: center; + } + + .sw-no { + display: inline-flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + background: #F3F4F6; + border-radius: 7px; + font-size: 12px; + font-weight: 600; + color: var(--text-soft); + } + + .sw-empty { + padding: 60px 20px; + text-align: center; + color: var(--text-soft); + } + + .sw-empty-icon { + font-size: 40px; + margin-bottom: 12px; + opacity: .4; + } + + .sw-empty p { + font-size: 14px; + margin: 0; + } + + +
+ + {{-- TOPBAR --}} +
+
+

Data Kelas

+

Pengelolaan data kelas SMA Negeri 1 Bondowoso

+
+ + + + + Tambah Kelas +
- - - Tambah Kelas - -
- {{-- ALERT --}} - @if(session('success')) -
- - {{ session('success') }} -
- @endif + {{-- ALERT --}} + @if(session('success')) +
+ + + + + {{ session('success') }} +
+ @endif - {{-- TABLE --}} -
-
- - - - - - - - - - - @forelse($kelas as $k) - - - - - - - @empty - - - - @endforelse - -
NoNama KelasJurusanAksi
- {{ $loop->iteration }} - {{ $k->nama_kelas }} - {{ $k->jurusan }} - -
- - - Edit - -
- @csrf - @method('DELETE') - -
-
-
-
-
🏫
-

Data kelas belum tersedia

-
-
+ {{-- TABLE --}} +
+
+ + + + + + + + + + + @forelse($kelas as $k) + + + + + + + @empty + + + + @endforelse + +
NoNama KelasJurusanAksi
+ {{ $loop->iteration }} + {{ $k->nama_kelas }} + {{ $k->jurusan }} + +
+ + + + + + Edit + +
+ @csrf + @method('DELETE') + +
+
+
+
+
🏫
+

Data kelas belum tersedia

+
+
+
-
-
+
@endsection \ No newline at end of file diff --git a/resources/views/guru/nilai/index.blade.php b/resources/views/guru/nilai/index.blade.php index 23a6e2d..bcb0c7d 100644 --- a/resources/views/guru/nilai/index.blade.php +++ b/resources/views/guru/nilai/index.blade.php @@ -154,10 +154,10 @@ function dismissToast(){

Rekap nilai tugas, UTS, UAS & hitung rata-rata siswa

- + {{-- INFO --}} @@ -293,20 +293,23 @@ function dismissToast(){ {{-- FOOTER + TOMBOL KIRIM SEMUA KELAS --}} - + diff --git a/resources/views/siswa/tugas/index.blade.php b/resources/views/siswa/tugas/index.blade.php index 81ad164..a7f46c2 100644 --- a/resources/views/siswa/tugas/index.blade.php +++ b/resources/views/siswa/tugas/index.blade.php @@ -429,7 +429,7 @@ Aksi - + diff --git a/resources/views/siswa/ujian/index.blade.php b/resources/views/siswa/ujian/index.blade.php index 10f2032..38be06c 100644 --- a/resources/views/siswa/ujian/index.blade.php +++ b/resources/views/siswa/ujian/index.blade.php @@ -2,219 +2,567 @@ @section('content') - + .sw-topbar p { + font-size: 13px; + color: var(--text-soft); + margin: 0; + } -
+ .sw-badge-ready { + display: inline-flex; + align-items: center; + gap: 7px; + background: var(--primary-light); + border: 1px solid #C7D2FE; + border-radius: 99px; + padding: 7px 16px; + font-size: 13px; + font-weight: 600; + color: var(--primary); + } - {{-- TOPBAR --}} -
-
-

Ujian Tersedia

-

Daftar ujian yang dapat Anda kerjakan sekarang

-
-
- - - - Siap mengerjakan ujian hari ini? -
-
+ .sw-alert-warning { + display: flex; + align-items: center; + gap: 10px; + background: #FFFBEB; + border: 1px solid #FDE68A; + border-radius: 10px; + padding: 12px 16px; + margin-bottom: 20px; + font-size: 13px; + font-weight: 600; + color: #92400E; + } - {{-- ALERT WARNING --}} - @if(session('warning')) -
- - - - {{ session('warning') }} -
- @endif + .sw-hero-card { + position: relative; + border-radius: 22px; + overflow: hidden; + margin-bottom: 24px; + box-shadow: 0 12px 40px rgba(79, 70, 229, .22); + } - {{-- DAFTAR UJIAN --}} - @forelse($ujians as $u) - @php $dikerjakan = in_array($u->id, $sudahDikerjakan); @endphp + .sw-hero-bg { + position: absolute; + inset: 0; + background: linear-gradient(135deg, #4F46E5 0%, #0284C7 100%); + } -
-
-
-
-
+ .sw-hero-blob1 { + position: absolute; + width: 260px; + height: 260px; + border-radius: 50%; + background: rgba(255, 255, 255, .07); + top: -80px; + right: -60px; + } + + .sw-hero-blob2 { + position: absolute; + width: 160px; + height: 160px; + border-radius: 50%; + background: rgba(255, 255, 255, .05); + bottom: -50px; + left: 40px; + } + + .sw-hero-inner { + position: relative; + z-index: 2; + padding: 36px 40px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + flex-wrap: wrap; + } + + .sw-hero-eyebrow { + display: inline-flex; + align-items: center; + gap: 6px; + background: rgba(255, 255, 255, .18); + border-radius: 99px; + padding: 4px 12px; + font-size: 11.5px; + font-weight: 700; + color: rgba(255, 255, 255, .9); + letter-spacing: .4px; + text-transform: uppercase; + margin-bottom: 12px; + } + + .sw-hero-title { + font-size: 24px; + font-weight: 800; + color: #fff; + margin-bottom: 14px; + letter-spacing: -.4px; + line-height: 1.3; + } + + .sw-hero-chips { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 22px; + } + + .sw-hero-chip { + display: inline-flex; + align-items: center; + gap: 6px; + background: rgba(255, 255, 255, .15); + border: 1px solid rgba(255, 255, 255, .2); + border-radius: 8px; + padding: 5px 12px; + font-size: 12px; + font-weight: 600; + color: #fff; + } + + .sw-btn-start { + display: inline-flex; + align-items: center; + gap: 8px; + background: #fff; + color: var(--primary); + border: none; + border-radius: 12px; + padding: 12px 26px; + font-size: 14px; + font-weight: 800; + cursor: pointer; + text-decoration: none; + transition: all .18s; + font-family: 'Plus Jakarta Sans', sans-serif; + box-shadow: 0 4px 16px rgba(0, 0, 0, .12); + } + + .sw-btn-start:hover { + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 0, 0, .18); + color: var(--primary-dark); + text-decoration: none; + } + + .sw-btn-done { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(255, 255, 255, .2); + color: rgba(255, 255, 255, .75); + border: 1px solid rgba(255, 255, 255, .3); + border-radius: 12px; + padding: 12px 26px; + font-size: 14px; + font-weight: 700; + cursor: not-allowed; + } + + .sw-hero-deco { + width: 100px; + height: 100px; + border-radius: 24px; + background: rgba(255, 255, 255, .12); + border: 1px solid rgba(255, 255, 255, .2); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: #fff; + } + + .sw-stats-row { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin-bottom: 24px; + } + + .sw-stat-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 16px; + padding: 20px 22px; + display: flex; + align-items: center; + gap: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, .04); + } + + .sw-stat-icon { + width: 44px; + height: 44px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + } + + .si-indigo { + background: var(--primary-light); + color: var(--primary); + } + + .si-blue { + background: var(--info-light); + color: var(--info); + } + + .si-green { + background: var(--success-light); + color: var(--success); + } + + .sw-stat-label { + font-size: 12px; + font-weight: 600; + color: var(--text-soft); + text-transform: uppercase; + letter-spacing: .4px; + margin-bottom: 3px; + } + + .sw-stat-value { + font-size: 22px; + font-weight: 800; + color: var(--text-dark); + line-height: 1; + } + + .sw-empty { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 20px; + padding: 64px 24px; + text-align: center; + box-shadow: 0 1px 3px rgba(0, 0, 0, .04); + margin-bottom: 24px; + } + + .sw-empty-ring { + width: 80px; + height: 80px; + border-radius: 50%; + background: var(--primary-light); + border: 2px solid #C7D2FE; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 20px; + color: var(--primary); + } + + .sw-empty h5 { + font-size: 17px; + font-weight: 700; + color: var(--text-dark); + margin-bottom: 8px; + } + + .sw-empty p { + font-size: 13.5px; + color: var(--text-soft); + margin: 0; + } + + @media (max-width: 768px) { + .sw-page { + padding: 16px; + } + + .sw-hero-inner { + padding: 24px; + } + + .sw-hero-deco { + display: none; + } + + .sw-hero-title { + font-size: 19px; + } + + .sw-stats-row { + grid-template-columns: 1fr; + } + } + + +
+ + {{-- TOPBAR --}} +
- {{-- EYEBROW --}} -
- @if($dikerjakan) - - Sudah Dikerjakan - @else - - Ujian Aktif - @endif +

Ujian Tersedia

+

Daftar ujian yang dapat Anda kerjakan sekarang

+
+
+ + + + Siap mengerjakan ujian hari ini? +
+
+ + {{-- ALERT WARNING --}} + @if(session('warning')) +
+ + + + {{ session('warning') }} +
+ @endif + + {{-- DAFTAR UJIAN --}} + @forelse($ujians as $u) + @php $dikerjakan = in_array($u->id, $sudahDikerjakan); @endphp + +
- {{-- JUDUL --}} -
{{ $u->judul }}
- - {{-- CHIPS --}} -
- - - {{ $u->durasi }} menit - - {{-- Ganti chip jenis yang ada di sw-hero-chips --}} - @if($u->jenis) - - - {{ $u->jenis }} — {{ $u->jenis == 'UTS' ? 'Ujian Tengah Semester' : 'Ujian Akhir Semester' }} - - @endif - @if($u->mulai) - - - {{ $u->mulai }} - - @endif - {{-- CHIP NILAI (muncul kalau sudah dikerjakan) --}} - @if($dikerjakan && isset($hasilSiswa[$u->id])) - - - Nilai: {{ $hasilSiswa[$u->id]->nilai }} - - @endif + @empty +
+
+ + + + + +
- - {{-- TOMBOL --}} - @if($dikerjakan) - - - Sudah Selesai Dikerjakan - - @else -
- - Mulai Ujian Sekarang - - @endif +
Tidak Ada Ujian
+

Saat ini belum ada ujian yang tersedia. Silakan cek kembali nanti.

+ @endforelse - {{-- DECO ICON --}} -
- @if($dikerjakan) - - @else - - @endif + {{-- MINI INFO --}} +
+
+
+ + + + +
+
+
Total Ujian
+
{{ $ujians->count() }}
+
+
+
+
+ + + + +
+
+
Sudah Dikerjakan
+
{{ count($sudahDikerjakan) }}
+
+
+
+
+ + + + + + +
+
+
Mode
+
Online
+
+
- - @empty -
-
- -
-
Tidak Ada Ujian
-

Saat ini belum ada ujian yang tersedia. Silakan cek kembali nanti.

-
- @endforelse - - {{-- MINI INFO --}} -
-
-
- -
-
-
Total Ujian
-
{{ $ujians->count() }}
-
-
-
-
- -
-
-
Sudah Dikerjakan
-
{{ count($sudahDikerjakan) }}
-
-
-
-
- -
-
-
Mode
-
Online
-
-
-
- -
@endsection \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 9204489..6d5a78e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -69,6 +69,7 @@ | OTP LOGIN |-------------------------------------------------------------------------- */ +// Route ini pakai OtpController, BUKAN AuthController Route::post('/send-otp', [OtpController::class, 'sendOtp'])->name('send.otp'); Route::post('/login-otp', [OtpController::class, 'loginOtp'])->name('login.otp');