perbaikan

perbaikan bismillah
This commit is contained in:
Ryfandii 2026-05-21 01:25:29 +07:00
parent 21f0229058
commit 7e3718f6a3
11 changed files with 1082 additions and 457 deletions

View File

@ -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');
}
}

View File

@ -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]);
}

View File

@ -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);

View File

@ -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;
public function kerjakan($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);
// Cegah submit ulang
$sudah = Hasil::where('siswa_id', $siswa->id)
->where('ujian_id', $id)
->exists();
// ✅ FIX: parse eksplisit dengan Carbon
$mulai = $ujian->mulai ? \Carbon\Carbon::parse($ujian->mulai) : null;
$selesai = $ujian->selesai ? \Carbon\Carbon::parse($ujian->selesai) : null;
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'));
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 submit(Request $request, $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);
$selesai = $ujian->selesai ? \Carbon\Carbon::parse($ujian->selesai) : null;
// ✅ 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'));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

View File

@ -2,156 +2,345 @@
@section('content')
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--danger-light: #FEF2F2;
--warning: #D97706;
--warning-light: #FFFBEB;
--info: #0284C7;
--info-light: #F0F9FF;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 14px;
--shadow-sm: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
}
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--danger-light: #FEF2F2;
--warning: #D97706;
--warning-light: #FFFBEB;
--info: #0284C7;
--info-light: #F0F9FF;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 14px;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .04);
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
* {
font-family: 'Plus Jakarta Sans', sans-serif;
box-sizing: border-box;
}
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
.sw-page {
padding: 28px 32px;
background: var(--bg);
min-height: 100vh;
}
.sw-topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 24px; }
.sw-topbar-left h3 { font-size: 22px; font-weight: 700; color: var(--text-dark); margin: 0; letter-spacing: -.3px; }
.sw-topbar-left p { font-size: 13px; color: var(--text-soft); margin: 2px 0 0; }
.sw-topbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.sw-btn { display: inline-flex; align-items: center; gap: 7px; padding: 9px 18px; border-radius: var(--radius-md); font-size: 13.5px; font-weight: 600; border: none; cursor: pointer; text-decoration: none; transition: all .18s ease; line-height: 1; }
.sw-btn-primary { background: var(--primary); color: #fff; }
.sw-btn-primary:hover { background: var(--primary-dark); color: #fff; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(79,70,229,.35); }
.sw-btn-warning { background: var(--warning-light); color: var(--warning); border: 1px solid #FDE68A; }
.sw-btn-warning:hover { background: var(--warning); color: #fff; }
.sw-btn-danger { background: var(--danger-light); color: var(--danger); border: 1px solid #FECACA; }
.sw-btn-danger:hover { background: var(--danger); color: #fff; }
.sw-btn-sm { padding: 6px 12px; font-size: 12.5px; border-radius: 8px; }
.sw-topbar-left h3 {
font-size: 22px;
font-weight: 700;
color: var(--text-dark);
margin: 0;
letter-spacing: -.3px;
}
.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-topbar-left p {
font-size: 13px;
color: var(--text-soft);
margin: 2px 0 0;
}
.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; }
.sw-btn {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 9px 18px;
border-radius: var(--radius-md);
font-size: 13.5px;
font-weight: 600;
border: none;
cursor: pointer;
text-decoration: none;
transition: all .18s ease;
line-height: 1;
}
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-btn-primary {
background: var(--primary);
color: #fff;
}
.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-btn-primary:hover {
background: var(--primary-dark);
color: #fff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(79, 70, 229, .35);
}
.sw-action-cell { display: flex; gap: 6px; align-items: center; justify-content: center; }
.sw-btn-warning {
background: var(--warning-light);
color: var(--warning);
border: 1px solid #FDE68A;
}
.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-btn-warning:hover {
background: var(--warning);
color: #fff;
}
.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; }
</style>
.sw-btn-danger {
background: var(--danger-light);
color: var(--danger);
border: 1px solid #FECACA;
}
<div class="sw-page">
.sw-btn-danger:hover {
background: var(--danger);
color: #fff;
}
{{-- TOPBAR --}}
<div class="sw-topbar">
<div class="sw-topbar-left">
<h3>Data Kelas</h3>
<p>Pengelolaan data kelas SMA Negeri 1 Bondowoso</p>
.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;
}
</style>
<div class="sw-page">
{{-- TOPBAR --}}
<div class="sw-topbar">
<div class="sw-topbar-left">
<h3>Data Kelas</h3>
<p>Pengelolaan data kelas SMA Negeri 1 Bondowoso</p>
</div>
<a href="{{ route('admin.kelas.create') }}" class="sw-btn sw-btn-primary">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<path d="M12 5v14M5 12h14" />
</svg>
Tambah Kelas
</a>
</div>
<a href="{{ route('admin.kelas.create') }}" class="sw-btn sw-btn-primary">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M5 12h14"/></svg>
Tambah Kelas
</a>
</div>
{{-- ALERT --}}
@if(session('success'))
<div class="sw-alert">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
{{ session('success') }}
</div>
@endif
{{-- ALERT --}}
@if(session('success'))
<div class="sw-alert">
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
{{ session('success') }}
</div>
@endif
{{-- TABLE --}}
<div class="sw-card sw-table-card">
<div class="sw-table-wrap">
<table class="sw-table">
<thead>
<tr>
<th class="center" style="width:52px">No</th>
<th>Nama Kelas</th>
<th class="center">Jurusan</th>
<th class="center" style="width:160px">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($kelas as $k)
<tr>
<td class="center">
<span class="sw-no">{{ $loop->iteration }}</span>
</td>
<td class="name-cell">{{ $k->nama_kelas }}</td>
<td class="center">
<span class="sw-badge sw-badge-info">{{ $k->jurusan }}</span>
</td>
<td>
<div class="sw-action-cell">
<a href="{{ route('admin.kelas.edit', $k->id) }}"
class="sw-btn sw-btn-warning sw-btn-sm">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
Edit
</a>
<form action="{{ route('admin.kelas.destroy', $k->id) }}"
method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="button"
class="sw-btn sw-btn-danger sw-btn-sm"
onclick="return confirm('Yakin hapus data kelas ini?')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6M14 11v6"/></svg>
Hapus
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="4">
<div class="sw-empty">
<div class="sw-empty-icon">🏫</div>
<p>Data kelas belum tersedia</p>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
{{-- TABLE --}}
<div class="sw-card sw-table-card">
<div class="sw-table-wrap">
<table class="sw-table">
<thead>
<tr>
<th class="center" style="width:52px">No</th>
<th>Nama Kelas</th>
<th class="center">Jurusan</th>
<th class="center" style="width:160px">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($kelas as $k)
<tr>
<td class="center">
<span class="sw-no">{{ $loop->iteration }}</span>
</td>
<td class="name-cell">{{ $k->nama_kelas }}</td>
<td class="center">
<span class="sw-badge sw-badge-info">{{ $k->jurusan }}</span>
</td>
<td>
<div class="sw-action-cell">
<a href="{{ route('admin.kelas.edit', $k->id) }}"
class="sw-btn sw-btn-warning sw-btn-sm">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2.2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
</svg>
Edit
</a>
<form action="{{ route('admin.kelas.destroy', $k->id) }}" method="POST"
class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="sw-btn sw-btn-danger sw-btn-sm"
onclick="return confirm('Yakin hapus data kelas ini?')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2.2">
<polyline points="3 6 5 6 21 6" />
<path d="M19 6l-1 14H6L5 6" />
<path d="M10 11v6M14 11v6" />
</svg>
Hapus
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="4">
<div class="sw-empty">
<div class="sw-empty-icon">🏫</div>
<p>Data kelas belum tersedia</p>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -154,10 +154,10 @@ function dismissToast(){
<p class="page-subtitle">Rekap nilai tugas, UTS, UAS &amp; hitung rata-rata siswa</p>
</div>
</div>
<a href="{{ route('guru.nilai.input') }}" class="btn-primary">
<!-- <a href="{{ route('guru.nilai.input') }}" class="btn-primary">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4"/></svg>
Input Nilai Manual
</a>
</a> -->
</div>
{{-- INFO --}}
@ -293,20 +293,23 @@ function dismissToast(){
</div>
{{-- FOOTER + TOMBOL KIRIM SEMUA KELAS --}}
<div class="table-footer">
<span>{{ $nilai->count() }} data nilai</span>
<div style="display:flex;align-items:center;gap:10px;">
<span>{{ now()->format('d M Y') }}</span>
<form action="{{ route('guru.nilai.kirimKeSiswa') }}" method="POST" style="margin:0;">
@csrf
<button type="submit" class="btn-violet" style="padding:7px 14px;font-size:12.5px;"
onclick="return confirm('Kirim nilai ke SEMUA kelas? Siswa di semua kelas akan bisa melihat nilai mereka.')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/></svg>
Kirim ke Semua Kelas
</button>
</form>
</div>
</div>
<div class="table-footer">
<span>{{ $nilai->count() }} data nilai</span>
<div style="display:flex;align-items:center;gap:10px;">
<span>{{ now()->format('d M Y') }}</span>
{{-- Hanya tampil jika TIDAK ada filter kelas --}}
@if(!request('kelas_id'))
<form action="{{ route('guru.nilai.kirimKeSiswa') }}" method="POST" style="margin:0;">
@csrf
<button type="submit" class="btn-violet" style="padding:7px 14px;font-size:12.5px;"
onclick="return confirm('Kirim nilai ke SEMUA kelas? Siswa di semua kelas akan bisa melihat nilai mereka.')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/></svg>
Kirim ke Semua Kelas
</button>
</form>
@endif
</div>
</div>
</div>
</div>

View File

@ -429,7 +429,7 @@
<th class="center">Aksi</th>
</tr>
</thead>
<!-- <tbody>
<tbody>
@forelse($tugas as $t)
<tr>
<td><span class="judul-cell">{{ $t->judul }}</span></td>
@ -516,7 +516,7 @@
</td>
</tr>
@endforelse
</tbody> -->
</tbody>
</table>
</div>

View File

@ -2,219 +2,567 @@
@section('content')
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--primary-light: #EEF2FF;
--success: #059669;
--success-light: #ECFDF5;
--info: #0284C7;
--info-light: #F0F9FF;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 16px;
}
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--primary-light: #EEF2FF;
--success: #059669;
--success-light: #ECFDF5;
--info: #0284C7;
--info-light: #F0F9FF;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 16px;
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
#content { padding: 0 !important; background: var(--bg) !important; }
#content-wrapper { background: var(--bg) !important; }
.sw-page { padding: 28px 32px; background: var(--bg); min-height: calc(100vh - 70px); }
* {
font-family: 'Plus Jakarta Sans', sans-serif;
box-sizing: border-box;
}
.sw-topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 28px; flex-wrap: wrap; gap: 12px; }
.sw-topbar h3 { font-size: 21px; font-weight: 800; color: var(--text-dark); margin: 0 0 3px; letter-spacing: -.3px; }
.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); }
#content {
padding: 0 !important;
background: var(--bg) !important;
}
.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; }
#content-wrapper {
background: var(--bg) !important;
}
.sw-hero-card { position: relative; border-radius: 22px; overflow: hidden; margin-bottom: 24px; box-shadow: 0 12px 40px rgba(79,70,229,.22); }
.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-page {
padding: 28px 32px;
background: var(--bg);
min-height: calc(100vh - 70px);
}
.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-topbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 28px;
flex-wrap: wrap;
gap: 12px;
}
.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; }
.sw-topbar h3 {
font-size: 21px;
font-weight: 800;
color: var(--text-dark);
margin: 0 0 3px;
letter-spacing: -.3px;
}
@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; }
}
</style>
.sw-topbar p {
font-size: 13px;
color: var(--text-soft);
margin: 0;
}
<div class="sw-page">
.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 --}}
<div class="sw-topbar">
<div>
<h3>Ujian Tersedia</h3>
<p>Daftar ujian yang dapat Anda kerjakan sekarang</p>
</div>
<div class="sw-badge-ready">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Siap mengerjakan ujian hari ini?
</div>
</div>
.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'))
<div class="sw-alert-warning">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/>
</svg>
{{ session('warning') }}
</div>
@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%);
}
<div class="sw-hero-card" style="{{ $dikerjakan ? 'opacity:0.85;box-shadow:0 8px 24px rgba(0,0,0,.1)' : '' }}">
<div class="sw-hero-bg" style="{{ $dikerjakan ? 'background:linear-gradient(135deg,#374151 0%,#6B7280 100%)' : '' }}"></div>
<div class="sw-hero-blob1"></div>
<div class="sw-hero-blob2"></div>
<div class="sw-hero-inner">
.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;
}
}
</style>
<div class="sw-page">
{{-- TOPBAR --}}
<div class="sw-topbar">
<div>
{{-- EYEBROW --}}
<div class="sw-hero-eyebrow">
@if($dikerjakan)
<svg width="11" height="11" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Sudah Dikerjakan
@else
<svg width="11" height="11" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
Ujian Aktif
@endif
<h3>Ujian Tersedia</h3>
<p>Daftar ujian yang dapat Anda kerjakan sekarang</p>
</div>
<div class="sw-badge-ready">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Siap mengerjakan ujian hari ini?
</div>
</div>
{{-- ALERT WARNING --}}
@if(session('warning'))
<div class="sw-alert-warning">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
</svg>
{{ session('warning') }}
</div>
@endif
{{-- DAFTAR UJIAN --}}
@forelse($ujians as $u)
@php $dikerjakan = in_array($u->id, $sudahDikerjakan); @endphp
<div class="sw-hero-card" style="{{ $dikerjakan ? 'opacity:0.85;box-shadow:0 8px 24px rgba(0,0,0,.1)' : '' }}">
<div class="sw-hero-bg"
style="{{ $dikerjakan ? 'background:linear-gradient(135deg,#374151 0%,#6B7280 100%)' : '' }}"></div>
<div class="sw-hero-blob1"></div>
<div class="sw-hero-blob2"></div>
<div class="sw-hero-inner">
<div>
{{-- EYEBROW --}}
<div class="sw-hero-eyebrow">
@if($dikerjakan)
<svg width="11" height="11" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2.5">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Sudah Dikerjakan
@else
<svg width="11" height="11" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2.5">
<path
d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
Ujian Aktif
@endif
</div>
{{-- JUDUL --}}
<div class="sw-hero-title">{{ $u->judul }}</div>
{{-- CHIPS --}}
<div class="sw-hero-chips">
<span class="sw-hero-chip">
<svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2">
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
{{ $u->durasi }} menit
</span>
{{-- Ganti chip jenis yang ada di sw-hero-chips --}}
@if($u->jenis)
<span class="sw-hero-chip"
style="{{ $u->jenis == 'UTS' ? 'background:rgba(2,132,199,.25);border-color:rgba(2,132,199,.4);' : 'background:rgba(217,119,6,.25);border-color:rgba(217,119,6,.4);' }} font-weight:800;">
<svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
</svg>
{{ $u->jenis }} {{ $u->jenis == 'UTS' ? 'Ujian Tengah Semester' : 'Ujian Akhir Semester' }}
</span>
@endif
@if($u->mulai)
<span class="sw-hero-chip">
<svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2" />
<line x1="3" y1="10" x2="21" y2="10" />
</svg>
{{ $u->mulai }}
</span>
@endif
{{-- CHIP NILAI (muncul kalau sudah dikerjakan) --}}
@if($dikerjakan && isset($hasilSiswa[$u->id]))
<span class="sw-hero-chip" style="background:rgba(255,255,255,.3);font-weight:800;font-size:13px;">
<svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2">
<path
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10" />
</svg>
Nilai: {{ $hasilSiswa[$u->id]->nilai }}
</span>
@endif
</div>
{{-- TOMBOL --}}
@php $berakhir = in_array($u->id, $sudahBerakhir); @endphp
@if($dikerjakan || $berakhir)
<span class="sw-btn-done"
style="{{ $berakhir && !$dikerjakan ? 'background:rgba(220,38,38,.2);border-color:rgba(220,38,38,.3);' : '' }}">
@if($berakhir && !$dikerjakan)
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2.5">
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
Waktu Ujian Sudah Berakhir
@else
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2.5">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Sudah Selesai Dikerjakan
@endif
</span>
@else
<a href="{{ route('siswa.ujian.kerjakan', $u->id) }}" class="sw-btn-start">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2.5">
<polygon points="5 3 19 12 5 21 5 3" />
</svg>
Mulai Ujian Sekarang
</a>
@endif
</div>
{{-- DECO ICON --}}
<div class="sw-hero-deco">
@if($dikerjakan)
<svg width="44" height="44" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
@else
<svg width="44" height="44" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
</svg>
@endif
</div>
</div>
</div>
{{-- JUDUL --}}
<div class="sw-hero-title">{{ $u->judul }}</div>
{{-- CHIPS --}}
<div class="sw-hero-chips">
<span class="sw-hero-chip">
<svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
{{ $u->durasi }} menit
</span>
{{-- Ganti chip jenis yang ada di sw-hero-chips --}}
@if($u->jenis)
<span class="sw-hero-chip" style="{{ $u->jenis == 'UTS' ? 'background:rgba(2,132,199,.25);border-color:rgba(2,132,199,.4);' : 'background:rgba(217,119,6,.25);border-color:rgba(217,119,6,.4);' }} font-weight:800;">
<svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
{{ $u->jenis }} {{ $u->jenis == 'UTS' ? 'Ujian Tengah Semester' : 'Ujian Akhir Semester' }}
</span>
@endif
@if($u->mulai)
<span class="sw-hero-chip">
<svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
{{ $u->mulai }}
</span>
@endif
{{-- CHIP NILAI (muncul kalau sudah dikerjakan) --}}
@if($dikerjakan && isset($hasilSiswa[$u->id]))
<span class="sw-hero-chip" style="background:rgba(255,255,255,.3);font-weight:800;font-size:13px;">
<svg width="13" height="13" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10"/></svg>
Nilai: {{ $hasilSiswa[$u->id]->nilai }}
</span>
@endif
@empty
<div class="sw-empty">
<div class="sw-empty-ring">
<svg width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
</svg>
</div>
{{-- TOMBOL --}}
@if($dikerjakan)
<span class="sw-btn-done">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Sudah Selesai Dikerjakan
</span>
@else
<a href="{{ route('siswa.ujian.kerjakan', $u->id) }}" class="sw-btn-start">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><polygon points="5 3 19 12 5 21 5 3"/></svg>
Mulai Ujian Sekarang
</a>
@endif
<h5>Tidak Ada Ujian</h5>
<p>Saat ini belum ada ujian yang tersedia. Silakan cek kembali nanti.</p>
</div>
@endforelse
{{-- DECO ICON --}}
<div class="sw-hero-deco">
@if($dikerjakan)
<svg width="44" height="44" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
@else
<svg width="44" height="44" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
@endif
{{-- MINI INFO --}}
<div class="sw-stats-row">
<div class="sw-stat-card">
<div class="sw-stat-icon si-indigo">
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path d="M9 11l3 3L22 4" />
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
</svg>
</div>
<div>
<div class="sw-stat-label">Total Ujian</div>
<div class="sw-stat-value">{{ $ujians->count() }}</div>
</div>
</div>
<div class="sw-stat-card">
<div class="sw-stat-icon si-blue">
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
</div>
<div>
<div class="sw-stat-label">Sudah Dikerjakan</div>
<div class="sw-stat-value" style="color:var(--info);">{{ count($sudahDikerjakan) }}</div>
</div>
</div>
<div class="sw-stat-card">
<div class="sw-stat-icon si-green">
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path d="M5 12.55a11 11 0 0 1 14.08 0" />
<path d="M1.42 9a16 16 0 0 1 21.16 0" />
<path d="M8.53 16.11a6 6 0 0 1 6.95 0" />
<line x1="12" y1="20" x2="12.01" y2="20" />
</svg>
</div>
<div>
<div class="sw-stat-label">Mode</div>
<div class="sw-stat-value" style="font-size:16px;color:var(--success);">Online</div>
</div>
</div>
</div>
</div>
@empty
<div class="sw-empty">
<div class="sw-empty-ring">
<svg width="32" height="32" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
</div>
<h5>Tidak Ada Ujian</h5>
<p>Saat ini belum ada ujian yang tersedia. Silakan cek kembali nanti.</p>
</div>
@endforelse
{{-- MINI INFO --}}
<div class="sw-stats-row">
<div class="sw-stat-card">
<div class="sw-stat-icon si-indigo">
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
</div>
<div>
<div class="sw-stat-label">Total Ujian</div>
<div class="sw-stat-value">{{ $ujians->count() }}</div>
</div>
</div>
<div class="sw-stat-card">
<div class="sw-stat-icon si-blue">
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
</div>
<div>
<div class="sw-stat-label">Sudah Dikerjakan</div>
<div class="sw-stat-value" style="color:var(--info);">{{ count($sudahDikerjakan) }}</div>
</div>
</div>
<div class="sw-stat-card">
<div class="sw-stat-icon si-green">
<svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path d="M5 12.55a11 11 0 0 1 14.08 0"/><path d="M1.42 9a16 16 0 0 1 21.16 0"/><path d="M8.53 16.11a6 6 0 0 1 6.95 0"/><line x1="12" y1="20" x2="12.01" y2="20"/></svg>
</div>
<div>
<div class="sw-stat-label">Mode</div>
<div class="sw-stat-value" style="font-size:16px;color:var(--success);">Online</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -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');