Bismillah Lancar sidang

Bagian yang kurang
1. Bagian Tugas guru filter nilai rata rata
2. Nilai Belum Tervalidasi
3. Ganti Passwoard di profil
This commit is contained in:
Ryfandii 2026-05-07 22:34:42 +07:00
parent c0d7a893cf
commit 3bdc385a6d
79 changed files with 17085 additions and 3858 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CekStatusUser
{
public function handle(Request $request, Closure $next)
{
$user = auth()->user();
if ($user && $user->guru && $user->guru->status !== 'aktif') {
auth()->logout();
return redirect('/login')
->with('error', 'Akun anda dinonaktifkan');
}
return $next($request);
}
}

View File

@ -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()
{

View File

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

View File

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

View File

@ -26,6 +26,6 @@ public function siswa()
public function tugas()
{
return $this->belongsTo(Tugas::class);
return $this->belongsTo(\App\Models\Tugas::class, 'tugas_id');
}
}

View File

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

View File

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

View File

@ -94,6 +94,8 @@ public function mapel()
return $this->belongsTo(MataPelajaran::class, 'mapel_id');
}
/*
|--------------------------------------------------------------------------
| 🔥 HELPER TAMBAHAN (OPSIONAL TAPI BAGUS)

View File

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

View File

@ -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) {
//

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::table('nilai', function (Blueprint $table) {
$table->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) {
//
});
}
};

View File

@ -0,0 +1,53 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// ── 1. Tambah kolom ke tabel ujians ──────────────────────────────
Schema::table('ujians', function (Blueprint $table) {
// mapel_id: guru mengajar mapel apa
if (!Schema::hasColumn('ujians', 'mapel_id')) {
$table->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');
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -1,80 +1,666 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Dashboard Admin</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
:root {
--brand-primary: #1a56db;
--brand-primary-light: #e8f0fe;
--brand-success: #0d9488;
--brand-success-light: #ccfbf1;
--brand-warning: #d97706;
--brand-warning-light: #fef3c7;
--brand-danger: #dc2626;
--brand-danger-light: #fee2e2;
--surface-base: #ffffff;
--surface-page: #f4f6fa;
--surface-raised: #ffffff;
--border-soft: rgba(0,0,0,0.07);
--border-medium: rgba(0,0,0,0.12);
--text-primary: #111827;
--text-secondary: #6b7280;
--text-muted: #9ca3af;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--shadow-xs: 0 1px 2px rgba(0,0,0,0.04);
--shadow-sm: 0 1px 4px rgba(0,0,0,0.06), 0 2px 8px rgba(0,0,0,0.04);
--shadow-md: 0 4px 16px rgba(0,0,0,0.08), 0 1px 4px rgba(0,0,0,0.04);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
font-family: 'Plus Jakarta Sans', sans-serif;
}
body, .dashboard-wrap * {
}
.dashboard-wrap {
background: var(--surface-page);
min-height: 100vh;
padding: 28px 32px 48px;
}
/* ─── HEADER ─── */
.db-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28px;
}
.db-header-left .db-eyebrow {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--brand-primary);
margin-bottom: 4px;
}
.db-header-left h1 {
font-size: 26px;
font-weight: 700;
color: var(--text-primary);
margin: 0;
letter-spacing: -0.5px;
}
.db-user-badge {
display: flex;
align-items: center;
gap: 10px;
background: var(--surface-base);
border: 1px solid var(--border-soft);
border-radius: 40px;
padding: 6px 14px 6px 8px;
box-shadow: var(--shadow-xs);
}
.db-user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, #1a56db 0%, #6366f1 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
color: white;
letter-spacing: 0;
}
.db-user-name {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
}
/* ─── STAT CARDS ─── */
.db-stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: var(--surface-base);
border-radius: var(--radius-lg);
border: 1px solid var(--border-soft);
padding: 20px 22px;
box-shadow: var(--shadow-sm);
display: flex;
flex-direction: column;
gap: 14px;
position: relative;
overflow: hidden;
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.stat-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
.stat-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 3px;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.stat-card.primary::before { background: var(--brand-primary); }
.stat-card.success::before { background: var(--brand-success); }
.stat-card.warning::before { background: var(--brand-warning); }
.stat-card.danger::before { background: var(--brand-danger); }
.stat-card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.stat-card-label {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--text-secondary);
}
.stat-icon {
width: 38px;
height: 38px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.stat-icon.primary { background: var(--brand-primary-light); color: var(--brand-primary); }
.stat-icon.success { background: var(--brand-success-light); color: var(--brand-success); }
.stat-icon.warning { background: var(--brand-warning-light); color: var(--brand-warning); }
.stat-icon.danger { background: var(--brand-danger-light); color: var(--brand-danger); }
.stat-value {
font-size: 36px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -1.5px;
line-height: 1;
font-variant-numeric: tabular-nums;
font-family: 'DM Mono', monospace;
}
/* ─── SECTION LABEL ─── */
.section-label {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text-muted);
margin: 0 0 14px;
}
/* ─── CARD BASE ─── */
.db-card {
background: var(--surface-base);
border-radius: var(--radius-lg);
border: 1px solid var(--border-soft);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
.db-card-header {
padding: 16px 20px;
border-bottom: 1px solid var(--border-soft);
display: flex;
align-items: center;
justify-content: space-between;
}
.db-card-title {
font-size: 14px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.2px;
display: flex;
align-items: center;
gap: 8px;
}
.db-card-title .title-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.db-card-title .title-dot.primary { background: var(--brand-primary); }
.db-card-title .title-dot.teal { background: var(--brand-success); }
.db-card-badge {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 20px;
background: var(--surface-page);
color: var(--text-secondary);
border: 1px solid var(--border-soft);
}
.db-card-body {
padding: 20px;
}
/* ─── CHARTS GRID ─── */
.charts-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 24px;
}
/* ─── DATA LISTS ─── */
.data-lists-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 24px;
}
.data-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 11px 20px;
border-bottom: 1px solid var(--border-soft);
transition: background 0.15s ease;
}
.data-row:last-child { border-bottom: none; }
.data-row:hover { background: var(--surface-page); }
.data-row-left {
display: flex;
align-items: center;
gap: 10px;
}
.data-row-index {
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
font-family: 'DM Mono', monospace;
min-width: 18px;
}
.data-row-name {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
}
.data-row-sub {
font-size: 12px;
color: var(--text-muted);
background: var(--surface-page);
border: 1px solid var(--border-soft);
border-radius: 6px;
padding: 2px 8px;
}
.score-badge {
font-size: 13px;
font-weight: 700;
font-family: 'DM Mono', monospace;
color: #fff;
background: var(--brand-success);
border-radius: var(--radius-sm);
padding: 3px 10px;
letter-spacing: 0.5px;
}
.score-badge.high { background: #0d9488; }
.score-badge.mid { background: #d97706; }
.score-badge.low { background: #dc2626; }
/* ─── QUICK ACTIONS ─── */
.quick-actions {
display: flex;
align-items: center;
justify-content: space-between;
background: var(--surface-base);
border-radius: var(--radius-lg);
border: 1px solid var(--border-soft);
box-shadow: var(--shadow-sm);
padding: 18px 24px;
}
.qa-left h5 {
font-size: 15px;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 2px;
}
.qa-left small {
font-size: 12px;
color: var(--text-muted);
}
.qa-buttons {
display: flex;
gap: 10px;
}
.btn-qa {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 9px 16px;
border-radius: var(--radius-sm);
font-size: 13px;
font-weight: 600;
border: 1.5px solid;
cursor: pointer;
text-decoration: none;
transition: all 0.15s ease;
letter-spacing: 0.01em;
}
.btn-qa:hover { transform: translateY(-1px); filter: brightness(0.95); }
.btn-qa.primary {
background: var(--brand-primary);
border-color: var(--brand-primary);
color: #fff;
}
.btn-qa.success {
background: var(--brand-success);
border-color: var(--brand-success);
color: #fff;
}
.btn-qa.outline {
background: transparent;
border-color: var(--border-medium);
color: var(--text-primary);
}
/* ─── EMPTY STATE ─── */
.empty-state {
padding: 28px 20px;
text-align: center;
color: var(--text-muted);
font-size: 13px;
}
</style>
<div class="dashboard-wrap">
{{-- HEADER --}}
<div class="db-header">
<div class="db-header-left">
<div class="db-eyebrow">Panel Admin</div>
<h1>Dashboard</h1>
</div>
<div class="db-user-badge">
<div class="db-user-avatar">
{{ strtoupper(substr(auth()->user()->name, 0, 2)) }}
</div>
<span class="db-user-name">{{ auth()->user()->name }}</span>
</div>
</div>
{{-- STATISTIK --}}
<div class="row">
@php
$cards = [
['title' => 'Total Siswa', 'value' => $jumlahSiswa, 'color' => 'primary', 'icon' => 'fa-solid fa-user-graduate'],
['title' => 'Total Guru', 'value' => $jumlahGuru, 'color' => 'success', 'icon' => 'fa-solid fa-chalkboard-teacher'],
['title' => 'Total Kelas', 'value' => $jumlahKelas, 'color' => 'warning', 'icon' => 'fa-solid fa-school'],
['title' => 'Mata Pelajaran', 'value' => $jumlahMapel, 'color' => 'danger', 'icon' => 'fa-solid fa-book'],
];
@endphp
<div class="col-md-3 mb-3">
<div class="card shadow border-left-primary">
<div class="card-body">
<h6>Jumlah Siswa</h6>
<h4>{{ $jumlahSiswa }}</h4>
<div class="db-stats-grid">
@foreach($cards as $c)
<div class="stat-card {{ $c['color'] }}">
<div class="stat-card-top">
<span class="stat-card-label">{{ $c['title'] }}</span>
<div class="stat-icon {{ $c['color'] }}">
<i class="{{ $c['icon'] }}"></i>
</div>
</div>
<div class="stat-value">{{ number_format($c['value']) }}</div>
</div>
@endforeach
</div>
{{-- CHARTS --}}
<p class="section-label">Visualisasi Data</p>
<div class="charts-grid">
{{-- CHART SISWA PER KELAS --}}
<div class="db-card">
<div class="db-card-header">
<div class="db-card-title">
<span class="title-dot primary"></span>
Siswa per Kelas
</div>
<span class="db-card-badge">Bar chart</span>
</div>
<div class="db-card-body">
<div style="position: relative; height: 280px;">
<canvas id="chartKelas" role="img" aria-label="Bar chart jumlah siswa per kelas"></canvas>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card shadow border-left-success">
<div class="card-body">
<h6>Jumlah Guru</h6>
<h4>{{ $jumlahGuru }}</h4>
{{-- CHART DISTRIBUSI NILAI --}}
<div class="db-card">
<div class="db-card-header">
<div class="db-card-title">
<span class="title-dot teal"></span>
Distribusi Nilai
</div>
<span class="db-card-badge">Pie chart</span>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card shadow border-left-warning">
<div class="card-body">
<h6>Jumlah Kelas</h6>
<h4>{{ $jumlahKelas }}</h4>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card shadow border-left-danger">
<div class="card-body">
<h6>Mata Pelajaran</h6>
<h4>{{ $jumlahMapel }}</h4>
<div class="db-card-body">
<div style="position: relative; height: 280px;">
<canvas id="chartNilai" role="img" aria-label="Pie chart distribusi nilai siswa"></canvas>
</div>
</div>
</div>
</div>
{{-- SISWA TERBARU --}}
<div class="card mt-4">
<div class="card-body">
<h5>Siswa Terbaru</h5>
<ul>
@foreach($siswaTerbaru as $siswa)
<li>
{{ $siswa->nama }}
({{ $siswa->kelas->nama ?? '-' }})
</li>
@endforeach
</ul>
{{-- DATA LISTS --}}
<p class="section-label">Data Terkini</p>
<div class="data-lists-grid">
{{-- SISWA TERBARU --}}
<div class="db-card">
<div class="db-card-header">
<div class="db-card-title">
<span class="title-dot primary"></span>
Siswa Terbaru
</div>
</div>
@forelse($siswaTerbaru as $i => $siswa)
<div class="data-row">
<div class="data-row-left">
<span class="data-row-index">{{ str_pad($loop->iteration, 2, '0', STR_PAD_LEFT) }}</span>
<span class="data-row-name">{{ $siswa->nama }}</span>
</div>
<span class="data-row-sub">{{ $siswa->kelas->nama ?? '—' }}</span>
</div>
@empty
<div class="empty-state">Belum ada data siswa</div>
@endforelse
</div>
{{-- NILAI TERTINGGI --}}
<div class="db-card">
<div class="db-card-header">
<div class="db-card-title">
<span class="title-dot teal"></span>
Nilai Tertinggi
</div>
</div>
@forelse($nilaiTertinggi as $n)
<div class="data-row">
<div class="data-row-left">
<span class="data-row-index">{{ str_pad($loop->iteration, 2, '0', STR_PAD_LEFT) }}</span>
<span class="data-row-name">{{ $n->siswa->nama ?? '—' }}</span>
</div>
@php $score = $n->nilai; @endphp
<span class="score-badge {{ $score >= 85 ? 'high' : ($score >= 70 ? 'mid' : 'low') }}">
{{ $score }}
</span>
</div>
@empty
<div class="empty-state">Belum ada data nilai</div>
@endforelse
</div>
</div>
{{-- 🔥 NILAI TERTINGGI --}}
<div class="card mt-4">
<div class="card-body">
<h5>Nilai Tertinggi</h5>
<ul>
@foreach($nilaiTertinggi as $n)
<li>
{{ $n->siswa->nama ?? '-' }}
- {{ $n->nilai }}
</li>
@endforeach
</ul>
{{-- QUICK ACTIONS --}}
<div class="quick-actions">
<div class="qa-left">
<h5>Aksi Cepat</h5>
<small>Tambah atau kelola data langsung dari sini</small>
</div>
<div class="qa-buttons">
<a href="{{ route('admin.guru.create') }}" class="btn-qa primary">
<i class="fa-solid fa-plus" style="font-size:11px;"></i> Tambah Guru
</a>
<a href="{{ route('admin.siswa.create') }}" class="btn-qa success">
<i class="fa-solid fa-plus" style="font-size:11px;"></i> Tambah Siswa
</a>
<a href="{{ route('admin.mapel.index') }}" class="btn-qa outline">
<i class="fa-solid fa-book" style="font-size:11px;"></i> Mata Pelajaran
</a>
</div>
</div>
</div>
@endsection
@endsection
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const kelasLabels = {!! json_encode($kelasLabels ?? []) !!};
const kelasData = {!! json_encode($kelasData ?? []) !!};
const nilaiLabels = {!! json_encode(array_keys(($nilaiDistribusi ?? collect())->toArray())) !!};
const nilaiData = {!! json_encode(array_values(($nilaiDistribusi ?? collect())->toArray())) !!};
console.log("Kelas:", kelasLabels, kelasData);
console.log("Nilai:", nilaiLabels, nilaiData);
Chart.defaults.font.family = "'Plus Jakarta Sans', sans-serif";
Chart.defaults.font.size = 12;
// ── CHART KELAS (Bar) ──────────────────────────────────
if (kelasLabels.length > 0) {
new Chart(document.getElementById('chartKelas'), {
type: 'bar',
data: {
labels: kelasLabels,
datasets: [{
label: 'Jumlah Siswa',
data: kelasData,
backgroundColor: 'rgba(26, 86, 219, 0.12)',
borderColor: 'rgba(26, 86, 219, 0.9)',
borderWidth: 2,
borderRadius: 6,
borderSkipped: false,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: '#111827',
titleColor: '#fff',
bodyColor: '#9ca3af',
padding: 12,
cornerRadius: 8,
displayColors: false,
callbacks: {
label: ctx => ` ${ctx.parsed.y} siswa`
}
}
},
scales: {
x: {
grid: { display: false },
border: { display: false },
ticks: { color: '#9ca3af', autoSkip: false, maxRotation: 30 }
},
y: {
grid: { color: 'rgba(0,0,0,0.05)', drawTicks: false },
border: { display: false, dash: [4, 4] },
ticks: { color: '#9ca3af', padding: 8, precision: 0 },
beginAtZero: true
}
}
}
});
}
// ── CHART NILAI (Pie) ──────────────────────────────────
if (nilaiLabels.length > 0) {
new Chart(document.getElementById('chartNilai'), {
type: 'pie',
data: {
labels: nilaiLabels,
datasets: [{
data: nilaiData,
backgroundColor: [
'rgba(26, 86, 219, 0.85)',
'rgba(13, 148, 136, 0.85)',
'rgba(217, 119, 6, 0.85)',
'rgba(220, 38, 38, 0.85)',
'rgba(99, 102, 241, 0.85)',
'rgba(236, 72, 153, 0.85)',
],
borderColor: '#ffffff',
borderWidth: 3,
hoverOffset: 6,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'bottom',
labels: {
color: '#6b7280',
padding: 14,
boxWidth: 10,
boxHeight: 10,
font: { size: 12, weight: '500' }
}
},
tooltip: {
backgroundColor: '#111827',
titleColor: '#fff',
bodyColor: '#9ca3af',
padding: 12,
cornerRadius: 8,
callbacks: {
label: ctx => ` ${ctx.label}: ${ctx.parsed}`
}
}
}
}
});
}
});
</script>
@endpush

View File

@ -1,96 +1,525 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h2 class="mb-4 text-primary">Tambah Guru</h2>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<div class="card shadow-sm border-0">
<div class="card-body">
:root {
--primary: #2563eb;
--primary-light: #eff6ff;
--primary-border: #bfdbfe;
--primary-dark: #1d4ed8;
--success: #16a34a;
--success-light: #f0fdf4;
--success-border: #bbf7d0;
--success-dark: #15803d;
--danger: #dc2626;
--danger-light: #fef2f2;
--danger-border: #fecaca;
--info: #0284c7;
--info-light: #f0f9ff;
--info-border: #bae6fd;
--text-primary: #0f172a;
--text-secondary: #475569;
--text-muted: #94a3b8;
--surface: #ffffff;
--surface-secondary: #f8fafc;
--border: #e2e8f0;
--border-hover: #cbd5e1;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 20px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--shadow-focus: 0 0 0 3px rgba(37,99,235,0.12);
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--radius-xl: 18px;
}
{{-- ERROR --}}
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
<form action="{{ route('admin.guru.store') }}" method="POST">
@csrf
/* ── PAKSA AREA KONTEN MENGISI RUANG YANG ADA ── */
.container-fluid {
width: 100%;
max-width: 100%;
padding: 24px !important;
background: #f1f5f9;
min-height: 100vh;
}
<div class="row">
.form-wrapper {
animation: fadeInUp 0.4s ease both;
width: 100%;
max-width: 100%;
margin: 0;
}
{{-- NAMA --}}
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Nama</label>
<input type="text" name="nama" class="form-control" required>
</div>
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
{{-- NIP --}}
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">NIP</label>
<input type="text" name="nip" class="form-control" required>
</div>
.back-nav {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--text-muted);
font-size: 13.5px;
font-weight: 500;
text-decoration: none;
margin-bottom: 22px;
transition: color 0.15s;
padding: 6px 0;
}
.back-nav:hover { color: var(--primary); text-decoration: none; }
.back-nav svg { width: 16px; height: 16px; }
{{-- MAPEL --}}
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Mata Pelajaran</label>
<select name="mapel_id" class="form-control" required>
<option value="">-- Pilih Mapel --</option>
@foreach($mapel as $m)
<option value="{{ $m->id }}">{{ $m->nama_mapel }}</option>
@endforeach
</select>
</div>
.form-page-header {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 28px;
}
{{-- TELEPON --}}
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Telepon</label>
<input type="text" name="telepon" class="form-control" required>
</div>
.form-page-icon {
width: 50px;
height: 50px;
border-radius: var(--radius-lg);
background: var(--primary-light);
border: 1px solid var(--primary-border);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.form-page-icon svg { width: 24px; height: 24px; color: var(--primary); }
{{-- ALAMAT --}}
<div class="col-md-12 mb-3">
<label class="form-label fw-bold">Alamat</label>
<input type="text" name="alamat" class="form-control" required>
</div>
.form-page-title {
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 4px 0;
letter-spacing: -0.3px;
}
.form-page-subtitle { font-size: 13.5px; color: var(--text-secondary); }
</div>
.steps-indicator {
display: flex;
align-items: center;
gap: 0;
margin-bottom: 24px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 16px 22px;
box-shadow: var(--shadow-sm);
overflow: hidden;
}
.step-item {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
}
.step-item:not(:last-child)::after {
content: '';
width: 1px;
height: 24px;
background: var(--border);
margin: 0 16px;
flex-shrink: 0;
}
.step-num {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--primary-light);
border: 1.5px solid var(--primary-border);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
color: var(--primary);
flex-shrink: 0;
}
.step-label { font-size: 12.5px; font-weight: 600; color: var(--text-secondary); }
.step-desc { font-size: 11.5px; color: var(--text-muted); margin-top: 1px; }
<hr>
.error-alert {
background: var(--danger-light);
border: 1px solid var(--danger-border);
border-radius: var(--radius-md);
padding: 14px 18px;
margin-bottom: 22px;
}
.error-alert-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 13.5px;
font-weight: 600;
color: var(--danger);
margin-bottom: 10px;
}
.error-alert-header svg { width: 16px; height: 16px; }
.error-list {
margin: 0;
padding: 0 0 0 20px;
font-size: 13px;
color: var(--danger);
line-height: 1.8;
}
<h5 class="text-primary">Akun Login</h5>
.form-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-md);
overflow: hidden;
margin-bottom: 16px;
}
.form-card-header {
padding: 17px 24px;
border-bottom: 1px solid var(--border);
background: var(--surface-secondary);
display: flex;
align-items: center;
gap: 10px;
}
.section-dot { width: 8px; height: 8px; border-radius: 50%; }
.dot-blue { background: var(--primary); }
.dot-green { background: var(--success); }
.form-card-header-label {
font-size: 13px;
font-weight: 700;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.6px;
}
.form-card-body { padding: 26px 24px; }
{{-- EMAIL --}}
<div class="mb-3">
<label class="form-label fw-bold">Email</label>
<input type="email" name="email" class="form-control" required>
</div>
.form-row-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
margin-bottom: 18px;
}
.form-row-full { margin-bottom: 18px; }
{{-- INFO PASSWORD --}}
<div class="alert alert-info">
Password default: <b>12345678</b><br>
Guru wajib mengganti password saat login pertama.
</div>
.form-label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 7px;
}
.form-label .required { color: var(--danger); margin-left: 3px; }
<div class="d-flex justify-content-between">
<a href="{{ route('admin.guru.index') }}" class="btn btn-secondary">
Kembali
</a>
.form-control {
width: 100%;
padding: 10px 14px;
font-size: 14px;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-primary);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
appearance: none;
}
.form-control:focus { border-color: var(--primary); box-shadow: var(--shadow-focus); }
.form-control:hover:not(:focus) { border-color: var(--border-hover); }
.form-control::placeholder { color: var(--text-muted); }
.form-control.is-invalid {
border-color: var(--danger);
box-shadow: 0 0 0 3px rgba(220,38,38,0.1);
}
<button type="submit" class="btn btn-success" onclick="this.disabled=true; this.form.submit();">
Simpan
</button>
</div>
select.form-control {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 16px;
padding-right: 38px;
cursor: pointer;
}
</form>
.info-box {
display: flex;
align-items: flex-start;
gap: 12px;
background: var(--info-light);
border: 1px solid var(--info-border);
border-radius: var(--radius-md);
padding: 14px 18px;
margin-top: 18px;
}
.info-box-icon {
width: 32px;
height: 32px;
border-radius: var(--radius-sm);
background: #e0f2fe;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.info-box-icon svg { width: 16px; height: 16px; color: var(--info); }
.info-box-title { font-size: 13px; font-weight: 600; color: #075985; margin-bottom: 4px; }
.info-box-desc { font-size: 12.5px; color: #0369a1; line-height: 1.6; }
.info-box-desc b { font-weight: 700; }
.form-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-top: 1px solid var(--border);
background: var(--surface-secondary);
border-radius: 0 0 var(--radius-xl) var(--radius-xl);
gap: 12px;
flex-wrap: wrap;
}
.btn-cancel {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 10px 20px;
font-size: 14px;
font-weight: 600;
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--surface);
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-md);
text-decoration: none;
cursor: pointer;
transition: all 0.15s;
}
.btn-cancel:hover { background: var(--surface-secondary); color: var(--text-primary); border-color: var(--border-hover); text-decoration: none; }
.btn-cancel svg { width: 15px; height: 15px; }
.btn-submit {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 26px;
font-size: 14px;
font-weight: 600;
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--success);
color: #fff;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.15s;
box-shadow: 0 2px 8px rgba(22,163,74,0.3);
}
.btn-submit svg { width: 15px; height: 15px; }
.btn-submit:hover { background: var(--success-dark); box-shadow: 0 4px 14px rgba(22,163,74,0.4); transform: translateY(-1px); }
.btn-submit:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
@media (max-width: 640px) {
.form-row-grid { grid-template-columns: 1fr; }
.steps-indicator { flex-direction: column; align-items: flex-start; gap: 12px; }
.step-item::after { display: none; }
.form-card-body { padding: 20px 16px; }
.form-footer { padding: 16px; }
}
</style>
<div class="form-wrapper">
{{-- BACK LINK --}}
<a href="{{ route('admin.guru.index') }}" class="back-nav">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/>
</svg>
Kembali ke Daftar Guru
</a>
{{-- PAGE HEADER --}}
<div class="form-page-header">
<div class="form-page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"/>
</svg>
</div>
<div>
<h2 class="form-page-title">Tambah Guru Baru</h2>
<p class="form-page-subtitle">Daftarkan guru baru beserta akun login ke sistem</p>
</div>
</div>
{{-- STEPS INDICATOR --}}
<div class="steps-indicator">
<div class="step-item">
<div class="step-num">1</div>
<div>
<div class="step-label">Data Pribadi</div>
<div class="step-desc">Nama, NIP, alamat</div>
</div>
</div>
<div class="step-item">
<div class="step-num">2</div>
<div>
<div class="step-label">Mata Pelajaran</div>
<div class="step-desc">Pilih bidang studi</div>
</div>
</div>
<div class="step-item">
<div class="step-num">3</div>
<div>
<div class="step-label">Akun Login</div>
<div class="step-desc">Email & password</div>
</div>
</div>
</div>
{{-- VALIDATION ERRORS --}}
@if ($errors->any())
<div class="error-alert">
<div class="error-alert-header">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856C18.07 19.126 19 17.643 19 16V8a2 2 0 00-2-2H7a2 2 0 00-2 2v8c0 1.643.93 3.126 2.062 4z"/>
</svg>
Terdapat {{ $errors->count() }} kesalahan pada form
</div>
<ul class="error-list">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('admin.guru.store') }}" method="POST" id="formTambahGuru">
@csrf
{{-- CARD 1: DATA PRIBADI --}}
<div class="form-card">
<div class="form-card-header">
<div class="section-dot dot-blue"></div>
<span class="form-card-header-label">Data Pribadi Guru</span>
</div>
<div class="form-card-body">
<div class="form-row-grid">
<div>
<label class="form-label">Nama Lengkap <span class="required">*</span></label>
<input type="text" name="nama" value="{{ old('nama') }}"
class="form-control {{ $errors->has('nama') ? 'is-invalid' : '' }}"
placeholder="Masukkan nama lengkap guru" required>
</div>
<div>
<label class="form-label">NIP <span class="required">*</span></label>
<input type="text" name="nip" value="{{ old('nip') }}"
class="form-control {{ $errors->has('nip') ? 'is-invalid' : '' }}"
placeholder="Nomor Induk Pegawai" required>
</div>
</div>
<div class="form-row-grid">
<div>
<label class="form-label">Mata Pelajaran <span class="required">*</span></label>
<select name="mapel_id" class="form-control {{ $errors->has('mapel_id') ? 'is-invalid' : '' }}" required>
<option value="">— Pilih Mata Pelajaran —</option>
@foreach($mapel as $m)
<option value="{{ $m->id }}" {{ old('mapel_id') == $m->id ? 'selected' : '' }}>
{{ $m->nama_mapel }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">Nomor Telepon <span class="required">*</span></label>
<input type="text" name="telepon" value="{{ old('telepon') }}"
class="form-control {{ $errors->has('telepon') ? 'is-invalid' : '' }}"
placeholder="Contoh: 081234567890" required>
</div>
</div>
<div class="form-row-full">
<label class="form-label">Alamat Lengkap <span class="required">*</span></label>
<input type="text" name="alamat" value="{{ old('alamat') }}"
class="form-control {{ $errors->has('alamat') ? 'is-invalid' : '' }}"
placeholder="Jl. ..., Kelurahan, Kecamatan, Kota" required>
</div>
</div>
</div>
{{-- CARD 2: AKUN LOGIN --}}
<div class="form-card">
<div class="form-card-header">
<div class="section-dot dot-green"></div>
<span class="form-card-header-label">Akun Login Guru</span>
</div>
<div class="form-card-body">
<div class="form-row-full">
<label class="form-label">Alamat Email <span class="required">*</span></label>
<input type="email" name="email" value="{{ old('email') }}"
class="form-control {{ $errors->has('email') ? 'is-invalid' : '' }}"
placeholder="contoh@sekolah.sch.id" required>
</div>
<div class="info-box">
<div class="info-box-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div>
<div class="info-box-title">Informasi Password</div>
<div class="info-box-desc">
Password default yang ditetapkan adalah <b>12345678</b>.<br>
Guru wajib mengganti password saat pertama kali login ke sistem.
</div>
</div>
</div>
</div>
<div class="form-footer">
<a href="{{ route('admin.guru.index') }}" class="btn-cancel">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
</svg>
Batalkan
</a>
<button type="submit" class="btn-submit" id="btnSubmit">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
</svg>
Simpan Data Guru
</button>
</div>
</div>
</form>
</div>
<script>
document.getElementById('btnSubmit').addEventListener('click', function(e) {
this.disabled = true;
this.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="animation:spin 0.8s linear infinite">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
Menyimpan...
`;
document.getElementById('formTambahGuru').submit();
});
const style = document.createElement('style');
style.textContent = '@keyframes spin { to { transform: rotate(360deg); } }';
document.head.appendChild(style);
</script>
@endsection

View File

@ -1,46 +1,453 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h2 class="mb-4 text-warning">Edit Guru</h2>
<div class="card shadow-lg border-0 rounded-3">
<div class="card-body">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--primary: #2563eb;
--primary-light: #eff6ff;
--primary-border: #bfdbfe;
--primary-dark: #1d4ed8;
--warning: #d97706;
--warning-light: #fffbeb;
--warning-border: #fde68a;
--warning-dark: #b45309;
--success: #16a34a;
--danger: #dc2626;
--text-primary: #0f172a;
--text-secondary: #475569;
--text-muted: #94a3b8;
--surface: #ffffff;
--surface-secondary: #f8fafc;
--border: #e2e8f0;
--border-hover: #cbd5e1;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 20px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--shadow-focus: 0 0 0 3px rgba(37,99,235,0.12);
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--radius-xl: 18px;
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.container-fluid {
width: 100%;
max-width: 100%;
padding: 24px !important;
background: #f1f5f9;
min-height: 100vh;
}
.form-wrapper {
animation: fadeInUp 0.4s ease both;
width: 100%;
max-width: 100%;
margin: 0;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
/* BREADCRUMB */
.back-nav {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--text-muted);
font-size: 13.5px;
font-weight: 500;
text-decoration: none;
margin-bottom: 22px;
transition: color 0.15s;
padding: 6px 0;
}
.back-nav:hover { color: var(--primary); text-decoration: none; }
.back-nav svg { width: 16px; height: 16px; }
/* PAGE HEADING */
.form-page-header {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 28px;
}
.form-page-icon {
width: 50px;
height: 50px;
border-radius: var(--radius-lg);
background: var(--warning-light);
border: 1px solid var(--warning-border);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.form-page-icon svg { width: 24px; height: 24px; color: var(--warning); }
.form-page-title {
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 4px 0;
letter-spacing: -0.3px;
}
.form-page-subtitle {
font-size: 13.5px;
color: var(--text-secondary);
}
/* CARD */
.form-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.form-card-header {
padding: 18px 24px;
border-bottom: 1px solid var(--border);
background: var(--surface-secondary);
display: flex;
align-items: center;
gap: 10px;
}
.form-card-header-label {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.6px;
}
.section-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--warning);
}
.form-card-body { padding: 28px 24px; }
/* SECTION DIVIDER */
.form-section-label {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
margin-bottom: 18px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
/* FORM ELEMENTS */
.form-row-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
margin-bottom: 18px;
}
.form-row-full { margin-bottom: 18px; }
.form-label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 7px;
}
.form-label .required {
color: var(--danger);
margin-left: 3px;
}
.form-hint {
font-size: 12px;
color: var(--text-muted);
font-weight: 400;
margin-left: 4px;
}
.form-control {
width: 100%;
padding: 10px 14px;
font-size: 14px;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-primary);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
outline: none;
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
appearance: none;
}
.form-control:focus {
border-color: var(--primary);
box-shadow: var(--shadow-focus);
background: var(--surface);
}
.form-control:hover:not(:focus) {
border-color: var(--border-hover);
}
.form-control::placeholder { color: var(--text-muted); }
select.form-control {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 16px;
padding-right: 38px;
cursor: pointer;
}
/* GURU INFO BANNER */
.guru-info-banner {
display: flex;
align-items: center;
gap: 14px;
background: var(--warning-light);
border: 1px solid var(--warning-border);
border-radius: var(--radius-md);
padding: 14px 18px;
margin-bottom: 26px;
}
.guru-avatar {
width: 42px;
height: 42px;
border-radius: var(--radius-md);
background: var(--warning-border);
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
font-weight: 700;
color: var(--warning-dark);
flex-shrink: 0;
letter-spacing: -0.5px;
}
.guru-info-name {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
}
.guru-info-meta {
font-size: 12.5px;
color: var(--text-secondary);
margin-top: 2px;
}
/* FOOTER ACTIONS */
.form-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-top: 1px solid var(--border);
background: var(--surface-secondary);
gap: 12px;
flex-wrap: wrap;
}
.btn-cancel {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 10px 20px;
font-size: 14px;
font-weight: 600;
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--surface);
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-md);
text-decoration: none;
cursor: pointer;
transition: all 0.15s;
}
.btn-cancel svg { width: 15px; height: 15px; }
.btn-cancel:hover {
background: var(--surface-secondary);
color: var(--text-primary);
border-color: var(--border-hover);
text-decoration: none;
}
.btn-update {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 26px;
font-size: 14px;
font-weight: 600;
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--warning);
color: #fff;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.15s;
box-shadow: 0 2px 8px rgba(217,119,6,0.3);
}
.btn-update svg { width: 15px; height: 15px; }
.btn-update:hover {
background: var(--warning-dark);
box-shadow: 0 4px 14px rgba(217,119,6,0.4);
transform: translateY(-1px);
}
.btn-update:active { transform: scale(0.98); }
@media (max-width: 640px) {
.form-row-grid { grid-template-columns: 1fr; }
.form-card-body { padding: 20px 16px; }
.form-footer { padding: 16px; }
}
</style>
<div class="container-fluid px-4 py-2">
<div class="form-wrapper">
{{-- BACK LINK --}}
<a href="{{ route('admin.guru.index') }}" class="back-nav">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/>
</svg>
Kembali ke Daftar Guru
</a>
{{-- PAGE HEADER --}}
<div class="form-page-header">
<div class="form-page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 012.828 2.828L11.828 15.828a2 2 0 01-1.414.586H8v-2.414a2 2 0 01.586-1.414z"/>
</svg>
</div>
<div>
<h2 class="form-page-title">Edit Data Guru</h2>
<p class="form-page-subtitle">Perbarui informasi data guru yang sudah terdaftar</p>
</div>
</div>
{{-- CURRENT GURU BANNER --}}
<div class="guru-info-banner">
<div class="guru-avatar">
{{ strtoupper(substr($guru->nama, 0, 2)) }}
</div>
<div>
<div class="guru-info-name">{{ $guru->nama }}</div>
<div class="guru-info-meta">NIP: {{ $guru->nip ?: '—' }} &nbsp;&bull;&nbsp; {{ $guru->mapel->nama_mapel ?? 'Belum ada mapel' }}</div>
</div>
</div>
{{-- FORM CARD --}}
<div class="form-card">
<div class="form-card-header">
<div class="section-dot"></div>
<span class="form-card-header-label">Informasi Data Guru</span>
</div>
<div class="form-card-body">
<form action="{{ route('admin.guru.update', $guru->id) }}" method="POST">
@csrf
@method('PUT')
<div class="mb-3">
<label class="form-label fw-bold">Nama</label>
<input type="text" name="nama" value="{{ $guru->nama }}" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">NIP</label>
<input type="text" name="nip" value="{{ $guru->nip }}" class="form-control">
</div>
<div class="mb-3">
<label class="form-label fw-bold">Mapel</label>
<input type="text" name="mapel" value="{{ $guru->mapel }}" class="form-control">
</div>
<div class="mb-3">
<label class="form-label fw-bold">Alamat</label>
<input type="text" name="alamat" value="{{ $guru->alamat }}" class="form-control">
</div>
<div class="mb-3">
<label class="form-label fw-bold">Telepon</label>
<input type="text" name="telepon" value="{{ $guru->telepon }}" class="form-control">
{{-- ROW 1 --}}
<div class="form-row-grid">
<div>
<label class="form-label">Nama Lengkap <span class="required">*</span></label>
<input type="text" name="nama" value="{{ old('nama', $guru->nama) }}"
class="form-control" placeholder="Masukkan nama lengkap" required>
</div>
<div>
<label class="form-label">NIP</label>
<input type="text" name="nip" value="{{ old('nip', $guru->nip) }}"
class="form-control" placeholder="Nomor Induk Pegawai">
</div>
</div>
<div class="d-flex justify-content-between">
<a href="{{ route('admin.guru.index') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Kembali
</a>
<button type="submit" class="btn btn-warning">
<i class="fas fa-edit"></i> Update
</button>
{{-- ROW 2 --}}
<div class="form-row-grid">
<div>
<label class="form-label">Mata Pelajaran <span class="required">*</span></label>
<select name="mapel_id" class="form-control" required>
<option value="">— Pilih Mata Pelajaran —</option>
@forelse($mapel as $m)
<option value="{{ $m->id }}" {{ $guru->mapel_id == $m->id ? 'selected' : '' }}>
{{ $m->nama_mapel }}
</option>
@empty
<option disabled>Tidak ada data mapel</option>
@endforelse
</select>
</div>
<div>
<label class="form-label">Nomor Telepon</label>
<input type="text" name="telepon" value="{{ old('telepon', $guru->telepon) }}"
class="form-control" placeholder="Contoh: 081234567890">
</div>
</div>
{{-- ROW 3 --}}
<div class="form-row-full">
<label class="form-label">Alamat</label>
<input type="text" name="alamat" value="{{ old('alamat', $guru->alamat) }}"
class="form-control" placeholder="Masukkan alamat lengkap">
</div>
{{-- Tombol di dalam form, sebelum </form> --}}
</form>
</div>
<div class="form-footer">
<a href="{{ route('admin.guru.index') }}" class="btn-cancel">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
</svg>
Batalkan
</a>
<button type="submit" form="formEditGuru" class="btn-update" id="btnUpdate">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
</svg>
Simpan Perubahan
</button>
</div>
</div>
</div>
@endsection
</div>
<script>
(function() {
// Attach form id so footer button can submit
const form = document.querySelector('form[action*="update"]');
if (form) form.id = 'formEditGuru';
})();
</script>
@endsection

View File

@ -3,281 +3,680 @@
@section('content')
<style>
/* ===== GLOBAL ===== */
* {
font-family: 'Poppins', sans-serif;
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--primary: #2563eb;
--primary-light: #eff6ff;
--primary-border: #bfdbfe;
--success: #16a34a;
--success-light: #f0fdf4;
--success-border: #bbf7d0;
--danger: #dc2626;
--danger-light: #fef2f2;
--danger-border: #fecaca;
--warning: #d97706;
--warning-light: #fffbeb;
--warning-border: #fde68a;
--text-primary: #0f172a;
--text-secondary: #475569;
--text-muted: #94a3b8;
--surface: #ffffff;
--surface-secondary: #f8fafc;
--border: #e2e8f0;
--border-hover: #cbd5e1;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 16px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--shadow-focus: 0 0 0 3px rgba(37,99,235,0.12);
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--radius-xl: 18px;
}
/* ===== PAGE HEADER ===== */
.page-title {
font-weight: 600;
letter-spacing: 0.3px;
color: #1f2937;
margin: 0;
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.guru-wrapper {
padding: 0 4px;
animation: fadeInUp 0.4s ease both;
}
/* ===== CARD ===== */
.card {
border: none !important;
border-radius: 14px;
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
overflow: hidden;
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
/* ===== TABLE ===== */
.table {
margin-bottom: 0;
border-collapse: separate;
border-spacing: 0;
/* ---- HEADER ---- */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 28px;
flex-wrap: wrap;
gap: 16px;
}
.table thead {
background: linear-gradient(to right, #f1f3f6, #e7ebf0);
}
.page-header-left {}
.table thead th {
border: none !important;
padding: 15px 14px;
font-size: 13px;
font-weight: 600;
color: #5b6472;
vertical-align: middle;
white-space: nowrap;
}
.table tbody tr {
background: #ffffff;
transition: background-color 0.2s ease, transform 0.2s ease;
}
.table tbody tr:nth-child(even) {
background: #fafbfc;
}
.table tbody tr:hover {
background: #f3f6fa;
}
.table tbody td {
border-top: 1px solid #edf1f5 !important;
border-left: none !important;
border-right: none !important;
border-bottom: none !important;
padding: 15px 14px;
font-size: 14px;
color: #374151;
vertical-align: middle;
}
/* ===== BADGE ===== */
.badge {
.page-breadcrumb {
font-size: 12px;
color: var(--text-muted);
margin-bottom: 4px;
letter-spacing: 0.3px;
}
.page-breadcrumb span { color: var(--primary); font-weight: 500; }
.page-title {
font-size: 26px;
font-weight: 700;
color: var(--text-primary);
margin: 0;
letter-spacing: -0.4px;
}
.page-subtitle {
font-size: 13.5px;
color: var(--text-secondary);
margin-top: 3px;
}
.btn-add-guru {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--primary);
color: #fff;
font-size: 14px;
font-weight: 600;
padding: 7px 12px;
border-radius: 999px;
letter-spacing: 0.2px;
padding: 11px 22px;
border-radius: var(--radius-md);
text-decoration: none;
transition: background 0.2s, box-shadow 0.2s, transform 0.15s;
box-shadow: 0 2px 8px rgba(37,99,235,0.25);
}
.badge-mapel {
background: #eef4ff;
color: #2f5ea8;
border: 1px solid #dbe7ff;
.btn-add-guru:hover {
background: #1d4ed8;
box-shadow: 0 4px 14px rgba(37,99,235,0.35);
transform: translateY(-1px);
color: #fff;
text-decoration: none;
}
.badge-status-active {
background: #eaf8ef;
color: #1f9d57;
border: 1px solid #d5f0df;
.btn-add-guru svg { width: 16px; height: 16px; }
/* ---- STATS ROW ---- */
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 14px;
margin-bottom: 24px;
}
.badge-status-inactive {
background: #fdeeee;
color: #d14343;
border: 1px solid #f8d6d6;
.stat-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 18px 20px;
display: flex;
align-items: center;
gap: 14px;
box-shadow: var(--shadow-sm);
transition: box-shadow 0.2s;
}
/* ===== BUTTON ===== */
.btn {
border-radius: 8px;
.stat-card:hover { box-shadow: var(--shadow-md); }
.stat-icon {
width: 44px;
height: 44px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.stat-icon.blue { background: var(--primary-light); }
.stat-icon.green { background: var(--success-light); }
.stat-icon.red { background: var(--danger-light); }
.stat-icon svg { width: 20px; height: 20px; }
.stat-icon.blue svg { color: var(--primary); }
.stat-icon.green svg { color: var(--success); }
.stat-icon.red svg { color: var(--danger); }
.stat-info {}
.stat-label { font-size: 12px; color: var(--text-muted); font-weight: 500; }
.stat-value { font-size: 22px; font-weight: 700; color: var(--text-primary); line-height: 1.1; margin-top: 2px; }
/* ---- ALERT ---- */
.alert-success-custom {
display: flex;
align-items: center;
gap: 12px;
background: var(--success-light);
border: 1px solid var(--success-border);
border-radius: var(--radius-md);
padding: 14px 18px;
margin-bottom: 20px;
font-size: 14px;
color: var(--success);
font-weight: 500;
}
.btn-sm {
padding: 6px 12px;
.alert-success-custom svg { width: 18px; height: 18px; flex-shrink: 0; }
.alert-close { margin-left: auto; background: none; border: none; cursor: pointer; color: var(--success); padding: 0; opacity: 0.7; }
.alert-close:hover { opacity: 1; }
/* ---- TABLE CARD ---- */
.table-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.table-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 22px 16px;
border-bottom: 1px solid var(--border);
gap: 16px;
flex-wrap: wrap;
}
.toolbar-label {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
}
.toolbar-right {
display: flex;
align-items: center;
gap: 10px;
}
.search-wrap {
position: relative;
display: flex;
align-items: center;
}
.search-wrap svg {
position: absolute;
left: 11px;
width: 15px;
height: 15px;
color: var(--text-muted);
pointer-events: none;
}
.search-input {
padding: 8px 12px 8px 34px;
font-size: 13px;
font-family: 'Plus Jakarta Sans', sans-serif;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface-secondary);
color: var(--text-primary);
width: 210px;
transition: border-color 0.2s, box-shadow 0.2s;
outline: none;
}
.search-input:focus {
border-color: var(--primary);
box-shadow: var(--shadow-focus);
background: var(--surface);
}
.search-input::placeholder { color: var(--text-muted); }
/* ---- TABLE ---- */
.table-responsive { overflow-x: auto; }
table.guru-table {
width: 100%;
border-collapse: collapse;
min-width: 860px;
}
.guru-table thead tr {
background: var(--surface-secondary);
border-bottom: 1px solid var(--border);
}
.guru-table thead th {
padding: 13px 16px;
font-size: 11.5px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.7px;
white-space: nowrap;
text-align: left;
}
.guru-table thead th:last-child { text-align: center; }
.guru-table tbody tr {
border-bottom: 1px solid var(--border);
transition: background 0.15s;
}
.guru-table tbody tr:last-child { border-bottom: none; }
.guru-table tbody tr:hover { background: #f8faff; }
.guru-table tbody td {
padding: 14px 16px;
font-size: 13.5px;
color: var(--text-primary);
vertical-align: middle;
}
.cell-no {
font-size: 13px;
color: var(--text-muted);
font-weight: 500;
width: 48px;
}
.cell-nama {
font-weight: 600;
color: var(--text-primary);
}
.cell-nip {
font-size: 13px;
color: var(--text-secondary);
font-family: 'Plus Jakarta Sans', monospace;
letter-spacing: 0.2px;
}
.cell-muted {
color: var(--text-secondary);
font-size: 13px;
}
.badge-mapel {
display: inline-flex;
align-items: center;
background: var(--primary-light);
color: var(--primary);
border: 1px solid var(--primary-border);
border-radius: 999px;
font-size: 12px;
font-weight: 600;
padding: 4px 11px;
}
.badge-aktif {
display: inline-flex;
align-items: center;
gap: 5px;
background: var(--success-light);
color: var(--success);
border: 1px solid var(--success-border);
border-radius: 999px;
font-size: 12px;
font-weight: 600;
padding: 4px 11px;
}
.badge-aktif::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--success);
}
.badge-nonaktif {
display: inline-flex;
align-items: center;
gap: 5px;
background: var(--danger-light);
color: var(--danger);
border: 1px solid var(--danger-border);
border-radius: 999px;
font-size: 12px;
font-weight: 600;
padding: 4px 11px;
}
.badge-nonaktif::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--danger);
}
/* ---- ACTION BUTTONS ---- */
.action-group {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
flex-wrap: wrap;
}
.btn-action {
min-width: 92px;
display: inline-flex;
align-items: center;
gap: 5px;
padding: 6px 13px;
font-size: 12.5px;
font-weight: 500;
font-family: 'Plus Jakarta Sans', sans-serif;
border-radius: var(--radius-sm);
text-decoration: none;
border: 1px solid transparent;
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
}
.btn-soft {
background: #f8fafc;
border: 1px solid #dbe2ea;
color: #374151;
.btn-action svg { width: 13px; height: 13px; }
.btn-edit {
background: var(--surface-secondary);
color: var(--text-secondary);
border-color: var(--border);
}
.btn-soft:hover {
background: #eef2f6;
border-color: #cfd8e3;
color: #111827;
.btn-edit:hover {
background: #f1f5f9;
color: var(--text-primary);
border-color: var(--border-hover);
text-decoration: none;
}
.btn-outline-warning:hover,
.btn-outline-success:hover {
color: #fff;
.btn-delete {
background: var(--danger-light);
color: var(--danger);
border-color: var(--danger-border);
}
/* ===== ALERT ===== */
.alert {
border: none;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.04);
.btn-delete:hover {
background: #fee2e2;
color: #b91c1c;
text-decoration: none;
}
/* ===== EMPTY STATE ===== */
.empty-row td {
padding: 28px 14px !important;
color: #6b7280;
font-size: 14px;
.btn-nonaktif {
background: var(--warning-light);
color: var(--warning);
border-color: var(--warning-border);
}
/* ===== RESPONSIVE ===== */
@media (max-width: 768px) {
.page-title {
font-size: 22px;
}
.btn-nonaktif:hover {
background: #fef3c7;
color: #b45309;
text-decoration: none;
}
.table thead th,
.table tbody td {
white-space: nowrap;
}
.btn-aktifkan {
background: var(--success-light);
color: var(--success);
border-color: var(--success-border);
}
.btn-aktifkan:hover {
background: #dcfce7;
color: #15803d;
text-decoration: none;
}
/* ---- EMPTY STATE ---- */
.empty-state {
padding: 64px 24px;
text-align: center;
}
.empty-icon {
width: 60px;
height: 60px;
background: var(--surface-secondary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
}
.empty-icon svg { width: 28px; height: 28px; color: var(--text-muted); }
.empty-title {
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 6px;
}
.empty-desc {
font-size: 13.5px;
color: var(--text-muted);
}
/* ---- TABLE FOOTER ---- */
.table-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 22px;
border-top: 1px solid var(--border);
background: var(--surface-secondary);
font-size: 13px;
color: var(--text-muted);
flex-wrap: wrap;
gap: 8px;
}
</style>
<div class="container-fluid px-4">
<div class="container-fluid px-4 py-2 guru-wrapper">
<div class="d-flex justify-content-between align-items-center mb-4">
<h3 class="page-title">Daftar Guru</h3>
<a href="{{ route('admin.guru.create') }}" class="btn btn-primary px-4">
{{-- HEADER --}}
<div class="page-header">
<div class="page-header-left">
<div class="page-breadcrumb">Manajemen &rsaquo; <span>Guru</span></div>
<h3 class="page-title">Data Guru</h3>
<p class="page-subtitle">Kelola seluruh data guru aktif dan nonaktif</p>
</div>
<a href="{{ route('admin.guru.create') }}" class="btn-add-guru">
<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>
Tambah Guru
</a>
</div>
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
<div class="card">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th>No</th>
<th>Nama</th>
<th>NIP</th>
<th>Mapel</th>
<th>Email</th>
<th>Alamat</th>
<th>Telepon</th>
<th>Status</th>
<th class="text-center">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($guru as $g)
<tr>
<td class="text-muted">{{ $loop->iteration }}</td>
<td class="fw-semibold text-start">
{{ $g->nama }}
</td>
<td>{{ $g->nip }}</td>
<td>
<span class="badge badge-mapel">
{{ $g->mapel->nama_mapel ?? '-' }}
</span>
</td>
<td class="text-muted">
{{ $g->user->email ?? '-' }}
</td>
<td class="text-start text-muted">
{{ $g->alamat }}
</td>
<td>{{ $g->telepon }}</td>
<td>
@if($g->status == 'aktif')
<span class="badge badge-status-active">
Aktif
</span>
@else
<span class="badge badge-status-inactive">
Nonaktif
</span>
@endif
</td>
<td class="text-center">
<div class="d-flex justify-content-center gap-2 flex-wrap">
<a href="{{ route('admin.guru.edit', $g->id) }}"
class="btn btn-sm btn-soft btn-action">
Edit
</a>
<form action="{{ route('admin.guru.destroy', $g->id) }}"
method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit"
class="btn btn-sm btn-soft btn-action text-danger"
onclick="return confirm('Yakin hapus data?')">
Hapus
</button>
</form>
@if($g->status == 'aktif')
<a href="{{ route('admin.guru.nonaktif', $g->id) }}"
class="btn btn-sm btn-outline-warning btn-action">
Nonaktifkan
</a>
@else
<a href="{{ route('admin.guru.aktifkan', $g->id) }}"
class="btn btn-sm btn-outline-success btn-action">
Aktifkan
</a>
@endif
</div>
</td>
</tr>
@empty
<tr class="empty-row">
<td colspan="9" class="text-center">
Belum ada data guru
</td>
</tr>
@endforelse
</tbody>
</table>
{{-- STATS --}}
<div class="stats-row">
<div class="stat-card">
<div class="stat-icon blue">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a4 4 0 00-5-3.87M9 20H4v-2a4 4 0 015-3.87m4 5.87a4 4 0 110-8 4 4 0 010 8zm6-12a4 4 0 10-8 0 4 4 0 008 0z"/>
</svg>
</div>
<div class="stat-info">
<div class="stat-label">Total Guru</div>
<div class="stat-value">{{ $guru->count() }}</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon green">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<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>
</div>
<div class="stat-info">
<div class="stat-label">Guru Aktif</div>
<div class="stat-value">{{ $guru->where('status','aktif')->count() }}</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon red">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="stat-info">
<div class="stat-label">Nonaktif</div>
<div class="stat-value">{{ $guru->where('status','nonaktif')->count() }}</div>
</div>
</div>
</div>
{{-- ALERT --}}
@if(session('success'))
<div class="alert-success-custom" id="alertSuccess">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<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>
<span>{{ session('success') }}</span>
<button class="alert-close" onclick="document.getElementById('alertSuccess').remove()">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
@endif
{{-- TABLE CARD --}}
<div class="table-card">
<div class="table-toolbar">
<span class="toolbar-label">Daftar Guru <span style="font-weight:400;color:var(--text-muted);font-size:13px;">({{ $guru->count() }} data)</span></span>
<div class="toolbar-right">
<div class="search-wrap">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-4.35-4.35M17 11A6 6 0 115 11a6 6 0 0112 0z"/>
</svg>
<input type="text" class="search-input" placeholder="Cari nama, NIP..." id="searchInput" oninput="filterTable()">
</div>
</div>
</div>
<div class="table-responsive">
<table class="guru-table" id="guruTable">
<thead>
<tr>
<th>No</th>
<th>Nama Guru</th>
<th>NIP</th>
<th>Mata Pelajaran</th>
<th>Email</th>
<th>Telepon</th>
<th>Status</th>
<th style="text-align:center;">Aksi</th>
</tr>
</thead>
<tbody id="guruTableBody">
@forelse($guru as $g)
<tr>
<td class="cell-no">{{ $loop->iteration }}</td>
<td>
<div class="cell-nama">{{ $g->nama }}</div>
@if($g->alamat)
<div class="cell-muted" style="font-size:12px;margin-top:2px;">{{ Str::limit($g->alamat, 40) }}</div>
@endif
</td>
<td class="cell-nip">{{ $g->nip ?: '—' }}</td>
<td>
<span class="badge-mapel">{{ $g->mapel->nama_mapel ?? '—' }}</span>
</td>
<td class="cell-muted">{{ $g->user->email ?? '—' }}</td>
<td class="cell-muted">{{ $g->telepon ?: '—' }}</td>
<td>
@if($g->status == 'aktif')
<span class="badge-aktif">Aktif</span>
@else
<span class="badge-nonaktif">Nonaktif</span>
@endif
</td>
<td>
<div class="action-group">
<a href="{{ route('admin.guru.edit', $g->id) }}" class="btn-action btn-edit">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 012.828 2.828L11.828 15.828a2 2 0 01-1.414.586H8v-2.414a2 2 0 01.586-1.414z"/>
</svg>
Edit
</a>
<form action="{{ route('admin.guru.destroy', $g->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Yakin ingin menghapus data guru ini?')">
@csrf
@method('DELETE')
<button type="submit" class="btn-action btn-delete">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6M9 7h6m2 0a1 1 0 00-1-1h-4a1 1 0 00-1 1m6 0H7"/>
</svg>
Hapus
</button>
</form>
@if($g->status == 'aktif')
<a href="{{ route('admin.guru.nonaktif', $g->id) }}" class="btn-action btn-nonaktif">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636"/>
</svg>
Nonaktifkan
</a>
@else
<a href="{{ route('admin.guru.aktifkan', $g->id) }}" class="btn-action btn-aktifkan">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<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>
Aktifkan
</a>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="8">
<div class="empty-state">
<div class="empty-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a4 4 0 00-5-3.87M9 20H4v-2a4 4 0 015-3.87m4 5.87a4 4 0 110-8 4 4 0 010 8zm6-12a4 4 0 10-8 0 4 4 0 008 0z"/>
</svg>
</div>
<div class="empty-title">Belum ada data guru</div>
<div class="empty-desc">Mulai tambahkan data guru menggunakan tombol di atas.</div>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="table-footer">
<span id="footerCount">Menampilkan {{ $guru->count() }} data</span>
<span>Sistem Manajemen Guru &bull; {{ now()->format('d M Y') }}</span>
</div>
</div>
</div>
<script>
function filterTable() {
const q = document.getElementById('searchInput').value.toLowerCase();
const rows = document.querySelectorAll('#guruTableBody tr');
let visible = 0;
rows.forEach(row => {
const text = row.textContent.toLowerCase();
const show = text.includes(q);
row.style.display = show ? '' : 'none';
if (show) visible++;
});
document.getElementById('footerCount').textContent = 'Menampilkan ' + visible + ' data';
}
</script>
@endsection

View File

@ -1,81 +1,173 @@
{{-- ================================================================
FILE 2: resources/views/admin/jadwal/create.blade.php
================================================================ --}}
@extends('layouts.app')
@if(session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Tambah Jadwal</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
:root {
--brand:#1a56db; --brand-light:#e8f0fe; --brand-dark:#1648c0;
--danger:#dc2626; --page-bg:#f4f6fa; --surface:#ffffff;
--border:rgba(0,0,0,0.08); --text-1:#111827; --text-2:#6b7280; --text-3:#9ca3af;
--r-sm:8px; --r-md:12px; --r-lg:16px;
--sh-sm:0 1px 4px rgba(0,0,0,.06),0 2px 8px rgba(0,0,0,.04);
}
*,*::before,*::after{font-family:'Plus Jakarta Sans',sans-serif;box-sizing:border-box;}
.form-wrap{background:var(--page-bg);min-height:100vh;padding:28px 32px 56px;}
.form-nav{display:flex;align-items:center;gap:8px;margin-bottom:20px;}
.form-nav a{font-size:13px;font-weight:500;color:var(--text-2);text-decoration:none;display:inline-flex;align-items:center;gap:5px;transition:color .12s;}
.form-nav a:hover{color:var(--brand);}
.form-nav-sep{color:var(--text-3);font-size:13px;}
.form-nav-current{font-size:13px;font-weight:600;color:var(--text-1);}
.form-page-title{margin-bottom:24px;}
.form-page-title .eyebrow{font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--brand);margin-bottom:4px;}
.form-page-title h1{font-size:24px;font-weight:700;color:var(--text-1);margin:0;letter-spacing:-.4px;}
.form-card{
background:var(--surface);
border-radius:var(--r-lg);
border:1px solid var(--border);
box-shadow:var(--sh-sm);
<div class="card shadow">
<div class="card-body">
width:100%;
max-width:100%;
}
.form-card-header{padding:20px 24px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;}
.form-card-icon{width:38px;height:38px;border-radius:9px;background:var(--brand-light);color:var(--brand);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;}
.form-card-header-text h5{font-size:15px;font-weight:700;color:var(--text-1);margin:0 0 2px;}
.form-card-header-text p{font-size:12px;color:var(--text-2);margin:0;}
.form-card-body{padding:24px;}
.form-errors{background:#fff5f5;border:1px solid rgba(220,38,38,.2);border-left:4px solid var(--danger);border-radius:var(--r-md);padding:14px 16px;margin-bottom:22px;}
.form-errors ul{margin:0;padding-left:18px;}
.form-errors li{font-size:13px;color:var(--danger);font-weight:500;line-height:1.7;}
.field{margin-bottom:18px;}
.field-label{font-size:13px;font-weight:600;color:var(--text-1);margin-bottom:7px;display:block;}
.field-input,.field-select{width:100%;padding:10px 13px;border:1.5px solid var(--border);border-radius:var(--r-sm);font-size:14px;color:var(--text-1);background:#fff;outline:none;transition:border-color .15s,box-shadow .15s;font-family:'Plus Jakarta Sans',sans-serif;appearance:none;}
.field-input::placeholder{color:var(--text-3);}
.field-input:focus,.field-select:focus{border-color:var(--brand);box-shadow:0 0 0 3px rgba(26,86,219,.1);}
.field-input[type="time"]{font-family:'DM Mono',monospace;cursor:pointer;}
.select-wrap{position:relative;}
.select-wrap::after{content:'\f107';font-family:'Font Awesome 6 Free';font-weight:900;position:absolute;right:13px;top:50%;transform:translateY(-50%);color:var(--text-3);pointer-events:none;font-size:12px;}
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.form-footer{display:flex;align-items:center;gap:10px;padding-top:20px;border-top:1px solid var(--border);margin-top:24px;}
.btn-submit{display:inline-flex;align-items:center;gap:7px;background:var(--brand);color:#fff;border:none;border-radius:var(--r-sm);padding:10px 22px;font-size:13px;font-weight:600;cursor:pointer;text-decoration:none;transition:all .15s ease;box-shadow:0 2px 8px rgba(26,86,219,.25);font-family:'Plus Jakarta Sans',sans-serif;}
.btn-submit:hover{background:var(--brand-dark);transform:translateY(-1px);color:#fff;}
.btn-submit i{font-size:11px;}
.btn-back{display:inline-flex;align-items:center;gap:7px;background:#f8fafc;color:var(--text-1);border:1.5px solid rgba(0,0,0,.12);border-radius:var(--r-sm);padding:10px 18px;font-size:13px;font-weight:600;cursor:pointer;text-decoration:none;transition:all .12s ease;font-family:'Plus Jakarta Sans',sans-serif;}
.btn-back:hover{background:#eef2f6;color:var(--text-1);}
@media(max-width:768px){.form-wrap{padding:20px 16px 40px;}.form-card{max-width:100%;}.field-row{grid-template-columns:1fr;}}
</style>
<div class="form-wrap">
<div class="form-nav">
<a href="{{ route('admin.jadwal.index') }}"><i class="fas fa-arrow-left" style="font-size:11px;"></i> Jadwal</a>
<span class="form-nav-sep">/</span>
<span class="form-nav-current">Tambah Baru</span>
</div>
<div class="form-page-title">
<div class="eyebrow">Akademik</div>
<h1>Tambah Jadwal</h1>
</div>
@if(session('error'))
<div class="form-errors" style="max-width:600px;">
<ul><li>{{ session('error') }}</li></ul>
</div>
@endif
<div class="form-card">
<div class="form-card-header">
<div class="form-card-icon"><i class="fas fa-calendar-plus"></i></div>
<div class="form-card-header-text">
<h5>Data Jadwal Baru</h5>
<p>Isi semua informasi jadwal pelajaran</p>
</div>
</div>
<div class="form-card-body">
@if ($errors->any())
<div class="form-errors">
<ul>@foreach ($errors->all() as $error)<li>{{ $error }}</li>@endforeach</ul>
</div>
@endif
<form action="{{ route('admin.jadwal.store') }}" method="POST">
@csrf
@csrf
<div class="form-group">
<label>Kelas</label>
<select name="kelas_id" class="form-control">
@foreach($kelas as $k)
<option value="{{ $k->id }}">{{ $k->nama_kelas }}</option>
@endforeach
</select>
</div>
{{-- KELAS --}}
<div class="field">
<label class="field-label">Kelas</label>
<div class="select-wrap">
<select name="kelas_id" class="field-select">
<option value="">-- Pilih Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}" {{ old('kelas_id') == $k->id ? 'selected' : '' }}>{{ $k->nama_kelas }}</option>
@endforeach
</select>
</div>
</div>
<div class="form-group">
<label>Mata Pelajaran</label>
<select name="mata_pelajaran_id" class="form-control">
@foreach($mapel as $m)
<option value="{{ $m->id }}">{{ $m->nama_mapel }}</option>
@endforeach
</select>
</div>
{{-- MAPEL --}}
<div class="field">
<label class="field-label">Mata Pelajaran</label>
<div class="select-wrap">
<select name="mata_pelajaran_id" class="field-select">
<option value="">-- Pilih Mata Pelajaran --</option>
@foreach($mapel as $m)
<option value="{{ $m->id }}" {{ old('mata_pelajaran_id') == $m->id ? 'selected' : '' }}>{{ $m->nama_mapel }}</option>
@endforeach
</select>
</div>
</div>
<!-- 🔥 TAMBAHAN GURU -->
<div class="form-group">
<label>Guru</label>
<select name="guru_id" class="form-control">
@foreach($guru as $g)
<option value="{{ $g->id }}">{{ $g->nama }}</option>
@endforeach
</select>
</div>
{{-- GURU --}}
<div class="field">
<label class="field-label">Guru</label>
<div class="select-wrap">
<select name="guru_id" class="field-select">
<option value="">-- Pilih Guru --</option>
@foreach($guru as $g)
<option value="{{ $g->id }}" {{ old('guru_id') == $g->id ? 'selected' : '' }}>{{ $g->nama }}</option>
@endforeach
</select>
</div>
</div>
<div class="form-group">
<label>Hari</label>
<select name="hari" class="form-control">
<option>Senin</option>
<option>Selasa</option>
<option>Rabu</option>
<option>Kamis</option>
<option>Jumat</option>
</select>
</div>
{{-- HARI --}}
<div class="field">
<label class="field-label">Hari</label>
<div class="select-wrap">
<select name="hari" class="field-select">
<option value="">-- Pilih Hari --</option>
@foreach(['Senin','Selasa','Rabu','Kamis','Jumat'] as $h)
<option value="{{ $h }}" {{ old('hari') == $h ? 'selected' : '' }}>{{ $h }}</option>
@endforeach
</select>
</div>
</div>
<div class="form-group">
<label>Jam Mulai</label>
<input type="time" name="jam_mulai" class="form-control">
</div>
{{-- JAM --}}
<div class="field-row">
<div class="field">
<label class="field-label">Jam Mulai</label>
<input type="time" name="jam_mulai" value="{{ old('jam_mulai') }}" class="field-input">
</div>
<div class="field">
<label class="field-label">Jam Selesai</label>
<input type="time" name="jam_selesai" value="{{ old('jam_selesai') }}" class="field-input">
</div>
</div>
<div class="form-group">
<label>Jam Selesai</label>
<input type="time" name="jam_selesai" class="form-control">
</div>
<button class="btn btn-success">
<i class="fas fa-save"></i> Simpan
</button>
<a href="{{ route('admin.jadwal.index') }}" class="btn btn-secondary">
Kembali
</a>
<div class="form-footer">
<button type="submit" class="btn-submit"><i class="fas fa-save"></i> Simpan</button>
<a href="{{ route('admin.jadwal.index') }}" class="btn-back"><i class="fas fa-times"></i> Batal</a>
</div>
</form>
</div>
</div>

View File

@ -1,87 +1,201 @@
{{-- ================================================================
FILE 3: resources/views/admin/jadwal/edit.blade.php
================================================================ --}}
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Edit Jadwal</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
:root {
--brand:#1a56db; --brand-light:#e8f0fe; --brand-dark:#1648c0;
--warning:#d97706; --warning-light:#fef3c7;
--page-bg:#f4f6fa; --surface:#ffffff;
--border:rgba(0,0,0,0.08); --text-1:#111827; --text-2:#6b7280; --text-3:#9ca3af;
--r-sm:8px; --r-md:12px; --r-lg:16px;
--sh-sm:0 1px 4px rgba(0,0,0,.06),0 2px 8px rgba(0,0,0,.04);
}
*,*::before,*::after{font-family:'Plus Jakarta Sans',sans-serif;box-sizing:border-box;}
<div class="card shadow">
<div class="card-body">
.container,
.container-fluid{
width:100% !important;
max-width:100% !important;
padding-left:0 !important;
padding-right:0 !important;
}
.form-wrap{
background:var(--page-bg);
min-height:100vh;
width:100%;
max-width:100%;
padding:28px 32px 56px;
}
.form-nav{display:flex;align-items:center;gap:8px;margin-bottom:20px;}
.form-nav a{font-size:13px;font-weight:500;color:var(--text-2);text-decoration:none;display:inline-flex;align-items:center;gap:5px;transition:color .12s;}
.form-nav a:hover{color:var(--brand);}
.form-nav-sep{color:var(--text-3);font-size:13px;}
.form-nav-current{font-size:13px;font-weight:600;color:var(--text-1);}
.form-page-title{margin-bottom:24px;}
.form-page-title .eyebrow{font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--warning);margin-bottom:4px;}
.form-page-title h1{font-size:24px;font-weight:700;color:var(--text-1);margin:0;letter-spacing:-.4px;}
.edit-banner{
display:flex;
align-items:center;
gap:12px;
background:var(--warning-light);
border:1px solid rgba(217,119,6,.2);
border-left:4px solid var(--warning);
border-radius:var(--r-md);
padding:13px 16px;
margin-bottom:22px;
font-size:13px;
font-weight:500;
color:#92400e;
width:100%;
max-width:100%;
}
.form-card{
background:var(--surface);
border-radius:var(--r-lg);
border:1px solid var(--border);
box-shadow:var(--sh-sm);
width:100%;
max-width:100%;
}
.form-card-header{padding:20px 24px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;}
.form-card-icon{width:38px;height:38px;border-radius:9px;background:var(--warning-light);color:var(--warning);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;}
.form-card-header-text h5{font-size:15px;font-weight:700;color:var(--text-1);margin:0 0 2px;}
.form-card-header-text p{font-size:12px;color:var(--text-2);margin:0;}
.form-card-body{padding:24px;}
.field{margin-bottom:18px;}
.field-label{font-size:13px;font-weight:600;color:var(--text-1);margin-bottom:7px;display:block;}
.field-input,.field-select{width:100%;padding:10px 13px;border:1.5px solid var(--border);border-radius:var(--r-sm);font-size:14px;color:var(--text-1);background:#fff;outline:none;transition:border-color .15s,box-shadow .15s;font-family:'Plus Jakarta Sans',sans-serif;appearance:none;}
.field-input:focus,.field-select:focus{border-color:var(--brand);box-shadow:0 0 0 3px rgba(26,86,219,.1);}
.field-input[type="time"]{font-family:'DM Mono',monospace;cursor:pointer;}
.select-wrap{position:relative;}
.select-wrap::after{content:'\f107';font-family:'Font Awesome 6 Free';font-weight:900;position:absolute;right:13px;top:50%;transform:translateY(-50%);color:var(--text-3);pointer-events:none;font-size:12px;}
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
.form-footer{display:flex;align-items:center;gap:10px;padding-top:20px;border-top:1px solid var(--border);margin-top:24px;}
.btn-submit{display:inline-flex;align-items:center;gap:7px;background:var(--warning);color:#fff;border:none;border-radius:var(--r-sm);padding:10px 22px;font-size:13px;font-weight:600;cursor:pointer;text-decoration:none;transition:all .15s ease;box-shadow:0 2px 8px rgba(217,119,6,.3);font-family:'Plus Jakarta Sans',sans-serif;}
.btn-submit:hover{background:#b45309;transform:translateY(-1px);color:#fff;}
.btn-submit i{font-size:11px;}
.btn-back{display:inline-flex;align-items:center;gap:7px;background:#f8fafc;color:var(--text-1);border:1.5px solid rgba(0,0,0,.12);border-radius:var(--r-sm);padding:10px 18px;font-size:13px;font-weight:600;cursor:pointer;text-decoration:none;transition:all .12s ease;font-family:'Plus Jakarta Sans',sans-serif;}
.btn-back:hover{background:#eef2f6;color:var(--text-1);}
@media(max-width:768px){.form-wrap{padding:20px 16px 40px;}.form-card,.edit-banner{max-width:100%;}.field-row{grid-template-columns:1fr;}}
</style>
<div class="form-wrap">
<div class="form-nav">
<a href="{{ route('admin.jadwal.index') }}"><i class="fas fa-arrow-left" style="font-size:11px;"></i> Jadwal</a>
<span class="form-nav-sep">/</span>
<span class="form-nav-current">Edit</span>
</div>
<div class="form-page-title">
<div class="eyebrow">Akademik</div>
<h1>Edit Jadwal</h1>
</div>
<div class="edit-banner">
<i class="fas fa-pen" style="font-size:14px;flex-shrink:0;"></i>
Mengedit jadwal:
<strong>&nbsp;{{ $jadwal->mapel->nama_mapel ?? '' }} — {{ $jadwal->hari }}</strong>
</div>
<div class="form-card">
<div class="form-card-header">
<div class="form-card-icon"><i class="fas fa-calendar-alt"></i></div>
<div class="form-card-header-text">
<h5>Ubah Data Jadwal</h5>
<p>Perubahan akan langsung tersimpan ke sistem</p>
</div>
</div>
<div class="form-card-body">
<form action="{{ route('admin.jadwal.update', $jadwal->id) }}" method="POST">
@csrf
@method('PUT')
<!-- KELAS -->
<div class="form-group mb-3">
<label>Kelas</label>
<select name="kelas_id" class="form-control" required>
@foreach($kelas as $k)
<option value="{{ $k->id }}"
{{ $jadwal->kelas_id == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
{{-- KELAS --}}
<div class="field">
<label class="field-label">Kelas</label>
<div class="select-wrap">
<select name="kelas_id" class="field-select" required>
@foreach($kelas as $k)
<option value="{{ $k->id }}" {{ $jadwal->kelas_id == $k->id ? 'selected' : '' }}>{{ $k->nama_kelas }}</option>
@endforeach
</select>
</div>
</div>
<!-- MAPEL -->
<div class="form-group mb-3">
<label>Mata Pelajaran</label>
<select name="mata_pelajaran_id" class="form-control" required>
@foreach($mapel as $m)
<option value="{{ $m->id }}"
{{ $jadwal->mata_pelajaran_id == $m->id ? 'selected' : '' }}>
{{ $m->nama_mapel }}
</option>
@endforeach
</select>
{{-- MAPEL --}}
<div class="field">
<label class="field-label">Mata Pelajaran</label>
<div class="select-wrap">
<select name="mata_pelajaran_id" class="field-select" required>
@foreach($mapel as $m)
<option value="{{ $m->id }}" {{ $jadwal->mata_pelajaran_id == $m->id ? 'selected' : '' }}>{{ $m->nama_mapel }}</option>
@endforeach
</select>
</div>
</div>
<!-- 🔥 TAMBAHAN WAJIB: GURU -->
<div class="form-group mb-3">
<label>Guru</label>
<select name="guru_id" class="form-control" required>
@foreach($guru as $g)
<option value="{{ $g->id }}"
{{ $jadwal->guru_id == $g->id ? 'selected' : '' }}>
{{ $g->nama }}
</option>
@endforeach
</select>
{{-- GURU --}}
<div class="field">
<label class="field-label">Guru</label>
<div class="select-wrap">
<select name="guru_id" class="field-select" required>
@foreach($guru as $g)
<option value="{{ $g->id }}" {{ $jadwal->guru_id == $g->id ? 'selected' : '' }}>{{ $g->nama }}</option>
@endforeach
</select>
</div>
</div>
<!-- HARI -->
<div class="form-group mb-3">
<label>Hari</label>
<input type="text" name="hari" value="{{ $jadwal->hari }}" class="form-control" required>
{{-- HARI --}}
<div class="field">
<label class="field-label">Hari</label>
<div class="select-wrap">
<select name="hari" class="field-select" required>
@foreach(['Senin','Selasa','Rabu','Kamis','Jumat'] as $h)
<option value="{{ $h }}" {{ $jadwal->hari == $h ? 'selected' : '' }}>{{ $h }}</option>
@endforeach
</select>
</div>
</div>
<!-- JAM MULAI -->
<div class="form-group mb-3">
<label>Jam Mulai</label>
<input type="time" name="jam_mulai" value="{{ $jadwal->jam_mulai }}" class="form-control" required>
{{-- JAM --}}
<div class="field-row">
<div class="field">
<label class="field-label">Jam Mulai</label>
<input type="time" name="jam_mulai" value="{{ $jadwal->jam_mulai }}" class="field-input" required>
</div>
<div class="field">
<label class="field-label">Jam Selesai</label>
<input type="time" name="jam_selesai" value="{{ $jadwal->jam_selesai }}" class="field-input" required>
</div>
</div>
<!-- JAM SELESAI -->
<div class="form-group mb-3">
<label>Jam Selesai</label>
<input type="time" name="jam_selesai" value="{{ $jadwal->jam_selesai }}" class="form-control" required>
<div class="form-footer">
<button type="submit" class="btn-submit"><i class="fas fa-save"></i> Update</button>
<a href="{{ route('admin.jadwal.index') }}" class="btn-back"><i class="fas fa-arrow-left"></i> Kembali</a>
</div>
<!-- BUTTON -->
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Update
</button>
<a href="{{ route('admin.jadwal.index') }}" class="btn btn-secondary">
Kembali
</a>
</form>
</div>
</div>
</div>
@endsection
@endsection
{{-- ================================================================
FILE 4: resources/views/admin/jadwal/grid.blade.php
================================================================ --}}

View File

@ -1,188 +1,328 @@
{{-- ================================================================
FILE: resources/views/admin/jadwal/index.blade.php
================================================================ --}}
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<!-- Judul -->
<h1 class="h3 mb-2 text-gray-800">Data Jadwal </h1>
<p class="mb-4">Pengelolaan jadwal SMA Negeri 1 Bondowoso</p>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
<!-- Alert -->
@if(session('success'))
<div class="alert alert-success">
{{ session('success') }}
:root {
--brand: #1a56db;
--brand-light: #e8f0fe;
--brand-dark: #1648c0;
--success: #0d9488;
--success-light:#ccfbf1;
--danger: #dc2626;
--danger-light: #fee2e2;
--warning: #d97706;
--warning-light:#fef3c7;
--page-bg: #f4f6fa;
--surface: #ffffff;
--border: rgba(0,0,0,0.07);
--border-md: rgba(0,0,0,0.12);
--text-1: #111827;
--text-2: #6b7280;
--text-3: #9ca3af;
--r-sm: 8px; --r-md: 12px; --r-lg: 16px;
--sh-sm: 0 1px 4px rgba(0,0,0,.06), 0 2px 8px rgba(0,0,0,.04);
--sh-md: 0 4px 16px rgba(0,0,0,.08);
}
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: 'Plus Jakarta Sans', sans-serif;
}
.jd-wrap { background: var(--page-bg); min-height: 100vh; padding: 28px 32px 56px; }
/* header */
.jd-header { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 24px; }
.jd-header-left .eyebrow { font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--brand); margin-bottom: 4px; }
.jd-header-left h1 { font-size: 24px; font-weight: 700; color: var(--text-1); margin: 0; letter-spacing: -.4px; }
.jd-header-left p { font-size: 13px; color: var(--text-2); margin: 4px 0 0; }
.btn-add {
display: inline-flex; align-items: center; gap: 7px;
background: var(--brand); color: #fff; border: none;
border-radius: var(--r-sm); padding: 10px 20px;
font-size: 13px; font-weight: 600; cursor: pointer;
text-decoration: none; transition: all .15s ease;
box-shadow: 0 2px 8px rgba(26,86,219,.25); white-space: nowrap;
}
.btn-add:hover { background: var(--brand-dark); transform: translateY(-1px); color: #fff; box-shadow: 0 4px 12px rgba(26,86,219,.35); }
.btn-add i { font-size: 11px; }
/* alert */
.jd-alert {
display: flex; align-items: center; gap: 12px;
background: var(--success-light); border: 1px solid rgba(13,148,136,.2);
border-left: 4px solid var(--success); border-radius: var(--r-md);
padding: 13px 16px; margin-bottom: 20px;
font-size: 13px; font-weight: 500; color: #065f46; position: relative;
}
.jd-alert-close { position: absolute; right: 14px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; color: #065f46; font-size: 18px; opacity: .6; }
.jd-alert-close:hover { opacity: 1; }
/* filter card */
.filter-card {
background: var(--surface); border-radius: var(--r-lg);
border: 1px solid var(--border); box-shadow: var(--sh-sm);
padding: 16px 20px; margin-bottom: 16px;
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.filter-label { font-size: 12px; font-weight: 700; color: var(--text-3); text-transform: uppercase; letter-spacing: .08em; white-space: nowrap; }
.filter-select {
flex: 1; min-width: 200px; max-width: 300px;
padding: 9px 13px; border: 1.5px solid var(--border);
border-radius: var(--r-sm); font-size: 13px; color: var(--text-1);
background: #f8fafc; outline: none; cursor: pointer;
transition: border-color .15s, box-shadow .15s;
font-family: 'Plus Jakarta Sans', sans-serif;
}
.filter-select:focus { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(26,86,219,.1); background: #fff; }
.btn-filter {
display: inline-flex; align-items: center; gap: 6px;
padding: 9px 16px; border-radius: var(--r-sm);
font-size: 13px; font-weight: 600; cursor: pointer;
border: none; text-decoration: none; transition: all .13s ease;
font-family: 'Plus Jakarta Sans', sans-serif;
}
.btn-filter.primary { background: var(--brand); color: #fff; }
.btn-filter.primary:hover { background: var(--brand-dark); color: #fff; }
.btn-filter.reset { background: #f8fafc; color: var(--text-2); border: 1.5px solid var(--border-md); }
.btn-filter.reset:hover { background: #eef2f6; color: var(--text-1); }
.btn-filter i { font-size: 11px; }
/* info banner */
.filter-info {
display: inline-flex; align-items: center; gap: 8px;
background: var(--brand-light); border: 1px solid rgba(26,86,219,.15);
border-radius: var(--r-sm); padding: 8px 14px;
font-size: 13px; font-weight: 500; color: var(--brand);
margin-bottom: 16px;
}
/* table card */
.jd-card { background: var(--surface); border-radius: var(--r-lg); border: 1px solid var(--border); box-shadow: var(--sh-sm); overflow: hidden; }
.jd-card-toolbar { display: flex; align-items: center; justify-content: space-between; padding: 15px 20px; border-bottom: 1px solid var(--border); }
.jd-count { font-size: 13px; font-weight: 600; color: var(--text-2); }
.jd-count strong { color: var(--text-1); font-weight: 700; }
/* table */
.jd-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.jd-table thead tr { background: #f8fafc; border-bottom: 1px solid var(--border); }
.jd-table thead th { padding: 12px 16px; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); white-space: nowrap; border: none; }
.jd-table thead th:first-child { padding-left: 20px; }
.jd-table thead th:last-child { padding-right: 20px; text-align: right; }
.jd-table tbody tr { border-bottom: 1px solid var(--border); transition: background .12s; }
.jd-table tbody tr:last-child { border-bottom: none; }
.jd-table tbody tr:hover { background: #f8fafc; }
.jd-table td { padding: 13px 16px; color: var(--text-1); vertical-align: middle; border: none; }
.jd-table td:first-child { padding-left: 20px; }
.jd-table td:last-child { padding-right: 20px; }
.cell-no { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-3); font-weight: 500; width: 44px; }
.cell-mapel { font-weight: 600; color: var(--text-1); }
.cell-muted { color: var(--text-2); font-size: 13px; }
/* hari pills */
.hari-pill {
display: inline-flex; align-items: center; padding: 3px 10px;
border-radius: 999px; font-size: 12px; font-weight: 600;
}
.hari-senin { background: #eff6ff; color: #1d4ed8; }
.hari-selasa { background: #f0fdf4; color: #15803d; }
.hari-rabu { background: #fefce8; color: #a16207; }
.hari-kamis { background: #fdf4ff; color: #9333ea; }
.hari-jumat { background: #fff7ed; color: #c2410c; }
.hari-default { background: #f1f5f9; color: #475569; }
/* jam badge */
.jam-badge {
display: inline-flex; align-items: center; gap: 5px;
font-family: 'DM Mono', monospace; font-size: 12.5px;
font-weight: 500; color: var(--text-2);
background: #f8fafc; border: 1px solid var(--border);
border-radius: 6px; padding: 3px 9px;
}
.jam-badge i { font-size: 10px; color: var(--text-3); }
/* kelas pill */
.kelas-pill { display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; background: var(--brand-light); color: var(--brand); border: 1px solid rgba(26,86,219,.15); }
/* guru pill */
.guru-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 500; color: var(--text-1); }
.guru-avatar { width: 24px; height: 24px; border-radius: 50%; background: linear-gradient(135deg, #1a56db, #6366f1); color: #fff; font-size: 9px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
/* action buttons */
.action-group { display: flex; align-items: center; gap: 6px; justify-content: flex-end; }
.btn-act { display: inline-flex; align-items: center; gap: 5px; padding: 6px 12px; border-radius: 7px; font-size: 12px; font-weight: 600; cursor: pointer; text-decoration: none; border: 1px solid; transition: all .12s ease; white-space: nowrap; font-family: 'Plus Jakarta Sans', sans-serif; background: none; }
.btn-act i { font-size: 10px; }
.btn-edit { background: #f8fafc; border-color: var(--border-md); color: var(--text-1); }
.btn-edit:hover { background: #eef2f6; color: var(--text-1); }
.btn-del { background: #fff5f5; border-color: rgba(220,38,38,.18); color: var(--danger); }
.btn-del:hover { background: var(--danger-light); border-color: rgba(220,38,38,.35); color: var(--danger); }
/* empty */
.empty-state { padding: 52px 24px; text-align: center; }
.empty-icon { width: 54px; height: 54px; border-radius: 50%; background: #f1f5f9; display: flex; align-items: center; justify-content: center; margin: 0 auto 14px; color: var(--text-3); font-size: 22px; }
.empty-state p { font-size: 14px; color: var(--text-2); font-weight: 500; margin: 0 0 4px; }
.empty-state small { font-size: 12px; color: var(--text-3); }
@media (max-width: 768px) {
.jd-wrap { padding: 20px 16px 40px; }
.jd-header { flex-direction: column; align-items: flex-start; gap: 14px; }
.jd-scroll { overflow-x: auto; }
}
</style>
<div class="jd-wrap">
{{-- HEADER --}}
<div class="jd-header">
<div class="jd-header-left">
<div class="eyebrow">Akademik</div>
<h1>Data Jadwal</h1>
<p>Pengelolaan jadwal SMA Negeri 1 Bondowoso</p>
</div>
<a href="{{ route('admin.jadwal.create') }}" class="btn-add">
<i class="fas fa-plus"></i> Tambah Jadwal
</a>
</div>
{{-- ALERT --}}
@if(session('success'))
<div class="jd-alert" id="jdAlert">
<i class="fas fa-check-circle" style="font-size:15px;flex-shrink:0;"></i>
{{ session('success') }}
<button class="jd-alert-close" onclick="document.getElementById('jdAlert').remove()">&times;</button>
</div>
@endif
<!-- Card -->
<div class="card shadow mb-4">
<!-- Header -->
<div class="card-header py-3 d-flex justify-content-between align-items-center">
<h6 class="m-0 font-weight-bold text-primary">Tabel Jadwal</h6>
<a href="{{ route('admin.jadwal.create') }}" class="btn btn-primary btn-sm">
<i class="fas fa-plus"></i> Tambah Jadwal
{{-- FILTER --}}
<form method="GET" action="{{ route('admin.jadwal.index') }}">
<div class="filter-card">
<span class="filter-label"><i class="fas fa-filter" style="font-size:10px;"></i> Filter Kelas</span>
<select name="kelas_id" class="filter-select">
<option value="">Semua Kelas</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}" {{ request('kelas_id') == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
<button type="submit" class="btn-filter primary">
<i class="fas fa-search"></i> Tampilkan
</button>
<a href="{{ route('admin.jadwal.index') }}" class="btn-filter reset">
<i class="fas fa-times"></i> Reset
</a>
</div>
</form>
<!-- FILTER -->
<div class="card-body pb-0">
<form method="GET" action="{{ route('admin.jadwal.index') }}">
<div class="row">
<div class="col-md-4 mb-2">
<select name="kelas_id" class="form-control">
<option value="">-- Semua Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}"
{{ request('kelas_id') == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
</div>
<div class="col-md-2 mb-2">
<button class="btn btn-primary btn-block">
<i class="fas fa-filter"></i> Filter
</button>
</div>
<div class="col-md-2 mb-2">
<a href="{{ route('admin.jadwal.index') }}" class="btn btn-secondary btn-block">
Reset
</a>
</div>
</div>
</form>
@if(request('kelas_id') && $kelas->where('id', request('kelas_id'))->first())
<div class="alert alert-info mt-2">
Menampilkan jadwal kelas:
<b>{{ $kelas->where('id', request('kelas_id'))->first()->nama_kelas }}</b>
</div>
@endif
@if(request('kelas_id') && $kelas->where('id', request('kelas_id'))->first())
<div class="filter-info">
<i class="fas fa-info-circle"></i>
Menampilkan jadwal kelas:
<strong>{{ $kelas->where('id', request('kelas_id'))->first()->nama_kelas }}</strong>
</div>
@endif
<!-- Body -->
<div class="card-body">
<div class="table-responsive">
<!-- <form method="GET" class="mb-3 d-flex gap-2">
<select name="kelas_id" class="form-control w-25">
<option value="">-- Semua Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}"
{{ request('kelas_id') == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
<!-- <button class="btn btn-primary">
<i class="fas fa-filter"></i> Filter
</button>
<a href="{{ route('admin.jadwal.index') }}" class="btn btn-secondary">
Reset
</a> -->
</form>
<table class="table table-bordered table-hover">
<thead class="thead-light text-center">
<tr>
<th>No</th>
<th>Kelas</th>
<th>Mata Pelajaran</th>
<th>Guru</th>
<th>Hari</th>
<th>Jam</th>
<th width="150px">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($jadwal as $j)
<tr>
<td class="text-center">{{ $loop->iteration }}</td>
<td class="text-center">
<span class="badge badge-info px-3 py-2">
{{ $j->kelas->nama_kelas ?? '-' }}
</span>
</td>
<td>
<b>{{ $j->mapel->nama_mapel ?? '-' }}</b>
</td>
<!-- 🔥 TAMBAHAN GURU -->
<td>
<span class="badge badge-primary px-3 py-2">
{{ $j->guru->nama ?? '-' }}
</span>
</td>
<td class="text-center">
<span class="badge badge-secondary">
{{ $j->hari }}
</span>
</td>
<td class="text-center">
<span class="badge badge-light">
{{ \Carbon\Carbon::parse($j->jam_mulai)->format('H:i') }}
-
{{ \Carbon\Carbon::parse($j->jam_selesai)->format('H:i') }}
</span>
</td>
<td class="text-center">
@if($jadwal->isEmpty())
<tr>
<td colspan="7" class="text-center text-muted">
Tidak ada jadwal
</td>
</tr>
@endif
<!-- EDIT -->
<a href="{{ route('admin.jadwal.edit', $j->id) }}"
class="btn btn-warning btn-sm">
<i class="fas fa-edit"></i>
</a>
<!-- DELETE -->
<form action="{{ route('admin.jadwal.destroy', $j->id) }}"
method="POST"
class="d-inline">
@csrf
@method('DELETE')
<button class="btn btn-danger btn-sm"
onclick="return confirm('Yakin hapus jadwal ini?')">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted">
Data jadwal belum tersedia
</td>
</tr>
@endforelse
</tbody>
</table>
{{-- TABLE CARD --}}
<div class="jd-card">
<div class="jd-card-toolbar">
<div class="jd-count">
Total <strong>{{ $jadwal->count() }}</strong> jadwal
</div>
</div>
<div class="jd-scroll">
<table class="jd-table">
<thead>
<tr>
<th>No</th>
<th>Kelas</th>
<th>Mata Pelajaran</th>
<th>Guru</th>
<th>Hari</th>
<th>Jam</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
@forelse($jadwal as $j)
<tr>
<td class="cell-no">{{ str_pad($loop->iteration, 2, '0', STR_PAD_LEFT) }}</td>
<td>
<span class="kelas-pill">{{ $j->kelas->nama_kelas ?? '—' }}</span>
</td>
<td class="cell-mapel">{{ $j->mapel->nama_mapel ?? '—' }}</td>
<td>
<div class="guru-pill">
<div class="guru-avatar">{{ strtoupper(substr($j->guru->nama ?? 'G', 0, 2)) }}</div>
{{ $j->guru->nama ?? '—' }}
</div>
</td>
<td>
@php
$hariClass = match(strtolower($j->hari)) {
'senin' => 'hari-senin',
'selasa' => 'hari-selasa',
'rabu' => 'hari-rabu',
'kamis' => 'hari-kamis',
'jumat' => 'hari-jumat',
default => 'hari-default',
};
@endphp
<span class="hari-pill {{ $hariClass }}">{{ $j->hari }}</span>
</td>
<td>
<span class="jam-badge">
<i class="fas fa-clock"></i>
{{ \Carbon\Carbon::parse($j->jam_mulai)->format('H:i') }} – {{ \Carbon\Carbon::parse($j->jam_selesai)->format('H:i') }}
</span>
</td>
<td>
<div class="action-group">
<a href="{{ route('admin.jadwal.edit', $j->id) }}" class="btn-act btn-edit">
<i class="fas fa-pen"></i> Edit
</a>
<form action="{{ route('admin.jadwal.destroy', $j->id) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" class="btn-act btn-del"
onclick="return confirm('Yakin hapus jadwal ini?')">
<i class="fas fa-trash"></i> Hapus
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7">
<div class="empty-state">
<div class="empty-icon"><i class="fas fa-calendar-times"></i></div>
<p>Belum ada data jadwal</p>
<small>Klik "Tambah Jadwal" untuk menambahkan.</small>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>

View File

@ -1,40 +1,174 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Tambah Kelas</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<div class="card shadow">
<div class="card-body">
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 14px;
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
.sw-topbar { margin-bottom: 24px; }
.sw-topbar h3 { font-size: 21px; font-weight: 700; color: var(--text-dark); margin: 0 0 4px; letter-spacing: -.3px; }
.sw-topbar p { font-size: 13px; color: var(--text-soft); margin: 0; }
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.05);
overflow: hidden;
}
.sw-card-header {
display: flex; align-items: center; gap: 12px;
padding: 20px 24px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, #ECFDF5 0%, #D1FAE5 100%);
}
.sw-card-header-icon {
width: 38px; height: 38px;
background: var(--success);
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: #fff; flex-shrink: 0;
}
.sw-card-header h5 { font-size: 15px; font-weight: 700; color: #065F46; margin: 0; }
.sw-card-header p { font-size: 12.5px; color: #047857; margin: 2px 0 0; }
.sw-card-body { padding: 28px 24px; }
.sw-section {
display: flex; align-items: center; gap: 10px;
margin: 0 0 20px;
}
.sw-section-label {
font-size: 12px; font-weight: 700; color: var(--success);
text-transform: uppercase; letter-spacing: .8px; white-space: nowrap;
}
.sw-section-line {
flex: 1; height: 1px;
background: linear-gradient(to right, #A7F3D0, transparent);
}
.sw-form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
.sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-mid); }
.sw-label span { color: var(--danger); margin-left: 2px; }
.sw-input, .sw-select {
padding: 10px 14px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark);
background: var(--neutral-light);
outline: none;
width: 100%;
transition: border-color .15s, box-shadow .15s, background .15s;
}
.sw-input:focus, .sw-select:focus {
border-color: var(--success);
background: #fff;
box-shadow: 0 0 0 3px rgba(5,150,105,.1);
}
.sw-input::placeholder { color: #C3C7D0; }
.sw-footer {
display: flex; justify-content: space-between; align-items: center;
padding-top: 24px;
border-top: 1px solid var(--border);
margin-top: 8px;
}
.sw-btn {
display: inline-flex; align-items: center; gap: 7px;
padding: 10px 20px;
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-secondary { background: var(--surface); color: var(--text-mid); border: 1.5px solid var(--border); }
.sw-btn-secondary:hover { background: var(--neutral-light); color: var(--text-mid); }
.sw-btn-success { background: var(--success); color: #fff; }
.sw-btn-success:hover { background: #047857; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(5,150,105,.3); color: #fff; }
@media (max-width: 640px) { .sw-page { padding: 16px; } }
</style>
<div class="sw-page">
<div class="sw-topbar">
<h3>Tambah Kelas</h3>
<p>Daftarkan kelas baru ke dalam sistem</p>
</div>
<div class="sw-card">
<div class="sw-card-header">
<div class="sw-card-header-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
</div>
<div>
<h5>Formulir Kelas Baru</h5>
<p>Semua kolom bertanda <span style="color:#DC2626">*</span> wajib diisi</p>
</div>
</div>
<div class="sw-card-body">
<div class="sw-section">
<span class="sw-section-label">Informasi Kelas</span>
<div class="sw-section-line"></div>
</div>
<form action="{{ route('admin.kelas.store') }}" method="POST">
@csrf
<div class="form-group">
<label>Nama Kelas</label>
<input type="text" name="nama_kelas" class="form-control" placeholder="Contoh: X IPA 1">
<div class="sw-form-group">
<label class="sw-label">Nama Kelas <span>*</span></label>
<input type="text" name="nama_kelas" class="sw-input" placeholder="Contoh: X IPA 1">
</div>
<div class="form-group">
<label>Jurusan</label>
<select name="jurusan" class="form-control">
<div class="sw-form-group">
<label class="sw-label">Jurusan <span>*</span></label>
<select name="jurusan" class="sw-select">
<option value="">-- Pilih Jurusan --</option>
<option value="IPA">IPA</option>
<option value="IPS">IPS</option>
</select>
</div>
<button class="btn btn-success">
<i class="fas fa-save"></i> Simpan
</button>
<a href="{{ route('admin.kelas.index') }}" class="btn btn-secondary">
Kembali
</a>
<div class="sw-footer">
<a href="{{ route('admin.kelas.index') }}" class="sw-btn sw-btn-secondary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Kembali
</a>
<button type="submit" class="sw-btn sw-btn-success">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Simpan Kelas
</button>
</div>
</form>
</div>
</div>

View File

@ -1,49 +1,218 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h2 class="mb-4 text-warning">Edit Kelas</h2>
<div class="card shadow-lg border-0">
<div class="card-body">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--warning: #D97706;
--warning-light: #FFFBEB;
--danger: #DC2626;
--danger-light: #FEF2F2;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 14px;
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
.sw-topbar { margin-bottom: 24px; }
.sw-topbar h3 { font-size: 21px; font-weight: 700; color: var(--text-dark); margin: 0 0 4px; letter-spacing: -.3px; }
.sw-topbar p { font-size: 13px; color: var(--text-soft); margin: 0; }
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.05);
overflow: hidden;
}
.sw-card-header {
display: flex; align-items: center; gap: 12px;
padding: 20px 24px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
}
.sw-card-header-icon {
width: 38px; height: 38px;
background: var(--warning);
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: #fff; flex-shrink: 0;
}
.sw-card-header h5 { font-size: 15px; font-weight: 700; color: #92400E; margin: 0; }
.sw-card-header p { font-size: 12.5px; color: #A16207; margin: 2px 0 0; }
.sw-id-chip {
margin-left: auto;
background: var(--warning-light);
border: 1px solid #FDE68A;
border-radius: 99px;
padding: 5px 14px;
font-size: 12px; font-weight: 600;
color: var(--warning);
display: flex; align-items: center; gap: 6px;
white-space: nowrap;
}
.sw-card-body { padding: 28px 24px; }
.sw-section {
display: flex; align-items: center; gap: 10px;
margin: 0 0 20px;
}
.sw-section-label {
font-size: 12px; font-weight: 700; color: var(--warning);
text-transform: uppercase; letter-spacing: .8px; white-space: nowrap;
}
.sw-section-line {
flex: 1; height: 1px;
background: linear-gradient(to right, #FDE68A, transparent);
}
.sw-form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
.sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-mid); }
.sw-label span { color: var(--danger); margin-left: 2px; }
.sw-input {
padding: 10px 14px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark);
background: var(--neutral-light);
outline: none; width: 100%;
transition: border-color .15s, box-shadow .15s, background .15s;
}
.sw-input:focus {
border-color: var(--warning);
background: #fff;
box-shadow: 0 0 0 3px rgba(217,119,6,.1);
}
.sw-input::placeholder { color: #C3C7D0; }
.sw-alert-danger {
display: flex; gap: 10px; align-items: flex-start;
padding: 14px 16px;
background: var(--danger-light);
border: 1px solid #FECACA;
border-radius: var(--radius-md);
color: var(--danger);
font-size: 13px;
margin-bottom: 22px;
}
.sw-alert-danger > div { display: flex; flex-direction: column; gap: 3px; }
.sw-footer {
display: flex; justify-content: space-between; align-items: center;
padding-top: 24px;
border-top: 1px solid var(--border);
margin-top: 8px;
}
.sw-btn {
display: inline-flex; align-items: center; gap: 7px;
padding: 10px 20px;
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-secondary { background: var(--surface); color: var(--text-mid); border: 1.5px solid var(--border); }
.sw-btn-secondary:hover { background: var(--neutral-light); color: var(--text-mid); }
.sw-btn-warning { background: var(--warning); color: #fff; }
.sw-btn-warning:hover { background: #B45309; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(217,119,6,.3); color: #fff; }
@media (max-width: 640px) {
.sw-page { padding: 16px; }
.sw-id-chip { display: none; }
}
</style>
<div class="sw-page">
<div class="sw-topbar">
<h3>Edit Kelas</h3>
<p>Perbarui informasi kelas yang tersimpan di sistem</p>
</div>
<div class="sw-card">
<div class="sw-card-header">
<div class="sw-card-header-icon">
<svg width="18" height="18" 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>
</div>
<div>
<h5>Edit Kelas</h5>
<p>Semua kolom bertanda <span style="color:#DC2626">*</span> wajib diisi</p>
</div>
<div class="sw-id-chip">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M8 12h8M8 16h5"/></svg>
{{ $kelas->nama_kelas }}
</div>
</div>
<div class="sw-card-body">
{{-- ERROR --}}
@if ($errors->any())
<div class="alert alert-danger">
<div class="sw-alert-danger">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" style="flex-shrink:0;margin-top:2px"><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>
<div>
@foreach ($errors->all() as $error)
<div>{{ $error }}</div>
@endforeach
</div>
</div>
@endif
<div class="sw-section">
<span class="sw-section-label">Informasi Kelas</span>
<div class="sw-section-line"></div>
</div>
<form action="{{ route('admin.kelas.update', $kelas->id) }}" method="POST">
@csrf
@method('PUT')
<div class="mb-3">
<label class="form-label fw-bold">Nama Kelas</label>
<input type="text" name="nama_kelas" class="form-control"
value="{{ old('nama_kelas', $kelas->nama_kelas) }}" required>
<div class="sw-form-group">
<label class="sw-label">Nama Kelas <span>*</span></label>
<input type="text" name="nama_kelas" class="sw-input"
value="{{ old('nama_kelas', $kelas->nama_kelas) }}"
placeholder="Contoh: X IPA 1" required>
</div>
<div class="mb-3">
<label class="form-label fw-bold">Jurusan</label>
<input type="text" name="jurusan" class="form-control"
value="{{ old('jurusan', $kelas->jurusan) }}" required>
<div class="sw-form-group">
<label class="sw-label">Jurusan <span>*</span></label>
<input type="text" name="jurusan" class="sw-input"
value="{{ old('jurusan', $kelas->jurusan) }}"
placeholder="Contoh: IPA" required>
</div>
<div class="d-flex justify-content-between">
<a href="{{ route('admin.kelas.index') }}" class="btn btn-secondary">
<div class="sw-footer">
<a href="{{ route('admin.kelas.index') }}" class="sw-btn sw-btn-secondary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Kembali
</a>
<button type="submit" class="btn btn-warning">
<i class="fas fa-edit"></i> Update
<button type="submit" class="sw-btn sw-btn-warning">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Update Kelas
</button>
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@ -1,92 +1,157 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<!-- Judul -->
<h1 class="h3 mb-2 text-gray-800">Data Kelas</h1>
<p class="mb-4">Pengelolaan data kelas SMA Negeri 1 Bondowoso</p>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<!-- Alert -->
: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; }
.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-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-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>
{{-- ALERT --}}
@if(session('success'))
<div class="alert alert-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
<!-- Card -->
<div class="card shadow mb-4">
<!-- Header -->
<div class="card-header py-3 d-flex justify-content-between align-items-center">
<h6 class="m-0 font-weight-bold text-primary">Tabel Data Kelas</h6>
<a href="{{ route('admin.kelas.create') }}" class="btn btn-primary btn-sm">
<i class="fas fa-plus"></i> Tambah Kelas
</a>
</div>
<!-- Body -->
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="thead-light text-center">
<tr>
<th>No</th>
<th>Nama Kelas</th>
<th>Jurusan</th>
<th width="150px">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($kelas as $k)
<tr>
<td class="text-center">{{ $loop->iteration }}</td>
<td>{{ $k->nama_kelas }}</td>
<td class="text-center">
<span class="badge badge-info">
{{ $k->jurusan }}
</span>
</td>
<td class="text-center">
<a href="{{ route('admin.kelas.edit', $k->id) }}"
class="btn btn-warning btn-sm">
<i class="fas fa-edit"></i>
{{-- 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">
<form action="{{ route('admin.kelas.destroy', $k->id) }}"
method="POST" class="d-inline">
@csrf
@method('DELETE')
<button class="btn btn-danger btn-sm"
<button type="button"
class="sw-btn sw-btn-danger sw-btn-sm"
onclick="return confirm('Yakin hapus data kelas ini?')">
<i class="fas fa-trash"></i>
<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>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center text-muted">
Data kelas belum tersedia
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</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>
@endsection

View File

@ -1,38 +1,231 @@
{{-- ============================================================
FILE 2: resources/views/admin/mapel/create.blade.php
============================================================ --}}
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h3>Tambah Mata Pelajaran</h3>
{{-- ERROR --}}
@if ($errors->any())
<div class="alert alert-danger">
@foreach ($errors->all() as $error)
<div>{{ $error }}</div>
@endforeach
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
:root {
--brand: #1a56db;
--brand-light: #e8f0fe;
--brand-dark: #1648c0;
--danger: #dc2626;
--danger-light: #fee2e2;
--page-bg: #f4f6fa;
--surface: #ffffff;
--border: rgba(0,0,0,0.08);
--border-focus: rgba(26,86,219,0.5);
--text-1: #111827;
--text-2: #6b7280;
--text-3: #9ca3af;
--r-sm: 8px; --r-md: 12px; --r-lg: 16px;
--sh-sm: 0 1px 4px rgba(0,0,0,.06), 0 2px 8px rgba(0,0,0,.04);
}
.container,
.container-fluid {
width: 100% !important;
max-width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
*, *::before, *::after { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.form-wrap {
background: var(--page-bg);
min-height: 100vh;
width: 100%;
max-width: 100%;
padding: 28px 32px 56px;
}
/* breadcrumb / back nav */
.form-nav { display: flex; align-items: center; gap: 8px; margin-bottom: 20px; }
.form-nav a { font-size: 13px; font-weight: 500; color: var(--text-2); text-decoration: none; display: inline-flex; align-items: center; gap: 5px; transition: color .12s; }
.form-nav a:hover { color: var(--brand); }
.form-nav-sep { color: var(--text-3); font-size: 13px; }
.form-nav-current { font-size: 13px; font-weight: 600; color: var(--text-1); }
/* page title */
.form-page-title { margin-bottom: 24px; }
.form-page-title .eyebrow { font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--brand); margin-bottom: 4px; }
.form-page-title h1 { font-size: 24px; font-weight: 700; color: var(--text-1); margin: 0; letter-spacing: -.4px; }
/* card */
.form-card {
background: var(--surface);
border-radius: var(--r-lg);
border: 1px solid var(--border);
box-shadow: var(--sh-sm);
width: 100%;
max-width: 100%;
}
.form-card-header { padding: 20px 24px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; }
.form-card-icon { width: 38px; height: 38px; border-radius: 9px; background: var(--brand-light); color: var(--brand); display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
.form-card-header-text h5 { font-size: 15px; font-weight: 700; color: var(--text-1); margin: 0 0 2px; }
.form-card-header-text p { font-size: 12px; color: var(--text-2); margin: 0; }
.form-card-body { padding: 24px; }
/* error list */
.form-errors { background: #fff5f5; border: 1px solid rgba(220,38,38,.2); border-left: 4px solid var(--danger); border-radius: var(--r-md); padding: 14px 16px; margin-bottom: 22px; }
.form-errors ul { margin: 0; padding-left: 18px; }
.form-errors li { font-size: 13px; color: var(--danger); font-weight: 500; line-height: 1.7; }
/* field */
.field { margin-bottom: 20px; }
.field:last-of-type { margin-bottom: 0; }
.field-label { display: flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 600; color: var(--text-1); margin-bottom: 7px; }
.field-label .req { color: var(--danger); font-size: 14px; line-height: 1; }
.field-hint { font-size: 12px; color: var(--text-3); margin-top: 5px; }
.field-input {
width: 100%; padding: 10px 13px;
border: 1.5px solid var(--border);
border-radius: var(--r-sm);
font-size: 14px; color: var(--text-1);
background: #fff;
outline: none;
transition: border-color .15s, box-shadow .15s;
font-family: 'Plus Jakarta Sans', sans-serif;
}
.field-input::placeholder { color: var(--text-3); }
.field-input:focus { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(26,86,219,.1); }
.field-input.is-invalid { border-color: var(--danger); }
.field-input.is-invalid:focus { box-shadow: 0 0 0 3px rgba(220,38,38,.1); }
.field-input[type="number"] { font-family: 'DM Mono', monospace; }
/* input with prefix/suffix */
.field-input-group { display: flex; align-items: stretch; border: 1.5px solid var(--border); border-radius: var(--r-sm); overflow: hidden; transition: border-color .15s, box-shadow .15s; background: #fff; }
.field-input-group:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(26,86,219,.1); }
.field-input-group .input-addon { display: flex; align-items: center; padding: 0 12px; background: #f8fafc; border-right: 1.5px solid var(--border); font-size: 12px; font-weight: 600; color: var(--text-3); white-space: nowrap; }
.field-input-group .input-addon-right { border-right: none; border-left: 1.5px solid var(--border); }
.field-input-group .field-input { border: none; border-radius: 0; box-shadow: none; }
.field-input-group .field-input:focus { box-shadow: none; }
/* form footer */
.form-footer { display: flex; align-items: center; gap: 10px; padding-top: 20px; border-top: 1px solid var(--border); margin-top: 24px; }
.btn-submit {
display: inline-flex; align-items: center; gap: 7px;
background: var(--brand); color: #fff; border: none;
border-radius: var(--r-sm); padding: 10px 22px;
font-size: 13px; font-weight: 600; cursor: pointer;
text-decoration: none; transition: all .15s ease;
box-shadow: 0 2px 8px rgba(26,86,219,.25);
font-family: 'Plus Jakarta Sans', sans-serif;
}
.btn-submit:hover { background: var(--brand-dark); transform: translateY(-1px); color: #fff; }
.btn-submit i { font-size: 11px; }
.btn-back {
display: inline-flex; align-items: center; gap: 7px;
background: #f8fafc; color: var(--text-1);
border: 1.5px solid rgba(0,0,0,.12);
border-radius: var(--r-sm); padding: 10px 18px;
font-size: 13px; font-weight: 600; cursor: pointer;
text-decoration: none; transition: all .12s ease;
font-family: 'Plus Jakarta Sans', sans-serif;
}
.btn-back:hover { background: #eef2f6; color: var(--text-1); }
.btn-back i { font-size: 11px; }
@media (max-width: 768px) {
.form-wrap { padding: 20px 16px 40px; }
.form-card { max-width: 100%; }
}
</style>
<div class="form-wrap">
{{-- BREADCRUMB --}}
<div class="form-nav">
<a href="{{ route('admin.mapel.index') }}">
<i class="fas fa-arrow-left" style="font-size:11px;"></i> Mata Pelajaran
</a>
<span class="form-nav-sep">/</span>
<span class="form-nav-current">Tambah Baru</span>
</div>
<div class="form-page-title">
<div class="eyebrow">Kurikulum</div>
<h1>Tambah Mata Pelajaran</h1>
</div>
<div class="form-card">
<div class="form-card-header">
<div class="form-card-icon"><i class="fas fa-book"></i></div>
<div class="form-card-header-text">
<h5>Data Mata Pelajaran</h5>
<p>Isi semua field yang bertanda <span style="color:#dc2626;">*</span> wajib diisi</p>
</div>
</div>
@endif
<div class="form-card-body">
<form action="{{ route('admin.mapel.store') }}" method="POST">
@csrf
@if ($errors->any())
<div class="form-errors">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="mb-3">
<label>Nama Mapel</label>
<input type="text" name="nama_mapel" class="form-control" required>
<form action="{{ route('admin.mapel.store') }}" method="POST">
@csrf
{{-- Nama Mapel --}}
<div class="field">
<label class="field-label">
Nama Mata Pelajaran <span class="req">*</span>
</label>
<input type="text" name="nama_mapel" value="{{ old('nama_mapel') }}"
class="field-input {{ $errors->has('nama_mapel') ? 'is-invalid' : '' }}"
placeholder="cth. Matematika Wajib" required>
</div>
{{-- Kode Mapel --}}
<div class="field">
<label class="field-label">
Kode Mata Pelajaran <span class="req">*</span>
</label>
<div class="field-input-group">
<span class="input-addon"><i class="fas fa-hashtag" style="font-size:11px;"></i></span>
<input type="text" name="kode_mapel" value="{{ old('kode_mapel') }}"
class="field-input {{ $errors->has('kode_mapel') ? 'is-invalid' : '' }}"
placeholder="cth. MTK-01" required>
</div>
<div class="field-hint">Gunakan kode unik untuk setiap mata pelajaran.</div>
</div>
{{-- Jam Pelajaran --}}
<div class="field">
<label class="field-label">
Jam Pelajaran <span class="req">*</span>
</label>
<div class="field-input-group">
<input type="number" name="jam_pelajaran" value="{{ old('jam_pelajaran') }}"
class="field-input {{ $errors->has('jam_pelajaran') ? 'is-invalid' : '' }}"
placeholder="0" min="1" max="40" required>
<span class="input-addon input-addon-right">jam / minggu</span>
</div>
</div>
<div class="form-footer">
<button type="submit" class="btn-submit">
<i class="fas fa-save"></i> Simpan
</button>
<a href="{{ route('admin.mapel.index') }}" class="btn-back">
<i class="fas fa-times"></i> Batal
</a>
</div>
</form>
</div>
</div>
<div class="mb-3">
<label>Kode Mapel</label>
<input type="text" name="kode_mapel" class="form-control" required>
</div>
<div class="mb-3">
<label>Jam Pelajaran</label>
<input type="number" name="jam_pelajaran" class="form-control" required>
</div>
<button class="btn btn-success">Simpan</button>
<a href="{{ route('admin.mapel.index') }}" class="btn btn-secondary">Kembali</a>
</form>
</div>
@endsection

View File

@ -1,37 +1,213 @@
{{-- ============================================================
FILE 3: resources/views/admin/mapel/edit.blade.php
============================================================ --}}
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Edit Mata Pelajaran</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
<div class="card shadow">
<div class="card-body">
:root {
--brand: #1a56db;
--brand-light: #e8f0fe;
--brand-dark: #1648c0;
--warning: #d97706;
--warning-light:#fef3c7;
--danger: #dc2626;
--page-bg: #f4f6fa;
--surface: #ffffff;
--border: rgba(0,0,0,0.08);
--text-1: #111827;
--text-2: #6b7280;
--text-3: #9ca3af;
--r-sm: 8px; --r-md: 12px; --r-lg: 16px;
--sh-sm: 0 1px 4px rgba(0,0,0,.06), 0 2px 8px rgba(0,0,0,.04);
}
.container,
.container-fluid {
width: 100% !important;
max-width: 100% !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
*, *::before, *::after { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.form-wrap { background: var(--page-bg); min-height: 100vh; padding: 28px 32px 56px; }
.form-nav { display: flex; align-items: center; gap: 8px; margin-bottom: 20px; }
.form-nav a { font-size: 13px; font-weight: 500; color: var(--text-2); text-decoration: none; display: inline-flex; align-items: center; gap: 5px; transition: color .12s; }
.form-nav a:hover { color: var(--brand); }
.form-nav-sep { color: var(--text-3); font-size: 13px; }
.form-nav-current { font-size: 13px; font-weight: 600; color: var(--text-1); }
.form-page-title { margin-bottom: 24px; }
.form-page-title .eyebrow { font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--warning); margin-bottom: 4px; }
.form-page-title h1 { font-size: 24px; font-weight: 700; color: var(--text-1); margin: 0; letter-spacing: -.4px; }
/* edit indicator banner */
.edit-banner {
display: flex;
align-items: center;
gap: 12px;
background: var(--warning-light);
border: 1px solid rgba(217,119,6,.2);
border-left: 4px solid var(--warning);
border-radius: var(--r-md);
padding: 13px 16px;
margin-bottom: 22px;
font-size: 13px;
font-weight: 500;
color: #92400e;
width: 100%;
max-width: 100%;
}
.form-card {
background: var(--surface);
border-radius: var(--r-lg);
border: 1px solid var(--border);
box-shadow: var(--sh-sm);
width: 100%;
max-width: 100%;
}
.form-card-header { padding: 20px 24px 16px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; }
.form-card-icon { width: 38px; height: 38px; border-radius: 9px; background: var(--warning-light); color: var(--warning); display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
.form-card-header-text h5 { font-size: 15px; font-weight: 700; color: var(--text-1); margin: 0 0 2px; }
.form-card-header-text p { font-size: 12px; color: var(--text-2); margin: 0; }
.form-card-body { padding: 24px; }
.field { margin-bottom: 20px; }
.field-label { display: flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 600; color: var(--text-1); margin-bottom: 7px; }
.field-hint { font-size: 12px; color: var(--text-3); margin-top: 5px; }
.field-input {
width: 100%; padding: 10px 13px;
border: 1.5px solid var(--border);
border-radius: var(--r-sm);
font-size: 14px; color: var(--text-1);
background: #fff; outline: none;
transition: border-color .15s, box-shadow .15s;
font-family: 'Plus Jakarta Sans', sans-serif;
}
.field-input::placeholder { color: var(--text-3); }
.field-input:focus { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(26,86,219,.1); }
.field-input[type="number"] { font-family: 'DM Mono', monospace; }
.field-input-group { display: flex; align-items: stretch; border: 1.5px solid var(--border); border-radius: var(--r-sm); overflow: hidden; transition: border-color .15s, box-shadow .15s; background: #fff; }
.field-input-group:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(26,86,219,.1); }
.field-input-group .input-addon { display: flex; align-items: center; padding: 0 12px; background: #f8fafc; border-right: 1.5px solid var(--border); font-size: 12px; font-weight: 600; color: var(--text-3); white-space: nowrap; }
.field-input-group .input-addon-right { border-right: none; border-left: 1.5px solid var(--border); }
.field-input-group .field-input { border: none; border-radius: 0; box-shadow: none; }
.field-input-group .field-input:focus { box-shadow: none; }
.form-footer { display: flex; align-items: center; gap: 10px; padding-top: 20px; border-top: 1px solid var(--border); margin-top: 24px; }
.btn-submit {
display: inline-flex; align-items: center; gap: 7px;
background: var(--warning); color: #fff; border: none;
border-radius: var(--r-sm); padding: 10px 22px;
font-size: 13px; font-weight: 600; cursor: pointer;
text-decoration: none; transition: all .15s ease;
box-shadow: 0 2px 8px rgba(217,119,6,.3);
font-family: 'Plus Jakarta Sans', sans-serif;
}
.btn-submit:hover { background: #b45309; transform: translateY(-1px); color: #fff; }
.btn-submit i { font-size: 11px; }
.btn-back {
display: inline-flex; align-items: center; gap: 7px;
background: #f8fafc; color: var(--text-1);
border: 1.5px solid rgba(0,0,0,.12);
border-radius: var(--r-sm); padding: 10px 18px;
font-size: 13px; font-weight: 600; cursor: pointer;
text-decoration: none; transition: all .12s ease;
font-family: 'Plus Jakarta Sans', sans-serif;
}
.btn-back:hover { background: #eef2f6; color: var(--text-1); }
.btn-back i { font-size: 11px; }
@media (max-width: 768px) {
.form-wrap { padding: 20px 16px 40px; }
.form-card { max-width: 100%; }
.edit-banner { max-width: 100%; }
}
</style>
<div class="form-wrap">
<div class="form-nav">
<a href="{{ route('admin.mapel.index') }}">
<i class="fas fa-arrow-left" style="font-size:11px;"></i> Mata Pelajaran
</a>
<span class="form-nav-sep">/</span>
<span class="form-nav-current">Edit</span>
</div>
<div class="form-page-title">
<div class="eyebrow">Kurikulum</div>
<h1>Edit Mata Pelajaran</h1>
</div>
<div class="edit-banner">
<i class="fas fa-pen" style="font-size:14px; flex-shrink:0;"></i>
Anda sedang mengedit: <strong>&nbsp;{{ $mapel->nama_mapel }}</strong>
</div>
<div class="form-card">
<div class="form-card-header">
<div class="form-card-icon"><i class="fas fa-pen"></i></div>
<div class="form-card-header-text">
<h5>Ubah Data Mata Pelajaran</h5>
<p>Perubahan akan langsung tersimpan ke sistem</p>
</div>
</div>
<div class="form-card-body">
<form action="{{ route('admin.mapel.update', $mapel->id) }}" method="POST">
@csrf
@method('PUT')
<div class="form-group">
<label>Nama Mapel</label>
<input type="text" name="nama_mapel" value="{{ $mapel->nama_mapel }}" class="form-control">
{{-- Nama Mapel --}}
<div class="field">
<label class="field-label">Nama Mata Pelajaran</label>
<input type="text" name="nama_mapel" value="{{ $mapel->nama_mapel }}"
class="field-input" placeholder="cth. Matematika Wajib">
</div>
<div class="form-group">
<label>Kode Mapel</label>
<input type="text" name="kode_mapel" value="{{ $mapel->kode_mapel }}" class="form-control">
{{-- Kode Mapel --}}
<div class="field">
<label class="field-label">Kode Mata Pelajaran</label>
<div class="field-input-group">
<span class="input-addon"><i class="fas fa-hashtag" style="font-size:11px;"></i></span>
<input type="text" name="kode_mapel" value="{{ $mapel->kode_mapel }}"
class="field-input" placeholder="cth. MTK-01">
</div>
</div>
<div class="form-group">
<label>Jam Pelajaran</label>
<input type="number" name="jam_pelajaran" value="{{ $mapel->jam_pelajaran }}" class="form-control">
{{-- Jam Pelajaran --}}
<div class="field">
<label class="field-label">Jam Pelajaran</label>
<div class="field-input-group">
<input type="number" name="jam_pelajaran" value="{{ $mapel->jam_pelajaran }}"
class="field-input" placeholder="0" min="1" max="40">
<span class="input-addon input-addon-right">jam / minggu</span>
</div>
</div>
<button class="btn btn-primary">Update</button>
<a href="{{ route('admin.mapel.index') }}" class="btn btn-secondary">Kembali</a>
<div class="form-footer">
<button type="submit" class="btn-submit">
<i class="fas fa-save"></i> Update
</button>
<a href="{{ route('admin.mapel.index') }}" class="btn-back">
<i class="fas fa-arrow-left"></i> Kembali
</a>
</div>
</form>
</div>
</div>

View File

@ -1,84 +1,218 @@
{{-- ============================================================
FILE 1: resources/views/admin/mapel/index.blade.php
============================================================ --}}
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<!-- Page Heading -->
<h1 class="h3 mb-2 text-gray-800">Data Mata Pelajaran</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap');
<!-- Alert -->
@if(session('success'))
<div class="alert alert-success">
{{ session('success') }}
:root {
--brand: #1a56db;
--brand-light: #e8f0fe;
--brand-dark: #1648c0;
--success: #0d9488;
--success-light:#ccfbf1;
--danger: #dc2626;
--danger-light: #fee2e2;
--warning: #d97706;
--warning-light:#fef3c7;
--page-bg: #f4f6fa;
--surface: #ffffff;
--border: rgba(0,0,0,0.07);
--border-md: rgba(0,0,0,0.12);
--text-1: #111827;
--text-2: #6b7280;
--text-3: #9ca3af;
--r-sm: 8px; --r-md: 12px; --r-lg: 16px;
--sh-sm: 0 1px 4px rgba(0,0,0,.06), 0 2px 8px rgba(0,0,0,.04);
--sh-md: 0 4px 16px rgba(0,0,0,.08);
}
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: 'Plus Jakarta Sans', sans-serif;
}
.mp-wrap { background: var(--page-bg); min-height: 100vh; padding: 28px 32px 56px; }
/* header */
.mp-header { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 24px; }
.mp-header-left .eyebrow { font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--brand); margin-bottom: 4px; }
.mp-header-left h1 { font-size: 24px; font-weight: 700; color: var(--text-1); margin: 0; letter-spacing: -.4px; }
/* primary button */
.btn-primary-mp {
display: inline-flex; align-items: center; gap: 7px;
background: var(--brand); color: #fff; border: none;
border-radius: var(--r-sm); padding: 10px 20px;
font-size: 13px; font-weight: 600; cursor: pointer;
text-decoration: none; transition: all .15s ease;
box-shadow: 0 2px 8px rgba(26,86,219,.25); white-space: nowrap;
}
.btn-primary-mp:hover { background: var(--brand-dark); transform: translateY(-1px); color: #fff; box-shadow: 0 4px 12px rgba(26,86,219,.35); }
.btn-primary-mp i { font-size: 11px; }
/* alert */
.mp-alert {
display: flex; align-items: center; gap: 12px;
background: var(--success-light); border: 1px solid rgba(13,148,136,.2);
border-left: 4px solid var(--success); border-radius: var(--r-md);
padding: 13px 16px; margin-bottom: 20px;
font-size: 13px; font-weight: 500; color: #065f46; position: relative;
}
.mp-alert-close { position: absolute; right: 14px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; color: #065f46; font-size: 18px; opacity: .6; line-height: 1; }
.mp-alert-close:hover { opacity: 1; }
/* card */
.mp-card { background: var(--surface); border-radius: var(--r-lg); border: 1px solid var(--border); box-shadow: var(--sh-sm); overflow: hidden; }
.mp-card-toolbar { display: flex; align-items: center; justify-content: space-between; padding: 15px 20px; border-bottom: 1px solid var(--border); }
.mp-count { font-size: 13px; font-weight: 600; color: var(--text-2); }
.mp-count strong { color: var(--text-1); font-weight: 700; }
/* table */
.mp-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.mp-table thead tr { background: #f8fafc; border-bottom: 1px solid var(--border); }
.mp-table thead th { padding: 12px 16px; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); white-space: nowrap; border: none; }
.mp-table thead th:first-child { padding-left: 20px; }
.mp-table thead th:last-child { padding-right: 20px; }
.mp-table tbody tr { border-bottom: 1px solid var(--border); transition: background .12s; }
.mp-table tbody tr:last-child { border-bottom: none; }
.mp-table tbody tr:hover { background: #f8fafc; }
.mp-table td { padding: 14px 16px; color: var(--text-1); vertical-align: middle; border: none; }
.mp-table td:first-child { padding-left: 20px; }
.mp-table td:last-child { padding-right: 20px; }
.cell-no { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-3); font-weight: 500; width: 44px; }
.cell-name { font-weight: 600; color: var(--text-1); display: flex; align-items: center; gap: 10px; }
.name-icon { width: 34px; height: 34px; border-radius: 8px; background: var(--brand-light); color: var(--brand); display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
.cell-mono { font-family: 'DM Mono', monospace; font-size: 13px; color: var(--text-2); }
/* badges */
.pill { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; letter-spacing: .02em; }
.pill-brand { background: var(--brand-light); color: var(--brand); border: 1px solid rgba(26,86,219,.15); }
/* jam display */
.jam-wrap { display: inline-flex; align-items: baseline; gap: 4px; }
.jam-num { font-size: 17px; font-weight: 700; font-family: 'DM Mono', monospace; color: var(--text-1); }
.jam-unit { font-size: 11px; color: var(--text-3); font-weight: 500; }
/* action buttons */
.action-group { display: flex; align-items: center; gap: 6px; justify-content: flex-end; }
.btn-act {
display: inline-flex; align-items: center; gap: 5px;
padding: 6px 12px; border-radius: 7px; font-size: 12px; font-weight: 600;
cursor: pointer; text-decoration: none; border: 1px solid;
transition: all .12s ease; white-space: nowrap;
font-family: 'Plus Jakarta Sans', sans-serif; background: none;
}
.btn-act i { font-size: 10px; }
.btn-edit { background: #f8fafc; border-color: var(--border-md); color: var(--text-1); }
.btn-edit:hover { background: #eef2f6; color: var(--text-1); }
.btn-del { background: #fff5f5; border-color: rgba(220,38,38,.18); color: var(--danger); }
.btn-del:hover { background: var(--danger-light); border-color: rgba(220,38,38,.35); color: var(--danger); }
/* empty */
.empty-state { padding: 52px 24px; text-align: center; }
.empty-icon { width: 54px; height: 54px; border-radius: 50%; background: #f1f5f9; display: flex; align-items: center; justify-content: center; margin: 0 auto 14px; color: var(--text-3); font-size: 22px; }
.empty-state p { font-size: 14px; color: var(--text-2); font-weight: 500; margin: 0 0 4px; }
.empty-state small { font-size: 12px; color: var(--text-3); }
@media (max-width: 768px) {
.mp-wrap { padding: 20px 16px 40px; }
.mp-header { flex-direction: column; align-items: flex-start; gap: 14px; }
.mp-table-scroll { overflow-x: auto; }
}
</style>
<div class="mp-wrap">
<div class="mp-header">
<div class="mp-header-left">
<div class="eyebrow">Kurikulum</div>
<h1>Mata Pelajaran</h1>
</div>
<a href="{{ route('admin.mapel.create') }}" class="btn-primary-mp">
<i class="fa-solid fa-plus"></i> Tambah Mapel
</a>
</div>
@if(session('success'))
<div class="mp-alert" id="mpAlert">
<i class="fa-solid fa-check-circle" style="font-size:15px;flex-shrink:0;"></i>
{{ session('success') }}
<button class="mp-alert-close" onclick="document.getElementById('mpAlert').remove()">&times;</button>
</div>
@endif
<!-- Card -->
<div class="card shadow mb-4">
<!-- Card Header -->
<div class="card-header py-3 d-flex justify-content-between align-items-center">
<h6 class="m-0 font-weight-bold text-primary">Tabel Mata Pelajaran</h6>
<a href="{{ route('admin.mapel.create') }}" class="btn btn-primary btn-sm">
<i class="fas fa-plus"></i> Tambah Data
</a>
<div class="mp-card">
<div class="mp-card-toolbar">
<div class="mp-count">Total <strong>{{ $mapel->count() }}</strong> mata pelajaran</div>
</div>
<!-- Card Body -->
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="thead-light">
<tr class="text-center">
<th>No</th>
<th>Nama Mapel</th>
<th>Kode</th>
<th>Jam Pelajaran</th>
<th width="150px">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($mapel as $m)
<tr>
<td class="text-center">{{ $loop->iteration }}</td>
<td>{{ $m->nama_mapel }}</td>
<td class="text-center">{{ $m->kode_mapel }}</td>
<td class="text-center">{{ $m->jam_pelajaran }} Jam</td>
<td class="text-center">
<a href="{{ route('admin.mapel.edit', $m->id) }}"
class="btn btn-warning btn-sm">
<i class="fas fa-edit"></i>
<div class="mp-table-scroll">
<table class="mp-table">
<thead>
<tr>
<th>No</th>
<th>Mata Pelajaran</th>
<th>Kode</th>
<th style="text-align:center;">Jam Pelajaran</th>
<th style="text-align:right;">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($mapel as $m)
<tr>
<td class="cell-no">{{ str_pad($loop->iteration, 2, '0', STR_PAD_LEFT) }}</td>
<td>
<div class="cell-name">
<div class="name-icon"><i class="fa-solid fa-book"></i></div>
{{ $m->nama_mapel }}
</div>
</td>
<td><span class="pill pill-brand">{{ $m->kode_mapel }}</span></td>
<td style="text-align:center;">
<div class="jam-wrap">
<span class="jam-num">{{ $m->jam_pelajaran }}</span>
<span class="jam-unit">jam</span>
</div>
</td>
<td>
<div class="action-group">
<a href="{{ route('admin.mapel.edit', $m->id) }}" class="btn-act btn-edit">
<i class="fa-solid fa-pen"></i> Edit
</a>
<form action="{{ route('admin.mapel.destroy', $m->id) }}"
method="POST"
class="d-inline">
<form action="{{ route('admin.mapel.destroy', $m->id) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button class="btn btn-danger btn-sm"
<button type="submit" class="btn-act btn-del"
onclick="return confirm('Yakin hapus data ini?')">
<i class="fas fa-trash"></i>
<i class="fa-solid fa-trash"></i> Hapus
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center text-muted">
Data belum tersedia
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5">
<div class="empty-state">
<div class="empty-icon"><i class="fa-solid fa-book"></i></div>
<p>Belum ada mata pelajaran</p>
<small>Klik "Tambah Mapel" untuk menambahkan data.</small>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
@endsection

View File

@ -1,46 +1,404 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Profile Saya</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--primary-light: #EEF2FF;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 16px;
}
<div class="card shadow">
<div class="card-body">
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
<form action="{{ route('admin.profile.update') }}" method="POST" enctype="multipart/form-data">
@csrf
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
<div class="mb-3">
<label>Nama</label>
<input type="text" name="name" class="form-control"
value="{{ auth()->user()->name }}">
</div>
/* ── TOPBAR ── */
.sw-topbar { margin-bottom: 28px; }
.sw-topbar h3 { font-size: 21px; font-weight: 800; color: var(--text-dark); margin: 0 0 4px; letter-spacing: -.3px; }
.sw-topbar p { font-size: 13px; color: var(--text-soft); margin: 0; }
<div class="mb-3">
<label>Foto</label><br>
/* ── ALERT ── */
.sw-alert-success {
display: flex; align-items: center; gap: 9px;
background: var(--success-light); border: 1px solid #A7F3D0;
border-radius: var(--radius-md); padding: 13px 16px;
font-size: 13px; color: var(--success); margin-bottom: 24px; font-weight: 500;
}
@if(auth()->user()->photo)
<img src="{{ asset('uploads/' . auth()->user()->photo) }}"
width="120" class="mb-2 rounded">
@else
<img src="{{ asset('sbadmin2/img/undraw_profile.svg') }}"
width="120" class="mb-2">
@endif
/* ── GRID LAYOUT ── */
.sw-grid {
display: grid;
grid-template-columns: 300px 1fr;
gap: 24px;
align-items: start;
}
<input type="file" name="photo" class="form-control mt-2">
</div>
/* ── CARD ── */
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.05);
overflow: hidden;
}
<button class="btn btn-primary">Update Profil</button>
/* ── AVATAR CARD ── */
.avatar-card { text-align: center; }
</form>
.avatar-banner {
height: 90px;
background: linear-gradient(135deg, #1E3A6E 0%, #4F46E5 60%, #7C3AED 100%);
position: relative;
}
.avatar-banner::after {
content: '';
position: absolute; inset: 0;
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.04'%3E%3Ccircle cx='30' cy='30' r='20'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}
</div>
.avatar-wrap {
position: relative;
display: inline-block;
margin-top: -50px;
margin-bottom: 14px;
}
.avatar-img {
width: 100px; height: 100px;
border-radius: 50%;
object-fit: cover;
border: 4px solid var(--surface);
box-shadow: 0 4px 16px rgba(0,0,0,.12);
display: block;
}
.avatar-online {
position: absolute; bottom: 6px; right: 6px;
width: 14px; height: 14px;
background: var(--success);
border: 2px solid var(--surface);
border-radius: 50%;
}
.avatar-body { padding: 0 24px 28px; }
.avatar-name { font-size: 17px; font-weight: 800; color: var(--text-dark); margin-bottom: 4px; }
.avatar-role {
display: inline-flex; align-items: center; gap: 5px;
background: var(--primary-light); border: 1px solid #C7D2FE;
border-radius: 99px; padding: 4px 12px;
font-size: 11.5px; font-weight: 700; color: var(--primary);
margin-bottom: 20px;
}
.avatar-role span { width: 6px; height: 6px; background: var(--primary); border-radius: 50%; }
.avatar-stats {
display: flex; gap: 0;
border: 1px solid var(--border);
border-radius: var(--radius-md);
overflow: hidden;
}
.avatar-stat {
flex: 1; padding: 12px 8px; text-align: center;
border-right: 1px solid var(--border);
}
.avatar-stat:last-child { border-right: none; }
.avatar-stat-val { font-size: 16px; font-weight: 800; color: var(--primary); }
.avatar-stat-lbl { font-size: 10.5px; color: var(--text-soft); font-weight: 600; margin-top: 2px; }
/* ── FORM CARD ── */
.sw-card-header {
display: flex; align-items: center; gap: 12px;
padding: 20px 24px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
}
.sw-card-header-icon {
width: 38px; height: 38px;
background: var(--primary); border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: #fff; flex-shrink: 0;
box-shadow: 0 2px 8px rgba(79,70,229,.3);
}
.sw-card-header h5 { font-size: 15px; font-weight: 700; color: #3730A3; margin: 0; }
.sw-card-header p { font-size: 12.5px; color: #6D6AA4; margin: 2px 0 0; }
.sw-card-body { padding: 28px 24px; }
/* ── SECTION ── */
.sw-section {
display: flex; align-items: center; gap: 10px;
margin: 0 0 20px;
}
.sw-section-label {
font-size: 11.5px; font-weight: 700; color: var(--primary);
text-transform: uppercase; letter-spacing: .8px; white-space: nowrap;
}
.sw-section-line {
flex: 1; height: 1px;
background: linear-gradient(to right, #C7D2FE, transparent);
}
/* ── FORM ELEMENTS ── */
.sw-form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 20px; }
.sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-mid); }
.sw-input-wrap { position: relative; }
.sw-input-wrap svg {
position: absolute; left: 14px; top: 50%;
transform: translateY(-50%); color: #9CA3AF; pointer-events: none;
}
.sw-input {
width: 100%;
padding: 11px 14px 11px 42px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px; font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark); background: var(--neutral-light);
outline: none; transition: all .15s;
}
.sw-input:focus {
border-color: var(--primary);
background: #fff;
box-shadow: 0 0 0 3px rgba(79,70,229,.1);
}
/* ── PHOTO UPLOAD ZONE ── */
.sw-upload-zone {
border: 2px dashed var(--border);
border-radius: var(--radius-md);
padding: 24px 20px;
text-align: center;
background: var(--neutral-light);
cursor: pointer;
transition: all .2s;
position: relative;
}
.sw-upload-zone:hover {
border-color: var(--primary);
background: var(--primary-light);
}
.sw-upload-zone input[type="file"] {
position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%;
}
.sw-upload-icon {
width: 44px; height: 44px;
background: var(--primary-light); border-radius: 10px;
display: flex; align-items: center; justify-content: center;
margin: 0 auto 12px; color: var(--primary);
}
.sw-upload-title { font-size: 13.5px; font-weight: 700; color: var(--text-dark); margin-bottom: 4px; }
.sw-upload-sub { font-size: 12px; color: var(--text-soft); }
.sw-upload-badge {
display: inline-block; margin-top: 10px;
background: var(--primary-light); border: 1px solid #C7D2FE;
border-radius: 99px; padding: 3px 10px;
font-size: 11px; font-weight: 600; color: var(--primary);
}
/* current photo preview */
.sw-photo-preview {
display: flex; align-items: center; gap: 14px;
background: var(--neutral-light); border: 1px solid var(--border);
border-radius: var(--radius-md); padding: 14px 16px;
margin-bottom: 12px;
}
.sw-photo-preview img {
width: 52px; height: 52px;
border-radius: 50%; object-fit: cover;
border: 2px solid var(--primary-light);
}
.sw-photo-preview-info .title { font-size: 13px; font-weight: 700; color: var(--text-dark); }
.sw-photo-preview-info .sub { font-size: 11.5px; color: var(--text-soft); margin-top: 2px; }
/* ── FOOTER ── */
.sw-footer {
display: flex; justify-content: flex-end; align-items: center;
padding-top: 24px; border-top: 1px solid var(--border); margin-top: 8px;
}
.sw-btn {
display: inline-flex; align-items: center; gap: 7px;
padding: 11px 22px; border-radius: var(--radius-md);
font-size: 14px; font-weight: 700; border: none;
cursor: pointer; text-decoration: none;
transition: all .18s ease; line-height: 1;
font-family: 'Plus Jakarta Sans', sans-serif;
}
.sw-btn-primary { background: var(--primary); color: #fff; box-shadow: 0 4px 14px rgba(79,70,229,.3); }
.sw-btn-primary:hover { background: var(--primary-dark); transform: translateY(-1px); box-shadow: 0 6px 20px rgba(79,70,229,.38); }
@media (max-width: 900px) {
.sw-grid { grid-template-columns: 1fr; }
.sw-page { padding: 16px; }
}
</style>
<div class="sw-page">
{{-- TOPBAR --}}
<div class="sw-topbar">
<h3>Profile Saya</h3>
<p>Kelola informasi dan foto profil akun Anda</p>
</div>
{{-- ALERT --}}
@if(session('success'))
<div class="sw-alert-success">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><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
<div class="sw-grid">
{{-- LEFT: AVATAR CARD --}}
<div class="sw-card avatar-card">
<div class="avatar-banner"></div>
<div class="avatar-body">
<div class="avatar-wrap">
@if(auth()->user()->photo)
<img src="{{ asset('uploads/' . auth()->user()->photo) }}" class="avatar-img" alt="Foto Profil">
@else
<img src="{{ asset('sbadmin2/img/undraw_profile.svg') }}" class="avatar-img" alt="Foto Profil">
@endif
<div class="avatar-online"></div>
</div>
<div class="avatar-name">{{ auth()->user()->name }}</div>
<div class="avatar-role">
<span></span> Administrator
</div>
<div class="avatar-stats">
<div class="avatar-stat">
<div class="avatar-stat-val">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:#4F46E5"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
</div>
<div class="avatar-stat-lbl">Profil</div>
</div>
<div class="avatar-stat">
<div class="avatar-stat-val">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:#059669"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
</div>
<div class="avatar-stat-lbl">Aman</div>
</div>
<div class="avatar-stat">
<div class="avatar-stat-val">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:#D97706"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
</div>
<div class="avatar-stat-lbl">Aktif</div>
</div>
</div>
</div>
</div>
{{-- RIGHT: FORM CARD --}}
<div class="sw-card">
<div class="sw-card-header">
<div class="sw-card-header-icon">
<svg width="18" height="18" 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>
</div>
<div>
<h5>Edit Profil</h5>
<p>Perbarui nama dan foto profil Anda</p>
</div>
</div>
<div class="sw-card-body">
<form action="{{ route('admin.profile.update') }}" method="POST" enctype="multipart/form-data">
@csrf
{{-- SECTION: INFO --}}
<div class="sw-section">
<span class="sw-section-label">Informasi Akun</span>
<div class="sw-section-line"></div>
</div>
<div class="sw-form-group">
<label class="sw-label">Nama Lengkap</label>
<div class="sw-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<input type="text" name="name" class="sw-input"
value="{{ auth()->user()->name }}" required>
</div>
</div>
{{-- SECTION: FOTO --}}
<div class="sw-section" style="margin-top:28px;">
<span class="sw-section-label">Foto Profil</span>
<div class="sw-section-line"></div>
</div>
{{-- Current photo preview --}}
<div class="sw-photo-preview">
@if(auth()->user()->photo)
<img src="{{ asset('uploads/' . auth()->user()->photo) }}" alt="Foto saat ini">
@else
<img src="{{ asset('sbadmin2/img/undraw_profile.svg') }}" alt="Foto saat ini">
@endif
<div class="sw-photo-preview-info">
<div class="title">Foto saat ini</div>
<div class="sub">Format: JPG, PNG, WEBP · Maks. 2MB</div>
</div>
</div>
{{-- Upload zone --}}
<div class="sw-upload-zone">
<input type="file" name="photo" accept="image/*">
<div class="sw-upload-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
</div>
<div class="sw-upload-title">Klik untuk unggah foto baru</div>
<div class="sw-upload-sub">atau seret & lepas file ke sini</div>
<div class="sw-upload-badge">Pilih File</div>
</div>
{{-- FOOTER --}}
<div class="sw-footer">
<button type="submit" class="sw-btn sw-btn-primary">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Update Profil
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
// Live preview when file is selected
const fileInput = document.querySelector('input[type="file"]');
const previewImg = document.querySelector('.sw-photo-preview img');
const uploadTitle = document.querySelector('.sw-upload-title');
if (fileInput) {
fileInput.addEventListener('change', function () {
const file = this.files[0];
if (file) {
const reader = new FileReader();
reader.onload = e => {
previewImg.src = e.target.result;
uploadTitle.textContent = file.name;
};
reader.readAsDataURL(file);
}
});
}
</script>
@endsection

View File

@ -1,103 +1,301 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h2 class="mb-4 text-primary">Tambah Siswa</h2>
<div class="card shadow-sm border-0">
<div class="card-body">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--primary: #4F46E5;
--primary-light: #EEF2FF;
--primary-dark: #3730A3;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--danger-light: #FEF2F2;
--info: #0284C7;
--info-light: #F0F9FF;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 14px;
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
/* ── BREADCRUMB / TOPBAR ── */
.sw-topbar { margin-bottom: 24px; }
.sw-topbar h3 {
font-size: 21px; font-weight: 700;
color: var(--text-dark); margin: 0 0 4px;
letter-spacing: -.3px;
}
.sw-topbar p { font-size: 13px; color: var(--text-soft); margin: 0; }
/* ── CARD ── */
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.05), 0 1px 2px rgba(0,0,0,.04);
overflow: hidden;
}
.sw-card-header {
display: flex;
align-items: center;
gap: 12px;
padding: 20px 24px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
}
.sw-card-header-icon {
width: 38px; height: 38px;
background: var(--primary);
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: #fff;
flex-shrink: 0;
}
.sw-card-header h5 {
font-size: 15px; font-weight: 700;
color: #3730A3; margin: 0;
}
.sw-card-header p { font-size: 12.5px; color: #6D6AA4; margin: 2px 0 0; }
.sw-card-body { padding: 28px 24px; }
/* ── SECTION DIVIDER ── */
.sw-section {
display: flex; align-items: center; gap: 10px;
margin: 28px 0 20px;
}
.sw-section-label {
font-size: 12px; font-weight: 700;
color: var(--primary); text-transform: uppercase; letter-spacing: .8px;
white-space: nowrap;
}
.sw-section-line {
flex: 1; height: 1px;
background: linear-gradient(to right, #C7D2FE, transparent);
}
/* ── FORM GROUP ── */
.sw-form-row { display: flex; gap: 18px; margin-bottom: 18px; }
.sw-form-group { flex: 1; display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
.sw-form-group.half { flex: 0 0 calc(50% - 9px); }
.sw-label {
font-size: 12.5px; font-weight: 600;
color: var(--text-mid);
}
.sw-label span { color: var(--danger); margin-left: 2px; }
.sw-input, .sw-select {
padding: 10px 14px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark);
background: var(--neutral-light);
outline: none;
transition: border-color .15s, box-shadow .15s, background .15s;
width: 100%;
}
.sw-input:focus, .sw-select:focus {
border-color: var(--primary);
background: #fff;
box-shadow: 0 0 0 3px rgba(79,70,229,.1);
}
.sw-input::placeholder { color: #C3C7D0; }
/* ── ALERT ── */
.sw-alert-info {
display: flex; gap: 12px; align-items: flex-start;
padding: 14px 16px;
background: var(--info-light);
border: 1px solid #BAE6FD;
border-radius: var(--radius-md);
color: var(--info);
font-size: 13px;
margin-bottom: 22px;
}
.sw-alert-info svg { flex-shrink: 0; margin-top: 1px; }
.sw-alert-info strong { font-weight: 700; }
.sw-alert-danger {
display: flex; gap: 10px; align-items: flex-start;
padding: 14px 16px;
background: var(--danger-light);
border: 1px solid #FECACA;
border-radius: var(--radius-md);
color: var(--danger);
font-size: 13px;
margin-bottom: 22px;
}
.sw-alert-danger ul { margin: 0; padding-left: 16px; }
.sw-alert-danger li { margin-bottom: 2px; }
/* ── FOOTER ── */
.sw-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 24px;
border-top: 1px solid var(--border);
margin-top: 8px;
}
.sw-btn {
display: inline-flex; align-items: center; gap: 7px;
padding: 10px 20px;
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-secondary { background: var(--surface); color: var(--text-mid); border: 1.5px solid var(--border); }
.sw-btn-secondary:hover { background: var(--neutral-light); color: var(--text-mid); }
.sw-btn-success { background: var(--success); color: #fff; }
.sw-btn-success:hover { background: #047857; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(5,150,105,.3); }
.sw-btn-success:disabled { opacity: .65; transform: none; box-shadow: none; cursor: not-allowed; }
@media (max-width: 640px) {
.sw-page { padding: 16px; }
.sw-form-row { flex-direction: column; gap: 0; }
.sw-form-group.half { flex: 1; }
}
</style>
<div class="sw-page">
<div class="sw-topbar">
<h3>Tambah Siswa</h3>
<p>Isi formulir di bawah untuk mendaftarkan siswa baru</p>
</div>
<div class="sw-card">
<div class="sw-card-header">
<div class="sw-card-header-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
</div>
<div>
<h5>Formulir Data Siswa</h5>
<p>Semua kolom bertanda <span style="color:#DC2626">*</span> wajib diisi</p>
</div>
</div>
<div class="sw-card-body">
{{-- ERROR --}}
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
<div class="sw-alert-danger">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><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>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('admin.siswa.store') }}" method="POST">
@csrf
<div class="row">
<div class="col-md-6 mb-3">
<label>Nama</label>
<input type="text" name="nama" class="form-control" required>
</div>
{{-- SECTION: DATA SISWA --}}
<div class="sw-section">
<span class="sw-section-label">Data Siswa</span>
<div class="sw-section-line"></div>
</div>
<div class="col-md-6 mb-3">
<label>NIS</label>
<input type="text" name="nis" class="form-control" required>
<div class="sw-form-row">
<div class="sw-form-group half">
<label class="sw-label">Nama <span>*</span></label>
<input type="text" name="nama" class="sw-input" placeholder="Nama lengkap siswa" required>
</div>
<div class="sw-form-group half">
<label class="sw-label">NIS <span>*</span></label>
<input type="text" name="nis" class="sw-input" placeholder="Nomor Induk Siswa" required>
</div>
</div>
<div class="col-md-6 mb-3">
<label>Jenis Kelamin</label>
<select name="jenis_kelamin" class="form-control" required>
<div class="sw-form-row">
<div class="sw-form-group half">
<label class="sw-label">Jenis Kelamin <span>*</span></label>
<select name="jenis_kelamin" class="sw-select" required>
<option value="">-- Pilih --</option>
<option value="L">Laki-laki</option>
<option value="P">Perempuan</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label>Nama Orang Tua</label>
<input type="text" name="nama_ortu" class="form-control" required>
</div>
<div class="col-md-12 mb-3">
<label>Kelas</label>
<select name="kelas_id" class="form-control" required>
<option value="">-- Pilih Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}">
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
</div>
<div class="col-md-12 mb-3">
<label>Alamat</label>
<input type="text" name="alamat" class="form-control" required>
</div>
<div class="col-md-12 mb-3">
<label>Telepon</label>
<input type="text" name="telepon" class="form-control" required>
<div class="sw-form-group half">
<label class="sw-label">Nama Orang Tua <span>*</span></label>
<input type="text" name="nama_ortu" class="sw-input" placeholder="Nama ayah/ibu/wali" required>
</div>
</div>
<hr>
<h5 class="text-primary">Akun Login</h5>
<div class="mb-3">
<label>Email</label>
<input type="email" name="email" class="form-control" required>
<div class="sw-form-group">
<label class="sw-label">Kelas <span>*</span></label>
<select name="kelas_id" class="sw-select" required>
<option value="">-- Pilih Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}">{{ $k->nama_kelas }}</option>
@endforeach
</select>
</div>
{{-- 🔥 INFO PASSWORD --}}
<div class="alert alert-info">
Password default: <strong>12345678</strong><br>
Siswa wajib mengganti password saat login pertama.
<div class="sw-form-group">
<label class="sw-label">Alamat <span>*</span></label>
<input type="text" name="alamat" class="sw-input" placeholder="Alamat lengkap" required>
</div>
<div class="d-flex justify-content-between">
<a href="{{ route('admin.siswa.index') }}" class="btn btn-secondary">
<div class="sw-form-group">
<label class="sw-label">Telepon <span>*</span></label>
<input type="text" name="telepon" class="sw-input" placeholder="Nomor telepon aktif" required>
</div>
{{-- SECTION: AKUN LOGIN --}}
<div class="sw-section">
<span class="sw-section-label">Akun Login</span>
<div class="sw-section-line"></div>
</div>
<div class="sw-form-group">
<label class="sw-label">Email <span>*</span></label>
<input type="email" name="email" class="sw-input" placeholder="email@siswa.com" required>
</div>
<div class="sw-alert-info">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
<div>
Password default: <strong>12345678</strong><br>
Siswa wajib mengganti password saat login pertama.
</div>
</div>
{{-- FOOTER --}}
<div class="sw-footer">
<a href="{{ route('admin.siswa.index') }}" class="sw-btn sw-btn-secondary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Kembali
</a>
<button type="submit" class="btn btn-success"
onclick="this.disabled=true; this.innerText='Menyimpan...'; this.form.submit();">
Simpan
</button>
<button type="submit" class="sw-btn sw-btn-success"
onclick="this.disabled=true; this.innerText='Menyimpan...'; this.form.submit();">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Simpan Siswa
</button>
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@ -1,76 +1,263 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h2 class="mb-4 text-primary">Edit Siswa</h2>
<div class="card shadow-lg border-0">
<div class="card-body">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--primary: #4F46E5;
--primary-light: #EEF2FF;
--primary-dark: #3730A3;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--danger-light: #FEF2F2;
--warning: #D97706;
--warning-light: #FFFBEB;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 14px;
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
/* ── TOPBAR ── */
.sw-topbar { margin-bottom: 24px; }
.sw-topbar h3 {
font-size: 21px; font-weight: 700;
color: var(--text-dark); margin: 0 0 4px;
letter-spacing: -.3px;
}
.sw-topbar p { font-size: 13px; color: var(--text-soft); margin: 0; }
/* ── CARD ── */
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.05), 0 1px 2px rgba(0,0,0,.04);
overflow: hidden;
}
.sw-card-header {
display: flex;
align-items: center;
gap: 12px;
padding: 20px 24px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
}
.sw-card-header-icon {
width: 38px; height: 38px;
background: var(--warning);
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: #fff; flex-shrink: 0;
}
.sw-card-header h5 {
font-size: 15px; font-weight: 700;
color: #92400E; margin: 0;
}
.sw-card-header p { font-size: 12.5px; color: #A16207; margin: 2px 0 0; }
/* ── IDENTITY CHIP ── */
.sw-id-chip {
margin-left: auto;
background: var(--warning-light);
border: 1px solid #FDE68A;
border-radius: 99px;
padding: 5px 14px;
font-size: 12px;
font-weight: 600;
color: var(--warning);
display: flex; align-items: center; gap: 6px;
}
.sw-card-body { padding: 28px 24px; }
/* ── SECTION DIVIDER ── */
.sw-section {
display: flex; align-items: center; gap: 10px;
margin: 28px 0 20px;
}
.sw-section:first-child { margin-top: 0; }
.sw-section-label {
font-size: 12px; font-weight: 700;
color: var(--warning); text-transform: uppercase; letter-spacing: .8px;
white-space: nowrap;
}
.sw-section-line {
flex: 1; height: 1px;
background: linear-gradient(to right, #FDE68A, transparent);
}
/* ── FORM ── */
.sw-form-row { display: flex; gap: 18px; margin-bottom: 18px; }
.sw-form-group { flex: 1; display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
.sw-form-group.half { flex: 0 0 calc(50% - 9px); }
.sw-label {
font-size: 12.5px; font-weight: 600;
color: var(--text-mid);
}
.sw-label span { color: var(--danger); margin-left: 2px; }
.sw-input, .sw-select {
padding: 10px 14px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark);
background: var(--neutral-light);
outline: none;
transition: border-color .15s, box-shadow .15s, background .15s;
width: 100%;
}
.sw-input:focus, .sw-select:focus {
border-color: var(--warning);
background: #fff;
box-shadow: 0 0 0 3px rgba(217,119,6,.1);
}
.sw-input::placeholder { color: #C3C7D0; }
/* ── ALERT DANGER ── */
.sw-alert-danger {
display: flex; gap: 10px; align-items: flex-start;
padding: 14px 16px;
background: var(--danger-light);
border: 1px solid #FECACA;
border-radius: var(--radius-md);
color: var(--danger);
font-size: 13px;
margin-bottom: 22px;
}
.sw-alert-danger ul { margin: 0; padding-left: 16px; }
.sw-alert-danger li { margin-bottom: 2px; }
/* ── FOOTER ── */
.sw-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 24px;
border-top: 1px solid var(--border);
margin-top: 8px;
}
.sw-btn {
display: inline-flex; align-items: center; gap: 7px;
padding: 10px 20px;
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-secondary {
background: var(--surface); color: var(--text-mid);
border: 1.5px solid var(--border);
}
.sw-btn-secondary:hover { background: var(--neutral-light); color: var(--text-mid); }
.sw-btn-warning { background: var(--warning); color: #fff; }
.sw-btn-warning:hover { background: #B45309; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(217,119,6,.3); color: #fff; }
@media (max-width: 640px) {
.sw-page { padding: 16px; }
.sw-form-row { flex-direction: column; gap: 0; }
.sw-form-group.half { flex: 1; }
.sw-id-chip { display: none; }
}
</style>
<div class="sw-page">
<div class="sw-topbar">
<h3>Edit Data Siswa</h3>
<p>Perbarui informasi siswa yang terdaftar di sistem</p>
</div>
<div class="sw-card">
<div class="sw-card-header">
<div class="sw-card-header-icon">
<svg width="18" height="18" 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>
</div>
<div>
<h5>Edit Siswa</h5>
<p>Semua kolom bertanda <span style="color:#DC2626">*</span> wajib diisi</p>
</div>
<div class="sw-id-chip">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M8 12h8M8 16h5"/></svg>
NIS: {{ $siswa->nis }}
</div>
</div>
<div class="sw-card-body">
{{-- ERROR --}}
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
<div class="sw-alert-danger">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><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>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('admin.siswa.update', $siswa->id) }}" method="POST">
@csrf
@method('PUT')
{{-- DATA SISWA --}}
<h5 class="text-primary mb-3">Data Siswa</h5>
{{-- SECTION: DATA SISWA --}}
<div class="sw-section">
<span class="sw-section-label">Data Siswa</span>
<div class="sw-section-line"></div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Nama</label>
<div class="sw-form-row">
<div class="sw-form-group half">
<label class="sw-label">Nama <span>*</span></label>
<input type="text" name="nama"
value="{{ old('nama', $siswa->nama) }}"
class="form-control" required>
class="sw-input" placeholder="Nama lengkap" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">NIS</label>
<div class="sw-form-group half">
<label class="sw-label">NIS <span>*</span></label>
<input type="text" name="nis"
value="{{ old('nis', $siswa->nis) }}"
class="form-control" required>
class="sw-input" placeholder="Nomor Induk Siswa" required>
</div>
</div>
<div class="row">
{{-- JENIS KELAMIN --}}
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Jenis Kelamin</label>
<select name="jenis_kelamin" class="form-control" required>
<div class="sw-form-row">
<div class="sw-form-group half">
<label class="sw-label">Jenis Kelamin <span>*</span></label>
<select name="jenis_kelamin" class="sw-select" required>
<option value="">-- Pilih --</option>
<option value="L"
{{ old('jenis_kelamin', $siswa->jenis_kelamin) == 'L' ? 'selected' : '' }}>
Laki-laki
</option>
<option value="P"
{{ old('jenis_kelamin', $siswa->jenis_kelamin) == 'P' ? 'selected' : '' }}>
Perempuan
</option>
<option value="L" {{ old('jenis_kelamin', $siswa->jenis_kelamin) == 'L' ? 'selected' : '' }}>Laki-laki</option>
<option value="P" {{ old('jenis_kelamin', $siswa->jenis_kelamin) == 'P' ? 'selected' : '' }}>Perempuan</option>
</select>
</div>
{{-- NAMA ORANG TUA --}}
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">Nama Orang Tua</label>
<div class="sw-form-group half">
<label class="sw-label">Nama Orang Tua <span>*</span></label>
<input type="text" name="nama_ortu"
value="{{ old('nama_ortu', $siswa->nama_ortu) }}"
class="form-control" required>
class="sw-input" placeholder="Nama ayah/ibu/wali" required>
</div>
</div>
{{-- KELAS --}}
<div class="mb-3">
<label class="form-label fw-bold">Kelas</label>
<select name="kelas_id" class="form-control" required>
<div class="sw-form-group">
<label class="sw-label">Kelas <span>*</span></label>
<select name="kelas_id" class="sw-select" required>
<option value="">-- Pilih Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}"
@ -81,30 +268,29 @@ class="form-control" required>
</select>
</div>
{{-- ALAMAT --}}
<div class="mb-3">
<label class="form-label fw-bold">Alamat</label>
<div class="sw-form-group">
<label class="sw-label">Alamat</label>
<input type="text" name="alamat"
value="{{ old('alamat', $siswa->alamat) }}"
class="form-control">
class="sw-input" placeholder="Alamat lengkap">
</div>
{{-- TELEPON --}}
<div class="mb-3">
<label class="form-label fw-bold">Telepon</label>
<div class="sw-form-group">
<label class="sw-label">Telepon</label>
<input type="text" name="telepon"
value="{{ old('telepon', $siswa->telepon) }}"
class="form-control">
class="sw-input" placeholder="Nomor telepon">
</div>
{{-- BUTTON --}}
<div class="d-flex justify-content-between mt-4">
<a href="{{ route('admin.siswa.index') }}" class="btn btn-secondary">
⬅️ Batal
{{-- FOOTER --}}
<div class="sw-footer">
<a href="{{ route('admin.siswa.index') }}" class="sw-btn sw-btn-secondary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Batal
</a>
<button class="btn btn-success">
💾 Update
<button type="submit" class="sw-btn sw-btn-warning">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Update Siswa
</button>
</div>
@ -112,4 +298,5 @@ class="form-control">
</div>
</div>
</div>
@endsection

View File

@ -3,301 +3,405 @@
@section('content')
<style>
/* ===== GLOBAL ===== */
* {
font-family: 'Poppins', sans-serif;
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--primary: #4F46E5;
--primary-light: #EEF2FF;
--primary-dark: #3730A3;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--danger-light: #FEF2F2;
--warning: #D97706;
--warning-light: #FFFBEB;
--info: #0284C7;
--info-light: #F0F9FF;
--neutral: #6B7280;
--neutral-light: #F9FAFB;
--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);
--shadow-md: 0 4px 16px rgba(0,0,0,.07);
}
/* ===== CARD ===== */
.card {
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
border: none;
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
/* ===== HEADER ===== */
h3 {
font-weight: 600;
letter-spacing: 0.5px;
}
/* ── PAGE WRAPPER ── */
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
/* ===== FILTER ===== */
.form-select {
border-radius: 8px;
padding: 6px 12px;
/* ── TOPBAR ── */
.sw-topbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
/* ===== TABLE ===== */
.table {
border-collapse: separate;
border-spacing: 0;
overflow: hidden;
border-radius: 10px;
.sw-topbar-left h3 {
font-size: 22px;
font-weight: 700;
color: var(--text-dark);
margin: 0;
letter-spacing: -.3px;
}
/* HEADER GRADIENT */
.table thead {
background: linear-gradient(to right, #eef1f5, #e6ebf1);
}
.table thead th {
font-weight: 600;
color: #555;
border: none;
padding: 14px;
}
/* BODY */
.table tbody tr {
background: #ffffff;
transition: 0.2s ease;
}
.table tbody tr:nth-child(even) {
background: #f8fafc;
}
.table tbody tr:hover {
background: #eef3f8;
}
.table td {
padding: 14px;
border: none;
font-size: 14px;
}
/* ===== BADGE ===== */
.badge {
padding: 6px 10px;
font-size: 12px;
border-radius: 8px;
font-weight: 500;
}
.badge.bg-success {
background: #e6f7ee !important;
color: #1f9d55;
}
.badge.bg-danger {
background: #fdecea !important;
color: #d93025;
}
.badge.bg-primary {
background: #e8f0fe !important;
color: #1a73e8;
}
.badge.bg-secondary {
background: #f1f3f4 !important;
color: #5f6368;
}
.badge.bg-info {
background: #e3f2fd !important;
color: #0b5ed7;
}
/* ===== BUTTON ===== */
.btn-sm {
padding: 6px 10px;
.sw-topbar-left p {
font-size: 13px;
border-radius: 6px;
color: var(--text-soft);
margin: 2px 0 0;
}
.btn-danger {
background: #d93025;
/* ── BUTTON ── */
.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-secondary { background: var(--surface); color: var(--text-mid); border: 1px solid var(--border); }
.sw-btn-secondary:hover { background: var(--neutral-light); color: var(--text-mid); }
.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-warning { background: var(--warning-light); color: var(--warning); border: 1px solid #FDE68A; }
.sw-btn-warning:hover { background: var(--warning); color: #fff; }
.sw-btn-success { background: var(--success-light); color: var(--success); border: 1px solid #A7F3D0; }
.sw-btn-success:hover { background: var(--success); color: #fff; }
.sw-btn-sm { padding: 6px 12px; font-size: 12.5px; border-radius: 8px; }
/* ── ALERT ── */
.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-alert svg { flex-shrink: 0; }
/* ── FILTER CARD ── */
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
border: 1px solid var(--border);
}
.sw-filter {
padding: 16px 20px;
margin-bottom: 18px;
}
.sw-filter form { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.sw-filter label { font-size: 13px; font-weight: 600; color: var(--text-mid); white-space: nowrap; }
.sw-filter .form-select {
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 14px;
font-size: 13px;
color: var(--text-mid);
background: var(--neutral-light);
font-family: 'Plus Jakarta Sans', sans-serif;
outline: none;
transition: border-color .15s;
}
.sw-filter .form-select:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(79,70,229,.1); }
/* ── BULK BAR ── */
.sw-bulk-bar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 14px;
}
/* ── TABLE CARD ── */
.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); }
.btn-danger:hover {
background: #b1271b;
/* ── CUSTOM CHECKBOX ── */
.sw-check {
width: 16px; height: 16px;
accent-color: var(--primary);
cursor: pointer;
}
td .btn {
min-width: 80px;
/* ── BADGES ── */
.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-success { background: var(--success-light); color: var(--success); }
.sw-badge-danger { background: var(--danger-light); color: var(--danger); }
.sw-badge-primary { background: var(--primary-light); color: var(--primary); }
.sw-badge-neutral { background: #F3F4F6; color: #6B7280; }
.sw-badge-info { background: var(--info-light); color: var(--info); }
/* ── NONAKTIF INLINE FORM ── */
.sw-nonaktif-form {
display: flex;
gap: 6px;
align-items: center;
}
.sw-nonaktif-input {
width: 110px;
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: 7px;
font-size: 12px;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-mid);
background: var(--neutral-light);
outline: none;
transition: border-color .15s;
}
.sw-nonaktif-input:focus { border-color: var(--danger); box-shadow: 0 0 0 3px rgba(220,38,38,.08); }
.sw-nonaktif-input::placeholder { color: #9CA3AF; }
/* ── ACTION CELL ── */
.sw-action-cell { display: flex; gap: 6px; align-items: center; justify-content: center; }
/* ── EMPTY STATE ── */
.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; }
/* ── DOT INDICATOR ── */
.sw-dot {
display: inline-block;
width: 7px; height: 7px;
border-radius: 50%;
margin-right: 5px;
}
.sw-dot-success { background: var(--success); }
.sw-dot-danger { background: var(--danger); }
</style>
<div class="container-fluid px-4">
<div class="sw-page">
{{-- HEADER --}}
<div class="d-flex justify-content-between align-items-center mb-4">
<h3 class="text-dark">Data Siswa</h3>
<a href="{{ route('admin.siswa.create') }}" class="btn btn-primary">
{{-- TOPBAR --}}
<div class="sw-topbar">
<div class="sw-topbar-left">
<h3>Data Siswa</h3>
<p>Kelola seluruh data dan status siswa</p>
</div>
<a href="{{ route('admin.siswa.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 Siswa
</a>
</div>
{{-- ALERT --}}
@if(session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
<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
{{-- FILTER --}}
<div class="card mb-3">
<div class="card-body">
<form method="GET" class="d-flex gap-2 align-items-center">
<select name="kelas_id" class="form-select w-auto">
<option value="">Semua Kelas</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}"
{{ request('kelas_id') == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
<button class="btn btn-primary">Filter</button>
<a href="{{ route('admin.siswa.index') }}" class="btn btn-secondary">
Reset
</a>
</form>
</div>
<div class="sw-card sw-filter">
<form method="GET">
<label>Kelas</label>
<select name="kelas_id" class="form-select">
<option value="">Semua Kelas</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}" {{ request('kelas_id') == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
<button type="submit" class="sw-btn sw-btn-primary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>
Filter
</button>
<a href="{{ route('admin.siswa.index') }}" class="sw-btn sw-btn-secondary">Reset</a>
</form>
</div>
{{-- BULK ACTION --}}
{{-- BULK ACTION + TABLE --}}
<form action="{{ route('admin.siswa.bulkNonaktif') }}" method="POST">
@csrf
<button class="btn btn-danger mb-3"
onclick="return confirm('Nonaktifkan siswa yang dipilih?')">
Nonaktifkan Terpilih
</button>
{{-- TABLE --}}
<div class="card">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table align-middle text-center">
<thead>
<tr>
<th width="30">
<input type="checkbox" id="checkAll">
</th>
<th>No</th>
<th>Nama</th>
<th>JK</th>
<th>NIS</th>
<th>Kelas</th>
<th>Orang Tua</th>
<th>Email</th>
<th>Alamat</th>
<th>Telepon</th>
<th>Status</th>
<th width="200">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($siswa as $item)
<tr>
<td>
<input type="checkbox" name="ids[]" value="{{ $item->id }}">
</td>
<td>{{ $loop->iteration }}</td>
<td class="text-start">{{ $item->nama }}</td>
<td>
@if($item->jenis_kelamin == 'L')
<span class="badge bg-primary">L</span>
@else
<span class="badge bg-secondary">P</span>
@endif
</td>
<td>{{ $item->nis }}</td>
<td>
<span class="badge bg-info">
{{ $item->kelas->nama_kelas ?? '-' }}
</span>
</td>
<td class="text-start">{{ $item->nama_ortu }}</td>
<td>{{ $item->user->email ?? '-' }}</td>
<td class="text-start">{{ $item->alamat }}</td>
<td>{{ $item->telepon }}</td>
<td>
@if($item->status == 'aktif')
<span class="badge bg-success">Aktif</span>
@else
<span class="badge bg-danger">Nonaktif</span>
@endif
</td>
<td>
<div class="d-flex justify-content-center gap-2">
<a href="{{ route('admin.siswa.edit', $item->id) }}"
class="btn btn-warning btn-sm">
Edit
</a>
@if($item->status == 'aktif')
<form action="{{ route('admin.siswa.nonaktif', $item->id) }}" method="POST" class="d-flex gap-1">
@csrf
<input type="text" name="alasan"
placeholder="Alasan..."
required
style="width:120px; font-size:12px;">
<button class="btn btn-danger btn-sm">
Nonaktif
</button>
</form>
@else
<a href="{{ route('admin.siswa.aktifkan', $item->id) }}"
class="btn btn-success btn-sm">
Aktifkan
</a>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="12" class="text-muted">
Tidak ada data siswa
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="sw-bulk-bar">
<button type="submit" class="sw-btn sw-btn-danger"
onclick="return confirm('Nonaktifkan siswa yang dipilih?')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
Nonaktifkan Terpilih
</button>
</div>
<div class="sw-card sw-table-card">
<div class="sw-table-wrap">
<table class="sw-table">
<thead>
<tr>
<th class="center" style="width:36px">
<input type="checkbox" id="checkAll" class="sw-check">
</th>
<th class="center" style="width:44px">No</th>
<th>Nama</th>
<th class="center">JK</th>
<th class="center">NIS</th>
<th class="center">Kelas</th>
<th>Orang Tua</th>
<th>Email</th>
<th>Alamat</th>
<th class="center">Telepon</th>
<th class="center">Status</th>
<th class="center" style="width:220px">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($siswa as $item)
<tr>
<td class="center">
<input type="checkbox" name="ids[]" value="{{ $item->id }}" class="sw-check">
</td>
<td class="center" style="color:var(--text-soft); font-size:12px;">
{{ $loop->iteration }}
</td>
<td class="name-cell">{{ $item->nama }}</td>
<td class="center">
@if($item->jenis_kelamin == 'L')
<span class="sw-badge sw-badge-primary">L</span>
@else
<span class="sw-badge sw-badge-neutral">P</span>
@endif
</td>
<td class="center" style="font-variant-numeric:tabular-nums; font-size:13px;">
{{ $item->nis }}
</td>
<td class="center">
<span class="sw-badge sw-badge-info">{{ $item->kelas->nama_kelas ?? '-' }}</span>
</td>
<td>{{ $item->nama_ortu }}</td>
<td style="font-size:12.5px; color:var(--text-soft);">{{ $item->user->email ?? '-' }}</td>
<td style="max-width:160px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" title="{{ $item->alamat }}">
{{ $item->alamat }}
</td>
<td class="center" style="font-size:12.5px;">{{ $item->telepon }}</td>
<td class="center">
@if($item->status == 'aktif')
<span class="sw-badge sw-badge-success">
<span class="sw-dot sw-dot-success"></span>Aktif
</span>
@else
<span class="sw-badge sw-badge-danger">
<span class="sw-dot sw-dot-danger"></span>Nonaktif
</span>
@endif
</td>
<td>
<div class="sw-action-cell">
<a href="{{ route('admin.siswa.edit', $item->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>
@if($item->status == 'aktif')
<form action="{{ route('admin.siswa.nonaktif', $item->id) }}" method="POST" class="sw-nonaktif-form">
@csrf
<input type="text" name="alasan" placeholder="Alasan..." required class="sw-nonaktif-input">
<button type="submit" class="sw-btn sw-btn-danger sw-btn-sm">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
Nonaktif
</button>
</form>
@else
<a href="{{ route('admin.siswa.aktifkan', $item->id) }}"
class="sw-btn sw-btn-success sw-btn-sm">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="20 6 9 17 4 12"/></svg>
Aktifkan
</a>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="12">
<div class="sw-empty">
<div class="sw-empty-icon">🎓</div>
<p>Tidak ada data siswa ditemukan</p>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</form>
</div>
{{-- CHECK ALL --}}
<script>
document.getElementById('checkAll').addEventListener('click', function() {
let checkboxes = document.querySelectorAll('input[name="ids[]"]');
checkboxes.forEach(cb => cb.checked = this.checked);
document.getElementById('checkAll').addEventListener('change', function () {
document.querySelectorAll('input[name="ids[]"]').forEach(cb => cb.checked = this.checked);
});
</script>

View File

@ -1,32 +1,161 @@
@extends('layouts.app')
@section('content')
<div class="container mt-5">
<div class="card shadow-sm col-md-6 mx-auto">
<div class="card-body">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<h4 class="mb-3 text-center">Ganti Password</h4>
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--primary-light:#EEF2FF;
--danger: #DC2626;
--neutral-light:#F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 14px;
}
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.sw-page {
min-height: 100vh; background: var(--bg);
display: flex; align-items: center; justify-content: center;
padding: 32px 16px;
}
.sw-card {
width: 100%; max-width: 440px;
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 4px 24px rgba(0,0,0,.07);
overflow: hidden;
}
.sw-card-header {
padding: 28px 32px 24px;
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
border-bottom: 1px solid #DDD6FE;
text-align: center;
}
.sw-card-header-icon {
width: 52px; height: 52px;
background: var(--primary);
border-radius: 14px;
display: flex; align-items: center; justify-content: center;
color: #fff; margin: 0 auto 16px;
box-shadow: 0 4px 14px rgba(79,70,229,.3);
}
.sw-card-header h4 {
font-size: 18px; font-weight: 700;
color: #3730A3; margin: 0 0 4px;
letter-spacing: -.3px;
}
.sw-card-header p {
font-size: 12.5px; color: #6D6AA4; margin: 0;
}
.sw-card-body { padding: 28px 32px; }
.sw-info-banner {
display: flex; align-items: flex-start; gap: 10px;
background: var(--primary-light);
border: 1px solid #C7D2FE;
border-radius: var(--radius-md);
padding: 12px 14px;
font-size: 12.5px; color: #3730A3;
margin-bottom: 24px; line-height: 1.6;
}
.sw-info-banner svg { flex-shrink: 0; margin-top: 1px; }
.sw-form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-mid); }
.sw-label span { color: var(--danger); margin-left: 2px; }
.sw-input-wrap { position: relative; }
.sw-input-wrap svg {
position: absolute; left: 14px; top: 50%;
transform: translateY(-50%); color: #9CA3AF; pointer-events: none;
}
.sw-input {
width: 100%;
padding: 11px 14px 11px 42px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px; font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark); background: var(--neutral-light);
outline: none; transition: all .15s;
}
.sw-input:focus {
border-color: var(--primary);
background: #fff;
box-shadow: 0 0 0 3px rgba(79,70,229,.1);
}
.sw-input::placeholder { color: #C3C7D0; }
.sw-btn-submit {
width: 100%; padding: 13px; border-radius: var(--radius-md); border: none;
background: var(--primary); color: #fff;
font-size: 14px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .18s; margin-top: 8px;
box-shadow: 0 4px 14px rgba(79,70,229,.3);
display: flex; align-items: center; justify-content: center; gap: 8px;
}
.sw-btn-submit:hover { background: var(--primary-dark); transform: translateY(-1px); box-shadow: 0 6px 20px rgba(79,70,229,.35); }
</style>
<div class="sw-page">
<div class="sw-card">
<div class="sw-card-header">
<div class="sw-card-header-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
</div>
<h4>Ganti Password</h4>
<p>Buat password baru yang kuat untuk akun Anda</p>
</div>
<div class="sw-card-body">
<div class="sw-info-banner">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
Anda diwajibkan mengganti password sebelum dapat menggunakan sistem. Gunakan minimal 8 karakter.
</div>
<form method="POST" action="{{ route('force.password.update') }}">
@csrf
<div class="mb-3">
<label>Password Baru</label>
<input type="password" name="password" class="form-control" required>
<div class="sw-form-group">
<label class="sw-label">Password Baru <span>*</span></label>
<div class="sw-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<input type="password" name="password" class="sw-input" placeholder="Minimal 8 karakter" required>
</div>
</div>
<div class="mb-3">
<label>Konfirmasi Password</label>
<input type="password" name="password_confirmation" class="form-control" required>
<div class="sw-form-group">
<label class="sw-label">Konfirmasi Password <span>*</span></label>
<div class="sw-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
<input type="password" name="password_confirmation" class="sw-input" placeholder="Ulangi password" required>
</div>
</div>
<button class="btn btn-primary w-100">Simpan Password</button>
<button type="submit" class="sw-btn-submit">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Simpan Password
</button>
</form>
</div>
</div>
</div>
@endsection

View File

@ -2,165 +2,177 @@
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Lupa Password</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lupa Password - Sistem Akademik</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Plus Jakarta Sans', sans-serif; }
html, body { height: 100%; }
body {
height: 100vh;
display: flex;
}
.page { display: flex; height: 100vh; width: 100%; }
/* CONTAINER */
.container-login {
display: flex;
width: 100%;
height: 100vh;
}
/* ── LEFT ── */
.left {
width: 38%;
background: #F3F4F8;
display: flex; flex-direction: column;
justify-content: center; align-items: center;
text-align: center; padding: 48px 40px;
position: relative;
}
.left::after {
content: '';
position: absolute; right: 0; top: 0; bottom: 0; width: 1px;
background: linear-gradient(to bottom, transparent, #C7D2FE 30%, #C7D2FE 70%, transparent);
}
/* LEFT */
.left {
width: 40%;
background: #f4f6fb;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 40px;
}
.logo {
width: 280px;
max-width: 100%;
margin-bottom: 32px;
.logo {
width: 250px;
margin-bottom: 25px;
}
filter: drop-shadow(0 8px 24px rgba(79,70,229,.18));
transition: transform .3s;
}
.left-title { font-size: 22px; font-weight: 800; color: #1E3A6E; line-height: 1.3; margin-bottom: 10px; }
.left-sub { font-size: 13px; color: #6B7280; line-height: 1.7; }
.left h2 {
color: #2c5aa0;
margin-bottom: 10px;
}
.left-badge {
margin-top: 28px;
display: inline-flex; align-items: center; gap: 6px;
background: #EEF2FF; border: 1px solid #C7D2FE;
border-radius: 99px; padding: 6px 16px;
font-size: 11.5px; font-weight: 600; color: #4F46E5;
}
.left-badge span { width: 6px; height: 6px; background: #4F46E5; border-radius: 50%; display: inline-block; }
.left p {
color: #666;
font-size: 14px;
}
/* ── RIGHT ── */
.right {
flex: 1;
background: linear-gradient(145deg, #1E3A6E 0%, #2D5BB5 55%, #4F8DD6 100%);
display: flex; justify-content: center; align-items: center;
position: relative; overflow: hidden;
}
.right::before {
content: ''; position: absolute;
width: 420px; height: 420px; border-radius: 50%;
border: 1px solid rgba(255,255,255,.07);
top: -80px; right: -80px;
}
.right::after {
content: ''; position: absolute;
width: 300px; height: 300px; border-radius: 50%;
border: 1px solid rgba(255,255,255,.06);
bottom: -60px; left: -60px;
}
/* RIGHT */
.right {
width: 60%;
background: linear-gradient(135deg, #3b6cb7, #4f8dd6);
display: flex;
justify-content: center;
align-items: center;
}
/* ── FORM BOX ── */
.form-box { width: 340px; position: relative; z-index: 2; }
/* FORM */
.form-box {
width: 320px;
text-align: center;
color: white;
}
.form-title {
font-size: 24px; font-weight: 800; color: #fff;
letter-spacing: -.5px; margin-bottom: 6px;
}
.form-sub {
font-size: 13px; color: rgba(255,255,255,.65);
line-height: 1.6; margin-bottom: 28px;
}
.form-box h3 {
font-size: 20px;
margin-bottom: 5px;
letter-spacing: 1px;
}
/* ── INPUT ── */
.input-wrap { position: relative; margin-bottom: 14px; }
.input-wrap svg {
position: absolute; left: 16px; top: 50%;
transform: translateY(-50%); color: rgba(255,255,255,.5); pointer-events: none;
}
.input-wrap input {
width: 100%;
padding: 13px 16px 13px 44px;
border-radius: 12px;
border: 1.5px solid rgba(255,255,255,.15);
background: rgba(255,255,255,.12);
color: #fff; font-size: 14px;
font-family: 'Plus Jakarta Sans', sans-serif;
outline: none; transition: all .2s;
backdrop-filter: blur(6px);
}
.input-wrap input::placeholder { color: rgba(255,255,255,.45); }
.input-wrap input:focus {
border-color: rgba(255,255,255,.5);
background: rgba(255,255,255,.18);
box-shadow: 0 0 0 3px rgba(255,255,255,.1);
}
.form-box p {
font-size: 13px;
margin-bottom: 20px;
opacity: 0.9;
}
/* ── BUTTON ── */
.btn-submit {
width: 100%; padding: 13px; border-radius: 12px; border: none;
background: linear-gradient(135deg, #10B981, #059669);
color: #fff; font-size: 14px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .2s; margin-top: 4px;
box-shadow: 0 4px 16px rgba(5,150,105,.35);
}
.btn-submit:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(5,150,105,.4); }
input {
width: 100%;
padding: 12px;
border-radius: 25px;
border: none;
margin-bottom: 15px;
outline: none;
}
button {
width: 100%;
padding: 12px;
border-radius: 25px;
border: none;
background: #00c851;
color: white;
font-weight: bold;
cursor: pointer;
}
button:hover {
background: #00a844;
}
.back {
margin-top: 10px;
display: block;
color: white;
font-size: 13px;
}
.alert {
background: rgba(255,255,255,0.2);
padding: 10px;
border-radius: 10px;
margin-bottom: 10px;
font-size: 12px;
}
/* ── ALERT ── */
.sw-alert-success {
display: flex; align-items: center; gap: 8px;
background: rgba(16,185,129,.15); border: 1px solid rgba(16,185,129,.3);
border-radius: 10px; padding: 11px 14px;
font-size: 12.5px; color: #6EE7B7; margin-bottom: 18px;
}
/* ── BACK LINK ── */
.back-link {
display: flex; align-items: center; justify-content: center; gap: 6px;
margin-top: 20px;
font-size: 12.5px; color: rgba(255,255,255,.6);
text-decoration: none; transition: color .2s;
}
.back-link:hover { color: #fff; }
</style>
</head>
<body>
<div class="container-login">
<div class="page">
<!-- LEFT -->
<div class="left">
<img src="{{ asset('sbadmin2/img/logosmasa.png') }}" class="logo">
<h2>SMAN 1 BONDOWOSO</h2>
<p>Silakan masukkan email untuk reset password</p>
<img src="{{ asset('sbadmin2/img/logosmasa.png') }}" class="logo" alt="Logo">
<div class="left-title">SMAN 1<br>BONDOWOSO</div>
<div class="left-sub">Silakan masukkan email untuk<br>reset password akun Anda</div>
<div class="left-badge"><span></span> Sistem Akademik Sekolah</div>
</div>
<!-- RIGHT -->
<div class="right">
<div class="form-box">
<h3>LUPA PASSWORD</h3>
<p>Masukkan email untuk menerima kode OTP</p>
<div class="form-title">Lupa Password?</div>
<div class="form-sub">Masukkan email terdaftar dan kami akan mengirimkan kode OTP untuk reset password.</div>
@if(session('success'))
<div class="alert">
{{ session('success') }}
</div>
<div class="sw-alert-success">
<svg width="15" height="15" 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
<form method="POST" action="/forgot-password">
@csrf
<input type="email" name="email" placeholder="Enter Email" required>
<button>Kirim OTP</button>
<a href="/login" class="back">← Kembali ke Login</a>
<div class="input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
<input type="email" name="email" placeholder="Enter Email" required>
</div>
<button type="submit" class="btn-submit">Kirim OTP</button>
</form>
<a href="/login" class="back-link">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Kembali ke Login
</a>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,275 +1,618 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Sistem Akademik</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Login - Sistem Akademik</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap"
rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Plus Jakarta Sans', sans-serif;
}
html, body {
height: 100%;
}
html, body { height: 100%; }
/* CONTAINER */
.container-login {
display: flex;
height: 100vh;
width: 100%;
}
.page {
display: flex;
height: 100vh;
width: 100%;
}
/* LEFT SIDE */
.left {
width: 40%;
background: #f4f6fb;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 40px;
}
/* LEFT */
.left {
width: 38%;
background: #F3F4F8;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 48px 40px;
position: relative;
}
/* LOGO */
.logo {
width: 300px;
margin-bottom: 25px;
transition: 0.3s;
}
.left::after {
content: '';
position: absolute;
right: 0; top: 0; bottom: 0;
width: 1px;
background: linear-gradient(to bottom, transparent, #C7D2FE 30%, #C7D2FE 70%, transparent);
}
.logo:hover {
transform: scale(1.05);
}
.logo {
width: 280px;
max-width: 100%;
margin-bottom: 32px;
filter: drop-shadow(0 8px 24px rgba(79, 70, 229, .18));
transition: transform .3s;
}
/* TEXT */
.left h2 {
color: #2c5aa0;
font-size: 26px;
margin-bottom: 10px;
font-weight: 600;
}
.logo:hover { transform: scale(1.04); }
.left p {
color: #666;
font-size: 14px;
}
.left-title {
font-size: 22px;
font-weight: 800;
color: #1E3A6E;
line-height: 1.3;
margin-bottom: 10px;
letter-spacing: -.3px;
}
/* RIGHT SIDE */
.right {
width: 60%;
background: linear-gradient(135deg, #3b6cb7, #4f8dd6);
display: flex;
justify-content: center;
align-items: center;
}
.left-sub {
font-size: 13px;
color: #6B7280;
line-height: 1.7;
}
/* LOGIN FORM */
.login-form {
width: 280px;
text-align: center;
color: white;
}
.left-badge {
margin-top: 32px;
display: inline-flex;
align-items: center;
gap: 6px;
background: #EEF2FF;
border: 1px solid #C7D2FE;
border-radius: 99px;
padding: 6px 16px;
font-size: 11.5px;
font-weight: 600;
color: #4F46E5;
}
/* TITLE */
.login-form h3 {
margin-bottom: 5px;
font-weight: 600;
}
.left-badge span {
width: 6px; height: 6px;
background: #4F46E5;
border-radius: 50%;
display: inline-block;
}
.login-form small {
display: block;
margin-bottom: 20px;
opacity: 0.8;
}
/* RIGHT */
.right {
flex: 1;
background: linear-gradient(145deg, #1E3A6E 0%, #2D5BB5 55%, #4F8DD6 100%);
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
}
/* INPUT */
.login-form input {
width: 100%;
padding: 13px;
border-radius: 30px;
border: none;
margin-bottom: 15px;
outline: none;
transition: 0.3s;
}
.right::before {
content: '';
position: absolute;
width: 420px; height: 420px;
border-radius: 50%;
border: 1px solid rgba(255,255,255,.07);
top: -80px; right: -80px;
}
.login-form input:focus {
box-shadow: 0 0 0 2px rgba(255,255,255,0.5);
}
.right::after {
content: '';
position: absolute;
width: 300px; height: 300px;
border-radius: 50%;
border: 1px solid rgba(255,255,255,.06);
bottom: -60px; left: -60px;
}
/* BUTTON */
.btn-login {
width: 100%;
padding: 13px;
border-radius: 30px;
border: none;
background: linear-gradient(135deg, #00c851, #00a844);
color: white;
font-weight: bold;
cursor: pointer;
transition: 0.3s;
}
/* FORM BOX */
.form-box {
width: 340px;
position: relative;
z-index: 2;
}
.btn-login:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
}
.form-title {
font-size: 26px;
font-weight: 800;
color: #fff;
letter-spacing: -.5px;
margin-bottom: 4px;
}
/* LINK */
.forgot {
margin-top: 10px;
font-size: 13px;
}
.form-sub {
font-size: 12px;
color: rgba(255,255,255,.6);
letter-spacing: 1.5px;
text-transform: uppercase;
margin-bottom: 28px;
}
.forgot a {
color: #fff;
text-decoration: underline;
opacity: 0.8;
}
.form-divider {
display: flex;
align-items: center;
gap: 10px;
margin: 18px 0;
}
.forgot a:hover {
opacity: 1;
}
.form-divider-line {
flex: 1;
height: 1px;
background: rgba(255,255,255,.2);
}
/* ALERT */
.alert {
background: rgba(255,255,255,0.2);
padding: 10px;
border-radius: 10px;
margin-bottom: 15px;
font-size: 12px;
}
.form-divider-text {
font-size: 11px;
color: rgba(255,255,255,.5);
font-weight: 600;
letter-spacing: .5px;
}
/* TITLE */
.login-form h3 {
font-size: 26px;
font-weight: 700;
letter-spacing: 1px;
margin-bottom: 5px;
color: #ffffff;
}
.section-label {
font-size: 11px;
font-weight: 700;
color: rgba(255,255,255,.5);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 10px;
}
/* SUBTITLE */
.login-form small {
display: block;
margin-bottom: 25px;
font-size: 12px;
color: rgba(255,255,255,0.7);
letter-spacing: 1px;
}
.input-wrap {
position: relative;
margin-bottom: 12px;
}
/* INPUT */
.login-form input {
width: 100%;
padding: 14px 18px;
border-radius: 30px;
border: none;
margin-bottom: 15px;
outline: none;
background: rgba(255,255,255,0.9);
font-size: 14px;
transition: 0.3s;
}
.input-wrap svg {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: #9CA3AF;
pointer-events: none;
}
.login-form input::placeholder {
color: #999;
}
.input-wrap input {
width: 100%;
padding: 13px 16px 13px 44px;
border-radius: 12px;
border: 1.5px solid rgba(255,255,255,.15);
background: rgba(255,255,255,.12);
color: #fff;
font-size: 14px;
font-family: 'Plus Jakarta Sans', sans-serif;
outline: none;
transition: all .2s;
backdrop-filter: blur(6px);
}
/* FOCUS EFFECT */
.login-form input:focus {
background: #fff;
box-shadow: 0 0 0 3px rgba(255,255,255,0.3);
}
.input-wrap input::placeholder { color: rgba(255,255,255,.45); }
/* BUTTON */
.btn-login {
width: 100%;
padding: 14px;
border-radius: 30px;
border: none;
background: linear-gradient(135deg, #00c851, #00a844);
color: white;
font-weight: 600;
font-size: 15px;
cursor: pointer;
transition: 0.3s;
}
.input-wrap input:focus {
border-color: rgba(255,255,255,.5);
background: rgba(255,255,255,.18);
box-shadow: 0 0 0 3px rgba(255,255,255,.1);
}
.btn-login:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0,0,0,0.25);
}
</style>
.btn-otp {
width: 100%;
padding: 13px;
border-radius: 12px;
border: 1.5px solid rgba(255,255,255,.35);
background: rgba(255,255,255,.1);
color: #fff;
font-size: 13.5px;
font-weight: 600;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer;
transition: all .2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 4px;
}
.btn-otp:hover {
background: rgba(255,255,255,.2);
border-color: rgba(255,255,255,.5);
transform: translateY(-1px);
}
.btn-login {
width: 100%;
padding: 13px;
border-radius: 12px;
border: none;
background: linear-gradient(135deg, #10B981, #059669);
color: #fff;
font-size: 14px;
font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer;
transition: all .2s;
margin-top: 4px;
box-shadow: 0 4px 16px rgba(5,150,105,.35);
}
.btn-login:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(5,150,105,.4);
}
/* Alert error */
.sw-alert {
display: flex;
align-items: center;
gap: 8px;
background: rgba(239,68,68,.15);
border: 1px solid rgba(239,68,68,.3);
border-radius: 10px;
padding: 10px 14px;
font-size: 12.5px;
color: #FCA5A5;
margin-bottom: 16px;
}
/* Alert success / OTP sent */
.sw-success {
display: flex;
align-items: center;
gap: 8px;
background: rgba(16,185,129,.15);
border: 1px solid rgba(16,185,129,.35);
border-radius: 10px;
padding: 10px 14px;
font-size: 12.5px;
color: #6EE7B7;
margin-bottom: 16px;
}
.forgot-link {
display: block;
text-align: center;
margin-top: 16px;
font-size: 12.5px;
color: rgba(255,255,255,.6);
text-decoration: none;
transition: color .2s;
}
.forgot-link:hover { color: #fff; }
.method-btn {
flex: 1;
padding: 9px 10px;
border-radius: 10px;
border: 1.5px solid rgba(255,255,255,.2);
background: rgba(255,255,255,.08);
color: rgba(255,255,255,.7);
font-size: 12px;
font-weight: 600;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer;
transition: all .2s;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.method-btn:hover {
background: rgba(255,255,255,.15);
border-color: rgba(255,255,255,.4);
color: #fff;
}
.method-btn.active {
background: rgba(255,255,255,.22);
border-color: #fff;
color: #fff;
}
/* Admin info badge */
.admin-info-badge {
display: none;
font-size: 11.5px;
color: rgba(255,255,255,.75);
background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.18);
border-radius: 10px;
padding: 9px 13px;
margin-bottom: 12px;
align-items: center;
gap: 7px;
}
.admin-info-badge svg {
flex-shrink: 0;
color: #6EE7B7;
}
</style>
</head>
<body>
<div class="container-login">
<div class="page">
<!-- LEFT -->
<div class="left">
<img src="{{ asset('sbadmin2/img/logosmasa.png') }}" class="logo">
<h2>SMAN 1<br>BONDOWOSO</h2>
<p>Masukkan email dan password<br>untuk melanjutkan</p>
<img src="{{ asset('sbadmin2/img/logosmasa.png') }}" class="logo" alt="Logo">
<div class="left-title">SMAN 1<br>BONDOWOSO</div>
<div class="left-sub">Masukkan email dan password<br>untuk melanjutkan ke portal</div>
<div class="left-badge">
<span></span> Sistem Akademik Sekolah
</div>
</div>
<!-- RIGHT -->
<div class="right">
<div class="login-form">
<!-- RIGHT -->
<div class="right">
<div class="form-box">
<h3>SIGN IN</h3>
<small>TO ACCESS THE PORTAL</small>
<div class="form-title">SIGN IN</div>
<div class="form-sub">TO ACCESS THE PORTAL</div>
@if(session('error'))
<div class="alert">
{{ session('error') }}
{{-- Alert error --}}
@if(session('error'))
<div class="sw-alert">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
<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>
{{ session('error') }}
</div>
@endif
{{-- Alert OTP berhasil dikirim --}}
@if(session('otp_sent'))
<div class="sw-success">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
<polyline points="20 6 9 17 4 12"/>
</svg>
{{ session('otp_sent') }}
</div>
@endif
{{-- ============================================================
LANGKAH 1: Dapatkan OTP
Langkah ini disembunyikan otomatis jika email adalah admin
============================================================ --}}
<div id="step1-section">
<div class="section-label">Langkah 1 — Dapatkan OTP</div>
<div style="font-size:10px; font-weight:700; color:rgba(255,255,255,.5); text-transform:uppercase; letter-spacing:1px; margin-bottom:8px;">
Pilih metode pengiriman OTP
</div>
<div style="display:flex; gap:8px; margin-bottom:10px;">
<button type="button" class="method-btn active" id="btn-email-method" onclick="selectMethod('email')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
Email
</button>
<button type="button" class="method-btn" id="btn-wa-method" onclick="selectMethod('wa')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
</svg>
WhatsApp
</button>
</div>
<form method="POST" action="{{ route('send.otp') }}" id="form-otp">
@csrf
<input type="hidden" name="method" id="otp_method" value="email">
{{-- Input email OTP --}}
<div class="input-wrap" id="wrap-email-otp">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
<input type="email" name="email" id="input-email-otp" placeholder="Email untuk OTP" required>
</div>
{{-- Input nomor WA --}}
<div class="input-wrap" id="wrap-wa-otp" style="display:none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2A19.79 19.79 0 0 1 3.08 4.18 2 2 0 0 1 5.09 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L9.91 9.91a16 16 0 0 0 6.18 6.18l1.27-1.34a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/>
</svg>
<input type="text" name="telepon" id="input-wa-otp" placeholder="Nomor WhatsApp (08xx...)">
</div>
<button type="submit" class="btn-otp">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
<span id="btn-otp-label">Dapatkan OTP via Email</span>
</button>
</form>
<div class="form-divider">
<div class="form-divider-line"></div>
<div class="form-divider-text">Langkah 2 — Login</div>
<div class="form-divider-line"></div>
</div>
</div>
@endif
{{-- FORM KIRIM OTP --}}
<form method="POST" action="{{ route('send.otp') }}">
@csrf
{{-- Divider khusus admin (tampil menggantikan step1 + divider di atas) --}}
<div id="admin-divider" style="display:none;">
<div class="form-divider" style="margin-bottom:16px;">
<div class="form-divider-line"></div>
<div class="form-divider-text">Login Admin</div>
<div class="form-divider-line"></div>
</div>
</div>
<input type="email" name="email" placeholder="Email untuk OTP" required>
{{-- ============================================================
LANGKAH 2: Login
Field OTP disembunyikan otomatis untuk admin
============================================================ --}}
<form method="POST" action="{{ route('login.otp') }}">
@csrf
<button type="submit" style="margin-bottom:10px;">
🔑 Dapatkan OTP
</button>
</form>
{{-- Email --}}
<div class="input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
<input
type="email"
name="email"
id="login-email"
placeholder="Email"
value="{{ session('email') ?? old('email') }}"
oninput="checkAdminEmail(this.value)"
required
autocomplete="email"
>
</div>
{{-- Password --}}
<div class="input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
</svg>
<input type="password" name="password" placeholder="Password" required autocomplete="current-password">
</div>
{{-- FORM LOGIN --}}
<form method="POST" action="{{ route('login.otp') }}">
@csrf
{{-- OTP — disembunyikan untuk admin --}}
<div class="input-wrap" id="wrap-otp-field">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 11 12 14 22 4"/>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
</svg>
<input
type="text"
name="otp"
id="otp-input"
placeholder="Masukkan OTP"
maxlength="6"
inputmode="numeric"
autocomplete="one-time-code"
>
</div>
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Password" required>
<input type="text" name="otp" placeholder="Masukkan OTP">
{{-- Badge info admin --}}
<div class="admin-info-badge" id="admin-info-badge">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
Akun admin tidak memerlukan kode OTP
</div>
<button type="submit">Login</button>
</form>
<div style="margin-top:10px;">
<a href="{{ route('password.request') }}" style="font-size:14px; color:#fff;">
Lupa Password?
</a>
</div>
<button type="submit" class="btn-login">Masuk ke Portal</button>
</form>
<a href="{{ route('password.request') }}" class="forgot-link">Lupa Password?</a>
</div>
</div>
</div>
<script>
// ================================================================
// Daftar email admin — sesuaikan dengan data di database Anda
// ================================================================
// const ADMIN_EMAILS = [
// 'admin@sman1bondowoso.sch.id',
// // tambahkan email admin lain di sini jika ada
// ];
// ================================================================
// Toggle tampilan Langkah 1 (OTP section) berdasarkan role
// ================================================================
function checkAdminEmail(email) {
const isAdmin = ADMIN_EMAILS.includes(email.trim().toLowerCase());
const step1 = document.getElementById('step1-section');
const adminDiv = document.getElementById('admin-divider');
const otpWrap = document.getElementById('wrap-otp-field');
const otpInput = document.getElementById('otp-input');
const adminBadge = document.getElementById('admin-info-badge');
if (isAdmin) {
// Sembunyikan Langkah 1 dan field OTP
step1.style.display = 'none';
adminDiv.style.display = 'block';
otpWrap.style.display = 'none';
adminBadge.style.display = 'flex';
otpInput.required = false;
otpInput.value = '';
} else {
// Tampilkan kembali semua untuk guru/siswa
step1.style.display = 'block';
adminDiv.style.display = 'none';
otpWrap.style.display = 'block';
adminBadge.style.display = 'none';
otpInput.required = true;
}
}
// ================================================================
// Toggle metode OTP (email / WhatsApp)
// ================================================================
function selectMethod(method) {
const emailBtn = document.getElementById('btn-email-method');
const waBtn = document.getElementById('btn-wa-method');
const emailWrap = document.getElementById('wrap-email-otp');
const waWrap = document.getElementById('wrap-wa-otp');
const emailInput = document.getElementById('input-email-otp');
const waInput = document.getElementById('input-wa-otp');
const label = document.getElementById('btn-otp-label');
const methodInput = document.getElementById('otp_method');
if (method === 'email') {
emailBtn.classList.add('active');
waBtn.classList.remove('active');
emailWrap.style.display = 'block';
waWrap.style.display = 'none';
emailInput.required = true;
waInput.required = false;
waInput.value = '';
label.textContent = 'Dapatkan OTP via Email';
methodInput.value = 'email';
} else {
waBtn.classList.add('active');
emailBtn.classList.remove('active');
waWrap.style.display = 'block';
emailWrap.style.display = 'none';
waInput.required = true;
emailInput.required = false;
emailInput.value = '';
label.textContent = 'Dapatkan OTP via WhatsApp';
methodInput.value = 'wa';
}
}
// ================================================================
// Jalankan saat halaman load — untuk prefill dari session (via WA)
// ================================================================
document.addEventListener('DOMContentLoaded', function () {
const emailEl = document.getElementById('login-email');
if (emailEl && emailEl.value.trim() !== '') {
checkAdminEmail(emailEl.value);
}
});
</script>
</body>
</html>

View File

@ -1,35 +1,173 @@
@extends('layouts.app')
@section('content')
<div class="container mt-5">
<div class="card shadow p-4">
<h4 class="mb-3 text-center">Reset Password</h4>
{{-- ERROR --}}
@if(session('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
@endif
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
{{-- SUCCESS --}}
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
:root {
--warning: #D97706;
--warning-light: #FFFBEB;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--danger-light: #FEF2F2;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 14px;
}
<form method="POST" action="{{ route('password.update') }}">
@csrf
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
<input type="hidden" name="email" value="{{ request('email') }}">
.sw-page {
min-height: 100vh; background: var(--bg);
display: flex; align-items: center; justify-content: center;
padding: 32px 16px;
}
<div>
<input type="password" name="password" placeholder="Password baru" required>
.sw-card {
width: 100%; max-width: 440px;
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 4px 24px rgba(0,0,0,.07);
overflow: hidden;
}
.sw-card-header {
padding: 28px 32px 24px;
background: linear-gradient(135deg, #FFFBEB 0%, #FEF3C7 100%);
border-bottom: 1px solid #FDE68A;
text-align: center;
}
.sw-card-header-icon {
width: 52px; height: 52px;
background: var(--warning);
border-radius: 14px;
display: flex; align-items: center; justify-content: center;
color: #fff; margin: 0 auto 16px;
box-shadow: 0 4px 14px rgba(217,119,6,.3);
}
.sw-card-header h4 {
font-size: 18px; font-weight: 700;
color: #92400E; margin: 0 0 4px;
letter-spacing: -.3px;
}
.sw-card-header p { font-size: 12.5px; color: #A16207; margin: 0; }
.sw-card-body { padding: 28px 32px; }
/* ALERTS */
.sw-alert {
display: flex; align-items: center; gap: 9px;
border-radius: var(--radius-md);
padding: 12px 14px;
font-size: 12.5px; margin-bottom: 20px;
}
.sw-alert-danger { background: var(--danger-light); border: 1px solid #FECACA; color: var(--danger); }
.sw-alert-success { background: var(--success-light); border: 1px solid #A7F3D0; color: var(--success); }
/* FORM */
.sw-form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-mid); }
.sw-label span { color: var(--danger); margin-left: 2px; }
.sw-input-wrap { position: relative; }
.sw-input-wrap svg {
position: absolute; left: 14px; top: 50%;
transform: translateY(-50%); color: #9CA3AF; pointer-events: none;
}
.sw-input {
width: 100%;
padding: 11px 14px 11px 42px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px; font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark); background: var(--neutral-light);
outline: none; transition: all .15s;
}
.sw-input:focus {
border-color: var(--warning);
background: #fff;
box-shadow: 0 0 0 3px rgba(217,119,6,.1);
}
.sw-input::placeholder { color: #C3C7D0; }
.sw-btn-submit {
width: 100%; padding: 13px; border-radius: var(--radius-md); border: none;
background: var(--warning); color: #fff;
font-size: 14px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .18s; margin-top: 8px;
box-shadow: 0 4px 14px rgba(217,119,6,.3);
display: flex; align-items: center; justify-content: center; gap: 8px;
}
.sw-btn-submit:hover { background: #B45309; transform: translateY(-1px); box-shadow: 0 6px 20px rgba(217,119,6,.35); }
</style>
<div class="sw-page">
<div class="sw-card">
<div class="sw-card-header">
<div class="sw-card-header-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>
</div>
<h4>Reset Password</h4>
<p>Buat password baru untuk akun Anda</p>
</div>
<div>
<input type="password" name="password_confirmation" placeholder="Konfirmasi password" required>
<div class="sw-card-body">
{{-- ERROR --}}
@if(session('error'))
<div class="sw-alert sw-alert-danger">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><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>
{{ session('error') }}
</div>
@endif
<button type="submit">Reset Password</button>
</form>
{{-- SUCCESS --}}
@if(session('success'))
<div class="sw-alert sw-alert-success">
<svg width="15" height="15" 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
<form method="POST" action="{{ route('password.update') }}">
@csrf
<input type="hidden" name="email" value="{{ request('email') }}">
<div class="sw-form-group">
<label class="sw-label">Password Baru <span>*</span></label>
<div class="sw-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<input type="password" name="password" class="sw-input" placeholder="Password baru" required>
</div>
</div>
<div class="sw-form-group">
<label class="sw-label">Konfirmasi Password <span>*</span></label>
<div class="sw-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
<input type="password" name="password_confirmation" class="sw-input" placeholder="Konfirmasi password" required>
</div>
</div>
<button type="submit" class="sw-btn-submit">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Reset Password
</button>
</form>
</div>
</div>
</div>
@endsection

View File

@ -2,161 +2,267 @@
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Verifikasi OTP</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verifikasi OTP - Sistem Akademik</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Plus Jakarta Sans', sans-serif; }
html, body { height: 100%; }
body {
height: 100vh;
display: flex;
}
.page { display: flex; height: 100vh; width: 100%; }
.container-login {
display: flex;
width: 100%;
height: 100vh;
}
/* ── LEFT ── */
.left {
width: 38%;
background: #F3F4F8;
display: flex; flex-direction: column;
justify-content: center; align-items: center;
text-align: center; padding: 48px 40px;
position: relative;
}
.left::after {
content: '';
position: absolute; right: 0; top: 0; bottom: 0; width: 1px;
background: linear-gradient(to bottom, transparent, #C7D2FE 30%, #C7D2FE 70%, transparent);
}
/* LEFT */
.left {
width: 40%;
background: #f4f6fb;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 40px;
}
.logo {
width: 280px;
max-width: 100%;
margin-bottom: 32px;
.logo {
width: 250px;
margin-bottom: 25px;
}
filter: drop-shadow(0 8px 24px rgba(79,70,229,.18));
transition: transform .3s;
}
.left-title { font-size: 22px; font-weight: 800; color: #1E3A6E; line-height: 1.3; margin-bottom: 10px; }
.left-sub { font-size: 13px; color: #6B7280; line-height: 1.7; }
.left h2 {
color: #2c5aa0;
margin-bottom: 10px;
}
.left-badge {
margin-top: 28px;
display: inline-flex; align-items: center; gap: 6px;
background: #EEF2FF; border: 1px solid #C7D2FE;
border-radius: 99px; padding: 6px 16px;
font-size: 11.5px; font-weight: 600; color: #4F46E5;
}
.left-badge span { width: 6px; height: 6px; background: #4F46E5; border-radius: 50%; display: inline-block; }
.left p {
color: #666;
font-size: 14px;
}
/* step indicators */
.steps {
margin-top: 36px;
display: flex; flex-direction: column; gap: 12px;
width: 100%; max-width: 220px;
}
.step {
display: flex; align-items: center; gap: 12px;
text-align: left;
}
.step-dot {
width: 30px; height: 30px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 12px; font-weight: 700; flex-shrink: 0;
}
.step-dot.done { background: #D1FAE5; color: #059669; }
.step-dot.active { background: #4F46E5; color: #fff; box-shadow: 0 0 0 4px #C7D2FE; }
.step-dot.pending { background: #F3F4F6; color: #9CA3AF; }
.step-text { font-size: 12.5px; font-weight: 600; }
.step-text.done { color: #059669; }
.step-text.active { color: #4F46E5; }
.step-text.pending{ color: #9CA3AF; }
/* RIGHT */
.right {
width: 60%;
background: linear-gradient(135deg, #3b6cb7, #4f8dd6);
display: flex;
justify-content: center;
align-items: center;
}
/* ── RIGHT ── */
.right {
flex: 1;
background: linear-gradient(145deg, #1E3A6E 0%, #2D5BB5 55%, #4F8DD6 100%);
display: flex; justify-content: center; align-items: center;
position: relative; overflow: hidden;
}
.right::before {
content: ''; position: absolute;
width: 420px; height: 420px; border-radius: 50%;
border: 1px solid rgba(255,255,255,.07);
top: -80px; right: -80px;
}
.right::after {
content: ''; position: absolute;
width: 300px; height: 300px; border-radius: 50%;
border: 1px solid rgba(255,255,255,.06);
bottom: -60px; left: -60px;
}
/* FORM */
.form-box {
width: 320px;
text-align: center;
color: white;
}
/* ── FORM BOX ── */
.form-box {
width: 340px;
position: relative; z-index: 2;
text-align: center;
}
.form-box h3 {
font-size: 20px;
margin-bottom: 5px;
}
/* OTP icon ring */
.otp-icon {
width: 68px; height: 68px;
border-radius: 50%;
background: rgba(255,255,255,.12);
border: 2px solid rgba(255,255,255,.25);
display: flex; align-items: center; justify-content: center;
margin: 0 auto 22px;
backdrop-filter: blur(8px);
}
.form-box p {
font-size: 13px;
margin-bottom: 20px;
}
.form-title {
font-size: 24px; font-weight: 800;
color: #fff; letter-spacing: -.5px; margin-bottom: 6px;
}
.form-sub {
font-size: 13px; color: rgba(255,255,255,.65);
line-height: 1.6; margin-bottom: 32px;
}
input {
width: 100%;
padding: 12px;
border-radius: 25px;
border: none;
margin-bottom: 15px;
outline: none;
}
/* ── OTP INPUT ── */
.input-wrap { position: relative; margin-bottom: 16px; }
.input-wrap svg {
position: absolute; left: 16px; top: 50%;
transform: translateY(-50%); color: rgba(255,255,255,.5); pointer-events: none;
}
.input-wrap input {
width: 100%;
padding: 15px 16px 15px 48px;
border-radius: 14px;
border: 1.5px solid rgba(255,255,255,.2);
background: rgba(255,255,255,.12);
color: #fff; font-size: 20px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
letter-spacing: 6px; text-align: center;
outline: none; transition: all .2s;
backdrop-filter: blur(6px);
}
.input-wrap input::placeholder {
color: rgba(255,255,255,.3);
font-size: 14px; font-weight: 400; letter-spacing: 1px;
}
.input-wrap input:focus {
border-color: rgba(255,255,255,.55);
background: rgba(255,255,255,.2);
box-shadow: 0 0 0 3px rgba(255,255,255,.12);
}
button {
width: 100%;
padding: 12px;
border-radius: 25px;
border: none;
background: #00c851;
color: white;
font-weight: bold;
cursor: pointer;
}
/* expiry hint */
.otp-hint {
font-size: 12px; color: rgba(255,255,255,.5);
margin-bottom: 20px; display: flex; align-items: center;
justify-content: center; gap: 5px;
}
button:hover {
background: #00a844;
}
/* ── BUTTON ── */
.btn-verify {
width: 100%; padding: 14px; border-radius: 12px; border: none;
background: linear-gradient(135deg, #10B981, #059669);
color: #fff; font-size: 14px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .2s;
box-shadow: 0 4px 16px rgba(5,150,105,.4);
display: flex; align-items: center; justify-content: center; gap: 8px;
}
.btn-verify:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(5,150,105,.45); }
.back {
margin-top: 10px;
display: block;
color: white;
font-size: 13px;
}
/* ── ALERT ── */
.sw-alert {
display: flex; align-items: center; gap: 8px;
background: rgba(239,68,68,.15); border: 1px solid rgba(239,68,68,.3);
border-radius: 10px; padding: 11px 14px;
font-size: 12.5px; color: #FCA5A5; margin-bottom: 18px;
text-align: left;
}
.alert {
background: rgba(255,255,255,0.2);
padding: 10px;
border-radius: 10px;
margin-bottom: 10px;
font-size: 12px;
}
/* ── BACK LINK ── */
.back-link {
display: flex; align-items: center; justify-content: center; gap: 6px;
margin-top: 20px;
font-size: 12.5px; color: rgba(255,255,255,.55);
text-decoration: none; transition: color .2s;
}
.back-link:hover { color: #fff; }
@media (max-width: 640px) {
.left { display: none; }
.right { width: 100%; }
}
</style>
</head>
<body>
<div class="container-login">
<div class="page">
<!-- LEFT -->
<div class="left">
<img src="{{ asset('sbadmin2/img/logosmasa.png') }}" class="logo">
<img src="{{ asset('sbadmin2/img/logosmasa.png') }}" class="logo" alt="Logo">
<div class="left-title">SMAN 1<br>BONDOWOSO</div>
<div class="left-sub">Masukkan kode OTP yang telah<br>dikirim ke email Anda</div>
<div class="left-badge"><span></span> Verifikasi 2 Langkah</div>
<h2>SMAN 1 BONDOWOSO</h2>
<p>Masukkan kode OTP yang telah dikirim ke email</p>
<!-- Step indicators -->
<div class="steps">
<div class="step">
<div class="step-dot done">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<div class="step-text done">Email & Password</div>
</div>
<div class="step">
<div class="step-dot active">2</div>
<div class="step-text active">Verifikasi OTP</div>
</div>
<div class="step">
<div class="step-dot pending">3</div>
<div class="step-text pending">Akses Portal</div>
</div>
</div>
</div>
<!-- RIGHT -->
<div class="right">
<div class="form-box">
<h3>VERIFIKASI OTP</h3>
<p>Masukkan kode OTP</p>
<div class="otp-icon">
<svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)" stroke-width="1.8"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
</div>
<div class="form-title">Verifikasi OTP</div>
<div class="form-sub">Kode OTP telah dikirim ke email Anda.<br>Segera masukkan sebelum kadaluarsa.</div>
@if(session('error'))
<div class="alert">
{{ session('error') }}
</div>
<div class="sw-alert">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" style="flex-shrink:0"><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>
{{ session('error') }}
</div>
@endif
<form method="POST" action="{{ route('verify.otp') }}">
@csrf
<input type="text" name="otp" placeholder="Masukkan OTP" required>
<div class="input-wrap">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
<input type="text" name="otp" placeholder="_ _ _ _ _ _"
maxlength="6" inputmode="numeric" autocomplete="one-time-code" required>
</div>
<button>Verifikasi</button>
<div class="otp-hint">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
Kode berlaku selama beberapa menit
</div>
<button type="submit" class="btn-verify">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
Verifikasi Sekarang
</button>
<a href="/login" class="back">← Kembali ke Login</a>
</form>
<a href="/login" class="back-link">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Kembali ke Login
</a>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,7 +1,7 @@
<footer class="sticky-footer bg-white">
<!-- <footer class="sticky-footer bg-white">
<div class="container my-auto">
<div class="copyright text-center my-auto">
<span>Copyright &copy; Your Website 2020</span>
</div>
</div>
</footer>
</footer> -->

View File

@ -1,225 +1,456 @@
{{-- ================================================================
FILE: resources/views/layouts/sidebar.blade.php
================================================================ --}}
@php
$role = auth()->user()->role ?? null;
@endphp
{{-- ================= ADMIN (SIDEBAR) ================= --}}
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
/* ── SIDEBAR SHELL ─────────────────────────────────── */
.custom-sidebar {
width: 240px;
min-height: 100vh;
background: #0f1e3d;
display: flex;
flex-direction: column;
font-family: 'Plus Jakarta Sans', sans-serif;
position: fixed;
top: 0; left: 0;
z-index: 1000;
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: thin;
scrollbar-color: rgba(255,255,255,0.08) transparent;
}
.custom-sidebar::-webkit-scrollbar { width: 4px; }
.custom-sidebar::-webkit-scrollbar-track { background: transparent; }
.custom-sidebar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 4px; }
/* ── BRAND ─────────────────────────────────────────── */
.sb-brand {
display: flex;
align-items: center;
gap: 11px;
padding: 20px 20px 18px;
text-decoration: none;
border-bottom: 1px solid rgba(255,255,255,0.06);
margin-bottom: 8px;
}
.sb-brand-icon {
width: 36px;
height: 36px;
border-radius: 9px;
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #fff;
flex-shrink: 0;
box-shadow: 0 4px 12px rgba(37,99,235,0.4);
}
.sb-brand-text {
display: flex;
flex-direction: column;
line-height: 1;
}
.sb-brand-name {
font-size: 15px;
font-weight: 700;
color: #ffffff;
letter-spacing: -0.3px;
}
.sb-brand-sub {
font-size: 10.5px;
font-weight: 500;
color: rgba(255,255,255,0.35);
letter-spacing: 0.05em;
margin-top: 2px;
}
/* ── SECTION HEADING ───────────────────────────────── */
.sb-section {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: rgba(255,255,255,0.28);
padding: 14px 20px 6px;
}
/* ── NAV ITEM ──────────────────────────────────────── */
.sb-nav {
list-style: none;
margin: 0;
padding: 0 10px 16px;
}
.sb-nav li { margin-bottom: 2px; }
.sb-link {
display: flex;
align-items: center;
gap: 11px;
padding: 9px 12px;
border-radius: 8px;
text-decoration: none;
color: rgba(255,255,255,0.55);
font-size: 13.5px;
font-weight: 500;
transition: all 0.15s ease;
position: relative;
}
.sb-link:hover {
background: rgba(255,255,255,0.06);
color: rgba(255,255,255,0.9);
text-decoration: none;
}
.sb-link .sb-icon {
width: 30px;
height: 30px;
border-radius: 7px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
background: rgba(255,255,255,0.05);
color: rgba(255,255,255,0.45);
flex-shrink: 0;
transition: all 0.15s ease;
}
.sb-link:hover .sb-icon {
background: rgba(255,255,255,0.1);
color: rgba(255,255,255,0.85);
}
/* ── ACTIVE STATE ──────────────────────────────────── */
.sb-link.active {
background: linear-gradient(135deg, rgba(37,99,235,0.35) 0%, rgba(29,78,216,0.2) 100%);
color: #ffffff;
font-weight: 600;
}
.sb-link.active::before {
content: '';
position: absolute;
left: 0; top: 50%;
transform: translateY(-50%);
width: 3px; height: 60%;
background: #3b82f6;
border-radius: 0 3px 3px 0;
}
.sb-link.active .sb-icon {
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
color: #ffffff;
box-shadow: 0 3px 8px rgba(37,99,235,0.4);
}
/* ── DIVIDER ───────────────────────────────────────── */
.sb-divider {
height: 1px;
background: rgba(255,255,255,0.06);
margin: 6px 20px 10px;
}
/* ── ROLE BADGE (bottom) ───────────────────────────── */
.sb-footer {
margin-top: auto;
padding: 14px 20px 20px;
border-top: 1px solid rgba(255,255,255,0.06);
}
.sb-role-card {
display: flex;
align-items: center;
gap: 10px;
background: rgba(255,255,255,0.05);
border-radius: 10px;
padding: 10px 12px;
}
.sb-role-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, #2563eb, #6366f1);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
color: #fff;
flex-shrink: 0;
letter-spacing: 0;
}
.sb-role-info { overflow: hidden; }
.sb-role-name {
font-size: 12.5px;
font-weight: 600;
color: rgba(255,255,255,0.85);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 140px;
}
.sb-role-label {
font-size: 10.5px;
color: rgba(255,255,255,0.3);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.06em;
}
</style>
{{-- ================= ADMIN SIDEBAR ================= --}}
@if($role == 'admin')
<ul class="navbar-nav bg-gradient-info sidebar sidebar-dark accordion" id="accordionSidebar">
<aside class="custom-sidebar">
<!-- BRAND -->
<a class="sidebar-brand d-flex align-items-center justify-content-center" href="#">
<div class="sidebar-brand-icon rotate-n-15">
<i class="fas fa-school"></i>
<a class="sb-brand" href="{{ route('admin.dashboard') }}">
<div class="sb-brand-icon"><i class="fa-solid fa-school"></i></div>
<div class="sb-brand-text">
<span class="sb-brand-name">SMAN 1</span>
<span class="sb-brand-sub">Panel Admin</span>
</div>
<div class="sidebar-brand-text mx-3">SMAN 1</div>
</a>
<hr class="sidebar-divider my-0">
<span class="sb-section">Menu Utama</span>
<ul class="sb-nav">
<li>
<a href="{{ route('admin.dashboard') }}"
class="sb-link {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-tachometer-alt"></i></span>
Dashboard
</a>
</li>
</ul>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.dashboard') }}">
<i class="fas fa-fw fa-tachometer-alt"></i>
<span>Dashboard</span>
</a>
</li>
<div class="sb-divider"></div>
<span class="sb-section">Manajemen Data</span>
<hr class="sidebar-divider">
<ul class="sb-nav">
<li>
<a href="{{ route('admin.guru.index') }}"
class="sb-link {{ request()->routeIs('admin.guru.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-chalkboard-teacher"></i></span>
Data Guru
</a>
</li>
<li>
<a href="{{ route('admin.siswa.index') }}"
class="sb-link {{ request()->routeIs('admin.siswa.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-user-graduate"></i></span>
Data Siswa
</a>
</li>
<li>
<a href="{{ route('admin.mapel.index') }}"
class="sb-link {{ request()->routeIs('admin.mapel.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-book"></i></span>
Mata Pelajaran
</a>
</li>
<li>
<a href="{{ route('admin.kelas.index') }}"
class="sb-link {{ request()->routeIs('admin.kelas.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-school"></i></span>
Data Kelas
</a>
</li>
<li>
<a href="{{ route('admin.jadwal.index') }}"
class="sb-link {{ request()->routeIs('admin.jadwal.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-calendar-alt"></i></span>
Jadwal
</a>
</li>
</ul>
<div class="sidebar-heading">Admin Menu</div>
<div class="sb-footer">
<div class="sb-role-card">
<div class="sb-role-avatar">
{{ strtoupper(substr(auth()->user()->name, 0, 2)) }}
</div>
<div class="sb-role-info">
<div class="sb-role-name">{{ auth()->user()->name }}</div>
<div class="sb-role-label">Administrator</div>
</div>
</div>
</div>
<li class="nav-item">
<a href="{{ route('admin.guru.index') }}" class="nav-link">
<i class="fas fa-chalkboard-teacher"></i>
<span>Data Guru</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.siswa.index') }}" class="nav-link">
<i class="fas fa-user-graduate"></i>
<span>Data Siswa</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.mapel.index') }}" class="nav-link">
<i class="fas fa-book"></i>
<span>Mata Pelajaran</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.kelas.index') }}" class="nav-link">
<i class="fas fa-school"></i>
<span>Data Kelas</span>
</a>
</li>
<li class="nav-item">
<a href="{{ route('admin.jadwal.index') }}" class="nav-link">
<i class="fas fa-calendar"></i>
<span>Jadwal</span>
</a>
</li>
</ul>
</aside>
@endif
{{-- ================= GURU (NAVBAR MOBILE) ================= --}}
{{-- ================= GURU SIDEBAR ================= --}}
@if(auth()->check() && auth()->user()->role == 'guru')
<nav class="navbar navbar-expand-lg navbar-dark bg-info shadow-sm fixed-top">
<div class="container-fluid">
<aside class="custom-sidebar">
<!-- Brand -->
<a class="navbar-brand fw-bold" href="{{ route('guru.dashboard') }}">
<i class="fas fa-school"></i> SMAN 1
</a>
<a class="sb-brand" href="{{ route('guru.dashboard') }}">
<div class="sb-brand-icon"><i class="fa-solid fa-school"></i></div>
<div class="sb-brand-text">
<span class="sb-brand-name">SMAN 1</span>
<span class="sb-brand-sub">Portal Guru</span>
</div>
</a>
<!-- Toggle -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarGuru">
<span class="navbar-toggler-icon"></span>
</button>
<span class="sb-section">Menu Utama</span>
<ul class="sb-nav">
<li>
<a href="{{ route('guru.dashboard') }}"
class="sb-link {{ request()->routeIs('guru.dashboard') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-tachometer-alt"></i></span>
Dashboard
</a>
</li>
</ul>
<!-- Menu -->
<div class="collapse navbar-collapse" id="navbarGuru">
<ul class="navbar-nav ms-auto">
<div class="sb-divider"></div>
<span class="sb-section">Menu Guru</span>
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('guru.dashboard') ? 'active' : '' }}"
href="{{ route('guru.dashboard') }}">
<i class="fas fa-home"></i> Dashboard
</a>
</li>
<ul class="sb-nav">
<li>
<a href="{{ route('guru.jadwal') }}"
class="sb-link {{ request()->routeIs('guru.jadwal') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-calendar-alt"></i></span>
Jadwal
</a>
</li>
<li>
<a href="{{ route('guru.absensi') }}"
class="sb-link {{ request()->routeIs('guru.absensi*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-user-check"></i></span>
Absensi
</a>
</li>
<li>
<a href="{{ route('guru.nilai.index') }}"
class="sb-link {{ request()->routeIs('guru.nilai.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-clipboard-list"></i></span>
Nilai
</a>
</li>
<li>
<a href="{{ route('guru.tugas.index') }}"
class="sb-link {{ request()->routeIs('guru.tugas.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-tasks"></i></span>
Tugas
</a>
</li>
<li>
<a href="{{ route('guru.ujian.index') }}"
class="sb-link {{ request()->routeIs('guru.ujian.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-file-alt"></i></span>
Ujian
</a>
</li>
</ul>
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('guru.jadwal') ? 'active' : '' }}"
href="{{ route('guru.jadwal') }}">
<i class="fas fa-calendar"></i> Jadwal
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('guru.absensi*') ? 'active' : '' }}"
href="{{ route('guru.absensi') }}">
<i class="fas fa-user-check"></i> Absensi
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('guru.nilai.*') ? 'active' : '' }}"
href="{{ route('guru.nilai.index') }}">
<i class="fas fa-clipboard-list"></i> Nilai
</a>
</li>
{{-- 🔥 MENU BARU: TUGAS --}}
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('guru.tugas.*') ? 'active' : '' }}"
href="{{ route('guru.tugas.index') }}">
<i class="fas fa-tasks"></i> Tugas
</a>
</li>
<li class="nav-item">
<a href="{{ route('guru.ujian.index') }}" class="nav-link">
<i class="fas fa-file-alt"></i>
<p>Ujian</p>
</a>
</li>
</ul>
<div class="sb-footer">
<div class="sb-role-card">
<div class="sb-role-avatar">
{{ strtoupper(substr(auth()->user()->name, 0, 2)) }}
</div>
<div class="sb-role-info">
<div class="sb-role-name">{{ auth()->user()->name }}</div>
<div class="sb-role-label">Guru</div>
</div>
</div>
</div>
</nav>
{{-- supaya konten tidak ketutup navbar --}}
<style>
body {
padding-top: 70px;
}
</style>
</aside>
@endif
{{-- ================= SISWA ================= --}}
{{-- ================= SISWA SIDEBAR ================= --}}
@if($role == 'siswa')
<nav class="navbar navbar-expand-lg navbar-dark bg-info shadow-sm fixed-top">
<div class="container-fluid">
<aside class="custom-sidebar">
<!-- Brand -->
<a class="navbar-brand fw-bold" href="#">
<i class="fas fa-school"></i> SMAN 1
</a>
<a class="sb-brand" href="{{ route('siswa.dashboard') }}">
<div class="sb-brand-icon"><i class="fa-solid fa-school"></i></div>
<div class="sb-brand-text">
<span class="sb-brand-name">SMAN 1</span>
<span class="sb-brand-sub">Portal Siswa</span>
</div>
</a>
<!-- Toggle -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSiswa">
<span class="navbar-toggler-icon"></span>
</button>
<span class="sb-section">Menu Utama</span>
<ul class="sb-nav">
<li>
<a href="{{ route('siswa.dashboard') }}"
class="sb-link {{ request()->routeIs('siswa.dashboard') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-tachometer-alt"></i></span>
Dashboard
</a>
</li>
</ul>
<!-- Menu -->
<div class="collapse navbar-collapse" id="navbarSiswa">
<ul class="navbar-nav ms-auto">
<div class="sb-divider"></div>
<span class="sb-section">Menu Siswa</span>
<li class="nav-item">
<a class="nav-link {{ request()->is('siswa/dashboard') ? 'active' : '' }}"
href="{{ route('siswa.dashboard') }}">
<i class="fas fa-home"></i> Dashboard
</a>
</li>
<ul class="sb-nav">
<li>
<a href="{{ route('siswa.jadwal') }}"
class="sb-link {{ request()->routeIs('siswa.jadwal') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-calendar-alt"></i></span>
Jadwal
</a>
</li>
<li>
<a href="{{ route('siswa.nilai') }}"
class="sb-link {{ request()->routeIs('siswa.nilai') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-clipboard-list"></i></span>
Nilai
</a>
</li>
<li>
<a href="{{ route('siswa.tugas.index') }}"
class="sb-link {{ request()->routeIs('siswa.tugas.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-tasks"></i></span>
Tugas
</a>
</li>
<li>
<a href="{{ route('siswa.ujian.index') }}"
class="sb-link {{ request()->routeIs('siswa.ujian.*') ? 'active' : '' }}">
<span class="sb-icon"><i class="fa-solid fa-file-alt"></i></span>
Ujian
</a>
</li>
</ul>
<li class="nav-item">
<a class="nav-link {{ request()->is('siswa/jadwal') ? 'active' : '' }}"
href="{{ route('siswa.jadwal') }}">
<i class="fas fa-calendar"></i> Jadwal
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ request()->is('siswa/nilai') ? 'active' : '' }}"
href="{{ route('siswa.nilai') }}">
<i class="fas fa-clipboard-list"></i> Nilai
</a>
</li>
<!-- ✅ TUGAS (SUDAH BENAR) -->
<li class="nav-item">
<a class="nav-link {{ request()->is('siswa/tugas*') ? 'active' : '' }}"
href="{{ route('siswa.tugas.index') }}">
<i class="fas fa-tasks"></i> Tugas
</a>
</li>
<li class="nav-item">
<a href="{{ route('siswa.ujian.index') }}" class="nav-link">
<i class="fas fa-edit"></i> Ujian
</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link {{ request()->routeIs('siswa.absensi*') ? 'active' : '' }}"
href="{{ route('siswa.absensi') }}">
<i class="fas fa-user-check"></i> Absensi
</a>
</li> -->
</ul>
<div class="sb-footer">
<div class="sb-role-card">
<div class="sb-role-avatar">
{{ strtoupper(substr(auth()->user()->name, 0, 2)) }}
</div>
<div class="sb-role-info">
<div class="sb-role-name">{{ auth()->user()->name }}</div>
<div class="sb-role-label">Siswa</div>
</div>
</div>
</div>
</nav>
{{-- supaya konten tidak ketutup navbar --}}
<style>
body {
padding-top: 70px;
}
</style>
</aside>
@endif

View File

@ -1,206 +1,545 @@
<nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
{{-- ================================================================
FILE: resources/views/layouts/topbar.blade.php
================================================================ --}}
<!-- Sidebar Toggle (Topbar) -->
<button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
<i class="fa fa-bars"></i>
</button>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<!-- Topbar Search -->
<form
class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
<div class="input-group">
<input type="text" class="form-control bg-light border-0 small" placeholder="Search for..."
aria-label="Search" aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-info" type="button">
<i class="fas fa-search fa-sm"></i>
</button>
</div>
</div>
</form>
/* ── TOPBAR SHELL ──────────────────────────────────── */
.custom-topbar {
position: fixed;
top: 0;
left: 240px; /* sama dengan lebar sidebar */
right: 0;
height: 60px;
background: #ffffff;
border-bottom: 1px solid rgba(0,0,0,0.07);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
z-index: 900;
font-family: 'Plus Jakarta Sans', sans-serif;
box-shadow: 0 1px 4px rgba(0,0,0,0.05);
}
<!-- Topbar Navbar -->
<ul class="navbar-nav ml-auto">
/* ── LEFT: SEARCH ──────────────────────────────────── */
.tb-search {
display: flex;
align-items: center;
gap: 9px;
background: #f4f6fa;
border: 1.5px solid rgba(0,0,0,0.07);
border-radius: 9px;
padding: 7px 14px;
width: 260px;
transition: border-color 0.15s, box-shadow 0.15s;
}
<!-- Nav Item - Search Dropdown (Visible Only XS) -->
<li class="nav-item dropdown no-arrow d-sm-none">
<a class="nav-link dropdown-toggle" href="#" id="searchDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-search fa-fw"></i>
</a>
<!-- Dropdown - Messages -->
<div class="dropdown-menu dropdown-menu-right p-3 shadow animated--grow-in"
aria-labelledby="searchDropdown">
<form class="form-inline mr-auto w-100 navbar-search">
<div class="input-group">
<input type="text" class="form-control bg-light border-0 small"
placeholder="Search for..." aria-label="Search"
aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-primary" type="button">
<i class="fas fa-search fa-sm"></i>
</button>
</div>
</div>
</form>
</div>
</li>
.tb-search:focus-within {
border-color: rgba(26,86,219,0.4);
box-shadow: 0 0 0 3px rgba(26,86,219,0.08);
background: #fff;
}
<!-- Nav Item - Alerts -->
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-bell fa-fw"></i>
<!-- Counter - Alerts -->
<span class="badge badge-danger badge-counter">3+</span>
</a>
<!-- Dropdown - Alerts -->
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="alertsDropdown">
<h6 class="dropdown-header">
Alerts Center
</h6>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle bg-primary">
<i class="fas fa-file-alt text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">December 12, 2019</div>
<span class="font-weight-bold">A new monthly report is ready to download!</span>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle bg-success">
<i class="fas fa-donate text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">December 7, 2019</div>
$290.29 has been deposited into your account!
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle bg-warning">
<i class="fas fa-exclamation-triangle text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">December 2, 2019</div>
Spending Alert: We've noticed unusually high spending for your account.
</div>
</a>
<a class="dropdown-item text-center small text-gray-500" href="#">Show All Alerts</a>
</div>
</li>
.tb-search i {
font-size: 13px;
color: #9ca3af;
flex-shrink: 0;
}
<!-- Nav Item - Messages -->
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" href="#" id="messagesDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-envelope fa-fw"></i>
<!-- Counter - Messages -->
<span class="badge badge-danger badge-counter">7</span>
</a>
<!-- Dropdown - Messages -->
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="messagesDropdown">
<h6 class="dropdown-header">
Message Center
</h6>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="{{ asset('/') }}sbadmin2/img/undraw_profile_1.svg"
alt="...">
<div class="status-indicator bg-success"></div>
</div>
<div class="font-weight-bold">
<div class="text-truncate">Hi there! I am wondering if you can help me with a
problem I've been having.</div>
<div class="small text-gray-500">Emily Fowler · 58m</div>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="{{ asset('/') }}sbadmin2/img/undraw_profile_2.svg"
alt="...">
<div class="status-indicator"></div>
</div>
<div>
<div class="text-truncate">I have the photos that you ordered last month, how
would you like them sent to you?</div>
<div class="small text-gray-500">Jae Chun · 1d</div>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="{{ asset('/') }}sbadmin2/img/undraw_profile_3.svg"
alt="...">
<div class="status-indicator bg-warning"></div>
</div>
<div>
<div class="text-truncate">Last month's report looks great, I am very happy with
the progress so far, keep up the good work!</div>
<div class="small text-gray-500">Morgan Alvarez · 2d</div>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="{{ asset('/') }}sbadmin2/https://source.unsplash.com/Mv9hjnEUHR4/60x60"
alt="...">
<div class="status-indicator bg-success"></div>
</div>
<div>
<div class="text-truncate">Am I a good boy? The reason I ask is because someone
told me that people say this to all dogs, even if they aren't good...</div>
<div class="small text-gray-500">Chicken the Dog · 2w</div>
</div>
</a>
<a class="dropdown-item text-center small text-gray-500" href="#">Read More Messages</a>
</div>
</li>
.tb-search input {
border: none;
background: transparent;
outline: none;
font-size: 13px;
color: #111827;
font-family: 'Plus Jakarta Sans', sans-serif;
width: 100%;
}
<div class="topbar-divider d-none d-sm-block"></div>
.tb-search input::placeholder { color: #9ca3af; }
<!-- Nav Item - User Information -->
<li class="nav-item dropdown no-arrow">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
/* ── RIGHT: ACTIONS ────────────────────────────────── */
.tb-right {
display: flex;
align-items: center;
gap: 4px;
}
<span class="mr-2 d-none d-lg-inline text-gray-600 small">
{{ auth()->user()->name ?? 'Guest' }}
</span>
/* ── ICON BUTTON ───────────────────────────────────── */
.tb-icon-btn {
position: relative;
width: 38px;
height: 38px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #6b7280;
background: transparent;
border: none;
text-decoration: none;
transition: background 0.13s, color 0.13s;
font-size: 15px;
}
@if(auth()->check() && auth()->user()->photo)
<img class="img-profile rounded-circle"
src="{{ asset('uploads/' . auth()->user()->photo) }}">
@else
<img class="img-profile rounded-circle"
src="{{ asset('sbadmin2/img/undraw_profile.svg') }}">
@endif
.tb-icon-btn:hover {
background: #f4f6fa;
color: #111827;
}
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="userDropdown">
.tb-badge {
position: absolute;
top: 5px; right: 5px;
width: 16px; height: 16px;
border-radius: 50%;
background: #dc2626;
color: #fff;
font-size: 9px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #fff;
line-height: 1;
}
<a class="dropdown-item" href="{{ route('admin.profile') }}">
<i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
Profile
</a>
/* ── DIVIDER ───────────────────────────────────────── */
.tb-sep {
width: 1px;
height: 24px;
background: rgba(0,0,0,0.08);
margin: 0 8px;
}
<div class="dropdown-divider"></div>
/* ── USER BUTTON ───────────────────────────────────── */
.tb-user {
display: flex;
align-items: center;
gap: 9px;
padding: 5px 10px 5px 6px;
border-radius: 10px;
cursor: pointer;
border: 1.5px solid transparent;
background: transparent;
text-decoration: none;
transition: background 0.13s, border-color 0.13s;
position: relative;
}
<a class="dropdown-item" href="#"
onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
Logout
</a>
.tb-user:hover {
background: #f4f6fa;
border-color: rgba(0,0,0,0.07);
text-decoration: none;
}
.tb-user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
border: 2px solid rgba(26,86,219,0.15);
}
.tb-user-avatar-text {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, #1a56db, #6366f1);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
color: #fff;
flex-shrink: 0;
border: 2px solid rgba(26,86,219,0.2);
letter-spacing: 0;
}
.tb-user-info { line-height: 1.2; }
.tb-user-name {
font-size: 13px;
font-weight: 600;
color: #111827;
white-space: nowrap;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
.tb-user-role {
font-size: 11px;
color: #9ca3af;
font-weight: 500;
white-space: nowrap;
}
.tb-user-caret {
font-size: 10px;
color: #9ca3af;
margin-left: 2px;
}
/* ── DROPDOWN SHARED ───────────────────────────────── */
.tb-dropdown {
position: absolute;
top: calc(100% + 8px);
right: 0;
background: #fff;
border: 1px solid rgba(0,0,0,0.08);
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0,0,0,0.10), 0 2px 6px rgba(0,0,0,0.06);
min-width: 280px;
z-index: 1100;
overflow: hidden;
opacity: 0;
transform: translateY(-6px);
pointer-events: none;
transition: opacity 0.15s ease, transform 0.15s ease;
}
.tb-dropdown.open {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.tb-dd-header {
padding: 12px 16px 10px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #9ca3af;
border-bottom: 1px solid rgba(0,0,0,0.06);
}
/* ── NOTIFICATION ITEMS ────────────────────────────── */
.tb-notif-item {
display: flex;
align-items: flex-start;
gap: 11px;
padding: 12px 16px;
text-decoration: none;
border-bottom: 1px solid rgba(0,0,0,0.05);
transition: background 0.12s;
}
.tb-notif-item:last-of-type { border-bottom: none; }
.tb-notif-item:hover { background: #f8fafc; text-decoration: none; }
.tb-notif-icon {
width: 34px;
height: 34px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
flex-shrink: 0;
color: #fff;
}
.tb-notif-icon.blue { background: linear-gradient(135deg, #1a56db, #3b82f6); }
.tb-notif-icon.green { background: linear-gradient(135deg, #0d9488, #10b981); }
.tb-notif-icon.yellow { background: linear-gradient(135deg, #d97706, #f59e0b); }
.tb-notif-body { flex: 1; min-width: 0; }
.tb-notif-text {
font-size: 13px;
font-weight: 500;
color: #111827;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.tb-notif-time {
font-size: 11px;
color: #9ca3af;
margin-top: 3px;
font-weight: 500;
}
.tb-dd-footer {
padding: 10px 16px;
text-align: center;
border-top: 1px solid rgba(0,0,0,0.06);
font-size: 12px;
font-weight: 600;
color: #1a56db;
text-decoration: none;
display: block;
transition: background 0.12s;
}
.tb-dd-footer:hover { background: #f4f6fa; text-decoration: none; }
/* ── USER DROPDOWN ─────────────────────────────────── */
.tb-user-dd { min-width: 200px; }
.tb-user-dd-profile {
padding: 14px 16px;
border-bottom: 1px solid rgba(0,0,0,0.06);
display: flex;
align-items: center;
gap: 11px;
}
.tb-user-dd-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
background: linear-gradient(135deg, #1a56db, #6366f1);
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
color: #fff;
flex-shrink: 0;
}
.tb-user-dd-name { font-size: 13.5px; font-weight: 700; color: #111827; }
.tb-user-dd-role { font-size: 11px; color: #9ca3af; font-weight: 500; }
.tb-user-dd-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
font-size: 13px;
font-weight: 500;
color: #374151;
text-decoration: none;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
transition: background 0.12s;
font-family: 'Plus Jakarta Sans', sans-serif;
}
.tb-user-dd-item:hover { background: #f4f6fa; color: #111827; text-decoration: none; }
.tb-user-dd-item.danger { color: #dc2626; }
.tb-user-dd-item.danger:hover { background: #fff5f5; }
.tb-user-dd-item i { font-size: 13px; width: 16px; text-align: center; color: #9ca3af; }
.tb-user-dd-item.danger i { color: #dc2626; }
.tb-user-dd-divider { height: 1px; background: rgba(0,0,0,0.06); margin: 4px 0; }
/* ── WRAPPER for dropdown trigger ─────────────────── */
.tb-dropdown-wrap { position: relative; }
/* ── MOBILE toggle ─────────────────────────────────── */
.tb-mobile-toggle {
display: none;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
border-radius: 9px;
border: none;
background: #f4f6fa;
color: #374151;
cursor: pointer;
font-size: 16px;
}
@media (max-width: 768px) {
.custom-topbar { left: 0; padding: 0 16px; }
.tb-search { display: none; }
.tb-mobile-toggle { display: flex; }
.tb-user-name, .tb-user-role, .tb-user-caret { display: none; }
}
</style>
<nav class="custom-topbar">
{{-- LEFT --}}
<div style="display:flex; align-items:center; gap:12px;">
{{-- Mobile sidebar toggle --}}
<button class="tb-mobile-toggle" id="sidebarToggleTop">
<i class="fas fa-bars"></i>
</button>
{{-- Search --}}
<div class="tb-search">
<i class="fas fa-search"></i>
<input type="text" placeholder="Cari sesuatu...">
</div>
</div>
{{-- RIGHT --}}
<div class="tb-right">
{{-- NOTIFIKASI --}}
<div class="tb-dropdown-wrap">
<button class="tb-icon-btn" id="btnNotif" onclick="toggleDropdown('ddNotif', this)">
<i class="fas fa-bell"></i>
<span class="tb-badge">3</span>
</button>
<div class="tb-dropdown" id="ddNotif">
<div class="tb-dd-header">Notifikasi</div>
<a href="#" class="tb-notif-item">
<div class="tb-notif-icon blue"><i class="fas fa-file-alt"></i></div>
<div class="tb-notif-body">
<div class="tb-notif-text">Laporan bulanan baru siap diunduh</div>
<div class="tb-notif-time">2 jam yang lalu</div>
</div>
</a>
<a href="#" class="tb-notif-item">
<div class="tb-notif-icon green"><i class="fas fa-user-graduate"></i></div>
<div class="tb-notif-body">
<div class="tb-notif-text">Siswa baru telah terdaftar ke sistem</div>
<div class="tb-notif-time">5 jam yang lalu</div>
</div>
</a>
<a href="#" class="tb-notif-item">
<div class="tb-notif-icon yellow"><i class="fas fa-exclamation-triangle"></i></div>
<div class="tb-notif-body">
<div class="tb-notif-text">Jadwal minggu depan belum dikonfirmasi</div>
<div class="tb-notif-time">1 hari yang lalu</div>
</div>
</a>
<a href="#" class="tb-dd-footer">Lihat Semua Notifikasi &rarr;</a>
</div>
</div>
{{-- PESAN --}}
<div class="tb-dropdown-wrap">
<button class="tb-icon-btn" id="btnMsg" onclick="toggleDropdown('ddMsg', this)">
<i class="fas fa-envelope"></i>
<span class="tb-badge">7</span>
</button>
<div class="tb-dropdown" id="ddMsg">
<div class="tb-dd-header">Pesan</div>
<a href="#" class="tb-notif-item">
<div class="tb-notif-icon blue"><i class="fas fa-user"></i></div>
<div class="tb-notif-body">
<div class="tb-notif-text">Ada pertanyaan mengenai jadwal pelajaran minggu ini</div>
<div class="tb-notif-time">Emily Fowler · 58 menit lalu</div>
</div>
</a>
<a href="#" class="tb-notif-item">
<div class="tb-notif-icon green"><i class="fas fa-user"></i></div>
<div class="tb-notif-body">
<div class="tb-notif-text">Foto tugas bulan lalu sudah siap, mau dikirim bagaimana?</div>
<div class="tb-notif-time">Jae Chun · 1 hari lalu</div>
</div>
</a>
<a href="#" class="tb-notif-item">
<div class="tb-notif-icon yellow"><i class="fas fa-user"></i></div>
<div class="tb-notif-body">
<div class="tb-notif-text">Laporan bulan ini terlihat bagus, perkembangannya sangat baik!</div>
<div class="tb-notif-time">Morgan Alvarez · 2 hari lalu</div>
</div>
</a>
<a href="#" class="tb-dd-footer">Baca Semua Pesan &rarr;</a>
</div>
</div>
<div class="tb-sep"></div>
{{-- USER MENU --}}
<div class="tb-dropdown-wrap">
<a href="#" class="tb-user" id="btnUser" onclick="toggleDropdown('ddUser', this); return false;">
@if(auth()->check() && auth()->user()->photo)
<img class="tb-user-avatar"
src="{{ asset('uploads/' . auth()->user()->photo) }}"
alt="{{ auth()->user()->name }}">
@else
<div class="tb-user-avatar-text">
{{ strtoupper(substr(auth()->user()->name ?? 'U', 0, 2)) }}
</div>
@endif
<div class="tb-user-info">
<span class="tb-user-name">{{ auth()->user()->name ?? 'Guest' }}</span>
<span class="tb-user-role">{{ ucfirst(auth()->user()->role ?? '') }}</span>
</div>
<i class="fas fa-chevron-down tb-user-caret"></i>
</a>
<div class="tb-dropdown tb-user-dd" id="ddUser">
{{-- Profile summary --}}
<div class="tb-user-dd-profile">
<div class="tb-user-dd-avatar">
{{ strtoupper(substr(auth()->user()->name ?? 'U', 0, 2)) }}
</div>
<div>
<div class="tb-user-dd-name">{{ auth()->user()->name ?? 'Guest' }}</div>
<div class="tb-user-dd-role">{{ ucfirst(auth()->user()->role ?? '') }}</div>
</div>
</div>
{{-- Menu items --}}
<a href="{{ route('admin.profile') }}" class="tb-user-dd-item">
<i class="fas fa-user"></i> Profil Saya
</a>
<div class="tb-user-dd-divider"></div>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display:none;">
@csrf
</form>
<button class="tb-user-dd-item danger"
onclick="document.getElementById('logout-form').submit()">
<i class="fas fa-sign-out-alt"></i> Keluar
</button>
</div>
</div>
</div>
</li>
</ul>
</nav>
</nav>
<script>
function toggleDropdown(id, trigger) {
const dd = document.getElementById(id);
const allDDs = document.querySelectorAll('.tb-dropdown');
allDDs.forEach(el => {
if (el.id !== id) el.classList.remove('open');
});
dd.classList.toggle('open');
// close on outside click
const handler = (e) => {
if (!trigger.contains(e.target) && !dd.contains(e.target)) {
dd.classList.remove('open');
document.removeEventListener('click', handler);
}
};
if (dd.classList.contains('open')) {
setTimeout(() => document.addEventListener('click', handler), 0);
}
}
</script>

View File

@ -1,163 +1,575 @@
@extends('layouts.app')
@section('content')
<div class="container mt-4">
<h4 class="fw-bold mb-4">📊 Manajemen Absensi Guru</h4>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<div class="card shadow p-4 mb-4">
:root {
--primary: #2563eb;
--primary-light: #eff6ff;
--primary-border: #bfdbfe;
--primary-dark: #1d4ed8;
--success: #16a34a;
--success-light: #f0fdf4;
--success-border: #bbf7d0;
--warning: #d97706;
--warning-light: #fffbeb;
--warning-border: #fde68a;
--danger: #dc2626;
--danger-light: #fef2f2;
--danger-border: #fecaca;
--info: #0284c7;
--info-light: #f0f9ff;
--info-border: #bae6fd;
--text-primary: #0f172a;
--text-secondary: #475569;
--text-muted: #94a3b8;
--surface: #ffffff;
--surface-secondary: #f8fafc;
--border: #e2e8f0;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
--shadow-md: 0 4px 20px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--shadow-focus: 0 0 0 3px rgba(37,99,235,0.12);
--radius-sm: 6px; --radius-md: 10px; --radius-lg: 14px; --radius-xl: 18px;
}
<form method="GET" action="{{ route('guru.absensi') }}">
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
<div class="row">
.page-wrapper { animation: fadeUp 0.4s ease both; }
@keyframes fadeUp { from { opacity:0; transform:translateY(12px); } to { opacity:1; transform:translateY(0); } }
<div class="col-md-6 mb-3">
<label class="fw-bold">Kelas</label>
<select name="kelas_id" class="form-control" required>
<option value="">-- Pilih Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}"
{{ request('kelas_id') == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
/* HEADER */
.page-header { display:flex; align-items:center; gap:14px; margin-bottom:28px; }
.page-icon { width:50px; height:50px; border-radius:var(--radius-lg); background:var(--primary-light); border:1px solid var(--primary-border); display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.page-icon svg { width:24px; height:24px; color:var(--primary); }
.page-title { font-size:22px; font-weight:700; color:var(--text-primary); margin:0 0 2px; letter-spacing:-0.3px; }
.page-subtitle { font-size:13px; color:var(--text-secondary); margin:0; }
/* CARD */
.section-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-xl); box-shadow:var(--shadow-md); overflow:hidden; margin-bottom:22px; }
.card-head { padding:16px 22px; border-bottom:1px solid var(--border); background:var(--surface-secondary); display:flex; align-items:center; gap:9px; }
.card-head-dot { width:8px; height:8px; border-radius:50%; }
.dot-blue { background:var(--primary); }
.dot-green { background:var(--success); }
.dot-orange { background:var(--warning); }
.card-head-label { font-size:12.5px; font-weight:700; color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.7px; }
.card-head-label span { font-weight:600; color:var(--primary); text-transform:none; font-size:13px; }
.card-body-p { padding:24px 22px; }
/* FORM FILTER */
.filter-grid { display:grid; grid-template-columns:1fr 1fr 1fr; gap:16px; margin-bottom:20px; }
.form-label { display:block; font-size:13px; font-weight:600; color:var(--text-primary); margin-bottom:6px; }
.form-control, .form-select {
width:100%; padding:10px 14px; font-size:13.5px;
font-family:'Plus Jakarta Sans',sans-serif; color:var(--text-primary);
background:var(--surface); border:1px solid var(--border);
border-radius:var(--radius-md); outline:none;
transition:border-color 0.2s, box-shadow 0.2s; appearance:none;
}
.form-control:focus, .form-select:focus { border-color:var(--primary); box-shadow:var(--shadow-focus); }
.form-control[readonly] { background:var(--surface-secondary); color:var(--text-secondary); }
.form-select {
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");
background-repeat:no-repeat; background-position:right 12px center; background-size:16px; padding-right:38px; cursor:pointer;
}
.btn-row { display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-top:8px; }
.btn-outline-primary {
display:inline-flex; align-items:center; justify-content:center; gap:8px;
padding:10px 20px; font-size:13.5px; font-weight:600;
font-family:'Plus Jakarta Sans',sans-serif;
background:var(--surface); color:var(--primary);
border:1.5px solid var(--primary); border-radius:var(--radius-md);
cursor:pointer; transition:all 0.15s; text-decoration:none;
}
.btn-outline-primary:hover { background:var(--primary-light); }
.btn-outline-primary svg { width:15px; height:15px; }
.btn-primary {
display:inline-flex; align-items:center; justify-content:center; gap:8px;
padding:10px 20px; font-size:13.5px; font-weight:600;
font-family:'Plus Jakarta Sans',sans-serif;
background:var(--primary); color:#fff;
border:none; border-radius:var(--radius-md);
cursor:pointer; transition:all 0.15s; box-shadow:0 2px 8px rgba(37,99,235,0.25);
}
.btn-primary:hover { background:var(--primary-dark); }
.btn-primary svg { width:15px; height:15px; }
/* TABLE */
.table-responsive { overflow-x:auto; }
table.styled-table { width:100%; border-collapse:collapse; min-width:500px; }
.styled-table thead tr { background:var(--surface-secondary); border-bottom:1px solid var(--border); }
.styled-table thead th { padding:12px 16px; font-size:11px; font-weight:700; color:var(--text-muted); text-transform:uppercase; letter-spacing:0.7px; text-align:left; white-space:nowrap; }
.styled-table tbody tr { border-bottom:1px solid var(--border); transition:background 0.15s; }
.styled-table tbody tr:last-child { border-bottom:none; }
.styled-table tbody tr:hover { background:#f8faff; }
.styled-table tbody td { padding:14px 16px; font-size:13.5px; color:var(--text-primary); vertical-align:middle; }
/* NAME CELL */
.name-cell { display:flex; align-items:center; gap:10px; }
.name-avatar { width:34px; height:34px; border-radius:50%; background:var(--primary-light); border:1px solid var(--primary-border); display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; color:var(--primary); flex-shrink:0; }
/* BADGES */
.badge { display:inline-flex; align-items:center; gap:5px; border-radius:999px; font-size:12px; font-weight:600; padding:4px 12px; }
.badge::before { content:''; width:6px; height:6px; border-radius:50%; flex-shrink:0; }
.badge-hadir { background:var(--success-light); color:var(--success); border:1px solid var(--success-border); }
.badge-hadir::before { background:var(--success); }
.badge-izin { background:var(--warning-light); color:var(--warning); border:1px solid var(--warning-border); }
.badge-izin::before { background:var(--warning); }
.badge-sakit { background:var(--info-light); color:var(--info); border:1px solid var(--info-border); }
.badge-sakit::before { background:var(--info); }
.badge-alpha { background:var(--danger-light); color:var(--danger); border:1px solid var(--danger-border); }
.badge-alpha::before { background:var(--danger); }
.ket-text { font-size:13px; color:var(--text-secondary); }
/* ACTION BUTTONS */
.btn-edit-sm {
display:inline-flex; align-items:center; gap:5px;
padding:5px 12px; font-size:12.5px; font-weight:600;
font-family:'Plus Jakarta Sans',sans-serif;
background:var(--warning-light); color:var(--warning);
border:1px solid var(--warning-border); border-radius:var(--radius-sm);
cursor:pointer; transition:all 0.15s;
}
.btn-edit-sm:hover { background:#fef3c7; }
.btn-edit-sm svg { width:13px; height:13px; }
/* SEMUA HADIR BUTTON */
.btn-all-hadir {
display:inline-flex; align-items:center; gap:6px;
padding:7px 15px; font-size:12.5px; font-weight:600;
font-family:'Plus Jakarta Sans',sans-serif;
background:var(--success-light); color:var(--success);
border:1px solid var(--success-border); border-radius:var(--radius-md);
cursor:pointer; transition:all 0.15s;
}
.btn-all-hadir:hover { background:#dcfce7; }
.btn-all-hadir svg { width:14px; height:14px; }
.btn-save-absen {
display:flex; align-items:center; justify-content:center; gap:8px;
width:100%; padding:12px; font-size:14px; font-weight:700;
font-family:'Plus Jakarta Sans',sans-serif;
background:var(--success); color:#fff;
border:none; border-radius:var(--radius-md);
cursor:pointer; transition:all 0.15s;
margin-top:20px; box-shadow:0 2px 8px rgba(22,163,74,0.3);
}
.btn-save-absen:hover { background:#15803d; }
.btn-save-absen svg { width:16px; height:16px; }
/* INPUT TABLE */
.styled-table select.status {
padding:7px 10px; font-size:13px; border:1px solid var(--border);
border-radius:var(--radius-sm); background:var(--surface);
font-family:'Plus Jakarta Sans',sans-serif; outline:none;
appearance:none;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");
background-repeat:no-repeat; background-position:right 8px center; background-size:14px;
padding-right:30px; width:100%;
}
.styled-table input.form-control { padding:7px 10px; font-size:13px; }
/* TOOLBAR */
.input-toolbar { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; }
.input-toolbar-label { font-size:13px; color:var(--text-muted); }
/* ALERT */
.alert-warn {
background:var(--warning-light); border:1px solid var(--warning-border);
border-radius:var(--radius-md); padding:14px 18px; text-align:center;
font-size:13.5px; color:var(--warning); font-weight:500;
}
/* EMPTY STATE */
.empty-state { padding:60px 24px; text-align:center; }
.empty-icon { width:60px; height:60px; background:var(--surface-secondary); border-radius:50%; border:1px solid var(--border); display:flex; align-items:center; justify-content:center; margin:0 auto 14px; }
.empty-icon svg { width:26px; height:26px; color:var(--text-muted); }
.empty-title { font-size:15px; font-weight:600; color:var(--text-primary); margin-bottom:4px; }
.empty-desc { font-size:13px; color:var(--text-muted); }
/* ── CUSTOM MODAL (tanpa Bootstrap JS dependency) ── */
.sw-modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.5);
z-index: 9999;
align-items: center;
justify-content: center;
padding: 20px;
backdrop-filter: blur(2px);
}
.sw-modal-overlay.active {
display: flex;
animation: overlayIn 0.2s ease;
}
@keyframes overlayIn { from { opacity:0; } to { opacity:1; } }
.sw-modal-box {
background: var(--surface);
border-radius: var(--radius-xl);
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
width: 100%;
max-width: 480px;
overflow: hidden;
animation: modalIn 0.25s ease;
}
@keyframes modalIn { from { opacity:0; transform:translateY(-16px) scale(0.97); } to { opacity:1; transform:translateY(0) scale(1); } }
.sw-modal-head {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 22px;
border-bottom: 1px solid var(--border);
background: var(--surface-secondary);
}
.sw-modal-title {
font-size: 15px; font-weight: 700; color: var(--text-primary); margin: 0;
display: flex; align-items: center; gap: 8px;
}
.sw-modal-title svg { width:18px; height:18px; color:var(--warning); }
.sw-modal-close {
width: 30px; height: 30px; border-radius: 50%;
background: var(--border); border: none; cursor: pointer;
display: flex; align-items: center; justify-content: center;
color: var(--text-secondary); transition: all 0.15s;
}
.sw-modal-close:hover { background: var(--danger-light); color: var(--danger); }
.sw-modal-body { padding: 22px; }
.sw-modal-field { margin-bottom: 16px; }
.sw-modal-field:last-child { margin-bottom: 0; }
.sw-modal-label {
display: block; font-size: 13px; font-weight: 600;
color: var(--text-primary); margin-bottom: 6px;
}
.sw-modal-foot {
display: flex; align-items: center; justify-content: flex-end; gap: 10px;
padding: 16px 22px;
border-top: 1px solid var(--border);
background: var(--surface-secondary);
}
.btn-modal-cancel {
display: inline-flex; align-items: center; gap: 6px;
padding: 9px 18px; font-size: 13.5px; font-weight: 600;
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--surface); color: var(--text-secondary);
border: 1px solid var(--border); border-radius: var(--radius-md);
cursor: pointer; transition: all 0.15s;
}
.btn-modal-cancel:hover { background: var(--surface-secondary); }
.btn-modal-save {
display: inline-flex; align-items: center; gap: 6px;
padding: 9px 22px; font-size: 13.5px; font-weight: 600;
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--primary); color: #fff;
border: none; border-radius: var(--radius-md);
cursor: pointer; transition: all 0.15s;
box-shadow: 0 2px 8px rgba(37,99,235,0.25);
}
.btn-modal-save:hover { background: var(--primary-dark); }
@media(max-width:768px) {
.filter-grid { grid-template-columns:1fr; }
.btn-row { grid-template-columns:1fr; }
}
</style>
<div class="container mt-4 page-wrapper">
{{-- HEADER --}}
<div class="page-header">
<div class="page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
</svg>
</div>
<div>
<h4 class="page-title">Manajemen Absensi</h4>
<p class="page-subtitle">Catat dan pantau kehadiran siswa per kelas dan mata pelajaran</p>
</div>
</div>
<div class="col-md-6 mb-3">
<label class="fw-bold">Mapel</label>
{{-- FILTER CARD --}}
<div class="section-card">
<div class="card-head">
<div class="card-head-dot dot-blue"></div>
<span class="card-head-label">Filter Absensi</span>
</div>
<div class="card-body-p">
<form method="GET" action="{{ route('guru.absensi') }}">
<div class="filter-grid">
<div>
<label class="form-label">Kelas</label>
<select name="kelas_id" class="form-select" required>
<option value="">Pilih Kelas</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}" {{ request('kelas_id') == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">Mata Pelajaran</label>
<input type="hidden" name="mapel_id" value="{{ $mapel->id }}">
<input type="text" class="form-control" value="{{ $mapel->nama_mapel }}" readonly>
</div>
<div>
<label class="form-label">Tanggal</label>
<input type="date" name="tanggal" class="form-control"
value="{{ request('tanggal') ?? date('Y-m-d') }}">
</div>
</div>
<div class="btn-row">
<button type="submit" name="aksi" value="lihat" class="btn-outline-primary">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
Lihat Riwayat
</button>
<button type="submit" name="aksi" value="input" class="btn-primary">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
Input Absensi
</button>
</div>
</form>
</div>
</div>
<input type="hidden" name="mapel_id" value="{{ $mapel->id }}">
{{-- ===== RIWAYAT ===== --}}
@if(request('aksi') == 'lihat' && isset($riwayat))
<div class="section-card">
<div class="card-head">
<div class="card-head-dot dot-orange"></div>
<span class="card-head-label">Riwayat Absensi &mdash; <span>{{ request('tanggal') }}</span></span>
</div>
<div class="card-body-p" style="padding:0;">
@if($riwayat && $riwayat->count())
<div class="table-responsive">
<table class="styled-table">
<thead>
<tr>
<th>Nama Siswa</th>
<th>Status</th>
<th>Keterangan</th>
<th style="text-align:center;">Aksi</th>
</tr>
</thead>
<tbody>
@foreach($riwayat as $r)
<tr>
<td>
<div class="name-cell">
<div class="name-avatar">{{ strtoupper(substr($r->siswa->nama,0,2)) }}</div>
<span style="font-weight:600;">{{ $r->siswa->nama }}</span>
</div>
</td>
<td>
<span class="badge
@if($r->status=='hadir') badge-hadir
@elseif($r->status=='izin') badge-izin
@elseif($r->status=='sakit') badge-sakit
@else badge-alpha
@endif">
{{ ucfirst($r->status) }}
</span>
</td>
<td><span class="ket-text">{{ $r->keterangan ?? '—' }}</span></td>
<td style="text-align:center;">
{{-- Tombol edit → buka custom modal --}}
<button class="btn-edit-sm"
onclick="openEditModal(
{{ $r->id }},
'{{ addslashes($r->siswa->nama) }}',
'{{ $r->status }}',
'{{ addslashes($r->keterangan ?? '') }}'
)">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 012.828 2.828L11.828 15.828a2 2 0 01-1.414.586H8v-2.414a2 2 0 01.586-1.414z"/>
</svg>
Edit
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<div class="card-body-p">
<div class="alert-warn">Tidak ada data absensi pada tanggal ini.</div>
</div>
@endif
</div>
</div>
@endif
<input type="text" class="form-control"
value="{{ $mapel->nama_mapel }}" readonly>
</div>
{{-- ===== FORM INPUT ===== --}}
@if(request('aksi') == 'input' && isset($siswa))
<div class="section-card">
<div class="card-head">
<div class="card-head-dot dot-green"></div>
<span class="card-head-label">Input Absensi &mdash; <span>{{ request('tanggal') }}</span></span>
</div>
<div class="card-body-p">
<form method="POST" action="{{ route('guru.absensi.simpan') }}">
@csrf
<input type="hidden" name="kelas_id" value="{{ $kelasAktif->id }}">
<input type="hidden" name="mapel_id" value="{{ $mapel->id }}">
<input type="hidden" name="tanggal" value="{{ request('tanggal') }}">
<div class="input-toolbar">
<span class="input-toolbar-label">{{ $siswa->count() }} siswa ditemukan</span>
<button type="button" class="btn-all-hadir" onclick="setAll('hadir')">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
</svg>
Semua Hadir
</button>
</div>
<div class="table-responsive">
<table class="styled-table">
<thead>
<tr>
<th>Nama Siswa</th>
<th style="width:180px;">Status</th>
<th>Keterangan</th>
</tr>
</thead>
<tbody>
@foreach($siswa as $s)
<tr>
<td>
<div class="name-cell">
<div class="name-avatar">{{ strtoupper(substr($s->nama,0,2)) }}</div>
<span style="font-weight:600;">{{ $s->nama }}</span>
</div>
</td>
<td>
<select name="absensi[{{ $s->id }}][status]" class="status">
<option value="hadir">Hadir</option>
<option value="izin">Izin</option>
<option value="sakit">Sakit</option>
<option value="alpha">Alpha</option>
</select>
</td>
<td>
<input type="text" name="absensi[{{ $s->id }}][keterangan]" class="form-control" placeholder="Opsional...">
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<button type="submit" class="btn-save-absen">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
</svg>
Simpan Absensi
</button>
</form>
</div>
</div>
@endif
</div>
<button class="btn btn-primary w-100">
▶ Mulai Absensi
</button>
</form>
</div>
{{-- 🔥 TAMPIL FORM ABSENSI --}}
@if(isset($siswa))
<div class="card shadow p-4 mb-4">
<h5 class="fw-bold mb-3">
📋 Absensi Kelas {{ $kelasAktif->nama_kelas }}
</h5>
<form method="POST" action="{{ route('guru.absensi.simpan') }}">
@csrf
<input type="hidden" name="kelas_id" value="{{ $kelasAktif->id }}">
<input type="hidden" name="mapel_id" value="{{ $mapel->id }}">
<div class="mb-3">
<label class="fw-bold">Mapel</label>
<input type="text" class="form-control"
value="{{ $mapel->nama_mapel }}" readonly>
</div>
<div class="mb-3">
<button type="button" class="btn btn-success btn-sm" onclick="setAll('hadir')">
✔ Semua Hadir
</button>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>Nama</th>
<th>Status</th>
<th>Keterangan</th>
</tr>
</thead>
<tbody>
@foreach($siswa as $s)
<tr>
<td>{{ $s->nama }}</td>
<td>
<select name="absensi[{{ $s->id }}][status]" class="form-control status">
<option value="hadir">Hadir</option>
<option value="izin">Izin</option>
<option value="sakit">Sakit</option>
<option value="alpha">Alpha</option>
</select>
</td>
<td>
<input type="text"
name="absensi[{{ $s->id }}][keterangan]"
class="form-control"
placeholder="Opsional...">
</td>
</tr>
@endforeach
</tbody>
</table>
<button class="btn btn-primary w-100 mt-3">
💾 Simpan Absensi
</button>
</form>
</div>
@endif
{{-- 🔥 DATA ABSENSI --}}
@if(isset($riwayat))
<div class="card shadow p-4">
<h5 class="fw-bold mb-3">📊 Riwayat Absensi</h5>
<table class="table table-bordered">
<thead>
<tr>
<th>Nama</th>
<th>Status</th>
<th>Keterangan</th>
</tr>
</thead>
<tbody>
@forelse($riwayat as $r)
<tr>
<td>{{ $r->siswa->nama }}</td>
<td>{{ ucfirst($r->status) }}</td>
<td>{{ $r->keterangan }}</td>
</tr>
@empty
<tr>
<td colspan="3" class="text-center text-muted">Belum ada data</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@endif
{{-- ══ CUSTOM MODAL EDIT (satu modal, diisi via JS) ══ --}}
<div class="sw-modal-overlay" id="editModalOverlay" onclick="closeEditModal(event)">
<div class="sw-modal-box" onclick="event.stopPropagation()">
<div class="sw-modal-head">
<h5 class="sw-modal-title">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 012.828 2.828L11.828 15.828a2 2 0 01-1.414.586H8v-2.414a2 2 0 01.586-1.414z"/>
</svg>
Edit Absensi Siswa
</h5>
<button class="sw-modal-close" onclick="closeEditModal()">
<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="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<form id="editAbsensiForm" method="POST" action="">
@csrf
@method('PUT')
<div class="sw-modal-body">
<div class="sw-modal-field">
<label class="sw-modal-label">Nama Siswa</label>
<input type="text" id="modalNama" class="form-control" readonly>
</div>
<div class="sw-modal-field">
<label class="sw-modal-label">Status Kehadiran</label>
<select id="modalStatus" name="status" class="form-select">
<option value="hadir">Hadir</option>
<option value="izin">Izin</option>
<option value="sakit">Sakit</option>
<option value="alpha">Alpha</option>
</select>
</div>
<div class="sw-modal-field">
<label class="sw-modal-label">Keterangan <span style="font-weight:400;color:var(--text-muted);">(opsional)</span></label>
<input type="text" id="modalKeterangan" name="keterangan" class="form-control" placeholder="Tulis keterangan...">
</div>
</div>
<div class="sw-modal-foot">
<button type="button" class="btn-modal-cancel" onclick="closeEditModal()">Batal</button>
<button type="submit" class="btn-modal-save">
<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="M5 13l4 4L19 7"/>
</svg>
Simpan Perubahan
</button>
</div>
</form>
</div>
</div>
<script>
function setAll(status) {
document.querySelectorAll('.status').forEach(el => {
el.value = status;
});
document.querySelectorAll('.status').forEach(el => { el.value = status; });
}
function openEditModal(id, nama, status, keterangan) {
// Set action URL dinamis
document.getElementById('editAbsensiForm').action = '/guru/absensi/update/' + id;
// Isi field
document.getElementById('modalNama').value = nama;
document.getElementById('modalStatus').value = status;
document.getElementById('modalKeterangan').value = keterangan;
// Tampilkan overlay
document.getElementById('editModalOverlay').classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeEditModal(event) {
// Kalau klik overlay (bukan modal box), tutup
if (event && event.target !== document.getElementById('editModalOverlay')) return;
document.getElementById('editModalOverlay').classList.remove('active');
document.body.style.overflow = '';
}
// Tutup dengan tombol ESC
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.getElementById('editModalOverlay').classList.remove('active');
document.body.style.overflow = '';
}
});
</script>
@endsection

View File

@ -1,25 +1,435 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Dashboard Admin</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
<div class="card shadow">
<div class="card-body text-center">
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--primary-light: #EEF2FF;
--success: #059669;
--success-light: #ECFDF5;
--warning: #D97706;
--warning-light: #FFFBEB;
--danger: #DC2626;
--danger-light: #FEF2F2;
--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;
}
<h4>Selamat Datang 👑</h4>
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
@auth
{{ auth()->user()->name }}
@endauth
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
<p class="text-muted">
Anda login sebagai <b>Admin</b>
</p>
/* ── HERO BANNER ── */
.sw-hero {
background: linear-gradient(135deg, #1E3A6E 0%, #3730A3 50%, #4F46E5 100%);
border-radius: var(--radius-lg);
padding: 32px 36px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
margin-bottom: 28px;
position: relative;
overflow: hidden;
}
.sw-hero::before {
content: '';
position: absolute; right: -60px; top: -60px;
width: 280px; height: 280px;
border-radius: 50%;
border: 1px solid rgba(255,255,255,.08);
}
.sw-hero::after {
content: '';
position: absolute; right: 80px; bottom: -80px;
width: 200px; height: 200px;
border-radius: 50%;
border: 1px solid rgba(255,255,255,.06);
}
.sw-hero-left { position: relative; z-index: 1; }
.sw-hero-greeting {
font-size: 13px; font-weight: 600;
color: rgba(255,255,255,.6);
text-transform: uppercase; letter-spacing: 1px;
margin-bottom: 8px;
}
.sw-hero-name {
font-size: 26px; font-weight: 800;
color: #fff; line-height: 1.2;
margin-bottom: 10px; letter-spacing: -.4px;
}
.sw-hero-meta {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.sw-hero-badge {
display: inline-flex; align-items: center; gap: 6px;
background: rgba(255,255,255,.12);
border: 1px solid rgba(255,255,255,.2);
border-radius: 99px; padding: 5px 14px;
font-size: 12px; font-weight: 600; color: rgba(255,255,255,.9);
}
.sw-hero-badge svg { opacity: .8; }
.sw-hero-right {
position: relative; z-index: 1;
text-align: right; flex-shrink: 0;
}
.sw-hero-date {
font-size: 12px; color: rgba(255,255,255,.55);
font-weight: 500; margin-bottom: 4px;
}
.sw-hero-time {
font-size: 28px; font-weight: 800;
color: #fff; letter-spacing: -1px;
font-variant-numeric: tabular-nums;
}
/* ── STATS GRID ── */
.sw-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px; }
.sw-stat-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.04);
padding: 22px 20px;
display: flex; align-items: center; gap: 16px;
position: relative; overflow: hidden;
transition: transform .2s, box-shadow .2s;
}
.sw-stat-card:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,.07); }
.sw-stat-card::after {
content: '';
position: absolute; top: 0; left: 0; right: 0;
height: 3px; border-radius: 99px 99px 0 0;
}
.sw-stat-card.primary::after { background: var(--primary); }
.sw-stat-card.success::after { background: var(--success); }
.sw-stat-card.warning::after { background: var(--warning); }
.sw-stat-card.danger::after { background: var(--danger); }
.sw-stat-icon {
width: 48px; height: 48px; border-radius: 12px;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.sw-stat-icon.primary { background: var(--primary-light); color: var(--primary); }
.sw-stat-icon.success { background: var(--success-light); color: var(--success); }
.sw-stat-icon.warning { background: var(--warning-light); color: var(--warning); }
.sw-stat-icon.danger { background: var(--danger-light); color: var(--danger); }
.sw-stat-val {
font-size: 28px; font-weight: 800;
color: var(--text-dark); line-height: 1;
margin-bottom: 4px; font-variant-numeric: tabular-nums;
}
.sw-stat-lbl { font-size: 12.5px; font-weight: 600; color: var(--text-soft); }
/* ── TWO-COL GRID ── */
.sw-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }
.sw-row-3 { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 20px; }
/* ── CARD BASE ── */
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.04);
overflow: hidden;
}
.sw-card-hd {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 20px;
border-bottom: 1px solid var(--border);
}
.sw-card-hd-left { display: flex; align-items: center; gap: 10px; }
.sw-card-hd-icon {
width: 34px; height: 34px; border-radius: 9px;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.sw-card-hd-icon.primary { background: var(--primary-light); color: var(--primary); }
.sw-card-hd-icon.success { background: var(--success-light); color: var(--success); }
.sw-card-hd-icon.warning { background: var(--warning-light); color: var(--warning); }
.sw-card-hd-icon.danger { background: var(--danger-light); color: var(--danger); }
.sw-card-hd h6 { font-size: 14px; font-weight: 700; color: var(--text-dark); margin: 0; }
.sw-card-hd p { font-size: 11.5px; color: var(--text-soft); margin: 2px 0 0; }
.sw-card-bd { padding: 20px; }
/* ── QUICK ACTIONS ── */
.sw-actions { display: flex; flex-direction: column; gap: 10px; padding: 20px; }
.sw-action-btn {
display: flex; align-items: center; gap: 12px;
padding: 14px 16px;
border-radius: var(--radius-md);
text-decoration: none;
transition: all .18s;
border: 1px solid transparent;
}
.sw-action-btn.success { background: var(--success-light); border-color: #A7F3D0; color: var(--success); }
.sw-action-btn.success:hover { background: var(--success); color: #fff; border-color: var(--success); }
.sw-action-btn.warning { background: var(--warning-light); border-color: #FDE68A; color: var(--warning); }
.sw-action-btn.warning:hover { background: var(--warning); color: #fff; border-color: var(--warning); }
.sw-action-btn.danger { background: var(--danger-light); border-color: #FECACA; color: var(--danger); }
.sw-action-btn.danger:hover { background: var(--danger); color: #fff; border-color: var(--danger); }
.sw-action-icon {
width: 38px; height: 38px; border-radius: 9px;
background: rgba(255,255,255,.6);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; transition: background .18s;
}
.sw-action-btn:hover .sw-action-icon { background: rgba(255,255,255,.2); }
.sw-action-text strong { display: block; font-size: 13.5px; font-weight: 700; }
.sw-action-text span { font-size: 11.5px; opacity: .75; }
.sw-action-btn svg.arrow { margin-left: auto; opacity: .5; }
/* ── INFO LIST ── */
.sw-info-list { display: flex; flex-direction: column; gap: 0; }
.sw-info-row {
display: flex; align-items: center; justify-content: space-between;
padding: 13px 0;
border-bottom: 1px solid var(--border);
font-size: 13.5px;
}
.sw-info-row:last-child { border-bottom: none; }
.sw-info-row-label { color: var(--text-soft); font-weight: 500; display: flex; align-items: center; gap: 8px; }
.sw-info-row-val { font-weight: 700; color: var(--text-dark); }
/* ── BADGE ── */
.sw-badge {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 10px; border-radius: 99px;
font-size: 11.5px; font-weight: 600;
}
.sw-badge-primary { background: var(--primary-light); color: var(--primary); }
.sw-badge-success { background: var(--success-light); color: var(--success); }
/* ── MAPEL HIGHLIGHT ── */
.sw-mapel-box {
background: linear-gradient(135deg, var(--primary-light), #DDD6FE);
border: 1px solid #C7D2FE;
border-radius: var(--radius-md);
padding: 18px 20px;
display: flex; align-items: center; gap: 14px;
}
.sw-mapel-icon {
width: 48px; height: 48px; border-radius: 12px;
background: var(--primary); color: #fff;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; box-shadow: 0 4px 12px rgba(79,70,229,.3);
}
.sw-mapel-label { font-size: 11.5px; font-weight: 600; color: var(--primary); text-transform: uppercase; letter-spacing: .6px; }
.sw-mapel-name { font-size: 17px; font-weight: 800; color: var(--primary-dark); margin-top: 3px; }
@media (max-width: 900px) {
.sw-stats { grid-template-columns: repeat(2, 1fr); }
.sw-row, .sw-row-3 { grid-template-columns: 1fr; }
.sw-page { padding: 16px; }
.sw-hero { flex-direction: column; text-align: center; }
.sw-hero-right { text-align: center; }
}
</style>
<div class="sw-page">
{{-- ── HERO BANNER ── --}}
<div class="sw-hero">
<div class="sw-hero-left">
<div class="sw-hero-greeting">Selamat Datang</div>
<div class="sw-hero-name">{{ auth()->user()->name }}</div>
<div class="sw-hero-meta">
<div class="sw-hero-badge">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
Guru
</div>
@if(auth()->user()->guru)
<div class="sw-hero-badge">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
{{ auth()->user()->guru->mapel->nama_mapel ?? '-' }}
</div>
@endif
</div>
</div>
<div class="sw-hero-right">
<div class="sw-hero-date" id="sw-date"></div>
<div class="sw-hero-time" id="sw-time"></div>
</div>
</div>
{{-- ── STATS ── --}}
<div class="sw-stats">
<div class="sw-stat-card primary">
<div class="sw-stat-icon primary">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
</div>
<div>
<div class="sw-stat-val">{{ $jumlahJadwal ?? 0 }}</div>
<div class="sw-stat-lbl">Jadwal Mengajar</div>
</div>
</div>
<div class="sw-stat-card success">
<div class="sw-stat-icon success">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><polyline points="23 11 17 17 14 14"/></svg>
</div>
<div>
<div class="sw-stat-val">{{ $jumlahAbsensi ?? 0 }}</div>
<div class="sw-stat-lbl">Absensi Hari Ini</div>
</div>
</div>
<div class="sw-stat-card warning">
<div class="sw-stat-icon warning">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" 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"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
</div>
<div>
<div class="sw-stat-val">{{ $jumlahTugas ?? 0 }}</div>
<div class="sw-stat-lbl">Tugas Dibuat</div>
</div>
</div>
<div class="sw-stat-card danger">
<div class="sw-stat-icon danger">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" 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-val">{{ $jumlahUjian ?? 0 }}</div>
<div class="sw-stat-lbl">Ujian</div>
</div>
</div>
</div>
{{-- ── ROW: QUICK ACTIONS + MAPEL INFO ── --}}
<div class="sw-row-3">
{{-- QUICK ACTIONS --}}
<div class="sw-card">
<div class="sw-card-hd">
<div class="sw-card-hd-left">
<div class="sw-card-hd-icon primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
</div>
<div>
<h6>Aksi Cepat</h6>
<p>Kelola aktivitas dengan mudah</p>
</div>
</div>
</div>
<div class="sw-actions">
<a href="{{ route('guru.absensi') }}" class="sw-action-btn success">
<div class="sw-action-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><polyline points="23 11 17 17 14 14"/></svg>
</div>
<div class="sw-action-text">
<strong>Input Absensi</strong>
<span>Catat kehadiran siswa hari ini</span>
</div>
<svg class="arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</a>
<a href="{{ route('guru.tugas.index') }}" class="sw-action-btn warning">
<div class="sw-action-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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>
</div>
<div class="sw-action-text">
<strong>Kelola Tugas</strong>
<span>Buat dan pantau tugas siswa</span>
</div>
<svg class="arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</a>
<a href="{{ route('guru.ujian.index') }}" class="sw-action-btn danger">
<div class="sw-action-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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 class="sw-action-text">
<strong>Kelola Ujian</strong>
<span>Buat soal dan jadwal ujian</span>
</div>
<svg class="arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</a>
</div>
</div>
{{-- INFO GURU --}}
<div class="sw-card">
<div class="sw-card-hd">
<div class="sw-card-hd-left">
<div class="sw-card-hd-icon primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
</div>
<div>
<h6>Info Saya</h6>
<p>Data profil guru</p>
</div>
</div>
</div>
<div class="sw-card-bd">
@if(auth()->user()->guru)
<div class="sw-mapel-box" style="margin-bottom:16px;">
<div class="sw-mapel-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
</div>
<div>
<div class="sw-mapel-label">Mata Pelajaran</div>
<div class="sw-mapel-name">{{ auth()->user()->guru->mapel->nama_mapel ?? '-' }}</div>
</div>
</div>
@endif
<div class="sw-info-list">
<div class="sw-info-row">
<div class="sw-info-row-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
Role
</div>
<span class="sw-badge sw-badge-primary">Guru</span>
</div>
<div class="sw-info-row">
<div class="sw-info-row-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
Status
</div>
<span class="sw-badge sw-badge-success">Aktif</span>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function updateClock() {
const now = new Date();
const days = ['Minggu','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'];
const months = ['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Agu','Sep','Okt','Nov','Des'];
const dateEl = document.getElementById('sw-date');
const timeEl = document.getElementById('sw-time');
if (dateEl) dateEl.textContent = days[now.getDay()] + ', ' + now.getDate() + ' ' + months[now.getMonth()] + ' ' + now.getFullYear();
if (timeEl) timeEl.textContent = String(now.getHours()).padStart(2,'0') + ':' + String(now.getMinutes()).padStart(2,'0');
}
updateClock();
setInterval(updateClock, 1000);
</script>
@endsection

View File

@ -1,38 +1,512 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Jadwal Mengajar</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<div class="card shadow">
<div class="card-body">
:root {
--primary: #2563eb;
--primary-light: #eff6ff;
--primary-border: #bfdbfe;
--success: #16a34a;
--success-light: #f0fdf4;
--success-border: #bbf7d0;
--info: #0284c7;
--info-light: #f0f9ff;
--info-border: #bae6fd;
--text-primary: #0f172a;
--text-secondary: #475569;
--text-muted: #94a3b8;
--surface: #ffffff;
--surface-secondary: #f8fafc;
--border: #e2e8f0;
--shadow-md: 0 4px 20px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--radius-md: 10px;
--radius-lg: 14px;
--radius-xl: 18px;
}
<table class="table table-bordered">
<thead>
<tr>
<th>Kelas</th>
<th>Mapel</th>
<th>Hari</th>
<th>Jam</th>
</tr>
</thead>
* {
box-sizing: border-box;
}
<tbody>
@foreach($jadwal as $j)
<tr>
<td>{{ $j->kelas->nama_kelas }}</td>
<td>{{ $j->mapel->nama_mapel }}</td>
<td>{{ $j->hari }}</td>
<td>{{ $j->jam_mulai }} - {{ $j->jam_selesai }}</td>
</tr>
@endforeach
</tbody>
body,
button,
input,
select,
textarea {
font-family: 'Plus Jakarta Sans', sans-serif;
}
</table>
.jadwal-wrapper {
width: 100%;
max-width: 100%;
animation: fadeInUp 0.4s ease both;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.container-fluid {
width: 100%;
max-width: 100%;
}
.table-card {
width: 100%;
}
.table-responsive {
width: 100%;
}
/* ---- HEADER ---- */
.page-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 28px;
}
.page-icon {
width: 52px;
height: 52px;
border-radius: var(--radius-lg);
background: var(--primary-light);
border: 1px solid var(--primary-border);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.page-icon svg { width: 24px; height: 24px; color: var(--primary); }
.page-title {
font-size: 22px;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 3px 0;
letter-spacing: -0.3px;
}
.page-subtitle {
font-size: 13.5px;
color: var(--text-secondary);
margin: 0;
}
/* ---- STATS ROW ---- */
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 18px;
margin-bottom: 24px;
}
.stat-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 16px 18px;
display: flex;
align-items: center;
gap: 12px;
}
.stat-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.stat-icon.blue { background: var(--primary-light); }
.stat-icon.green { background: var(--success-light); }
.stat-icon.teal { background: var(--info-light); }
.stat-icon svg { width: 18px; height: 18px; }
.stat-icon.blue svg { color: var(--primary); }
.stat-icon.green svg { color: var(--success); }
.stat-icon.teal svg { color: var(--info); }
.stat-label { font-size: 12px; color: var(--text-muted); font-weight: 500; }
.stat-value { font-size: 20px; font-weight: 700; color: var(--text-primary); line-height: 1.1; margin-top: 1px; }
/* ---- TABLE CARD ---- */
.table-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.table-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 22px;
border-bottom: 1px solid var(--border);
background: var(--surface-secondary);
gap: 12px;
flex-wrap: wrap;
}
.toolbar-title {
font-size: 13.5px;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-badge {
background: var(--primary-light);
color: var(--primary);
border: 1px solid var(--primary-border);
border-radius: 999px;
font-size: 11.5px;
font-weight: 600;
padding: 2px 10px;
}
.search-wrap {
position: relative;
display: flex;
align-items: center;
}
.search-wrap svg {
position: absolute;
left: 10px;
width: 14px;
height: 14px;
color: var(--text-muted);
pointer-events: none;
}
.search-input {
padding: 7px 12px 7px 32px;
font-size: 13px;
font-family: 'Plus Jakarta Sans', sans-serif;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface);
color: var(--text-primary);
width: 200px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.search-input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37,99,235,0.1);
}
.search-input::placeholder { color: var(--text-muted); }
/* ---- TABLE ---- */
.table-responsive { overflow-x: auto; }
table.jadwal-table {
width: 100%;
border-collapse: collapse;
min-width: 540px;
}
.jadwal-table thead tr {
background: var(--surface-secondary);
border-bottom: 1px solid var(--border);
}
.jadwal-table thead th {
padding: 12px 18px;
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.7px;
text-align: left;
white-space: nowrap;
}
.jadwal-table thead th.center { text-align: center; }
.jadwal-table tbody tr {
border-bottom: 1px solid var(--border);
transition: background 0.15s;
}
.jadwal-table tbody tr:last-child { border-bottom: none; }
.jadwal-table tbody tr:hover { background: #f8faff; }
.jadwal-table tbody td {
padding: 15px 18px;
font-size: 13.5px;
color: var(--text-primary);
vertical-align: middle;
}
.jadwal-table tbody td.center { text-align: center; }
/* KELAS CELL */
.kelas-pill {
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--text-primary);
}
.kelas-dot {
width: 32px;
height: 32px;
border-radius: 8px;
background: var(--primary-light);
border: 1px solid var(--primary-border);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
color: var(--primary);
flex-shrink: 0;
}
/* MAPEL CELL */
.mapel-text {
font-size: 13.5px;
color: var(--text-secondary);
font-weight: 500;
}
/* HARI BADGE */
.badge-hari {
display: inline-flex;
align-items: center;
background: var(--info-light);
color: var(--info);
border: 1px solid var(--info-border);
border-radius: 999px;
font-size: 12px;
font-weight: 600;
padding: 5px 14px;
letter-spacing: 0.2px;
}
/* JAM BADGE */
.badge-jam {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--success-light);
color: var(--success);
border: 1px solid var(--success-border);
border-radius: 999px;
font-size: 12px;
font-weight: 600;
padding: 5px 14px;
white-space: nowrap;
}
.badge-jam svg { width: 13px; height: 13px; }
/* EMPTY STATE */
.empty-state {
padding: 72px 24px;
text-align: center;
}
.empty-icon {
width: 64px;
height: 64px;
background: var(--surface-secondary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
border: 1px solid var(--border);
}
.empty-icon svg { width: 28px; height: 28px; color: var(--text-muted); }
.empty-title { font-size: 15px; font-weight: 600; color: var(--text-primary); margin-bottom: 6px; }
.empty-desc { font-size: 13.5px; color: var(--text-muted); }
/* FOOTER */
.table-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 13px 22px;
border-top: 1px solid var(--border);
background: var(--surface-secondary);
font-size: 12.5px;
color: var(--text-muted);
flex-wrap: wrap;
gap: 6px;
}
@media (max-width: 600px) {
.stats-row { grid-template-columns: 1fr 1fr; }
.page-title { font-size: 18px; }
.search-input { width: 160px; }
}
</style>
<div class="container-fluid px-4 py-4">
<div class="jadwal-wrapper">
{{-- PAGE HEADER --}}
<div class="page-header">
<div class="page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
</div>
<div>
<h1 class="page-title">Jadwal Mengajar</h1>
<p class="page-subtitle">Daftar jadwal kelas dan mata pelajaran yang diampu</p>
</div>
</div>
{{-- STATS --}}
<div class="stats-row">
<div class="stat-card">
<div class="stat-icon blue">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
</div>
<div>
<div class="stat-label">Total Jadwal</div>
<div class="stat-value">{{ $jadwal->count() }}</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon green">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
</svg>
</div>
<div>
<div class="stat-label">Kelas Diampu</div>
<div class="stat-value">{{ $jadwal->unique(fn($j) => $j->kelas->nama_kelas)->count() }}</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon teal">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div>
<div class="stat-label">Hari Aktif</div>
<div class="stat-value">{{ $jadwal->unique('hari')->count() }}</div>
</div>
</div>
</div>
{{-- TABLE CARD --}}
<div class="table-card">
<div class="table-toolbar">
<div class="toolbar-title">
Daftar Jadwal
<span class="toolbar-badge">{{ $jadwal->count() }} sesi</span>
</div>
<div class="search-wrap">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-4.35-4.35M17 11A6 6 0 115 11a6 6 0 0112 0z"/>
</svg>
<input type="text" class="search-input" placeholder="Cari kelas, hari..." id="searchInput" oninput="filterJadwal()">
</div>
</div>
<div class="table-responsive">
<table class="jadwal-table" id="jadwalTable">
<thead>
<tr>
<th>Kelas</th>
<th>Mata Pelajaran</th>
<th class="center">Hari</th>
<th class="center">Jam Mengajar</th>
</tr>
</thead>
<tbody id="jadwalBody">
@forelse($jadwal as $j)
<tr>
<td>
<div class="kelas-pill">
<div class="kelas-dot">
{{ strtoupper(substr($j->kelas->nama_kelas, 0, 2)) }}
</div>
{{ $j->kelas->nama_kelas }}
</div>
</td>
<td>
<span class="mapel-text">{{ $j->mapel->nama_mapel }}</span>
</td>
<td class="center">
<span class="badge-hari">{{ $j->hari }}</span>
</td>
<td class="center">
<span class="badge-jam">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
{{ $j->jam_mulai }} – {{ $j->jam_selesai }}
</span>
</td>
</tr>
@empty
<tr>
<td colspan="4">
<div class="empty-state">
<div class="empty-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
</div>
<div class="empty-title">Belum ada jadwal tersedia</div>
<div class="empty-desc">Jadwal mengajar akan muncul di sini setelah ditambahkan oleh admin.</div>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="table-footer">
<span id="footerCount">Menampilkan {{ $jadwal->count() }} jadwal</span>
<span>{{ now()->format('l, d F Y') }}</span>
</div>
</div>
</div>
</div>
<script>
function filterJadwal() {
const q = document.getElementById('searchInput').value.toLowerCase();
const rows = document.querySelectorAll('#jadwalBody tr');
let count = 0;
rows.forEach(row => {
const show = row.textContent.toLowerCase().includes(q);
row.style.display = show ? '' : 'none';
if (show) count++;
});
document.getElementById('footerCount').textContent = 'Menampilkan ' + count + ' jadwal';
}
</script>
@endsection

View File

@ -1,45 +1,173 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Edit Nilai</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<div class="card shadow">
<div class="card-body">
:root {
--primary:#2563eb; --primary-light:#eff6ff; --primary-border:#bfdbfe; --primary-dark:#1d4ed8;
--success:#16a34a; --success-light:#f0fdf4; --success-border:#bbf7d0;
--warning:#d97706; --warning-light:#fffbeb; --warning-border:#fde68a;
--danger:#dc2626;
--text-primary:#0f172a; --text-secondary:#475569; --text-muted:#94a3b8;
--surface:#ffffff; --surface-secondary:#f8fafc; --border:#e2e8f0;
--shadow-md:0 4px 20px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--shadow-focus:0 0 0 3px rgba(37,99,235,0.12);
--radius-sm:6px; --radius-md:10px; --radius-lg:14px; --radius-xl:18px;
}
*{
box-sizing:border-box;
}
<form action="{{ route('guru.nilai.update', $nilai->id) }}" method="POST">
body,
button,
input,
select,
textarea{
font-family:'Plus Jakarta Sans',sans-serif;
}
.form-wrapper{
width:100%;
max-width:100%;
animation:fadeUp .4s ease both;
}
@keyframes fadeUp { from{opacity:0;transform:translateY(14px)} to{opacity:1;transform:translateY(0)} }
.back-nav { display:inline-flex; align-items:center; gap:6px; color:var(--text-muted); font-size:13.5px; font-weight:500; text-decoration:none; margin-bottom:22px; transition:color 0.15s; }
.back-nav:hover { color:var(--primary); text-decoration:none; }
.back-nav svg { width:16px; height:16px; }
.page-header { display:flex; align-items:center; gap:14px; margin-bottom:24px; }
.page-icon { width:50px; height:50px; border-radius:var(--radius-lg); background:var(--success-light); border:1px solid var(--success-border); display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.page-icon svg { width:24px; height:24px; color:var(--success); }
.page-title { font-size:22px; font-weight:700; color:var(--text-primary); margin:0 0 2px; letter-spacing:-0.3px; }
.page-subtitle { font-size:13px; color:var(--text-secondary); margin:0; }
/* INFO BANNER */
.siswa-banner { display:flex; align-items:center; gap:14px; background:var(--surface-secondary); border:1px solid var(--border); border-radius:var(--radius-lg); padding:16px 20px; margin-bottom:22px; }
.siswa-avatar { width:44px; height:44px; border-radius:var(--radius-md); background:var(--primary-light); border:1px solid var(--primary-border); display:flex; align-items:center; justify-content:center; font-size:15px; font-weight:700; color:var(--primary); flex-shrink:0; }
.siswa-name { font-size:14px; font-weight:600; color:var(--text-primary); }
.siswa-meta { font-size:12.5px; color:var(--text-secondary); margin-top:2px; }
/* CARD */
.form-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-xl); box-shadow:var(--shadow-md); overflow:hidden; }
.card-head { padding:16px 22px; border-bottom:1px solid var(--border); background:var(--surface-secondary); display:flex; align-items:center; gap:9px; }
.card-head-dot { width:8px; height:8px; border-radius:50%; background:var(--success); }
.card-head-label { font-size:12.5px; font-weight:700; color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.7px; }
.card-body-p { padding:26px 24px; }
.form-label { display:block; font-size:13px; font-weight:600; color:var(--text-primary); margin-bottom:7px; }
.form-control {
width:100%; padding:10px 14px; font-size:14px; font-family:'Plus Jakarta Sans',sans-serif;
color:var(--text-primary); background:var(--surface); border:1px solid var(--border);
border-radius:var(--radius-md); outline:none; transition:border-color 0.2s, box-shadow 0.2s;
}
.form-control:focus { border-color:var(--primary); box-shadow:var(--shadow-focus); }
.form-control[readonly] { background:var(--surface-secondary); color:var(--text-secondary); }
.mb-3 { margin-bottom:18px; }
/* NILAI INPUT SPECIAL */
.nilai-input-wrap { position:relative; }
.nilai-input-wrap input { font-size:18px; font-weight:700; padding:12px 50px 12px 16px; text-align:center; }
.nilai-unit { position:absolute; right:14px; top:50%; transform:translateY(-50%); font-size:13px; color:var(--text-muted); font-weight:600; pointer-events:none; }
/* CARD FOOTER */
.card-footer-p { display:flex; align-items:center; justify-content:space-between; padding:18px 24px; border-top:1px solid var(--border); background:var(--surface-secondary); flex-wrap:wrap; gap:10px; }
.btn-cancel { display:inline-flex; align-items:center; gap:7px; padding:10px 20px; font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; background:var(--surface); color:var(--text-secondary); border:1px solid var(--border); border-radius:var(--radius-md); text-decoration:none; cursor:pointer; transition:all 0.15s; }
.btn-cancel:hover { background:var(--surface-secondary); color:var(--text-primary); text-decoration:none; }
.btn-cancel svg { width:15px; height:15px; }
.btn-save { display:inline-flex; align-items:center; gap:8px; padding:10px 26px; font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; background:var(--success); color:#fff; border:none; border-radius:var(--radius-md); cursor:pointer; transition:all 0.15s; box-shadow:0 2px 8px rgba(22,163,74,0.3); }
.btn-save:hover { background:#15803d; transform:translateY(-1px); }
.btn-save svg { width:15px; height:15px; }
.edit-page{
width:100%;
padding:24px 28px 40px;
}
.form-grid{
display:grid;
grid-template-columns:repeat(auto-fit,minmax(320px,1fr));
gap:20px;
}
</style>
<div class="edit-page">
<div class="form-wrapper">
<a href="{{ route('guru.nilai.index') }}" class="back-nav">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/></svg>
Kembali ke Daftar Nilai
</a>
<div class="page-header">
<div class="page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
</div>
<div>
<h2 class="page-title">Edit Nilai</h2>
<p class="page-subtitle">Perbarui nilai akademik siswa</p>
</div>
</div>
{{-- SISWA BANNER --}}
<div class="siswa-banner">
<div class="siswa-avatar">{{ strtoupper(substr($nilai->siswa->nama, 0, 2)) }}</div>
<div>
<div class="siswa-name">{{ $nilai->siswa->nama }}</div>
<div class="siswa-meta">{{ $nilai->mapel->nama_mapel }}</div>
</div>
<div style="margin-left:auto; text-align:right;">
<div style="font-size:11px;color:var(--text-muted);font-weight:600;text-transform:uppercase;letter-spacing:0.5px;">Nilai Saat Ini</div>
<div style="font-size:24px;font-weight:700;color:var(--primary);">{{ $nilai->nilai }}</div>
</div>
</div>
<div class="form-card">
<div class="card-head">
<div class="card-head-dot"></div>
<span class="card-head-label">Perbarui Nilai</span>
</div>
<div class="card-body-p">
<form action="{{ route('guru.nilai.update', $nilai->id) }}" method="POST" id="formEditNilai">
@csrf
@method('PUT')
<div class="mb-3">
<label>Nama Siswa</label>
<label class="form-label">Nama Siswa</label>
<input type="text" class="form-control" value="{{ $nilai->siswa->nama }}" readonly>
</div>
<div class="mb-3">
<label>Mata Pelajaran</label>
<label class="form-label">Mata Pelajaran</label>
<input type="text" class="form-control" value="{{ $nilai->mapel->nama_mapel }}" readonly>
</div>
<div class="mb-3">
<label>Nilai</label>
<input type="number" name="nilai" class="form-control"
value="{{ $nilai->nilai }}" min="0" max="100" required>
<label class="form-label">Nilai Baru <span style="color:var(--danger);">*</span></label>
<div class="nilai-input-wrap">
<input type="number" name="nilai" class="form-control" value="{{ $nilai->nilai }}" min="0" max="100" required>
<span class="nilai-unit">/ 100</span>
</div>
</div>
<button type="submit" class="btn btn-success">
Update Nilai
</button>
<a href="{{ route('guru.nilai.index') }}" class="btn btn-secondary">
Kembali
</a>
</form>
</div>
<div class="card-footer-p">
<a href="{{ route('guru.nilai.index') }}" class="btn-cancel">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
Batalkan
</a>
<button type="submit" form="formEditNilai" class="btn-save">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
Simpan Nilai
</button>
</div>
</div>
</div>
</div>
@endsection

View File

@ -1,70 +1,215 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<!-- Header + Button -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 text-gray-800">Data Nilai Siswa</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<a href="{{ route('guru.nilai.input') }}" class="btn btn-primary">
+ Input Nilai
:root {
--primary:#2563eb; --primary-light:#eff6ff; --primary-border:#bfdbfe; --primary-dark:#1d4ed8;
--success:#16a34a; --success-light:#f0fdf4; --success-border:#bbf7d0;
--warning:#d97706; --warning-light:#fffbeb; --warning-border:#fde68a;
--danger:#dc2626; --danger-light:#fef2f2; --danger-border:#fecaca;
--text-primary:#0f172a; --text-secondary:#475569; --text-muted:#94a3b8;
--surface:#ffffff; --surface-secondary:#f8fafc; --border:#e2e8f0;
--shadow-md:0 4px 20px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--shadow-focus:0 0 0 3px rgba(37,99,235,0.12);
--radius-sm:6px; --radius-md:10px; --radius-lg:14px; --radius-xl:18px;
}
* { font-family:'Plus Jakarta Sans',sans-serif; box-sizing:border-box; }
.page-wrapper { animation:fadeUp 0.4s ease both; }
@keyframes fadeUp { from{opacity:0;transform:translateY(12px)} to{opacity:1;transform:translateY(0)} }
/* HEADER */
.page-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:28px; gap:16px; flex-wrap:wrap; }
.page-header-left { display:flex; align-items:center; gap:14px; }
.page-icon { width:50px; height:50px; border-radius:var(--radius-lg); background:var(--primary-light); border:1px solid var(--primary-border); display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.page-icon svg { width:24px; height:24px; color:var(--primary); }
.page-title { font-size:22px; font-weight:700; color:var(--text-primary); margin:0 0 2px; letter-spacing:-0.3px; }
.page-subtitle { font-size:13px; color:var(--text-secondary); margin:0; }
/* BUTTON ADD */
.btn-add {
display:inline-flex; align-items:center; gap:7px; padding:10px 20px;
font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif;
background:var(--primary); color:#fff; border:none; border-radius:var(--radius-md);
text-decoration:none; cursor:pointer; transition:all 0.15s;
box-shadow:0 2px 8px rgba(37,99,235,0.25);
}
.btn-add:hover { background:var(--primary-dark); color:#fff; text-decoration:none; transform:translateY(-1px); }
.btn-add svg { width:15px; height:15px; }
/* FILTER BAR */
.filter-bar { display:flex; align-items:center; gap:12px; margin-bottom:0; }
.filter-select {
padding:8px 34px 8px 12px; font-size:13px; font-family:'Plus Jakarta Sans',sans-serif;
border:1px solid var(--border); border-radius:var(--radius-md);
background:var(--surface); color:var(--text-primary); outline:none;
appearance:none; cursor:pointer; width:200px;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");
background-repeat:no-repeat; background-position:right 10px center; background-size:14px;
transition:border-color 0.2s, box-shadow 0.2s;
}
.filter-select:focus { border-color:var(--primary); box-shadow:var(--shadow-focus); }
.filter-label { font-size:13px; font-weight:500; color:var(--text-secondary); }
/* CARD */
.section-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-xl); box-shadow:var(--shadow-md); overflow:hidden; }
.card-toolbar { display:flex; align-items:center; justify-content:space-between; padding:16px 22px; border-bottom:1px solid var(--border); background:var(--surface-secondary); flex-wrap:wrap; gap:12px; }
.toolbar-title { font-size:13.5px; font-weight:600; color:var(--text-primary); }
.toolbar-badge { background:var(--primary-light); color:var(--primary); border:1px solid var(--primary-border); border-radius:999px; font-size:11.5px; font-weight:600; padding:2px 10px; margin-left:8px; }
/* TABLE */
.table-responsive { overflow-x:auto; }
table.nilai-table { width:100%; border-collapse:collapse; min-width:600px; }
.nilai-table thead tr { background:var(--surface-secondary); border-bottom:1px solid var(--border); }
.nilai-table thead th { padding:12px 16px; font-size:11px; font-weight:700; color:var(--text-muted); text-transform:uppercase; letter-spacing:0.7px; text-align:left; white-space:nowrap; }
.nilai-table tbody tr { border-bottom:1px solid var(--border); transition:background 0.15s; }
.nilai-table tbody tr:last-child { border-bottom:none; }
.nilai-table tbody tr:hover { background:#f8faff; }
.nilai-table tbody td { padding:14px 16px; font-size:13.5px; color:var(--text-primary); vertical-align:middle; }
.cell-no { font-size:13px; color:var(--text-muted); font-weight:500; }
/* NAME CELL */
.name-cell { display:flex; align-items:center; gap:10px; }
.name-avatar { width:34px; height:34px; border-radius:50%; background:var(--primary-light); border:1px solid var(--primary-border); display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:700; color:var(--primary); flex-shrink:0; }
/* NILAI BADGE */
.nilai-chip {
display:inline-flex; align-items:center; justify-content:center;
width:46px; height:28px; border-radius:var(--radius-sm);
font-size:13px; font-weight:700;
}
.nilai-chip.tinggi { background:var(--success-light); color:var(--success); border:1px solid var(--success-border); }
.nilai-chip.sedang { background:var(--warning-light); color:var(--warning); border:1px solid var(--warning-border); }
.nilai-chip.rendah { background:var(--danger-light); color:var(--danger); border:1px solid var(--danger-border); }
.badge-kelas { background:var(--primary-light); color:var(--primary); border:1px solid var(--primary-border); border-radius:999px; font-size:11.5px; font-weight:600; padding:3px 11px; display:inline-flex; }
.badge-mapel { background:var(--surface-secondary); color:var(--text-secondary); border:1px solid var(--border); border-radius:999px; font-size:11.5px; font-weight:600; padding:3px 11px; display:inline-flex; }
.btn-edit-sm {
display:inline-flex; align-items:center; gap:5px; padding:5px 12px;
font-size:12.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif;
background:var(--warning-light); color:var(--warning);
border:1px solid var(--warning-border); border-radius:var(--radius-sm);
text-decoration:none; transition:all 0.15s;
}
.btn-edit-sm:hover { background:#fef3c7; color:var(--warning); text-decoration:none; }
.btn-edit-sm svg { width:13px; height:13px; }
/* EMPTY */
.empty-state { padding:64px 24px; text-align:center; }
.empty-icon { width:60px; height:60px; background:var(--surface-secondary); border-radius:50%; border:1px solid var(--border); display:flex; align-items:center; justify-content:center; margin:0 auto 14px; }
.empty-icon svg { width:26px; height:26px; color:var(--text-muted); }
.empty-title { font-size:15px; font-weight:600; color:var(--text-primary); margin-bottom:4px; }
.empty-desc { font-size:13px; color:var(--text-muted); }
/* TABLE FOOTER */
.table-footer { display:flex; align-items:center; justify-content:space-between; padding:13px 22px; border-top:1px solid var(--border); background:var(--surface-secondary); font-size:12.5px; color:var(--text-muted); flex-wrap:wrap; gap:6px; }
</style>
<div class="container-fluid px-4 py-2 page-wrapper">
{{-- HEADER --}}
<div class="page-header">
<div class="page-header-left">
<div class="page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<div>
<h4 class="page-title">Data Nilai Siswa</h4>
<p class="page-subtitle">Kelola dan pantau nilai akademik siswa</p>
</div>
</div>
<a href="{{ route('guru.nilai.input') }}" class="btn-add">
<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
</a>
</div>
<!-- Card -->
<div class="card shadow">
<div class="card-body">
{{-- TABLE CARD --}}
<div class="section-card">
<div class="card-toolbar">
<span class="toolbar-title">Daftar Nilai<span class="toolbar-badge">{{ $nilai->count() }} data</span></span>
<form method="GET" action="{{ route('guru.nilai.index') }}" style="margin:0;">
<div class="filter-bar">
<span class="filter-label">Filter:</span>
<select name="kelas_id" class="filter-select" onchange="this.form.submit()">
<option value="">Semua Kelas</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}" {{ request('kelas_id') == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
</div>
</form>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>No</th>
<th>Nama Siswa</th>
<th>Kelas</th>
<th>Mata Pelajaran</th>
<th>Nilai</th>
<th>Aksi</th> <!-- 🔥 TAMBAH -->
</tr>
</thead>
<div class="table-responsive">
<table class="nilai-table">
<thead>
<tr>
<th>No</th>
<th>Nama Siswa</th>
<th>Kelas</th>
<th>Mata Pelajaran</th>
<th>Nilai</th>
<th style="text-align:center;">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($nilai as $n)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $n->siswa->nama ?? '-' }}</td>
<td>{{ $n->siswa->kelas->nama_kelas ?? '-' }}</td>
<td>{{ $n->mapel->nama_mapel ?? '-' }}</td>
<td>{{ $n->nilai }}</td>
<td>
<a href="{{ route('guru.nilai.edit', $n->id) }}" class="btn btn-warning btn-sm">
Edit
</a>
</td>
<td class="cell-no">{{ $loop->iteration }}</td>
<td>
<div class="name-cell">
<div class="name-avatar">{{ strtoupper(substr($n->siswa->nama ?? 'S', 0, 2)) }}</div>
<span style="font-weight:600;">{{ $n->siswa->nama ?? '—' }}</span>
</div>
</td>
<td><span class="badge-kelas">{{ $n->siswa->kelas->nama_kelas ?? '—' }}</span></td>
<td><span class="badge-mapel">{{ $n->mapel->nama_mapel ?? '—' }}</span></td>
<td>
<span class="nilai-chip {{ $n->nilai >= 80 ? 'tinggi' : ($n->nilai >= 60 ? 'sedang' : 'rendah') }}">
{{ $n->nilai }}
</span>
</td>
<td style="text-align:center;">
<a href="{{ route('guru.nilai.edit', $n->id) }}" class="btn-edit-sm">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 012.828 2.828L11.828 15.828a2 2 0 01-1.414.586H8v-2.414a2 2 0 01.586-1.414z"/>
</svg>
Edit
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center">Belum ada data</td>
<td colspan="6">
<div class="empty-state">
<div class="empty-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<div class="empty-title">Belum ada data nilai</div>
<div class="empty-desc">Mulai input nilai dengan klik tombol Input Nilai di atas.</div>
</div>
</td>
</tr>
@endforelse
</tbody>
<form method="GET" action="{{ route('guru.nilai.index') }}" class="mb-3">
<div class="row">
<div class="col-md-4">
<select name="kelas_id" class="form-control" onchange="this.form.submit()">
<option value="">-- Semua Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}"
{{ request('kelas_id') == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
</div>
</div>
</form>
</table>
</div>
<div class="table-footer">
<span>Menampilkan {{ $nilai->count() }} data nilai</span>
<span>{{ now()->format('d M Y') }}</span>
</div>
</div>

View File

@ -1,104 +1,309 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Input Nilai Siswa</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<div class="card shadow">
<div class="card-body">
:root {
--primary:#2563eb; --primary-light:#eff6ff; --primary-border:#bfdbfe; --primary-dark:#1d4ed8;
--success:#16a34a; --success-light:#f0fdf4; --success-border:#bbf7d0;
--danger:#dc2626;
--text-primary:#0f172a; --text-secondary:#475569; --text-muted:#94a3b8;
--surface:#ffffff; --surface-secondary:#f8fafc; --border:#e2e8f0;
--shadow-md:0 4px 20px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--shadow-focus:0 0 0 3px rgba(37,99,235,0.12);
--radius-sm:6px; --radius-md:10px; --radius-lg:14px; --radius-xl:18px;
}
* {
box-sizing:border-box;
}
<!-- PILIH KELAS -->
<div class="mb-3">
<label>Kelas</label>
<select id="kelas" class="form-control">
<option value="">-- Pilih Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}">{{ $k->nama_kelas }}</option>
@endforeach
</select>
body,
button,
input,
select,
textarea {
font-family:'Plus Jakarta Sans',sans-serif;
}
.form-wrapper {
width:100%;
max-width:100%;
animation:fadeUp 0.4s ease both;
}
@keyframes fadeUp { from{opacity:0;transform:translateY(14px)} to{opacity:1;transform:translateY(0)} }
.back-nav { display:inline-flex; align-items:center; gap:6px; color:var(--text-muted); font-size:13.5px; font-weight:500; text-decoration:none; margin-bottom:22px; transition:color 0.15s; }
.back-nav:hover { color:var(--primary); text-decoration:none; }
.back-nav svg { width:16px; height:16px; }
.page-header { display:flex; align-items:center; gap:14px; margin-bottom:24px; }
.page-icon { width:50px; height:50px; border-radius:var(--radius-lg); background:var(--primary-light); border:1px solid var(--primary-border); display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.page-icon svg { width:24px; height:24px; color:var(--primary); }
.page-title { font-size:22px; font-weight:700; color:var(--text-primary); margin:0 0 2px; letter-spacing:-0.3px; }
.page-subtitle { font-size:13px; color:var(--text-secondary); margin:0; }
.form-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-xl); box-shadow:var(--shadow-md); overflow:hidden; margin-bottom:16px; }
.card-head { padding:16px 22px; border-bottom:1px solid var(--border); background:var(--surface-secondary); display:flex; align-items:center; gap:9px; }
.card-head-dot { width:8px; height:8px; border-radius:50%; }
.dot-blue { background:var(--primary); }
.dot-green { background:var(--success); }
.card-head-label { font-size:12.5px; font-weight:700; color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.7px; }
.card-body-p { padding:22px 24px; }
.form-label { display:block; font-size:13px; font-weight:600; color:var(--text-primary); margin-bottom:7px; }
.form-control {
width:100%; padding:10px 14px; font-size:13.5px; font-family:'Plus Jakarta Sans',sans-serif;
color:var(--text-primary); background:var(--surface); border:1px solid var(--border);
border-radius:var(--radius-md); outline:none; transition:border-color 0.2s, box-shadow 0.2s;
}
.form-control:focus { border-color:var(--primary); box-shadow:var(--shadow-focus); }
.form-control[readonly] { background:var(--surface-secondary); color:var(--text-secondary); }
.form-select {
width:100%; padding:10px 38px 10px 14px; font-size:13.5px; font-family:'Plus Jakarta Sans',sans-serif;
color:var(--text-primary); background:var(--surface); border:1px solid var(--border);
border-radius:var(--radius-md); outline:none; appearance:none; cursor:pointer;
transition:border-color 0.2s, box-shadow 0.2s;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");
background-repeat:no-repeat; background-position:right 12px center; background-size:16px;
}
.form-select:focus { border-color:var(--primary); box-shadow:var(--shadow-focus); }
.mb-3 { margin-bottom:16px; }
.filter-row {
display:grid;
grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
gap:18px;
}
/* TABLE */
.table-responsive { overflow-x:auto; }
table.nilai-input-table { width:100%; border-collapse:collapse; min-width:400px; }
.nilai-input-table thead tr { background:var(--surface-secondary); border-bottom:1px solid var(--border); }
.nilai-input-table thead th { padding:12px 16px; font-size:11px; font-weight:700; color:var(--text-muted); text-transform:uppercase; letter-spacing:0.7px; text-align:left; white-space:nowrap; }
.nilai-input-table tbody tr { border-bottom:1px solid var(--border); transition:background 0.15s; }
.nilai-input-table tbody tr:last-child { border-bottom:none; }
.nilai-input-table tbody tr:hover { background:#f8faff; }
.nilai-input-table tbody td { padding:12px 16px; font-size:13.5px; color:var(--text-primary); vertical-align:middle; }
.nilai-page{
width:100%;
padding:24px 28px 40px;
}
.cell-no { font-size:12px; color:var(--text-muted); width:42px; }
.name-cell { display:flex; align-items:center; gap:10px; }
.name-avatar { width:32px; height:32px; border-radius:50%; background:var(--primary-light); border:1px solid var(--primary-border); display:flex; align-items:center; justify-content:center; font-size:10px; font-weight:700; color:var(--primary); flex-shrink:0; }
.nilai-input {
width:90px; padding:7px 10px; font-size:14px; font-weight:700; text-align:center;
font-family:'Plus Jakarta Sans',sans-serif; border:1px solid var(--border);
border-radius:var(--radius-sm); outline:none; transition:border-color 0.2s, box-shadow 0.2s;
}
.nilai-input:focus { border-color:var(--primary); box-shadow:var(--shadow-focus); }
/* PLACEHOLDER STATE */
.table-placeholder { padding:48px 24px; text-align:center; }
.placeholder-icon { width:52px; height:52px; background:var(--surface-secondary); border-radius:50%; border:1px solid var(--border); display:flex; align-items:center; justify-content:center; margin:0 auto 12px; }
.placeholder-icon svg { width:24px; height:24px; color:var(--text-muted); }
.placeholder-text { font-size:13.5px; color:var(--text-muted); }
/* FOOTER */
.card-footer-p { display:flex; align-items:center; justify-content:space-between; padding:18px 24px; border-top:1px solid var(--border); background:var(--surface-secondary); flex-wrap:wrap; gap:10px; }
.btn-save { display:inline-flex; align-items:center; gap:8px; padding:10px 26px; font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; background:var(--success); color:#fff; border:none; border-radius:var(--radius-md); cursor:pointer; transition:all 0.15s; box-shadow:0 2px 8px rgba(22,163,74,0.3); }
.btn-save:hover { background:#15803d; transform:translateY(-1px); }
.btn-save svg { width:15px; height:15px; }
.footer-info { font-size:12.5px; color:var(--text-muted); }
.form-card{
width:100%;
}
.table-responsive{
width:100%;
overflow-x:auto;
}
.card-body-p{
padding:28px;
}
.card-footer-p{
padding:20px 28px;
}
@media (max-width:768px){
.nilai-page{
padding:16px;
}
.page-header{
align-items:flex-start;
}
.page-title{
font-size:20px;
}
.filter-row{
grid-template-columns:1fr;
}
.card-body-p{
padding:18px;
}
.card-footer-p{
padding:16px 18px;
flex-direction:column;
align-items:flex-start;
}
.btn-save{
width:100%;
justify-content:center;
}
}
</style>
<div class="nilai-page">
<div class="form-wrapper">
<a href="{{ route('guru.nilai.index') }}" class="back-nav">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/></svg>
Kembali ke Daftar Nilai
</a>
<div class="page-header">
<div class="page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
</div>
<div>
<h2 class="page-title">Input Nilai Siswa</h2>
<p class="page-subtitle">Masukkan nilai akademik siswa per kelas</p>
</div>
</div>
{{-- FILTER CARD --}}
<div class="form-card">
<div class="card-head">
<div class="card-head-dot dot-blue"></div>
<span class="card-head-label">Pilih Kelas</span>
</div>
<div class="card-body-p">
<div class="filter-row">
<div>
<label class="form-label">Kelas</label>
<select id="kelas" class="form-select">
<option value="">— Pilih Kelas —</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}">{{ $k->nama_kelas }}</option>
@endforeach
</select>
</div>
<div>
<label class="form-label">Mata Pelajaran</label>
<input type="text" class="form-control" value="{{ $mapel->nama_mapel }}" readonly>
</div>
</div>
</div>
</div>
<!-- 🔥 MAPEL OTOMATIS -->
<div class="mb-3">
<label>Mata Pelajaran</label>
<input type="text" class="form-control" value="{{ $mapel->nama_mapel }}" readonly>
</div>
{{-- INPUT NILAI CARD --}}
<div class="form-card">
<div class="card-head">
<div class="card-head-dot dot-green"></div>
<span class="card-head-label">Input Nilai</span>
</div>
<form action="{{ route('guru.nilai.storeBatch') }}" method="POST">
@csrf
<form action="{{ route('guru.nilai.storeBatch') }}" method="POST">
@csrf
<input type="hidden" name="mapel_id" value="{{ $mapel->id }}">
<!-- 🔥 kirim mapel otomatis -->
<input type="hidden" name="mapel_id" value="{{ $mapel->id }}">
<table class="table table-bordered">
<div class="table-responsive">
<table class="nilai-input-table">
<thead>
<tr>
<th width="50">No</th>
<th class="cell-no">No</th>
<th>Nama Siswa</th>
<th width="200">Nilai</th>
<th style="text-align:center;">Nilai</th>
</tr>
</thead>
<tbody id="data-siswa">
<tr>
<td colspan="3" class="text-center">Pilih kelas terlebih dahulu</td>
<td colspan="3">
<div class="table-placeholder">
<div class="placeholder-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a4 4 0 00-5-3.87M9 20H4v-2a4 4 0 015-3.87m4 5.87a4 4 0 110-8 4 4 0 010 8zm6-12a4 4 0 10-8 0 4 4 0 008 0z"/>
</svg>
</div>
<p class="placeholder-text">Pilih kelas terlebih dahulu untuk memuat daftar siswa</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<button type="submit" class="btn btn-success mt-2">
<div class="card-footer-p">
<span class="footer-info" id="siswaCount">Pilih kelas untuk mulai</span>
<button type="submit" class="btn-save" id="btnSave" style="display:none;">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
Simpan Nilai
</button>
</form>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
document.getElementById('kelas').addEventListener('change', function() {
let kelas_id = this.value;
if (!kelas_id) {
document.getElementById('data-siswa').innerHTML =
`<tr><td colspan="3" class="text-center">Pilih kelas terlebih dahulu</td></tr>`;
document.getElementById('data-siswa').innerHTML = `
<tr><td colspan="3"><div class="table-placeholder">
<div class="placeholder-icon"><svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" style="width:24px;height:24px;color:var(--text-muted)"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a4 4 0 00-5-3.87M9 20H4v-2a4 4 0 015-3.87m4 5.87a4 4 0 110-8 4 4 0 010 8zm6-12a4 4 0 10-8 0 4 4 0 008 0z"/></svg></div>
<p class="placeholder-text">Pilih kelas terlebih dahulu untuk memuat daftar siswa</p>
</div></td></tr>`;
document.getElementById('btnSave').style.display = 'none';
document.getElementById('siswaCount').textContent = 'Pilih kelas untuk mulai';
return;
}
fetch('/guru/get-siswa/' + kelas_id)
.then(res => res.json())
.then(data => {
let html = '';
let no = 1;
if (data.length === 0) {
html = `<tr><td colspan="3" class="text-center">Tidak ada siswa</td></tr>`;
html = `<tr><td colspan="3"><div class="table-placeholder"><p class="placeholder-text">Tidak ada siswa di kelas ini</p></div></td></tr>`;
document.getElementById('btnSave').style.display = 'none';
document.getElementById('siswaCount').textContent = '0 siswa';
} else {
let no = 1;
data.forEach(siswa => {
const initials = siswa.nama.substring(0, 2).toUpperCase();
html += `
<tr>
<td>${no++}</td>
<td>${siswa.nama}</td>
<td>
<input
type="number"
name="nilai[${siswa.id}]"
class="form-control"
min="0"
max="100"
required
>
</td>
</tr>
`;
<tr>
<td class="cell-no">${no++}</td>
<td>
<div class="name-cell">
<div class="name-avatar">${initials}</div>
<span style="font-weight:600;">${siswa.nama}</span>
</div>
</td>
<td style="text-align:center;">
<input type="number" name="nilai[${siswa.id}]" class="nilai-input" min="0" max="100" required placeholder="0">
</td>
</tr>`;
});
document.getElementById('btnSave').style.display = 'inline-flex';
document.getElementById('siswaCount').textContent = data.length + ' siswa siap diinput';
}
document.getElementById('data-siswa').innerHTML = html;
});
});
</script>

View File

@ -1,83 +1,211 @@
{{-- ============================================================
FILE: tugas/create.blade.php
============================================================ --}}
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--primary:#2563eb; --primary-light:#eff6ff; --primary-border:#bfdbfe; --primary-dark:#1d4ed8;
--success:#16a34a; --success-light:#f0fdf4; --success-border:#bbf7d0;
--danger:#dc2626; --danger-light:#fef2f2; --danger-border:#fecaca;
--info:#0284c7; --info-light:#f0f9ff; --info-border:#bae6fd;
--text-primary:#0f172a; --text-secondary:#475569; --text-muted:#94a3b8;
--surface:#ffffff; --surface-secondary:#f8fafc; --border:#e2e8f0;
--shadow-md:0 4px 20px rgba(0,0,0,0.08); --shadow-focus:0 0 0 3px rgba(37,99,235,0.12);
--radius-sm:6px; --radius-md:10px; --radius-lg:14px; --radius-xl:18px;
}
*{
box-sizing:border-box;
}
<div class="mb-4">
<h4 class="fw-bold mb-1">Tambah Tugas</h4>
<small class="text-muted">Isi data tugas yang akan diberikan kepada siswa</small>
body,
button,
input,
select,
textarea{
font-family:'Plus Jakarta Sans',sans-serif;
}
.form-wrapper{
width:100%;
max-width:100%;
animation:fadeUp .4s ease both;
}
@keyframes fadeUp { from{opacity:0;transform:translateY(14px)} to{opacity:1;transform:translateY(0)} }
.back-nav { display:inline-flex; align-items:center; gap:6px; color:var(--text-muted); font-size:13.5px; font-weight:500; text-decoration:none; margin-bottom:22px; transition:color 0.15s; }
.back-nav:hover { color:var(--primary); text-decoration:none; }
.back-nav svg { width:16px; height:16px; }
.page-header { display:flex; align-items:center; gap:14px; margin-bottom:24px; }
.page-icon { width:50px; height:50px; border-radius:var(--radius-lg); background:var(--primary-light); border:1px solid var(--primary-border); display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.page-icon svg { width:24px; height:24px; color:var(--primary); }
.page-title { font-size:22px; font-weight:700; color:var(--text-primary); margin:0 0 2px; letter-spacing:-0.3px; }
.page-subtitle { font-size:13px; color:var(--text-secondary); margin:0; }
.form-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-xl); box-shadow:var(--shadow-md); overflow:hidden; margin-bottom:16px; }
.card-head { padding:16px 22px; border-bottom:1px solid var(--border); background:var(--surface-secondary); display:flex; align-items:center; gap:9px; }
.card-head-dot { width:8px; height:8px; border-radius:50%; }
.dot-blue { background:var(--primary); } .dot-green { background:var(--success); }
.card-head-label { font-size:12.5px; font-weight:700; color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.7px; }
.card-body-p { padding:24px; }
.form-row { display:grid; gap:16px; margin-bottom:16px; }
.form-row.cols-2{
grid-template-columns:
repeat(auto-fit,minmax(280px,1fr));
}
.form-row.cols-3{
grid-template-columns:
repeat(auto-fit,minmax(260px,1fr));
}
.form-row.cols-1 { grid-template-columns:1fr; }
.form-label { display:block; font-size:13px; font-weight:600; color:var(--text-primary); margin-bottom:7px; }
.form-control, .form-select, textarea.form-control {
width:100%; padding:10px 14px; font-size:13.5px; font-family:'Plus Jakarta Sans',sans-serif;
color:var(--text-primary); background:var(--surface); border:1px solid var(--border);
border-radius:var(--radius-md); outline:none; transition:border-color 0.2s, box-shadow 0.2s;
}
.form-control:focus, .form-select:focus, textarea.form-control:focus { border-color:var(--primary); box-shadow:var(--shadow-focus); }
.form-control[readonly] { background:var(--surface-secondary); color:var(--text-secondary); }
.form-select { appearance:none; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 12px center; background-size:16px; padding-right:38px; cursor:pointer; }
textarea.form-control { resize:vertical; min-height:100px; }
.file-hint { font-size:12px; color:var(--text-muted); margin-top:5px; }
.card-footer-p { display:flex; align-items:center; justify-content:space-between; padding:18px 24px; border-top:1px solid var(--border); background:var(--surface-secondary); flex-wrap:wrap; gap:10px; }
.btn-cancel { display:inline-flex; align-items:center; gap:7px; padding:10px 20px; font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; background:var(--surface); color:var(--text-secondary); border:1px solid var(--border); border-radius:var(--radius-md); text-decoration:none; cursor:pointer; transition:all 0.15s; }
.btn-cancel:hover { background:var(--surface-secondary); color:var(--text-primary); text-decoration:none; }
.btn-cancel svg { width:15px; height:15px; }
.btn-save { display:inline-flex; align-items:center; gap:8px; padding:10px 26px; font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; background:var(--primary); color:#fff; border:none; border-radius:var(--radius-md); cursor:pointer; transition:all 0.15s; box-shadow:0 2px 8px rgba(37,99,235,0.25); }
.btn-save:hover { background:var(--primary-dark); transform:translateY(-1px); }
.btn-save svg { width:15px; height:15px; }
@media(max-width:768px){
.tugas-page{
padding:16px;
}
.page-header{
align-items:flex-start;
}
.page-title{
font-size:20px;
}
.form-row.cols-2,
.form-row.cols-3{
grid-template-columns:1fr;
}
.card-body-p{
padding:18px;
}
.card-footer-p{
flex-direction:column;
align-items:stretch;
}
.btn-save,
.btn-cancel{
width:100%;
justify-content:center;
}
}
.tugas-page{
width:100%;
padding:24px 28px 40px;
}
.form-card{
width:100%;
}
.card-body-p{
padding:28px;
}
</style>
<div class="tugas-page">
<div class="form-wrapper">
<a href="{{ route('guru.tugas.index') }}" class="back-nav">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/></svg>
Kembali ke Daftar Tugas
</a>
<div class="page-header">
<div class="page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4"/></svg>
</div>
<div>
<h2 class="page-title">Tambah Tugas Baru</h2>
<p class="page-subtitle">Buat tugas untuk siswa lengkap dengan file dan deadline</p>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-body">
<form action="{{ route('guru.tugas.store') }}" method="POST" enctype="multipart/form-data">
<div class="form-card">
<div class="card-head"><div class="card-head-dot dot-blue"></div><span class="card-head-label">Informasi Tugas</span></div>
<div class="card-body-p">
<form action="{{ route('guru.tugas.store') }}" method="POST" enctype="multipart/form-data" id="formCreate">
@csrf
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Judul Tugas</label>
<input type="text" name="judul" class="form-control" required>
<div class="form-row cols-3">
<div>
<label class="form-label">Judul Tugas <span style="color:var(--danger)">*</span></label>
<input type="text" name="judul" class="form-control" placeholder="Masukkan judul tugas" required>
</div>
<div class="col-md-3 mb-3">
<label class="form-label">Kelas</label>
<div>
<label class="form-label">Kelas <span style="color:var(--danger)">*</span></label>
<select name="kelas_id" class="form-select" required>
<option value="">-- Pilih Kelas --</option>
<option value="">— Pilih Kelas —</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}">{{ $k->nama_kelas }}</option>
@endforeach
</select>
</div>
<div class="col-md-3 mb-3">
<div>
<label class="form-label">Mata Pelajaran</label>
@if($mapel)
<input type="text" class="form-control"
value="{{ $mapel->nama_mapel }}"
readonly>
<input type="text" class="form-control" value="{{ $mapel->nama_mapel }}" readonly>
<input type="hidden" name="mapel_id" value="{{ $mapel->id }}">
@else
<input type="text" class="form-control"
value="Mapel belum diset di akun guru" readonly>
<input type="text" class="form-control" value="Mapel belum diset" readonly>
@endif
</div>
</div>
<div class="col-md-12 mb-3">
<div class="form-row cols-1">
<div>
<label class="form-label">Deskripsi</label>
<textarea name="deskripsi" rows="4" class="form-control"></textarea>
<textarea name="deskripsi" class="form-control" placeholder="Tuliskan deskripsi dan instruksi tugas..."></textarea>
</div>
</div>
<div class="col-md-12 mb-3">
<div class="form-row cols-2">
<div>
<label class="form-label">Upload File Tugas</label>
<input type="file" name="file" class="form-control"
accept=".pdf,.doc,.docx,.ppt,.pptx">
<input type="file" name="file" class="form-control" accept=".pdf,.doc,.docx,.ppt,.pptx">
<p class="file-hint">Format: PDF, DOC, DOCX, PPT, PPTX</p>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Deadline</label>
<div>
<label class="form-label">Deadline <span style="color:var(--danger)">*</span></label>
<input type="datetime-local" name="deadline" class="form-control" required>
</div>
</div>
<div class="mt-3 d-flex justify-content-end">
<a href="{{ route('guru.tugas.index') }}" class="btn btn-secondary me-2">
Kembali
</a>
<button type="submit" class="btn btn-primary">
Simpan Tugas
</button>
</div>
</form>
</div>
<div class="card-footer-p">
<a href="{{ route('guru.tugas.index') }}" class="btn-cancel">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
Batalkan
</a>
<button type="submit" form="formCreate" class="btn-save">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
Simpan Tugas
</button>
</div>
</div>
</div>
</div>
@endsection

View File

@ -1,80 +1,176 @@
{{-- ============================================================
FILE: tugas/edit.blade.php
============================================================ --}}
@extends('layouts.app')
@section('content')
<div class="container">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
:root {
--primary:#2563eb; --primary-light:#eff6ff; --primary-border:#bfdbfe; --primary-dark:#1d4ed8;
--success:#16a34a; --success-light:#f0fdf4; --success-border:#bbf7d0;
--warning:#d97706; --warning-light:#fffbeb; --warning-border:#fde68a;
--danger:#dc2626; --danger-light:#fef2f2; --danger-border:#fecaca;
--text-primary:#0f172a; --text-secondary:#475569; --text-muted:#94a3b8;
--surface:#ffffff; --surface-secondary:#f8fafc; --border:#e2e8f0;
--shadow-md:0 4px 20px rgba(0,0,0,0.08); --shadow-focus:0 0 0 3px rgba(37,99,235,0.12);
--radius-sm:6px; --radius-md:10px; --radius-lg:14px; --radius-xl:18px;
}
*{
box-sizing:border-box;
}
<h4>Edit Tugas</h4>
body,
button,
input,
select,
textarea{
font-family:'Plus Jakarta Sans',sans-serif;
}
.form-wrapper{
width:100%;
max-width:100%;
animation:fadeUp .4s ease both;
}
@keyframes fadeUp { from{opacity:0;transform:translateY(14px)} to{opacity:1;transform:translateY(0)} }
.back-nav { display:inline-flex; align-items:center; gap:6px; color:var(--text-muted); font-size:13.5px; font-weight:500; text-decoration:none; margin-bottom:22px; transition:color 0.15s; }
.back-nav:hover { color:var(--primary); text-decoration:none; }
.back-nav svg { width:16px; height:16px; }
.page-header { display:flex; align-items:center; gap:14px; margin-bottom:24px; }
.page-icon { width:50px; height:50px; border-radius:var(--radius-lg); background:var(--warning-light); border:1px solid var(--warning-border); display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.page-icon svg { width:24px; height:24px; color:var(--warning); }
.page-title { font-size:22px; font-weight:700; color:var(--text-primary); margin:0 0 2px; letter-spacing:-0.3px; }
.page-subtitle { font-size:13px; color:var(--text-secondary); margin:0; }
.form-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-xl); box-shadow:var(--shadow-md); overflow:hidden; }
.card-head { padding:16px 22px; border-bottom:1px solid var(--border); background:var(--surface-secondary); display:flex; align-items:center; gap:9px; }
.card-head-dot { width:8px; height:8px; border-radius:50%; background:var(--warning); }
.card-head-label { font-size:12.5px; font-weight:700; color:var(--text-secondary); text-transform:uppercase; letter-spacing:0.7px; }
.card-body-p { padding:24px; }
.form-label { display:block; font-size:13px; font-weight:600; color:var(--text-primary); margin-bottom:7px; }
.form-control, .form-select, textarea.form-control {
width:100%; padding:10px 14px; font-size:13.5px; font-family:'Plus Jakarta Sans',sans-serif;
color:var(--text-primary); background:var(--surface); border:1px solid var(--border);
border-radius:var(--radius-md); outline:none; transition:border-color 0.2s, box-shadow 0.2s;
}
.form-control:focus, .form-select:focus, textarea.form-control:focus { border-color:var(--primary); box-shadow:var(--shadow-focus); }
.form-select { appearance:none; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 12px center; background-size:16px; padding-right:38px; cursor:pointer; }
textarea.form-control { resize:vertical; min-height:90px; }
.mb-3 { margin-bottom:16px; }
.file-link { display:inline-flex; align-items:center; gap:7px; color:var(--primary); font-size:13px; font-weight:600; text-decoration:none; padding:7px 14px; background:var(--primary-light); border:1px solid var(--primary-border); border-radius:var(--radius-sm); margin-top:4px; }
.file-link:hover { background:#dbeafe; text-decoration:none; color:var(--primary); }
.file-link svg { width:14px; height:14px; }
.file-hint { font-size:12px; color:var(--text-muted); margin-top:5px; }
.alert-danger-c { background:var(--danger-light); border:1px solid var(--danger-border); border-radius:var(--radius-md); padding:14px 18px; margin-bottom:20px; font-size:13px; color:var(--danger); }
.card-footer-p { display:flex; align-items:center; justify-content:space-between; padding:18px 24px; border-top:1px solid var(--border); background:var(--surface-secondary); flex-wrap:wrap; gap:10px; }
.btn-cancel { display:inline-flex; align-items:center; gap:7px; padding:10px 20px; font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; background:var(--surface); color:var(--text-secondary); border:1px solid var(--border); border-radius:var(--radius-md); text-decoration:none; cursor:pointer; transition:all 0.15s; }
.btn-cancel:hover { background:var(--surface-secondary); color:var(--text-primary); text-decoration:none; }
.btn-cancel svg { width:15px; height:15px; }
.btn-update { display:inline-flex; align-items:center; gap:8px; padding:10px 26px; font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; background:var(--warning); color:#fff; border:none; border-radius:var(--radius-md); cursor:pointer; transition:all 0.15s; box-shadow:0 2px 8px rgba(217,119,6,0.3); }
.btn-update:hover { background:#b45309; transform:translateY(-1px); }
.btn-update svg { width:15px; height:15px; }
<form action="{{ route('guru.tugas.update', $tugas->id) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
{{-- ERROR --}}
@if ($errors->any())
<div class="alert alert-danger">
@foreach ($errors->all() as $error)
<div>{{ $error }}</div>
@endforeach
.edit-tugas-page{
width:100%;
padding:24px 28px 40px;
}
.form-grid{
display:grid;
grid-template-columns:
repeat(auto-fit,minmax(320px,1fr));
gap:20px;
}
</style>
<div class="edit-tugas-page">
<div class="form-wrapper">
<a href="{{ route('guru.tugas.index') }}" class="back-nav">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/></svg>
Kembali ke Daftar Tugas
</a>
<div class="page-header">
<div class="page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 012.828 2.828L11.828 15.828a2 2 0 01-1.414.586H8v-2.414a2 2 0 01.586-1.414z"/></svg>
</div>
<div>
<h2 class="page-title">Edit Tugas</h2>
<p class="page-subtitle">Perbarui informasi tugas yang sudah dibuat</p>
</div>
</div>
<div class="form-card">
<div class="card-head"><div class="card-head-dot"></div><span class="card-head-label">Perbarui Data Tugas</span></div>
<div class="card-body-p">
@if ($errors->any())
<div class="alert-danger-c">
@foreach ($errors->all() as $error)<div>{{ $error }}</div>@endforeach
</div>
@endif
{{-- JUDUL --}}
<div class="mb-3">
<label>Judul</label>
<input type="text" name="judul" class="form-control" value="{{ $tugas->judul }}" required>
</div>
{{-- KELAS --}}
<div class="mb-3">
<label>Kelas</label>
<select name="kelas_id" class="form-control" required>
@foreach($kelas as $k)
<option value="{{ $k->id }}"
{{ $tugas->kelas_id == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
</div>
{{-- DESKRIPSI --}}
<div class="mb-3">
<label>Deskripsi</label>
<textarea name="deskripsi" class="form-control">{{ $tugas->deskripsi }}</textarea>
</div>
{{-- FILE LAMA --}}
<div class="mb-3">
<label>File Lama</label><br>
@if($tugas->file)
<a href="{{ asset('storage/' . $tugas->file) }}" target="_blank">
Lihat / Download File
</a>
@else
<span class="text-muted">Tidak ada file</span>
@endif
<form action="{{ route('guru.tugas.update', $tugas->id) }}" method="POST" enctype="multipart/form-data" id="formEdit">
@csrf
@method('PUT')
<div class="mb-3">
<label class="form-label">Judul Tugas <span style="color:var(--danger)">*</span></label>
<input type="text" name="judul" class="form-control" value="{{ $tugas->judul }}" required>
</div>
<div class="mb-3">
<label class="form-label">Kelas <span style="color:var(--danger)">*</span></label>
<select name="kelas_id" class="form-select" required>
@foreach($kelas as $k)
<option value="{{ $k->id }}" {{ $tugas->kelas_id == $k->id ? 'selected' : '' }}>{{ $k->nama_kelas }}</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label class="form-label">Deskripsi</label>
<textarea name="deskripsi" class="form-control">{{ $tugas->deskripsi }}</textarea>
</div>
<div class="mb-3">
<label class="form-label">File Saat Ini</label><br>
@if($tugas->file)
<a href="{{ asset('storage/' . $tugas->file) }}" target="_blank" class="file-link">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
Lihat / Download File
</a>
@else
<span style="font-size:13px;color:var(--text-muted);">Tidak ada file terlampir</span>
@endif
</div>
<div class="mb-3">
<label class="form-label">Ganti File <span style="font-weight:400;color:var(--text-muted);">(opsional)</span></label>
<input type="file" name="file" class="form-control" accept=".pdf,.doc,.docx,.ppt,.pptx">
<p class="file-hint">Biarkan kosong jika tidak ingin mengganti file</p>
</div>
<div class="mb-3">
<label class="form-label">Deadline <span style="color:var(--danger)">*</span></label>
<input type="datetime-local" name="deadline" class="form-control"
value="{{ \Carbon\Carbon::parse($tugas->deadline)->format('Y-m-d\TH:i') }}">
</div>
</form>
</div>
{{-- GANTI FILE --}}
<div class="mb-3">
<label>Ganti File (opsional)</label>
<input type="file" name="file" class="form-control"
accept=".pdf,.doc,.docx,.ppt,.pptx">
<div class="card-footer-p">
<a href="{{ route('guru.tugas.index') }}" class="btn-cancel">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
Batalkan
</a>
<button type="submit" form="formEdit" class="btn-update">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
Simpan Perubahan
</button>
</div>
{{-- DEADLINE (DATETIME) --}}
<div class="mb-3">
<label>Deadline</label>
<input type="datetime-local" name="deadline"
value="{{ \Carbon\Carbon::parse($tugas->deadline)->format('Y-m-d\TH:i') }}">
</div>
{{-- BUTTON --}}
<button type="submit" class="btn btn-primary">Update</button>
<a href="{{ route('guru.tugas.index') }}" class="btn btn-secondary">Kembali</a>
</form>
</div>
</div>
</div>
@endsection

View File

@ -1,117 +1,381 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<!-- HEADER -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 class="mb-1 fw-bold">Manajemen Tugas</h4>
<small class="text-muted">Kelola dan pantau tugas yang diberikan kepada siswa</small>
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');
<a href="{{ route('guru.tugas.create') }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Tambah Tugas
</a>
</div>
:root {
--primary:#2563eb; --primary-light:#eff6ff; --primary-border:#bfdbfe; --primary-dark:#1d4ed8;
--success:#16a34a; --success-light:#f0fdf4; --success-border:#bbf7d0;
--warning:#d97706; --warning-light:#fffbeb; --warning-border:#fde68a;
--danger:#dc2626; --danger-light:#fef2f2; --danger-border:#fecaca;
--info:#0284c7; --info-light:#f0f9ff; --info-border:#bae6fd;
--text-primary:#0f172a; --text-secondary:#475569; --text-muted:#94a3b8;
--surface:#ffffff; --surface-secondary:#f8fafc; --border:#e2e8f0;
--shadow-md:0 4px 20px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--radius-sm:6px; --radius-md:10px; --radius-lg:14px; --radius-xl:18px;
}
* { font-family:'Plus Jakarta Sans',sans-serif; box-sizing:border-box; }
.page-wrapper { animation:fadeUp 0.4s ease both; }
@keyframes fadeUp { from{opacity:0;transform:translateY(12px)} to{opacity:1;transform:translateY(0)} }
<!-- CARD -->
<div class="card shadow-sm border-0">
<div class="card-body">
/* HEADER */
.page-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:28px; gap:16px; flex-wrap:wrap; }
.page-header-left { display:flex; align-items:center; gap:14px; }
.page-icon { width:50px; height:50px; border-radius:var(--radius-lg); background:var(--primary-light); border:1px solid var(--primary-border); display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.page-icon svg { width:24px; height:24px; color:var(--primary); }
.page-title { font-size:22px; font-weight:700; color:var(--text-primary); margin:0 0 2px; letter-spacing:-0.3px; }
.page-subtitle { font-size:13px; color:var(--text-secondary); margin:0; }
<div class="table-responsive">
<table class="table table-hover align-middle">
.btn-add { display:inline-flex; align-items:center; gap:7px; padding:10px 20px; font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; background:var(--primary); color:#fff; border:none; border-radius:var(--radius-md); text-decoration:none; cursor:pointer; transition:all 0.15s; box-shadow:0 2px 8px rgba(37,99,235,0.25); }
.btn-add:hover { background:var(--primary-dark); color:#fff; text-decoration:none; transform:translateY(-1px); }
.btn-add svg { width:15px; height:15px; }
<thead class="table-light">
<tr>
<th>Judul</th>
<th>Kelas</th>
<th>Mata Pelajaran</th>
<th>Pengumpulan</th> {{-- 🔥 baru --}}
<th>Status</th> {{-- 🔥 baru --}}
<th>File</th>
<th>Deadline</th>
<th class="text-center">Aksi</th>
</tr>
</thead>
/* CARD */
.section-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-xl); box-shadow:var(--shadow-md); overflow:hidden; }
.card-toolbar { display:flex; align-items:center; justify-content:space-between; padding:16px 22px; border-bottom:1px solid var(--border); background:var(--surface-secondary); }
.toolbar-title { font-size:13.5px; font-weight:600; color:var(--text-primary); }
.toolbar-badge { background:var(--primary-light); color:var(--primary); border:1px solid var(--primary-border); border-radius:999px; font-size:11.5px; font-weight:600; padding:2px 10px; margin-left:8px; }
<tbody>
@forelse($tugas as $t)
<tr>
<td class="fw-semibold">{{ $t->judul }}</td>
/* TABLE */
.table-responsive { overflow-x:auto; }
table.tugas-table { width:100%; border-collapse:collapse; min-width:780px; }
.tugas-table thead tr { background:var(--surface-secondary); border-bottom:1px solid var(--border); }
.tugas-table thead th { padding:12px 16px; font-size:11px; font-weight:700; color:var(--text-muted); text-transform:uppercase; letter-spacing:0.7px; text-align:left; white-space:nowrap; }
.tugas-table thead th.center { text-align:center; }
.tugas-table tbody tr { border-bottom:1px solid var(--border); transition:background 0.15s; }
.tugas-table tbody tr:last-child { border-bottom:none; }
.tugas-table tbody tr:hover { background:#f8faff; }
.tugas-table tbody td { padding:15px 16px; font-size:13.5px; color:var(--text-primary); vertical-align:middle; }
.tugas-table tbody td.center { text-align:center; }
<td>{{ $t->kelas->nama_kelas ?? '-' }}</td>
.judul-cell { font-weight:600; color:var(--text-primary); }
<td>{{ $t->mapel->nama_mapel ?? '-' }}</td>
.badge-kelas { background:var(--primary-light); color:var(--primary); border:1px solid var(--primary-border); border-radius:999px; font-size:11.5px; font-weight:600; padding:3px 11px; display:inline-flex; }
.badge-mapel { background:var(--surface-secondary); color:var(--text-secondary); border:1px solid var(--border); border-radius:999px; font-size:11.5px; font-weight:600; padding:3px 11px; display:inline-flex; }
{{-- 🔥 JUMLAH PENGUMPULAN --}}
<td>
<span class="badge bg-primary">
{{ $t->pengumpulan_count }} / {{ $t->total_siswa }}
</span>
</td>
.badge-kumpul { background:var(--primary-light); color:var(--primary); border:1px solid var(--primary-border); border-radius:var(--radius-sm); font-size:12px; font-weight:700; padding:3px 10px; display:inline-flex; align-items:center; }
.badge-aktif { background:var(--success-light); color:var(--success); border:1px solid var(--success-border); border-radius:999px; font-size:12px; font-weight:600; padding:4px 10px; display:inline-flex; align-items:center; gap:4px; }
.badge-aktif::before { content:''; width:6px; height:6px; border-radius:50%; background:var(--success); }
.badge-lewat { background:var(--danger-light); color:var(--danger); border:1px solid var(--danger-border); border-radius:999px; font-size:12px; font-weight:600; padding:4px 10px; display:inline-flex; align-items:center; gap:4px; }
.badge-lewat::before { content:''; width:6px; height:6px; border-radius:50%; background:var(--danger); }
{{-- 🔥 STATUS DEADLINE --}}
<td>
@if(now()->gt($t->deadline))
<span class="badge bg-danger">Lewat</span>
@else
<span class="badge bg-success">Aktif</span>
@endif
</td>
.deadline-text { font-size:12.5px; color:var(--text-secondary); white-space:nowrap; }
{{-- FILE --}}
<td>
@if($t->file)
<a href="{{ route('guru.tugas.download', $t->id) }}" class="btn btn-sm btn-success">
Download
</a>
@else
-
@endif
</td>
/* ACTION GROUP */
.action-group { display:flex; align-items:center; justify-content:center; gap:5px; flex-wrap:wrap; }
.btn-act { display:inline-flex; align-items:center; gap:4px; padding:5px 11px; font-size:12px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; border-radius:var(--radius-sm); text-decoration:none; border:1px solid transparent; cursor:pointer; transition:all 0.15s; white-space:nowrap; }
.btn-act svg { width:12px; height:12px; }
.btn-pengumpulan { background:var(--primary-light); color:var(--primary); border-color:var(--primary-border); }
.btn-pengumpulan:hover { background:#dbeafe; color:var(--primary); text-decoration:none; }
.btn-detail { background:var(--info-light); color:var(--info); border-color:var(--info-border); }
.btn-detail:hover { background:#e0f2fe; color:var(--info); text-decoration:none; }
.btn-edit-act { background:var(--warning-light); color:var(--warning); border-color:var(--warning-border); }
.btn-edit-act:hover { background:#fef3c7; color:var(--warning); text-decoration:none; }
{{-- DEADLINE --}}
<td>
<span class="badge bg-light text-dark">
{{ \Carbon\Carbon::parse($t->deadline)->format('d M Y H:i') }}
</span>
</td>
/* EMPTY */
.empty-state { padding:64px 24px; text-align:center; }
.empty-icon { width:60px; height:60px; background:var(--surface-secondary); border-radius:50%; border:1px solid var(--border); display:flex; align-items:center; justify-content:center; margin:0 auto 14px; }
.empty-icon svg { width:26px; height:26px; color:var(--text-muted); }
.empty-title { font-size:15px; font-weight:600; color:var(--text-primary); margin-bottom:4px; }
.empty-desc { font-size:13px; color:var(--text-muted); margin-bottom:20px; }
.btn-empty { display:inline-flex; align-items:center; gap:7px; padding:10px 20px; font-size:13.5px; font-weight:600; font-family:'Plus Jakarta Sans',sans-serif; background:var(--primary); color:#fff; border:none; border-radius:var(--radius-md); text-decoration:none; }
.btn-empty:hover { background:var(--primary-dark); color:#fff; text-decoration:none; }
{{-- AKSI --}}
<td class="text-center">
.table-footer { display:flex; align-items:center; justify-content:space-between; padding:13px 22px; border-top:1px solid var(--border); background:var(--surface-secondary); font-size:12.5px; color:var(--text-muted); }
<a href="{{ route('guru.tugas.pengumpulan', $t->id) }}"
class="btn btn-sm btn-primary mb-1">
Pengumpulan
</a>
/* RATA-RATA SECTION */
.btn-hitung-rata {
display: inline-flex; align-items: center; gap: 8px;
padding: 10px 22px; border-radius: var(--radius-md); border: none;
background: var(--success); color: #fff;
font-size: 13.5px; font-weight: 600;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .18s;
box-shadow: 0 2px 8px rgba(22,163,74,.25);
margin-top: 24px;
}
.btn-hitung-rata:hover { background: #15803d; transform: translateY(-1px); }
.btn-hitung-rata svg { width: 15px; height: 15px; }
<a href="{{ route('guru.tugas.show', $t->id) }}"
class="btn btn-sm btn-info mb-1">
Detail
</a>
.rata-section { margin-top: 20px; animation: fadeUp 0.3s ease both; }
<a href="{{ route('guru.tugas.edit', $t->id) }}"
class="btn btn-sm btn-warning">
Edit
</a>
.rata-card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius-xl); box-shadow: var(--shadow-md); overflow: hidden;
}
.rata-card-toolbar {
display: flex; align-items: center; justify-content: space-between;
padding: 16px 22px; border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
}
.rata-toolbar-left { display: flex; align-items: center; gap: 10px; }
.rata-toolbar-icon {
width: 34px; height: 34px; border-radius: 9px;
background: var(--success); color: #fff;
display: flex; align-items: center; justify-content: center;
}
.rata-toolbar-icon svg { width: 16px; height: 16px; }
.rata-toolbar-title { font-size: 13.5px; font-weight: 700; color: #166534; margin: 0; }
.rata-toolbar-sub { font-size: 11.5px; color: #15803d; margin: 2px 0 0; }
</td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center text-muted">
Belum ada tugas
</td>
</tr>
@endforelse
</tbody>
table.rata-table { width: 100%; border-collapse: collapse; min-width: 600px; }
.rata-table thead tr {
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
border-bottom: 2px solid #bbf7d0;
}
.rata-table thead th {
padding: 12px 16px; font-size: 11px; font-weight: 700;
color: #166534; text-transform: uppercase; letter-spacing: .7px;
white-space: nowrap;
}
.rata-table thead th.center { text-align: center; }
.rata-table tbody tr { border-bottom: 1px solid var(--border); transition: background .15s; }
.rata-table tbody tr:last-child { border-bottom: none; }
.rata-table tbody tr:hover { background: #f0fdf4; }
.rata-table tbody td { padding: 14px 16px; font-size: 13.5px; color: var(--text-primary); vertical-align: middle; }
.rata-table tbody td.center { text-align: center; }
</table>
.rata-avatar-row { display: flex; align-items: center; gap: 10px; }
.rata-avatar {
width: 36px; height: 36px; border-radius: 50%;
background: var(--success-light); border: 2px solid #bbf7d0;
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 800; color: var(--success); flex-shrink: 0;
}
.rata-nama { font-weight: 700; color: var(--text-primary); font-size: 13.5px; }
.chip-nilai {
display: inline-flex; padding: 5px 16px;
border-radius: 999px; font-size: 14px; font-weight: 800;
}
.chip-nilai.a { background: #dcfce7; color: #166534; }
.chip-nilai.b { background: var(--primary-light); color: var(--primary); }
.chip-nilai.c { background: var(--warning-light); color: var(--warning); }
.chip-nilai.d { background: var(--danger-light); color: var(--danger); }
.chip-predikat {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 12px; border-radius: 999px; font-size: 11.5px; font-weight: 700;
}
.chip-predikat.a { background: #dcfce7; color: #166534; }
.chip-predikat.b { background: var(--primary-light); color: var(--primary); }
.chip-predikat.c { background: var(--warning-light); color: var(--warning); }
.chip-predikat.d { background: var(--danger-light); color: var(--danger); }
.rata-footer {
display: flex; align-items: center; justify-content: space-between;
padding: 13px 22px; border-top: 1px solid var(--border);
background: var(--surface-secondary); font-size: 13px; color: var(--text-muted);
}
.rata-footer-nilai { font-size: 16px; font-weight: 800; color: var(--text-primary); }
</style>
<div class="container-fluid px-4 py-2 page-wrapper">
<div class="page-header">
<div class="page-header-left">
<div class="page-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
</div>
<div>
<h4 class="page-title">Manajemen Tugas</h4>
<p class="page-subtitle">Kelola dan pantau tugas yang diberikan kepada siswa</p>
</div>
</div>
<a href="{{ route('guru.tugas.create') }}" class="btn-add">
<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>
Tambah Tugas
</a>
</div>
<div class="section-card">
<div class="card-toolbar">
<span class="toolbar-title">Daftar Tugas<span class="toolbar-badge">{{ $tugas->count() }} tugas</span></span>
</div>
<div class="table-responsive">
<table class="tugas-table">
<thead>
<tr>
<th>Judul Tugas</th>
<th>Kelas</th>
<th>Mata Pelajaran</th>
<th class="center">Pengumpulan</th>
<th class="center">Status</th>
<th class="center">File</th>
<th>Deadline</th>
<th class="center">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($tugas as $t)
<tr>
<td><span class="judul-cell">{{ $t->judul }}</span></td>
<td><span class="badge-kelas">{{ $t->kelas->nama_kelas ?? '—' }}</span></td>
<td><span class="badge-mapel">{{ $t->mapel->nama_mapel ?? '—' }}</span></td>
<td class="center">
<span class="badge-kumpul">{{ $t->pengumpulan_count }} / {{ $t->total_siswa }}</span>
</td>
<td class="center">
@if(now()->gt($t->deadline))
<span class="badge-lewat">Lewat</span>
@else
<span class="badge-aktif">Aktif</span>
@endif
</td>
<td class="center">
@if($t->file)
<a href="{{ route('guru.tugas.download', $t->id) }}" class="btn-act btn-detail">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
Unduh
</a>
@else
<span style="font-size:12px;color:var(--text-muted);">—</span>
@endif
</td>
<td><span class="deadline-text">{{ \Carbon\Carbon::parse($t->deadline)->format('d M Y H:i') }}</span></td>
<td>
<div class="action-group">
<a href="{{ route('guru.tugas.pengumpulan', $t->id) }}" class="btn-act btn-pengumpulan">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
Kumpulan
</a>
<a href="{{ route('guru.tugas.show', $t->id) }}" class="btn-act btn-detail">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
Detail
</a>
<a href="{{ route('guru.tugas.edit', $t->id) }}" class="btn-act btn-edit-act">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 012.828 2.828L11.828 15.828a2 2 0 01-1.414.586H8v-2.414a2 2 0 01.586-1.414z"/></svg>
Edit
</a>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="8">
<div class="empty-state">
<div class="empty-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
</div>
<div class="empty-title">Belum ada tugas</div>
<div class="empty-desc">Buat tugas pertama untuk mulai memantau pengumpulan siswa.</div>
<a href="{{ route('guru.tugas.create') }}" class="btn-empty">Buat Tugas Pertama</a>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($tugas->count())
<div class="table-footer">
<span>{{ $tugas->count() }} tugas ditemukan</span>
<span>{{ now()->format('d M Y') }}</span>
</div>
@endif
</div>
{{-- TOMBOL HITUNG --}}
<div style="text-align:right">
<button class="btn-hitung-rata" onclick="tampilRataRata()">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
Hitung Rata-Rata Nilai Siswa
</button>
</div>
{{-- TABEL RATA-RATA --}}
<div id="rata-section" class="rata-section" style="display:none">
<div class="rata-card">
<div class="rata-card-toolbar">
<div class="rata-toolbar-left">
<div class="rata-toolbar-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<div>
<p class="rata-toolbar-title">Rekap Rata-Rata Nilai per Siswa</p>
<p class="rata-toolbar-sub">Dihitung dari semua tugas yang sudah dinilai guru</p>
</div>
</div>
</div>
<div class="table-responsive">
<table class="rata-table">
<thead>
<tr>
<th style="width:36px">#</th>
<th>Nama Siswa</th>
<th class="center">Jumlah Tugas Dinilai</th>
<th class="center">Rata-Rata Nilai</th>
<th class="center">Predikat</th>
</tr>
</thead>
<tbody>
@forelse($rekapNilai as $i => $r)
<tr>
<td style="color:var(--text-muted);font-size:12px">{{ $i + 1 }}</td>
<td>
<div class="rata-avatar-row">
<div class="rata-avatar">{{ $r['inisial'] }}</div>
<span class="rata-nama">{{ $r['nama'] }}</span>
</div>
</td>
<td class="center" style="color:var(--text-secondary)">
{{ $r['jumlah_tugas'] }} tugas
</td>
<td class="center">
@php
$n = $r['rata_rata'];
$cls = $n >= 88 ? 'a' : ($n >= 75 ? 'b' : ($n >= 60 ? 'c' : 'd'));
@endphp
<span class="chip-nilai {{ $cls }}">{{ $n }}</span>
</td>
<td class="center">
@php
$predikat = $n >= 88 ? 'A — Sangat Baik'
: ($n >= 75 ? 'B — Baik'
: ($n >= 60 ? 'C — Cukup' : 'D — Kurang'));
@endphp
<span class="chip-predikat {{ $cls }}">{{ $predikat }}</span>
</td>
</tr>
@empty
<tr>
<td colspan="5" style="text-align:center;padding:40px;color:var(--text-muted);font-size:13px">
Belum ada siswa yang mendapatkan nilai
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($rekapNilai->count())
<div class="rata-footer">
<span>{{ $rekapNilai->count() }} siswa terhitung</span>
<div style="display:flex;align-items:center;gap:8px">
<span>Rata-rata kelas:</span>
<span class="rata-footer-nilai">
{{ round($rekapNilai->avg('rata_rata'), 1) }} / 100
</span>
</div>
</div>
@endif
</div>
</div>
<script>
function tampilRataRata() {
const sec = document.getElementById('rata-section');
sec.style.display = 'block';
sec.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
</script>
</div>
@endsection

View File

@ -1,78 +1,533 @@
@extends('layouts.app')
@section('content')
<div class="container">
<h4>Pengumpulan Tugas: {{ $tugas->judul }}</h4>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
<table class="table table-bordered">
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--primary-light: #EEF2FF;
--success: #059669;
--success-light: #ECFDF5;
--warning: #D97706;
--warning-light: #FFFBEB;
--danger: #DC2626;
--danger-light: #FEF2F2;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 16px;
}
<thead>
<tr>
<th>Nama Siswa</th>
<th>File</th>
<th>Waktu Kumpul</th>
<th>Status</th>
<th>Nilai</th>
<th>Komentar</th>
<th>Aksi</th>
</tr>
</thead>
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
<tbody>
@foreach($tugas->pengumpulan as $p)
<tr>
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
<td>{{ $p->siswa->nama ?? '-' }}</td>
/* ── TOPBAR ── */
.sw-topbar { margin-bottom: 24px; }
.sw-topbar h3 { font-size: 21px; font-weight: 800; color: var(--text-dark); margin: 0 0 4px; letter-spacing: -.3px; }
.sw-topbar p { font-size: 13px; color: var(--text-soft); margin: 0; }
<td>
<a href="{{ asset('storage/' . $p->file) }}" target="_blank">
Download
</a>
</td>
/* ── HEADER CARD ── */
.sw-header-card {
background: linear-gradient(135deg, #1E3A6E 0%, #3730A3 55%, #4F46E5 100%);
border-radius: var(--radius-lg);
padding: 24px 28px;
display: flex; align-items: center; gap: 16px;
margin-bottom: 24px;
position: relative; overflow: hidden;
}
.sw-header-card::before {
content: ''; position: absolute;
right: -40px; top: -40px;
width: 180px; height: 180px; border-radius: 50%;
border: 1px solid rgba(255,255,255,.08);
}
.sw-header-icon {
width: 52px; height: 52px; border-radius: 14px;
background: rgba(255,255,255,.15);
border: 1px solid rgba(255,255,255,.2);
display: flex; align-items: center; justify-content: center;
color: #fff; flex-shrink: 0;
}
.sw-header-title { font-size: 19px; font-weight: 800; color: #fff; margin: 0 0 6px; line-height: 1.3; }
.sw-header-chips { display: flex; gap: 8px; flex-wrap: wrap; }
.sw-header-chip {
display: inline-flex; align-items: center; gap: 5px;
background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.2);
border-radius: 99px; padding: 4px 12px;
font-size: 11.5px; font-weight: 600; color: rgba(255,255,255,.9);
}
<td>
{{ \Carbon\Carbon::parse($p->created_at)->format('d M Y H:i') }}
</td>
/* ── SUMMARY STATS ── */
.sw-stats { display: flex; gap: 14px; margin-bottom: 24px; flex-wrap: wrap; }
.sw-stat {
flex: 1; min-width: 130px;
background: var(--surface);
border-radius: var(--radius-md);
border: 1px solid var(--border);
padding: 16px 18px;
display: flex; align-items: center; gap: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,.04);
}
.sw-stat-icon {
width: 40px; height: 40px; border-radius: 10px;
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.sw-stat-icon.primary { background: var(--primary-light); color: var(--primary); }
.sw-stat-icon.success { background: var(--success-light); color: var(--success); }
.sw-stat-icon.warning { background: var(--warning-light); color: var(--warning); }
.sw-stat-val { font-size: 22px; font-weight: 800; color: var(--text-dark); line-height: 1; }
.sw-stat-lbl { font-size: 11.5px; font-weight: 600; color: var(--text-soft); margin-top: 3px; }
<td>
@if($p->status == 'telat')
<span class="badge bg-warning text-dark">Telat</span>
@else
<span class="badge bg-success">Tepat Waktu</span>
@endif
</td>
/* ── MAIN CARD ── */
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.05);
overflow: hidden;
}
.sw-card-hd {
padding: 16px 22px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
display: flex; align-items: center; justify-content: space-between;
}
.sw-card-hd-left { display: flex; align-items: center; gap: 10px; }
.sw-card-hd-icon {
width: 34px; height: 34px; border-radius: 9px;
background: var(--primary); color: #fff;
display: flex; align-items: center; justify-content: center;
}
.sw-card-hd h6 { font-size: 14px; font-weight: 700; color: #3730A3; margin: 0; }
.sw-card-hd p { font-size: 11.5px; color: #6D6AA4; margin: 2px 0 0; }
<!-- 🔥 FORM NILAI LANGSUNG -->
<form method="POST" action="{{ route('guru.tugas.nilai', $p->id) }}">
@csrf
/* ── TABLE ── */
.sw-table-wrap { overflow-x: auto; }
table.sw-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
<td>
<input type="number" name="nilai"
value="{{ $p->nilai }}"
class="form-control"
style="width:80px;">
</td>
table.sw-table thead tr {
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
border-bottom: 2px solid #DDD6FE;
}
table.sw-table thead th {
padding: 12px 16px; font-weight: 700;
font-size: 11.5px; 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: 14px 16px; border: none; vertical-align: middle; color: var(--text-mid); }
<td>
<input type="text" name="komentar"
value="{{ $p->komentar }}"
class="form-control">
</td>
/* ── AVATAR ── */
.sw-avatar-row { display: flex; align-items: center; gap: 10px; }
.sw-avatar {
width: 36px; height: 36px; border-radius: 50%;
background: var(--primary-light); border: 2px solid #C7D2FE;
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 800; color: var(--primary);
flex-shrink: 0;
}
.sw-avatar-name { font-weight: 700; color: var(--text-dark); font-size: 13.5px; }
<td>
<button class="btn btn-sm btn-success">
Simpan
</button>
</td>
/* ── DOWNLOAD LINK ── */
.sw-dl-btn {
display: inline-flex; align-items: center; gap: 6px;
background: var(--primary-light); border: 1px solid #C7D2FE;
border-radius: 8px; padding: 6px 12px;
font-size: 12px; font-weight: 600; color: var(--primary);
text-decoration: none; transition: all .15s;
white-space: nowrap;
}
.sw-dl-btn:hover { background: var(--primary); color: #fff; }
</form>
/* ── TIME ── */
.sw-time { font-size: 12.5px; color: var(--text-soft); white-space: nowrap; }
</tr>
@endforeach
</tbody>
/* ── BADGES ── */
.sw-badge {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 10px; border-radius: 99px;
font-size: 11.5px; font-weight: 600;
}
.sw-badge-success { background: var(--success-light); color: var(--success); }
.sw-badge-warning { background: var(--warning-light); color: var(--warning); }
.sw-badge span { width: 6px; height: 6px; border-radius: 50%; }
.sw-badge-success span { background: var(--success); }
.sw-badge-warning span { background: var(--warning); }
</table>
/* ── INLINE FORM INPUTS ── */
.sw-nilai-input {
width: 72px; padding: 7px 10px;
border: 1.5px solid var(--border);
border-radius: 8px; font-size: 14px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark); background: var(--neutral-light);
outline: none; text-align: center;
transition: all .15s;
}
.sw-nilai-input:focus { border-color: var(--primary); background: #fff; box-shadow: 0 0 0 3px rgba(79,70,229,.1); }
.sw-komentar-input {
width: 100%; min-width: 160px; padding: 7px 12px;
border: 1.5px solid var(--border);
border-radius: 8px; font-size: 13px;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark); background: var(--neutral-light);
outline: none; transition: all .15s;
}
.sw-komentar-input:focus { border-color: var(--primary); background: #fff; box-shadow: 0 0 0 3px rgba(79,70,229,.1); }
/* ── SAVE BUTTON ── */
.sw-save-btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 7px 14px; border-radius: 8px; border: none;
background: var(--success); color: #fff;
font-size: 12.5px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .18s;
white-space: nowrap;
}
.sw-save-btn:hover { background: #047857; transform: translateY(-1px); box-shadow: 0 4px 10px rgba(5,150,105,.3); }
/* ── EMPTY ── */
.sw-empty { padding: 50px 20px; text-align: center; color: var(--text-soft); }
.sw-empty-icon { font-size: 36px; opacity: .3; margin-bottom: 10px; }
.sw-empty p { font-size: 14px; margin: 0; }
@media (max-width: 768px) { .sw-page { padding: 14px; } }
.btn-hitung-rata {
display: inline-flex; align-items: center; gap: 8px;
padding: 10px 22px; border-radius: 10px; border: none;
background: #059669; color: #fff;
font-size: 13.5px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .18s;
}
.btn-hitung-rata:hover { background: #047857; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(5,150,105,.3); }
.badge-predikat {
display: inline-flex; align-items: center;
padding: 4px 12px; border-radius: 99px;
font-size: 11.5px; font-weight: 700;
}
.badge-predikat.a { background: #DCFCE7; color: #166534; }
.badge-predikat.b { background: #EEF2FF; color: #3730A3; }
.badge-predikat.c { background: #FFFBEB; color: #92400E; }
.badge-predikat.d { background: #FEF2F2; color: #991B1B; }
.nilai-chip {
display: inline-flex; padding: 5px 16px;
border-radius: 99px; font-size: 15px; font-weight: 800;
}
.nilai-chip.a { background: #DCFCE7; color: #166534; }
.nilai-chip.b { background: #EEF2FF; color: #3730A3; }
.nilai-chip.c { background: #FFFBEB; color: #92400E; }
.nilai-chip.d { background: #FEF2F2; color: #991B1B; }
</style>
<div class="sw-page">
{{-- TOPBAR --}}
<div class="sw-topbar">
<h3>Pengumpulan Tugas</h3>
<p>Nilai dan berikan komentar untuk setiap siswa</p>
</div>
{{-- HEADER CARD --}}
<div class="sw-header-card">
<div class="sw-header-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" 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"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
</div>
<div style="position:relative;z-index:1;">
<div class="sw-header-title">{{ $tugas->judul }}</div>
<div class="sw-header-chips">
<div class="sw-header-chip">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
{{ $tugas->pengumpulan->count() }} Pengumpulan
</div>
<div class="sw-header-chip">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
{{ $tugas->pengumpulan->whereNotNull('nilai')->count() }} Sudah Dinilai
</div>
</div>
</div>
</div>
{{-- STATS --}}
<div class="sw-stats">
<div class="sw-stat">
<div class="sw-stat-icon primary">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
</div>
<div>
<div class="sw-stat-val">{{ $tugas->pengumpulan->count() }}</div>
<div class="sw-stat-lbl">Total Kumpul</div>
</div>
</div>
<div class="sw-stat">
<div class="sw-stat-icon success">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polyline points="20 6 9 17 4 12"/></svg>
</div>
<div>
<div class="sw-stat-val">{{ $tugas->pengumpulan->where('status', '!=', 'telat')->count() }}</div>
<div class="sw-stat-lbl">Tepat Waktu</div>
</div>
</div>
<div class="sw-stat">
<div class="sw-stat-icon warning">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
</div>
<div>
<div class="sw-stat-val">{{ $tugas->pengumpulan->where('status', 'telat')->count() }}</div>
<div class="sw-stat-lbl">Telat</div>
</div>
</div>
<div class="sw-stat">
<div class="sw-stat-icon primary">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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-val">{{ $tugas->pengumpulan->whereNotNull('nilai')->count() }}</div>
<div class="sw-stat-lbl">Sudah Dinilai</div>
</div>
</div>
</div>
{{-- TABLE CARD --}}
<div class="sw-card">
<div class="sw-card-hd">
<div class="sw-card-hd-left">
<div class="sw-card-hd-icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
</div>
<div>
<h6>Daftar Pengumpulan</h6>
<p>Isi nilai dan komentar lalu klik Simpan</p>
</div>
</div>
</div>
<div class="sw-table-wrap">
<table class="sw-table">
<thead>
<tr>
<th style="width:36px">#</th>
<th>Nama Siswa</th>
<th>File</th>
<th>Waktu Kumpul</th>
<th class="text-center">Status</th>
<th class="text-center" style="width:90px">Nilai</th>
<th>Komentar</th>
<th class="text-center">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($tugas->pengumpulan as $p)
<tr>
<td style="color:var(--text-soft);font-size:12px;">{{ $loop->iteration }}</td>
<td>
<div class="sw-avatar-row">
<div class="sw-avatar">
{{ strtoupper(substr($p->siswa->nama ?? 'S', 0, 1)) }}
</div>
<div class="sw-avatar-name">{{ $p->siswa->nama ?? '-' }}</div>
</div>
</td>
<td>
<a href="{{ asset('storage/' . $p->file) }}" target="_blank" class="sw-dl-btn">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Download
</a>
</td>
<td>
<div class="sw-time">
{{ \Carbon\Carbon::parse($p->created_at)->format('d M Y') }}<br>
<span style="font-size:11.5px">{{ \Carbon\Carbon::parse($p->created_at)->format('H:i') }}</span>
</div>
</td>
<td style="text-align:center">
@if($p->status == 'telat')
<span class="sw-badge sw-badge-warning"><span></span>Telat</span>
@else
<span class="sw-badge sw-badge-success"><span></span>Tepat Waktu</span>
@endif
</td>
{{-- INLINE FORM --}}
<form method="POST" action="{{ route('guru.tugas.nilai', $p->id) }}" style="display:contents">
@csrf
<td style="text-align:center">
<input type="number" name="nilai"
value="{{ $p->nilai }}"
class="sw-nilai-input"
min="0" max="100"
placeholder="—">
</td>
<td>
<input type="text" name="komentar"
value="{{ $p->komentar }}"
class="sw-komentar-input"
placeholder="Tambah komentar...">
</td>
<td style="text-align:center">
<button type="submit" class="sw-save-btn">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Simpan
</button>
</td>
</form>
</tr>
@empty
<tr>
<td colspan="8">
<div class="sw-empty">
<div class="sw-empty-icon">📭</div>
<p>Belum ada siswa yang mengumpulkan tugas ini</p>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
{{-- TOMBOL HITUNG RATA-RATA --}}
<!-- <div style="margin-top:20px;text-align:right">
<button onclick="hitungRataRata()" class="btn-hitung-rata">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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>
Hitung Rata-Rata Nilai
</button>
</div> -->
{{-- TABEL RATA-RATA --}}
<div id="rata-section" style="display:none;margin-top:20px">
<div class="sw-card">
<div class="sw-card-hd">
<div class="sw-card-hd-left">
<div class="sw-card-hd-icon" style="background:#059669">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M18 20V10M12 20V4M6 20v-6"/></svg>
</div>
<div>
<h6 style="color:#065F46">Rekap Nilai Rata-Rata Siswa</h6>
<p>Rata-rata dihitung dari semua tugas yang sudah dinilai guru</p>
</div>
</div>
</div>
<div class="sw-table-wrap">
<table class="sw-table">
<thead style="background:linear-gradient(135deg,#F0FDF4,#DCFCE7)">
<tr>
<th style="color:#166534">#</th>
<th style="color:#166534">Nama Siswa</th>
<th style="color:#166534;text-align:center">Tugas Dikumpul</th>
<th style="color:#166534;text-align:center">Tugas Dinilai</th>
<th style="color:#166634;text-align:center">Rata-Rata Nilai</th>
<th style="color:#166534;text-align:center">Predikat</th>
</tr>
</thead>
<tbody id="rata-tbody"></tbody>
</table>
</div>
<div style="display:flex;align-items:center;justify-content:space-between;padding:13px 22px;border-top:1px solid var(--border);background:var(--neutral-light);font-size:13px;color:var(--text-soft)">
<span>Rata-rata keseluruhan kelas</span>
<strong id="rata-kelas" style="font-size:16px;color:var(--text-dark)">—</strong>
</div>
</div>
</div>
</div>
<script>
const pengumpulanData = @json($pengumpulanJson);
function hitungRataRata() {
const tbody = document.getElementById('rata-tbody');
tbody.innerHTML = '';
let totalKelas = 0;
let jumlahDinilai = 0;
pengumpulanData.forEach((p, i) => {
const nilai = p.nilai !== null ? parseFloat(p.nilai) : null;
const avgText = nilai !== null ? Math.round(nilai) : '—';
const cls = nilai !== null ? getKelas(nilai) : '';
if (nilai !== null) {
totalKelas += nilai;
jumlahDinilai++;
}
tbody.innerHTML += `
<tr>
<td style="color:var(--text-soft);font-size:12px">${i+1}</td>
<td>
<div class="sw-avatar-row">
<div class="sw-avatar">${p.inisial}</div>
<span style="font-weight:700">${p.nama}</span>
</div>
</td>
<td style="text-align:center">1 tugas</td>
<td style="text-align:center">
${nilai !== null
? '<span style="color:var(--success);font-weight:600">✔ Sudah dinilai</span>'
: '<span style="color:var(--text-soft)">Belum dinilai</span>'}
</td>
<td style="text-align:center">
${nilai !== null
? `<span class="nilai-chip ${cls}">${avgText}</span>`
: '<span style="color:var(--text-muted)">—</span>'}
</td>
<td style="text-align:center">
${nilai !== null
? `<span class="badge-predikat ${cls}">${getPredikat(nilai)}</span>`
: '—'}
</td>
</tr>`;
});
const rataKelas = jumlahDinilai > 0
? Math.round(totalKelas / jumlahDinilai)
: '—';
document.getElementById('rata-kelas').textContent =
jumlahDinilai > 0 ? rataKelas + ' / 100' : 'Belum ada nilai';
document.getElementById('rata-section').style.display = 'block';
document.getElementById('rata-section').scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function getKelas(n) {
if (n >= 88) return 'a';
if (n >= 75) return 'b';
if (n >= 60) return 'c';
return 'd';
}
function getPredikat(n) {
if (n >= 88) return 'A — Sangat Baik';
if (n >= 75) return 'B — Baik';
if (n >= 60) return 'C — Cukup';
return 'D — Kurang';
}
</script>
@endsection

View File

@ -1,23 +1,359 @@
@extends('layouts.app')
@section('content')
<div class="container">
<h4>Detail Tugas</h4>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
<div class="card">
<div class="card-body">
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--primary-light: #EEF2FF;
--success: #059669;
--success-light: #ECFDF5;
--warning: #D97706;
--warning-light: #FFFBEB;
--danger: #DC2626;
--danger-light: #FEF2F2;
--info: #0284C7;
--info-light: #F0F9FF;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 16px;
}
<p><b>Judul:</b> {{ $tugas->judul }}</p>
<p><b>Kelas:</b> {{ $tugas->kelas->nama_kelas }}</p>
<p><b>Mapel:</b> {{ $tugas->mapel->nama_mapel }}</p>
<p><b>Deskripsi:</b> {{ $tugas->deskripsi }}</p>
<p><b>Deadline:</b> {{ $tugas->deadline }}</p>
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
<a href="{{ route('guru.tugas.index') }}" class="btn btn-secondary">Kembali</a>
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
/* ── TOPBAR ── */
.sw-topbar { margin-bottom: 24px; display: flex; align-items: center; gap: 12px; }
.sw-back-btn {
display: inline-flex; align-items: center; justify-content: center;
width: 36px; height: 36px;
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius-md); color: var(--text-mid);
text-decoration: none; transition: all .15s; flex-shrink: 0;
}
.sw-back-btn:hover { background: var(--primary-light); border-color: #C7D2FE; color: var(--primary); }
.sw-topbar-text h3 { font-size: 21px; font-weight: 800; color: var(--text-dark); margin: 0 0 2px; letter-spacing: -.3px; }
.sw-topbar-text p { font-size: 13px; color: var(--text-soft); margin: 0; }
/* ── LAYOUT ── */
.sw-layout {
display: grid;
grid-template-columns: 1fr 320px;
gap: 22px;
align-items: start;
}
/* ── CARD ── */
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.05);
overflow: hidden;
}
/* ── BANNER ── */
.sw-card-banner {
height: 6px;
background: linear-gradient(90deg, #4F46E5, #7C3AED, #059669);
}
/* ── CARD HEADER ── */
.sw-card-hd {
padding: 22px 26px 18px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
}
.sw-card-hd-top {
display: flex; align-items: flex-start; gap: 14px;
margin-bottom: 14px;
}
.sw-card-hd-icon {
width: 50px; height: 50px; border-radius: 13px;
background: var(--primary); color: #fff;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; box-shadow: 0 4px 12px rgba(79,70,229,.3);
}
.sw-card-hd-title {
font-size: 20px; font-weight: 800;
color: var(--text-dark); margin: 0 0 8px;
letter-spacing: -.3px; line-height: 1.3;
}
.sw-chip-row { display: flex; gap: 7px; flex-wrap: wrap; }
.sw-chip {
display: inline-flex; align-items: center; gap: 5px;
border-radius: 99px; padding: 4px 12px;
font-size: 12px; font-weight: 600;
}
.sw-chip-primary { background: var(--primary-light); border: 1px solid #C7D2FE; color: var(--primary); }
.sw-chip-info { background: var(--info-light); border: 1px solid #BAE6FD; color: var(--info); }
/* ── CARD BODY ── */
.sw-card-bd { padding: 26px; }
/* ── SECTION ── */
.sw-section {
display: flex; align-items: center; gap: 10px;
margin-bottom: 16px;
}
.sw-section-label {
font-size: 11.5px; font-weight: 700; color: var(--primary);
text-transform: uppercase; letter-spacing: .8px; white-space: nowrap;
}
.sw-section-line {
flex: 1; height: 1px;
background: linear-gradient(to right, #C7D2FE, transparent);
}
/* ── INFO LIST ── */
.sw-info-list { display: flex; flex-direction: column; gap: 0; margin-bottom: 26px; }
.sw-info-row {
display: flex; align-items: flex-start;
padding: 13px 0; border-bottom: 1px solid var(--border);
gap: 16px;
}
.sw-info-row:last-child { border-bottom: none; }
.sw-info-label {
display: flex; align-items: center; gap: 7px;
font-size: 12.5px; font-weight: 600; color: var(--text-soft);
min-width: 110px; flex-shrink: 0; padding-top: 1px;
}
.sw-info-label svg { color: var(--primary); flex-shrink: 0; }
.sw-info-val { font-size: 14px; font-weight: 600; color: var(--text-dark); flex: 1; }
/* ── DESCRIPTION BOX ── */
.sw-desc-box {
background: var(--neutral-light);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 16px 18px;
font-size: 14px; color: var(--text-mid);
line-height: 1.7; margin-top: 4px;
}
/* ── DEADLINE CARD ── */
.sw-deadline-box {
background: linear-gradient(135deg, #FFFBEB, #FEF3C7);
border: 1px solid #FDE68A;
border-radius: var(--radius-md);
padding: 16px 18px;
display: flex; align-items: center; gap: 14px;
}
.sw-deadline-icon {
width: 44px; height: 44px; border-radius: 11px;
background: var(--warning); color: #fff;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; box-shadow: 0 3px 10px rgba(217,119,6,.3);
}
.sw-deadline-label { font-size: 11.5px; font-weight: 600; color: #92400E; text-transform: uppercase; letter-spacing: .5px; }
.sw-deadline-val { font-size: 16px; font-weight: 800; color: #78350F; margin-top: 3px; }
/* ── SIDEBAR ── */
.sw-sidebar-card { padding: 20px; }
.sw-sidebar-title {
display: flex; align-items: center; gap: 8px;
font-size: 13px; font-weight: 700; color: var(--text-dark);
margin-bottom: 16px;
}
.sw-sidebar-title svg { color: var(--primary); }
/* stat mini */
.sw-mini-stats { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
.sw-mini-stat {
display: flex; align-items: center; justify-content: space-between;
padding: 12px 14px;
background: var(--neutral-light); border: 1px solid var(--border);
border-radius: var(--radius-md);
}
.sw-mini-stat-left { display: flex; align-items: center; gap: 9px; font-size: 13px; font-weight: 600; color: var(--text-mid); }
.sw-mini-stat-left svg { color: var(--text-soft); }
.sw-mini-stat-val { font-size: 15px; font-weight: 800; color: var(--text-dark); }
/* divider */
.sw-divider { height: 1px; background: var(--border); margin: 16px 0; }
/* action buttons */
.sw-actions { display: flex; flex-direction: column; gap: 8px; }
.sw-action-link {
display: flex; align-items: center; gap: 10px;
padding: 12px 14px; border-radius: var(--radius-md);
text-decoration: none; font-size: 13.5px; font-weight: 600;
transition: all .18s; border: 1px solid transparent;
}
.sw-action-link.primary { background: var(--primary-light); border-color: #C7D2FE; color: var(--primary); }
.sw-action-link.primary:hover { background: var(--primary); color: #fff; }
.sw-action-link.secondary { background: var(--neutral-light); border-color: var(--border); color: var(--text-mid); }
.sw-action-link.secondary:hover { background: var(--surface); color: var(--text-dark); }
.sw-action-link svg { flex-shrink: 0; }
.sw-action-link svg.arrow { margin-left: auto; opacity: .45; }
@media (max-width: 900px) {
.sw-layout { grid-template-columns: 1fr; }
.sw-page { padding: 16px; }
.sw-card-bd, .sw-card-hd { padding: 18px; }
}
</style>
<div class="sw-page">
{{-- TOPBAR --}}
<div class="sw-topbar">
<a href="{{ route('guru.tugas.index') }}" class="sw-back-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
</a>
<div class="sw-topbar-text">
<h3>Detail Tugas</h3>
<p>Informasi lengkap tugas yang diberikan</p>
</div>
</div>
<div class="sw-layout">
{{-- ── MAIN CARD ── --}}
<div class="sw-card">
<div class="sw-card-banner"></div>
<div class="sw-card-hd">
<div class="sw-card-hd-top">
<div class="sw-card-hd-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" 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"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
</div>
<div>
<div class="sw-card-hd-title">{{ $tugas->judul }}</div>
<div class="sw-chip-row">
<span class="sw-chip sw-chip-primary">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
{{ $tugas->kelas->nama_kelas }}
</span>
<span class="sw-chip sw-chip-info">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
{{ $tugas->mapel->nama_mapel }}
</span>
</div>
</div>
</div>
</div>
<div class="sw-card-bd">
{{-- INFO ROWS --}}
<div class="sw-section">
<span class="sw-section-label">Informasi Tugas</span>
<div class="sw-section-line"></div>
</div>
<div class="sw-info-list">
<div class="sw-info-row">
<div class="sw-info-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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>
Judul
</div>
<div class="sw-info-val">{{ $tugas->judul }}</div>
</div>
<div class="sw-info-row">
<div class="sw-info-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
Kelas
</div>
<div class="sw-info-val">{{ $tugas->kelas->nama_kelas }}</div>
</div>
<div class="sw-info-row">
<div class="sw-info-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
Mapel
</div>
<div class="sw-info-val">{{ $tugas->mapel->nama_mapel }}</div>
</div>
</div>
{{-- DESKRIPSI --}}
<div class="sw-section" style="margin-top:4px;">
<span class="sw-section-label">Deskripsi</span>
<div class="sw-section-line"></div>
</div>
<div class="sw-desc-box">
{{ $tugas->deskripsi ?: 'Tidak ada deskripsi.' }}
</div>
{{-- DEADLINE --}}
<div class="sw-section" style="margin-top:24px;">
<span class="sw-section-label">Batas Waktu</span>
<div class="sw-section-line"></div>
</div>
<div class="sw-deadline-box">
<div class="sw-deadline-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
</div>
<div>
<div class="sw-deadline-label">Deadline Pengumpulan</div>
<div class="sw-deadline-val">{{ $tugas->deadline }}</div>
</div>
</div>
</div>
</div>
{{-- ── SIDEBAR ── --}}
<div>
<div class="sw-card sw-sidebar-card">
<div class="sw-sidebar-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
Aksi
</div>
<div class="sw-actions">
<a href="{{ route('guru.tugas.index') }}" class="sw-action-link secondary">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Kembali ke Daftar
<svg class="arrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</a>
</div>
<div class="sw-divider"></div>
<div class="sw-sidebar-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><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>
Ringkasan
</div>
<div class="sw-mini-stats">
<div class="sw-mini-stat">
<div class="sw-mini-stat-left">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
Kelas
</div>
<div class="sw-mini-stat-val">{{ $tugas->kelas->nama_kelas }}</div>
</div>
<div class="sw-mini-stat">
<div class="sw-mini-stat-left">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
Mapel
</div>
<div class="sw-mini-stat-val" style="font-size:13px;">{{ $tugas->mapel->nama_mapel }}</div>
</div>
<div class="sw-mini-stat">
<div class="sw-mini-stat-left">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
Deadline
</div>
<div class="sw-mini-stat-val" style="font-size:12.5px;">{{ $tugas->deadline }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -1,131 +1,534 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h2 class="mb-4 text-primary">📝 Buat Ujian + Soal</h2>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
<div class="card shadow-sm border-0">
<div class="card-body">
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--primary-light: #EEF2FF;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--danger-light: #FEF2F2;
--info: #0284C7;
--info-light: #F0F9FF;
--warning: #D97706;
--warning-light: #FFFBEB;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 16px;
}
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
@if($errors->any())
<div class="alert alert-danger">{{ $errors->first() }}</div>
@endif
.sw-page { padding: 28px 32px; background: var(--bg); min-height: 100vh; }
<form method="POST" action="{{ route('guru.ujian.store') }}">
@csrf
/* ── TOPBAR ── */
.sw-topbar { display: flex; align-items: center; gap: 12px; margin-bottom: 26px; }
.sw-back-btn {
width: 36px; height: 36px;
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius-md); color: var(--text-mid);
display: inline-flex; align-items: center; justify-content: center;
text-decoration: none; transition: all .15s; flex-shrink: 0;
}
.sw-back-btn:hover { background: var(--primary-light); border-color: #C7D2FE; color: var(--primary); }
.sw-topbar-text h3 { font-size: 21px; font-weight: 800; color: var(--text-dark); margin: 0 0 2px; letter-spacing: -.3px; }
.sw-topbar-text p { font-size: 13px; color: var(--text-soft); margin: 0; }
{{-- ================= UJIAN ================= --}}
<h5 class="text-primary mb-3">📘 Data Ujian</h5>
/* ── ALERTS ── */
.sw-alert {
display: flex; align-items: flex-start; gap: 10px;
border-radius: var(--radius-md); padding: 13px 16px;
font-size: 13px; margin-bottom: 22px; font-weight: 500;
}
.sw-alert-success { background: var(--success-light); border: 1px solid #A7F3D0; color: var(--success); }
.sw-alert-danger { background: var(--danger-light); border: 1px solid #FECACA; color: var(--danger); }
.sw-alert svg { flex-shrink: 0; margin-top: 1px; }
<div class="row">
<div class="col-md-6 mb-3">
<label>Judul</label>
<input type="text" name="judul" class="form-control" required>
/* ── CARD ── */
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.05);
overflow: hidden;
margin-bottom: 22px;
}
.sw-card-banner { height: 5px; }
.sw-card-banner.purple { background: linear-gradient(90deg, #4F46E5, #7C3AED); }
.sw-card-banner.green { background: linear-gradient(90deg, #059669, #10B981); }
.sw-card-hd {
display: flex; align-items: center; gap: 12px;
padding: 18px 22px; border-bottom: 1px solid var(--border);
}
.sw-card-hd.purple { background: linear-gradient(135deg, #F5F3FF, #EEF2FF); }
.sw-card-hd.green { background: linear-gradient(135deg, #ECFDF5, #D1FAE5); }
.sw-card-hd-icon {
width: 38px; height: 38px; border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: #fff; flex-shrink: 0;
}
.sw-card-hd-icon.purple { background: var(--primary); box-shadow: 0 3px 10px rgba(79,70,229,.3); }
.sw-card-hd-icon.green { background: var(--success); box-shadow: 0 3px 10px rgba(5,150,105,.3); }
.sw-card-hd-icon.orange { background: var(--warning); box-shadow: 0 3px 10px rgba(217,119,6,.3); }
.sw-card-hd h5 { font-size: 15px; font-weight: 700; margin: 0; }
.sw-card-hd h5.purple { color: #3730A3; }
.sw-card-hd h5.green { color: #065F46; }
.sw-card-hd p { font-size: 12px; margin: 2px 0 0; }
.sw-card-hd p.purple { color: #6D6AA4; }
.sw-card-hd p.green { color: #047857; }
.sw-card-bd { padding: 24px 22px; }
/* ── SECTION DIVIDER ── */
.sw-section {
display: flex; align-items: center; gap: 10px;
margin-bottom: 18px;
}
.sw-section-label {
font-size: 11.5px; font-weight: 700;
text-transform: uppercase; letter-spacing: .8px; white-space: nowrap;
}
.sw-section-label.purple { color: var(--primary); }
.sw-section-label.green { color: var(--success); }
.sw-section-line {
flex: 1; height: 1px;
}
.sw-section-line.purple { background: linear-gradient(to right, #C7D2FE, transparent); }
.sw-section-line.green { background: linear-gradient(to right, #A7F3D0, transparent); }
/* ── FORM GRID ── */
.sw-form-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; margin-bottom: 20px; }
.sw-form-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; }
.sw-form-group { display: flex; flex-direction: column; gap: 6px; }
.sw-form-group.full { grid-column: 1 / -1; }
.sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-mid); }
.sw-label span { color: var(--danger); margin-left: 2px; }
.sw-input, .sw-select, .sw-textarea {
padding: 10px 14px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px; font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark); background: var(--neutral-light);
outline: none; width: 100%;
transition: border-color .15s, box-shadow .15s, background .15s;
}
.sw-input:focus, .sw-select:focus, .sw-textarea:focus {
border-color: var(--primary); background: #fff;
box-shadow: 0 0 0 3px rgba(79,70,229,.1);
}
.sw-textarea { resize: vertical; min-height: 80px; }
/* ── SOAL CARD ── */
.soal-item {
background: var(--surface);
border: 1.5px solid #DDD6FE;
border-radius: var(--radius-lg);
overflow: hidden;
margin-bottom: 16px;
transition: box-shadow .2s;
}
.soal-item:hover { box-shadow: 0 4px 16px rgba(79,70,229,.08); }
.soal-header {
display: flex; align-items: center; justify-content: space-between;
padding: 12px 18px;
background: linear-gradient(135deg, #F5F3FF, #EEF2FF);
border-bottom: 1px solid #DDD6FE;
}
.soal-num {
display: flex; align-items: center; gap: 8px;
font-size: 13px; font-weight: 700; color: var(--primary);
}
.soal-num-badge {
width: 26px; height: 26px; border-radius: 50%;
background: var(--primary); color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: 12px; font-weight: 800;
}
.soal-body { padding: 18px; }
.sw-choices-grid {
display: grid; grid-template-columns: 1fr 1fr;
gap: 10px; margin: 14px 0;
}
.sw-choice-wrap { position: relative; }
.sw-choice-label {
position: absolute; left: 12px; top: 50%;
transform: translateY(-50%);
width: 22px; height: 22px; border-radius: 50%;
background: var(--primary-light); color: var(--primary);
font-size: 11px; font-weight: 800;
display: flex; align-items: center; justify-content: center;
pointer-events: none; z-index: 1;
}
.sw-choice-input {
width: 100%;
padding: 9px 12px 9px 42px;
border: 1.5px solid var(--border);
border-radius: var(--radius-md);
font-size: 13.5px; font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark); background: var(--neutral-light);
outline: none; transition: all .15s;
}
.sw-choice-input:focus {
border-color: var(--primary); background: #fff;
box-shadow: 0 0 0 3px rgba(79,70,229,.1);
}
.sw-answer-row {
display: flex; align-items: center; gap: 12px;
margin-top: 4px;
}
.sw-answer-label {
font-size: 12px; font-weight: 700; color: var(--success);
white-space: nowrap; display: flex; align-items: center; gap: 5px;
}
.sw-answer-select {
flex: 1; padding: 9px 14px;
border: 1.5px solid #A7F3D0;
border-radius: var(--radius-md);
background: var(--success-light);
font-size: 13.5px; font-weight: 700; color: var(--success);
font-family: 'Plus Jakarta Sans', sans-serif;
outline: none; transition: all .15s;
}
.sw-answer-select:focus { border-color: var(--success); box-shadow: 0 0 0 3px rgba(5,150,105,.1); }
/* ── ADD SOAL BUTTON ── */
.sw-add-soal {
width: 100%; padding: 14px;
border-radius: var(--radius-md);
border: 2px dashed #C7D2FE;
background: var(--primary-light);
color: var(--primary); font-size: 14px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .2s;
display: flex; align-items: center; justify-content: center; gap: 8px;
margin-bottom: 8px;
}
.sw-add-soal:hover {
background: var(--primary); color: #fff;
border-color: var(--primary);
transform: translateY(-1px);
}
/* ── FOOTER ── */
.sw-footer {
display: flex; justify-content: space-between; align-items: center;
padding-top: 22px; border-top: 1px solid var(--border); margin-top: 8px;
}
.sw-btn {
display: inline-flex; align-items: center; gap: 7px;
padding: 11px 22px; border-radius: var(--radius-md);
font-size: 14px; font-weight: 700; border: none;
cursor: pointer; text-decoration: none;
transition: all .18s ease; line-height: 1;
font-family: 'Plus Jakarta Sans', sans-serif;
}
.sw-btn-secondary { background: var(--surface); color: var(--text-mid); border: 1.5px solid var(--border); }
.sw-btn-secondary:hover { background: var(--neutral-light); color: var(--text-dark); }
.sw-btn-success { background: var(--success); color: #fff; box-shadow: 0 4px 14px rgba(5,150,105,.3); }
.sw-btn-success:hover { background: #047857; transform: translateY(-1px); box-shadow: 0 6px 20px rgba(5,150,105,.35); }
@media (max-width: 900px) {
.sw-page { padding: 14px; }
.sw-form-row { grid-template-columns: 1fr; }
.sw-form-row-2 { grid-template-columns: 1fr; }
.sw-choices-grid { grid-template-columns: 1fr; }
}
</style>
<div class="sw-page">
{{-- TOPBAR --}}
<div class="sw-topbar">
<a href="{{ route('guru.ujian.index') }}" class="sw-back-btn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
</a>
<div class="sw-topbar-text">
<h3>Buat Ujian</h3>
<p>Isi data ujian dan tambahkan soal pilihan ganda</p>
</div>
</div>
<form method="POST" action="{{ route('guru.ujian.store') }}">
@csrf
{{-- ALERTS --}}
@if(session('success'))
<div class="sw-alert sw-alert-success">
<svg width="16" height="16" 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
@if($errors->any())
<div class="sw-alert sw-alert-danger">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><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>
{{ $errors->first() }}
</div>
@endif
{{-- ── CARD 1: PENGATURAN ── --}}
<div class="sw-card">
<div class="sw-card-banner purple"></div>
<div class="sw-card-hd purple">
<div class="sw-card-hd-icon orange">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93l-1.41 1.41M4.93 4.93l1.41 1.41M19.07 19.07l-1.41-1.41M4.93 19.07l1.41-1.41M12 2v2M12 20v2M2 12h2M20 12h2"/></svg>
</div>
<div>
<h5 class="purple">Pengaturan Ujian</h5>
<p class="purple">Pilih kelas, mapel, dan jenis ujian</p>
</div>
</div>
<div class="sw-card-bd">
<div class="sw-section">
<span class="sw-section-label purple">Klasifikasi</span>
<div class="sw-section-line purple"></div>
</div>
<div class="sw-form-row">
<!-- <div class="sw-form-group">
<label class="sw-label">Kelas <span>*</span></label>
<select name="kelas_id" class="sw-select" required>
<option value="">-- Pilih Kelas --</option>
@foreach($kelas as $k)
<option value="{{ $k->id }}">{{ $k->nama_kelas }}</option>
@endforeach
</select>
</div> -->
<div class="sw-form-group">
<label class="sw-label">Kelas <span>*</span></label>
<div style="background:var(--neutral-light);border:1.5px solid var(--border);border-radius:var(--radius-md);padding:12px 14px;display:flex;flex-wrap:wrap;gap:10px;">
@foreach($kelas as $k)
<label style="display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;font-weight:600;color:var(--text-mid);background:#fff;border:1.5px solid var(--border);border-radius:8px;padding:6px 12px;transition:all .15s;"
onmouseover="this.style.borderColor='#6366f1'" onmouseout="this.style.borderColor=''">
<input type="checkbox" name="kelas_ids[]" value="{{ $k->id }}"
style="accent-color:var(--primary);width:15px;height:15px;">
{{ $k->nama_kelas }}
</label>
@endforeach
</div>
<div style="font-size:11.5px;color:var(--text-soft);margin-top:4px;">
✓ Centang semua kelas yang akan menerima ujian ini
</div>
</div>
<div class="sw-form-group">
<label class="sw-label">Jenis Ujian <span>*</span></label>
<select name="jenis" class="sw-select" required>
<option value="UTS">UTS</option>
<option value="UAS">UAS</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label>Durasi (menit)</label>
<input type="number" name="durasi" class="form-control">
<div class="sw-form-group">
<label class="sw-label">Mapel</label>
<input type="hidden" name="mapel_id" value="{{ auth()->user()->guru->mapel_id }}">
<input type="text" class="sw-input"
value="{{ auth()->user()->guru->mapel->nama_mapel ?? '-' }}"
disabled>
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<label>Mulai</label>
<input type="datetime-local" name="mulai" class="form-control">
{{-- ── CARD 2: DATA UJIAN ── --}}
<div class="sw-card">
<div class="sw-card-banner purple"></div>
<div class="sw-card-hd purple">
<div class="sw-card-hd-icon purple">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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>
</div>
<div>
<h5 class="purple">Data Ujian</h5>
<p class="purple">Judul, durasi, dan jadwal ujian</p>
</div>
</div>
<div class="sw-card-bd">
<div class="sw-section">
<span class="sw-section-label purple">Informasi Umum</span>
<div class="sw-section-line purple"></div>
</div>
<div class="sw-form-row-2" style="margin-bottom:16px;">
<div class="sw-form-group">
<label class="sw-label">Judul <span>*</span></label>
<input type="text" name="judul" class="sw-input" placeholder="Judul ujian" required>
</div>
<div class="col-md-6 mb-3">
<label>Selesai</label>
<input type="datetime-local" name="selesai" class="form-control">
<div class="sw-form-group">
<label class="sw-label">Durasi (menit)</label>
<input type="number" name="durasi" class="sw-input" placeholder="Contoh: 90">
</div>
</div>
<hr>
<div class="sw-section" style="margin-top:4px;">
<span class="sw-section-label purple">Jadwal</span>
<div class="sw-section-line purple"></div>
</div>
<div class="sw-form-row-2">
<div class="sw-form-group">
<label class="sw-label">Waktu Mulai</label>
<input type="datetime-local" name="mulai" class="sw-input">
</div>
<div class="sw-form-group">
<label class="sw-label">Waktu Selesai</label>
<input type="datetime-local" name="selesai" class="sw-input">
</div>
</div>
</div>
</div>
{{-- ================= SOAL ================= --}}
<h5 class="text-success mb-3">🧠 Soal Ujian</h5>
{{-- ── CARD 3: SOAL ── --}}
<div class="sw-card">
<div class="sw-card-banner green"></div>
<div class="sw-card-hd green">
<div class="sw-card-hd-icon green">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
</div>
<div>
<h5 class="green">Soal Ujian</h5>
<p class="green">Tambahkan soal pilihan ganda</p>
</div>
</div>
<div class="sw-card-bd">
<div id="soal-container">
{{-- SOAL PERTAMA --}}
<div class="soal-item border p-3 mb-3 rounded">
<label>Pertanyaan</label>
<textarea name="soal[0][pertanyaan]" class="form-control" required></textarea>
<div class="row mt-2">
<div class="col-md-6">
<input name="soal[0][a]" class="form-control" placeholder="Pilihan A">
</div>
<div class="col-md-6">
<input name="soal[0][b]" class="form-control" placeholder="Pilihan B">
</div>
<div class="col-md-6 mt-2">
<input name="soal[0][c]" class="form-control" placeholder="Pilihan C">
</div>
<div class="col-md-6 mt-2">
<input name="soal[0][d]" class="form-control" placeholder="Pilihan D">
<div class="soal-item">
<div class="soal-header">
<div class="soal-num">
<div class="soal-num-badge">1</div>
Soal Nomor 1
</div>
</div>
<div class="soal-body">
<div class="sw-form-group">
<label class="sw-label">Pertanyaan <span>*</span></label>
<textarea name="soal[0][pertanyaan]" class="sw-textarea" placeholder="Tulis pertanyaan di sini..." required></textarea>
</div>
<select name="soal[0][jawaban]" class="form-control mt-2">
<option value="a">Jawaban A</option>
<option value="b">Jawaban B</option>
<option value="c">Jawaban C</option>
<option value="d">Jawaban D</option>
</select>
<div class="sw-choices-grid">
<div class="sw-choice-wrap">
<div class="sw-choice-label">A</div>
<input name="soal[0][a]" class="sw-choice-input" placeholder="Pilihan A">
</div>
<div class="sw-choice-wrap">
<div class="sw-choice-label">B</div>
<input name="soal[0][b]" class="sw-choice-input" placeholder="Pilihan B">
</div>
<div class="sw-choice-wrap">
<div class="sw-choice-label">C</div>
<input name="soal[0][c]" class="sw-choice-input" placeholder="Pilihan C">
</div>
<div class="sw-choice-wrap">
<div class="sw-choice-label">D</div>
<input name="soal[0][d]" class="sw-choice-input" placeholder="Pilihan D">
</div>
</div>
<div class="sw-answer-row">
<div class="sw-answer-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><polyline points="20 6 9 17 4 12"/></svg>
Kunci Jawaban
</div>
<select name="soal[0][jawaban]" class="sw-answer-select">
<option value="a">Jawaban A</option>
<option value="b">Jawaban B</option>
<option value="c">Jawaban C</option>
<option value="d">Jawaban D</option>
</select>
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-info mb-3" onclick="tambahSoal()">+ Tambah Soal</button>
<button type="button" class="sw-add-soal" onclick="tambahSoal()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
Tambah Soal
</button>
<hr>
<div class="d-flex justify-content-between">
<a href="{{ route('guru.ujian.index') }}" class="btn btn-secondary">
← Kembali
<div class="sw-footer">
<a href="{{ route('guru.ujian.index') }}" class="sw-btn sw-btn-secondary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Kembali
</a>
<button class="btn btn-success">
💾 Simpan Ujian + Soal
<button type="submit" class="sw-btn sw-btn-success">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Simpan Ujian + Soal
</button>
</div>
</form>
</div>
</div>
</div>
</form>
</div>
<script>
let index = 1;
function tambahSoal() {
let html = `
<div class="soal-item border p-3 mb-3 rounded">
<label>Pertanyaan</label>
<textarea name="soal[${index}][pertanyaan]" class="form-control"></textarea>
<input name="soal[${index}][a]" class="form-control mt-2" placeholder="A">
<input name="soal[${index}][b]" class="form-control mt-2" placeholder="B">
<input name="soal[${index}][c]" class="form-control mt-2" placeholder="C">
<input name="soal[${index}][d]" class="form-control mt-2" placeholder="D">
<select name="soal[${index}][jawaban]" class="form-control mt-2">
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
<option value="d">D</option>
</select>
</div>
`;
const html = `
<div class="soal-item">
<div class="soal-header">
<div class="soal-num">
<div class="soal-num-badge">${index + 1}</div>
Soal Nomor ${index + 1}
</div>
</div>
<div class="soal-body">
<div class="sw-form-group">
<label class="sw-label">Pertanyaan</label>
<textarea name="soal[${index}][pertanyaan]" class="sw-textarea" placeholder="Tulis pertanyaan di sini..."></textarea>
</div>
<div class="sw-choices-grid">
<div class="sw-choice-wrap">
<div class="sw-choice-label">A</div>
<input name="soal[${index}][a]" class="sw-choice-input" placeholder="Pilihan A">
</div>
<div class="sw-choice-wrap">
<div class="sw-choice-label">B</div>
<input name="soal[${index}][b]" class="sw-choice-input" placeholder="Pilihan B">
</div>
<div class="sw-choice-wrap">
<div class="sw-choice-label">C</div>
<input name="soal[${index}][c]" class="sw-choice-input" placeholder="Pilihan C">
</div>
<div class="sw-choice-wrap">
<div class="sw-choice-label">D</div>
<input name="soal[${index}][d]" class="sw-choice-input" placeholder="Pilihan D">
</div>
</div>
<div class="sw-answer-row">
<div class="sw-answer-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><polyline points="20 6 9 17 4 12"/></svg>
Kunci Jawaban
</div>
<select name="soal[${index}][jawaban]" class="sw-answer-select">
<option value="a">Jawaban A</option>
<option value="b">Jawaban B</option>
<option value="c">Jawaban C</option>
<option value="d">Jawaban D</option>
</select>
</div>
</div>
</div>`;
document.getElementById('soal-container').insertAdjacentHTML('beforeend', html);
index++;

View File

@ -0,0 +1,568 @@
@extends('layouts.app')
@section('content')
<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;
--warning: #D97706;
--warning-light: #FFFBEB;
--danger: #DC2626;
--danger-light: #FEF2F2;
--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;
--radius-xl: 18px;
}
* {
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);
}
/* ── BACK NAV ── */
.back-nav {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--text-soft);
font-size: 13.5px;
font-weight: 500;
text-decoration: none;
margin-bottom: 22px;
transition: color .15s;
}
.back-nav:hover {
color: var(--primary);
text-decoration: none;
}
.back-nav svg {
width: 16px;
height: 16px;
}
/* ── PAGE HEADER ── */
.sw-ph {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 26px;
}
.sw-ph-icon {
width: 48px;
height: 48px;
border-radius: 14px;
background: var(--warning-light);
border: 1px solid #FDE68A;
display: flex;
align-items: center;
justify-content: center;
color: var(--warning);
flex-shrink: 0;
}
.sw-ph-icon svg {
width: 22px;
height: 22px;
}
.sw-ph-title {
font-size: 21px;
font-weight: 800;
color: var(--text-dark);
margin: 0 0 3px;
letter-spacing: -.3px;
}
.sw-ph-sub {
font-size: 13px;
color: var(--text-soft);
margin: 0;
}
/* ── FORM CARD ── */
.form-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
box-shadow: 0 4px 20px rgba(0, 0, 0, .06);
overflow: hidden;
margin-bottom: 16px;
}
.fc-hd {
padding: 15px 24px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, #F5F3FF 0%, #EEF2FF 100%);
display: flex;
align-items: center;
gap: 10px;
}
.fc-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.dot-indigo {
background: var(--primary);
}
.dot-green {
background: var(--success);
}
.dot-orange {
background: var(--warning);
}
.fc-label {
font-size: 12px;
font-weight: 700;
color: #5B21B6;
text-transform: uppercase;
letter-spacing: .6px;
}
.fc-body {
padding: 24px;
}
/* ── FORM ELEMENTS ── */
.row-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
margin-bottom: 18px;
}
.row-full {
margin-bottom: 18px;
}
.form-label-sw {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--text-dark);
margin-bottom: 7px;
}
.form-label-sw .req {
color: var(--danger);
margin-left: 2px;
}
.form-control-sw {
width: 100%;
padding: 10px 14px;
font-size: 13.5px;
font-family: 'Plus Jakarta Sans', sans-serif;
color: var(--text-dark);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
outline: none;
transition: border-color .2s, box-shadow .2s;
appearance: none;
}
.form-control-sw:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(79, 70, 229, .12);
}
.form-control-sw:hover:not(:focus) {
border-color: #CBD5E1;
}
.form-control-sw::placeholder {
color: #9CA3AF;
}
select.form-control-sw {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 16px;
padding-right: 36px;
cursor: pointer;
}
.form-hint {
font-size: 11.5px;
color: var(--text-soft);
margin-top: 5px;
}
/* ── JENIS RADIO ── */
.jenis-wrap {
display: flex;
gap: 10px;
}
.jenis-opt {
flex: 1;
position: relative;
}
.jenis-opt input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.jenis-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 11px 0;
border: 1.5px solid var(--border);
border-radius: 10px;
font-size: 13px;
font-weight: 700;
color: var(--text-soft);
cursor: pointer;
transition: all .15s;
background: var(--surface);
font-family: 'Plus Jakarta Sans', sans-serif;
}
.jenis-opt input:checked+.jenis-btn.uts {
background: var(--info-light);
border-color: #BAE6FD;
color: var(--info);
}
.jenis-opt input:checked+.jenis-btn.uas {
background: var(--warning-light);
border-color: #FDE68A;
color: var(--warning);
}
/* ── FOOTER ── */
.form-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 24px;
border-top: 1px solid var(--border);
background: #FAFAFA;
flex-wrap: wrap;
gap: 12px;
}
.sw-btn {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 10px 22px;
border-radius: var(--radius-md);
font-size: 13.5px;
font-weight: 700;
border: none;
cursor: pointer;
text-decoration: none;
transition: all .15s;
font-family: 'Plus Jakarta Sans', sans-serif;
line-height: 1;
}
.sw-btn-cancel {
background: var(--surface);
color: var(--text-mid);
border: 1px solid var(--border);
}
.sw-btn-cancel:hover {
background: #F1F5F9;
color: var(--text-dark);
text-decoration: none;
}
.sw-btn-save {
background: var(--warning);
color: #fff;
box-shadow: 0 4px 14px rgba(217, 119, 6, .3);
}
.sw-btn-save:hover {
background: #B45309;
color: #fff;
box-shadow: 0 6px 20px rgba(217, 119, 6, .4);
transform: translateY(-1px);
}
@media (max-width: 640px) {
.sw-page {
padding: 16px;
}
.row-grid {
grid-template-columns: 1fr;
}
.jenis-wrap {
flex-direction: column;
}
}
</style>
<div class="sw-page">
{{-- BACK LINK --}}
<a href="{{ route('guru.ujian.index') }}" class="back-nav">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
</svg>
Kembali ke Daftar Ujian
</a>
{{-- PAGE HEADER --}}
<div class="sw-ph">
<div class="sw-ph-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round"
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
</svg>
</div>
<div>
<h2 class="sw-ph-title">Edit Ujian</h2>
<p class="sw-ph-sub">Perbarui informasi dan pengaturan ujian</p>
</div>
</div>
<form action="{{ route('guru.ujian.update', $ujian->id) }}" method="POST" id="formEditUjian">
@if ($errors->any())
<div style="
background:#FEF2F2;
border:1px solid #FECACA;
color:#B91C1C;
padding:14px;
border-radius:10px;
margin-bottom:18px;
">
<ul style="margin:0;padding-left:18px;">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@csrf
@method('PUT')
{{-- CARD 1: INFORMASI UJIAN --}}
<div class="form-card">
<div class="fc-hd">
<div class="fc-dot dot-indigo"></div>
<span class="fc-label">Informasi Ujian</span>
</div>
<div class="fc-body">
<div class="row-full">
<label class="form-label-sw">
Judul Ujian <span class="req">*</span>
</label>
<input type="text" name="judul" class="form-control-sw" value="{{ old('judul', $ujian->judul) }}"
placeholder="Masukkan judul ujian" required>
</div>
<div class="row-grid">
<div>
<label class="form-label-sw">
Mata Pelajaran <span class="req">*</span>
</label>
<select name="mapel_id" class="form-control-sw" required>
@foreach($mapel as $m)
<option value="{{ $m->id }}" {{ $ujian->mapel_id == $m->id ? 'selected' : '' }}>
{{ $m->nama_mapel }}
</option>
@endforeach
</select>
</div>
<div>
<label class="form-label-sw">
Kelas <span class="req">*</span>
</label>
<div
style="background:#F9FAFB;border:1px solid #E5E7EB;border-radius:10px;padding:12px 14px;display:flex;flex-wrap:wrap;gap:10px;">
@foreach($kelas as $k)
<label
style="display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;font-weight:600;color:#374151;background:#fff;border:1.5px solid #E5E7EB;border-radius:8px;padding:6px 12px;transition:all .15s;"
onmouseover="this.style.borderColor='#6366f1'" onmouseout="this.style.borderColor=''">
<input type="checkbox" name="kelas_ids[]" value="{{ $k->id }}" {{ in_array($k->id, $selectedKelas) ? 'checked' : '' }}
style="accent-color:#4F46E5;width:15px;height:15px;">
{{ $k->nama_kelas }}
</label>
@endforeach
</div>
<p class="form-hint">✓ Centang semua kelas yang akan menerima ujian ini</p>
</div>
</div>
<div class="row-full">
<label class="form-label-sw">
Durasi <span class="req">*</span>
</label>
<input type="number" name="durasi" class="form-control-sw"
value="{{ old('durasi', $ujian->durasi) }}" placeholder="Contoh: 90" required>
<p class="form-hint">Dalam satuan menit</p>
</div>
</div>
</div>
{{-- CARD 2: JADWAL --}}
<div class="form-card">
<div class="fc-hd">
<div class="fc-dot dot-green"></div>
<span class="fc-label">Jadwal Pelaksanaan</span>
</div>
<div class="fc-body">
<div class="row-grid">
<div>
<label class="form-label-sw">
Waktu Mulai <span class="req">*</span>
</label>
<input type="datetime-local" name="mulai" class="form-control-sw"
value="{{ date('Y-m-d\TH:i', strtotime($ujian->mulai)) }}" required>
</div>
<div>
<label class="form-label-sw">
Waktu Selesai <span class="req">*</span>
</label>
<input type="datetime-local" name="selesai" class="form-control-sw"
value="{{ date('Y-m-d\TH:i', strtotime($ujian->selesai)) }}" required>
</div>
</div>
</div>
</div>
{{-- CARD 3: JENIS UJIAN --}}
<div class="form-card">
<div class="fc-hd">
<div class="fc-dot dot-orange"></div>
<span class="fc-label">Jenis Ujian</span>
</div>
<div class="fc-body">
<label class="form-label-sw">Pilih Jenis <span class="req">*</span></label>
<div class="jenis-wrap">
<label class="jenis-opt">
<input type="radio" name="jenis" value="UTS" {{ $ujian->jenis == 'UTS' ? 'checked' : '' }}>
<div class="jenis-btn uts">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2.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>
UTS — Ujian Tengah Semester
</div>
</label>
<label class="jenis-opt">
<input type="radio" name="jenis" value="UAS" {{ $ujian->jenis == 'UAS' ? 'checked' : '' }}>
<div class="jenis-btn uas">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor"
stroke-width="2.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>
UAS — Ujian Akhir Semester
</div>
</label>
</div>
</div>
{{-- FOOTER --}}
<div class="form-footer">
<a href="{{ route('guru.ujian.index') }}" class="sw-btn sw-btn-cancel">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
Batalkan
</a>
<button type="submit" class="sw-btn sw-btn-save" id="btnUpdate">
<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="M5 13l4 4L19 7" />
</svg>
Update Ujian
</button>
</div>
</div>
</form>
</div>
<script>
document.getElementById('formEditUjian').addEventListener('submit', function () {
const btn = document.getElementById('btnUpdate');
btn.disabled = true;
btn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
style="animation:spin .8s linear infinite">
<path stroke-linecap="round"
stroke-linejoin="round"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
Menyimpan...
`;
});
const s = document.createElement('style');
s.textContent = '@keyframes spin{to{transform:rotate(360deg)}}';
document.head.appendChild(s);
</script>
@endsection

View File

@ -1,12 +1,282 @@
@extends('layouts.app')
@section('content')
<a href="{{ route('guru.ujian.create') }}">+ Buat Ujian</a>
<ul>
@foreach($data as $u)
<li>
{{ $u->judul }}
<a href="{{ route('guru.ujian.soal', $u->id) }}">Input Soal</a>
</li>
@endforeach
</ul>
<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;
--warning:#D97706;--warning-light:#FFFBEB;
--danger:#DC2626;--danger-light:#FEF2F2;
--info:#0284C7;--info-light:#F0F9FF;
--neutral-light:#F9FAFB;--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);}
.sw-topbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:26px;gap:16px;flex-wrap:wrap;}
.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-btn{display:inline-flex;align-items:center;gap:7px;padding:10px 20px;border-radius:var(--radius-md);font-size:13.5px;font-weight:700;border:none;cursor:pointer;text-decoration:none;transition:all .18s ease;line-height:1;font-family:'Plus Jakarta Sans',sans-serif;}
.sw-btn-primary{background:var(--primary);color:#fff;box-shadow:0 4px 14px rgba(79,70,229,.3);}
.sw-btn-primary:hover{background:var(--primary-dark);color:#fff;transform:translateY(-1px);}
.sw-btn-sm{padding:7px 13px;font-size:12.5px;border-radius:8px;}
.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-success{background:var(--success);color:#fff;box-shadow:0 4px 14px rgba(5,150,105,.3);}
.sw-btn-success:hover{background:#047857;color:#fff;transform:translateY(-1px);}
.sw-btn-info{background:var(--info-light);color:var(--info);border:1px solid #BAE6FD;}
.sw-btn-info:hover{background:var(--info);color:#fff;}
.sw-card{background:var(--surface);border-radius:var(--radius-lg);border:1px solid var(--border);box-shadow:0 1px 3px rgba(0,0,0,.05);overflow:hidden;margin-bottom:22px;}
.sw-card-hd{display:flex;align-items:center;justify-content:space-between;padding:16px 22px;border-bottom:1px solid var(--border);background:linear-gradient(135deg,#F5F3FF 0%,#EEF2FF 100%);}
.sw-card-hd-left{display:flex;align-items:center;gap:10px;}
.sw-card-hd-icon{width:34px;height:34px;border-radius:9px;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;}
.sw-card-hd h6{font-size:14px;font-weight:700;color:#3730A3;margin:0;}
.sw-card-hd p{font-size:11.5px;color:#6D6AA4;margin:2px 0 0;}
.sw-count-badge{background:var(--primary-light);border:1px solid #C7D2FE;border-radius:99px;padding:3px 12px;font-size:12px;font-weight:700;color:var(--primary);}
.sw-ujian-list{padding:8px 0;}
.sw-ujian-item{display:flex;align-items:center;gap:18px;padding:18px 22px;border-bottom:1px solid var(--border);transition:background .15s;flex-wrap:wrap;}
.sw-ujian-item:last-child{border-bottom:none;}
.sw-ujian-item:hover{background:#FAFAFF;}
.sw-ujian-icon{width:46px;height:46px;border-radius:12px;background:var(--primary-light);border:1px solid #C7D2FE;display:flex;align-items:center;justify-content:center;color:var(--primary);flex-shrink:0;}
.sw-ujian-info{flex:1;min-width:0;}
.sw-ujian-title{font-size:15px;font-weight:700;color:var(--text-dark);margin-bottom:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.sw-ujian-meta{display:flex;gap:10px;flex-wrap:wrap;}
.sw-meta-chip{display:inline-flex;align-items:center;gap:5px;background:var(--neutral-light);border:1px solid var(--border);border-radius:7px;padding:3px 10px;font-size:11.5px;font-weight:600;color:var(--text-soft);}
.sw-jenis{display:inline-flex;align-items:center;padding:3px 10px;border-radius:99px;font-size:11px;font-weight:800;letter-spacing:.5px;}
.sw-jenis-uts{background:var(--info-light);border:1px solid #BAE6FD;color:var(--info);}
.sw-jenis-uas{background:var(--warning-light);border:1px solid #FDE68A;color:var(--warning);}
.sw-status-draft{display:inline-flex;align-items:center;gap:5px;background:#F3F4F6;border:1px solid #D1D5DB;border-radius:99px;padding:3px 10px;font-size:11px;font-weight:700;color:#6B7280;}
.sw-status-kirim{display:inline-flex;align-items:center;gap:5px;background:var(--success-light);border:1px solid #A7F3D0;border-radius:99px;padding:3px 10px;font-size:11px;font-weight:700;color:var(--success);}
.sw-ujian-actions{display:flex;gap:8px;align-items:center;flex-shrink:0;flex-wrap:wrap;}
.sw-empty{padding:70px 20px;text-align:center;}
.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:16px;font-weight:700;color:var(--text-dark);margin-bottom:6px;}
.sw-empty p{font-size:13px;color:var(--text-soft);margin-bottom:20px;}
/* NILAI SECTION */
.sw-nilai-card{background:var(--surface);border-radius:var(--radius-lg);border:1px solid var(--border);box-shadow:0 1px 3px rgba(0,0,0,.05);overflow:hidden;}
.sw-nilai-hd{display:flex;align-items:center;justify-content:space-between;padding:16px 22px;border-bottom:1px solid var(--border);background:linear-gradient(135deg,#ECFDF5,#D1FAE5);}
.sw-nilai-filter{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.sw-select-kelas{padding:8px 14px;border:1.5px solid var(--border);border-radius:var(--radius-md);font-size:13px;font-weight:600;color:var(--text-dark);background:var(--neutral-light);font-family:'Plus Jakarta Sans',sans-serif;outline:none;}
.sw-select-kelas:focus{border-color:var(--success);box-shadow:0 0 0 3px rgba(5,150,105,.1);}
.sw-table{width:100%;border-collapse:collapse;}
.sw-table th{padding:11px 16px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--text-soft);background:var(--neutral-light);border-bottom:1px solid var(--border);text-align:left;}
.sw-table td{padding:12px 16px;font-size:13.5px;color:var(--text-mid);border-bottom:1px solid var(--border);}
.sw-table tr:last-child td{border-bottom:none;}
.sw-table tr:hover td{background:#FAFAFF;}
.sw-nilai-badge{display:inline-flex;align-items:center;justify-content:center;min-width:44px;padding:4px 10px;border-radius:99px;font-size:13px;font-weight:800;}
.n-a{background:#DCFCE7;color:#166534;}
.n-b{background:#FEF9C3;color:#854D0E;}
.n-c{background:#FEE2E2;color:#991B1B;}
@media(max-width:768px){
.sw-page{padding:14px;}
.sw-ujian-actions{width:100%;justify-content:flex-end;}
}
</style>
<div class="sw-page">
{{-- TOPBAR --}}
<div class="sw-topbar">
<div>
<h3>Manajemen Ujian</h3>
<p>Kelola ujian dan pantau nilai siswa</p>
</div>
<a href="{{ route('guru.ujian.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"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
Buat Ujian
</a>
</div>
{{-- ALERT --}}
@if(session('success'))
<div style="display:flex;align-items:center;gap:10px;background:var(--success-light);border:1px solid #A7F3D0;border-radius:var(--radius-md);padding:13px 16px;margin-bottom:20px;font-size:13px;font-weight:600;color:var(--success);">
<svg width="16" height="16" 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
{{-- DAFTAR UJIAN --}}
<div class="sw-card">
<div class="sw-card-hd">
<div class="sw-card-hd-left">
<div class="sw-card-hd-icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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>
<h6>Daftar Ujian</h6>
<p>Semua ujian yang telah dibuat</p>
</div>
</div>
<span class="sw-count-badge">{{ $data->count() }} Ujian</span>
</div>
<div class="sw-ujian-list">
@forelse($data as $u)
<div class="sw-ujian-item">
<div class="sw-ujian-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" 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"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
</div>
<div class="sw-ujian-info">
<div class="sw-ujian-title">{{ $u->judul }}</div>
<div class="sw-ujian-meta">
@if($u->jenis)
<span class="sw-jenis {{ $u->jenis == 'UTS' ? 'sw-jenis-uts' : 'sw-jenis-uas' }}">
{{ $u->jenis }}
</span>
@endif
{{-- STATUS KIRIM --}}
@if($u->status_kirim == 'terkirim')
<span class="sw-status-kirim">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M22 2L11 13"/><path d="M22 2L15 22 11 13 2 9l20-7z"/></svg>
Terkirim ke Siswa
</span>
@else
<span class="sw-status-draft">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" 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>
Draft
</span>
@endif
<span class="sw-meta-chip">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" 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>
{{-- KELAS --}}
@foreach($u->kelas as $k)
<span class="sw-meta-chip" style="background:var(--primary-light);border-color:#C7D2FE;color:var(--primary);">
{{ $k->nama_kelas }}
</span>
@endforeach
</div>
</div>
<div class="sw-ujian-actions">
{{-- Tombol Kirim (hanya kalau masih draft) --}}
@if($u->status_kirim == 'draft')
<form action="{{ route('guru.ujian.kirim', $u->id) }}" method="POST" class="d-inline"
onsubmit="return confirm('Kirim ujian ini ke semua kelas yang dipilih?')">
@csrf
<button type="submit" class="sw-btn sw-btn-sm sw-btn-success">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M22 2L11 13"/><path d="M22 2L15 22 11 13 2 9l20-7z"/></svg>
Kirim ke Siswa
</button>
</form>
@endif
<a href="{{ route('guru.ujian.edit', $u->id) }}" class="sw-btn sw-btn-sm sw-btn-warning">
<svg width="13" height="13" 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('guru.ujian.destroy', $u->id) }}" method="POST" class="d-inline"
onsubmit="return confirm('Yakin hapus ujian ini?')">
@csrf
@method('DELETE')
<button type="submit" class="sw-btn sw-btn-sm sw-btn-danger">
<svg width="13" height="13" 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 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
Hapus
</button>
</form>
</div>
</div>
@empty
<div class="sw-empty">
<div class="sw-empty-ring">
<svg width="34" height="34" viewBox="0 0 24 24" fill="none" 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"/></svg>
</div>
<h5>Belum Ada Ujian</h5>
<p>Klik tombol di atas untuk membuat ujian pertama</p>
<a href="{{ route('guru.ujian.create') }}" class="sw-btn sw-btn-primary">+ Buat Ujian</a>
</div>
@endforelse
</div>
</div>
{{-- ══ NILAI SISWA ══ --}}
<div class="sw-nilai-card">
<div class="sw-nilai-hd">
<div style="display:flex;align-items:center;gap:10px;">
<div style="width:34px;height:34px;border-radius:9px;background:var(--success);color:#fff;display:flex;align-items:center;justify-content:center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2"/></svg>
</div>
<div>
<div style="font-size:14px;font-weight:700;color:#065F46;">Nilai Siswa</div>
<div style="font-size:11.5px;color:#047857;margin-top:2px;">Filter berdasarkan kelas</div>
</div>
</div>
{{-- FILTER KELAS --}}
<form method="GET" action="{{ route('guru.ujian.index') }}" style="display:flex;gap:8px;align-items:center;">
<select name="kelas_id" class="sw-select-kelas" onchange="this.form.submit()">
<option value="">-- Pilih Kelas --</option>
@foreach($kelasList as $k)
<option value="{{ $k->id }}" {{ $selectedKelas == $k->id ? 'selected' : '' }}>
{{ $k->nama_kelas }}
</option>
@endforeach
</select>
@if($selectedKelas)
<a href="{{ route('guru.ujian.index') }}" style="font-size:12px;color:var(--text-soft);text-decoration:none;font-weight:600;">✕ Reset</a>
@endif
</form>
</div>
@if($selectedKelas && $nilaiSiswa->count())
<table class="sw-table">
<thead>
<tr>
<th>#</th>
<th>Nama Siswa</th>
<th>Ujian</th>
<th>Jenis</th>
<th>Nilai</th>
</tr>
</thead>
<tbody>
@foreach($nilaiSiswa as $i => $n)
<tr>
<td style="color:var(--text-soft);font-weight:600;">{{ $i + 1 }}</td>
<td style="font-weight:700;color:var(--text-dark);">{{ $n->siswa->nama ?? '-' }}</td>
<td>{{ $n->ujian->judul ?? '-' }}</td>
<td>
@if($n->ujian && $n->ujian->jenis)
<span class="sw-jenis {{ $n->ujian->jenis == 'UTS' ? 'sw-jenis-uts' : 'sw-jenis-uas' }}">
{{ $n->ujian->jenis }}
</span>
@endif
</td>
<td>
@php $v = $n->nilai; @endphp
<span class="sw-nilai-badge {{ $v >= 75 ? 'n-a' : ($v >= 60 ? 'n-b' : 'n-c') }}">
{{ $v }}
</span>
</td>
</tr>
@endforeach
</tbody>
</table>
@elseif($selectedKelas)
<div style="padding:40px;text-align:center;color:var(--text-soft);font-size:13px;font-weight:600;">
Belum ada siswa yang mengerjakan ujian di kelas ini.
</div>
@else
<div style="padding:40px;text-align:center;color:var(--text-soft);font-size:13px;font-weight:600;">
Pilih kelas di atas untuk melihat nilai siswa.
</div>
@endif
</div>
</div>
@endsection

View File

@ -1,34 +1,120 @@
@extends('layouts.app')
@section('content')
<h3>Input Soal: {{ $ujian->judul }}</h3>
<div class="container my-5" style="max-width: 700px;">
@if(session('success')) <div>{{ session('success') }}</div> @endif
@if($errors->any()) <div>{{ $errors->first() }}</div> @endif
<h3 class="mb-4 fw-500" style="border-left: 4px solid #1d4ed8; padding-left: 12px; color: #1a2e4a;">
Input Soal: {{ $ujian->judul }}
</h3>
<form method="POST" action="{{ route('guru.ujian.storeSoal', $ujian->id) }}">
@csrf
@if(session('success'))
<div class="alert alert-dismissible fade show d-flex align-items-center gap-2" role="alert"
style="background:#dcfce7; color:#166534; border: 0.5px solid #86efac; border-radius: 8px;">
<i class="bi bi-check-circle"></i>
{{ session('success') }}
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
@for($i=0; $i<5; $i++)
<p>Soal {{ $i+1 }}</p>
@if($errors->any())
<div class="alert alert-dismissible fade show d-flex align-items-center gap-2" role="alert"
style="background:#fef2f2; color:#991b1b; border: 0.5px solid #fca5a5; border-radius: 8px;">
<i class="bi bi-exclamation-circle"></i>
{{ $errors->first() }}
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<input type="text" name="soal[{{ $i }}][pertanyaan]" placeholder="Pertanyaan" required><br>
<form method="POST" action="{{ route('guru.ujian.storeSoal', $ujian->id) }}">
@csrf
<input type="text" name="soal[{{ $i }}][a]" placeholder="A" required><br>
<input type="text" name="soal[{{ $i }}][b]" placeholder="B" required><br>
<input type="text" name="soal[{{ $i }}][c]" placeholder="C" required><br>
<input type="text" name="soal[{{ $i }}][d]" placeholder="D" required><br>
@for($i=0; $i<5; $i++)
<div class="card mb-4" style="border: 0.5px solid #e2e8f0; border-radius: 12px; overflow: hidden;">
<select name="soal[{{ $i }}][jawaban]" required>
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
<option value="d">D</option>
</select>
<hr>
@endfor
<div class="card-header d-flex align-items-center gap-2"
style="background: #1a2e4a; color: #e0eeff; padding: 12px 20px; border: none;">
<i class="bi bi-file-text" style="font-size: 16px;"></i>
Soal
<span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 2px 10px;
font-size: 12px; color: #bfdbfe;">
{{ $i + 1 }} / 5
</span>
</div>
<button>Simpan Soal</button>
</form>
<div class="card-body p-4">
<div class="mb-3">
<label for="pertanyaan-{{ $i }}" class="form-label"
style="font-size: 13px; font-weight: 500; color: #1a2e4a;">
<i class="bi bi-question-circle" style="margin-right: 4px; color: #1d4ed8;"></i>
Pertanyaan
</label>
<input type="text" id="pertanyaan-{{ $i }}" name="soal[{{ $i }}][pertanyaan]"
class="form-control" placeholder="Masukkan pertanyaan soal ke-{{ $i+1 }}..."
style="font-size: 14px; border: 0.5px solid #cbd5e1; border-radius: 8px;"
required>
</div>
<div class="mb-3">
<label class="form-label" style="font-size: 13px; font-weight: 500; color: #1a2e4a;">
Pilihan Jawaban
</label>
<div class="row g-2">
@foreach(['a','b','c','d'] as $opt)
<div class="col-6 col-md-3">
<div class="d-flex align-items-center gap-1 mb-1">
<span style="width: 20px; height: 20px; border-radius: 50%;
background: #1a2e4a; color: #e0eeff; display: inline-flex;
align-items: center; justify-content: center;
font-size: 11px; font-weight: 500;">
{{ strtoupper($opt) }}
</span>
</div>
<input type="text" id="{{ $opt }}-{{ $i }}"
name="soal[{{ $i }}][{{ $opt }}]"
class="form-control"
placeholder="Pilihan {{ strtoupper($opt) }}"
style="font-size: 14px; border: 0.5px solid #cbd5e1; border-radius: 8px;"
required>
</div>
@endforeach
</div>
</div>
<hr style="border: none; border-top: 0.5px solid #e2e8f0; margin: 1rem 0;">
<div class="mb-0">
<label for="jawaban-{{ $i }}" class="form-label"
style="font-size: 13px; font-weight: 500; color: #1a2e4a;">
<i class="bi bi-check-circle" style="margin-right: 4px; color: #1d4ed8;"></i>
Jawaban Benar
</label>
<select id="jawaban-{{ $i }}" name="soal[{{ $i }}][jawaban]"
class="form-select"
style="font-size: 14px; border: 0.5px solid #cbd5e1; border-radius: 8px;"
required>
<option value="" disabled selected>Pilih jawaban yang benar</option>
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
<option value="d">D</option>
</select>
</div>
</div>
</div>
@endfor
<div class="d-grid">
<button type="submit" class="btn btn-lg d-flex align-items-center justify-content-center gap-2"
style="background: #1a2e4a; color: #e0eeff; border: none; border-radius: 8px;
padding: 13px; font-size: 15px; font-weight: 500;">
<i class="bi bi-floppy"></i>
Simpan Soal
</button>
</div>
</form>
</div>
@endsection

View File

@ -1,117 +1,105 @@
{{-- ================================================================
FILE: resources/views/layouts/app.blade.php
================================================================ --}}
<!DOCTYPE html>
<html lang="en">
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>SMAN 1</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
{{-- Font Awesome --}}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
<title>SB Admin 2 - Blank</title>
<!-- Custom fonts for this template-->
<link href= "{{ asset('/') }}sbadmin2/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
{{-- Google Fonts --}}
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=DM+Mono:wght@400;500&display=swap"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="{{ asset('/') }}sbadmin2/css/sb-admin-2.min.css" rel="stylesheet">
{{-- Chart.js --}}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
background: #f4f6fa;
font-family: 'Plus Jakarta Sans', sans-serif;
}
/* Geser konten agar tidak tertimpa sidebar (240px) dan topbar (60px) */
#main-content {
margin-left: 240px;
padding: 72px 24px 24px;
min-height: 100vh;
background: #f4f6fa;
overflow-x: hidden;
}
@media (max-width: 768px) {
#main-content {
margin-left: 0;
padding: 72px 16px 20px;
}
.custom-sidebar {
transform: translateX(-100%);
transition: transform 0.25s ease;
}
.custom-sidebar.open {
transform: translateX(0);
}
}
</style>
@stack('styles')
</head>
<style>
.btn {
position: relative;
z-index: 9999 !important;
}
</style>
<style>
.dropdown-menu {
z-index: 9999 !important;
}
</style>
<style>
.topbar .dropdown-menu {
z-index: 9999 !important;
position: absolute !important;
}
</style>
<body id="page-top">
<!-- Page Wrapper -->
<div id="wrapper">
<body>
<!-- Sidebar -->
<x-sidebar/>
<!-- End of Sidebar -->
{{-- SIDEBAR → resources/views/components/sidebar.blade.php --}}
@include('components.sidebar')
<!-- Content Wrapper -->
<div id="content-wrapper" class="d-flex flex-column">
{{-- TOPBAR → resources/views/components/topbar.blade.php --}}
@include('components.topbar')
<!-- Main Content -->
<div id="content">
<!-- Topbar -->
<x-topbar/>
<!-- End of Topbar -->
<!-- Begin Page Content -->
@yield('content')
<!-- /.container-fluid -->
</div>
<!-- End of Main Content -->
<!-- Footer -->
<x-footer/>
<!-- End of Footer -->
</div>
<!-- End of Content Wrapper -->
</div>
<!-- End of Page Wrapper -->
<!-- Scroll to Top Button-->
<a class="scroll-to-top rounded" href="#page-top">
<i class="fas fa-angle-up"></i>
</a>
<!-- Logout Modal-->
<div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Ready to Leave?</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
<a class="btn btn-primary" href="login.html">Logout</a>
</div>
</div>
</div>
{{-- KONTEN UTAMA --}}
<div id="main-content">
@yield('content')
</div>
<!-- Bootstrap core JavaScript-->
<script src="{{ asset('/') }}sbadmin2/vendor/jquery/jquery.min.js"></script>
<script src="{{ asset('/') }}sbadmin2/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script>
const sidebarToggleBtn = document.getElementById('sidebarToggleTop');
const sidebar = document.querySelector('.custom-sidebar');
<!-- Core plugin JavaScript-->
<script src="{{ asset('/') }}sbadmin2/vendor/jquery-easing/jquery.easing.min.js"></script>
if (sidebarToggleBtn && sidebar) {
sidebarToggleBtn.addEventListener('click', function () {
sidebar.classList.toggle('open');
});
<!-- Custom scripts for all pages-->
<script src="{{ asset('/') }}sbadmin2/js/sb-admin-2.min.js"></script>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display:none;">
@csrf
</form>
document.addEventListener('click', function (e) {
if (window.innerWidth <= 768) {
if (!sidebar.contains(e.target) && !sidebarToggleBtn.contains(e.target)) {
sidebar.classList.remove('open');
}
}
});
}
</script>
@stack('scripts')
</body>
</html>

View File

@ -1,25 +1,683 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<h1 class="h3 mb-4 text-gray-800">Dashboard Admin</h1>
<style>
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');
<div class="card shadow">
<div class="card-body text-center">
:root {
--ink: #0c0f1a;
--ink-2: #1e2235;
--ink-3: #2d3452;
--slate: #64748b;
--muted: #94a3b8;
--dim: #cbd5e1;
--surface: #ffffff;
--surface-2: #f6f8fc;
--surface-3: #eef1f8;
<h4>Selamat Datang 👑</h4>
--blue: #2563eb;
--blue-2: #1d4ed8;
--blue-glow: rgba(37,99,235,0.18);
--blue-soft: #eff6ff;
--blue-border:#bfdbfe;
@auth
{{ auth()->user()->name }}
@endauth
--green: #059669;
--green-soft:#ecfdf5;
--green-border:#a7f3d0;
<p class="text-muted">
Anda login sebagai <b>Admin</b>
</p>
--amber: #d97706;
--amber-soft:#fffbeb;
--amber-border:#fde68a;
--red: #dc2626;
--red-soft: #fef2f2;
--red-border:#fecaca;
--violet: #7c3aed;
--violet-soft:#f5f3ff;
--violet-border:#ddd6fe;
--r-sm:5px; --r-md:10px; --r-lg:16px; --r-xl:22px; --r-2xl:30px;
--shadow-card: 0 2px 12px rgba(12,15,26,0.06), 0 1px 3px rgba(12,15,26,0.04);
--shadow-hover: 0 8px 28px rgba(12,15,26,0.10), 0 2px 8px rgba(12,15,26,0.06);
--shadow-blue: 0 4px 20px rgba(37,99,235,0.22);
}
*, *::before, *::after { box-sizing:border-box; margin:0; padding:0; }
body { background: var(--surface-2); }
.db { font-family:'Sora',sans-serif; padding: 28px 32px 48px; }
/* ─── HERO GREETING ─────────────────────────────────────── */
.greeting-band {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 24px;
background: var(--ink);
border-radius: var(--r-2xl);
padding: 32px 36px;
margin-bottom: 28px;
position: relative;
overflow: hidden;
}
.greeting-band::before {
content:'';
position:absolute; inset:0;
background:
radial-gradient(ellipse 60% 80% at 90% 50%, rgba(37,99,235,0.22) 0%, transparent 70%),
radial-gradient(ellipse 40% 60% at 10% 0%, rgba(124,58,237,0.14) 0%, transparent 60%);
pointer-events:none;
}
.greeting-band::after {
content:'';
position:absolute;
right:-60px; top:-60px;
width:260px; height:260px;
border-radius:50%;
background: rgba(37,99,235,0.06);
border: 1px solid rgba(255,255,255,0.05);
}
.greeting-text { position:relative; z-index:1; }
.greeting-tag {
display:inline-flex; align-items:center; gap:7px;
background: rgba(255,255,255,0.08);
border: 1px solid rgba(255,255,255,0.12);
border-radius:999px;
padding:5px 14px;
font-size:12px; font-weight:600; color:rgba(255,255,255,0.7);
letter-spacing:0.5px;
text-transform:uppercase;
margin-bottom:14px;
}
.greeting-tag span {
width:7px; height:7px; border-radius:50%;
background:#22c55e;
box-shadow:0 0 8px #22c55e;
animation: pulse-dot 2s ease-in-out infinite;
}
@keyframes pulse-dot {
0%,100% { opacity:1; transform:scale(1); }
50% { opacity:0.6; transform:scale(0.8); }
}
.greeting-name {
font-size:28px; font-weight:800; color:#fff;
line-height:1.15; letter-spacing:-0.5px;
margin-bottom:8px;
}
.greeting-name em { font-style:normal; color:#93c5fd; }
.greeting-sub {
font-size:13.5px; color:rgba(255,255,255,0.5);
font-weight:400; line-height:1.5;
}
.greeting-date-box {
position:relative; z-index:1;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.1);
border-radius:var(--r-lg);
padding:18px 22px;
text-align:center;
backdrop-filter:blur(8px);
flex-shrink:0;
}
.date-day {
font-size:38px; font-weight:800; color:#fff;
line-height:1; font-family:'Sora',sans-serif;
letter-spacing:-1px;
}
.date-month {
font-size:13px; font-weight:500; color:rgba(255,255,255,0.5);
text-transform:uppercase; letter-spacing:1px;
margin-top:4px;
}
.date-time {
font-family:'JetBrains Mono',monospace;
font-size:12px; font-weight:500;
color:#93c5fd;
margin-top:8px;
background: rgba(37,99,235,0.2);
border-radius:var(--r-sm);
padding:3px 8px;
}
/* ─── STATS GRID ─────────────────────────────────────────── */
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin-bottom: 24px;
}
.stat-card {
background: var(--surface);
border: 1px solid var(--surface-3);
border-radius: var(--r-xl);
padding: 22px 20px;
box-shadow: var(--shadow-card);
transition: box-shadow 0.2s, transform 0.2s;
position: relative;
overflow: hidden;
}
.stat-card:hover {
box-shadow: var(--shadow-hover);
transform: translateY(-2px);
}
.stat-card::before {
content:'';
position:absolute; top:0; left:0; right:0;
height:3px;
border-radius:var(--r-xl) var(--r-xl) 0 0;
}
.stat-card.blue::before { background: linear-gradient(90deg, var(--blue), #60a5fa); }
.stat-card.green::before { background: linear-gradient(90deg, var(--green), #34d399); }
.stat-card.amber::before { background: linear-gradient(90deg, var(--amber), #fbbf24); }
.stat-card.violet::before { background: linear-gradient(90deg, var(--violet), #a78bfa); }
.stat-icon-wrap {
width:44px; height:44px;
border-radius:var(--r-md);
display:flex; align-items:center; justify-content:center;
margin-bottom:16px;
}
.stat-card.blue .stat-icon-wrap { background:var(--blue-soft); border:1px solid var(--blue-border); }
.stat-card.green .stat-icon-wrap { background:var(--green-soft); border:1px solid var(--green-border); }
.stat-card.amber .stat-icon-wrap { background:var(--amber-soft); border:1px solid var(--amber-border); }
.stat-card.violet.stat-icon-wrap { background:var(--violet-soft); border:1px solid var(--violet-border); }
.stat-card.violet .stat-icon-wrap { background:var(--violet-soft); border:1px solid var(--violet-border); }
.stat-icon-wrap svg { width:20px; height:20px; }
.stat-card.blue .stat-icon-wrap svg { color:var(--blue); }
.stat-card.green .stat-icon-wrap svg { color:var(--green); }
.stat-card.amber .stat-icon-wrap svg { color:var(--amber); }
.stat-card.violet .stat-icon-wrap svg { color:var(--violet); }
.stat-label {
font-size:11.5px; font-weight:600; color:var(--muted);
text-transform:uppercase; letter-spacing:0.7px;
margin-bottom:5px;
}
.stat-value {
font-size:30px; font-weight:800; color:var(--ink);
letter-spacing:-1px; line-height:1;
font-family:'Sora',sans-serif;
}
.stat-value.mono {
font-family:'JetBrains Mono',monospace;
font-size:26px;
}
.stat-sub {
font-size:12px; color:var(--muted);
margin-top:6px;
}
/* ─── MAIN 2-COL ─────────────────────────────────────────── */
.main-cols {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
}
/* ─── SECTION CARD ───────────────────────────────────────── */
.section-card {
background: var(--surface);
border: 1px solid var(--surface-3);
border-radius: var(--r-xl);
box-shadow: var(--shadow-card);
overflow: hidden;
display: flex;
flex-direction: column;
}
.sc-head {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 22px 14px;
border-bottom: 1px solid var(--surface-3);
}
.sc-head-left { display:flex; align-items:center; gap:10px; }
.sc-icon {
width:36px; height:36px;
border-radius:var(--r-md);
display:flex; align-items:center; justify-content:center;
flex-shrink:0;
}
.sc-icon.blue { background:var(--blue-soft); border:1px solid var(--blue-border); }
.sc-icon.green { background:var(--green-soft); border:1px solid var(--green-border); }
.sc-icon.amber { background:var(--amber-soft); border:1px solid var(--amber-border); }
.sc-icon.violet { background:var(--violet-soft); border:1px solid var(--violet-border); }
.sc-icon svg { width:17px; height:17px; }
.sc-icon.blue svg { color:var(--blue); }
.sc-icon.green svg { color:var(--green); }
.sc-icon.amber svg { color:var(--amber); }
.sc-icon.violet svg { color:var(--violet); }
.sc-title {
font-size:14px; font-weight:700; color:var(--ink);
letter-spacing:-0.2px;
}
.sc-badge {
font-size:11.5px; font-weight:600; color:var(--muted);
background:var(--surface-3); border:1px solid var(--dim);
border-radius:999px; padding:2px 10px;
}
/* ─── NILAI LIST ─────────────────────────────────────────── */
.nilai-list { padding: 6px 0; flex:1; }
.nilai-row {
display: flex; align-items: center; gap:12px;
padding: 13px 22px;
border-bottom: 1px solid var(--surface-3);
transition: background 0.15s;
}
.nilai-row:last-child { border-bottom:none; }
.nilai-row:hover { background: var(--surface-2); }
.nilai-mapel-dot {
width:9px; height:9px; border-radius:50%; flex-shrink:0;
background: var(--blue);
}
.nilai-mapel {
flex: 1;
font-size:13.5px; font-weight:600; color:var(--ink);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nilai-chip {
font-family:'JetBrains Mono',monospace;
font-size:13px; font-weight:700;
padding:3px 10px;
border-radius:var(--r-sm);
flex-shrink:0;
}
.nilai-chip.a { background:var(--green-soft); color:var(--green); border:1px solid var(--green-border); }
.nilai-chip.b { background:var(--blue-soft); color:var(--blue); border:1px solid var(--blue-border); }
.nilai-chip.c { background:var(--amber-soft); color:var(--amber); border:1px solid var(--amber-border); }
.nilai-chip.d { background:var(--red-soft); color:var(--red); border:1px solid var(--red-border); }
/* ─── ABSENSI LIST ───────────────────────────────────────── */
.absensi-list { padding: 6px 0; flex:1; }
.absensi-row {
display: flex; align-items: center; gap:12px;
padding: 13px 22px;
border-bottom: 1px solid var(--surface-3);
transition: background 0.15s;
}
.absensi-row:last-child { border-bottom:none; }
.absensi-row:hover { background: var(--surface-2); }
.ab-date-box {
width:40px; height:40px;
border-radius:var(--r-md);
background:var(--surface-2);
border:1px solid var(--surface-3);
display:flex; flex-direction:column;
align-items:center; justify-content:center;
flex-shrink:0;
}
.ab-date-d { font-size:14px; font-weight:700; color:var(--ink); line-height:1; }
.ab-date-m { font-size:9px; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:0.5px; margin-top:2px; }
.ab-info { flex:1; min-width:0; }
.ab-mapel { font-size:13px; font-weight:600; color:var(--ink); }
.ab-time { font-size:11.5px; color:var(--muted); margin-top:2px; font-family:'JetBrains Mono',monospace; }
.ab-badge {
font-size:11.5px; font-weight:700;
padding:4px 11px; border-radius:999px;
flex-shrink:0; display:inline-flex; align-items:center; gap:5px;
}
.ab-badge::before {
content:''; width:6px; height:6px; border-radius:50%;
}
.ab-badge.hadir { background:var(--green-soft); color:var(--green); border:1px solid var(--green-border); }
.ab-badge.hadir::before { background:var(--green); }
.ab-badge.izin { background:var(--amber-soft); color:var(--amber); border:1px solid var(--amber-border); }
.ab-badge.izin::before { background:var(--amber); }
.ab-badge.sakit { background:var(--blue-soft); color:var(--blue); border:1px solid var(--blue-border); }
.ab-badge.sakit::before { background:var(--blue); }
.ab-badge.alpha { background:var(--red-soft); color:var(--red); border:1px solid var(--red-border); }
.ab-badge.alpha::before { background:var(--red); }
/* ─── EMPTY STATE ────────────────────────────────────────── */
.empty-state {
padding:42px 24px; text-align:center;
}
.empty-icon {
width:52px; height:52px;
background:var(--surface-2); border:1px solid var(--surface-3);
border-radius:50%; display:flex; align-items:center; justify-content:center;
margin:0 auto 12px;
}
.empty-icon svg { width:23px; height:23px; color:var(--muted); }
.empty-text { font-size:13px; color:var(--muted); font-weight:500; }
/* ─── RATA NILAI PROGRESS ────────────────────────────────── */
.avg-band {
background: var(--surface);
border: 1px solid var(--surface-3);
border-radius: var(--r-xl);
box-shadow: var(--shadow-card);
padding: 22px 24px;
margin-bottom: 18px;
display: flex; align-items: center; gap: 24px;
}
.avg-circle {
width:88px; height:88px;
border-radius:50%; flex-shrink:0;
background: conic-gradient(
var(--blue) calc(var(--pct) * 1%),
var(--surface-3) 0
);
display:flex; align-items:center; justify-content:center;
position:relative;
}
.avg-circle::before {
content:'';
position:absolute;
width:68px; height:68px;
border-radius:50%;
background: var(--surface);
}
.avg-circle-val {
position:relative; z-index:1;
font-size:20px; font-weight:800;
color:var(--ink); font-family:'JetBrains Mono',monospace;
letter-spacing:-0.5px;
}
.avg-info { flex:1; }
.avg-label { font-size:12px; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:0.7px; margin-bottom:6px; }
.avg-title { font-size:19px; font-weight:800; color:var(--ink); letter-spacing:-0.4px; margin-bottom:8px; }
.avg-bar-wrap { background:var(--surface-3); border-radius:999px; height:7px; overflow:hidden; }
.avg-bar {
height:100%; border-radius:999px;
background: linear-gradient(90deg, var(--blue), #60a5fa);
width: calc(var(--pct) * 1%);
transition: width 1.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.avg-foot { display:flex; align-items:center; justify-content:space-between; margin-top:6px; }
.avg-foot-text { font-size:12px; color:var(--muted); }
.avg-foot-pct { font-size:12px; font-weight:700; color:var(--blue); font-family:'JetBrains Mono',monospace; }
/* ─── RESPONSIVE ─────────────────────────────────────────── */
@media(max-width:1024px) {
.stats-grid { grid-template-columns:repeat(2,1fr); }
}
@media(max-width:768px) {
.db { padding:16px 16px 40px; }
.greeting-band { grid-template-columns:1fr; }
.greeting-date-box { display:none; }
.greeting-name { font-size:22px; }
.stats-grid { grid-template-columns:repeat(2,1fr); }
.main-cols { grid-template-columns:1fr; }
}
@media(max-width:480px) {
.stats-grid { grid-template-columns:1fr 1fr; gap:10px; }
.stat-value { font-size:24px; }
}
/* ─── ANIMATION ──────────────────────────────────────────── */
.db > * { animation: rise 0.5s ease both; }
.db > *:nth-child(1) { animation-delay:0s; }
.db > *:nth-child(2) { animation-delay:0.07s; }
.db > *:nth-child(3) { animation-delay:0.14s; }
.db > *:nth-child(4) { animation-delay:0.20s; }
@keyframes rise {
from { opacity:0; transform:translateY(16px); }
to { opacity:1; transform:translateY(0); }
}
.stats-grid .stat-card { animation: rise 0.5s ease both; }
.stats-grid .stat-card:nth-child(1) { animation-delay:0.08s; }
.stats-grid .stat-card:nth-child(2) { animation-delay:0.14s; }
.stats-grid .stat-card:nth-child(3) { animation-delay:0.20s; }
.stats-grid .stat-card:nth-child(4) { animation-delay:0.26s; }
</style>
<div class="db">
{{-- ── GREETING BAND ────────────────────────────── --}}
<div class="greeting-band">
<div class="greeting-text">
<div class="greeting-tag">
<span></span> Portal Akademik Siswa
</div>
<h1 class="greeting-name">
Selamat datang,<br>
<em>{{ auth()->user()->name ?? 'Siswa' }}</em>
</h1>
<p class="greeting-sub">Pantau nilai, absensi, dan progres belajarmu hari ini.</p>
</div>
<div class="greeting-date-box">
<div class="date-day" id="clockDay">—</div>
<div class="date-month" id="clockMonth">—</div>
<div class="date-time" id="clockTime">00:00:00</div>
</div>
</div>
</div>
{{-- ── STATS ROW ─────────────────────────────────── --}}
<div class="stats-grid">
{{-- Rata-rata Nilai --}}
<div class="stat-card blue">
<div class="stat-icon-wrap">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<div class="stat-label">Rata-rata Nilai</div>
<div class="stat-value mono">{{ $rataNilai ? number_format($rataNilai, 1) : '—' }}</div>
<div class="stat-sub">dari 100 poin</div>
</div>
{{-- Hadir --}}
<div class="stat-card green">
<div class="stat-icon-wrap">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<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>
</div>
<div class="stat-label">Total Hadir</div>
<div class="stat-value">{{ $hadir }}</div>
<div class="stat-sub">pertemuan tercatat</div>
</div>
{{-- Tugas --}}
<div class="stat-card amber">
<div class="stat-icon-wrap">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
</div>
<div class="stat-label">Tugas</div>
<div class="stat-value">{{ $tugas }}</div>
<div class="stat-sub">tugas diberikan</div>
</div>
{{-- Mata Pelajaran --}}
<div class="stat-card violet">
<div class="stat-icon-wrap">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
</svg>
</div>
<div class="stat-label">Mata Pelajaran</div>
<div class="stat-value">{{ $nilaiTerbaru->pluck('mapel_id')->unique()->count() }}</div>
<div class="stat-sub">mapel dinilai</div>
</div>
</div>
{{-- ── RATA NILAI BAR ────────────────────────────── --}}
@php $pct = $rataNilai ? min(round($rataNilai), 100) : 0; @endphp
<div class="avg-band" style="--pct:{{ $pct }};">
<div class="avg-circle" style="--pct:{{ $pct }};">
<span class="avg-circle-val">{{ $pct }}%</span>
</div>
<div class="avg-info">
<div class="avg-label">Progres Akademik</div>
<div class="avg-title">
@if($pct >= 85) Prestasi Sangat Baik ⭐
@elseif($pct >= 70) Performa Baik 👍
@elseif($pct >= 55) Cukup, Terus Semangat 💪
@else Butuh Peningkatan 📚
@endif
</div>
<div class="avg-bar-wrap">
<div class="avg-bar"></div>
</div>
<div class="avg-foot">
<span class="avg-foot-text">Rata-rata dari semua mata pelajaran</span>
<span class="avg-foot-pct">{{ $rataNilai ? number_format($rataNilai, 1) : '0' }} / 100</span>
</div>
</div>
</div>
{{-- ── 2 COLUMNS ─────────────────────────────────── --}}
<div class="main-cols">
{{-- NILAI TERBARU --}}
<div class="section-card">
<div class="sc-head">
<div class="sc-head-left">
<div class="sc-icon blue">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<span class="sc-title">Nilai Terbaru</span>
</div>
<span class="sc-badge">5 terkini</span>
</div>
<div class="nilai-list">
@forelse($nilaiTerbaru as $n)
@php
$v = $n->nilai;
$cls = $v >= 85 ? 'a' : ($v >= 70 ? 'b' : ($v >= 55 ? 'c' : 'd'));
$colors = ['a'=>'#059669','b'=>'#2563eb','c'=>'#d97706','d'=>'#dc2626'];
@endphp
<div class="nilai-row">
<div class="nilai-mapel-dot" style="background:{{ $colors[$cls] }};"></div>
<span class="nilai-mapel">{{ $n->mapel->nama_mapel ?? '—' }}</span>
<span class="nilai-chip {{ $cls }}">{{ $v }}</span>
</div>
@empty
<div class="empty-state">
<div class="empty-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<p class="empty-text">Belum ada data nilai</p>
</div>
@endforelse
</div>
</div>
{{-- ABSENSI TERAKHIR --}}
<div class="section-card">
<div class="sc-head">
<div class="sc-head-left">
<div class="sc-icon green">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
</svg>
</div>
<span class="sc-title">Riwayat Absensi</span>
</div>
<span class="sc-badge">5 terkini</span>
</div>
<div class="absensi-list">
@forelse($absensiTerakhir as $a)
@php
$tgl = \Carbon\Carbon::parse($a->created_at);
$statusClass = in_array($a->status, ['hadir','izin','sakit','alpha']) ? $a->status : 'hadir';
$statusLabel = ucfirst($a->status);
@endphp
<div class="absensi-row">
<div class="ab-date-box">
<div class="ab-date-d">{{ $tgl->format('d') }}</div>
<div class="ab-date-m">{{ $tgl->format('M') }}</div>
</div>
<div class="ab-info">
<div class="ab-mapel">Absensi Kelas</div>
<div class="ab-time">{{ $tgl->format('H:i') }} WIB</div>
</div>
<span class="ab-badge {{ $statusClass }}">{{ $statusLabel }}</span>
</div>
@empty
<div class="empty-state">
<div class="empty-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
</div>
<p class="empty-text">Belum ada data absensi</p>
</div>
@endforelse
</div>
</div>
</div>{{-- /.main-cols --}}
</div>{{-- /.db --}}
<script>
// Live clock
function tick() {
const now = new Date();
const days = ['Minggu','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'];
const months = ['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Agu','Sep','Okt','Nov','Des'];
document.getElementById('clockDay').textContent = String(now.getDate()).padStart(2,'0');
document.getElementById('clockMonth').textContent = months[now.getMonth()] + ' ' + now.getFullYear();
document.getElementById('clockTime').textContent =
String(now.getHours()).padStart(2,'0') + ':' +
String(now.getMinutes()).padStart(2,'0') + ':' +
String(now.getSeconds()).padStart(2,'0');
}
tick(); setInterval(tick, 1000);
</script>
@endsection

View File

@ -1,38 +1,586 @@
@extends('layouts.app')
@section('content')
<div class="container">
<h3>Jadwal Siswa</h3>
<style>
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');
@if($jadwal->isEmpty())
<div class="alert alert-warning">
Jadwal belum tersedia
:root {
--ink: #0c0f1a;
--text-sec: #475569;
--text-muted: #94a3b8;
--surface: #ffffff;
--surface-2: #f6f8fc;
--surface-3: #eef1f8;
--border: #e2e8f0;
--blue: #2563eb;
--blue-dark: #1d4ed8;
--blue-soft: #eff6ff;
--blue-border: #bfdbfe;
--green: #059669;
--green-soft: #ecfdf5;
--green-border:#a7f3d0;
--violet: #7c3aed;
--violet-soft: #f5f3ff;
--violet-border:#ddd6fe;
--amber: #d97706;
--amber-soft: #fffbeb;
--amber-border:#fde68a;
--r-md: 10px;
--r-lg: 16px;
--r-xl: 22px;
--r-2xl: 28px;
--shadow-sm: 0 1px 4px rgba(12,15,26,0.05);
--shadow-card: 0 4px 20px rgba(12,15,26,0.07), 0 1px 4px rgba(12,15,26,0.04);
--shadow-hover:0 10px 32px rgba(12,15,26,0.11), 0 2px 8px rgba(12,15,26,0.05);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
.jadwal-page {
font-family: 'Sora', sans-serif;
padding: 28px 28px 52px;
background: var(--surface-2);
min-height: 100vh;
}
/* ── PAGE HEADER ─────────────────────────────────── */
.page-hero {
background: var(--ink);
border-radius: var(--r-2xl);
padding: 28px 32px;
margin-bottom: 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
position: relative;
overflow: hidden;
}
.page-hero::before {
content: '';
position: absolute; inset: 0;
background:
radial-gradient(ellipse 55% 90% at 100% 50%, rgba(37,99,235,0.20) 0%, transparent 65%),
radial-gradient(ellipse 35% 60% at 0% 0%, rgba(124,58,237,0.12) 0%, transparent 55%);
pointer-events: none;
}
.hero-left { position: relative; z-index: 1; }
.hero-tag {
display: inline-flex; align-items: center; gap: 7px;
background: rgba(255,255,255,0.08);
border: 1px solid rgba(255,255,255,0.13);
border-radius: 999px;
padding: 4px 14px;
font-size: 11.5px; font-weight: 600;
color: rgba(255,255,255,0.65);
letter-spacing: 0.6px; text-transform: uppercase;
margin-bottom: 12px;
}
.hero-tag-dot {
width: 7px; height: 7px; border-radius: 50%;
background: #22c55e;
box-shadow: 0 0 7px #22c55e;
}
.hero-title {
font-size: 24px; font-weight: 800;
color: #fff; letter-spacing: -0.4px;
line-height: 1.2;
}
.hero-title em { font-style: normal; color: #93c5fd; }
.hero-sub {
font-size: 13px; color: rgba(255,255,255,0.48);
margin-top: 6px;
}
.hero-right {
position: relative; z-index: 1;
display: flex; align-items: center; gap: 12px;
flex-shrink: 0;
}
.hero-stat {
background: rgba(255,255,255,0.07);
border: 1px solid rgba(255,255,255,0.11);
border-radius: var(--r-lg);
padding: 14px 20px;
text-align: center;
backdrop-filter: blur(6px);
}
.hero-stat-val {
font-size: 28px; font-weight: 800; color: #fff;
letter-spacing: -1px; line-height: 1;
font-family: 'JetBrains Mono', monospace;
}
.hero-stat-lbl {
font-size: 11px; font-weight: 600;
color: rgba(255,255,255,0.45);
text-transform: uppercase; letter-spacing: 0.7px;
margin-top: 4px;
}
/* ── HARI FILTER TABS ────────────────────────────── */
.hari-tabs {
display: flex; gap: 8px; flex-wrap: wrap;
margin-bottom: 20px;
}
.hari-tab {
padding: 7px 16px;
font-size: 12.5px; font-weight: 600;
font-family: 'Sora', sans-serif;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text-sec);
cursor: pointer;
transition: all 0.15s;
user-select: none;
}
.hari-tab:hover { border-color: var(--blue); color: var(--blue); background: var(--blue-soft); }
.hari-tab.active { background: var(--blue); color: #fff; border-color: var(--blue); box-shadow: 0 2px 10px rgba(37,99,235,0.3); }
/* ── EMPTY STATE ─────────────────────────────────── */
.empty-wrap {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-xl);
padding: 64px 24px;
text-align: center;
box-shadow: var(--shadow-card);
}
.empty-icon {
width: 60px; height: 60px;
background: var(--surface-2); border: 1px solid var(--border);
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
margin: 0 auto 16px;
}
.empty-icon svg { width: 26px; height: 26px; color: var(--text-muted); }
.empty-title { font-size: 15px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
.empty-desc { font-size: 13px; color: var(--text-muted); }
/* ── DESKTOP TABLE ───────────────────────────────── */
.table-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-xl);
box-shadow: var(--shadow-card);
overflow: hidden;
}
.table-toolbar {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 24px 14px;
border-bottom: 1px solid var(--border);
background: var(--surface);
flex-wrap: wrap; gap: 12px;
}
.toolbar-title {
font-size: 14px; font-weight: 700; color: var(--ink);
display: flex; align-items: center; gap: 10px;
}
.toolbar-icon {
width: 34px; height: 34px;
border-radius: var(--r-md);
background: var(--blue-soft); border: 1px solid var(--blue-border);
display: flex; align-items: center; justify-content: center;
}
.toolbar-icon svg { width: 16px; height: 16px; color: var(--blue); }
.toolbar-badge {
font-size: 11.5px; font-weight: 600;
background: var(--surface-3); border: 1px solid var(--border);
color: var(--text-muted); border-radius: 999px;
padding: 2px 10px;
}
.table-responsive { overflow-x: auto; }
table.jadwal-table {
width: 100%; border-collapse: collapse;
min-width: 580px;
}
.jadwal-table thead tr {
background: var(--surface-2);
border-bottom: 1px solid var(--border);
}
.jadwal-table thead th {
padding: 12px 18px;
font-size: 11px; font-weight: 700;
color: var(--text-muted);
text-transform: uppercase; letter-spacing: 0.8px;
text-align: left; white-space: nowrap;
}
.jadwal-table thead th.center { text-align: center; }
.jadwal-table tbody tr {
border-bottom: 1px solid var(--border);
transition: background 0.15s;
}
.jadwal-table tbody tr:last-child { border-bottom: none; }
.jadwal-table tbody tr:hover { background: #f8faff; }
.jadwal-table tbody td {
padding: 15px 18px;
font-size: 13.5px; color: var(--ink);
vertical-align: middle;
}
.jadwal-table tbody td.center { text-align: center; }
/* NO CELL */
.cell-no {
font-family: 'JetBrains Mono', monospace;
font-size: 13px; color: var(--text-muted);
font-weight: 500; width: 50px;
}
/* HARI BADGE */
@php
$hariColor = [
'Senin' => ['bg'=>'#eff6ff','color'=>'#2563eb','border'=>'#bfdbfe'],
'Selasa' => ['bg'=>'#f5f3ff','color'=>'#7c3aed','border'=>'#ddd6fe'],
'Rabu' => ['bg'=>'#ecfdf5','color'=>'#059669','border'=>'#a7f3d0'],
'Kamis' => ['bg'=>'#fffbeb','color'=>'#d97706','border'=>'#fde68a'],
'Jumat' => ['bg'=>'#fef2f2','color'=>'#dc2626','border'=>'#fecaca'],
'Sabtu' => ['bg'=>'#fff7ed','color'=>'#ea580c','border'=>'#fed7aa'],
];
@endphp
.badge-hari {
display: inline-flex; align-items: center;
border-radius: 999px;
font-size: 12px; font-weight: 700;
padding: 4px 13px;
white-space: nowrap;
}
/* MAPEL */
.mapel-cell {
display: flex; align-items: center; gap: 10px;
}
.mapel-dot {
width: 8px; height: 8px; border-radius: 50%;
flex-shrink: 0;
background: var(--blue);
}
.mapel-name {
font-weight: 600; color: var(--ink);
}
/* GURU */
.guru-pill {
display: inline-flex; align-items: center; gap: 7px;
background: var(--surface-2); border: 1px solid var(--border);
border-radius: 999px;
padding: 5px 13px;
font-size: 12.5px; color: var(--text-sec); font-weight: 500;
}
.guru-avatar {
width: 22px; height: 22px; border-radius: 50%;
background: var(--blue-soft); border: 1px solid var(--blue-border);
display: flex; align-items: center; justify-content: center;
font-size: 9px; font-weight: 700; color: var(--blue);
flex-shrink: 0;
}
/* JAM */
.jam-pill {
display: inline-flex; align-items: center; gap: 6px;
background: var(--green-soft); border: 1px solid var(--green-border);
border-radius: 999px;
padding: 5px 13px;
font-size: 12.5px; font-weight: 700; color: var(--green);
font-family: 'JetBrains Mono', monospace;
white-space: nowrap;
}
.jam-pill svg { width: 13px; height: 13px; }
/* TABLE FOOTER */
.table-footer {
display: flex; align-items: center; justify-content: space-between;
padding: 13px 24px;
border-top: 1px solid var(--border);
background: var(--surface-2);
font-size: 12.5px; color: var(--text-muted);
flex-wrap: wrap; gap: 6px;
}
/* ── MOBILE CARDS ─────────────────────────────────── */
.mobile-list { display: flex; flex-direction: column; gap: 12px; }
.m-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-xl);
box-shadow: var(--shadow-sm);
padding: 18px;
position: relative;
overflow: hidden;
transition: box-shadow 0.2s, transform 0.2s;
}
.m-card:hover {
box-shadow: var(--shadow-hover);
transform: translateY(-2px);
}
.m-card::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 4px;
background: var(--blue);
border-radius: 4px 0 0 4px;
}
.m-card-top {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 12px;
}
.m-card-mapel {
font-size: 15px; font-weight: 700; color: var(--ink);
line-height: 1.2;
}
.m-card-meta {
display: flex; align-items: center; gap: 10px;
flex-wrap: wrap;
margin-top: 10px;
}
/* ── ANIMATION ────────────────────────────────────── */
.jadwal-page > * { animation: rise 0.45s ease both; }
.jadwal-page > *:nth-child(1) { animation-delay: 0s; }
.jadwal-page > *:nth-child(2) { animation-delay: 0.06s; }
.jadwal-page > *:nth-child(3) { animation-delay: 0.12s; }
.jadwal-page > *:nth-child(4) { animation-delay: 0.18s; }
@keyframes rise {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── RESPONSIVE ───────────────────────────────────── */
@media (max-width: 768px) {
.jadwal-page { padding: 16px 16px 48px; }
.page-hero { padding: 22px 20px; }
.hero-title { font-size: 20px; }
.hero-right { display: none; }
}
</style>
<div class="jadwal-page">
{{-- ── HERO ──────────────────────────────────────── --}}
<div class="page-hero">
<div class="hero-left">
<div class="hero-tag">
<span class="hero-tag-dot"></span>
Akademik Siswa
</div>
<h1 class="hero-title">Jadwal <em>Pelajaran</em></h1>
<p class="hero-sub">Semua jadwal mata pelajaran kamu tersedia di sini</p>
</div>
<div class="hero-right">
<div class="hero-stat">
<div class="hero-stat-val">{{ $jadwal->count() }}</div>
<div class="hero-stat-lbl">Total Sesi</div>
</div>
<div class="hero-stat">
<div class="hero-stat-val">{{ $jadwal->unique('hari')->count() }}</div>
<div class="hero-stat-lbl">Hari Aktif</div>
</div>
</div>
</div>
@endif
<table class="table table-bordered">
<tr>
<th>No</th>
<th>Hari</th>
<th>Mapel</th>
<th>Guru</th>
<th>Jam</th>
</tr>
{{-- ── HARI FILTER TABS ───────────────────────────── --}}
@if(!$jadwal->isEmpty())
<div class="hari-tabs">
<div class="hari-tab active" onclick="filterHari('semua', this)">Semua</div>
@foreach($jadwal->unique('hari') as $jh)
<div class="hari-tab" onclick="filterHari('{{ $jh->hari }}', this)">{{ $jh->hari }}</div>
@endforeach
</div>
@endif
@foreach($jadwal as $j)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $j->hari }}</td>
<td>{{ $j->mapel->nama_mapel ?? '-' }}</td>
<td>{{ $j->guru->nama ?? '-' }}</td>
<td>
{{ $j->jam_mulai }} - {{ $j->jam_selesai }}
</td>
</tr>
@endforeach
{{-- ── EMPTY STATE ────────────────────────────────── --}}
@if($jadwal->isEmpty())
<div class="empty-wrap">
<div class="empty-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
</div>
<div class="empty-title">Jadwal Belum Tersedia</div>
<div class="empty-desc">Hubungi wali kelas atau admin untuk informasi jadwal.</div>
</div>
@else
</table>
{{-- ── DESKTOP TABLE ──────────────────────────────── --}}
<div class="table-card d-none d-md-block" id="desktopTable">
<div class="table-toolbar">
<div class="toolbar-title">
<div class="toolbar-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
</div>
Daftar Jadwal
<span class="toolbar-badge">{{ $jadwal->count() }} sesi</span>
</div>
</div>
<div class="table-responsive">
<table class="jadwal-table" id="jadwalTable">
<thead>
<tr>
<th>No</th>
<th>Hari</th>
<th>Mata Pelajaran</th>
<th>Guru</th>
<th class="center">Jam Mengajar</th>
</tr>
</thead>
<tbody>
@foreach($jadwal as $j)
@php
$hc = $hariColor[$j->hari] ?? ['bg'=>'#f1f5f9','color'=>'#64748b','border'=>'#e2e8f0'];
$inisial = strtoupper(substr($j->guru->nama ?? 'G', 0, 2));
@endphp
<tr data-hari="{{ $j->hari }}">
<td class="cell-no">{{ $loop->iteration }}</td>
<td>
<span class="badge-hari"
style="background:{{ $hc['bg'] }};color:{{ $hc['color'] }};border:1px solid {{ $hc['border'] }};">
{{ $j->hari }}
</span>
</td>
<td>
<div class="mapel-cell">
<div class="mapel-dot" style="background:{{ $hc['color'] }};"></div>
<span class="mapel-name">{{ $j->mapel->nama_mapel ?? '—' }}</span>
</div>
</td>
<td>
<div class="guru-pill">
<div class="guru-avatar">{{ $inisial }}</div>
{{ $j->guru->nama ?? '—' }}
</div>
</td>
<td class="center">
<span class="jam-pill">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
{{ $j->jam_mulai }} – {{ $j->jam_selesai }}
</span>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="table-footer">
<span id="footerCount">Menampilkan {{ $jadwal->count() }} jadwal</span>
<span>{{ now()->format('l, d F Y') }}</span>
</div>
</div>
{{-- ── MOBILE CARDS ────────────────────────────────── --}}
<div class="mobile-list d-block d-md-none" id="mobileList">
@foreach($jadwal as $j)
@php
$hc = $hariColor[$j->hari] ?? ['bg'=>'#f1f5f9','color'=>'#64748b','border'=>'#e2e8f0'];
$inisial = strtoupper(substr($j->guru->nama ?? 'G', 0, 2));
@endphp
<div class="m-card" data-hari="{{ $j->hari }}"
style="--accent: {{ $hc['color'] }};">
<style>.m-card[data-hari="{{ $j->hari }}"]:first-of-type::before { background: {{ $hc['color'] }}; }</style>
<div class="m-card-top">
<span class="badge-hari"
style="background:{{ $hc['bg'] }};color:{{ $hc['color'] }};border:1px solid {{ $hc['border'] }};">
{{ $j->hari }}
</span>
<span class="jam-pill" style="font-size:12px;padding:4px 11px;">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="width:12px;height:12px;">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
{{ $j->jam_mulai }} – {{ $j->jam_selesai }}
</span>
</div>
<div class="m-card-mapel">{{ $j->mapel->nama_mapel ?? '—' }}</div>
<div class="m-card-meta">
<div class="guru-pill" style="font-size:12px;">
<div class="guru-avatar">{{ $inisial }}</div>
{{ $j->guru->nama ?? '—' }}
</div>
</div>
</div>
@endforeach
</div>
@endif {{-- end if not empty --}}
</div>
<script>
function filterHari(hari, el) {
// update tab active
document.querySelectorAll('.hari-tab').forEach(t => t.classList.remove('active'));
el.classList.add('active');
// filter desktop rows
document.querySelectorAll('#jadwalTable tbody tr').forEach(row => {
row.style.display = (hari === 'semua' || row.dataset.hari === hari) ? '' : 'none';
});
// filter mobile cards
document.querySelectorAll('#mobileList .m-card').forEach(card => {
card.style.display = (hari === 'semua' || card.dataset.hari === hari) ? '' : 'none';
});
// update footer count
const visible = [...document.querySelectorAll('#jadwalTable tbody tr')]
.filter(r => r.style.display !== 'none').length;
const el2 = document.getElementById('footerCount');
if (el2) el2.textContent = 'Menampilkan ' + visible + ' jadwal';
}
</script>
@endsection

View File

@ -1,26 +1,585 @@
@extends('layouts.app')
@section('content')
<div class="container">
<h3>Data Nilai</h3>
<style>
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');
<table class="table table-bordered">
<tr>
<th>No</th>
<th>Mapel</th>
<th>Nilai</th>
</tr>
:root {
--ink: #0c0f1a;
--text-sec: #475569;
--text-muted: #94a3b8;
--surface: #ffffff;
--surface-2: #f6f8fc;
--surface-3: #eef1f8;
--border: #e2e8f0;
@foreach($nilai as $n)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $n->mapel->nama_mapel }}</td>
<td>{{ $n->nilai }}</td>
</tr>
@endforeach
--blue: #2563eb;
--blue-dark: #1d4ed8;
--blue-soft: #eff6ff;
--blue-border: #bfdbfe;
</table>
--green: #059669;
--green-soft: #ecfdf5;
--green-border: #a7f3d0;
--amber: #d97706;
--amber-soft: #fffbeb;
--amber-border: #fde68a;
--violet: #7c3aed;
--violet-soft: #f5f3ff;
--violet-border:#ddd6fe;
--red: #dc2626;
--red-soft: #fef2f2;
--red-border: #fecaca;
--r-md: 10px;
--r-lg: 16px;
--r-xl: 22px;
--r-2xl: 28px;
--shadow-card: 0 4px 20px rgba(12,15,26,0.07), 0 1px 4px rgba(12,15,26,0.04);
--shadow-hover:0 10px 32px rgba(12,15,26,0.11), 0 2px 8px rgba(12,15,26,0.05);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
.nilai-page {
font-family: 'Sora', sans-serif;
padding: 28px 28px 52px;
background: var(--surface-2);
min-height: 100vh;
}
/* ── ANIMATIONS ───────────────────────────────────── */
.nilai-page > * { animation: rise 0.45s ease both; }
.nilai-page > *:nth-child(1) { animation-delay: 0s; }
.nilai-page > *:nth-child(2) { animation-delay: 0.07s; }
.nilai-page > *:nth-child(3) { animation-delay: 0.14s; }
.nilai-page > *:nth-child(4) { animation-delay: 0.20s; }
@keyframes rise {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── HERO BANNER ──────────────────────────────────── */
.page-hero {
background: var(--ink);
border-radius: var(--r-2xl);
padding: 28px 32px;
margin-bottom: 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
position: relative;
overflow: hidden;
}
.page-hero::before {
content: '';
position: absolute; inset: 0;
background:
radial-gradient(ellipse 55% 90% at 100% 50%, rgba(37,99,235,0.22) 0%, transparent 65%),
radial-gradient(ellipse 35% 60% at 0% 0%, rgba(124,58,237,0.13) 0%, transparent 55%);
pointer-events: none;
}
.hero-left { position: relative; z-index: 1; }
.hero-tag {
display: inline-flex; align-items: center; gap: 7px;
background: rgba(255,255,255,0.08);
border: 1px solid rgba(255,255,255,0.13);
border-radius: 999px;
padding: 4px 14px;
font-size: 11.5px; font-weight: 600;
color: rgba(255,255,255,0.6);
letter-spacing: 0.6px; text-transform: uppercase;
margin-bottom: 12px;
}
.hero-tag-dot {
width: 7px; height: 7px; border-radius: 50%;
background: #22c55e;
box-shadow: 0 0 7px #22c55e;
}
.hero-title {
font-size: 24px; font-weight: 800;
color: #fff; letter-spacing: -0.4px; line-height: 1.2;
}
.hero-title em { font-style: normal; color: #93c5fd; }
.hero-sub {
font-size: 13px; color: rgba(255,255,255,0.45);
margin-top: 6px;
}
.hero-right {
position: relative; z-index: 1;
display: flex; align-items: center; gap: 12px;
flex-shrink: 0;
}
.hero-stat {
background: rgba(255,255,255,0.07);
border: 1px solid rgba(255,255,255,0.11);
border-radius: var(--r-lg);
padding: 14px 20px;
text-align: center;
}
.hero-stat-val {
font-size: 28px; font-weight: 800; color: #fff;
letter-spacing: -1px; line-height: 1;
font-family: 'JetBrains Mono', monospace;
}
.hero-stat-lbl {
font-size: 11px; font-weight: 600;
color: rgba(255,255,255,0.42);
text-transform: uppercase; letter-spacing: 0.7px;
margin-top: 4px;
}
/* ── SUMMARY CARDS ────────────────────────────────── */
.summary-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin-bottom: 22px;
}
.sum-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-xl);
padding: 20px 18px;
box-shadow: var(--shadow-card);
position: relative;
overflow: hidden;
transition: box-shadow 0.2s, transform 0.2s;
}
.sum-card:hover {
box-shadow: var(--shadow-hover);
transform: translateY(-2px);
}
.sum-card::before {
content: '';
position: absolute; top: 0; left: 0; right: 0;
height: 3px; border-radius: var(--r-xl) var(--r-xl) 0 0;
}
.sum-card.blue::before { background: linear-gradient(90deg, var(--blue), #60a5fa); }
.sum-card.amber::before { background: linear-gradient(90deg, var(--amber), #fbbf24); }
.sum-card.green::before { background: linear-gradient(90deg, var(--green), #34d399); }
.sum-card.violet::before { background: linear-gradient(90deg, var(--violet), #a78bfa); }
.sum-icon {
width: 40px; height: 40px;
border-radius: var(--r-md);
display: flex; align-items: center; justify-content: center;
margin-bottom: 14px;
}
.sum-card.blue .sum-icon { background: var(--blue-soft); border: 1px solid var(--blue-border); }
.sum-card.amber .sum-icon { background: var(--amber-soft); border: 1px solid var(--amber-border); }
.sum-card.green .sum-icon { background: var(--green-soft); border: 1px solid var(--green-border); }
.sum-card.violet .sum-icon { background: var(--violet-soft); border: 1px solid var(--violet-border); }
.sum-icon svg { width: 18px; height: 18px; }
.sum-card.blue .sum-icon svg { color: var(--blue); }
.sum-card.amber .sum-icon svg { color: var(--amber); }
.sum-card.green .sum-icon svg { color: var(--green); }
.sum-card.violet .sum-icon svg { color: var(--violet); }
.sum-label {
font-size: 11px; font-weight: 700;
color: var(--text-muted);
text-transform: uppercase; letter-spacing: 0.7px;
margin-bottom: 4px;
}
.sum-value {
font-size: 26px; font-weight: 800;
color: var(--ink); letter-spacing: -0.8px;
font-family: 'JetBrains Mono', monospace;
line-height: 1;
}
.sum-sub {
font-size: 11.5px; color: var(--text-muted);
margin-top: 5px;
}
/* ── TABLE CARD ───────────────────────────────────── */
.table-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-xl);
box-shadow: var(--shadow-card);
overflow: hidden;
}
.table-toolbar {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 24px 14px;
border-bottom: 1px solid var(--border);
flex-wrap: wrap; gap: 12px;
}
.toolbar-title {
font-size: 14px; font-weight: 700; color: var(--ink);
display: flex; align-items: center; gap: 10px;
}
.toolbar-icon {
width: 34px; height: 34px;
border-radius: var(--r-md);
background: var(--blue-soft); border: 1px solid var(--blue-border);
display: flex; align-items: center; justify-content: center;
}
.toolbar-icon svg { width: 16px; height: 16px; color: var(--blue); }
.toolbar-badge {
font-size: 11.5px; font-weight: 600;
background: var(--surface-3); border: 1px solid var(--border);
color: var(--text-muted); border-radius: 999px;
padding: 2px 10px;
}
/* ── TABLE ────────────────────────────────────────── */
.table-responsive { overflow-x: auto; }
table.nilai-table {
width: 100%;
border-collapse: collapse;
min-width: 620px;
}
.nilai-table thead tr {
background: var(--surface-2);
border-bottom: 1px solid var(--border);
}
.nilai-table thead th {
padding: 12px 18px;
font-size: 11px; font-weight: 700;
color: var(--text-muted);
text-transform: uppercase; letter-spacing: 0.8px;
text-align: left; white-space: nowrap;
}
.nilai-table thead th.center { text-align: center; }
.nilai-table tbody tr {
border-bottom: 1px solid var(--border);
transition: background 0.15s;
}
.nilai-table tbody tr:last-child { border-bottom: none; }
.nilai-table tbody tr:hover { background: #f8faff; }
.nilai-table tbody td {
padding: 15px 18px;
font-size: 13.5px; color: var(--ink);
vertical-align: middle;
}
.nilai-table tbody td.center { text-align: center; }
/* NO CELL */
.cell-no {
font-family: 'JetBrains Mono', monospace;
font-size: 12.5px; color: var(--text-muted);
font-weight: 500; width: 50px;
}
/* MAPEL CELL */
.mapel-cell {
display: flex; align-items: center; gap: 10px;
}
.mapel-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--blue); flex-shrink: 0;
}
.mapel-name {
font-weight: 600; color: var(--ink);
}
/* NILAI CHIPS */
.chip {
display: inline-flex; align-items: center; justify-content: center;
min-width: 52px;
padding: 5px 13px;
border-radius: var(--r-md);
font-family: 'JetBrains Mono', monospace;
font-size: 13px; font-weight: 700;
}
.chip.tugas { background: var(--blue-soft); color: var(--blue); border: 1px solid var(--blue-border); }
.chip.uts { background: var(--amber-soft); color: var(--amber); border: 1px solid var(--amber-border); }
.chip.uas { background: var(--green-soft); color: var(--green); border: 1px solid var(--green-border); }
/* RATA CHIP — dynamic color */
.chip.rata-a { background: var(--green-soft); color: var(--green); border: 1px solid var(--green-border); }
.chip.rata-b { background: var(--blue-soft); color: var(--blue); border: 1px solid var(--blue-border); }
.chip.rata-c { background: var(--amber-soft); color: var(--amber); border: 1px solid var(--amber-border); }
.chip.rata-d { background: var(--red-soft); color: var(--red); border: 1px solid var(--red-border); }
/* PROGRESS BAR inline */
.rata-wrap {
display: flex; align-items: center; gap: 10px;
}
.mini-bar {
flex: 1; height: 6px;
background: var(--surface-3);
border-radius: 999px;
overflow: hidden;
min-width: 60px;
}
.mini-bar-fill {
height: 100%; border-radius: 999px;
transition: width 0.8s cubic-bezier(0.16,1,0.3,1);
}
/* EMPTY */
.empty-state {
padding: 64px 24px; text-align: center;
}
.empty-icon {
width: 60px; height: 60px;
background: var(--surface-2); border: 1px solid var(--border);
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
margin: 0 auto 14px;
}
.empty-icon svg { width: 26px; height: 26px; color: var(--text-muted); }
.empty-title { font-size: 15px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
.empty-desc { font-size: 13px; color: var(--text-muted); }
/* TABLE FOOTER */
.table-footer {
display: flex; align-items: center; justify-content: space-between;
padding: 13px 24px;
border-top: 1px solid var(--border);
background: var(--surface-2);
font-size: 12.5px; color: var(--text-muted);
flex-wrap: wrap; gap: 6px;
}
/* ── RESPONSIVE ───────────────────────────────────── */
@media (max-width: 1024px) { .summary-grid { grid-template-columns: repeat(2,1fr); } }
@media (max-width: 768px) {
.nilai-page { padding: 16px 16px 48px; }
.page-hero { padding: 22px 20px; }
.hero-title { font-size: 20px; }
.hero-right { display: none; }
.summary-grid { grid-template-columns: repeat(2,1fr); gap: 10px; }
}
@media (max-width: 420px) {
.summary-grid { grid-template-columns: 1fr 1fr; }
}
</style>
<div class="nilai-page">
{{-- ── HERO ──────────────────────────────────────── --}}
<div class="page-hero">
<div class="hero-left">
<div class="hero-tag">
<span class="hero-tag-dot"></span>
Rekap Akademik
</div>
<h1 class="hero-title">Data <em>Nilai Siswa</em></h1>
<p class="hero-sub">Ringkasan nilai tugas, UTS, UAS, dan rata-rata per mata pelajaran</p>
</div>
<div class="hero-right">
<div class="hero-stat">
<div class="hero-stat-val">{{ count($data) }}</div>
<div class="hero-stat-lbl">Mata Pelajaran</div>
</div>
@php
$avgAll = collect($data)->avg('rata');
@endphp
<div class="hero-stat">
<div class="hero-stat-val">{{ $avgAll ? number_format($avgAll, 1) : '—' }}</div>
<div class="hero-stat-lbl">Rata-rata</div>
</div>
</div>
</div>
{{-- ── SUMMARY CARDS ──────────────────────────────── --}}
@php
$avgTugas = collect($data)->whereNotNull('tugas')->avg('tugas');
$avgUts = collect($data)->whereNotNull('uts')->avg('uts');
$avgUas = collect($data)->whereNotNull('uas')->avg('uas');
$avgRata = collect($data)->whereNotNull('rata')->avg('rata');
@endphp
<div class="summary-grid">
<div class="sum-card blue">
<div class="sum-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
</div>
<div class="sum-label">Avg. Tugas</div>
<div class="sum-value">{{ $avgTugas ? number_format($avgTugas,1) : '—' }}</div>
<div class="sum-sub">rata-rata nilai tugas</div>
</div>
<div class="sum-card amber">
<div class="sum-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
</div>
<div class="sum-label">Avg. UTS</div>
<div class="sum-value">{{ $avgUts ? number_format($avgUts,1) : '—' }}</div>
<div class="sum-sub">rata-rata nilai UTS</div>
</div>
<div class="sum-card green">
<div class="sum-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"/>
</svg>
</div>
<div class="sum-label">Avg. UAS</div>
<div class="sum-value">{{ $avgUas ? number_format($avgUas,1) : '—' }}</div>
<div class="sum-sub">rata-rata nilai UAS</div>
</div>
<div class="sum-card violet">
<div class="sum-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<div class="sum-label">Avg. Keseluruhan</div>
<div class="sum-value">{{ $avgRata ? number_format($avgRata,1) : '—' }}</div>
<div class="sum-sub">rata-rata semua mapel</div>
</div>
</div>
{{-- ── TABLE ──────────────────────────────────────── --}}
<div class="table-card">
<div class="table-toolbar">
<div class="toolbar-title">
<div class="toolbar-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
Rekap Nilai Per Mapel
<span class="toolbar-badge">{{ count($data) }} mapel</span>
</div>
</div>
<div class="table-responsive">
<table class="nilai-table">
<thead>
<tr>
<th>No</th>
<th>Mata Pelajaran</th>
<th class="center">Tugas</th>
<th class="center">UTS</th>
<th class="center">UAS</th>
<th>Rata-rata</th>
</tr>
</thead>
<tbody>
@forelse($data as $d)
@php
$rata = $d['rata'] ?? null;
$rataClass = !$rata ? 'rata-b'
: ($rata >= 85 ? 'rata-a'
: ($rata >= 70 ? 'rata-b'
: ($rata >= 55 ? 'rata-c' : 'rata-d')));
$barColor = !$rata ? '#2563eb'
: ($rata >= 85 ? '#059669'
: ($rata >= 70 ? '#2563eb'
: ($rata >= 55 ? '#d97706' : '#dc2626')));
$pct = $rata ? min($rata, 100) : 0;
@endphp
<tr>
<td class="cell-no">{{ $loop->iteration }}</td>
<td>
<div class="mapel-cell">
<div class="mapel-dot" style="background:{{ $barColor }};"></div>
<span class="mapel-name">{{ $d['mapel'] ?? '—' }}</span>
</div>
</td>
<td class="center">
<span class="chip tugas">{{ $d['tugas'] ?? '—' }}</span>
</td>
<td class="center">
<span class="chip uts">{{ $d['uts'] ?? '—' }}</span>
</td>
<td class="center">
<span class="chip uas">{{ $d['uas'] ?? '—' }}</span>
</td>
<td>
<div class="rata-wrap">
<span class="chip {{ $rataClass }}">{{ $rata ?? '—' }}</span>
@if($rata)
<div class="mini-bar">
<div class="mini-bar-fill"
style="width:{{ $pct }}%; background:{{ $barColor }};"></div>
</div>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6">
<div class="empty-state">
<div class="empty-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" 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 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
</div>
<div class="empty-title">Belum Ada Data Nilai</div>
<div class="empty-desc">Nilai akan muncul setelah guru menginput data penilaian.</div>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if(count($data) > 0)
<div class="table-footer">
<span>Menampilkan {{ count($data) }} mata pelajaran</span>
<span>{{ now()->format('d F Y') }}</span>
</div>
@endif
</div>
</div>
@endsection

View File

@ -1,92 +1,533 @@
@extends('layouts.app')
@section('content')
<div class="container">
<h4>Daftar Tugas</h4>
<style>
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap');
<table class="table table-bordered">
:root {
--ink: #0c0f1a;
--text-sec: #475569;
--text-muted: #94a3b8;
--surface: #ffffff;
--surface-2: #f6f8fc;
--surface-3: #eef1f8;
--border: #e2e8f0;
<thead>
<tr>
<th>Judul</th>
<th>Mapel</th>
<th>Deadline</th>
<th>Waktu Kumpul</th>
<th>Nilai</th>
<th>Komentar</th>
<th>Status</th>
<th>Aksi</th>
</tr>
</thead>
--blue: #2563eb;
--blue-dark: #1d4ed8;
--blue-soft: #eff6ff;
--blue-border: #bfdbfe;
<tbody>
@foreach($tugas as $t)
<tr>
--green: #059669;
--green-soft: #ecfdf5;
--green-border: #a7f3d0;
<td>{{ $t->judul }}</td>
<td>{{ $t->mapel->nama_mapel ?? '-' }}</td>
--amber: #d97706;
--amber-soft: #fffbeb;
--amber-border: #fde68a;
<td>
{{ \Carbon\Carbon::parse($t->deadline)->format('d M Y H:i') }}
</td>
--red: #dc2626;
--red-soft: #fef2f2;
--red-border: #fecaca;
<td>
@if($t->waktu_kumpul)
{{ \Carbon\Carbon::parse($t->waktu_kumpul)->format('d M Y H:i') }}
@else
-
@endif
</td>
--slate: #64748b;
--slate-soft: #f1f5f9;
--slate-border: #cbd5e1;
{{-- NILAI --}}
<td>
{{ $t->nilai ?? '-' }}
</td>
--r-md: 10px; --r-lg: 16px;
--r-xl: 22px; --r-2xl: 28px;
{{-- KOMENTAR --}}
<td>
{{ $t->komentar ?? '-' }}
</td>
--shadow-sm: 0 1px 4px rgba(12,15,26,0.05);
--shadow-card: 0 4px 20px rgba(12,15,26,0.07), 0 1px 4px rgba(12,15,26,0.04);
--shadow-hover:0 10px 32px rgba(12,15,26,0.11), 0 2px 8px rgba(12,15,26,0.05);
}
{{-- STATUS --}}
<td>
@if($t->status == 'tepat')
<span class="badge bg-success">Tepat</span>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@elseif($t->status == 'telat')
<span class="badge bg-warning text-dark">Telat</span>
.tugas-page {
font-family: 'Sora', sans-serif;
padding: 28px 28px 52px;
background: var(--surface-2);
min-height: 100vh;
}
@elseif($t->status == 'lewat')
<span class="badge bg-danger">Belum Kumpul</span>
/* ANIMATION */
.tugas-page > * { animation: rise 0.45s ease both; }
.tugas-page > *:nth-child(1) { animation-delay: 0s; }
.tugas-page > *:nth-child(2) { animation-delay: 0.07s; }
.tugas-page > *:nth-child(3) { animation-delay: 0.13s; }
.tugas-page > *:nth-child(4) { animation-delay: 0.19s; }
@keyframes rise {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
@else
<span class="badge bg-secondary">Belum</span>
@endif
</td>
/* ── HERO ─────────────────────────────────────────── */
.page-hero {
background: var(--ink);
border-radius: var(--r-2xl);
padding: 28px 32px;
margin-bottom: 24px;
display: flex; align-items: center;
justify-content: space-between;
gap: 20px; position: relative; overflow: hidden;
}
.page-hero::before {
content: ''; position: absolute; inset: 0;
background:
radial-gradient(ellipse 55% 90% at 100% 50%, rgba(37,99,235,0.22) 0%, transparent 65%),
radial-gradient(ellipse 35% 60% at 0% 0%, rgba(124,58,237,0.13) 0%, transparent 55%);
pointer-events: none;
}
.hero-left { position: relative; z-index: 1; }
.hero-tag {
display: inline-flex; align-items: center; gap: 7px;
background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.13);
border-radius: 999px; padding: 4px 14px;
font-size: 11.5px; font-weight: 600; color: rgba(255,255,255,0.6);
letter-spacing: 0.6px; text-transform: uppercase; margin-bottom: 12px;
}
.hero-tag-dot {
width: 7px; height: 7px; border-radius: 50%;
background: #22c55e; box-shadow: 0 0 7px #22c55e;
}
.hero-title {
font-size: 24px; font-weight: 800; color: #fff;
letter-spacing: -0.4px; line-height: 1.2;
}
.hero-title em { font-style: normal; color: #93c5fd; }
.hero-sub { font-size: 13px; color: rgba(255,255,255,0.45); margin-top: 6px; }
{{-- AKSI --}}
<td>
@if($t->file)
<a href="{{ route('guru.tugas.download', $t->id) }}"
class="btn btn-sm btn-success">
Download
</a>
@endif
.hero-right {
position: relative; z-index: 1;
display: flex; align-items: center; gap: 12px; flex-shrink: 0;
}
.hero-stat {
background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.11);
border-radius: var(--r-lg); padding: 14px 20px; text-align: center;
}
.hero-stat-val {
font-size: 28px; font-weight: 800; color: #fff;
letter-spacing: -1px; line-height: 1;
font-family: 'JetBrains Mono', monospace;
}
.hero-stat-lbl {
font-size: 11px; font-weight: 600; color: rgba(255,255,255,0.42);
text-transform: uppercase; letter-spacing: 0.7px; margin-top: 4px;
}
@if(!$t->waktu_kumpul && now()->lt($t->deadline))
<a href="{{ route('siswa.tugas.kumpul', $t->id) }}"
class="btn btn-sm btn-primary">
Kumpulkan
</a>
@endif
</td>
/* ── STATS ROW ────────────────────────────────────── */
.stats-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px; margin-bottom: 22px;
}
.s-card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--r-xl); padding: 18px 16px;
box-shadow: var(--shadow-card);
position: relative; overflow: hidden;
transition: box-shadow 0.2s, transform 0.2s;
}
.s-card:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); }
.s-card::before {
content: ''; position: absolute; top: 0; left: 0; right: 0;
height: 3px; border-radius: var(--r-xl) var(--r-xl) 0 0;
}
.s-card.blue::before { background: linear-gradient(90deg,#2563eb,#60a5fa); }
.s-card.green::before { background: linear-gradient(90deg,#059669,#34d399); }
.s-card.amber::before { background: linear-gradient(90deg,#d97706,#fbbf24); }
.s-card.red::before { background: linear-gradient(90deg,#dc2626,#f87171); }
</tr>
@endforeach
</tbody>
.s-icon {
width: 38px; height: 38px; border-radius: var(--r-md);
display: flex; align-items: center; justify-content: center; margin-bottom: 12px;
}
.s-card.blue .s-icon { background: var(--blue-soft); border: 1px solid var(--blue-border); }
.s-card.green .s-icon { background: var(--green-soft); border: 1px solid var(--green-border); }
.s-card.amber .s-icon { background: var(--amber-soft); border: 1px solid var(--amber-border); }
.s-card.red .s-icon { background: var(--red-soft); border: 1px solid var(--red-border); }
.s-icon svg { width: 17px; height: 17px; }
.s-card.blue .s-icon svg { color: var(--blue); }
.s-card.green .s-icon svg { color: var(--green); }
.s-card.amber .s-icon svg { color: var(--amber); }
.s-card.red .s-icon svg { color: var(--red); }
</table>
.s-label { font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.7px; margin-bottom: 4px; }
.s-value { font-size: 24px; font-weight: 800; color: var(--ink); letter-spacing: -0.5px; font-family: 'JetBrains Mono', monospace; line-height: 1; }
.s-sub { font-size: 11.5px; color: var(--text-muted); margin-top: 4px; }
/* ── TABLE CARD ───────────────────────────────────── */
.table-card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--r-xl); box-shadow: var(--shadow-card); overflow: hidden;
}
.table-toolbar {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 24px 14px; border-bottom: 1px solid var(--border);
flex-wrap: wrap; gap: 12px;
}
.toolbar-title {
font-size: 14px; font-weight: 700; color: var(--ink);
display: flex; align-items: center; gap: 10px;
}
.toolbar-icon {
width: 34px; height: 34px; border-radius: var(--r-md);
background: var(--blue-soft); border: 1px solid var(--blue-border);
display: flex; align-items: center; justify-content: center;
}
.toolbar-icon svg { width: 16px; height: 16px; color: var(--blue); }
.toolbar-badge {
font-size: 11.5px; font-weight: 600;
background: var(--surface-3); border: 1px solid var(--border);
color: var(--text-muted); border-radius: 999px; padding: 2px 10px;
}
/* ── TABLE ────────────────────────────────────────── */
.table-responsive { overflow-x: auto; }
table.tugas-table { width: 100%; border-collapse: collapse; min-width: 720px; }
.tugas-table thead tr { background: var(--surface-2); border-bottom: 1px solid var(--border); }
.tugas-table thead th {
padding: 12px 18px; font-size: 11px; font-weight: 700;
color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.8px;
text-align: left; white-space: nowrap;
}
.tugas-table thead th.center { text-align: center; }
.tugas-table tbody tr { border-bottom: 1px solid var(--border); transition: background 0.15s; }
.tugas-table tbody tr:last-child { border-bottom: none; }
.tugas-table tbody tr:hover { background: #f8faff; }
.tugas-table tbody td { padding: 15px 18px; font-size: 13.5px; color: var(--ink); vertical-align: middle; }
.tugas-table tbody td.center { text-align: center; }
/* JUDUL CELL */
.judul-cell { font-weight: 700; color: var(--ink); line-height: 1.3; }
.mapel-pill {
display: inline-flex; align-items: center; gap: 6px;
background: var(--blue-soft); border: 1px solid var(--blue-border);
border-radius: 999px; padding: 3px 11px;
font-size: 12px; font-weight: 600; color: var(--blue);
}
/* DEADLINE */
.deadline-chip {
display: inline-flex; align-items: center; gap: 6px;
background: var(--amber-soft); border: 1px solid var(--amber-border);
border-radius: var(--r-md); padding: 5px 12px;
font-size: 12px; font-weight: 600; color: var(--amber);
font-family: 'JetBrains Mono', monospace; white-space: nowrap;
}
.deadline-chip svg { width: 12px; height: 12px; }
/* WAKTU KUMPUL */
.kumpul-chip {
display: inline-flex; align-items: center; gap: 6px;
background: var(--green-soft); border: 1px solid var(--green-border);
border-radius: var(--r-md); padding: 5px 12px;
font-size: 12px; font-weight: 600; color: var(--green);
font-family: 'JetBrains Mono', monospace; white-space: nowrap;
}
.kumpul-chip svg { width: 12px; height: 12px; }
.dash-text { color: var(--text-muted); font-size: 13px; }
/* NILAI */
.nilai-chip {
display: inline-flex; align-items: center; justify-content: center;
min-width: 48px; padding: 4px 12px; border-radius: var(--r-md);
font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 700;
background: var(--green-soft); color: var(--green); border: 1px solid var(--green-border);
}
/* STATUS BADGES */
.badge-status {
display: inline-flex; align-items: center; gap: 5px;
border-radius: 999px; font-size: 12px; font-weight: 700; padding: 4px 12px;
}
.badge-status::before { content: ''; width: 6px; height: 6px; border-radius: 50%; }
.badge-tepat { background: var(--green-soft); color: var(--green); border: 1px solid var(--green-border); }
.badge-tepat::before { background: var(--green); }
.badge-telat { background: var(--amber-soft); color: var(--amber); border: 1px solid var(--amber-border); }
.badge-telat::before { background: var(--amber); }
.badge-belum { background: var(--red-soft); color: var(--red); border: 1px solid var(--red-border); }
.badge-belum::before { background: var(--red); }
.badge-default{ background: var(--slate-soft); color: var(--slate); border: 1px solid var(--slate-border); }
.badge-default::before{ background: var(--slate); }
/* ACTION BUTTONS */
.action-group { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.btn-dl {
display: inline-flex; align-items: center; gap: 5px;
padding: 6px 13px; font-size: 12.5px; font-weight: 600;
font-family: 'Sora', sans-serif;
background: var(--green-soft); color: var(--green); border: 1px solid var(--green-border);
border-radius: var(--r-md); text-decoration: none; transition: all 0.15s;
}
.btn-dl:hover { background: #d1fae5; color: var(--green); text-decoration: none; }
.btn-dl svg { width: 13px; height: 13px; }
.btn-kumpul {
display: inline-flex; align-items: center; gap: 5px;
padding: 6px 13px; font-size: 12.5px; font-weight: 600;
font-family: 'Sora', sans-serif;
background: var(--blue); color: #fff; border: none;
border-radius: var(--r-md); text-decoration: none; transition: all 0.15s;
box-shadow: 0 2px 8px rgba(37,99,235,0.25);
}
.btn-kumpul:hover { background: var(--blue-dark); color: #fff; text-decoration: none; transform: translateY(-1px); }
.btn-kumpul svg { width: 13px; height: 13px; }
/* EMPTY */
.empty-state { padding: 64px 24px; text-align: center; }
.empty-icon {
width: 60px; height: 60px; background: var(--surface-2); border: 1px solid var(--border);
border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 14px;
}
.empty-icon svg { width: 26px; height: 26px; color: var(--text-muted); }
.empty-title { font-size: 15px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
.empty-desc { font-size: 13px; color: var(--text-muted); }
.table-footer {
display: flex; align-items: center; justify-content: space-between;
padding: 13px 24px; border-top: 1px solid var(--border);
background: var(--surface-2); font-size: 12.5px; color: var(--text-muted);
flex-wrap: wrap; gap: 6px;
}
/* ── MOBILE CARDS ─────────────────────────────────── */
.mobile-list { display: flex; flex-direction: column; gap: 14px; }
.m-card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--r-xl); box-shadow: var(--shadow-sm);
padding: 18px; position: relative; overflow: hidden;
transition: box-shadow 0.2s, transform 0.2s;
}
.m-card:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); }
.m-card::before {
content: ''; position: absolute; left: 0; top: 0; bottom: 0;
width: 4px; border-radius: 4px 0 0 4px; background: var(--blue);
}
.m-card-head {
display: flex; align-items: flex-start; justify-content: space-between;
gap: 10px; margin-bottom: 10px;
}
.m-card-judul { font-size: 14px; font-weight: 700; color: var(--ink); line-height: 1.3; }
.m-card-meta { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.m-card-foot { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.m-btn-full { width: 100%; justify-content: center; }
/* ── RESPONSIVE ───────────────────────────────────── */
@media (max-width: 1024px) { .stats-row { grid-template-columns: repeat(2,1fr); } }
@media (max-width: 768px) {
.tugas-page { padding: 16px 16px 48px; }
.page-hero { padding: 22px 20px; }
.hero-title { font-size: 20px; }
.hero-right { display: none; }
.stats-row { grid-template-columns: repeat(2,1fr); gap: 10px; }
}
</style>
<div class="tugas-page">
{{-- HERO --}}
<div class="page-hero">
<div class="hero-left">
<div class="hero-tag"><span class="hero-tag-dot"></span> Akademik Siswa</div>
<h1 class="hero-title">Daftar <em>Tugas</em></h1>
<p class="hero-sub">Pantau semua tugas, deadline, dan status pengumpulan kamu</p>
</div>
<div class="hero-right">
@php
$total = $tugas->count();
$sudah = $tugas->whereNotNull('waktu_kumpul')->count();
$belum = $total - $sudah;
@endphp
<div class="hero-stat">
<div class="hero-stat-val">{{ $total }}</div>
<div class="hero-stat-lbl">Total Tugas</div>
</div>
<div class="hero-stat">
<div class="hero-stat-val">{{ $belum }}</div>
<div class="hero-stat-lbl">Belum Kumpul</div>
</div>
</div>
</div>
{{-- STATS --}}
@php
$sudahKumpul = $tugas->whereNotNull('waktu_kumpul')->count();
$tepat = $tugas->where('status','tepat')->count();
$telat = $tugas->where('status','telat')->count();
$belumKumpul = $tugas->whereNull('waktu_kumpul')->count();
@endphp
<div class="stats-row">
<div class="s-card blue">
<div class="s-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
</div>
<div class="s-label">Total Tugas</div>
<div class="s-value">{{ $tugas->count() }}</div>
<div class="s-sub">semua tugas</div>
</div>
<div class="s-card green">
<div class="s-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<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>
</div>
<div class="s-label">Tepat Waktu</div>
<div class="s-value">{{ $tepat }}</div>
<div class="s-sub">dikumpulkan tepat</div>
</div>
<div class="s-card amber">
<div class="s-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="s-label">Telat</div>
<div class="s-value">{{ $telat }}</div>
<div class="s-sub">melewati deadline</div>
</div>
<div class="s-card red">
<div class="s-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="s-label">Belum Kumpul</div>
<div class="s-value">{{ $belumKumpul }}</div>
<div class="s-sub">perlu dikumpulkan</div>
</div>
</div>
{{-- DESKTOP TABLE --}}
<div class="table-card d-none d-md-block">
<div class="table-toolbar">
<div class="toolbar-title">
<div class="toolbar-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
</div>
Daftar Tugas
<span class="toolbar-badge">{{ $tugas->count() }} tugas</span>
</div>
</div>
<div class="table-responsive">
<table class="tugas-table">
<thead>
<tr>
<th>Judul Tugas</th>
<th>Mata Pelajaran</th>
<th>Deadline</th>
<th>Waktu Kumpul</th>
<th class="center">Nilai</th>
<th class="center">Status</th>
<th class="center">Aksi</th>
</tr>
</thead>
<tbody>
@forelse($tugas as $t)
<tr>
<td><span class="judul-cell">{{ $t->judul }}</span></td>
<td>
<span class="mapel-pill">{{ $t->mapel->nama_mapel ?? '—' }}</span>
</td>
<td>
<span class="deadline-chip">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
{{ \Carbon\Carbon::parse($t->deadline)->format('d M Y H:i') }}
</span>
</td>
<td>
@if($t->waktu_kumpul)
<span class="kumpul-chip">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
</svg>
{{ \Carbon\Carbon::parse($t->waktu_kumpul)->format('d M Y H:i') }}
</span>
@else
<span class="dash-text">—</span>
@endif
</td>
<td class="center">
@if($t->nilai)
<span class="nilai-chip">{{ $t->nilai }}</span>
@else
<span class="dash-text">—</span>
@endif
</td>
<td class="center">
@if($t->status == 'tepat')
<span class="badge-status badge-tepat">Tepat</span>
@elseif($t->status == 'telat')
<span class="badge-status badge-telat">Telat</span>
@elseif($t->status == 'lewat')
<span class="badge-status badge-belum">Belum</span>
@else
<span class="badge-status badge-default">Belum</span>
@endif
</td>
<td class="center">
<div class="action-group">
@if($t->file)
<a href="{{ route('guru.tugas.download', $t->id) }}" class="btn-dl">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
Unduh
</a>
@endif
@if(!$t->waktu_kumpul && now()->lt($t->deadline))
<a href="{{ route('siswa.tugas.kumpul', $t->id) }}" class="btn-kumpul">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l4-4m0 0l4 4m-4-4v12"/>
</svg>
Kumpulkan
</a>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7">
<div class="empty-state">
<div class="empty-icon">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
</svg>
</div>
<div class="empty-title">Belum Ada Tugas</div>
<div class="empty-desc">Tugas yang diberikan guru akan muncul di sini.</div>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($tugas->count() > 0)
<div class="table-footer">
<span>Menampilkan {{ $tugas->count() }} tugas</span>
<span>{{ now()->format('d F Y') }}</span>
</div>
@endif
</div>
</div>
@endsection

View File

@ -1,21 +1,392 @@
@extends('layouts.app')
@section('content')
<div class="container">
<h4>Kumpulkan Tugas</h4>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
<form action="{{ route('siswa.tugas.store', $tugas->id) }}" method="POST" enctype="multipart/form-data">
@csrf
:root {
--primary: #4F46E5;
--primary-dark: #3730A3;
--primary-light: #EEF2FF;
--success: #059669;
--success-light: #ECFDF5;
--warning: #D97706;
--warning-light: #FFFBEB;
--danger: #DC2626;
--danger-light: #FEF2F2;
--neutral-light: #F9FAFB;
--bg: #F3F4F8;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
--radius-md: 10px;
--radius-lg: 16px;
}
<div class="mb-3">
<label>Upload Jawaban</label>
<input type="file" name="file" class="form-control" required>
* { font-family: 'Plus Jakarta Sans', sans-serif; box-sizing: border-box; }
.sw-page {
padding: 28px 32px;
background: var(--bg);
min-height: 100vh;
}
/* ── TOPBAR ── */
.sw-topbar { margin-bottom: 28px; }
.sw-topbar h3 { font-size: 21px; font-weight: 800; color: var(--text-dark); margin: 0 0 4px; letter-spacing: -.3px; }
.sw-topbar p { font-size: 13px; color: var(--text-soft); margin: 0; }
/* ── LAYOUT ── */
.sw-layout {
display: grid;
grid-template-columns: 1fr 360px;
gap: 24px;
align-items: start;
}
/* ── CARD ── */
.sw-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,.05);
overflow: hidden;
}
/* ── CARD HEADER ── */
.sw-card-banner {
height: 8px;
background: linear-gradient(90deg, #4F46E5, #7C3AED, #059669);
}
.sw-card-hd {
padding: 24px 28px 20px;
border-bottom: 1px solid var(--border);
}
.sw-card-hd-top {
display: flex; align-items: flex-start; gap: 14px;
margin-bottom: 14px;
}
.sw-card-hd-icon {
width: 48px; height: 48px; border-radius: 12px;
background: var(--primary); color: #fff;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
box-shadow: 0 4px 12px rgba(79,70,229,.3);
}
.sw-card-hd-title {
font-size: 19px; font-weight: 800;
color: var(--text-dark); margin: 0 0 5px;
letter-spacing: -.3px; line-height: 1.3;
}
.sw-card-hd-mapel {
display: inline-flex; align-items: center; gap: 6px;
background: var(--primary-light); border: 1px solid #C7D2FE;
border-radius: 99px; padding: 4px 12px;
font-size: 12px; font-weight: 600; color: var(--primary);
}
/* meta row */
.sw-meta-row {
display: flex; gap: 8px; flex-wrap: wrap;
}
.sw-meta-chip {
display: inline-flex; align-items: center; gap: 5px;
background: var(--neutral-light); border: 1px solid var(--border);
border-radius: 8px; padding: 5px 12px;
font-size: 12px; font-weight: 600; color: var(--text-mid);
}
.sw-meta-chip svg { color: var(--text-soft); }
/* ── CARD BODY ── */
.sw-card-bd { padding: 28px; }
/* ── SECTION ── */
.sw-section {
display: flex; align-items: center; gap: 10px;
margin-bottom: 16px;
}
.sw-section-label {
font-size: 11.5px; font-weight: 700; color: var(--primary);
text-transform: uppercase; letter-spacing: .8px; white-space: nowrap;
}
.sw-section-line {
flex: 1; height: 1px;
background: linear-gradient(to right, #C7D2FE, transparent);
}
/* ── UPLOAD ZONE ── */
.sw-upload-zone {
border: 2px dashed #C7D2FE;
border-radius: var(--radius-lg);
padding: 40px 28px;
text-align: center;
background: var(--primary-light);
cursor: pointer;
transition: all .2s;
position: relative;
margin-bottom: 16px;
}
.sw-upload-zone:hover, .sw-upload-zone.dragover {
border-color: var(--primary);
background: #E0E7FF;
}
.sw-upload-zone input[type="file"] {
position: absolute; inset: 0;
opacity: 0; cursor: pointer;
width: 100%; height: 100%;
}
.sw-upload-icon {
width: 64px; height: 64px;
background: var(--surface);
border: 1.5px solid #C7D2FE;
border-radius: 16px;
display: flex; align-items: center; justify-content: center;
margin: 0 auto 16px;
color: var(--primary);
box-shadow: 0 2px 8px rgba(79,70,229,.1);
}
.sw-upload-title { font-size: 15px; font-weight: 700; color: var(--text-dark); margin-bottom: 6px; }
.sw-upload-sub { font-size: 13px; color: var(--text-soft); margin-bottom: 14px; }
.sw-upload-formats {
display: flex; justify-content: center; flex-wrap: wrap; gap: 6px;
}
.sw-format-chip {
background: var(--surface); border: 1px solid #C7D2FE;
border-radius: 6px; padding: 3px 10px;
font-size: 11px; font-weight: 700; color: var(--primary);
}
/* ── FILE SELECTED STATE ── */
.sw-file-selected {
display: none;
align-items: center; gap: 14px;
background: var(--success-light);
border: 1.5px solid #A7F3D0;
border-radius: var(--radius-md);
padding: 14px 18px;
margin-bottom: 16px;
}
.sw-file-selected.show { display: flex; }
.sw-file-icon {
width: 42px; height: 42px; border-radius: 10px;
background: var(--success); color: #fff;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.sw-file-name { font-size: 13.5px; font-weight: 700; color: var(--success); word-break: break-all; }
.sw-file-size { font-size: 12px; color: #065F46; margin-top: 2px; }
/* ── SUBMIT BUTTON ── */
.sw-btn-submit {
width: 100%; padding: 15px;
border-radius: var(--radius-md); border: none;
background: linear-gradient(135deg, var(--primary), #7C3AED);
color: #fff; font-size: 15px; font-weight: 700;
font-family: 'Plus Jakarta Sans', sans-serif;
cursor: pointer; transition: all .2s;
box-shadow: 0 4px 16px rgba(79,70,229,.35);
display: flex; align-items: center; justify-content: center; gap: 9px;
}
.sw-btn-submit:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(79,70,229,.4); }
/* ── SIDEBAR CARD ── */
.sw-sidebar { display: flex; flex-direction: column; gap: 16px; }
.sw-info-card { padding: 20px; }
.sw-info-card-title {
display: flex; align-items: center; gap: 8px;
font-size: 13px; font-weight: 700; color: var(--text-dark);
margin-bottom: 14px;
}
.sw-info-card-title svg { color: var(--primary); }
.sw-tip-list { display: flex; flex-direction: column; gap: 10px; }
.sw-tip {
display: flex; align-items: flex-start; gap: 10px;
font-size: 12.5px; color: var(--text-mid); line-height: 1.5;
}
.sw-tip-dot {
width: 20px; height: 20px; border-radius: 50%;
background: var(--primary-light);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; margin-top: 1px;
font-size: 10px; font-weight: 800; color: var(--primary);
}
/* warning box */
.sw-warn-box {
background: var(--warning-light);
border: 1px solid #FDE68A;
border-radius: var(--radius-md);
padding: 14px 16px;
display: flex; gap: 10px; align-items: flex-start;
font-size: 12.5px; color: var(--warning); line-height: 1.6;
}
.sw-warn-box svg { flex-shrink: 0; margin-top: 1px; }
/* ── RESPONSIVE ── */
@media (max-width: 900px) {
.sw-layout { grid-template-columns: 1fr; }
.sw-page { padding: 16px; }
.sw-card-bd { padding: 20px; }
.sw-card-hd { padding: 18px 20px 16px; }
.sw-upload-zone { padding: 28px 16px; }
}
</style>
<div class="sw-page">
{{-- TOPBAR --}}
<div class="sw-topbar">
<h3>Kumpulkan Tugas</h3>
<p>Upload jawaban tugas Anda sebelum batas waktu</p>
</div>
<div class="sw-layout">
{{-- ── MAIN CARD ── --}}
<div class="sw-card">
<div class="sw-card-banner"></div>
<div class="sw-card-hd">
<div class="sw-card-hd-top">
<div class="sw-card-hd-icon">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
</div>
<div>
<div class="sw-card-hd-title">{{ $tugas->judul }}</div>
<div class="sw-card-hd-mapel">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
{{ $tugas->mapel->nama_mapel ?? '-' }}
</div>
</div>
</div>
<div class="sw-meta-row">
<div class="sw-meta-chip">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
Tugas
</div>
<div class="sw-meta-chip">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
Upload File
</div>
</div>
</div>
<div class="sw-card-bd">
<form action="{{ route('siswa.tugas.store', $tugas->id) }}" method="POST" enctype="multipart/form-data" id="sw-form">
@csrf
<div class="sw-section">
<span class="sw-section-label">Upload Jawaban</span>
<div class="sw-section-line"></div>
</div>
{{-- File selected preview --}}
<div class="sw-file-selected" id="sw-file-preview">
<div class="sw-file-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.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>
</div>
<div>
<div class="sw-file-name" id="sw-file-name">-</div>
<div class="sw-file-size" id="sw-file-size">-</div>
</div>
</div>
{{-- Upload Zone --}}
<div class="sw-upload-zone" id="sw-upload-zone">
<input type="file" name="file" required id="sw-file-input">
<div class="sw-upload-icon">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
</div>
<div class="sw-upload-title">Klik untuk pilih file</div>
<div class="sw-upload-sub">atau seret & lepas file jawaban ke sini</div>
<div class="sw-upload-formats">
<span class="sw-format-chip">PDF</span>
<span class="sw-format-chip">DOC</span>
<span class="sw-format-chip">DOCX</span>
<span class="sw-format-chip">JPG</span>
<span class="sw-format-chip">PNG</span>
<span class="sw-format-chip">ZIP</span>
</div>
</div>
<button type="submit" class="sw-btn-submit">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.3"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
Upload Tugas Sekarang
</button>
</form>
</div>
</div>
<button class="btn btn-success">Upload</button>
{{-- ── SIDEBAR ── --}}
<div class="sw-sidebar">
</form>
{{-- Tips --}}
<div class="sw-card sw-info-card">
<div class="sw-info-card-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
Panduan Upload
</div>
<div class="sw-tip-list">
<div class="sw-tip">
<div class="sw-tip-dot">1</div>
Pastikan file jawaban sudah lengkap sebelum diupload
</div>
<div class="sw-tip">
<div class="sw-tip-dot">2</div>
Gunakan format PDF untuk hasil terbaik
</div>
<div class="sw-tip">
<div class="sw-tip-dot">3</div>
Beri nama file dengan nama lengkap Anda
</div>
<div class="sw-tip">
<div class="sw-tip-dot">4</div>
Pastikan koneksi internet stabil saat upload
</div>
</div>
</div>
{{-- Warning --}}
<div class="sw-warn-box">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<div>
<strong>Perhatian!</strong><br>
Tugas yang sudah dikumpulkan tidak dapat diubah. Pastikan file sudah benar sebelum submit.
</div>
</div>
</div>
</div>
</div>
<script>
const fileInput = document.getElementById('sw-file-input');
const preview = document.getElementById('sw-file-preview');
const nameEl = document.getElementById('sw-file-name');
const sizeEl = document.getElementById('sw-file-size');
const zone = document.getElementById('sw-upload-zone');
fileInput.addEventListener('change', function () {
const file = this.files[0];
if (file) {
nameEl.textContent = file.name;
sizeEl.textContent = (file.size / 1024 / 1024).toFixed(2) + ' MB';
preview.classList.add('show');
}
});
// Drag & drop visual
zone.addEventListener('dragover', e => { e.preventDefault(); zone.classList.add('dragover'); });
zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
zone.addEventListener('drop', e => { e.preventDefault(); zone.classList.remove('dragover'); });
</script>
@endsection

View File

@ -2,15 +2,254 @@
@section('content')
<h2>Hasil Ujian</h2>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
<div class="card p-4">
<h3>Nilai: {{ $nilai }}</h3>
<p>Benar: {{ $benar }} / {{ $total }}</p>
:root {
--navy: #0F1A2E;
--navy-light: #1E3050;
--accent: #3B6FE8;
--success: #059669;
--success-light: #ECFDF5;
--warning: #D97706;
--warning-light: #FFFBEB;
--danger: #DC2626;
--danger-light: #FEF2F2;
--bg: #F0F4FA;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-soft: #6B7280;
}
<a href="{{ route('siswa.dashboard') }}" class="btn btn-primary">
Kembali ke Dashboard
</a>
* { 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; }
/* ── PAGE ── */
.hasil-wrapper {
min-height: calc(100vh - 70px);
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
}
/* ── CARD ── */
.result-card {
background: var(--surface);
border-radius: 24px;
box-shadow: 0 8px 40px rgba(15,26,46,.12);
width: 100%;
max-width: 480px;
overflow: hidden;
}
/* ── CARD TOP (navy) ── */
.card-top {
background: linear-gradient(135deg, var(--navy) 0%, var(--navy-light) 100%);
padding: 32px 32px 28px;
text-align: center;
position: relative;
overflow: hidden;
}
.card-top::before {
content: '';
position: absolute;
width: 200px; height: 200px;
border-radius: 50%;
background: rgba(255,255,255,.05);
top: -60px; right: -60px;
}
.card-top::after {
content: '';
position: absolute;
width: 120px; height: 120px;
border-radius: 50%;
background: rgba(255,255,255,.04);
bottom: -40px; left: -30px;
}
/* ── SCORE RING ── */
.score-ring {
position: relative; z-index: 2;
width: 110px; height: 110px;
border-radius: 50%;
background: rgba(255,255,255,.1);
border: 3px solid rgba(255,255,255,.2);
display: flex; flex-direction: column;
align-items: center; justify-content: center;
margin: 0 auto 16px;
}
.score-num {
font-size: 36px; font-weight: 800;
color: #fff; line-height: 1;
}
.score-sub {
font-size: 11px; font-weight: 600;
color: rgba(255,255,255,.5);
text-transform: uppercase; letter-spacing: .4px;
}
.card-top h3 {
position: relative; z-index: 2;
font-size: 16px; font-weight: 700;
color: rgba(255,255,255,.85); margin: 0;
}
/* ── CARD BODY ── */
.card-body { padding: 28px 32px; }
/* ── STAT ROW ── */
.stat-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px; margin-bottom: 22px;
}
.stat-item {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 12px;
padding: 14px 16px; text-align: center;
}
.stat-val {
font-size: 22px; font-weight: 800;
color: var(--text-dark); margin-bottom: 2px;
}
.stat-lbl {
font-size: 11.5px; font-weight: 600;
color: var(--text-soft);
text-transform: uppercase; letter-spacing: .3px;
}
/* ── PROGRESS ── */
.progress-wrap { margin-bottom: 20px; }
.progress-head {
display: flex; justify-content: space-between;
align-items: center; margin-bottom: 8px;
}
.progress-title { font-size: 12.5px; font-weight: 600; color: var(--text-soft); }
.progress-pct { font-size: 12.5px; font-weight: 700; color: var(--accent); }
.progress-track {
height: 10px; background: #F1F5F9;
border-radius: 99px; overflow: hidden;
}
.progress-fill {
height: 100%; border-radius: 99px;
background: linear-gradient(90deg, var(--accent), var(--success));
}
/* ── FEEDBACK ── */
.feedback-badge {
display: flex; align-items: center; gap: 10px;
border-radius: 12px; padding: 13px 16px;
margin-bottom: 22px;
font-size: 13.5px; font-weight: 700;
}
.fb-success { background: var(--success-light); color: var(--success); border: 1px solid #A7F3D0; }
.fb-warning { background: var(--warning-light); color: var(--warning); border: 1px solid #FDE68A; }
.fb-danger { background: var(--danger-light); color: var(--danger); border: 1px solid #FECACA; }
/* ── BUTTON ── */
.btn-home {
display: flex; align-items: center;
justify-content: center; gap: 9px;
width: 100%; background: var(--navy); color: #fff;
border: none; border-radius: 13px; padding: 14px;
font-size: 14.5px; font-weight: 800;
text-decoration: none; transition: all .18s;
font-family: 'Plus Jakarta Sans', sans-serif;
box-shadow: 0 4px 16px rgba(15,26,46,.2);
}
.btn-home:hover {
background: var(--navy-light); color: #fff;
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(15,26,46,.3);
text-decoration: none;
}
@media (max-width: 480px) {
.card-top { padding: 24px 20px 20px; }
.card-body { padding: 22px 20px; }
.score-num { font-size: 30px; }
}
</style>
<div class="hasil-wrapper">
<div class="result-card">
{{-- TOP (navy) --}}
<div class="card-top">
<div class="score-ring">
<div class="score-num">{{ $nilai }}</div>
<div class="score-sub">Nilai</div>
</div>
<h3>Hasil Ujian — {{ $ujian->judul ?? 'Ujian' }}</h3>
</div>
{{-- BODY --}}
<div class="card-body">
{{-- STAT --}}
<div class="stat-row">
<div class="stat-item">
<div class="stat-val" style="color:var(--success);">{{ $benar }}</div>
<div class="stat-lbl">Benar</div>
</div>
<div class="stat-item">
<div class="stat-val" style="color:var(--danger);">{{ $total - $benar }}</div>
<div class="stat-lbl">Salah</div>
</div>
</div>
{{-- PROGRESS --}}
<div class="progress-wrap">
<div class="progress-head">
<span class="progress-title">Jawaban benar {{ $benar }} dari {{ $total }} soal</span>
<span class="progress-pct">{{ round(($benar / $total) * 100) }}%</span>
</div>
<div class="progress-track">
<div class="progress-fill"
style="width: {{ ($benar / $total) * 100 }}%">
</div>
</div>
</div>
{{-- FEEDBACK --}}
@if($nilai >= 80)
<div class="feedback-badge fb-success">
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
<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>
Sangat Baik! Pertahankan prestasi ini.
</div>
@elseif($nilai >= 60)
<div class="feedback-badge fb-warning">
<svg width="18" height="18" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2">
<path stroke-linecap="round" stroke-linejoin="round" d="M14 10h.01M12 14h.01M10 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Cukup Baik. Masih bisa ditingkatkan!
</div>
@else
<div class="feedback-badge fb-danger">
<svg width="18" height="18" 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>
Perlu Belajar Lagi. Jangan menyerah!
</div>
@endif
{{-- BUTTON --}}
<a href="{{ route('siswa.dashboard') }}" class="btn-home">
<svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</svg>
Kembali ke Dashboard
</a>
</div>
</div>
</div>
@endsection

View File

@ -2,17 +2,219 @@
@section('content')
<h2>Ujian Tersedia</h2>
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
@foreach($ujians as $u)
<div class="card mb-2 p-3">
<h5>{{ $u->judul }}</h5>
<p>Durasi: {{ $u->durasi }} menit</p>
: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;
}
<a href="{{ route('siswa.ujian.kerjakan', $u->id) }}" class="btn btn-primary">
Kerjakan
</a>
* { 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); }
.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); }
.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; }
.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-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>
<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>
@endforeach
{{-- 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 --}}
@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
</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>
@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

@ -2,114 +2,337 @@
@section('content')
<div class="container-fluid">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
<h3 class="mb-3">{{ $ujian->judul }}</h3>
:root {
--navy: #0F1A2E;
--navy-mid: #162236;
--navy-light: #1E3050;
--accent: #3B6FE8;
--success: #059669;
--success-light: #ECFDF5;
--bg: #F0F4FA;
--surface: #FFFFFF;
--border: #E5E7EB;
--text-dark: #111827;
--text-mid: #374151;
--text-soft: #6B7280;
}
{{-- TIMER --}}
<div class="alert alert-info">
⏱ Sisa waktu: <b id="timer"></b>
* { 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: 22px 28px; background: var(--bg); min-height: calc(100vh - 70px); }
/* ── HEADER ── */
.ujian-header {
display: flex; align-items: flex-start;
justify-content: space-between;
gap: 16px; flex-wrap: wrap; margin-bottom: 22px;
}
.ujian-eyebrow {
display: inline-flex; align-items: center; gap: 6px;
background: rgba(59,111,232,.1); border: 1px solid rgba(59,111,232,.2);
border-radius: 99px; padding: 4px 12px;
font-size: 11.5px; font-weight: 700; color: var(--accent);
letter-spacing: .4px; text-transform: uppercase; margin-bottom: 8px;
}
.ujian-title {
font-size: 20px; font-weight: 800;
color: var(--text-dark); margin: 0; letter-spacing: -.3px;
}
/* ── TIMER ── */
.timer-wrap {
display: flex; align-items: center; gap: 12px;
background: var(--navy); border-radius: 14px;
padding: 12px 20px; box-shadow: 0 4px 16px rgba(15,26,46,.25);
flex-shrink: 0;
}
.timer-label {
font-size: 11px; font-weight: 600;
color: rgba(255,255,255,.5); text-transform: uppercase;
letter-spacing: .5px; margin-bottom: 2px;
}
.timer-value {
font-size: 22px; font-weight: 800;
color: #fff; letter-spacing: 2px;
font-variant-numeric: tabular-nums;
}
#timer-dot {
width: 8px; height: 8px; border-radius: 50%;
background: #22C55E; flex-shrink: 0;
animation: timerPulse 1.2s ease infinite;
}
@keyframes timerPulse {
0%,100% { opacity: 1; transform: scale(1); }
50% { opacity: .4; transform: scale(.6); }
}
/* ── LAYOUT ── */
.ujian-layout {
display: grid;
grid-template-columns: 1fr 260px;
gap: 20px;
align-items: start;
}
/* ── SOAL CARD ── */
.soal-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 18px;
overflow: hidden;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,.04);
}
.soal-head {
display: flex; align-items: center; gap: 10px;
padding: 14px 20px;
background: linear-gradient(135deg, var(--navy) 0%, var(--navy-light) 100%);
border-bottom: 1px solid rgba(255,255,255,.06);
}
.soal-num {
width: 30px; height: 30px; border-radius: 8px;
background: var(--accent);
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 800; color: #fff; flex-shrink: 0;
}
.soal-head-label {
font-size: 13px; font-weight: 600;
color: rgba(255,255,255,.75);
}
.soal-body { padding: 22px 20px; }
.pertanyaan {
font-size: 14.5px; font-weight: 600;
color: var(--text-dark); line-height: 1.75; margin-bottom: 18px;
}
/* ── OPSI ── */
.opsi-item { display: block; cursor: pointer; margin-bottom: 10px; }
.opsi-item input { display: none; }
.opsi-inner {
display: flex; align-items: center; gap: 12px;
background: #F8FAFC; border: 1.5px solid var(--border);
border-radius: 11px; padding: 11px 15px; transition: all .15s;
}
.opsi-item:hover .opsi-inner {
border-color: var(--accent); background: #EEF4FF;
}
.opsi-item input:checked + .opsi-inner {
background: rgba(59,111,232,.07);
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(59,111,232,.1);
}
.opsi-key {
width: 28px; height: 28px; border-radius: 7px;
background: var(--border);
display: flex; align-items: center; justify-content: center;
font-size: 12px; font-weight: 800; color: var(--text-soft);
flex-shrink: 0; transition: all .15s;
}
.opsi-item input:checked + .opsi-inner .opsi-key {
background: var(--accent); color: #fff;
}
.opsi-text {
font-size: 13.5px; font-weight: 500; color: var(--text-mid);
}
.opsi-item input:checked + .opsi-inner .opsi-text {
color: var(--accent); font-weight: 600;
}
/* ── SUBMIT ── */
.submit-wrap {
display: flex; justify-content: flex-end; padding-bottom: 32px;
}
.btn-finish {
display: inline-flex; align-items: center; gap: 9px;
background: var(--navy); color: #fff; border: none;
border-radius: 13px; padding: 13px 30px;
font-size: 14.5px; font-weight: 800; cursor: pointer;
transition: all .18s; font-family: 'Plus Jakarta Sans', sans-serif;
box-shadow: 0 4px 16px rgba(15,26,46,.25);
}
.btn-finish:hover {
background: var(--navy-light);
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(15,26,46,.35);
}
/* ── NAVIGASI SOAL ── */
.nav-card {
background: var(--navy);
border-radius: 18px; padding: 18px;
box-shadow: 0 4px 20px rgba(15,26,46,.25);
position: sticky; top: 90px;
}
.nav-title {
display: flex; align-items: center; gap: 8px;
font-size: 11.5px; font-weight: 700;
color: rgba(255,255,255,.55);
text-transform: uppercase; letter-spacing: .5px;
margin-bottom: 14px;
}
.nav-grid { display: flex; flex-wrap: wrap; gap: 7px; }
.nav-number {
width: 38px; height: 38px; border-radius: 9px;
border: 1.5px solid rgba(255,255,255,.12);
display: flex; align-items: center; justify-content: center;
text-decoration: none; color: rgba(255,255,255,.7);
font-size: 12.5px; font-weight: 700; transition: all .15s;
background: rgba(255,255,255,.05);
}
.nav-number:hover {
background: var(--accent); border-color: var(--accent); color: #fff;
}
.nav-number.answered {
background: var(--success); border-color: var(--success); color: #fff;
}
.nav-legend {
display: flex; gap: 12px; margin-top: 14px; flex-wrap: wrap;
}
.nav-leg-item {
display: flex; align-items: center; gap: 6px;
font-size: 11px; font-weight: 600; color: rgba(255,255,255,.45);
}
.nav-leg-dot { width: 10px; height: 10px; border-radius: 3px; }
@media (max-width: 992px) {
.ujian-layout { grid-template-columns: 1fr; }
.nav-card { position: relative; top: 0; }
.sw-page { padding: 16px; }
}
</style>
<div class="sw-page">
{{-- HEADER --}}
<div class="ujian-header">
<div>
<div class="ujian-eyebrow">
<svg width="11" height="11" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.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"/>
</svg>
Sedang Berlangsung
</div>
<h2 class="ujian-title">{{ $ujian->judul }}</h2>
</div>
<div class="timer-wrap">
<div>
<div class="timer-label">Sisa Waktu</div>
<div class="timer-value"><span id="timer"></span></div>
</div>
<div id="timer-dot"></div>
</div>
</div>
<form method="POST" action="{{ route('siswa.ujian.submit', $ujian->id) }}">
@csrf
<div class="row">
{{-- ================= KIRI: SOAL ================= --}}
<div class="col-md-9">
<div class="ujian-layout">
{{-- SOAL --}}
<div>
@foreach($soals as $i => $s)
<div class="card mb-3 p-3" id="soal{{ $s->id }}">
<b>Soal {{ $loop->iteration }}</b><br>
{{ $s->pertanyaan }}
<hr>
<label>
<input type="radio" name="jawaban[{{ $s->id }}]" value="a">
{{ $s->a }}
</label><br>
<label>
<input type="radio" name="jawaban[{{ $s->id }}]" value="b">
{{ $s->b }}
</label><br>
<label>
<input type="radio" name="jawaban[{{ $s->id }}]" value="c">
{{ $s->c }}
</label><br>
<label>
<input type="radio" name="jawaban[{{ $s->id }}]" value="d">
{{ $s->d }}
</label><br>
<div class="soal-card" id="soal{{ $s->id }}">
<div class="soal-head">
<div class="soal-num">{{ $loop->iteration }}</div>
<div class="soal-head-label">Soal Nomor {{ $loop->iteration }}</div>
</div>
<div class="soal-body">
<p class="pertanyaan">{{ $s->pertanyaan }}</p>
<div>
@foreach(['a','b','c','d'] as $opt)
<label class="opsi-item">
<input type="radio"
name="jawaban[{{ $s->id }}]"
value="{{ $opt }}">
<div class="opsi-inner">
<div class="opsi-key">{{ strtoupper($opt) }}</div>
<div class="opsi-text">{{ $s->$opt }}</div>
</div>
</label>
@endforeach
</div>
</div>
</div>
@endforeach
{{-- BUTTON FINISH --}}
<div class="text-end mb-5">
<button class="btn btn-success btn-lg">
✅ Finish Ujian
<div class="submit-wrap">
<button type="submit" class="btn-finish"
onclick="return confirm('Yakin ingin mengumpulkan ujian?')">
<svg width="16" height="16" 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>
Selesai & Kumpulkan Ujian
</button>
</div>
</div>
{{-- ================= KANAN: DAFTAR SOAL ================= --}}
<div class="col-md-3">
<div class="card p-3">
<b>Daftar Soal</b>
<div class="d-flex flex-wrap mt-2">
@foreach($soals as $i => $s)
<a href="#soal{{ $s->id }}"
class="btn btn-sm btn-outline-primary m-1">
{{ $loop->iteration }}
</a>
@endforeach
</div>
{{-- NAVIGASI SOAL --}}
<div class="nav-card">
<div class="nav-title">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
</svg>
Navigasi Soal
</div>
<div class="nav-grid">
@foreach($soals as $s)
<a href="#soal{{ $s->id }}"
class="nav-number"
id="nav{{ $s->id }}">
{{ $loop->iteration }}
</a>
@endforeach
</div>
<div class="nav-legend">
<div class="nav-leg-item">
<div class="nav-leg-dot" style="background:#059669;"></div>
Sudah dijawab
</div>
<div class="nav-leg-item">
<div class="nav-leg-dot" style="background:rgba(255,255,255,.12); border:1px solid rgba(255,255,255,.2);"></div>
Belum dijawab
</div>
</div>
</div>
</div>
</form>
</div>
{{-- ================= TIMER SCRIPT ================= --}}
<script>
// TIMER
let waktu = {{ $ujian->durasi * 60 }};
let timer = setInterval(function() {
let timerInterval = setInterval(function () {
let menit = Math.floor(waktu / 60);
let detik = waktu % 60;
let detik = waktu % 60;
document.getElementById('timer').innerHTML =
menit + " : " + (detik < 10 ? "0" + detik : detik);
waktu--;
if (waktu < 0) {
clearInterval(timer);
clearInterval(timerInterval);
alert("Waktu habis!");
document.querySelector('form').submit();
}
}, 1000);
// AUTO HIGHLIGHT NAV
document.querySelectorAll('input[type=radio]').forEach(el => {
el.addEventListener('change', function () {
let soalId = this.name.match(/\[(\d+)\]/)[1];
let navEl = document.getElementById('nav' + soalId);
navEl.classList.add('answered');
});
});
</script>
@endsection

View File

@ -2,6 +2,7 @@
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\AuthController;
@ -16,13 +17,14 @@
// ================= GURU =================
use App\Http\Controllers\Guru\DashboardController as GuruDashboard;
use App\Http\Controllers\Guru\DashboardController;
use App\Http\Controllers\Guru\JadwalController as GuruJadwal;
use App\Http\Controllers\Guru\AbsensiController;
use App\Http\Controllers\Guru\NilaiController as GuruNilai;
use App\Http\Controllers\Guru\TugasController;
use App\Models\Jadwal;
use App\Http\Controllers\Guru\MapelController;
use App\Http\Controllers\Guru\UjianController as GuruUjian;
use App\Models\Jadwal;
// ================= SISWA =================
use App\Http\Controllers\Siswa\DashboardController as SiswaDashboard;
@ -30,58 +32,54 @@
use App\Http\Controllers\Siswa\AbsensiController as SiswaAbsensi;
use App\Http\Controllers\Siswa\NilaiController as SiswaNilai;
use App\Http\Controllers\Siswa\ProfileController as SiswaProfile;
use App\Http\Controllers\Siswa\AbsensiController as SiswaAbsensiController;
//=================otp=================
use App\Http\Controllers\Siswa\TugasController as SiswaTugasController;
use App\Http\Controllers\Siswa\UjianController as SiswaUjian;
// ================= AUTH TAMBAHAN =================
use App\Http\Controllers\Auth\OtpController;
use Illuminate\Support\Facades\Password;
use App\Http\Controllers\Auth\ForgotPasswordController;
use App\Http\Controllers\Auth\ResetPasswordController;
use App\Http\Controllers\Siswa\UjianController;
use App\Http\Controllers\Guru\UjianController as GuruUjian;
use App\Http\Controllers\Siswa\UjianController as SiswaUjian;
/*
|----------------------------------------
|--------------------------------------------------------------------------
| ROOT
|----------------------------------------
|--------------------------------------------------------------------------
*/
Route::get('/', function () {
return redirect()->route('login');
});
/*
|----------------------------------------
| LOGIN
|----------------------------------------
|--------------------------------------------------------------------------
| LOGIN & LOGOUT
|--------------------------------------------------------------------------
*/
Route::get('/login', [AuthController::class, 'showLogin'])->name('login');
Route::post('/login', [AuthController::class, 'login'])->name('login.process');
/*
|----------------------------------------
| LOGOUT
|----------------------------------------
*/
Route::post('/logout', function () {
Auth::logout();
request()->session()->invalidate();
request()->session()->regenerateToken();
return redirect()->route('login');
})->name('logout');
/*
|----------------------------------------
| FORCE PASSWORD
|----------------------------------------
|--------------------------------------------------------------------------
| OTP LOGIN (Langkah 1 & 2 di halaman login)
| — sendOtp : kirim OTP ke email SAJA atau WhatsApp SAJA (tidak keduanya)
| — loginOtp : login dengan email + password + kode OTP
|--------------------------------------------------------------------------
*/
Route::post('/send-otp', [OtpController::class, 'sendOtp'])->name('send.otp');
Route::post('/login-otp', [OtpController::class, 'loginOtp'])->name('login.otp');
/*
|--------------------------------------------------------------------------
| FORCE PASSWORD (ganti password default setelah login pertama)
|--------------------------------------------------------------------------
*/
Route::middleware('auth')->group(function () {
Route::get('/force-password', function () {
return view('auth.force-password');
})->name('force.password');
@ -91,18 +89,43 @@
});
/*
|----------------------------------------
|--------------------------------------------------------------------------
| LUPA PASSWORD — via OTP (flow: kirim OTP → verify → reset)
|--------------------------------------------------------------------------
*/
// Langkah 1: Form kirim OTP lupa password
Route::get('/forgot-password', function () {
return view('auth.forgot-password');
})->name('password.request');
Route::post('/forgot-password', [AuthController::class, 'sendOtp'])
->name('password.email');
// Langkah 2: Verify OTP
Route::get('/verify-otp', [AuthController::class, 'showVerifyOtp'])
->name('verify.otp.form');
Route::post('/verify-otp', [AuthController::class, 'verifyOtp'])
->name('verify.otp');
// Langkah 3: Reset password baru
Route::get('/reset-password', [AuthController::class, 'showResetPassword'])
->name('reset.password.form');
Route::post('/reset-password', [AuthController::class, 'resetPassword'])
->name('password.update');
/*
|--------------------------------------------------------------------------
| ADMIN
|----------------------------------------
|--------------------------------------------------------------------------
*/
Route::middleware(['auth', 'force.password'])
->prefix('admin')
->name('admin.')
->group(function () {
// 🔥 /admin langsung ke dashboard
Route::get('/', [AdminDashboard::class, 'index']);
Route::get('/dashboard', [AdminDashboard::class, 'index'])->name('dashboard');
Route::resource('guru', GuruController::class);
@ -111,41 +134,27 @@
Route::resource('kelas', KelasController::class);
Route::resource('jadwal', AdminJadwal::class);
// 🔥 route tambahan
Route::get('jadwal/grid', [AdminJadwal::class, 'grid'])->name('jadwal.grid');
// PROFILE
Route::get('/profile', [ProfileController::class, 'index'])->name('profile');
Route::post('/profile', [ProfileController::class, 'update'])->name('profile.update');
// STATUS
Route::get('/guru/{id}/nonaktif', [GuruController::class, 'nonaktif'])->name('guru.nonaktif');
Route::get('/guru/{id}/aktifkan', [GuruController::class, 'aktifkan'])->name('guru.aktifkan');
// Route::get('/siswa/{id}/nonaktif', [SiswaController::class, 'nonaktif'])->name('siswa.nonaktif');
// Route::post('/nonaktif/{id}', [SiswaController::class, 'nonaktif'])
// ->name('admin.siswa.nonaktif');
// Route::get('/siswa/{id}/aktifkan', [SiswaController::class, 'aktifkan'])->name('siswa.aktifkan');
Route::post('/siswa/bulk/nonaktif', [SiswaController::class, 'bulkNonaktif'])->name('siswa.bulkNonaktif');
});
Route::prefix('admin')->name('admin.')->group(function () {
Route::post('/siswa/nonaktif/{id}', [SiswaController::class, 'nonaktif'])
->name('siswa.nonaktif');
Route::get('/siswa/aktifkan/{id}', [SiswaController::class, 'aktifkan'])
->name('siswa.aktifkan');
// Siswa nonaktif/aktifkan (di luar middleware force.password agar fleksibel)
Route::prefix('admin')->name('admin.')->group(function () {
Route::post('/siswa/nonaktif/{id}', [SiswaController::class, 'nonaktif'])->name('siswa.nonaktif');
Route::get('/siswa/aktifkan/{id}', [SiswaController::class, 'aktifkan'])->name('siswa.aktifkan');
});
/*
|----------------------------------------
|--------------------------------------------------------------------------
| GURU
|----------------------------------------
|--------------------------------------------------------------------------
*/
Route::middleware(['auth', 'force.password'])
->prefix('guru')
@ -157,19 +166,12 @@
Route::get('/jadwal', [GuruJadwal::class, 'index'])->name('jadwal');
// =========================
// ✅ ABSENSI (FINAL)
// =========================
Route::get('/absensi', [AbsensiController::class, 'index'])
->name('absensi');
// ABSENSI
Route::get('/absensi', [AbsensiController::class, 'index'])->name('absensi');
Route::post('/absensi/simpan', [AbsensiController::class, 'simpan'])->name('absensi.simpan');
Route::put('/absensi/update/{id}', [AbsensiController::class, 'update'])->name('absensi.update');
Route::post('/absensi/simpan', [AbsensiController::class, 'simpan'])
->name('absensi.simpan');
// =========================
// NILAI
// =========================
Route::get('/nilai', [GuruNilai::class, 'index'])->name('nilai.index');
Route::get('/nilai/input', [GuruNilai::class, 'inputNilai'])->name('nilai.input');
Route::get('/get-siswa/{id}', [GuruNilai::class, 'getSiswaByKelas'])->name('get.siswa');
@ -177,91 +179,58 @@
Route::get('/nilai/{id}/edit', [GuruNilai::class, 'edit'])->name('nilai.edit');
Route::put('/nilai/{id}', [GuruNilai::class, 'update'])->name('nilai.update');
// =========================
// TUGAS
// =========================
Route::resource('tugas', TugasController::class);
Route::get('/tugas/{id}/download', [TugasController::class, 'download'])->name('tugas.download');
Route::get('/tugas/{id}/pengumpulan', [TugasController::class, 'pengumpulan'])->name('tugas.pengumpulan');
Route::post('/pengumpulan/{id}/nilai', [TugasController::class, 'nilai'])->name('tugas.nilai');
Route::get('/tugas/{id}/download', [TugasController::class, 'download'])
->name('tugas.download');
Route::get('/tugas/{id}/pengumpulan', [TugasController::class, 'pengumpulan'])
->name('tugas.pengumpulan');
Route::post('/pengumpulan/{id}/nilai', [TugasController::class, 'nilai'])
->name('tugas.nilai');
// =========================
// MAPEL
// =========================
Route::get('/get-mapel-by-kelas/{id}', [MapelController::class, 'getByKelas'])
->name('get.mapel');
Route::get('/get-mapel-by-kelas/{id}', [MapelController::class, 'getByKelas'])->name('get.mapel');
// UJIAN
Route::get('/ujian', [GuruUjian::class, 'index'])->name('ujian.index');
Route::get('/ujian/create', [GuruUjian::class, 'create'])->name('ujian.create');
Route::post('/ujian/store', [GuruUjian::class, 'store'])->name('ujian.store');
Route::get('/ujian/{id}/edit', [GuruUjian::class, 'edit'])->name('ujian.edit');
Route::put('/ujian/{id}', [GuruUjian::class, 'update'])->name('ujian.update');
Route::delete('/ujian/{id}', [GuruUjian::class, 'destroy'])->name('ujian.destroy');
Route::get('/ujian/{id}/soal', [GuruUjian::class, 'soal'])->name('ujian.soal');
Route::post('/ujian/{id}/soal', [GuruUjian::class, 'storeSoal'])->name('ujian.storeSoal');
});
// routes/web.php — tambahkan di dalam group guru
//==================otp============
// Route::post('/send-otp', [OtpController::class, 'sendOtp'])->name('send.otp');
// Route::post('/login-otp', [OtpController::class, 'loginOtp'])->name('login.otp');
// Route::post('/send-otp-login', [OtpController::class, 'sendOtp'])
// ->name('send.otp.login');
Route::post('/send-otp', [OtpController::class, 'sendOtp'])->name('send.otp');
Route::post('/login-otp', [OtpController::class, 'loginOtp'])->name('login.otp');
// di dalam group guru
// UJIAN
Route::get('/ujian', [GuruUjian::class, 'index'])->name('ujian.index');
Route::get('/ujian/create', [GuruUjian::class, 'create'])->name('ujian.create');
Route::post('/ujian/store', [GuruUjian::class, 'store'])->name('ujian.store');
Route::get('/ujian/{id}/edit', [GuruUjian::class, 'edit'])->name('ujian.edit');
Route::put('/ujian/{id}', [GuruUjian::class, 'update'])->name('ujian.update');
Route::delete('/ujian/{id}', [GuruUjian::class, 'destroy'])->name('ujian.destroy');
Route::get('/ujian/{id}/soal', [GuruUjian::class, 'soal'])->name('ujian.soal');
Route::post('/ujian/{id}/soal', [GuruUjian::class, 'storeSoal'])->name('ujian.storeSoal');
Route::post('/ujian/{id}/kirim', [GuruUjian::class, 'kirim'])->name('ujian.kirim'); // ✅ TAMBAH DI SINI
// form email
Route::get('/forgot-password', [ForgotPasswordController::class, 'showLinkRequestForm'])
->name('password.request');
// kirim email
Route::post('/forgot-password', [ForgotPasswordController::class, 'sendResetLinkEmail'])
->name('password.email');
// form reset
Route::get('/reset-password/{token}', [ResetPasswordController::class, 'showResetForm'])
->name('password.reset');
// simpan password baru
Route::post('/reset-password', [ResetPasswordController::class, 'reset'])
->name('password.update');
Route::get('/forgot-password', function () {
return view('auth.forgot-password');
})->name('password.request');
Route::post('/forgot-password', [AuthController::class, 'sendOtp'])
->name('password.email');
Route::get('/verify-otp', [AuthController::class, 'showVerifyOtp'])
->name('verify.otp.form');
Route::post('/verify-otp', [AuthController::class, 'verifyOtp'])
->name('verify.otp');
// FORM RESET PASSWORD (GET)
Route::get('/reset-password', [AuthController::class, 'showResetPassword'])
->name('reset.password');
// PROSES RESET PASSWORD (POST)
Route::post('/reset-password', [AuthController::class, 'resetPassword'])
->name('password.update');
Route::post('/reset-password', [AuthController::class, 'resetPassword'])
->name('password.update');
// Guru: cek status middleware tambahan (jika diperlukan)
Route::middleware(['auth', 'cek.status'])->prefix('guru')->name('guru.')->group(function () {
// tambahkan route khusus cek.status di sini jika ada
});
/*
|----------------------------------------
|--------------------------------------------------------------------------
| SISWA
|----------------------------------------
|--------------------------------------------------------------------------
*/
use App\Http\Controllers\Siswa\TugasController as SiswaTugasController;
Route::middleware(['auth', 'force.password'])
->prefix('siswa')
->name('siswa.')
->group(function () {
Route::get('/', [SiswaDashboard::class, 'index']);
Route::get('/dashboard', [SiswaDashboard::class, 'index'])->name('dashboard');
Route::get('/jadwal', [SiswaJadwal::class, 'index'])->name('jadwal');
Route::get('/absensi', [SiswaAbsensi::class, 'index'])->name('absensi');
Route::get('/nilai', [SiswaNilai::class, 'index'])->name('nilai');
@ -269,64 +238,29 @@
Route::get('/profile', [SiswaProfile::class, 'index'])->name('profile');
Route::post('/profile', [SiswaProfile::class, 'update'])->name('profile.update');
// ✅ SCAN QR
Route::get('/absen/{token}', [SiswaAbsensi::class, 'scan'])
->name('absensi.scan');
// QR Absensi
Route::get('/absen/{token}', [SiswaAbsensi::class, 'scan'])->name('absensi.scan');
Route::post('/absen/hadir', [SiswaAbsensi::class, 'hadir'])->name('absensi.hadir');
// ✅ SUBMIT HADIR
Route::post('/absen/hadir', [SiswaAbsensi::class, 'hadir'])
->name('absensi.hadir');
// ================= TUGAS =================
// TUGAS
Route::get('/tugas', [SiswaTugasController::class, 'index'])->name('tugas.index');
Route::get('/tugas/{id}/kumpul', [SiswaTugasController::class, 'kumpul'])->name('tugas.kumpul');
Route::post('/tugas/{id}/kumpul', [SiswaTugasController::class, 'store'])->name('tugas.store');
// UJIAN
Route::get('/ujian', [SiswaUjian::class, 'index'])->name('ujian.index');
Route::get('/ujian/{id}/kerjakan', [SiswaUjian::class, 'kerjakan'])->name('ujian.kerjakan');
Route::post('/ujian/{id}/submit', [SiswaUjian::class, 'submit'])->name('ujian.submit');
});
Route::middleware(['auth'])->group(function () {
Route::prefix('guru')->name('guru.')->group(function () {
Route::get('/ujian', [\App\Http\Controllers\Guru\UjianController::class, 'index'])->name('ujian.index');
Route::get('/ujian/create', [\App\Http\Controllers\Guru\UjianController::class, 'create'])->name('ujian.create');
Route::post('/ujian/store', [\App\Http\Controllers\Guru\UjianController::class, 'store'])->name('ujian.store');
Route::get('/ujian/{id}/soal', [\App\Http\Controllers\Guru\UjianController::class, 'soal'])->name('ujian.soal');
Route::post('/ujian/{id}/soal', [\App\Http\Controllers\Guru\UjianController::class, 'storeSoal'])->name('ujian.storeSoal');
// Route::get('/guru/ujian/{id}/soal', [UjianController::class, 'soal']);
// Route::post('/guru/ujian/{id}/soal', [UjianController::class, 'storeSoal']);
});
});
Route::middleware(['auth'])->prefix('siswa')->group(function () {
Route::get('/ujian', [UjianController::class, 'index'])
->name('siswa.ujian.index');
Route::get('/ujian/{id}/kerjakan', [UjianController::class, 'kerjakan'])
->name('siswa.ujian.kerjakan');
Route::post('/ujian/{id}/submit', [UjianController::class, 'submit'])
->name('siswa.ujian.submit');
});
/*
|----------------------------------------
| TEST EMAIL
|----------------------------------------
|--------------------------------------------------------------------------
| TEST EMAIL (hapus di production)
|--------------------------------------------------------------------------
*/
Route::get('/test-email', function () {
Mail::raw('Tes email berhasil', function ($message) {
$message->to('EMAIL_KAMU@gmail.com')
->subject('Test Email Laravel');
$message->to('EMAIL_KAMU@gmail.com')->subject('Test Email Laravel');
});
return 'Email terkirim!';
});
});