diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 3511cb1..9a7bbb1 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -13,77 +13,164 @@ use Illuminate\Support\Facades\Http; class AuthController extends Controller - { - // ================= LOGIN ================= + // ================= SHOW LOGIN ================= public function showLogin() { return view('auth.login'); } - - -public function login(Request $request) + // ================= LOGIN BIASA (email + password) ================= + public function login(Request $request) { $credentials = $request->only('email', 'password'); + $user = \App\Models\User::with('guru') + ->where('email', $request->email) + ->first(); + + if (!$user) { + return back()->with('error', 'Email tidak ditemukan'); + } + + if (!$user->guru || $user->guru->status !== 'aktif') { + return back()->with('error', 'Akun anda telah dinonaktifkan'); + } + if (Auth::attempt($credentials)) { - $request->session()->regenerate(); - $user = Auth::user(); - // 🔥 HANYA JIKA PASSWORD DEFAULT + // Notifikasi login pertama → HANYA kirim email, tidak WA + // Hilangkan logika WA di sini agar tidak mengganggu flow OTP if ($user->is_default_password) { - - // ================= EMAIL ================= - Mail::to($user->email)->send(new LoginNotification($user)); - - // ================= WHATSAPP ================= - $telepon = $user->getTeleponLengkap(); - - if ($telepon) { - - // normalisasi nomor - $no = preg_replace('/[^0-9]/', '', $telepon); - if (substr($no, 0, 1) == '0') { - $no = '62' . substr($no, 1); - } - - $pesan = "🔐 LOGIN PERTAMA\n"; - $pesan .= "Nama: {$user->name}\n"; - $pesan .= "Email: {$user->email}\n"; - $pesan .= "Gunakan password default\n"; - $pesan .= "Segera ganti password!\n"; - $pesan .= "Waktu: " . now(); - - Http::asForm()->withHeaders([ - 'Authorization' => env('FONNTE_TOKEN') - ])->post('https://api.fonnte.com/send', [ - 'target' => $no, - 'message' => $pesan, - ]); - } + Mail::to($user->email)->send(new \App\Mail\LoginNotification($user)); } - // 🔥 REDIRECT ROLE - if ($user->role == 'admin') { - return redirect()->route('admin.dashboard'); - } - - if ($user->role == 'guru') { - return redirect()->route('guru.dashboard'); - } - - if ($user->role == 'siswa') { - return redirect()->route('siswa.dashboard'); - } - - return redirect('/login'); + return $this->redirectByRole($user); } return back()->with('error', 'Email atau password salah'); } + + // ================= KIRIM OTP ================= + // Dipanggil dari form Langkah 1 di halaman login + public function sendOtp(Request $request) + { + $method = $request->input('method', 'email'); // 'email' atau 'wa' + + if ($method === 'email') { + $request->validate([ + 'email' => 'required|email|exists:users,email', + ]); + + $user = User::where('email', $request->email)->first(); + + } else { + $request->validate([ + 'telepon' => 'required|string', + ]); + + $no = preg_replace('/[^0-9]/', '', $request->telepon); + if (str_starts_with($no, '0')) { + $no = '62' . substr($no, 1); + } + + $user = User::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}%"]); + })->first(); + + if (!$user) { + return back()->with('error', 'Nomor WhatsApp tidak ditemukan.'); + } + } + + // Kirim OTP sesuai method — email SAJA atau WA SAJA + 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!'); + } + + + // ================= LOGIN DENGAN OTP (Langkah 2) ================= + // Dipanggil dari form Langkah 2 di halaman login + public function loginOtp(Request $request) +{ + $request->validate([ + 'email' => 'required|email', + 'password' => 'required', + 'otp' => 'nullable|digits:6', + ]); + + $user = User::where('email', $request->email)->first(); + + if (!$user) { + return back()->with('error', 'Email tidak ditemukan.'); + } + + // Cek password + if (!Hash::check($request->password, $user->password)) { + return back()->with('error', 'Password salah.'); + } + + // ================================================= + // ADMIN → LANGSUNG LOGIN TANPA OTP + // ================================================= + if ($user->role === 'admin') { + + Auth::login($user); + $request->session()->regenerate(); + + return redirect()->route('admin.dashboard'); + } + + // ================================================= + // GURU / SISWA WAJIB OTP + // ================================================= + if (in_array($user->role, ['guru', 'siswa'])) { + + if (empty($request->otp)) { + return back()->with('error', 'OTP wajib diisi.'); + } + + if (!$user->otp) { + return back()->with('error', 'OTP belum diminta.'); + } + + if ((string)$request->otp !== (string)$user->otp) { + return back()->with('error', 'Kode OTP salah.'); + } + + if (!$user->otp_expired_at || now()->gt($user->otp_expired_at)) { + return back()->with('error', 'OTP sudah kadaluarsa.'); + } + + // Login + Auth::login($user); + $request->session()->regenerate(); + + // Hapus OTP + $user->update([ + 'otp' => null, + 'otp_expired_at' => null, + ]); + + return match ($user->role) { + 'guru' => redirect()->route('guru.dashboard'), + 'siswa' => redirect()->route('siswa.dashboard'), + default => redirect('/'), + }; + } + + return back()->with('error', 'Role tidak dikenali.'); +} + + // ================= UPDATE PASSWORD ================= public function updatePassword(Request $request) { @@ -93,12 +180,10 @@ public function updatePassword(Request $request) $user = Auth::user(); - // 🔥 UPDATE + FORCE REFRESH USER $user->password = Hash::make($request->password); $user->is_default_password = false; $user->save(); - // 🔥 PENTING: refresh data user (hindari cache lama) Auth::setUser($user->fresh()); return $this->redirectByRole($user); @@ -113,18 +198,18 @@ public function showRegister() public function register(Request $request) { $request->validate([ - 'name' => 'required', - 'email' => 'required|email|unique:users', + 'name' => 'required', + 'email' => 'required|email|unique:users', 'password' => 'required|min:6', - 'role' => 'required' + 'role' => 'required' ]); User::create([ - 'name' => $request->name, - 'email' => $request->email, - 'password' => Hash::make($request->password), - 'role' => $request->role, - 'is_default_password' => false // 🔥 register normal + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + 'role' => $request->role, + 'is_default_password' => false, ]); return redirect('/login')->with('success', 'Register berhasil'); @@ -141,134 +226,73 @@ public function logout(Request $request) return redirect('/login'); } - // ================= BONUS: REDIRECT BY ROLE ================= - private function redirectByRole($user) + // ================= VERIFY OTP (halaman terpisah jika ada) ================= + public function showVerifyOtp() + { + return view('auth.verify-otp'); + } + + public function verifyOtp(Request $request) + { + if ((string) $request->otp === (string) session('otp')) { + return redirect()->route('reset.password.form'); + } + + return back()->with('error', 'OTP salah'); + } + + // ================= RESET PASSWORD (via OTP lupa password) ================= + public function showResetPassword() + { + return view('auth.reset-password'); + } + + public function resetPasswordOtp(Request $request) + { + $request->validate([ + 'password' => 'required|min:6|confirmed' + ]); + + $user = User::find(session('otp_user')); + + $user->update([ + 'password' => Hash::make($request->password), + 'otp' => null, + 'otp_expired_at' => null, + 'is_default_password' => false, + ]); + + session()->forget('otp_user'); + + return redirect('/login')->with('success', 'Password berhasil diubah'); + } + + public function resetPassword(Request $request) + { + $request->validate([ + 'password' => 'required|confirmed|min:6' + ]); + + $user = \App\Models\User::where('email', session('email'))->first(); + + if (!$user) { + return back()->with('error', 'User tidak ditemukan'); + } + + $user->password = bcrypt($request->password); + $user->save(); + + return redirect()->route('login')->with('success', 'Password berhasil diubah'); + } + + // ================= HELPER: REDIRECT BY ROLE ================= + private function redirectByRole($user) { return match ($user->role) { 'admin' => redirect()->route('admin.dashboard'), - 'guru' => redirect()->route('guru.dashboard'), + 'guru' => redirect()->route('guru.dashboard'), 'siswa' => redirect()->route('siswa.dashboard'), default => redirect('/login'), }; } - - public function verifyOtp(Request $request) -{ - if ($request->otp == session('otp')) { - - return redirect()->route('reset.password.form'); // ✅ INI WAJIB - } - - return back()->with('error', 'OTP salah'); -} - -public function resetPasswordOtp(Request $request) -{ - $request->validate([ - 'password' => 'required|min:6|confirmed' - ]); - - $user = User::find(session('otp_user')); - - $user->update([ - 'password' => Hash::make($request->password), - 'otp' => null, - 'otp_expired_at' => null, - 'is_default_password' => false - ]); - - session()->forget('otp_user'); - - return redirect('/login')->with('success', 'Password berhasil diubah'); -} - -public function sendOtp(Request $request) -{ - $request->validate([ - 'email' => 'required|email|exists:users,email' - ]); - - $otp = rand(100000, 999999); - - session([ - 'otp' => $otp, - 'email' => $request->email - ]); - - // kirim email (Mailtrap sudah jalan) - - \Mail::raw("Kode OTP kamu adalah: $otp", function ($message) use ($request) { - $message->to($request->email) - ->subject('Kode OTP Reset Password'); - }); - - return redirect()->route('verify.otp.form'); -} - -public function showVerifyOtp() -{ - return view('auth.verify-otp'); -} - -public function showResetPassword() -{ - return view('auth.reset-password'); -} - -public function resetPassword(Request $request) -{ - $request->validate([ - 'password' => 'required|confirmed|min:6' - ]); - - $user = \App\Models\User::where('email', session('email'))->first(); - - if (!$user) { - return back()->with('error', 'User tidak ditemukan'); - } - - $user->password = bcrypt($request->password); - $user->save(); - - return redirect()->route('login')->with('success', 'Password berhasil diubah'); -} - -public function sendWhatsapp($nomor, $pesan) -{ - $token = env('rJ5jmNfJ99gJFNdmjmTW'); - - // 🔥 NORMALISASI NOMOR (ANTI SALAH FORMAT) - $nomor = preg_replace('/[^0-9]/', '', $nomor); - - if (substr($nomor, 0, 1) == '0') { - $nomor = '62' . substr($nomor, 1); - } - - $curl = curl_init(); - - curl_setopt_array($curl, array( - CURLOPT_URL => "https://api.fonnte.com/send", - CURLOPT_RETURNTRANSFER => true, - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => [ - 'target' => $nomor, - 'message' => $pesan, - 'countryCode' => '62' - ], - CURLOPT_HTTPHEADER => [ - "Authorization: $token" - ], - )); - - $response = curl_exec($curl); - - if (curl_errno($curl)) { - dd("CURL ERROR: " . curl_error($curl)); - } - - curl_close($curl); - - // 🔥 DEBUG WAJIB - return $response; -} } \ No newline at end of file diff --git a/app/Http/Controllers/admin/DashboardController.php b/app/Http/Controllers/admin/DashboardController.php index a02ba1a..abe2fa5 100644 --- a/app/Http/Controllers/admin/DashboardController.php +++ b/app/Http/Controllers/admin/DashboardController.php @@ -13,31 +13,66 @@ class DashboardController extends Controller { public function index() { - // Statistik utama + // ===================== + // 🔥 STATISTIK + // ===================== $jumlahSiswa = Siswa::count(); $jumlahGuru = Guru::count(); $jumlahKelas = Kelas::count(); $jumlahMapel = MataPelajaran::count(); - // Siswa terbaru + // ===================== + // 🔥 SISWA TERBARU + // ===================== $siswaTerbaru = Siswa::with('kelas') ->latest() ->take(5) ->get(); - // 🔥 Nilai tertinggi + // ===================== + // 🔥 NILAI TERTINGGI $nilaiTertinggi = Nilai::with('siswa') - ->orderBy('nilai', 'desc') + ->selectRaw('*, (nilai_tugas + nilai_uts + nilai_uas)/3 as nilai_akhir') + ->orderBy('nilai_akhir', 'desc') ->take(5) ->get(); + // ===================== + // 🔥 CHART: SISWA PER KELAS + // ===================== + $kelas = Kelas::withCount('siswa')->get(); + + $kelasLabels = $kelas->pluck('nama'); + $kelasData = $kelas->pluck('siswa_count'); + + // ===================== + // 🔥 CHART: DISTRIBUSI NILAI + // ===================== + $nilaiDistribusi = Nilai::selectRaw(' + CASE + WHEN (nilai_tugas + nilai_uts + nilai_uas)/3 >= 90 THEN "A" + WHEN (nilai_tugas + nilai_uts + nilai_uas)/3 >= 80 THEN "B" + WHEN (nilai_tugas + nilai_uts + nilai_uas)/3 >= 70 THEN "C" + ELSE "D" + END as grade, + COUNT(*) as total +') + ->groupBy('grade') + ->pluck('total', 'grade'); + + // ===================== + // RETURN VIEW + // ===================== return view('admin.dashboard', compact( 'jumlahSiswa', 'jumlahGuru', 'jumlahKelas', 'jumlahMapel', 'siswaTerbaru', - 'nilaiTertinggi' + 'nilaiTertinggi', + 'kelasLabels', + 'kelasData', + 'nilaiDistribusi' )); } } \ No newline at end of file diff --git a/app/Http/Controllers/admin/GuruController.php b/app/Http/Controllers/admin/GuruController.php index dbfa18b..598bd92 100644 --- a/app/Http/Controllers/admin/GuruController.php +++ b/app/Http/Controllers/admin/GuruController.php @@ -72,10 +72,12 @@ public function store(Request $request) } // ================= EDIT ================= - public function edit(Guru $guru) + public function edit($id) { + $guru = Guru::findOrFail($id); $mapel = MataPelajaran::all(); - return view('admin.guru.edit', compact('guru', 'mapel')); + + return view('admin.guru.edit', compact('guru', 'mapel')); } // ================= UPDATE ================= @@ -92,7 +94,7 @@ public function update(Request $request, Guru $guru) $guru->update([ 'nama' => $request->nama, 'nip' => $request->nip, - 'mapel_id' => $request->mapel_id, // 🔥 FIX + 'mapel_id' => $request->mapel_id, // 🔥 INI WAJIB 'alamat' => $request->alamat, 'telepon' => $request->telepon, ]); diff --git a/app/Http/Controllers/auth/OtpController.php b/app/Http/Controllers/auth/OtpController.php index 5d787f1..b453b7a 100644 --- a/app/Http/Controllers/auth/OtpController.php +++ b/app/Http/Controllers/auth/OtpController.php @@ -5,48 +5,78 @@ use App\Http\Controllers\Controller; use Illuminate\Http\Request; use App\Models\User; -use Illuminate\Support\Facades\Auth; -use Carbon\Carbon; class OtpController extends Controller { // ================= KIRIM OTP ================= public function sendOtp(Request $request) { - $user = User::where('email', $request->email)->first(); + $method = $request->input('method', 'email'); - if (!$user) { - return back()->with('error', 'Email tidak ditemukan'); + if ($method === 'email') { + // ── EMAIL ── + $request->validate([ + 'email' => 'required|email|exists:users,email', + ]); + + $user = User::where('email', $request->email)->first(); + + } else { + // ── WHATSAPP ── + $request->validate([ + 'telepon' => 'required|string', + ]); + + $no = preg_replace('/[^0-9]/', '', $request->telepon); + if (str_starts_with($no, '0')) { + $no = '62' . substr($no, 1); + } + $no08 = '0' . substr($no, 2); + + $user = User::whereHas('guru', function ($q) use ($no, $no08) { + $q->where('telepon', $no) + ->orWhere('telepon', $no08) + ->orWhere('telepon', 'like', '%' . substr($no, -9) . '%'); + })->orWhereHas('siswa', function ($q) use ($no, $no08) { + $q->where('telepon', $no) + ->orWhere('telepon', $no08) + ->orWhere('telepon', 'like', '%' . substr($no, -9) . '%'); + })->first(); + + if (!$user) { + return back()->with('error', 'Nomor WhatsApp tidak ditemukan.'); + } } - app(\App\Services\OtpService::class)->send($user); + // Kirim OTP sesuai method (email SAJA atau WA SAJA) + app(\App\Services\OtpService::class)->send($user, $method); - return back()->with('success', 'OTP berhasil dikirim'); + // Simpan email ke session untuk prefill form login + session(['email' => $user->email]); + + return back()->with('otp_sent', 'OTP berhasil dikirim!'); } - // ================= LOGIN ================= + // ================= LOGIN DENGAN OTP ================= public function loginOtp(Request $request) { $request->validate([ - 'email' => 'required|email', + 'email' => 'required|email', 'password' => 'required', - 'otp' => 'nullable|digits:6' + 'otp' => 'nullable|digits:6', ]); - $user = \App\Models\User::where('email', $request->email)->first(); + $user = User::where('email', $request->email)->first(); if (!$user) { return back()->with('error', 'User tidak ditemukan'); } - // 🔐 CEK PASSWORD if (!\Hash::check($request->password, $user->password)) { return back()->with('error', 'Password salah'); } - // ========================= - // 🔥 KHUSUS GURU & SISWA WAJIB OTP - // ========================= + // Guru & Siswa wajib OTP if (in_array($user->role, ['guru', 'siswa'])) { if (!$request->otp) { @@ -57,7 +87,7 @@ public function loginOtp(Request $request) return back()->with('error', 'OTP belum diminta'); } - if ((string)$request->otp !== (string)$user->otp) { + if ((string) $request->otp !== (string) $user->otp) { return back()->with('error', 'OTP salah'); } @@ -66,34 +96,21 @@ public function loginOtp(Request $request) } } - // ========================= - // ✅ LOGIN - // ========================= \Auth::login($user); + $request->session()->regenerate(); - // 🔥 HAPUS OTP SETELAH DIPAKAI (hanya untuk guru & siswa) + // Hapus OTP setelah dipakai if (in_array($user->role, ['guru', 'siswa'])) { - $user->update([ - 'otp' => null, - 'otp_expired_at' => null - ]); + $user->update(['otp' => null, 'otp_expired_at' => null]); } - // ========================= - // 🔁 REDIRECT ROLE - // ========================= - if ($user->role == 'admin') { - return redirect()->route('admin.dashboard'); - } + session()->forget('email'); - if ($user->role == 'guru') { - return redirect()->route('guru.dashboard'); - } - - if ($user->role == 'siswa') { - return redirect()->route('siswa.dashboard'); - } - - return redirect('/'); + return match ($user->role) { + 'admin' => redirect()->route('admin.dashboard'), + 'guru' => redirect()->route('guru.dashboard'), + 'siswa' => redirect()->route('siswa.dashboard'), + default => redirect('/'), + }; } } \ No newline at end of file diff --git a/app/Http/Controllers/guru/AbsensiController.php b/app/Http/Controllers/guru/AbsensiController.php index e97e451..62a6567 100644 --- a/app/Http/Controllers/guru/AbsensiController.php +++ b/app/Http/Controllers/guru/AbsensiController.php @@ -9,122 +9,106 @@ use App\Models\Kelas; use App\Models\MataPelajaran; use App\Models\Siswa; -use Carbon\Carbon; class AbsensiController extends Controller { -public function index(Request $request) -{ - $kelas = Kelas::all(); - $mapel = MataPelajaran::find(auth()->user()->guru->mapel_id); - // 🔥 VALIDASI DI SINI - if (!$mapel) { - return back()->with('error', 'Mapel guru belum diatur!'); - } - $siswa = null; - $kelasAktif = null; - $riwayat = null; + public function index(Request $request) + { + $kelas = Kelas::all(); + $mapel = MataPelajaran::find(auth()->user()->guru->mapel_id); - if ($request->kelas_id && $request->mapel_id) { - - $kelasAktif = Kelas::findOrFail($request->kelas_id); - - $siswa = Siswa::where('kelas_id', $request->kelas_id)->get(); - - // 🔥 ambil absensi hari ini - $absensi = Absensi::where('kelas_id', $request->kelas_id) - ->where('mapel_id', $request->mapel_id) - ->whereDate('tanggal', now()) - ->first(); - - if ($absensi) { - $riwayat = AbsensiDetail::with('siswa') - ->where('absensi_id', $absensi->id) - ->get(); + if (!$mapel) { + return back()->with('error', 'Mapel guru belum diatur!'); } + + $siswa = null; + $kelasAktif = null; + $riwayat = null; + + $aksi = $request->aksi; + $tanggal = $request->tanggal ?? now()->toDateString(); + + if ($request->kelas_id && $request->mapel_id) { + + // 🔍 MODE LIHAT RIWAYAT + if ($aksi == 'lihat') { + + $absensi = Absensi::where('kelas_id', $request->kelas_id) + ->where('mapel_id', $request->mapel_id) + ->whereDate('tanggal', $tanggal) + ->first(); + + if ($absensi) { + $riwayat = AbsensiDetail::with('siswa') + ->where('absensi_id', $absensi->id) + ->get(); + } + } + + // ✏️ MODE INPUT ABSENSI + if ($aksi == 'input') { + + $kelasAktif = Kelas::findOrFail($request->kelas_id); + + $siswa = Siswa::where('kelas_id', $request->kelas_id)->get(); + } + } + + return view('guru.absensi.index', compact( + 'kelas', + 'mapel', + 'siswa', + 'kelasAktif', + 'riwayat' + )); } - return view('guru.absensi.index', compact( - 'kelas', - 'mapel', - 'siswa', - 'kelasAktif', - 'riwayat' - )); -} + public function simpan(Request $request) + { + $request->validate([ + 'kelas_id' => 'required', + 'mapel_id' => 'required', + 'absensi' => 'required|array' + ]); - public function buka(Request $request) + $absensi = Absensi::firstOrCreate([ + 'kelas_id' => $request->kelas_id, + 'mapel_id' => $request->mapel_id, + 'tanggal' => $request->tanggal, + ], [ + 'guru_id' => auth()->user()->guru->id + ]); + + foreach ($request->absensi as $siswaId => $data) { + + AbsensiDetail::updateOrCreate( + [ + 'absensi_id' => $absensi->id, + 'siswa_id' => $siswaId + ], + [ + 'status' => $data['status'], + 'keterangan' => $data['keterangan'] ?? null + ] + ); + } + + return redirect()->back()->with('success', 'Absensi berhasil disimpan'); + } + + public function update(Request $request, $id) { $request->validate([ - 'kelas_id' => 'required', - 'mapel_id' => 'required' + 'status' => 'required' ]); - $absensi = Absensi::create([ - 'guru_id' => auth()->user()->guru->id, - 'kelas_id' => $request->kelas_id, - 'mapel_id' => $request->mapel_id, - 'tanggal' => now(), + $detail = AbsensiDetail::findOrFail($id); + + $detail->update([ + 'status' => $request->status, + 'keterangan' => $request->keterangan ]); - return redirect()->route('guru.absensi.kelola', $absensi->id); -} - - public function kelola($id) - { - $absensi = Absensi::findOrFail($id); - - $siswa = Siswa::where('kelas_id', $absensi->kelas_id)->get(); - - return view('guru.absensi.kelola', compact('absensi', 'siswa')); - } - - public function simpan(Request $request) -{ - $request->validate([ - 'kelas_id' => 'required', - 'mapel_id' => 'required', - 'absensi' => 'required|array' - ]); - - // 🔥 CEK / BUAT absensi hari ini - $absensi = Absensi::firstOrCreate([ - 'kelas_id' => $request->kelas_id, - 'mapel_id' => $request->mapel_id, - 'tanggal' => now()->toDateString(), - ], [ - 'guru_id' => auth()->user()->guru->id - ]); - - // 🔥 SIMPAN DETAIL - foreach ($request->absensi as $siswaId => $data) { - - AbsensiDetail::updateOrCreate( - [ - 'absensi_id' => $absensi->id, - 'siswa_id' => $siswaId - ], - [ - 'status' => $data['status'], - 'keterangan' => $data['keterangan'] ?? null - ] - ); - } - - return redirect()->back()->with('success', 'Absensi berhasil disimpan'); -} - - public function tutup($id) - { - Absensi::findOrFail($id)->update(['dibuka' => false]); - - return back()->with('success', 'Absensi ditutup'); - } - - public function delete($id) -{ - AbsensiDetail::findOrFail($id)->delete(); - - return back()->with('success', 'Data dihapus'); + return back()->with('success', 'Absensi berhasil diupdate'); } } \ No newline at end of file diff --git a/app/Http/Controllers/guru/DashboardController.php b/app/Http/Controllers/guru/DashboardController.php index 48a2ed0..883ca24 100644 --- a/app/Http/Controllers/guru/DashboardController.php +++ b/app/Http/Controllers/guru/DashboardController.php @@ -2,12 +2,34 @@ namespace App\Http\Controllers\guru; +use App\Models\Jadwal; +use App\Models\Absensi; +use App\Models\Tugas; +use App\Models\Ujian; + + use App\Http\Controllers\Controller; class DashboardController extends Controller { - public function index() - { - return view('guru.dashboard'); - } -} \ No newline at end of file + +public function index() +{ + $guruId = auth()->user()->guru_id; + + $jumlahJadwal = Jadwal::where('guru_id', $guruId)->count(); + + $jumlahAbsensi = Absensi::where('guru_id', $guruId)->count(); + + $jumlahTugas = Tugas::where('guru_id', $guruId)->count(); + + $jumlahUjian = Ujian::where('guru_id', $guruId)->count(); + + return view('guru.dashboard', compact( + 'jumlahJadwal', + 'jumlahAbsensi', + 'jumlahTugas', + 'jumlahUjian' + )); +} +} diff --git a/app/Http/Controllers/guru/TugasController.php b/app/Http/Controllers/guru/TugasController.php index fddfbe3..5f17097 100644 --- a/app/Http/Controllers/guru/TugasController.php +++ b/app/Http/Controllers/guru/TugasController.php @@ -15,21 +15,41 @@ class TugasController extends Controller { - public function index() + public function index() { $guru = auth()->user()->guru; $tugas = Tugas::with(['kelas', 'mapel']) - ->withCount('pengumpulan') // 🔥 hitung jumlah yang kumpul + ->withCount('pengumpulan') ->where('guru_id', $guru->id) ->get(); - // 🔥 tambahkan total siswa per kelas foreach ($tugas as $t) { $t->total_siswa = Siswa::where('kelas_id', $t->kelas_id)->count(); } - return view('guru.tugas.index', compact('tugas')); + // Ambil semua pengumpulan dari semua tugas guru ini + $tugasIds = $tugas->pluck('id'); + + $rekapNilai = \App\Models\PengumpulanTugas::with('siswa') + ->whereIn('tugas_id', $tugasIds) + ->whereNotNull('nilai') + ->get() + ->groupBy('siswa_id') + ->map(function($kumpulan) { + $siswa = $kumpulan->first()->siswa; + $nilaiList = $kumpulan->pluck('nilai')->map(fn($n) => (float)$n); + $rata = round($nilaiList->avg(), 1); + return [ + 'nama' => $siswa->nama ?? '-', + 'inisial' => strtoupper(substr($siswa->nama ?? 'S', 0, 1)), + 'jumlah_tugas' => $kumpulan->count(), + 'rata_rata' => $rata, + ]; + }) + ->values(); + + return view('guru.tugas.index', compact('tugas', 'rekapNilai')); } public function create() @@ -145,15 +165,19 @@ public function pengumpulan($id) foreach ($tugas->pengumpulan as $p) { $waktu = Carbon::parse($p->created_at); - - if ($waktu->gt($deadline)) { - $p->status = 'telat'; - } else { - $p->status = 'tepat'; - } + $p->status = $waktu->gt($deadline) ? 'telat' : 'tepat'; } - return view('guru.tugas.pengumpulan', compact('tugas')); + // Siapkan data untuk JS rata-rata + $pengumpulanJson = $tugas->pengumpulan->map(function($p) { + return [ + 'nama' => $p->siswa->nama ?? '-', + 'inisial' => strtoupper(substr($p->siswa->nama ?? 'S', 0, 1)), + 'nilai' => $p->nilai, + ]; + }); + + return view('guru.tugas.pengumpulan', compact('tugas', 'pengumpulanJson')); } public function nilai(Request $request, $id) diff --git a/app/Http/Controllers/guru/UjianController.php b/app/Http/Controllers/guru/UjianController.php index ad818d4..dba7df5 100644 --- a/app/Http/Controllers/guru/UjianController.php +++ b/app/Http/Controllers/guru/UjianController.php @@ -6,92 +6,169 @@ use Illuminate\Http\Request; use App\Models\Ujian; use App\Models\Soal; +use App\Models\Hasil; +use App\Models\Kelas; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; + class UjianController extends Controller { - public function index() + // ── INDEX: daftar ujian + filter kelas untuk lihat nilai ────────── + public function index(Request $request) { - $data = Ujian::where('guru_id', auth()->id())->latest()->get(); - return view('guru.ujian.index', compact('data')); - } + $data = Ujian::where('guru_id', auth()->id())->latest()->get(); + $kelasList = Kelas::all(); + $selectedKelas = $request->kelas_id; - public function create() - { - $user = Auth::user(); - - // safety check - if (!$user) { - abort(403, 'Belum login'); + // Nilai siswa: tampilkan kalau ada filter kelas + $nilaiSiswa = collect(); + if ($selectedKelas) { + $nilaiSiswa = Hasil::with(['siswa', 'ujian']) + ->whereHas('siswa', fn($q) => $q->where('kelas_id', $selectedKelas)) + ->whereHas('ujian', fn($q) => $q->where('guru_id', auth()->id())) + ->get(); } - $mapel = $user->mapel; // ✔ relasi sudah benar - - return view('guru.ujian.create', compact('mapel')); + return view('guru.ujian.index', compact('data', 'kelasList', 'selectedKelas', 'nilaiSiswa')); } + // ── CREATE ──────────────────────────────────────────────────────── + public function create() + { + $user = Auth::user(); + if (!$user) abort(403, 'Belum login'); + + $kelas = Kelas::all(); + $guru = $user->guru; // relasi user -> guru + $mapel = $guru ? \App\Models\MataPelajaran::where('id', $guru->mapel_id)->get() : collect(); + + return view('guru.ujian.create', compact('kelas', 'mapel')); + } + + // ── STORE ───────────────────────────────────────────────────────── public function store(Request $request) { $request->validate([ - 'judul' => 'required', - 'soal' => 'required|array' + 'judul' => 'required', + 'durasi' => 'required|integer', + 'mulai' => 'required|date', + 'selesai' => 'required|date|after:mulai', + 'kelas_ids'=> 'required|array|min:1', + 'mapel_id' => 'required', + 'jenis' => 'required|in:UTS,UAS', + 'soal' => 'required|array|min:1', + 'soal.*.pertanyaan' => 'required', + 'soal.*.a' => 'required', + 'soal.*.b' => 'required', + 'soal.*.c' => 'required', + 'soal.*.d' => 'required', + 'soal.*.jawaban' => 'required|in:a,b,c,d', ]); - // 🔥 SIMPAN UJIAN - $ujian = Ujian::create([ - 'judul' => $request->judul, - 'durasi' => $request->durasi, - 'mulai' => $request->mulai, - 'selesai' => $request->selesai, - 'guru_id' => auth()->user()->guru_id - ]); - - // 🔥 SIMPAN SOAL - foreach ($request->soal as $s) { - Soal::create([ - 'ujian_id' => $ujian->id, - 'pertanyaan' => $s['pertanyaan'], - 'a' => $s['a'], - 'b' => $s['b'], - 'c' => $s['c'], - 'd' => $s['d'], - 'jawaban_benar' => $s['jawaban'], + DB::beginTransaction(); + try { + $ujian = Ujian::create([ + 'judul' => $request->judul, + 'durasi' => $request->durasi, + 'mulai' => $request->mulai, + 'selesai' => $request->selesai, + 'guru_id' => auth()->id(), + 'mapel_id' => $request->mapel_id, + 'jenis' => $request->jenis, + 'status_kirim' => 'draft', ]); - } - return redirect()->route('guru.ujian.index') - ->with('success', 'Ujian + soal berhasil dibuat'); + // Simpan pivot kelas (multi) + $ujian->kelas()->sync($request->kelas_ids); + + // Simpan soal + foreach ($request->soal as $s) { + Soal::create([ + 'ujian_id' => $ujian->id, + 'pertanyaan' => $s['pertanyaan'], + 'a' => $s['a'], + 'b' => $s['b'], + 'c' => $s['c'], + 'd' => $s['d'], + 'jawaban_benar' => $s['jawaban'], + ]); + } + + DB::commit(); + return redirect()->route('guru.ujian.index') + ->with('success', 'Ujian berhasil dibuat. Kirim ke siswa saat siap.'); + + } catch (\Exception $e) { + DB::rollBack(); + return back()->with('error', 'Gagal: ' . $e->getMessage()); + } } + // ── KIRIM ke semua kelas (ubah status_kirim → terkirim) ────────── + public function kirim($id) + { + $ujian = Ujian::where('guru_id', auth()->id())->findOrFail($id); + $ujian->update(['status_kirim' => 'terkirim']); + + return redirect()->route('guru.ujian.index') + ->with('success', 'Ujian "' . $ujian->judul . '" berhasil dikirim ke siswa!'); + } + + // ── EDIT ───────────────────────────────────────────────────────── + public function edit($id) + { + $ujian = Ujian::where('guru_id', auth()->id())->with('kelas')->findOrFail($id); + $kelas = Kelas::all(); + $mapel = \App\Models\MataPelajaran::all(); + $selectedKelas = $ujian->kelas->pluck('id')->toArray(); + + return view('guru.ujian.edit', compact('ujian', 'kelas', 'mapel', 'selectedKelas')); + } + + // ── UPDATE ──────────────────────────────────────────────────────── + public function update(Request $request, $id) + { + $request->validate([ + 'judul' => 'required', + 'durasi' => 'required|integer', + 'mulai' => 'required|date', + 'selesai' => 'required|date|after:mulai', + 'kelas_ids' => 'required|array|min:1', + 'mapel_id' => 'required', + 'jenis' => 'required|in:UTS,UAS', + ]); + + $ujian = Ujian::where('guru_id', auth()->id())->findOrFail($id); + $ujian->update([ + 'judul' => $request->judul, + 'durasi' => $request->durasi, + 'mulai' => $request->mulai, + 'selesai' => $request->selesai, + 'mapel_id' => $request->mapel_id, + 'jenis' => $request->jenis, + ]); + $ujian->kelas()->sync($request->kelas_ids); + + return redirect()->route('guru.ujian.index') + ->with('success', 'Ujian berhasil diupdate'); + } + + // ── DESTROY ─────────────────────────────────────────────────────── + public function destroy($id) + { + $ujian = Ujian::where('guru_id', auth()->id())->findOrFail($id); + Soal::where('ujian_id', $ujian->id)->delete(); + $ujian->kelas()->detach(); + $ujian->delete(); + + return redirect()->route('guru.ujian.index') + ->with('success', 'Ujian berhasil dihapus'); + } + + // ── SOAL (opsional, halaman terpisah) ──────────────────────────── public function soal($id) { $ujian = Ujian::findOrFail($id); return view('guru.ujian.soal', compact('ujian')); } - - public function storeSoal(Request $request, $id) - { - $request->validate([ - 'soal' => 'required|array', - 'soal.*.pertanyaan' => 'required|string', - 'soal.*.a' => 'required|string', - 'soal.*.b' => 'required|string', - 'soal.*.c' => 'required|string', - 'soal.*.d' => 'required|string', - 'soal.*.jawaban' => 'required|in:a,b,c,d', - ]); - - foreach ($request->soal as $s) { - Soal::create([ - 'ujian_id' => $id, - 'pertanyaan' => $s['pertanyaan'], - 'a' => $s['a'], - 'b' => $s['b'], - 'c' => $s['c'], - 'd' => $s['d'], - 'jawaban_benar' => $s['jawaban'], - ]); - } - - return back()->with('success','Soal disimpan'); - } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/siswa/DashboardController.php b/app/Http/Controllers/siswa/DashboardController.php index 4aa3401..e0afd3d 100644 --- a/app/Http/Controllers/siswa/DashboardController.php +++ b/app/Http/Controllers/siswa/DashboardController.php @@ -2,12 +2,44 @@ namespace App\Http\Controllers\Siswa; +use App\Models\Nilai; +use App\Models\AbsensiDetail; use App\Http\Controllers\Controller; class DashboardController extends Controller { - public function index() + + public function index() { - return view('siswa.dashboard'); + $user = auth()->user(); + + $nilai = \App\Models\Nilai::where('siswa_id', $user->siswa_id)->get(); + + $rataNilai = $nilai->avg('nilai'); + + $nilaiTerbaru = \App\Models\Nilai::with('mapel') + ->where('siswa_id', $user->siswa_id) + ->latest() + ->limit(5) + ->get(); + + $absensiTerakhir = \App\Models\AbsensiDetail::where('siswa_id', $user->siswa_id) + ->latest() + ->limit(5) + ->get(); + + $hadir = \App\Models\AbsensiDetail::where('siswa_id', $user->siswa_id) + ->where('status', 'hadir') + ->count(); + + $tugas = 0; + + return view('siswa.dashboard', compact( + 'rataNilai', + 'nilaiTerbaru', + 'absensiTerakhir', + 'hadir', + 'tugas' + )); } } \ No newline at end of file diff --git a/app/Http/Controllers/siswa/NilaiController.php b/app/Http/Controllers/siswa/NilaiController.php index ded7d09..98cf22a 100644 --- a/app/Http/Controllers/siswa/NilaiController.php +++ b/app/Http/Controllers/siswa/NilaiController.php @@ -9,13 +9,86 @@ class NilaiController extends Controller { public function index() - { - $user = Auth::user(); +{ + $siswa_id = auth()->user()->siswa_id; - $nilai = Nilai::with(['mapel']) - ->where('siswa_id', $user->siswa_id) - ->get(); + // 🔹 Ambil nilai tugas + $tugas = \App\Models\PengumpulanTugas::with('tugas.mapel') + ->where('siswa_id', $siswa_id) + ->get(); - return view('siswa.nilai.index', compact('nilai')); + // 🔹 Ambil nilai ujian + $ujian = \App\Models\Hasil::with('ujian.mapel') + ->where('siswa_id', $siswa_id) + ->get(); + + // 🔥 REKAP DATA + $data = []; + + // ======================= + // 🔹 PROSES NILAI TUGAS + // ======================= + foreach ($tugas as $t) { + $mapel_id = $t->tugas->mapel->id ?? null; + + if (!$mapel_id) continue; + + if (!isset($data[$mapel_id])) { + $data[$mapel_id] = [ + 'mapel' => $t->tugas->mapel->nama_mapel, + 'tugas' => [], + 'uts' => null, + 'uas' => null, + ]; + } + + $data[$mapel_id]['tugas'][] = $t->nilai; } + + // ======================= + // 🔹 PROSES NILAI UJIAN + // ======================= + foreach ($ujian as $u) { + $mapel_id = $u->ujian->mapel->id ?? null; + + if (!$mapel_id) continue; + + if (!isset($data[$mapel_id])) { + $data[$mapel_id] = [ + 'mapel' => $u->ujian->mapel->nama_mapel, + 'tugas' => [], + 'uts' => null, + 'uas' => null, + ]; + } + + if ($u->ujian->jenis == 'UTS') { + $data[$mapel_id]['uts'] = $u->nilai; + } + + if ($u->ujian->jenis == 'UAS') { + $data[$mapel_id]['uas'] = $u->nilai; + } + } + + // ======================= + // 🔹 HITUNG RATA-RATA + // ======================= + foreach ($data as $k => $d) { + + $rata_tugas = count($d['tugas']) + ? array_sum($d['tugas']) / count($d['tugas']) + : 0; + + $uts = $d['uts'] ?? 0; + $uas = $d['uas'] ?? 0; + + $rata = ($rata_tugas + $uts + $uas) / 3; + + $data[$k]['tugas'] = round($rata_tugas); + $data[$k]['rata'] = round($rata); + } + + return view('siswa.nilai.index', compact('data')); +} } \ No newline at end of file diff --git a/app/Http/Controllers/siswa/UjianController.php b/app/Http/Controllers/siswa/UjianController.php index 6c27097..45adbfe 100644 --- a/app/Http/Controllers/siswa/UjianController.php +++ b/app/Http/Controllers/siswa/UjianController.php @@ -6,47 +6,84 @@ use Illuminate\Http\Request; use App\Models\Ujian; use App\Models\Soal; -use App\Models\Jawaban; use App\Models\Hasil; class UjianController extends Controller { - public function index() - { - $ujians = Ujian::all(); - return view('siswa.ujian.index', compact('ujians')); - } + public function index() +{ + $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() + ->get(); + + $sudahDikerjakan = Hasil::where('siswa_id', $siswa->id) + ->pluck('ujian_id')->toArray(); + + $hasilSiswa = Hasil::where('siswa_id', $siswa->id) + ->get()->keyBy('ujian_id'); + + return view('siswa.ujian.index', compact('ujians', 'sudahDikerjakan', 'hasilSiswa')); +} 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); $soals = Soal::where('ujian_id', $id)->get(); - return view('siswa.ujian.kerjakan', compact('ujian','soals')); + return view('siswa.ujian.kerjakan', compact('ujian', 'soals')); } - public function submit(Request $request, $id) -{ - $soals = Soal::where('ujian_id', $id)->get(); - $benar = 0; - $total = $soals->count(); + public function submit(Request $request, $id) + { + $siswa = auth()->user()->siswa; - foreach ($soals as $s) { - $jawabanUser = $request->jawaban[$s->id] ?? null; + // Cegah submit ulang + $sudah = Hasil::where('siswa_id', $siswa->id) + ->where('ujian_id', $id) + ->exists(); - if ($jawabanUser == $s->jawaban_benar) { - $benar++; + 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')); } - - $nilai = ($benar / $total) * 100; - - return view('siswa.ujian.hasil', [ - 'nilai' => $nilai, - 'benar' => $benar, - 'total' => $total - ]); -} - - -} +} \ No newline at end of file diff --git a/app/Http/Middleware/CekStatusUser.php b/app/Http/Middleware/CekStatusUser.php new file mode 100644 index 0000000..cba4b45 --- /dev/null +++ b/app/Http/Middleware/CekStatusUser.php @@ -0,0 +1,23 @@ +user(); + + if ($user && $user->guru && $user->guru->status !== 'aktif') { + auth()->logout(); + + return redirect('/login') + ->with('error', 'Akun anda dinonaktifkan'); + } + + return $next($request); + } +} diff --git a/app/Models/AbsensiDetail.php b/app/Models/AbsensiDetail.php index c2e71fa..0aea7aa 100644 --- a/app/Models/AbsensiDetail.php +++ b/app/Models/AbsensiDetail.php @@ -11,12 +11,13 @@ class AbsensiDetail extends Model protected $table = 'absensi_detail'; - protected $fillable = [ - 'absensi_id', // 🔥 WAJIB - 'siswa_id', // 🔥 WAJIB - 'status', // 🔥 hadir / izin / alpha - 'waktu_absen' // 🔥 timestamp - ]; + protected $fillable = [ + 'absensi_id', + 'siswa_id', + 'status', + 'keterangan', // 🔥 TAMBAHKAN INI + 'waktu_absen' +]; public function absensi() { diff --git a/app/Models/Hasil.php b/app/Models/Hasil.php index bbdcb10..c45062d 100644 --- a/app/Models/Hasil.php +++ b/app/Models/Hasil.php @@ -2,15 +2,19 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Hasil extends Model { - protected $fillable = ['user_id','ujian_id','nilai']; + protected $fillable = ['siswa_id', 'ujian_id', 'nilai']; public function ujian() { - return $this->belongsTo(Ujian::class); + return $this->belongsTo(\App\Models\Ujian::class, 'ujian_id'); } -} + + public function siswa() + { + return $this->belongsTo(\App\Models\Siswa::class, 'siswa_id'); + } +} \ No newline at end of file diff --git a/app/Models/Nilai.php b/app/Models/Nilai.php index 828a5dd..773aec9 100644 --- a/app/Models/Nilai.php +++ b/app/Models/Nilai.php @@ -8,15 +8,42 @@ class Nilai extends Model { protected $table = 'nilai'; - protected $fillable = ['siswa_id', 'mapel_id', 'nilai']; + protected $fillable = [ + 'siswa_id', + 'mapel_id', + + // 🔥 lama (jangan dihapus dulu) + 'nilai', + + // 🔥 baru + 'nilai_tugas', + 'nilai_uts', + 'nilai_uas' + ]; public function siswa() -{ - return $this->belongsTo(\App\Models\Siswa::class); -} + { + return $this->belongsTo(\App\Models\Siswa::class); + } public function mapel() { - return $this->belongsTo(MataPelajaran::class); + return $this->belongsTo(MataPelajaran::class, 'mapel_id'); + } + + // 🔥 helper biar fleksibel (INI KUNCI AMAN) + public function getNilaiAkhirAttribute() + { + // kalau pakai sistem baru + if ($this->nilai_tugas || $this->nilai_uts || $this->nilai_uas) { + return collect([ + $this->nilai_tugas, + $this->nilai_uts, + $this->nilai_uas + ])->filter()->avg(); + } + + // fallback ke sistem lama + return $this->nilai; } } \ No newline at end of file diff --git a/app/Models/PengumpulanTugas.php b/app/Models/PengumpulanTugas.php index e2550a3..831ba49 100644 --- a/app/Models/PengumpulanTugas.php +++ b/app/Models/PengumpulanTugas.php @@ -26,6 +26,6 @@ public function siswa() public function tugas() { - return $this->belongsTo(Tugas::class); + return $this->belongsTo(\App\Models\Tugas::class, 'tugas_id'); } } \ No newline at end of file diff --git a/app/Models/Tugas.php b/app/Models/Tugas.php index 1d9074a..5957f75 100644 --- a/app/Models/Tugas.php +++ b/app/Models/Tugas.php @@ -28,7 +28,7 @@ public function kelas() public function mapel() { - return $this->belongsTo(MataPelajaran::class, 'mapel_id'); + return $this->belongsTo(\App\Models\MataPelajaran::class, 'mapel_id'); } public function guru() diff --git a/app/Models/Ujian.php b/app/Models/Ujian.php index 8ee40eb..c290030 100644 --- a/app/Models/Ujian.php +++ b/app/Models/Ujian.php @@ -2,19 +2,43 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Ujian extends Model -{ protected $fillable = ['judul','durasi','mulai','selesai','guru_id']; +{ + protected $fillable = [ + 'judul', 'durasi', 'mulai', 'selesai', + 'guru_id', 'mapel_id', 'jenis', 'status_kirim', + ]; - public function soals() + // ── Relasi ke banyak kelas (pivot ujian_kelas) ───────────────────── + // Satu ujian bisa dikirim ke banyak kelas + public function kelas() { - return $this->hasMany(Soal::class); + return $this->belongsToMany(Kelas::class, 'ujian_kelas', 'ujian_id', 'kelas_id'); } + // ── Relasi ke soal ──────────────────────────────────────────────── + public function soals() + { + return $this->hasMany(Soal::class, 'ujian_id'); + } + + // ── Relasi ke guru ──────────────────────────────────────────────── public function guru() { return $this->belongsTo(User::class, 'guru_id'); } -} + + // ── Relasi ke mapel ─────────────────────────────────────────────── + public function mapel() + { + return $this->belongsTo(MataPelajaran::class, 'mapel_id'); + } + + // ── Relasi ke hasil (nilai) ─────────────────────────────────────── + public function hasils() + { + return $this->hasMany(Hasil::class, 'ujian_id'); + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 4e20a12..cb7d65a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -94,6 +94,8 @@ public function mapel() return $this->belongsTo(MataPelajaran::class, 'mapel_id'); } + + /* |-------------------------------------------------------------------------- | 🔥 HELPER TAMBAHAN (OPSIONAL TAPI BAGUS) diff --git a/app/Services/OtpService.php b/app/Services/OtpService.php index d31dd63..c780ae3 100644 --- a/app/Services/OtpService.php +++ b/app/Services/OtpService.php @@ -9,64 +9,70 @@ class OtpService { - public function send(User $user) -{ - $otp = rand(100000, 999999); + /** + * Kirim OTP ke email SAJA atau WA SAJA, tidak keduanya. + * $method = 'email' | 'wa' + */ + public function send(User $user, string $method = 'email') + { + $otp = rand(100000, 999999); - // simpan OTP - $user->update([ - 'otp' => $otp, - 'otp_expired_at' => now()->addMinutes(5) - ]); - - // ================= EMAIL ================= - \Mail::raw("Kode OTP kamu: $otp", function ($msg) use ($user) { - $msg->to($user->email) - ->subject('Kode OTP Login'); - }); - - // ================= AMBIL TELEPON ================= - $telepon = null; - - if ($user->siswa && $user->siswa->telepon) { - $telepon = $user->siswa->telepon; - } elseif ($user->guru && $user->guru->telepon) { - $telepon = $user->guru->telepon; - } elseif ($user->telepon) { - $telepon = $user->telepon; - } - - // ================= KIRIM WA ================= - if ($telepon) { - $this->sendWA($telepon, $otp); - } else { - \Log::error('NO HP TIDAK ADA UNTUK USER', [ - 'user_id' => $user->id, - 'role' => $user->role + // Simpan OTP ke database + $user->update([ + 'otp' => $otp, + 'otp_expired_at' => now()->addMinutes(5), ]); + + if ($method === 'email') { + // ── KIRIM KE EMAIL SAJA ────────────────────── + \Mail::raw( + "Kode OTP kamu: {$otp}\nBerlaku 5 menit. Jangan bagikan ke siapapun.", + function ($msg) use ($user) { + $msg->to($user->email)->subject('Kode OTP Login'); + } + ); + + } else { + // ── KIRIM KE WA SAJA ───────────────────────── + $telepon = null; + + if ($user->siswa && $user->siswa->telepon) { + $telepon = $user->siswa->telepon; + } elseif ($user->guru && $user->guru->telepon) { + $telepon = $user->guru->telepon; + } elseif ($user->telepon) { + $telepon = $user->telepon; + } + + if ($telepon) { + $this->sendWA($telepon, $otp); + } else { + Log::error('NO HP TIDAK ADA UNTUK USER', [ + 'user_id' => $user->id, + 'role' => $user->role, + ]); + } + } } -} private function sendWA($no, $otp) { $no = preg_replace('/[^0-9]/', '', $no); - if (substr($no, 0, 1) === '0') { + if (str_starts_with($no, '0')) { $no = '62' . substr($no, 1); } - $token = env('FONNTE_TOKEN'); - $response = Http::asForm()->withHeaders([ - 'Authorization' => $token + 'Authorization' => env('FONNTE_TOKEN'), ])->post('https://api.fonnte.com/send', [ - 'target' => $no, - 'message' => "Kode OTP kamu: $otp" + 'target' => $no, + 'message' => "🔐 Kode OTP kamu: *{$otp}*\nBerlaku 5 menit. Jangan bagikan ke siapapun.", ]); Log::info('WA RESULT', [ - 'nomor' => $no, - 'response' => $response->json() + 'nomor' => $no, + 'response' => $response->json(), ]); } } \ No newline at end of file diff --git a/bootstrap/app.php b/bootstrap/app.php index 7bd8fc8..84c2c08 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,11 +11,12 @@ commands: __DIR__.'/../routes/console.php', health: '/up', ) - ->withMiddleware(function ($middleware) { - $middleware->alias([ - 'force.password' => ForcePasswordChange::class, - ]); -}) + ->withMiddleware(function ($middleware) { + $middleware->alias([ + 'force.password' => \App\Http\Middleware\ForcePasswordChange::class, + 'cek.status' => \App\Http\Middleware\CekStatusUser::class, + ]); + }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/database/migrations/2026_05_05_133411_add_kategori_nilai_to_nilai_table.php b/database/migrations/2026_05_05_133411_add_kategori_nilai_to_nilai_table.php new file mode 100644 index 0000000..51c277a --- /dev/null +++ b/database/migrations/2026_05_05_133411_add_kategori_nilai_to_nilai_table.php @@ -0,0 +1,33 @@ +integer('nilai_tugas')->nullable(); + $table->integer('nilai_uts')->nullable(); + $table->integer('nilai_uas')->nullable(); + + // optional: hapus kolom lama + $table->dropColumn('nilai'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('nilai', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/2026_05_07_201446_add_fields_to_ujians_and_create_ujian_kelas.php b/database/migrations/2026_05_07_201446_add_fields_to_ujians_and_create_ujian_kelas.php new file mode 100644 index 0000000..655d172 --- /dev/null +++ b/database/migrations/2026_05_07_201446_add_fields_to_ujians_and_create_ujian_kelas.php @@ -0,0 +1,53 @@ +unsignedBigInteger('mapel_id')->nullable()->after('guru_id'); + } + // jenis: UTS atau UAS + if (!Schema::hasColumn('ujians', 'jenis')) { + $table->enum('jenis', ['UTS', 'UAS'])->default('UTS')->after('mapel_id'); + } + // status_kirim: draft = belum dikirim ke siswa, terkirim = sudah dikirim + if (!Schema::hasColumn('ujians', 'status_kirim')) { + $table->enum('status_kirim', ['draft', 'terkirim'])->default('draft')->after('jenis'); + } + }); + + // ── 2. Buat tabel pivot ujian_kelas ────────────────────────────── + // Satu ujian bisa dikirim ke BANYAK kelas sekaligus + if (!Schema::hasTable('ujian_kelas')) { + Schema::create('ujian_kelas', function (Blueprint $table) { + $table->id(); + $table->unsignedBigInteger('ujian_id'); + $table->unsignedBigInteger('kelas_id'); + $table->timestamps(); + + // Satu ujian tidak boleh duplikat ke kelas yang sama + $table->unique(['ujian_id', 'kelas_id']); + + $table->foreign('ujian_id')->references('id')->on('ujians')->onDelete('cascade'); + $table->foreign('kelas_id')->references('id')->on('kelas')->onDelete('cascade'); + }); + } + } + + public function down(): void + { + Schema::table('ujians', function (Blueprint $table) { + $table->dropColumn(['mapel_id', 'jenis', 'status_kirim']); + }); + + Schema::dropIfExists('ujian_kelas'); + } +}; \ No newline at end of file diff --git a/public/uploads/1778034403.png b/public/uploads/1778034403.png new file mode 100644 index 0000000..dd7fa74 Binary files /dev/null and b/public/uploads/1778034403.png differ diff --git a/public/uploads/1778037578.png b/public/uploads/1778037578.png new file mode 100644 index 0000000..bc55821 Binary files /dev/null and b/public/uploads/1778037578.png differ diff --git a/public/uploads/1778037593.jpg b/public/uploads/1778037593.jpg new file mode 100644 index 0000000..f8931f3 Binary files /dev/null and b/public/uploads/1778037593.jpg differ diff --git a/public/uploads/1778041028.png b/public/uploads/1778041028.png new file mode 100644 index 0000000..bc4b0b9 Binary files /dev/null and b/public/uploads/1778041028.png differ diff --git a/public/uploads/1778041039.PNG b/public/uploads/1778041039.PNG new file mode 100644 index 0000000..c773722 Binary files /dev/null and b/public/uploads/1778041039.PNG differ diff --git a/public/uploads/1778167699.png b/public/uploads/1778167699.png new file mode 100644 index 0000000..9370fbf Binary files /dev/null and b/public/uploads/1778167699.png differ diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index c24cb80..03ed3c3 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -1,80 +1,666 @@ @extends('layouts.app') @section('content') -
Visualisasi Data
+Data Terkini
+Daftarkan guru baru beserta akun login ke sistem
Perbarui informasi data guru yang sudah terdaftar
+Kelola seluruh data guru aktif dan nonaktif
+| No | -Nama | -NIP | -Mapel | -Alamat | -Telepon | -Status | -Aksi | -|
|---|---|---|---|---|---|---|---|---|
| {{ $loop->iteration }} | - -- {{ $g->nama }} - | - -{{ $g->nip }} | - -- - {{ $g->mapel->nama_mapel ?? '-' }} - - | - -- {{ $g->user->email ?? '-' }} - | - -- {{ $g->alamat }} - | - -{{ $g->telepon }} | - -- @if($g->status == 'aktif') - - Aktif - - @else - - Nonaktif - - @endif - | - -
-
-
-
- Edit
-
-
-
-
- @if($g->status == 'aktif')
-
- Nonaktifkan
-
- @else
-
- Aktifkan
-
- @endif
-
-
- |
-
| - Belum ada data guru - | -||||||||
| No | +Nama Guru | +NIP | +Mata Pelajaran | +Telepon | +Status | +Aksi | +|
|---|---|---|---|---|---|---|---|
| {{ $loop->iteration }} | +
+ {{ $g->nama }}
+ @if($g->alamat)
+ {{ Str::limit($g->alamat, 40) }}
+ @endif
+ |
+ {{ $g->nip ?: '—' }} | ++ {{ $g->mapel->nama_mapel ?? '—' }} + | +{{ $g->user->email ?? '—' }} | +{{ $g->telepon ?: '—' }} | ++ @if($g->status == 'aktif') + Aktif + @else + Nonaktif + @endif + | +
+
+
+
+ Edit
+
+
+
+
+ @if($g->status == 'aktif')
+
+
+ Nonaktifkan
+
+ @else
+
+
+ Aktifkan
+
+ @endif
+
+ |
+
|
+
+
+
+ Belum ada data guru
+ Mulai tambahkan data guru menggunakan tombol di atas.
+ |
+ |||||||
Isi semua informasi jadwal pelajaran
+Perubahan akan langsung tersimpan ke sistem
+